├── .gitignore ├── Dockerfile ├── LICENSE ├── blacklist-originals ├── 183030964134081.jpg ├── 222612955025266.jpg ├── 483791103037622.jpg └── 99581805320818.jpg ├── libpuzzle ├── .env ├── AUTHORS ├── COPYING ├── ChangeLog ├── Dockerfile ├── INSTALL ├── Makefile.am ├── NEWS ├── README ├── README-PHP ├── THANKS ├── autogen.sh ├── configure.ac ├── man │ ├── Makefile.am │ ├── libpuzzle.3 │ ├── puzzle-diff.8 │ └── puzzle_set.3 ├── php │ ├── Makefile.am │ ├── examples │ │ ├── Makefile.am │ │ └── similar │ │ │ ├── Makefile.am │ │ │ ├── config.inc.php │ │ │ ├── schema.pgsql.sql │ │ │ ├── schema.sqlite3.sql │ │ │ ├── similar.inc.php │ │ │ └── similar.php │ └── libpuzzle │ │ ├── CREDITS │ │ ├── EXPERIMENTAL │ │ ├── LICENSE │ │ ├── Makefile.am │ │ ├── README │ │ ├── build │ │ └── Makefile.am │ │ ├── config.m4 │ │ ├── include │ │ └── Makefile.am │ │ ├── libpuzzle.c │ │ ├── libpuzzle.php │ │ ├── modules │ │ └── Makefile.am │ │ ├── php_libpuzzle.h │ │ └── tests │ │ ├── 001.phpt │ │ ├── 002.phpt │ │ ├── 003.phpt │ │ ├── Makefile.am │ │ └── pics │ │ ├── Makefile.am │ │ ├── pic-a-0.jpg │ │ └── pic-a-1.jpg └── src │ ├── Makefile.am │ ├── compress.c │ ├── cvec.c │ ├── dvec.c │ ├── globals.h │ ├── pics │ ├── Makefile.am │ ├── duck.gif │ ├── luxmarket_tshirt01.jpg │ ├── luxmarket_tshirt01_black.jpg │ ├── luxmarket_tshirt01_sal.jpg │ ├── luxmarket_tshirt01_sheum.jpg │ ├── pic-a-0.jpg │ └── pic-a-1.jpg │ ├── puzzle-diff.c │ ├── puzzle.c │ ├── puzzle.h │ ├── puzzle_common.h │ ├── puzzle_p.h │ ├── regress_1.c │ ├── regress_2.c │ ├── regress_3.c │ ├── tunables.c │ └── vector_ops.c ├── readme.md ├── screenshot.gif ├── scripts ├── bulk-search ├── entrypoint.sh └── fake-video └── setup.sh /.gitignore: -------------------------------------------------------------------------------- 1 | blacklist-resized 2 | tmp 3 | *.log 4 | *.txt 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:17.10 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | # Add in required packages to run the script... 6 | RUN apt-get update -q && \ 7 | apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" \ 8 | libgd2-dev libpuzzle-bin build-essential libtool m4 automake ffmpeg imagemagick 9 | 10 | WORKDIR /opt/ 11 | ADD libpuzzle /opt/libpuzzle 12 | 13 | # Build libpuzzle 14 | RUN cd /opt/libpuzzle/ && \ 15 | ./autogen.sh && \ 16 | ./configure && \ 17 | make && make install 18 | 19 | # Add in blacklist images & scripts 20 | ADD scripts /opt/scripts 21 | ADD blacklist-originals /opt/blacklist-originals 22 | 23 | # Add in scripts dir to container's path also... 24 | ENV PATH="/opt/scripts:${PATH}" 25 | 26 | CMD /opt/scripts/fake-video 27 | #ENTRYPOINT puzzle-diff -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018-2019 - Ultimate PMS Group Github Project 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /blacklist-originals/183030964134081.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/blacklist-originals/183030964134081.jpg -------------------------------------------------------------------------------- /blacklist-originals/222612955025266.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/blacklist-originals/222612955025266.jpg -------------------------------------------------------------------------------- /blacklist-originals/483791103037622.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/blacklist-originals/483791103037622.jpg -------------------------------------------------------------------------------- /blacklist-originals/99581805320818.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/blacklist-originals/99581805320818.jpg -------------------------------------------------------------------------------- /libpuzzle/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=libpuzzle 2 | -------------------------------------------------------------------------------- /libpuzzle/AUTHORS: -------------------------------------------------------------------------------- 1 | Frank DENIS 2 | -------------------------------------------------------------------------------- /libpuzzle/COPYING: -------------------------------------------------------------------------------- 1 | /* 2 | * ISC License 3 | * 4 | * Copyright (c) 2007-2015 Frank DENIS 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | -------------------------------------------------------------------------------- /libpuzzle/ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/ChangeLog -------------------------------------------------------------------------------- /libpuzzle/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:17.10 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update -q 6 | RUN apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" libgd2-dev libpuzzle-bin 7 | RUN apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" build-essential 8 | RUN apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" libtool m4 automake 9 | 10 | RUN apt-get install imagemagick -y 11 | ADD ./ /opt/libpuzzle/ 12 | 13 | WORKDIR /opt/libpuzzle/ 14 | 15 | RUN cd /opt/libpuzzle/ && \ 16 | ./autogen.sh && \ 17 | ./configure && \ 18 | make && make install 19 | 20 | #ENTRYPOINT puzzle-diff -------------------------------------------------------------------------------- /libpuzzle/INSTALL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/INSTALL -------------------------------------------------------------------------------- /libpuzzle/Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = gnu 2 | 3 | EXTRA_DIST = \ 4 | autogen.sh 5 | 6 | SUBDIRS = \ 7 | src \ 8 | man \ 9 | php 10 | -------------------------------------------------------------------------------- /libpuzzle/NEWS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/NEWS -------------------------------------------------------------------------------- /libpuzzle/README: -------------------------------------------------------------------------------- 1 | 2 | .:. LIBPUZZLE .:. 3 | 4 | http://libpuzzle.pureftpd.org 5 | 6 | 7 | ------------------------ BLURB ------------------------ 8 | 9 | 10 | The Puzzle library is designed to quickly find visually similar images (gif, 11 | png, jpg), even if they have been resized, recompressed, recolored or slightly 12 | modified. 13 | 14 | The library is free, lightweight yet very fast, configurable, easy to use and 15 | it has been designed with security in mind. This is a C library, but it also 16 | comes with a command-line tool and PHP bindings. 17 | 18 | 19 | ------------------------ REFERENCE ------------------------ 20 | 21 | 22 | The Puzzle library is a implementation of "An image signature for any kind of 23 | image", by H. CHI WONG, Marschall BERN and David GOLDBERG. 24 | 25 | 26 | ------------------------ COMPILATION ------------------------ 27 | 28 | 29 | In order to load images, the library relies on the GD2 library. 30 | You need to install gdlib2 and its development headers before compiling 31 | libpuzzle. 32 | The GD2 library is available as a pre-built package for most operating systems. 33 | Debian and Ubuntu users should install the "libgd2-dev" or the "libgd2-xpm-dev" 34 | package. 35 | Gentoo users should install "media-libs/gd". 36 | OpenBSD, NetBSD and DragonflyBSD users should install the "gd" package. 37 | MacPorts users should install the "gd2" package. 38 | X11 support is not required for the Puzzle library. 39 | 40 | Once GD2 has been installed, configure the Puzzle library as usual: 41 | 42 | ./configure 43 | 44 | This is a standard autoconf script, if you're not familiar with it, please 45 | have a look at the INSTALL file. 46 | 47 | Compile the beast: 48 | 49 | make 50 | 51 | Try the built-in tests: 52 | 53 | make check 54 | 55 | If everything looks fine, install the software: 56 | 57 | make install 58 | 59 | If anything goes wrong, please submit a bug report to: 60 | libpuzzle [at] pureftpd [dot] org 61 | 62 | 63 | ------------------------ USAGE ------------------------ 64 | 65 | 66 | The API is documented in the libpuzzle(3) and puzzle_set(3) man pages. 67 | You can also play with the puzzle-diff test application. 68 | See puzzle-diff(8) for more info about the puzzle-diff application. 69 | 70 | In order to be thread-safe, every exported function of the library requires a 71 | PuzzleContext object. That object stores various run-time tunables. 72 | 73 | Out of a bitmap picture, the Puzzle library can fill a PuzzleCVec object : 74 | 75 | PuzzleContext context; 76 | PuzzleCVec cvec; 77 | 78 | puzzle_init_context(&context); 79 | puzzle_init_cvec(&context, &cvec); 80 | puzzle_fill_cvec_from_file(&context, &cvec, "directory/filename.jpg"); 81 | 82 | The PuzzleCvec structure holds two fields: 83 | signed char *vec: a pointer to the first element of the vector 84 | size_t sizeof_vec: the number of elements 85 | 86 | The size depends on the "lambdas" value (see puzzle_set(3)). 87 | 88 | PuzzleCvec structures can be compared: 89 | 90 | d = puzzle_vector_normalized_distance(&context, &cvec1, &cvec2, 1); 91 | 92 | d is the normalized distance between both vectors. If d is below 0.6, pictures 93 | are probably similar. 94 | 95 | If you need further help, feel free to subscribe to the mailing-list (see 96 | below). 97 | 98 | 99 | ------------------------ INDEXING ------------------------ 100 | 101 | 102 | How to quickly find similar pictures, if they are millions of records? 103 | 104 | The original paper has a simple, yet efficient answer. 105 | 106 | Cut the vector in fixed-length words. For instance, let's consider the 107 | following vector: 108 | 109 | [ a b c d e f g h i j k l m n o p q r s t u v w x y z ] 110 | 111 | With a word length (K) of 10, you can get the following words: 112 | 113 | [ a b c d e f g h i j ] found at position 0 114 | [ b c d e f g h i j k ] found at position 1 115 | [ c d e f g h i j k l ] found at position 2 116 | etc. until position N-1 117 | 118 | Then, index your vector with a compound index of (word + position). 119 | 120 | Even with millions of images, K = 10 and N = 100 should be enough to have very 121 | little entries sharing the same index. 122 | 123 | Here's a very basic sample database schema: 124 | 125 | +-----------------------------+ 126 | | signatures | 127 | +-----------------------------+ 128 | | sig_id | signature | pic_id | 129 | +--------+-----------+--------+ 130 | 131 | +--------------------------+ 132 | | words | 133 | +--------------------------+ 134 | | pos_and_word | fk_sig_id | 135 | +--------------+-----------+ 136 | 137 | I'd recommend splitting at least the "words" table into multiple tables and/or 138 | servers. 139 | 140 | By default (lambas=9) signatures are 544 bytes long. In order to save storage 141 | space, they can be compressed to 1/third of their original size through the 142 | puzzle_compress_cvec() function. Before use, they must be uncompressed with 143 | puzzle_uncompress_cvec(). 144 | 145 | 146 | ------------------------ PUZZLE-DIFF ------------------------ 147 | 148 | 149 | A command-line tool is also available for scripting or testing. 150 | 151 | It is installed as "puzzle-diff" and comes with a man page. 152 | 153 | Sample usage: 154 | 155 | - Output distance between two images: 156 | 157 | $ puzzle-diff pic-a-0.jpg pics-a-1.jpg 158 | 0.102286 159 | 160 | - Compare two images, exit with 10 if they look the same, exit with 20 if 161 | they don't (may be useful for scripts): 162 | 163 | $ puzzle-diff -e pic-a-0.jpg pics-a-1.jpg 164 | $ echo $? 165 | 10 166 | 167 | - Compute distance, without cropping and with computing the average intensity 168 | of the whole blocks: 169 | 170 | $ puzzle-diff -p 1.0 -c pic-a-0.jpg pic-a-1.jpg 171 | 0.0523151 172 | 173 | 174 | ------------------------ COMPARING IMAGES WITH PHP ------------------------ 175 | 176 | 177 | A PHP extension is bundled with the Libpuzzle package, and it provides PHP 178 | bindings to most functions of the library. 179 | 180 | Documentation for the Libpuzzle PHP extension is available in the README-PHP 181 | file. 182 | 183 | 184 | ------------------------ APPS USING LIBPUZZLE ------------------------ 185 | 186 | 187 | Here are third-party projects using libpuzzle: 188 | 189 | * ftwin - http://jok.is-a-geek.net/ftwin.php 190 | ftwin is a tool useful to find duplicate files according to their content on 191 | your file system. 192 | 193 | * Python bindings for libpuzzle: PyPuzzle 194 | https://github.com/ArchangelSDY/PyPuzzle 195 | 196 | 197 | ------------------------ STATUS ------------------------ 198 | 199 | 200 | This project is unfortunately not maintained any more. Pull requests are 201 | always welcome, but I don't use this library any more and I don't have enough 202 | spare time to actively work on it. 203 | 204 | 205 | ---------------------- Docker --------------------------- 206 | 207 | 208 | Build: 209 | 210 | sudo docker build -t puzzle-diff . 211 | 212 | Run: 213 | 214 | sudo docker run -v /path/to/local/file1.jpg:/tmp/file1.jpg -v /path/to/local/file2.jpg:/tmp/file2.jpg --rm -it puzzle-diff:latest puzzle-diff /tmp/file1.jpg /tmp/file2.jpg 215 | 216 | You need to only change "/path/to/local/file1.jpg" and "/path/to/local/file2.jpg" to fit your parameters. 217 | 218 | -------------------------------------------------------------------------------- /libpuzzle/README-PHP: -------------------------------------------------------------------------------- 1 | 2 | .:. LIBPUZZLE - PHP EXTENSION .:. 3 | 4 | http://libpuzzle.pureftpd.org 5 | 6 | 7 | ------------------------ PHP EXTENSION ------------------------ 8 | 9 | 10 | The Puzzle library can also be used through PHP, using a native extension. 11 | 12 | Prerequisites are the PHP headers, libtool, autoconf and automake. 13 | 14 | Here are the basic steps in order to install the extension: 15 | 16 | (on OpenBSD: export AUTOMAKE_VERSION=1.9 ; export AUTOCONF_VERSION=2.61) 17 | 18 | cd php/libpuzzle 19 | phpize 20 | ./configure --with-libpuzzle 21 | make clean 22 | make 23 | make install 24 | 25 | If libpuzzle is installed in a non-standard location, use: 26 | ./configure --with-libpuzzle=/base/directory/for/libpuzzle 27 | 28 | Then edit your php.ini file and add: 29 | 30 | extension=libpuzzle.so 31 | 32 | 33 | ------------------------ USAGE ------------------------ 34 | 35 | 36 | The PHP extension provides bindings for the following tuning functions: 37 | - puzzle_set_max_width() 38 | - puzzle_set_max_height() 39 | - puzzle_set_lambdas() 40 | - puzzle_set_noise_cutoff() 41 | - puzzle_set_p_ratio() 42 | - puzzle_set_contrast_barrier_for_cropping() 43 | - puzzle_set_max_cropping_ratio() 44 | - puzzle_set_autocrop() 45 | 46 | Have a look at the puzzle_set man page for more info about those. 47 | 48 | Getting the signature of a picture is as simple as: 49 | 50 | $signature = puzzle_fill_cvec_from_file($filename); 51 | 52 | In order to compute the similarity between two pictures using their 53 | signatures, use: 54 | 55 | $d = puzzle_vector_normalized_distance($signature1, $signature2); 56 | 57 | The result is between 0.0 and 1.0, with 0.6 being a good threshold to detect 58 | visually similar pictures. 59 | 60 | The PUZZLE_CVEC_SIMILARITY_THRESHOLD, PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD, 61 | PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD and PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD 62 | constants can also be used to get common thresholds : 63 | 64 | if ($d < PUZZLE_CVEC_SIMILARITY_THRESHOLD) { 65 | echo "Pictures look similar\n"; 66 | } 67 | 68 | Before storing a signature into a database, you can compress it in order to 69 | save some storage space: 70 | 71 | $compressed_signature = puzzle_compress_cvec($signature); 72 | 73 | Before use, those compressed signatures must be uncompressed with: 74 | 75 | $signature = puzzle_uncompress_cvec($compressed_signature); 76 | 77 | -------------------------------------------------------------------------------- /libpuzzle/THANKS: -------------------------------------------------------------------------------- 1 | Xerox Research Center 2 | H. CHI WONG 3 | Marschall BERN 4 | David GOLDBERG 5 | Sameh CHAFIK 6 | Gregory MAXWELL 7 | -------------------------------------------------------------------------------- /libpuzzle/autogen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if [ -x "`which autoreconf 2>/dev/null`" ] ; then 4 | exec autoreconf -ivf 5 | fi 6 | 7 | if glibtoolize --version > /dev/null 2>&1; then 8 | LIBTOOLIZE='glibtoolize' 9 | else 10 | LIBTOOLIZE='libtoolize' 11 | fi 12 | 13 | $LIBTOOLIZE && \ 14 | aclocal && \ 15 | autoheader && \ 16 | automake --add-missing --force-missing --include-deps && \ 17 | autoconf 18 | -------------------------------------------------------------------------------- /libpuzzle/configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ(2.61) 5 | AC_INIT(libpuzzle, 0.11, bugs@pureftpd.org) 6 | AC_CONFIG_SRCDIR([src/puzzle.h]) 7 | AC_CONFIG_HEADER([config.h]) 8 | AM_INIT_AUTOMAKE([1.9 dist-bzip2]) 9 | AM_MAINTAINER_MODE 10 | 11 | # Checks for programs. 12 | AC_PROG_CXX 13 | AC_PROG_CC 14 | AC_PROG_CPP 15 | AC_PROG_INSTALL 16 | AC_PROG_LN_S 17 | AC_PROG_MAKE_SET 18 | AC_PATH_PROG(GDLIBCONFIG, [gdlib-config]) 19 | CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE=1" 20 | CPPFLAGS="$CPPFLAGS `$GDLIBCONFIG --cflags`" 21 | LDFLAGS="$LDFLAGS `$GDLIBCONFIG --ldflags`" 22 | LDADD="$LDADD `$GDLIBCONFIG --libs`" 23 | 24 | # Checks for libraries. 25 | 26 | AC_CHECK_LIB([gd], [gdImageCreateFromGd2],, 27 | AC_ERROR([libgd2 development files not found])) 28 | 29 | # Checks for header files. 30 | AC_HEADER_STDC 31 | AM_PROG_LIBTOOL 32 | AC_CHECK_HEADERS([limits.h memory.h stddef.h stdlib.h string.h unistd.h]) 33 | 34 | # Checks for typedefs, structures, and compiler characteristics. 35 | AC_C_CONST 36 | AC_TYPE_SIZE_T 37 | AC_TYPE_SSIZE_T 38 | AC_TYPE_OFF_T 39 | 40 | # Checks for library functions. 41 | AC_FUNC_MALLOC 42 | AC_FUNC_REALLOC 43 | AC_FUNC_MEMCMP 44 | AC_CHECK_FUNC([floor], ,[AC_CHECK_LIB([math], [floor])]) 45 | AC_CHECK_FUNC([round], ,[AC_CHECK_LIB([math], [round])]) 46 | AC_CHECK_FUNCS([strtoul]) 47 | 48 | AC_SUBST([MAINT]) 49 | 50 | AC_CONFIG_FILES([Makefile 51 | man/Makefile 52 | src/Makefile 53 | src/pics/Makefile 54 | php/Makefile 55 | php/libpuzzle/Makefile 56 | php/libpuzzle/include/Makefile 57 | php/libpuzzle/modules/Makefile 58 | php/libpuzzle/build/Makefile 59 | php/libpuzzle/tests/Makefile 60 | php/libpuzzle/tests/pics/Makefile 61 | php/examples/Makefile 62 | php/examples/similar/Makefile 63 | ]) 64 | AC_OUTPUT 65 | 66 | AC_MSG_NOTICE([+-------------------------------------------------------+]) 67 | AC_MSG_NOTICE([| You can subscribe to the Libpuzzle users mailing-list |]) 68 | AC_MSG_NOTICE([| to ask for help and to stay informed of new releases. |]) 69 | AC_MSG_NOTICE([| Go to http://libpuzzle.pureftpd.org/ml/ now! |]) 70 | AC_MSG_NOTICE([+-------------------------------------------------------+]) 71 | -------------------------------------------------------------------------------- /libpuzzle/man/Makefile.am: -------------------------------------------------------------------------------- 1 | man_MANS = \ 2 | libpuzzle.3 \ 3 | puzzle_set.3 \ 4 | puzzle-diff.8 5 | 6 | EXTRA_DIST = \ 7 | $(man_MANS) 8 | -------------------------------------------------------------------------------- /libpuzzle/man/libpuzzle.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) 2007-2014 Frank DENIS 3 | .\" 4 | .\" Permission to use, copy, modify, and distribute this software for any 5 | .\" purpose with or without fee is hereby granted, provided that the above 6 | .\" copyright notice and this permission notice appear in all copies. 7 | .\" 8 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | .\" 16 | .Dd $Mdocdate: March 31 2011 $ 17 | .Dt LIBPUZZLE 3 18 | .Sh NAME 19 | .Nm puzzle_init_cvec , 20 | .Nm puzzle_init_dvec , 21 | .Nm puzzle_fill_dvec_from_file , 22 | .Nm puzzle_fill_cvec_from_file , 23 | .Nm puzzle_fill_dvec_from_mem , 24 | .Nm puzzle_fill_cvec_from_mem , 25 | .Nm puzzle_fill_cvec_from_dvec , 26 | .Nm puzzle_free_cvec , 27 | .Nm puzzle_free_dvec , 28 | .Nm puzzle_init_compressed_cvec , 29 | .Nm puzzle_free_compressed_cvec , 30 | .Nm puzzle_compress_cvec , 31 | .Nm puzzle_uncompress_cvec , 32 | .Nm puzzle_vector_normalized_distance 33 | .Nd compute comparable signatures of bitmap images. 34 | .Sh SYNOPSIS 35 | .Fd #include 36 | .Ft void 37 | .Fn puzzle_init_context "PuzzleContext *context" 38 | .Ft void 39 | .Fn puzzle_free_context "PuzzleContext *context" 40 | .Ft void 41 | .Fn puzzle_init_cvec "PuzzleContext *context" "PuzzleCvec *cvec" 42 | .Ft void 43 | .Fn puzzle_init_dvec "PuzzleContext *context" "PuzzleDvec *dvec" 44 | .Ft int 45 | .Fn puzzle_fill_dvec_from_file "PuzzleContext *context" "PuzzleDvec * dvec" "const char *file" 46 | .Ft int 47 | .Fn puzzle_fill_cvec_from_file "PuzzleContext *context" "PuzzleCvec * cvec" "const char *file" 48 | .Ft int 49 | .Fn puzzle_fill_dvec_from_mem "PuzzleContext *context" "PuzzleDvec * dvec" "const void *mem" "size_t size" 50 | .Ft int 51 | .Fn puzzle_fill_cvec_from_mem "PuzzleContext *context" "PuzzleCvec * cvec" "const void *mem" "size_t size" 52 | .Ft int 53 | .Fn puzzle_fill_cvec_from_dvec "PuzzleContext *context" "PuzzleCvec * cvec" "const PuzzleDvec *dvec" 54 | .Ft void 55 | .Fn puzzle_free_cvec "PuzzleContext *context" "PuzzleCvec *cvec" 56 | .Ft void 57 | .Fn puzzle_free_dvec "PuzzleContext *context" "PuzzleDvec *dvec" 58 | .Ft void 59 | .Fn puzzle_init_compressed_cvec "PuzzleContext *context" "PuzzleCompressedCvec * compressed_cvec" 60 | .Ft void 61 | .Fn puzzle_free_compressed_cvec "PuzzleContext *context" "PuzzleCompressedCvec * compressed_cvec" 62 | .Ft int 63 | .Fn puzzle_compress_cvec "PuzzleContext *context" "PuzzleCompressedCvec * compressed_cvec" "const PuzzleCvec * cvec" 64 | .Ft int 65 | .Fn puzzle_uncompress_cvec "PuzzleContext *context" "PuzzleCompressedCvec * compressed_cvec" "PuzzleCvec * const cvec" 66 | .Ft double 67 | .Fn puzzle_vector_normalized_distance "PuzzleContext *context" "const PuzzleCvec * cvec1" "const PuzzleCvec * cvec2" "int fix_for_texts" 68 | .Sh DESCRIPTION 69 | The Puzzle library computes a signature out of a bitmap picture. 70 | Signatures are comparable and similar pictures have similar signatures. 71 | .Pp 72 | After a picture has been loaded and uncompressed, featureless parts of 73 | the image are skipped (autocrop), unless that step has been explicitely 74 | disabled, see 75 | .Xr puzzle_set 3 76 | .Sh LIBPUZZLE CONTEXT 77 | Every public function requires a 78 | .Va PuzzleContext 79 | object, that stores every required tunables. 80 | .Pp 81 | Any application using libpuzzle should initialize a 82 | .Va PuzzleContext 83 | object with 84 | .Fn puzzle_init_context 85 | and free it after use with 86 | .Fn puzzle_free_context 87 | .Bd \-literal \-offset indent 88 | PuzzleContext context; 89 | 90 | puzzle_init_context(&context); 91 | ... 92 | puzzle_free_context(&context); 93 | .Ed 94 | .Sh DVEC AND CVEC VECTORS 95 | The next step is to divide the cropped image into a grid and to compute 96 | the average intensity of soft\(hyedged pixels in every block. The result is a 97 | .Va PuzzleDvec 98 | object. 99 | .Pp 100 | .Va PuzzleDvec 101 | objects should be initialized before use, with 102 | .Fn puzzle_init_dvec 103 | and freed after use with 104 | .Fn puzzle_free_dvec 105 | .Pp 106 | The 107 | .Va PuzzleDvec 108 | structure has two important fields: 109 | .Va vec 110 | is the pointer to the first element of the array containing the average 111 | intensities, and 112 | .Va sizeof_compressed_vec 113 | is the number of elements. 114 | .Pp 115 | .Va PuzzleDvec 116 | objects are not comparable, so what you usually want is to transform these 117 | objects into 118 | .Va PuzzleCvec 119 | objects. 120 | .Pp 121 | A 122 | .Va PuzzleCvec 123 | object is a vector with relationships between adjacent blocks from a 124 | .Va PuzzleDvec 125 | object. 126 | .Pp 127 | The 128 | .Fn puzzle_fill_cvec_from_dvec 129 | fills a 130 | .Va PuzzleCvec 131 | object from a 132 | .Va PuzzleDvec 133 | object. 134 | .Pp 135 | But just like the other structure, 136 | .Va PuzzleCvec 137 | objects must be initialized and freed with 138 | .Fn puzzle_init_cvec 139 | and 140 | .Fn puzzle_free_cvec 141 | .Pp 142 | .Va PuzzleCvec 143 | objects have a vector whoose first element is in the 144 | .Va vec 145 | field, and the number of elements is in the 146 | .Va sizeof_vec 147 | field 148 | .Sh LOADING PICTURES 149 | .Va PuzzleDvec 150 | and 151 | .Va PuzzleCvec 152 | objects can be computed from a bitmap picture file, with 153 | .Fn puzzle_fill_dvec_from_file 154 | and 155 | .Fn puzzle_fill_cvec_from_file 156 | .Pp 157 | .Em GIF 158 | , 159 | .Em PNG 160 | and 161 | .Em JPEG 162 | files formats are currently supported and automatically recognized. 163 | .Pp 164 | Here's a simple example that creates a 165 | .Va PuzzleCvec 166 | objects out of a file. 167 | .Bd \-literal \-offset indent 168 | PuzzleContext context; 169 | PuzzleCvec cvec; 170 | 171 | puzzle_init_context(&context); 172 | puzzle_init_cvec(&context, &cvec); 173 | puzzle_fill_cvec_from_file(&context, &cvec, "test\-picture.jpg"); 174 | ... 175 | puzzle_free_cvec(&context, &cvec); 176 | puzzle_free_context(&context); 177 | .Ed 178 | .Sh COMPARING VECTORS 179 | In order to check whether two pictures are similar, you need to compare their 180 | .Va PuzzleCvec 181 | signatures, using 182 | .Fn puzzle_vector_normalized_distance 183 | .Pp 184 | That function returns a distance, between 0.0 and 1.0. The lesser, the nearer. 185 | .Pp 186 | Tests on common pictures show that a normalized distance of 0.6 (also defined as 187 | .Va PUZZLE_CVEC_SIMILARITY_THRESHOLD 188 | ) means that both pictures are visually similar. 189 | .Pp 190 | If that threshold is not right for your set of pictures, you can experiment 191 | with 192 | .Va PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD 193 | , 194 | .Va PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD 195 | and 196 | .Va PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD 197 | or with your own value. 198 | .Pp 199 | If the 200 | .Fa fix_for_texts 201 | of 202 | .Fn puzzle_vector_normalized_distance 203 | is 204 | .Em 1 205 | , a fix is applied to the computation in order to deal with bitmap pictures 206 | that contain text. That fix is recommended, as it allows using the same 207 | threshold for that kind of picture as for generic pictures. 208 | .Pp 209 | If 210 | .Fa fix_for_texts 211 | is 212 | .Em 0 213 | , that special way of computing the normalized distance is disabled. 214 | .Bd \-literal \-offset indent 215 | PuzzleContext context; 216 | PuzzleCvec cvec1, cvec2; 217 | double d; 218 | 219 | puzzle_init_context(&context); 220 | puzzle_init_cvec(&context, &cvec1); 221 | puzzle_init_cvec(&context, &cvec2); 222 | puzzle_fill_cvec_from_file(&context, &cvec1, "test\-picture\-1.jpg"); 223 | puzzle_fill_cvec_from_file(&context, &cvec2, "test\-picture\-2.jpg"); 224 | d = puzzle_vector_normalized_distance(&context, &cvec1, &cvec2, 1); 225 | if (d < PUZZLE_CVEC_SIMILARITY_THRESHOLD) { 226 | puts("Pictures are similar"); 227 | } 228 | puzzle_free_cvec(&context, &cvec2); 229 | puzzle_free_cvec(&context, &cvec1); 230 | puzzle_free_context(&context); 231 | .Ed 232 | .Sh CVEC COMPRESSION 233 | In order to reduce storage needs, 234 | .Va PuzzleCvec 235 | objects can be compressed to 1/3 of their original size. 236 | .Pp 237 | .Va PuzzleCompressedCvec 238 | structures hold the compressed data. Before and after use, these structures 239 | have to be passed to 240 | .Fn puzzle_init_compressed_cvec 241 | and 242 | .Fn puzzle_free_compressed_cvec 243 | .Pp 244 | .Fn puzzle_compress_cvec 245 | compresses a 246 | .Va PuzzleCvec 247 | object into a 248 | .Va PuzzleCompressedCvec 249 | object. 250 | .Pp 251 | And 252 | .Fn puzzle_uncompress_cvec 253 | uncompresses a 254 | .Va PuzzleCompressedCvec 255 | object into a 256 | .Va PuzzleCvec 257 | object. 258 | .Bd \-literal \-offset indent 259 | PuzzleContext context; 260 | PuzzleCvec cvec; 261 | PuzzleCompressedCvec c_cvec; 262 | ... 263 | puzzle_init_compressed_cvec(&context, &c_cvec); 264 | puzzle_compress_cvec(&context, &c_cvec, &cvec); 265 | ... 266 | puzzle_free_compressed_cvec(&context, &c_cvec); 267 | .Ed 268 | The 269 | .Va PuzzleCompressedCvec 270 | structure has two important fields: 271 | .Va vec 272 | that is a pointer to the first element of the compressed data, and 273 | .Va sizeof_compressed_vec 274 | that contains the number of elements. 275 | .Sh RETURN VALUE 276 | Functions return 277 | .Em 0 278 | on success, and 279 | .Em \-1 280 | if something went wrong. 281 | .Sh AUTHORS 282 | .Nf 283 | Frank DENIS 284 | libpuzzle at pureftpd dot org 285 | .Fi 286 | .Sh ACKNOWLEDGMENTS 287 | .Nf 288 | Xerox Research Center 289 | H. CHI WONG 290 | Marschall BERN 291 | David GOLDBERG 292 | Sameh SCHAFIK 293 | .Fi 294 | .Sh SEE ALSO 295 | .Xr puzzle_set 3 296 | .Xr puzzle\-diff 8 297 | -------------------------------------------------------------------------------- /libpuzzle/man/puzzle-diff.8: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) 2007-2014 Frank DENIS 3 | .\" 4 | .\" Permission to use, copy, modify, and distribute this software for any 5 | .\" purpose with or without fee is hereby granted, provided that the above 6 | .\" copyright notice and this permission notice appear in all copies. 7 | .\" 8 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | .\" 16 | .Dd $Mdocdate: September 23 2007 $ 17 | .Dt PUZZLE-DIFF 1 18 | .Os 19 | .Sh NAME 20 | .Nm puzzle\-diff 21 | .Nd compare pictures with libpuzzle 22 | .Sh SYNOPSIS 23 | .Nm puzzle\-diff 24 | [\-b ] 25 | [\-e] [\-E ] [\-h] [\-H ] [\-l ] 26 | [\-n ] [\-p

] [\-t] [\-W ] 27 | 28 | 29 | .Sh DESCRIPTION 30 | puzzle\-diff compares two pictures and outputs the normalized distance. 31 | .Pp 32 | Try 33 | .Em puzzle\-diff \-h 34 | for more info. 35 | .Sh EXAMPLES 36 | Output distance between two images: 37 | .Bd -literal -offset indent 38 | $ puzzle\-diff pic\-a\-0.jpg pics\-a\-1.jpg 39 | 0.102286 40 | .Ed 41 | .Pp 42 | Compare two images, exit with 10 if they look the same, exit with 20 if 43 | they don't (may be useful for scripts): 44 | .Bd -literal -offset indent 45 | $ puzzle\-diff \-e pic\-a\-0.jpg pics\-a\-1.jpg 46 | $ echo $? 47 | 10 48 | .Ed 49 | .Pp 50 | Compute distance, without cropping and with computing the average intensity 51 | of the whole blocks: 52 | .Bd -literal -offset indent 53 | $ puzzle\-diff \-p 1.0 \-c pic\-a\-0.jpg pic\-a\-1.jpg 54 | 0.0523151 55 | .Ed 56 | .Sh SEE ALSO 57 | .Xr libpuzzle 3 58 | .Xr puzzle_set 3 59 | -------------------------------------------------------------------------------- /libpuzzle/man/puzzle_set.3: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) 2007-2014 Frank DENIS 3 | .\" 4 | .\" Permission to use, copy, modify, and distribute this software for any 5 | .\" purpose with or without fee is hereby granted, provided that the above 6 | .\" copyright notice and this permission notice appear in all copies. 7 | .\" 8 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | .\" 16 | .Dd $Mdocdate: September 24 2007 $ 17 | .Dt PUZZLE_SET 3 18 | .Sh NAME 19 | .Nm puzzle_set_max_width , 20 | .Nm puzzle_set_max_height , 21 | .Nm puzzle_set_lambdas , 22 | .Nm puzzle_set_p_ratio , 23 | .Nm puzzle_set_noise_cutoff , 24 | .Nm puzzle_set_contrast_barrier_for_cropping , 25 | .Nm puzzle_set_max_cropping_ratio , 26 | .Nm puzzle_set_autocrop 27 | .Nd set tunables for libpuzzle functions. 28 | .Sh SYNOPSIS 29 | .Fd #include 30 | .Ft int 31 | .Fn puzzle_set_max_width "PuzzleContext *context" "unsigned int width" 32 | .Ft int 33 | .Fn puzzle_set_max_height "PuzzleContext *context" "unsigned int height" 34 | .Ft int 35 | .Fn puzzle_set_lambdas "PuzzleContext *context" "unsigned int lambdas" 36 | .Ft int 37 | .Fn puzzle_set_p_ratio "PuzzleContext *context" "double p_ratio" 38 | .Ft int 39 | .Fn puzzle_set_noise_cutoff "PuzzleContext *context" "double noise_cutoff" 40 | .Ft int 41 | .Fn puzzle_set_contrast_barrier_for_cropping "PuzzleContext *context" "double barrier" 42 | .Ft int 43 | .Fn puzzle_set_max_cropping_ratio "PuzzleContext *context" "double ratio" 44 | .Ft int 45 | .Fn puzzle_set_autocrop "PuzzleContext *context" "int enable" 46 | .Sh DESCRIPTION 47 | While default values have been chosen to be ok for most people, the 48 | .Fn puzzle_set_* 49 | functions are knobs to fit the algorithm to your set of data and to your 50 | applications. 51 | .Sh LAMBDAS 52 | By default, pictures are divided in 9 x 9 blocks. 53 | .Pp 54 | .Em 9 55 | is the 56 | .Em lambdas 57 | value, and it can be changed with 58 | .Fn puzzle_set_lambdas 59 | .Pp 60 | For large databases, for complex images, for images with a lot of text or 61 | for sets of near\(hysimilar images, it might be better to raise that value to 62 | .Em 11 63 | or even 64 | .Em 13 65 | .Pp 66 | However, raising that value obviously means that vectors will require more 67 | storage space. 68 | .Pp 69 | The 70 | .Em lambdas 71 | value should remain the same in order to get comparable vectors. So if you 72 | pick 73 | .Em 11 74 | (for instance), you should always use that value for all pictures you will 75 | compute a digest for. 76 | .Fn puzzle_set_p_ratio 77 | .Pp 78 | The average intensity of each block is based upon a small centered zone. 79 | .Pp 80 | The "p ratio" determines the size of that zone. The default is 2.0, and that 81 | ratio mimics the behavior that is described in the reference algorithm. 82 | .Pp 83 | For very specific cases (complex images) or if you get too many false 84 | positives, as an alternative to increasing lambdas, you can try to lower that 85 | value, for instance to 1.5. 86 | .Pp 87 | The lowest acceptable value is 1.0. 88 | .Sh MAXIMUM SIZES 89 | In order to avoid CPU starvation, pictures won't be processed if their width 90 | or height is larger than 3000 pixels. 91 | .Pp 92 | These limits are rather large, but if you ever need to change them, the 93 | .Fn puzzle_set_max_width 94 | and 95 | .Fn puzzle_set_max_height 96 | are available. 97 | .Sh NOISE CUTOFF 98 | The noise cutoff defaults to 2. If you raise that value, more zones with 99 | little difference of intensity will be considered as similar. 100 | .Pp 101 | Unless you have very specialized sets of pictures, you probably don't want 102 | to change this. 103 | .Sh AUTOCROP 104 | By default, featureless borders of the original image are ignored. The size 105 | of each border depends on the sum of absolute values of differences between 106 | adjacent pixels, relative to the total sum. 107 | .Pp 108 | That feature can be disabled with 109 | .Fn puzzle_set_autocrop "0" 110 | Any other value will enable it. 111 | .Pp 112 | .Fn puzzle_set_contrast_barrier_for_cropping 113 | changes the tolerance. The default value is 5. Less shaves less, more shaves 114 | more. 115 | .Pp 116 | .Fn puzzle_set_max_cropping_ratio 117 | This is a safe\(hyguard against unwanted excessive auto\(hycropping. 118 | .Pp 119 | The default (0.25) means that no more than 25% of the total width (or 120 | height) will ever be shaved. 121 | .Sh RETURN VALUE 122 | Functions return 123 | .Em 0 124 | on success, and 125 | .Em \-1 126 | if something went wrong. 127 | .Sh SEE ALSO 128 | .Xr libpuzzle 3 129 | .Xr puzzle\-diff 8 130 | -------------------------------------------------------------------------------- /libpuzzle/php/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = \ 2 | libpuzzle \ 3 | examples 4 | -------------------------------------------------------------------------------- /libpuzzle/php/examples/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = \ 2 | similar 3 | -------------------------------------------------------------------------------- /libpuzzle/php/examples/similar/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = \ 2 | schema.sqlite3.sql \ 3 | schema.pgsql.sql \ 4 | similar.php \ 5 | similar.inc.php \ 6 | config.inc.php 7 | -------------------------------------------------------------------------------- /libpuzzle/php/examples/similar/config.inc.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /libpuzzle/php/examples/similar/schema.pgsql.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PostgreSQL database dump 3 | -- 4 | 5 | SET client_encoding = 'UTF8'; 6 | SET standard_conforming_strings = off; 7 | SET check_function_bodies = false; 8 | SET client_min_messages = warning; 9 | SET escape_string_warning = off; 10 | 11 | SET SESSION AUTHORIZATION 'similar'; 12 | 13 | -- 14 | -- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: similar 15 | -- 16 | 17 | COMMENT ON SCHEMA public IS 'Standard public schema'; 18 | 19 | 20 | SET search_path = public, pg_catalog; 21 | 22 | SET default_tablespace = ''; 23 | 24 | SET default_with_oids = false; 25 | 26 | -- 27 | -- Name: pictures; Type: TABLE; Schema: public; Owner: similar; Tablespace: 28 | -- 29 | 30 | CREATE TABLE pictures ( 31 | id integer NOT NULL, 32 | digest character(32) NOT NULL, 33 | CONSTRAINT ck_digest CHECK ((char_length(digest) = 32)) 34 | ); 35 | 36 | 37 | -- 38 | -- Name: pictures_id_seq; Type: SEQUENCE; Schema: public; Owner: similar 39 | -- 40 | 41 | CREATE SEQUENCE pictures_id_seq 42 | START WITH 1 43 | INCREMENT BY 1 44 | NO MAXVALUE 45 | NO MINVALUE 46 | CACHE 1; 47 | 48 | 49 | -- 50 | -- Name: pictures_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: similar 51 | -- 52 | 53 | ALTER SEQUENCE pictures_id_seq OWNED BY pictures.id; 54 | 55 | 56 | -- 57 | -- Name: sentpictures; Type: TABLE; Schema: public; Owner: similar; Tablespace: 58 | -- 59 | 60 | CREATE TABLE sentpictures ( 61 | id integer NOT NULL, 62 | url character varying(255) NOT NULL, 63 | sender character varying(100) NOT NULL, 64 | picture_id integer NOT NULL, 65 | CONSTRAINT ck_url CHECK (((url)::text <> ''::text)) 66 | ); 67 | 68 | 69 | -- 70 | -- Name: sentpictures_id_seq; Type: SEQUENCE; Schema: public; Owner: similar 71 | -- 72 | 73 | CREATE SEQUENCE sentpictures_id_seq 74 | START WITH 1 75 | INCREMENT BY 1 76 | NO MAXVALUE 77 | NO MINVALUE 78 | CACHE 1; 79 | 80 | 81 | -- 82 | -- Name: sentpictures_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: similar 83 | -- 84 | 85 | ALTER SEQUENCE sentpictures_id_seq OWNED BY sentpictures.id; 86 | 87 | 88 | -- 89 | -- Name: signatures; Type: TABLE; Schema: public; Owner: similar; Tablespace: 90 | -- 91 | 92 | CREATE TABLE signatures ( 93 | id integer NOT NULL, 94 | compressed_signature bytea NOT NULL, 95 | picture_id integer NOT NULL, 96 | CONSTRAINT ck_signature CHECK ((octet_length(compressed_signature) >= 182)) 97 | ); 98 | 99 | 100 | -- 101 | -- Name: signatures_id_seq; Type: SEQUENCE; Schema: public; Owner: similar 102 | -- 103 | 104 | CREATE SEQUENCE signatures_id_seq 105 | START WITH 1 106 | INCREMENT BY 1 107 | NO MAXVALUE 108 | NO MINVALUE 109 | CACHE 1; 110 | 111 | 112 | -- 113 | -- Name: signatures_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: similar 114 | -- 115 | 116 | ALTER SEQUENCE signatures_id_seq OWNED BY signatures.id; 117 | 118 | 119 | -- 120 | -- Name: words; Type: TABLE; Schema: public; Owner: similar; Tablespace: 121 | -- 122 | 123 | CREATE TABLE words ( 124 | pos_and_word bytea NOT NULL, 125 | signature_id integer NOT NULL, 126 | CONSTRAINT ck_pos_and_word CHECK ((octet_length(pos_and_word) >= 2)) 127 | ); 128 | 129 | 130 | -- 131 | -- Name: id; Type: DEFAULT; Schema: public; Owner: similar 132 | -- 133 | 134 | ALTER TABLE pictures ALTER COLUMN id SET DEFAULT nextval('pictures_id_seq'::regclass); 135 | 136 | 137 | -- 138 | -- Name: id; Type: DEFAULT; Schema: public; Owner: similar 139 | -- 140 | 141 | ALTER TABLE sentpictures ALTER COLUMN id SET DEFAULT nextval('sentpictures_id_seq'::regclass); 142 | 143 | 144 | -- 145 | -- Name: id; Type: DEFAULT; Schema: public; Owner: similar 146 | -- 147 | 148 | ALTER TABLE signatures ALTER COLUMN id SET DEFAULT nextval('signatures_id_seq'::regclass); 149 | 150 | 151 | -- 152 | -- Name: pictures_pkey; Type: CONSTRAINT; Schema: public; Owner: similar; Tablespace: 153 | -- 154 | 155 | ALTER TABLE ONLY pictures 156 | ADD CONSTRAINT pictures_pkey PRIMARY KEY (id); 157 | 158 | 159 | -- 160 | -- Name: sentpictures_pkey; Type: CONSTRAINT; Schema: public; Owner: similar; Tablespace: 161 | -- 162 | 163 | ALTER TABLE ONLY sentpictures 164 | ADD CONSTRAINT sentpictures_pkey PRIMARY KEY (id); 165 | 166 | 167 | -- 168 | -- Name: signatures_pkey; Type: CONSTRAINT; Schema: public; Owner: similar; Tablespace: 169 | -- 170 | 171 | ALTER TABLE ONLY signatures 172 | ADD CONSTRAINT signatures_pkey PRIMARY KEY (id); 173 | 174 | 175 | -- 176 | -- Name: idx_digest; Type: INDEX; Schema: public; Owner: similar; Tablespace: 177 | -- 178 | 179 | CREATE UNIQUE INDEX idx_digest ON pictures USING btree (digest); 180 | 181 | 182 | -- 183 | -- Name: idx_picture_id; Type: INDEX; Schema: public; Owner: similar; Tablespace: 184 | -- 185 | 186 | CREATE INDEX idx_picture_id ON sentpictures USING btree (picture_id); 187 | 188 | 189 | -- 190 | -- Name: idx_pos_and_word; Type: INDEX; Schema: public; Owner: similar; Tablespace: 191 | -- 192 | 193 | CREATE INDEX idx_pos_and_word ON words USING btree (pos_and_word); 194 | 195 | 196 | -- 197 | -- Name: idx_url; Type: INDEX; Schema: public; Owner: similar; Tablespace: 198 | -- 199 | 200 | CREATE UNIQUE INDEX idx_url ON sentpictures USING btree (url); 201 | 202 | 203 | -- 204 | -- Name: sentpictures_picture_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: similar 205 | -- 206 | 207 | ALTER TABLE ONLY sentpictures 208 | ADD CONSTRAINT sentpictures_picture_id_fkey FOREIGN KEY (picture_id) REFERENCES pictures(id) ON UPDATE CASCADE ON DELETE CASCADE; 209 | 210 | 211 | -- 212 | -- Name: signatures_picture_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: similar 213 | -- 214 | 215 | ALTER TABLE ONLY signatures 216 | ADD CONSTRAINT signatures_picture_id_fkey FOREIGN KEY (picture_id) REFERENCES pictures(id) ON UPDATE CASCADE ON DELETE CASCADE; 217 | 218 | 219 | -- 220 | -- Name: words_signature_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: similar 221 | -- 222 | 223 | ALTER TABLE ONLY words 224 | ADD CONSTRAINT words_signature_id_fkey FOREIGN KEY (signature_id) REFERENCES signatures(id) ON UPDATE CASCADE ON DELETE CASCADE; 225 | 226 | 227 | -- 228 | -- PostgreSQL database dump complete 229 | -- 230 | 231 | -------------------------------------------------------------------------------- /libpuzzle/php/examples/similar/schema.sqlite3.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE pictures ( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT, 3 | digest CHAR(32) NOT NULL 4 | ); 5 | CREATE TABLE sentpictures ( 6 | id INTEGER PRIMARY KEY AUTOINCREMENT, 7 | url VARCHAR(255) NOT NULL, 8 | sender VARCHAR(100) NOT NULL, 9 | picture_id INTEGER NOT NULL 10 | ); 11 | CREATE TABLE signatures ( 12 | id INTEGER PRIMARY KEY AUTOINCREMENT, 13 | compressed_signature CHAR(182) NOT NULL, 14 | picture_id INTEGER NOT NULL 15 | ); 16 | CREATE TABLE words ( 17 | pos_and_word CHAR(5) NOT NULL, 18 | signature_id INTEGER NOT NULL 19 | ); 20 | CREATE UNIQUE INDEX idx_digest ON pictures(digest); 21 | CREATE INDEX idx_picture_id ON sentpictures (picture_id); 22 | CREATE INDEX idx_pos_and_word ON words(pos_and_word); 23 | CREATE UNIQUE INDEX idx_url ON sentpictures (url); 24 | -------------------------------------------------------------------------------- /libpuzzle/php/examples/similar/similar.inc.php: -------------------------------------------------------------------------------- 1 | beginTransaction(); 18 | try { 19 | $st = $dbh->prepare 20 | ('DELETE FROM sentpictures WHERE url = :url'); 21 | $st->execute(array(':url' => $url)); 22 | $st = $dbh->prepare 23 | ('SELECT id FROM pictures WHERE digest = :digest'); 24 | $st->execute(array(':digest' => $md5)); 25 | $picture_id = $st->fetchColumn(); 26 | $st->closeCursor(); 27 | $duplicate = TRUE; 28 | if ($picture_id === FALSE) { 29 | $duplicate = FALSE; 30 | $st = $dbh->prepare 31 | ('INSERT INTO pictures (digest) VALUES (:digest)'); 32 | $st->execute(array(':digest' => $md5)); 33 | $picture_id = $dbh->lastInsertId('id'); 34 | } 35 | $st = $dbh->prepare 36 | ('INSERT INTO sentpictures (url, sender, picture_id) ' . 37 | 'VALUES (:url, :sender, :picture_id)'); 38 | $st->execute(array(':url' => $url, ':sender' => $client_info, 39 | ':picture_id' => $picture_id)); 40 | if ($duplicate === TRUE) { 41 | $dbh->commit(); 42 | return TRUE; 43 | } 44 | $st = $dbh->prepare 45 | ('INSERT INTO signatures (compressed_signature, picture_id) ' . 46 | 'VALUES(:compressed_signature, :picture_id)'); 47 | $st->execute(array(':compressed_signature' => $compressed_cvec, 48 | ':picture_id' => $picture_id)); 49 | $signature_id = $dbh->lastInsertId('id'); 50 | $st = $dbh->prepare 51 | ('INSERT INTO words (pos_and_word, signature_id) ' . 52 | 'VALUES (:pos_and_word, :signature_id)'); 53 | foreach ($words as $u => $word) { 54 | $st->execute(array('pos_and_word' 55 | => chr($u) . puzzle_compress_cvec($word), 56 | 'signature_id' => $signature_id)); 57 | } 58 | $dbh->commit(); 59 | } catch (Exception $e) { 60 | var_dump($e); 61 | $dbh->rollback(); 62 | } 63 | return TRUE; 64 | } 65 | 66 | function find_similar_pictures($md5, $cvec, 67 | $threshold = PUZZLE_CVEC_SIMILARITY_THRESHOLD) { 68 | $compressed_cvec = puzzle_compress_cvec($cvec); 69 | $words = split_into_words($cvec); 70 | $dbh = new PDO(DB_DSN); 71 | $dbh->beginTransaction(); 72 | $sql = 'SELECT DISTINCT(signature_id) AS signature_id FROM words ' . 73 | 'WHERE pos_and_word IN ('; 74 | $coma = FALSE; 75 | foreach ($words as $u => $word) { 76 | if ($coma === TRUE) { 77 | $sql .= ','; 78 | } 79 | $sql .= $dbh->quote(chr($u) . puzzle_compress_cvec($word)); 80 | $coma = TRUE; 81 | } 82 | $sql .= ')'; 83 | $res_words = $dbh->query($sql); 84 | $scores = array(); 85 | $st = $dbh->prepare('SELECT compressed_signature, picture_id ' . 86 | 'FROM signatures WHERE id = :id'); 87 | while (($signature_id = $res_words->fetchColumn()) !== FALSE) { 88 | $st->execute(array(':id' => $signature_id)); 89 | $row = $st->fetch(); 90 | $found_compressed_signature = $row['compressed_signature']; 91 | $picture_id = $row['picture_id']; 92 | $found_cvec = puzzle_uncompress_cvec($found_compressed_signature); 93 | $distance = puzzle_vector_normalized_distance($cvec, $found_cvec); 94 | if ($distance < $threshold && $distance > 0.0) { 95 | $scores[$picture_id] = $distance; 96 | } 97 | } 98 | $sql = 'SELECT url FROM sentpictures WHERE picture_id IN ('; 99 | $coma = FALSE; 100 | foreach ($scores as $picture_id => $score) { 101 | if ($coma === TRUE) { 102 | $sql .= ','; 103 | } 104 | $sql .= $dbh->quote($picture_id); 105 | $coma = TRUE; 106 | } 107 | $sql .= ')'; 108 | $urls = array(); 109 | if (!empty($scores)) { 110 | $res_urls = $dbh->query($sql); 111 | while (($url = $res_urls->fetchColumn()) !== FALSE) { 112 | array_push($urls, $url); 113 | } 114 | } 115 | $dbh->commit(); 116 | 117 | return $urls; 118 | } 119 | 120 | ?> 121 | -------------------------------------------------------------------------------- /libpuzzle/php/examples/similar/similar.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/php/examples/similar/similar.php -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/CREDITS: -------------------------------------------------------------------------------- 1 | Frank DENIS 2 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/EXPERIMENTAL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/php/libpuzzle/EXPERIMENTAL -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007-2015 Frank DENIS 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = \ 2 | CREDITS \ 3 | EXPERIMENTAL \ 4 | LICENSE \ 5 | README \ 6 | config.m4 \ 7 | libpuzzle.c \ 8 | libpuzzle.php \ 9 | php_libpuzzle.h 10 | 11 | SUBDIRS = \ 12 | build \ 13 | include \ 14 | modules \ 15 | tests 16 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/README: -------------------------------------------------------------------------------- 1 | This is a PHP extension for libpuzzle. 2 | 3 | Have a look at the README-PHP file on top of the libpuzzle distribution for 4 | more info about that extension. 5 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/build/Makefile.am: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/php/libpuzzle/build/Makefile.am -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 for extension libpuzzle 2 | 3 | dnl If your extension references something external, use with: 4 | 5 | PHP_ARG_WITH(libpuzzle, for libpuzzle support, 6 | [ --with-libpuzzle Include libpuzzle support]) 7 | 8 | if test "$PHP_LIBPUZZLE" != "no"; then 9 | for i in $PHP_LIBPUZZLE /usr/local /usr; do 10 | if test -x "$i/bin/gdlib-config"; then 11 | GDLIB_CONFIG=$i/bin/gdlib-config 12 | break 13 | fi 14 | done 15 | GDLIB_LIBS=$($GDLIB_CONFIG --ldflags --libs) 16 | GDLIB_INCS=$($GDLIB_CONFIG --cflags) 17 | 18 | PHP_EVAL_LIBLINE($GDLIB_LIBS, LIBPUZZLE_SHARED_LIBADD) 19 | PHP_EVAL_INCLINE($GDLIB_INCS) 20 | 21 | SEARCH_PATH="/usr/local /usr" # you might want to change this 22 | SEARCH_FOR="/include/puzzle.h" # you most likely want to change this 23 | if test -r $PHP_LIBPUZZLE/$SEARCH_FOR; then # path given as parameter 24 | LIBPUZZLE_DIR=$PHP_LIBPUZZLE 25 | else # search default path list 26 | AC_MSG_CHECKING([for libpuzzle files in default path]) 27 | for i in $SEARCH_PATH ; do 28 | if test -r $i/$SEARCH_FOR; then 29 | LIBPUZZLE_DIR=$i 30 | AC_MSG_RESULT(found in $i) 31 | fi 32 | done 33 | fi 34 | 35 | if test -z "$LIBPUZZLE_DIR"; then 36 | AC_MSG_RESULT([not found]) 37 | AC_MSG_ERROR([Please reinstall the libpuzzle distribution]) 38 | fi 39 | 40 | dnl # --with-libpuzzle -> add include path 41 | PHP_ADD_INCLUDE($LIBPUZZLE_DIR/include) 42 | 43 | PHP_ADD_LIBRARY_WITH_PATH(puzzle, $LIBPUZZLE_DIR/lib, 44 | LIBPUZZLE_SHARED_LIBADD) 45 | 46 | PHP_SUBST(LIBPUZZLE_SHARED_LIBADD) 47 | 48 | PHP_NEW_EXTENSION(libpuzzle, libpuzzle.c, $ext_shared) 49 | fi 50 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/include/Makefile.am: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/php/libpuzzle/include/Makefile.am -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/libpuzzle.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | # include "config.h" 3 | #endif 4 | 5 | #include "php.h" 6 | #include "php_ini.h" 7 | #include "ext/standard/info.h" 8 | #include 9 | #include "php_libpuzzle.h" 10 | 11 | ZEND_DECLARE_MODULE_GLOBALS(libpuzzle) 12 | 13 | /* True global resources - no need for thread safety here */ 14 | static int le_libpuzzle; 15 | 16 | /* {{{ libpuzzle_functions[] 17 | */ 18 | zend_function_entry libpuzzle_functions[] = { 19 | PHP_FE(puzzle_set_max_width, NULL) 20 | PHP_FE(puzzle_set_max_height, NULL) 21 | PHP_FE(puzzle_set_lambdas, NULL) 22 | PHP_FE(puzzle_set_noise_cutoff, NULL) 23 | PHP_FE(puzzle_set_p_ratio, NULL) 24 | PHP_FE(puzzle_set_contrast_barrier_for_cropping, NULL) 25 | PHP_FE(puzzle_set_max_cropping_ratio, NULL) 26 | PHP_FE(puzzle_set_autocrop, NULL) 27 | 28 | PHP_FE(puzzle_fill_cvec_from_file, NULL) 29 | PHP_FE(puzzle_compress_cvec, NULL) 30 | PHP_FE(puzzle_uncompress_cvec, NULL) 31 | PHP_FE(puzzle_vector_normalized_distance, NULL) 32 | 33 | {NULL, NULL, NULL} /* Must be the last line in libpuzzle_functions[] */ 34 | }; 35 | /* }}} */ 36 | 37 | /* {{{ libpuzzle_module_entry 38 | */ 39 | zend_module_entry libpuzzle_module_entry = { 40 | #if ZEND_MODULE_API_NO >= 20010901 41 | STANDARD_MODULE_HEADER, 42 | #endif 43 | "libpuzzle", 44 | libpuzzle_functions, 45 | PHP_MINIT(libpuzzle), 46 | PHP_MSHUTDOWN(libpuzzle), 47 | PHP_RINIT(libpuzzle), /* Replace with NULL if there's nothing to do at request start */ 48 | PHP_RSHUTDOWN(libpuzzle), /* Replace with NULL if there's nothing to do at request end */ 49 | PHP_MINFO(libpuzzle), 50 | #if ZEND_MODULE_API_NO >= 20010901 51 | "0.10", /* Replace with version number for your extension */ 52 | #endif 53 | STANDARD_MODULE_PROPERTIES 54 | }; 55 | /* }}} */ 56 | 57 | #ifdef COMPILE_DL_LIBPUZZLE 58 | ZEND_GET_MODULE(libpuzzle) 59 | #endif 60 | 61 | 62 | /* {{{ PHP_MINIT_FUNCTION 63 | */ 64 | PHP_MINIT_FUNCTION(libpuzzle) 65 | { 66 | REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_THRESHOLD", 67 | PUZZLE_CVEC_SIMILARITY_THRESHOLD, 68 | CONST_CS | CONST_PERSISTENT); 69 | REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD", 70 | PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD, 71 | CONST_CS | CONST_PERSISTENT); 72 | REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD", 73 | PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD, 74 | CONST_CS | CONST_PERSISTENT); 75 | REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD", 76 | PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD, 77 | CONST_CS | CONST_PERSISTENT); 78 | return SUCCESS; 79 | } 80 | /* }}} */ 81 | 82 | /* {{{ PHP_MSHUTDOWN_FUNCTION 83 | */ 84 | PHP_MSHUTDOWN_FUNCTION(libpuzzle) 85 | { 86 | return SUCCESS; 87 | } 88 | /* }}} */ 89 | 90 | /* Remove if there's nothing to do at request start */ 91 | /* {{{ PHP_RINIT_FUNCTION 92 | */ 93 | PHP_RINIT_FUNCTION(libpuzzle) 94 | { 95 | puzzle_init_context(&LIBPUZZLE_G(global_context)); 96 | return SUCCESS; 97 | } 98 | /* }}} */ 99 | 100 | /* Remove if there's nothing to do at request end */ 101 | /* {{{ PHP_RSHUTDOWN_FUNCTION 102 | */ 103 | PHP_RSHUTDOWN_FUNCTION(libpuzzle) 104 | { 105 | puzzle_free_context(&LIBPUZZLE_G(global_context)); 106 | return SUCCESS; 107 | } 108 | /* }}} */ 109 | 110 | /* {{{ PHP_MINFO_FUNCTION 111 | */ 112 | PHP_MINFO_FUNCTION(libpuzzle) 113 | { 114 | php_info_print_table_start(); 115 | php_info_print_table_header(2, "libpuzzle support", "enabled"); 116 | php_info_print_table_end(); 117 | } 118 | /* }}} */ 119 | 120 | /* {{{ proto string puzzle_fill_cvec_from_file(string filename) 121 | * Creates a signature out of an image file */ 122 | PHP_FUNCTION(puzzle_fill_cvec_from_file) 123 | { 124 | char *arg = NULL; 125 | int arg_len; 126 | PuzzleContext *context; 127 | PuzzleCvec cvec; 128 | 129 | context = &LIBPUZZLE_G(global_context); 130 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 131 | "s", &arg, &arg_len) == FAILURE || 132 | arg_len <= 0) { 133 | RETURN_FALSE; 134 | } 135 | puzzle_init_cvec(context, &cvec); 136 | if (puzzle_fill_cvec_from_file(context, &cvec, arg) != 0) { 137 | puzzle_free_cvec(context, &cvec); 138 | RETURN_FALSE; 139 | } 140 | RETVAL_STRINGL(cvec.vec, cvec.sizeof_vec, 1); 141 | puzzle_free_cvec(context, &cvec); 142 | } 143 | /* }}} */ 144 | 145 | /* {{{ proto string puzzle_compress_cvec(string cvec) 146 | * Compress a signature to save storage space */ 147 | PHP_FUNCTION(puzzle_compress_cvec) 148 | { 149 | char *arg = NULL; 150 | int arg_len; 151 | PuzzleContext *context; 152 | PuzzleCompressedCvec compressed_cvec; 153 | PuzzleCvec cvec; 154 | 155 | context = &LIBPUZZLE_G(global_context); 156 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 157 | "s", &arg, &arg_len) == FAILURE || 158 | arg_len <= 0) { 159 | RETURN_FALSE; 160 | } 161 | puzzle_init_compressed_cvec(context, &compressed_cvec); 162 | puzzle_init_cvec(context, &cvec); 163 | cvec.vec = arg; 164 | cvec.sizeof_vec = (size_t) arg_len; 165 | if (puzzle_compress_cvec(context, &compressed_cvec, &cvec) != 0) { 166 | puzzle_free_compressed_cvec(context, &compressed_cvec); 167 | cvec.vec = NULL; 168 | puzzle_free_cvec(context, &cvec); 169 | RETURN_FALSE; 170 | } 171 | RETVAL_STRINGL(compressed_cvec.vec, 172 | compressed_cvec.sizeof_compressed_vec, 1); 173 | puzzle_free_compressed_cvec(context, &compressed_cvec); 174 | cvec.vec = NULL; 175 | puzzle_free_cvec(context, &cvec); 176 | } 177 | /* }}} */ 178 | 179 | /* {{{ proto string puzzle_uncompress_cvec(string compressed_cvec) 180 | * Uncompress a compressed signature so that it can be used for computations */ 181 | PHP_FUNCTION(puzzle_uncompress_cvec) 182 | { 183 | char *arg = NULL; 184 | int arg_len; 185 | PuzzleContext *context; 186 | PuzzleCompressedCvec compressed_cvec; 187 | PuzzleCvec cvec; 188 | 189 | context = &LIBPUZZLE_G(global_context); 190 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 191 | "s", &arg, &arg_len) == FAILURE || 192 | arg_len <= 0) { 193 | RETURN_FALSE; 194 | } 195 | puzzle_init_compressed_cvec(context, &compressed_cvec); 196 | puzzle_init_cvec(context, &cvec); 197 | compressed_cvec.vec = arg; 198 | compressed_cvec.sizeof_compressed_vec = (size_t) arg_len; 199 | if (puzzle_uncompress_cvec(context, &compressed_cvec, &cvec) != 0) { 200 | puzzle_free_cvec(context, &cvec); 201 | compressed_cvec.vec = NULL; 202 | puzzle_free_compressed_cvec(context, &compressed_cvec); 203 | RETURN_FALSE; 204 | } 205 | RETVAL_STRINGL(cvec.vec, cvec.sizeof_vec, 1); 206 | puzzle_free_cvec(context, &cvec); 207 | compressed_cvec.vec = NULL; 208 | puzzle_free_compressed_cvec(context, &compressed_cvec); 209 | } 210 | /* }}} */ 211 | 212 | /* {{{ proto double puzzle_vector_normalized_distance(string cvec1, string cvec2 [, bool fix_for_texts]) 213 | * Computes the distance between two signatures. Result is between 0.0 and 1.0 */ 214 | PHP_FUNCTION(puzzle_vector_normalized_distance) 215 | { 216 | char *vec1 = NULL, *vec2 = NULL; 217 | int vec1_len, vec2_len; 218 | PuzzleContext *context; 219 | PuzzleCvec cvec1, cvec2; 220 | double d; 221 | zend_bool fix_for_texts; 222 | 223 | context = &LIBPUZZLE_G(global_context); 224 | if (zend_parse_parameters 225 | (ZEND_NUM_ARGS() TSRMLS_CC, "ss|b", 226 | &vec1, &vec1_len, &vec2, &vec2_len, &fix_for_texts) == FAILURE || 227 | vec1_len <= 0 || vec2_len <= 0) { 228 | RETURN_FALSE; 229 | } 230 | if (ZEND_NUM_ARGS() TSRMLS_CC < 3) { 231 | fix_for_texts = (zend_bool) 1; 232 | } 233 | puzzle_init_cvec(context, &cvec1); 234 | puzzle_init_cvec(context, &cvec2); 235 | cvec1.vec = vec1; 236 | cvec1.sizeof_vec = (size_t) vec1_len; 237 | cvec2.vec = vec2; 238 | cvec2.sizeof_vec = (size_t) vec2_len; 239 | d = puzzle_vector_normalized_distance(context, &cvec1, &cvec2, 240 | (int) fix_for_texts); 241 | cvec1.vec = cvec2.vec = NULL; 242 | puzzle_free_cvec(context, &cvec1); 243 | puzzle_free_cvec(context, &cvec2); 244 | RETVAL_DOUBLE(d); 245 | } 246 | /* }}} */ 247 | 248 | /* {{{ proto bool puzzle_set_max_width(int width) 249 | * Set the maximum picture width */ 250 | PHP_FUNCTION(puzzle_set_max_width) 251 | { 252 | PuzzleContext *context; 253 | long width; 254 | 255 | context = &LIBPUZZLE_G(global_context); 256 | if (zend_parse_parameters 257 | (ZEND_NUM_ARGS() TSRMLS_CC, "l", &width) == FAILURE || 258 | width <= 0L || width > INT_MAX) { 259 | RETURN_FALSE; 260 | } 261 | if (puzzle_set_max_width(context, (unsigned int) width) != 0) { 262 | RETURN_FALSE; 263 | } 264 | RETVAL_TRUE; 265 | } 266 | /* }}} */ 267 | 268 | /* {{{ proto bool puzzle_set_max_height(int height) 269 | * Set the maximum picture height */ 270 | PHP_FUNCTION(puzzle_set_max_height) 271 | { 272 | PuzzleContext *context; 273 | long height; 274 | 275 | context = &LIBPUZZLE_G(global_context); 276 | if (zend_parse_parameters 277 | (ZEND_NUM_ARGS() TSRMLS_CC, "l", &height) == FAILURE || 278 | height <= 0L || height > INT_MAX) { 279 | RETURN_FALSE; 280 | } 281 | if (puzzle_set_max_height(context, (unsigned int) height) != 0) { 282 | RETURN_FALSE; 283 | } 284 | RETVAL_TRUE; 285 | } 286 | /* }}} */ 287 | 288 | /* {{{ proto bool puzzle_set_lambdas(int lambdas) 289 | * Set the size of the computation grid */ 290 | PHP_FUNCTION(puzzle_set_lambdas) 291 | { 292 | PuzzleContext *context; 293 | long lambdas; 294 | 295 | context = &LIBPUZZLE_G(global_context); 296 | if (zend_parse_parameters 297 | (ZEND_NUM_ARGS() TSRMLS_CC, "l", &lambdas) == FAILURE || 298 | lambdas <= 0L || lambdas > INT_MAX) { 299 | RETURN_FALSE; 300 | } 301 | if (puzzle_set_lambdas(context, (unsigned int) lambdas) != 0) { 302 | RETURN_FALSE; 303 | } 304 | RETVAL_TRUE; 305 | } 306 | /* }}} */ 307 | 308 | /* {{{ proto bool puzzle_set_noise_cutoff(double cutoff) 309 | * Set the noise cutoff level */ 310 | PHP_FUNCTION(puzzle_set_noise_cutoff) 311 | { 312 | PuzzleContext *context; 313 | double cutoff; 314 | 315 | context = &LIBPUZZLE_G(global_context); 316 | if (zend_parse_parameters 317 | (ZEND_NUM_ARGS() TSRMLS_CC, "d", &cutoff) == FAILURE) { 318 | RETURN_FALSE; 319 | } 320 | if (puzzle_set_noise_cutoff(context, cutoff) != 0) { 321 | RETURN_FALSE; 322 | } 323 | RETVAL_TRUE; 324 | } 325 | /* }}} */ 326 | 327 | /* {{{ proto bool puzzle_set_p_ratio(double ratio) 328 | * Set the p_ratio */ 329 | PHP_FUNCTION(puzzle_set_p_ratio) 330 | { 331 | PuzzleContext *context; 332 | double p_ratio; 333 | 334 | context = &LIBPUZZLE_G(global_context); 335 | if (zend_parse_parameters 336 | (ZEND_NUM_ARGS() TSRMLS_CC, "d", &p_ratio) == FAILURE) { 337 | RETURN_FALSE; 338 | } 339 | if (puzzle_set_p_ratio(context, p_ratio) != 0) { 340 | RETURN_FALSE; 341 | } 342 | RETVAL_TRUE; 343 | } 344 | /* }}} */ 345 | 346 | /* {{{ proto bool puzzle_set_contrast_barrier_for_cropping(double barrier) 347 | * Set the tolerance level for cropping */ 348 | PHP_FUNCTION(puzzle_set_contrast_barrier_for_cropping) 349 | { 350 | PuzzleContext *context; 351 | double barrier; 352 | 353 | context = &LIBPUZZLE_G(global_context); 354 | if (zend_parse_parameters 355 | (ZEND_NUM_ARGS() TSRMLS_CC, "d", &barrier) == FAILURE) { 356 | RETURN_FALSE; 357 | } 358 | if (puzzle_set_contrast_barrier_for_cropping(context, barrier) != 0) { 359 | RETURN_FALSE; 360 | } 361 | RETVAL_TRUE; 362 | } 363 | /* }}} */ 364 | 365 | /* {{{ proto bool puzzle_set_max_cropping_ratio(double ratio) 366 | * Set the maximum ratio between the cropped area and the whole picture */ 367 | PHP_FUNCTION(puzzle_set_max_cropping_ratio) 368 | { 369 | PuzzleContext *context; 370 | double ratio; 371 | 372 | context = &LIBPUZZLE_G(global_context); 373 | if (zend_parse_parameters 374 | (ZEND_NUM_ARGS() TSRMLS_CC, "d", &ratio) == FAILURE) { 375 | RETURN_FALSE; 376 | } 377 | if (puzzle_set_max_cropping_ratio(context, ratio) != 0) { 378 | RETURN_FALSE; 379 | } 380 | RETVAL_TRUE; 381 | } 382 | /* }}} */ 383 | 384 | /* {{{ proto bool puzzle_set_autocrop(bool autocrop) 385 | * TRUE to enable autocropping, FALSE to disable */ 386 | PHP_FUNCTION(puzzle_set_autocrop) 387 | { 388 | PuzzleContext *context; 389 | zend_bool autocrop; 390 | 391 | context = &LIBPUZZLE_G(global_context); 392 | if (zend_parse_parameters 393 | (ZEND_NUM_ARGS() TSRMLS_CC, "b", &autocrop) == FAILURE) { 394 | RETURN_FALSE; 395 | } 396 | if (puzzle_set_autocrop(context, (int) autocrop) != 0) { 397 | RETURN_FALSE; 398 | } 399 | RETVAL_TRUE; 400 | } 401 | /* }}} */ 402 | 403 | /* 404 | * Local variables: 405 | * tab-width: 4 406 | * c-basic-offset: 4 407 | * End: 408 | * vim600: noet sw=4 ts=4 fdm=marker 409 | * vim<600: noet sw=4 ts=4 410 | */ 411 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/libpuzzle.php: -------------------------------------------------------------------------------- 1 | "; 3 | 4 | if(!extension_loaded('libpuzzle')) { 5 | dl('libpuzzle.' . PHP_SHLIB_SUFFIX); 6 | } 7 | $module = 'libpuzzle'; 8 | $functions = get_extension_funcs($module); 9 | echo "Functions available in the test extension:$br\n"; 10 | foreach($functions as $func) { 11 | echo $func."$br\n"; 12 | } 13 | echo "$br\n"; 14 | $function = 'confirm_' . $module . '_compiled'; 15 | if (extension_loaded($module)) { 16 | $str = $function($module); 17 | } else { 18 | $str = "Module $module is not compiled into PHP"; 19 | } 20 | echo "$str\n"; 21 | ?> 22 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/modules/Makefile.am: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/php/libpuzzle/modules/Makefile.am -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/php_libpuzzle.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_LIBPUZZLE_H 2 | #define PHP_LIBPUZZLE_H 3 | 4 | extern zend_module_entry libpuzzle_module_entry; 5 | #define phpext_libpuzzle_ptr &libpuzzle_module_entry 6 | 7 | #ifdef PHP_WIN32 8 | #define PHP_LIBPUZZLE_API __declspec(dllexport) 9 | #else 10 | #define PHP_LIBPUZZLE_API 11 | #endif 12 | 13 | #ifdef ZTS 14 | #include "TSRM.h" 15 | #endif 16 | 17 | PHP_MINIT_FUNCTION(libpuzzle); 18 | PHP_MSHUTDOWN_FUNCTION(libpuzzle); 19 | PHP_RINIT_FUNCTION(libpuzzle); 20 | PHP_RSHUTDOWN_FUNCTION(libpuzzle); 21 | PHP_MINFO_FUNCTION(libpuzzle); 22 | 23 | PHP_FUNCTION(puzzle_set_max_width); 24 | PHP_FUNCTION(puzzle_set_max_height); 25 | PHP_FUNCTION(puzzle_set_lambdas); 26 | PHP_FUNCTION(puzzle_set_noise_cutoff); 27 | PHP_FUNCTION(puzzle_set_p_ratio); 28 | PHP_FUNCTION(puzzle_set_contrast_barrier_for_cropping); 29 | PHP_FUNCTION(puzzle_set_max_cropping_ratio); 30 | PHP_FUNCTION(puzzle_set_autocrop); 31 | 32 | PHP_FUNCTION(puzzle_fill_cvec_from_file); 33 | PHP_FUNCTION(puzzle_compress_cvec); 34 | PHP_FUNCTION(puzzle_uncompress_cvec); 35 | PHP_FUNCTION(puzzle_vector_normalized_distance); 36 | 37 | ZEND_BEGIN_MODULE_GLOBALS(libpuzzle) 38 | PuzzleContext global_context; 39 | ZEND_END_MODULE_GLOBALS(libpuzzle) 40 | 41 | /* In every utility function you add that needs to use variables 42 | in php_libpuzzle_globals, call TSRMLS_FETCH(); after declaring other 43 | variables used by that function, or better yet, pass in TSRMLS_CC 44 | after the last function argument and declare your utility function 45 | with TSRMLS_DC after the last declared argument. Always refer to 46 | the globals in your function as LIBPUZZLE_G(variable). You are 47 | encouraged to rename these macros something shorter, see 48 | examples in any other php module directory. 49 | */ 50 | 51 | #ifdef ZTS 52 | #define LIBPUZZLE_G(v) TSRMG(libpuzzle_globals_id, zend_libpuzzle_globals *, v) 53 | #else 54 | #define LIBPUZZLE_G(v) (libpuzzle_globals.v) 55 | #endif 56 | 57 | #endif /* PHP_LIBPUZZLE_H */ 58 | 59 | /* 60 | * Local variables: 61 | * tab-width: 4 62 | * c-basic-offset: 4 63 | * End: 64 | * vim600: noet sw=4 ts=4 fdm=marker 65 | * vim<600: noet sw=4 ts=4 66 | */ 67 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/tests/001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check for libpuzzle presence 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 9 | --EXPECT-- 10 | libpuzzle extension is available 11 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/tests/002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check for distance between similar images 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 14 | --EXPECT-- 15 | 1 16 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/tests/003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check the puzzle_set(3) interface 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 23 | --EXPECT-- 24 | 1 25 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/tests/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = \ 2 | 001.phpt \ 3 | 002.phpt \ 4 | 003.phpt 5 | 6 | SUBDIRS = \ 7 | pics 8 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/tests/pics/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = \ 2 | pic-a-0.jpg \ 3 | pic-a-1.jpg 4 | -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/tests/pics/pic-a-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/php/libpuzzle/tests/pics/pic-a-0.jpg -------------------------------------------------------------------------------- /libpuzzle/php/libpuzzle/tests/pics/pic-a-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/php/libpuzzle/tests/pics/pic-a-1.jpg -------------------------------------------------------------------------------- /libpuzzle/src/Makefile.am: -------------------------------------------------------------------------------- 1 | lib_LTLIBRARIES = \ 2 | libpuzzle.la 3 | 4 | libpuzzle_la_LDFLAGS = -version-info 1:0 5 | 6 | libpuzzle_la_SOURCES = \ 7 | puzzle.c \ 8 | tunables.c \ 9 | dvec.c \ 10 | cvec.c \ 11 | compress.c \ 12 | vector_ops.c \ 13 | puzzle_common.h \ 14 | puzzle_p.h \ 15 | globals.h \ 16 | puzzle.h 17 | 18 | include_HEADERS = \ 19 | puzzle.h 20 | 21 | noinst_HEADERS = \ 22 | puzzle_common.h \ 23 | puzzle_p.h \ 24 | globals.h 25 | 26 | bin_PROGRAMS = \ 27 | puzzle-diff 28 | 29 | puzzle_diff_SOURCES = \ 30 | puzzle-diff.c \ 31 | puzzle_common.h \ 32 | puzzle.h 33 | 34 | puzzle_diff_LDADD = \ 35 | libpuzzle.la 36 | 37 | TESTS = \ 38 | regress_1 \ 39 | regress_2 \ 40 | regress_3 41 | 42 | check_PROGRAMS = \ 43 | regress_1 \ 44 | regress_2 \ 45 | regress_3 46 | 47 | regress_1_SOURCES = \ 48 | regress_1.c \ 49 | puzzle_common.h \ 50 | puzzle.h 51 | 52 | regress_2_SOURCES = \ 53 | regress_2.c \ 54 | puzzle_common.h \ 55 | puzzle.h 56 | 57 | regress_3_SOURCES = \ 58 | regress_3.c \ 59 | puzzle_common.h \ 60 | puzzle.h 61 | 62 | regress_1_LDADD = \ 63 | libpuzzle.la 64 | 65 | regress_2_LDADD = \ 66 | libpuzzle.la 67 | 68 | regress_3_LDADD = \ 69 | libpuzzle.la 70 | 71 | SUBDIRS = \ 72 | pics 73 | -------------------------------------------------------------------------------- /libpuzzle/src/compress.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle_p.h" 3 | #include "puzzle.h" 4 | #include "globals.h" 5 | 6 | void puzzle_init_compressed_cvec(PuzzleContext * const context, 7 | PuzzleCompressedCvec * const compressed_cvec) 8 | { 9 | (void) context; 10 | compressed_cvec->sizeof_compressed_vec = (size_t) 0U; 11 | compressed_cvec->vec = NULL; 12 | } 13 | 14 | void puzzle_free_compressed_cvec(PuzzleContext * const context, 15 | PuzzleCompressedCvec * const compressed_cvec) 16 | { 17 | (void) context; 18 | free(compressed_cvec->vec); 19 | compressed_cvec->vec = NULL; 20 | } 21 | 22 | int puzzle_compress_cvec(PuzzleContext * const context, 23 | PuzzleCompressedCvec * const compressed_cvec, 24 | const PuzzleCvec * const cvec) 25 | { 26 | #define PC_NM(X) ((unsigned char) ((X) + 2)) 27 | size_t remaining = cvec->sizeof_vec; 28 | const signed char *ptr; 29 | unsigned char *cptr; 30 | 31 | (void) context; 32 | compressed_cvec->sizeof_compressed_vec = 33 | (cvec->sizeof_vec + (size_t) 2U) / (size_t) 3U; 34 | if ((compressed_cvec->vec = 35 | calloc(compressed_cvec->sizeof_compressed_vec, 36 | sizeof *compressed_cvec->vec)) == NULL) { 37 | return -1; 38 | } 39 | ptr = cvec->vec; 40 | cptr = compressed_cvec->vec; 41 | while (remaining >= (size_t) 3U) { 42 | *cptr++ = PC_NM(ptr[0]) + PC_NM(ptr[1]) * 5U + 43 | PC_NM(ptr[2]) * (5U * 5U); 44 | ptr += 3U; 45 | remaining -= 3U; 46 | } 47 | if (remaining == (size_t) 1U) { 48 | *cptr++ = PC_NM(ptr[0]); 49 | compressed_cvec->vec[0] |= 128U; 50 | } else if (remaining == (size_t) 2U) { 51 | *cptr++ = PC_NM(ptr[0]) + PC_NM(ptr[1]) * 5U; 52 | if (compressed_cvec->sizeof_compressed_vec < (size_t) 2U) { 53 | puzzle_err_bug(__FILE__, __LINE__); 54 | } 55 | compressed_cvec->vec[1] |= 128U; 56 | } 57 | if ((size_t) (cptr - compressed_cvec->vec) != 58 | compressed_cvec->sizeof_compressed_vec) { 59 | puzzle_err_bug(__FILE__, __LINE__); 60 | } 61 | return 0; 62 | } 63 | 64 | int puzzle_uncompress_cvec(PuzzleContext * const context, 65 | const PuzzleCompressedCvec * const compressed_cvec, 66 | PuzzleCvec * const cvec) 67 | { 68 | #define PC_FL(X) ((X) & 127U) 69 | #define PC_NP(X) ((signed char) (X) - 2) 70 | 71 | size_t remaining; 72 | unsigned char trailing_bits; 73 | const unsigned char *cptr = compressed_cvec->vec; 74 | signed char *ptr; 75 | unsigned char c; 76 | 77 | (void) context; 78 | if (cvec->vec != NULL) { 79 | puzzle_err_bug(__FILE__, __LINE__); 80 | } 81 | if ((remaining = compressed_cvec->sizeof_compressed_vec) < (size_t) 2U) { 82 | puzzle_err_bug(__FILE__, __LINE__); 83 | } 84 | trailing_bits = ((cptr[0] & 128U) >> 7) | ((cptr[1] & 128U) >> 6); 85 | if (trailing_bits > 2U) { 86 | puzzle_err_bug(__FILE__, __LINE__); 87 | } 88 | cvec->sizeof_vec = (size_t) 3U * 89 | (compressed_cvec->sizeof_compressed_vec - trailing_bits) + 90 | trailing_bits; 91 | if (compressed_cvec->sizeof_compressed_vec > 92 | SIZE_MAX / (size_t) 3U - (size_t) 2U) { 93 | puzzle_err_bug(__FILE__, __LINE__); 94 | } 95 | if ((cvec->vec = calloc(cvec->sizeof_vec, sizeof *cvec->vec)) == NULL) { 96 | return -1; 97 | } 98 | if (trailing_bits != 0U) { 99 | if (remaining <= (size_t) 0U) { 100 | puzzle_err_bug(__FILE__, __LINE__); 101 | } 102 | remaining--; 103 | } 104 | ptr = cvec->vec; 105 | while (remaining > (size_t) 0U) { 106 | c = PC_FL(*cptr++); 107 | *ptr++ = PC_NP(c % 5U); 108 | c /= 5U; 109 | *ptr++ = PC_NP(c % 5U); 110 | c /= 5U; 111 | *ptr++ = PC_NP(c % 5U); 112 | remaining--; 113 | } 114 | if (trailing_bits == 1U) { 115 | *ptr++ = PC_NP(PC_FL(*cptr) % 5U); 116 | } else if (trailing_bits == 2U) { 117 | c = PC_FL(*cptr); 118 | *ptr++ = PC_NP(c % 5U); 119 | *ptr++ = PC_NP(c / 5U % 5U); 120 | } 121 | if ((size_t) (ptr - cvec->vec) != cvec->sizeof_vec) { 122 | puzzle_err_bug(__FILE__, __LINE__); 123 | } 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /libpuzzle/src/cvec.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle_p.h" 3 | #include "puzzle.h" 4 | #include "globals.h" 5 | 6 | static int puzzle_median_cmp(const void * const a_, const void * const b_) 7 | { 8 | const double a = * (const double *) a_; 9 | const double b = * (const double *) b_; 10 | 11 | if (a < b) { 12 | return -1; 13 | } else if (a > b) { 14 | return 1; 15 | } 16 | return 0; 17 | } 18 | 19 | static double puzzle_median(double * const vec, size_t size) 20 | { 21 | size_t n; 22 | size_t o; 23 | double avg; 24 | 25 | if (size <= (size_t) 0U) { 26 | return 0.0; 27 | } 28 | qsort((void *) vec, size, sizeof *vec, puzzle_median_cmp); 29 | if ((n = size / (size_t) 2U) == (size_t) 0U) { 30 | if (size > (size_t) 1U) { 31 | o = (size_t) 1U; 32 | } else { 33 | o = (size_t) 0U; 34 | } 35 | } else { 36 | o = n + (size_t) 1U; 37 | } 38 | if (o < n) { 39 | puzzle_err_bug(__FILE__, __LINE__); 40 | } 41 | avg = (vec[n] + vec[o]) / 2.0; 42 | if (avg < vec[n] || avg > vec[o]) { 43 | avg = vec[n]; 44 | } 45 | return avg; 46 | } 47 | 48 | int puzzle_fill_cvec_from_dvec(PuzzleContext * const context, 49 | PuzzleCvec * const cvec, 50 | const PuzzleDvec * const dvec) 51 | { 52 | size_t s; 53 | const double *dvecptr; 54 | signed char *cvecptr; 55 | double *lights = NULL, *darks = NULL; 56 | size_t pos_lights = (size_t) 0U, pos_darks = (size_t) 0U; 57 | size_t sizeof_lights, sizeof_darks; 58 | double lighter_cutoff, darker_cutoff; 59 | int err = 0; 60 | double dv; 61 | 62 | if ((cvec->sizeof_vec = dvec->sizeof_compressed_vec) <= (size_t) 0U) { 63 | puzzle_err_bug(__FILE__, __LINE__); 64 | } 65 | if ((cvec->vec = calloc(cvec->sizeof_vec, sizeof *cvec->vec)) == NULL) { 66 | return -1; 67 | } 68 | sizeof_lights = sizeof_darks = cvec->sizeof_vec; 69 | if ((lights = calloc(sizeof_lights, sizeof *lights)) == NULL || 70 | (darks = calloc(sizeof_darks, sizeof *darks)) == NULL) { 71 | err = -1; 72 | goto out; 73 | } 74 | dvecptr = dvec->vec; 75 | s = cvec->sizeof_vec; 76 | do { 77 | dv = *dvecptr++; 78 | if (dv >= - context->puzzle_noise_cutoff && 79 | dv <= context->puzzle_noise_cutoff) { 80 | continue; 81 | } 82 | if (dv < context->puzzle_noise_cutoff) { 83 | darks[pos_darks++] = dv; 84 | if (pos_darks > sizeof_darks) { 85 | puzzle_err_bug(__FILE__, __LINE__); 86 | } 87 | } else if (dv > context->puzzle_noise_cutoff) { 88 | lights[pos_lights++] = dv; 89 | if (pos_lights > sizeof_lights) { 90 | puzzle_err_bug(__FILE__, __LINE__); 91 | } 92 | } 93 | } while (--s != (size_t) 0U); 94 | lighter_cutoff = puzzle_median(lights, pos_lights); 95 | darker_cutoff = puzzle_median(darks, pos_darks); 96 | free(lights); 97 | lights = NULL; 98 | free(darks); 99 | darks = NULL; 100 | dvecptr = dvec->vec; 101 | cvecptr = cvec->vec; 102 | s = cvec->sizeof_vec; 103 | do { 104 | dv = *dvecptr++; 105 | if (dv >= - context->puzzle_noise_cutoff && 106 | dv <= context->puzzle_noise_cutoff) { 107 | *cvecptr++ = 0; 108 | } else if (dv < 0.0) { 109 | *cvecptr++ = dv < darker_cutoff ? -2 : -1; 110 | } else { 111 | *cvecptr++ = dv > lighter_cutoff ? +2 : +1; 112 | } 113 | } while (--s != (size_t) 0U); 114 | if ((size_t) (cvecptr - cvec->vec) != cvec->sizeof_vec) { 115 | puzzle_err_bug(__FILE__, __LINE__); 116 | } 117 | out: 118 | free(lights); 119 | free(darks); 120 | 121 | return err; 122 | } 123 | 124 | void puzzle_init_cvec(PuzzleContext * const context, PuzzleCvec * const cvec) 125 | { 126 | (void) context; 127 | cvec->sizeof_vec = (size_t) 0U; 128 | cvec->vec = NULL; 129 | } 130 | 131 | void puzzle_free_cvec(PuzzleContext * const context, PuzzleCvec * const cvec) 132 | { 133 | (void) context; 134 | free(cvec->vec); 135 | cvec->vec = NULL; 136 | } 137 | 138 | int puzzle_dump_cvec(PuzzleContext * const context, 139 | const PuzzleCvec * const cvec) 140 | { 141 | size_t s = cvec->sizeof_vec; 142 | const signed char *vecptr = cvec->vec; 143 | 144 | (void) context; 145 | if (s <= (size_t) 0U) { 146 | puzzle_err_bug(__FILE__, __LINE__); 147 | } 148 | do { 149 | printf("%d\n", *vecptr++); 150 | } while (--s != (size_t) 0U); 151 | 152 | return 0; 153 | } 154 | 155 | int puzzle_cvec_cksum(PuzzleContext * const context, 156 | const PuzzleCvec * const cvec, unsigned int * const sum) 157 | { 158 | size_t s = cvec->sizeof_vec; 159 | const signed char *vecptr = cvec->vec; 160 | 161 | (void) context; 162 | *sum = 5381; 163 | do { 164 | *sum += *sum << 5; 165 | *sum ^= (unsigned int) *vecptr++; 166 | } while (--s != (size_t) 0U); 167 | 168 | return 0; 169 | } 170 | 171 | int puzzle_fill_cvec_from_file(PuzzleContext * const context, 172 | PuzzleCvec * const cvec, 173 | const char * const file) 174 | { 175 | PuzzleDvec dvec; 176 | int ret; 177 | 178 | puzzle_init_dvec(context, &dvec); 179 | if ((ret = puzzle_fill_dvec_from_file(context, &dvec, file)) == 0) { 180 | ret = puzzle_fill_cvec_from_dvec(context, cvec, &dvec); 181 | } 182 | puzzle_free_dvec(context, &dvec); 183 | 184 | return ret; 185 | } 186 | 187 | int puzzle_fill_cvec_from_mem(PuzzleContext * const context, 188 | PuzzleCvec * const cvec, 189 | const void * const mem, 190 | const size_t size) 191 | { 192 | PuzzleDvec dvec; 193 | int ret; 194 | 195 | puzzle_init_dvec(context, &dvec); 196 | if ((ret = puzzle_fill_dvec_from_mem(context, &dvec, mem, size)) == 0) { 197 | ret = puzzle_fill_cvec_from_dvec(context, cvec, &dvec); 198 | } 199 | puzzle_free_dvec(context, &dvec); 200 | 201 | return ret; 202 | } 203 | -------------------------------------------------------------------------------- /libpuzzle/src/dvec.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle_p.h" 3 | #include "puzzle.h" 4 | #include "globals.h" 5 | 6 | static void puzzle_init_view(PuzzleView * const view) 7 | { 8 | view->width = view->height = 0U; 9 | view->sizeof_map = (size_t) 0U; 10 | view->map = NULL; 11 | } 12 | 13 | static void puzzle_free_view(PuzzleView * const view) 14 | { 15 | free(view->map); 16 | view->map = NULL; 17 | } 18 | 19 | static void puzzle_init_avglvls(PuzzleAvgLvls * const avglvls) 20 | { 21 | avglvls->lambdas = 0U; 22 | avglvls->sizeof_lvls = (size_t) 0U; 23 | avglvls->lvls = NULL; 24 | } 25 | 26 | static void puzzle_free_avglvls(PuzzleAvgLvls * const avglvls) 27 | { 28 | free(avglvls->lvls); 29 | avglvls->lvls = NULL; 30 | } 31 | 32 | void puzzle_init_dvec(PuzzleContext * const context, PuzzleDvec * const dvec) 33 | { 34 | (void) context; 35 | dvec->sizeof_vec = dvec->sizeof_compressed_vec = (size_t) 0U; 36 | dvec->vec = NULL; 37 | } 38 | 39 | void puzzle_free_dvec(PuzzleContext * const context, PuzzleDvec * const dvec) 40 | { 41 | (void) context; 42 | free(dvec->vec); 43 | dvec->vec = NULL; 44 | } 45 | 46 | #define MAX_SIGNATURE_LENGTH 8U 47 | 48 | static PuzzleImageTypeCode puzzle_get_image_type_from_header(const unsigned char * const header) 49 | { 50 | static const PuzzleImageType image_types[] = { 51 | { (size_t) 4U, (const unsigned char *) 52 | "GIF8", PUZZLE_IMAGE_TYPE_GIF }, 53 | { (size_t) 3U, (const unsigned char *) 54 | "\xff\xd8\xff", PUZZLE_IMAGE_TYPE_JPEG }, 55 | { (size_t) 8U, (const unsigned char *) 56 | "\x89PNG\r\n\x1a\n", PUZZLE_IMAGE_TYPE_PNG }, 57 | { (size_t) 0U, NULL, PUZZLE_IMAGE_TYPE_UNKNOWN } 58 | }; 59 | const PuzzleImageType *image_type = image_types; 60 | PuzzleImageTypeCode ret = PUZZLE_IMAGE_TYPE_UNKNOWN; 61 | do { 62 | if (image_type->sizeof_signature > MAX_SIGNATURE_LENGTH) { 63 | puzzle_err_bug(__FILE__, __LINE__); 64 | } 65 | if (memcmp(header, image_type->signature, 66 | image_type->sizeof_signature) == 0) { 67 | ret = image_type->image_type_code; 68 | break; 69 | } 70 | image_type++; 71 | } while (image_type->signature != NULL); 72 | return ret; 73 | } 74 | 75 | static PuzzleImageTypeCode puzzle_get_image_type_from_fp(FILE * const fp) 76 | { 77 | unsigned char header[MAX_SIGNATURE_LENGTH]; 78 | PuzzleImageTypeCode ret = PUZZLE_IMAGE_TYPE_ERROR; 79 | fpos_t pos; 80 | 81 | if (fgetpos(fp, &pos) != 0) { 82 | return PUZZLE_IMAGE_TYPE_ERROR; 83 | } 84 | rewind(fp); 85 | if (fread(header, (size_t) 1U, sizeof header, fp) != sizeof header) { 86 | goto bye; 87 | } 88 | ret = puzzle_get_image_type_from_header(header); 89 | bye: 90 | if (fsetpos(fp, &pos) != 0) { 91 | puzzle_err_bug(__FILE__, __LINE__); 92 | } 93 | return ret; 94 | } 95 | 96 | static int puzzle_autocrop_axis(PuzzleContext * const context, 97 | PuzzleView * const view, 98 | unsigned int * const crop0, 99 | unsigned int * const crop1, 100 | const unsigned int axisn, 101 | const unsigned int axiso, 102 | const int omaptrinc, const int nmaptrinc) 103 | { 104 | double *chunk_contrasts; 105 | size_t sizeof_chunk_contrasts; 106 | double chunk_contrast = 0.0, total_contrast = 0.0, barrier_contrast; 107 | unsigned char level = 0U; 108 | unsigned char previous_level = 0U; 109 | unsigned int chunk_n, chunk_o; 110 | unsigned int chunk_n1, chunk_o1; 111 | unsigned int max_crop; 112 | const unsigned char *maptr; 113 | 114 | chunk_n1 = axisn - 1U; 115 | chunk_o1 = axiso - 1U; 116 | *crop0 = 0U; 117 | *crop1 = chunk_n1; 118 | if (axisn < (unsigned int) PUZZLE_MIN_SIZE_FOR_CROPPING || 119 | axiso < (unsigned int) PUZZLE_MIN_SIZE_FOR_CROPPING) { 120 | return 1; 121 | } 122 | sizeof_chunk_contrasts = chunk_n1 + 1U; 123 | if ((chunk_contrasts = calloc(sizeof_chunk_contrasts, 124 | sizeof *chunk_contrasts)) == NULL) { 125 | return -1; 126 | } 127 | maptr = view->map; 128 | if (axisn >= INT_MAX || axiso >= INT_MAX) { 129 | puzzle_err_bug(__FILE__, __LINE__); 130 | } 131 | if (INT_MAX / axisn < axiso) { 132 | puzzle_err_bug(__FILE__, __LINE__); 133 | } 134 | chunk_n = chunk_n1; 135 | do { 136 | chunk_contrast = 0.0; 137 | chunk_o = chunk_o1; 138 | previous_level = *maptr; 139 | do { 140 | level = *maptr; 141 | if (previous_level > level) { 142 | chunk_contrast += (double) (previous_level - level); 143 | } else { 144 | chunk_contrast += (double) (level - previous_level); 145 | } 146 | previous_level = level; 147 | maptr += omaptrinc; 148 | } while (chunk_o-- != 0U); 149 | chunk_contrasts[chunk_n] = chunk_contrast; 150 | total_contrast += chunk_contrast; 151 | maptr += nmaptrinc; 152 | } while (chunk_n-- != 0U); 153 | barrier_contrast = 154 | total_contrast * context->puzzle_contrast_barrier_for_cropping; 155 | total_contrast = 0.0; 156 | *crop0 = 0U; 157 | do { 158 | total_contrast += chunk_contrasts[*crop0]; 159 | if (total_contrast >= barrier_contrast) { 160 | break; 161 | } 162 | } while ((*crop0)++ < chunk_n1); 163 | total_contrast = 0.0; 164 | *crop1 = chunk_n1; 165 | do { 166 | total_contrast += chunk_contrasts[*crop1]; 167 | if (total_contrast >= barrier_contrast) { 168 | break; 169 | } 170 | } while ((*crop1)-- > 0U); 171 | free(chunk_contrasts); 172 | if (*crop0 > chunk_n1 || *crop1 > chunk_n1) { 173 | puzzle_err_bug(__FILE__, __LINE__); 174 | } 175 | max_crop = (unsigned int) 176 | round((double) chunk_n1 * context->puzzle_max_cropping_ratio); 177 | if (max_crop > chunk_n1) { 178 | puzzle_err_bug(__FILE__, __LINE__); 179 | } 180 | *crop0 = MIN(*crop0, max_crop); 181 | *crop1 = MAX(*crop1, chunk_n1 - max_crop); 182 | 183 | return 0; 184 | } 185 | 186 | static int puzzle_autocrop_view(PuzzleContext * context, 187 | PuzzleView * const view) 188 | { 189 | unsigned int cropx0, cropx1; 190 | unsigned int cropy0, cropy1; 191 | unsigned int x, y; 192 | unsigned char *maptr; 193 | 194 | if (puzzle_autocrop_axis(context, view, &cropx0, &cropx1, 195 | view->width, view->height, 196 | (int) view->width, 197 | 1 - (int) (view->width * view->height)) < 0 || 198 | puzzle_autocrop_axis(context, view, &cropy0, &cropy1, 199 | view->height, view->width, 200 | 1, 0) < 0) { 201 | return -1; 202 | } 203 | if (cropx0 > cropx1 || cropy0 > cropy1) { 204 | puzzle_err_bug(__FILE__, __LINE__); 205 | } 206 | maptr = view->map; 207 | y = cropy0; 208 | do { 209 | x = cropx0; 210 | do { 211 | *maptr++ = PUZZLE_VIEW_PIXEL(view, x, y); 212 | } while (x++ != cropx1); 213 | } while (y++ != cropy1); 214 | view->width = cropx1 - cropx0 + 1U; 215 | view->height = cropy1 - cropy0 + 1U; 216 | view->sizeof_map = (size_t) view->width * (size_t) view->height; 217 | if (view->width <= 0U || view->height <= 0U || 218 | SIZE_MAX / view->width < view->height) { 219 | puzzle_err_bug(__FILE__, __LINE__); 220 | } 221 | return 0; 222 | } 223 | 224 | static int puzzle_getview_from_gdimage(PuzzleContext * const context, 225 | PuzzleView * const view, 226 | gdImagePtr gdimage) 227 | { 228 | unsigned int x, y; 229 | const unsigned int x0 = 0U, y0 = 0U; 230 | unsigned int x1, y1; 231 | unsigned char *maptr; 232 | int pixel; 233 | 234 | view->map = NULL; 235 | view->width = (unsigned int) gdImageSX(gdimage); 236 | view->height = (unsigned int) gdImageSY(gdimage); 237 | view->sizeof_map = (size_t) (view->width * view->height); 238 | if (view->width > context->puzzle_max_width || 239 | view->height > context->puzzle_max_height) { 240 | return -1; 241 | } 242 | if (view->sizeof_map <= (size_t) 0U || 243 | INT_MAX / view->width < view->height || 244 | SIZE_MAX / view->width < view->height || 245 | (unsigned int) view->sizeof_map != view->sizeof_map) { 246 | puzzle_err_bug(__FILE__, __LINE__); 247 | } 248 | x1 = view->width - 1U; 249 | y1 = view->height - 1U; 250 | if (view->width <= 0U || view->height <= 0U) { 251 | puzzle_err_bug(__FILE__, __LINE__); 252 | } 253 | if ((view->map = calloc(view->sizeof_map, sizeof *view->map)) == NULL) { 254 | return -1; 255 | } 256 | if (x1 > INT_MAX || y1 > INT_MAX) { /* GD uses "int" for coordinates */ 257 | puzzle_err_bug(__FILE__, __LINE__); 258 | } 259 | maptr = view->map; 260 | x = x1; 261 | if (gdImageTrueColor(gdimage) != 0) { 262 | do { 263 | y = y1; 264 | do { 265 | pixel = gdImageGetTrueColorPixel(gdimage, (int) x, (int) y); 266 | *maptr++ = (unsigned char) 267 | ((gdTrueColorGetRed(pixel) * 77 + 268 | gdTrueColorGetGreen(pixel) * 151 + 269 | gdTrueColorGetBlue(pixel) * 28 + 128) / 256); 270 | } while (y-- != y0); 271 | } while (x-- != x0); 272 | } else { 273 | do { 274 | y = y1; 275 | do { 276 | pixel = gdImagePalettePixel(gdimage, x, y); 277 | *maptr++ = (unsigned char) 278 | ((gdimage->red[pixel] * 77 + 279 | gdimage->green[pixel] * 151 + 280 | gdimage->blue[pixel] * 28 + 128) / 256); 281 | } while (y-- != y0); 282 | } while (x-- != x0); 283 | } 284 | return 0; 285 | } 286 | 287 | static double puzzle_softedgedlvl(const PuzzleView * const view, 288 | const unsigned int x, const unsigned int y) 289 | { 290 | unsigned int lvl = 0U; 291 | unsigned int ax, ay; 292 | unsigned int count = 0U; 293 | const unsigned int xlimit = x + PUZZLE_PIXEL_FUZZ_SIZE; 294 | const unsigned int ylimit = y + PUZZLE_PIXEL_FUZZ_SIZE; 295 | if (x >= view->width || y >= view->height || xlimit <= x || ylimit <= y) { 296 | puzzle_err_bug(__FILE__, __LINE__); 297 | } 298 | if (x > PUZZLE_PIXEL_FUZZ_SIZE) { 299 | ax = x - PUZZLE_PIXEL_FUZZ_SIZE; 300 | } else { 301 | ax = 0U; 302 | } 303 | do { 304 | if (ax >= view->width) { 305 | break; 306 | } 307 | if (y > PUZZLE_PIXEL_FUZZ_SIZE) { 308 | ay = y - PUZZLE_PIXEL_FUZZ_SIZE; 309 | } else { 310 | ay = 0U; 311 | } 312 | do { 313 | if (ay >= view->height) { 314 | break; 315 | } 316 | count++; 317 | lvl += (unsigned int) PUZZLE_VIEW_PIXEL(view, ax, ay); 318 | } while (ay++ < ylimit); 319 | } while (ax++ < xlimit); 320 | if (count <= 0U) { 321 | return 0.0; 322 | } 323 | return (double) lvl / (double) count; 324 | } 325 | 326 | static double puzzle_get_avglvl(const PuzzleView * const view, 327 | const unsigned int x, const unsigned int y, 328 | const unsigned int width, 329 | const unsigned int height) 330 | { 331 | double lvl = 0.0; 332 | const unsigned int xlimit = x + width - 1U; 333 | const unsigned int ylimit = y + height - 1U; 334 | unsigned int ax, ay; 335 | 336 | if (width <= 0U || height <= 0U) { 337 | puzzle_err_bug(__FILE__, __LINE__); 338 | } 339 | if (xlimit < x || ylimit < y) { 340 | puzzle_err_bug(__FILE__, __LINE__); 341 | } 342 | ax = x; 343 | do { 344 | if (ax >= view->width) { 345 | puzzle_err_bug(__FILE__, __LINE__); 346 | } 347 | ay = y; 348 | do { 349 | if (ay >= view->height) { 350 | puzzle_err_bug(__FILE__, __LINE__); 351 | } 352 | lvl += puzzle_softedgedlvl(view, ax, ay); 353 | } while (ay++ < ylimit); 354 | } while (ax++ < xlimit); 355 | 356 | return lvl / (double) (width * height); 357 | } 358 | 359 | static int puzzle_fill_avglgls(PuzzleContext * const context, 360 | PuzzleAvgLvls * const avglvls, 361 | const PuzzleView * const view, 362 | const unsigned int lambdas) 363 | { 364 | double width = (double) view->width; 365 | double height = (double) view->height; 366 | double xshift, yshift; 367 | double x, y; 368 | unsigned int p; 369 | unsigned int lx, ly; 370 | unsigned int xd, yd; 371 | unsigned int px, py; 372 | unsigned int lwidth, lheight; 373 | double avglvl; 374 | 375 | avglvls->lambdas = lambdas; 376 | avglvls->sizeof_lvls = (size_t) lambdas * lambdas; 377 | if (UINT_MAX / lambdas < lambdas || 378 | (unsigned int) avglvls->sizeof_lvls != avglvls->sizeof_lvls) { 379 | puzzle_err_bug(__FILE__, __LINE__); 380 | } 381 | if ((avglvls->lvls = calloc(avglvls->sizeof_lvls, 382 | sizeof *avglvls->lvls)) == NULL) { 383 | return -1; 384 | } 385 | xshift = (width - 386 | (width * (double) lambdas / (double) SUCC(lambdas))) / 2.0; 387 | yshift = (height - 388 | (height * (double) lambdas / (double) SUCC(lambdas))) / 2.0; 389 | p = (unsigned int) round(MIN(width, height) / 390 | (SUCC(lambdas) * context->puzzle_p_ratio)); 391 | if (p < PUZZLE_MIN_P) { 392 | p = PUZZLE_MIN_P; 393 | } 394 | lx = 0U; 395 | do { 396 | ly = 0U; 397 | do { 398 | x = xshift + (double) lx * PRED(width) / SUCC(lambdas); 399 | y = yshift + (double) ly * PRED(height) / SUCC(lambdas); 400 | lwidth = (unsigned int) round 401 | (xshift + (double) SUCC(lx) * PRED(width) / 402 | (double) SUCC(lambdas) - x); 403 | lheight = (unsigned int) round 404 | (yshift + (double) SUCC(ly) * PRED(height) / 405 | (double) SUCC(lambdas) - y); 406 | if (p < lwidth) { 407 | xd = (unsigned int) round(x + (lwidth - p) / 2.0); 408 | } else { 409 | xd = (unsigned int) round(x); 410 | } 411 | if (p < lheight) { 412 | yd = (unsigned int) round(y + (lheight - p) / 2.0); 413 | } else { 414 | yd = (unsigned int) round(y); 415 | } 416 | if (view->width - xd < p) { 417 | px = 1U; 418 | } else { 419 | px = p; 420 | } 421 | if (view->height - yd < p) { 422 | py = 1U; 423 | } else { 424 | py = p; 425 | } 426 | if (px > 0U && py > 0U) { 427 | avglvl = puzzle_get_avglvl(view, xd, yd, px, py); 428 | } else { 429 | avglvl = 0.0; 430 | } 431 | PUZZLE_AVGLVL(avglvls, lx, ly) = avglvl; 432 | } while (++ly < lambdas); 433 | } while (++lx < lambdas); 434 | 435 | return 0; 436 | } 437 | 438 | static unsigned int puzzle_add_neighbors(double ** const vecur, 439 | const unsigned int max_neighbors, 440 | const PuzzleAvgLvls * const avglvls, 441 | const unsigned int lx, 442 | const unsigned int ly) 443 | { 444 | unsigned int ax, ay; 445 | unsigned int xlimit, ylimit; 446 | unsigned int neighbors = 0U; 447 | const double ref = PUZZLE_AVGLVL(avglvls, lx, ly); 448 | 449 | if (max_neighbors != 8U) { 450 | puzzle_err_bug(__FILE__, __LINE__); 451 | } 452 | if (lx >= avglvls->lambdas - 1U) { 453 | xlimit = avglvls->lambdas - 1U; 454 | } else { 455 | xlimit = lx + 1U; 456 | } 457 | if (ly >= avglvls->lambdas - 1U) { 458 | ylimit = avglvls->lambdas - 1U; 459 | } else { 460 | ylimit = ly + 1U; 461 | } 462 | if (lx <= 0U) { 463 | ax = 0U; 464 | } else { 465 | ax = lx - 1U; 466 | } 467 | do { 468 | if (ly <= 0U) { 469 | ay = 0U; 470 | } else { 471 | ay = ly - 1U; 472 | } 473 | do { 474 | if (ax == lx && ay == ly) { 475 | continue; 476 | } 477 | *(*vecur)++ = ref - PUZZLE_AVGLVL(avglvls, ax, ay); 478 | neighbors++; 479 | if (neighbors <= 0U) { 480 | puzzle_err_bug(__FILE__, __LINE__); 481 | } 482 | } while (ay++ < ylimit); 483 | } while (ax++ < xlimit); 484 | if (neighbors > max_neighbors) { 485 | puzzle_err_bug(__FILE__, __LINE__); 486 | } 487 | return neighbors; 488 | } 489 | 490 | static int puzzle_fill_dvec(PuzzleDvec * const dvec, 491 | const PuzzleAvgLvls * const avglvls) 492 | { 493 | unsigned int lambdas; 494 | unsigned int lx, ly; 495 | double *vecur; 496 | 497 | lambdas = avglvls->lambdas; 498 | dvec->sizeof_compressed_vec = (size_t) 0U; 499 | dvec->sizeof_vec = (size_t) (lambdas * lambdas * PUZZLE_NEIGHBORS); 500 | if (SIZE_MAX / 501 | ((size_t) (lambdas * lambdas)) < (size_t) PUZZLE_NEIGHBORS || 502 | (unsigned int) dvec->sizeof_vec != dvec->sizeof_vec) { 503 | puzzle_err_bug(__FILE__, __LINE__); 504 | } 505 | if ((dvec->vec = calloc(dvec->sizeof_vec, sizeof *dvec->vec)) == NULL) { 506 | return -1; 507 | } 508 | vecur = dvec->vec; 509 | lx = 0U; 510 | do { 511 | ly = 0U; 512 | do { 513 | (void) puzzle_add_neighbors(&vecur, PUZZLE_NEIGHBORS, 514 | avglvls, lx, ly); 515 | } while (++ly < lambdas); 516 | } while (++lx < lambdas); 517 | dvec->sizeof_compressed_vec = (size_t) (vecur - dvec->vec); 518 | 519 | return 0; 520 | } 521 | 522 | static void puzzle_remove_transparency(gdImagePtr gdimage) 523 | { 524 | int background = gdTrueColor(255, 255, 255); 525 | int x, y, cpix; 526 | 527 | gdImagePaletteToTrueColor(gdimage); 528 | 529 | for (y = 0; y < gdImageSY(gdimage); y++) { 530 | for (x = 0; x < gdImageSX(gdimage); x++) { 531 | cpix = gdImageGetTrueColorPixel(gdimage, x, y); 532 | gdImageSetPixel(gdimage, x, y, gdAlphaBlend(background, cpix)); 533 | } 534 | } 535 | } 536 | 537 | static gdImagePtr puzzle_create_gdimage_from_file(const char * const file) 538 | { 539 | gdImagePtr gdimage = NULL; 540 | FILE *fp; 541 | PuzzleImageTypeCode image_type_code; 542 | if ((fp = fopen(file, "rb")) == NULL) { 543 | return NULL; 544 | } 545 | image_type_code = puzzle_get_image_type_from_fp(fp); 546 | switch (image_type_code) { 547 | case PUZZLE_IMAGE_TYPE_JPEG: 548 | gdimage = gdImageCreateFromJpeg(fp); 549 | break; 550 | case PUZZLE_IMAGE_TYPE_PNG: 551 | gdimage = gdImageCreateFromPng(fp); 552 | break; 553 | case PUZZLE_IMAGE_TYPE_GIF: 554 | gdimage = gdImageCreateFromGif(fp); 555 | break; 556 | default: 557 | gdimage = NULL; 558 | } 559 | (void) fclose(fp); 560 | return gdimage; 561 | } 562 | 563 | static gdImagePtr puzzle_create_gdimage_from_mem(const void * const mem, const size_t size) 564 | { 565 | gdImagePtr gdimage = NULL; 566 | PuzzleImageTypeCode image_type_code = puzzle_get_image_type_from_header(mem); 567 | switch (image_type_code) { 568 | case PUZZLE_IMAGE_TYPE_JPEG: 569 | gdimage = gdImageCreateFromJpegPtr(size, (void *)mem); 570 | break; 571 | case PUZZLE_IMAGE_TYPE_PNG: 572 | gdimage = gdImageCreateFromPngPtr(size, (void *)mem); 573 | break; 574 | case PUZZLE_IMAGE_TYPE_GIF: 575 | gdimage = gdImageCreateFromGifPtr(size, (void *)mem); 576 | break; 577 | default: 578 | gdimage = NULL; 579 | } 580 | return gdimage; 581 | } 582 | 583 | static int puzzle_fill_dvec_from_gdimage(PuzzleContext * const context, 584 | PuzzleDvec * const dvec, 585 | const gdImagePtr gdimage) 586 | { 587 | PuzzleView view; 588 | PuzzleAvgLvls avglvls; 589 | int ret = 0; 590 | 591 | if (context->magic != PUZZLE_CONTEXT_MAGIC) { 592 | puzzle_err_bug(__FILE__, __LINE__); 593 | } 594 | puzzle_init_view(&view); 595 | puzzle_init_avglvls(&avglvls); 596 | puzzle_init_dvec(context, dvec); 597 | ret = puzzle_getview_from_gdimage(context, &view, gdimage); 598 | if (ret != 0) { 599 | goto out; 600 | } 601 | if (context->puzzle_enable_autocrop != 0 && 602 | (ret = puzzle_autocrop_view(context, &view)) < 0) { 603 | goto out; 604 | } 605 | if ((ret = puzzle_fill_avglgls(context, &avglvls, 606 | &view, context->puzzle_lambdas)) != 0) { 607 | goto out; 608 | } 609 | ret = puzzle_fill_dvec(dvec, &avglvls); 610 | out: 611 | puzzle_free_view(&view); 612 | puzzle_free_avglvls(&avglvls); 613 | 614 | return ret; 615 | } 616 | 617 | int puzzle_fill_dvec_from_file(PuzzleContext * const context, 618 | PuzzleDvec * const dvec, 619 | const char * const file) 620 | { 621 | int ret; 622 | gdImagePtr gdimage = puzzle_create_gdimage_from_file(file); 623 | if (gdimage == NULL) { 624 | return -1; 625 | } 626 | puzzle_remove_transparency(gdimage); 627 | ret = puzzle_fill_dvec_from_gdimage(context, dvec, gdimage); 628 | gdImageDestroy(gdimage); 629 | return ret; 630 | } 631 | 632 | int puzzle_fill_dvec_from_mem(PuzzleContext * const context, 633 | PuzzleDvec * const dvec, 634 | const void * const mem, 635 | const size_t size) 636 | { 637 | int ret; 638 | gdImagePtr gdimage = puzzle_create_gdimage_from_mem(mem, size); 639 | if (gdimage == NULL) { 640 | return -1; 641 | } 642 | puzzle_remove_transparency(gdimage); 643 | ret = puzzle_fill_dvec_from_gdimage(context, dvec, gdimage); 644 | gdImageDestroy(gdimage); 645 | return ret; 646 | } 647 | 648 | int puzzle_dump_dvec(PuzzleContext * const context, 649 | const PuzzleDvec * const dvec) 650 | { 651 | size_t s = dvec->sizeof_compressed_vec; 652 | const double *vecptr = dvec->vec; 653 | 654 | (void) context; 655 | if (s <= (size_t) 0U) { 656 | puzzle_err_bug(__FILE__, __LINE__); 657 | } 658 | do { 659 | printf("%g\n", *vecptr++); 660 | } while (--s != (size_t) 0U); 661 | 662 | return 0; 663 | } 664 | -------------------------------------------------------------------------------- /libpuzzle/src/globals.h: -------------------------------------------------------------------------------- 1 | #ifndef __GLOBALS_H__ 2 | #define __GLOBALS_H__ 1 3 | 4 | #ifdef DEFINE_GLOBALS 5 | # define GLOBAL0(A) A 6 | # define GLOBAL(A, B) A = B 7 | #else 8 | # define GLOBAL0(A) extern A 9 | # define GLOBAL(A, B) extern A 10 | #endif 11 | 12 | GLOBAL(PuzzleContext puzzle_global_context, 13 | { 14 | /* unsigned int puzzle_max_width */ PUZZLE_DEFAULT_MAX_WIDTH _COMA_ 15 | /* unsigned int puzzle_max_height */ PUZZLE_DEFAULT_MAX_HEIGHT _COMA_ 16 | /* unsigned int puzzle_lambdas */ PUZZLE_DEFAULT_LAMBDAS _COMA_ 17 | /* double puzzle_p_ratio */ PUZZLE_DEFAULT_P_RATIO _COMA_ 18 | /* double puzzle_noise_cutoff */ PUZZLE_DEFAULT_NOISE_CUTOFF _COMA_ 19 | /* double puzzle_contrast_barrier_for_cropping */ 20 | PUZZLE_DEFAULT_CONTRAST_BARRIER_FOR_CROPPING _COMA_ 21 | /* double puzzle_max_cropping_ratio */ 22 | PUZZLE_DEFAULT_MAX_CROPPING_RATIO _COMA_ 23 | /* int puzzle_enable_autocrop */ PUZZLE_DEFAULT_ENABLE_AUTOCROP _COMA_ 24 | /* unsigned long magic */ PUZZLE_CONTEXT_MAGIC _COMA_ 25 | }); 26 | #endif 27 | -------------------------------------------------------------------------------- /libpuzzle/src/pics/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = \ 2 | pic-a-0.jpg \ 3 | pic-a-1.jpg \ 4 | luxmarket_tshirt01.jpg \ 5 | luxmarket_tshirt01_black.jpg \ 6 | luxmarket_tshirt01_sal.jpg \ 7 | luxmarket_tshirt01_sheum.jpg \ 8 | duck.gif 9 | -------------------------------------------------------------------------------- /libpuzzle/src/pics/duck.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/src/pics/duck.gif -------------------------------------------------------------------------------- /libpuzzle/src/pics/luxmarket_tshirt01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/src/pics/luxmarket_tshirt01.jpg -------------------------------------------------------------------------------- /libpuzzle/src/pics/luxmarket_tshirt01_black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/src/pics/luxmarket_tshirt01_black.jpg -------------------------------------------------------------------------------- /libpuzzle/src/pics/luxmarket_tshirt01_sal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/src/pics/luxmarket_tshirt01_sal.jpg -------------------------------------------------------------------------------- /libpuzzle/src/pics/luxmarket_tshirt01_sheum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/src/pics/luxmarket_tshirt01_sheum.jpg -------------------------------------------------------------------------------- /libpuzzle/src/pics/pic-a-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/src/pics/pic-a-0.jpg -------------------------------------------------------------------------------- /libpuzzle/src/pics/pic-a-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/libpuzzle/src/pics/pic-a-1.jpg -------------------------------------------------------------------------------- /libpuzzle/src/puzzle-diff.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle.h" 3 | 4 | typedef struct Opts_ { 5 | const char *file1; 6 | const char *file2; 7 | int fix_for_texts; 8 | int exit; 9 | double similarity_threshold; 10 | } Opts; 11 | 12 | void usage(void) 13 | { 14 | puts("\nUsage: puzzle-diff [-b ] [-e] [-E similarity threshold] [-h]\n" 16 | " [-H ] [-l ] [-n ]\n" 17 | " [-p

] [-t] [-W ] \n\n" 18 | "Visually compares two images and returns their distance.\n\n" 19 | "-b \n" 20 | "-c : disable autocrop\n" 21 | "-C \n" 22 | "-e : exit with 10 (images are similar) or 20 (images are not)\n" 23 | "-E : for -e\n" 24 | "-h : show help\n" 25 | "-H : set max height\n" 26 | "-l : change lambdas\n" 27 | "-n : change noise cutoff\n" 28 | "-p : set p ratio\n" 29 | "-t disable fix for texts\n" 30 | "-W : set max width\n" 31 | "\n"); 32 | exit(EXIT_SUCCESS); 33 | } 34 | 35 | int parse_opts(Opts * const opts, PuzzleContext * context, 36 | int argc, char * const *argv) { 37 | int opt; 38 | extern char *optarg; 39 | extern int optind; 40 | 41 | opts->fix_for_texts = 1; 42 | opts->exit = 0; 43 | opts->similarity_threshold = PUZZLE_CVEC_SIMILARITY_THRESHOLD; 44 | while ((opt = getopt(argc, argv, "b:cC:eE:hH:l:n:p:tW:")) != -1) { 45 | switch (opt) { 46 | case 'b': 47 | puzzle_set_contrast_barrier_for_cropping(context, atof(optarg)); 48 | break; 49 | case 'c': 50 | puzzle_set_autocrop(context, 0); 51 | break; 52 | case 'C': 53 | puzzle_set_max_cropping_ratio(context, atof(optarg)); 54 | break; 55 | case 'e': 56 | opts->exit = 1; 57 | break; 58 | case 'E': 59 | opts->similarity_threshold = atof(optarg); 60 | break; 61 | case 'h': 62 | usage(); 63 | /* NOTREACHED */ 64 | case 'H': 65 | puzzle_set_max_height(context, strtoul(optarg, NULL, 10)); 66 | break; 67 | case 'l': 68 | puzzle_set_lambdas(context, strtoul(optarg, NULL, 10)); 69 | break; 70 | case 'n': 71 | puzzle_set_noise_cutoff(context, atof(optarg)); 72 | break; 73 | case 'p': 74 | puzzle_set_p_ratio(context, atof(optarg)); 75 | break; 76 | case 't': 77 | opts->fix_for_texts = 0; 78 | break; 79 | case 'W': 80 | puzzle_set_max_width(context, strtoul(optarg, NULL, 10)); 81 | break; 82 | default: 83 | usage(); 84 | /* NOTREACHED */ 85 | } 86 | } 87 | argc -= optind; 88 | argv += optind; 89 | if (argc != 2) { 90 | usage(); 91 | } 92 | opts->file1 = *argv++; 93 | opts->file2 = *argv; 94 | 95 | return 0; 96 | } 97 | 98 | int main(int argc, char *argv[]) 99 | { 100 | Opts opts; 101 | PuzzleContext context; 102 | PuzzleCvec cvec1, cvec2; 103 | double d; 104 | 105 | puzzle_init_context(&context); 106 | parse_opts(&opts, &context, argc, argv); 107 | puzzle_init_cvec(&context, &cvec1); 108 | puzzle_init_cvec(&context, &cvec2); 109 | if (puzzle_fill_cvec_from_file(&context, &cvec1, opts.file1) != 0) { 110 | fprintf(stderr, "Unable to read [%s]\n", opts.file1); 111 | return 1; 112 | } 113 | if (puzzle_fill_cvec_from_file(&context, &cvec2, opts.file2) != 0) { 114 | fprintf(stderr, "Unable to read [%s]\n", opts.file2); 115 | return 1; 116 | } 117 | d = puzzle_vector_normalized_distance(&context, &cvec1, &cvec2, 118 | opts.fix_for_texts); 119 | puzzle_free_cvec(&context, &cvec1); 120 | puzzle_free_cvec(&context, &cvec2); 121 | puzzle_free_context(&context); 122 | if (opts.exit == 0) { 123 | printf("%g\n", d); 124 | return 0; 125 | } 126 | if (d > opts.similarity_threshold) { 127 | return 20; 128 | } 129 | return 10; 130 | } 131 | -------------------------------------------------------------------------------- /libpuzzle/src/puzzle.c: -------------------------------------------------------------------------------- 1 | #define DEFINE_GLOBALS 1 2 | #include "puzzle_common.h" 3 | #include "puzzle_p.h" 4 | #include "puzzle.h" 5 | #include "globals.h" 6 | 7 | void puzzle_init_context(PuzzleContext * const context) 8 | { 9 | *context = puzzle_global_context; 10 | } 11 | 12 | void puzzle_free_context(PuzzleContext * const context) 13 | { 14 | (void) context; 15 | } 16 | 17 | void puzzle_err_bug(const char * const file, const int line) 18 | { 19 | fprintf(stderr, "*BUG* File: [%s] Line: [%d]\n", file, line); 20 | abort(); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /libpuzzle/src/puzzle.h: -------------------------------------------------------------------------------- 1 | #ifndef __PUZZLE_H__ 2 | #define __PUZZLE_H__ 1 3 | 4 | #define PUZZLE_VERSION_MAJOR 0 5 | #define PUZZLE_VERSION_MINOR 11 6 | 7 | typedef struct PuzzleDvec_ { 8 | size_t sizeof_vec; 9 | size_t sizeof_compressed_vec; 10 | double *vec; 11 | } PuzzleDvec; 12 | 13 | typedef struct PuzzleCvec_ { 14 | size_t sizeof_vec; 15 | signed char *vec; 16 | } PuzzleCvec; 17 | 18 | typedef struct PuzzleCompressedCvec_ { 19 | size_t sizeof_compressed_vec; 20 | unsigned char *vec; 21 | } PuzzleCompressedCvec; 22 | 23 | typedef struct PuzzleContext_ { 24 | unsigned int puzzle_max_width; 25 | unsigned int puzzle_max_height; 26 | unsigned int puzzle_lambdas; 27 | double puzzle_p_ratio; 28 | double puzzle_noise_cutoff; 29 | double puzzle_contrast_barrier_for_cropping; 30 | double puzzle_max_cropping_ratio; 31 | int puzzle_enable_autocrop; 32 | unsigned long magic; 33 | } PuzzleContext; 34 | 35 | void puzzle_init_context(PuzzleContext * const context); 36 | void puzzle_free_context(PuzzleContext * const context); 37 | int puzzle_set_max_width(PuzzleContext * const context, 38 | const unsigned int width); 39 | int puzzle_set_max_height(PuzzleContext * const context, 40 | const unsigned int height); 41 | int puzzle_set_lambdas(PuzzleContext * const context, 42 | const unsigned int lambdas); 43 | int puzzle_set_noise_cutoff(PuzzleContext * const context, 44 | const double noise_cutoff); 45 | int puzzle_set_p_ratio(PuzzleContext * const context, 46 | const double p_ratio); 47 | int puzzle_set_contrast_barrier_for_cropping(PuzzleContext * const context, 48 | const double barrier); 49 | int puzzle_set_max_cropping_ratio(PuzzleContext * const context, 50 | const double ratio); 51 | int puzzle_set_autocrop(PuzzleContext * const context, 52 | const int enable); 53 | void puzzle_init_cvec(PuzzleContext * const context, 54 | PuzzleCvec * const cvec); 55 | void puzzle_init_dvec(PuzzleContext * const context, 56 | PuzzleDvec * const dvec); 57 | int puzzle_fill_dvec_from_file(PuzzleContext * const context, 58 | PuzzleDvec * const dvec, 59 | const char * const file); 60 | int puzzle_fill_cvec_from_file(PuzzleContext * const context, 61 | PuzzleCvec * const cvec, 62 | const char * const file); 63 | int puzzle_fill_dvec_from_mem(PuzzleContext * const context, 64 | PuzzleDvec * const dvec, 65 | const void * const mem, 66 | const size_t size); 67 | int puzzle_fill_cvec_from_mem(PuzzleContext * const context, 68 | PuzzleCvec * const cvec, 69 | const void * const mem, 70 | const size_t size); 71 | int puzzle_fill_cvec_from_dvec(PuzzleContext * const context, 72 | PuzzleCvec * const cvec, 73 | const PuzzleDvec * const dvec); 74 | void puzzle_free_cvec(PuzzleContext * const context, 75 | PuzzleCvec * const cvec); 76 | void puzzle_free_dvec(PuzzleContext * const context, 77 | PuzzleDvec * const dvec); 78 | int puzzle_dump_cvec(PuzzleContext * const context, 79 | const PuzzleCvec * const cvec); 80 | int puzzle_dump_dvec(PuzzleContext * const context, 81 | const PuzzleDvec * const dvec); 82 | int puzzle_cvec_cksum(PuzzleContext * const context, 83 | const PuzzleCvec * const cvec, unsigned int * const sum); 84 | void puzzle_init_compressed_cvec(PuzzleContext * const context, 85 | PuzzleCompressedCvec * const compressed_cvec); 86 | void puzzle_free_compressed_cvec(PuzzleContext * const context, 87 | PuzzleCompressedCvec * const compressed_cvec); 88 | int puzzle_compress_cvec(PuzzleContext * const context, 89 | PuzzleCompressedCvec * const compressed_cvec, 90 | const PuzzleCvec * const cvec); 91 | int puzzle_uncompress_cvec(PuzzleContext * const context, 92 | const PuzzleCompressedCvec * const compressed_cvec, 93 | PuzzleCvec * const cvec); 94 | int puzzle_vector_sub(PuzzleContext * const context, 95 | PuzzleCvec * const cvecr, 96 | const PuzzleCvec * const cvec1, 97 | const PuzzleCvec * const cvec2, 98 | const int fix_for_texts); 99 | double puzzle_vector_euclidean_length(PuzzleContext * const context, 100 | const PuzzleCvec * const cvec); 101 | double puzzle_vector_normalized_distance(PuzzleContext * const context, 102 | const PuzzleCvec * const cvec1, 103 | const PuzzleCvec * const cvec2, 104 | const int fix_for_texts); 105 | 106 | #define PUZZLE_CVEC_SIMILARITY_THRESHOLD 0.6 107 | #define PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD 0.7 108 | #define PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD 0.3 109 | #define PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD 0.2 110 | 111 | #define _COMA_ , 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /libpuzzle/src/puzzle_common.h: -------------------------------------------------------------------------------- 1 | #ifndef __PUZZLE_COMMON_H__ 2 | #define __PUZZLE_COMMON_H__ 1 3 | 4 | #include 5 | #ifdef STDC_HEADERS 6 | # include 7 | # include 8 | # include 9 | #else 10 | # if HAVE_STDLIB_H 11 | # include 12 | # endif 13 | #endif 14 | #include 15 | #ifdef HAVE_STRING_H 16 | # if !STDC_HEADERS && HAVE_MEMORY_H 17 | # include 18 | # endif 19 | # include 20 | #else 21 | # if HAVE_STRINGS_H 22 | # include 23 | # endif 24 | #endif 25 | #include 26 | #include 27 | #ifdef HAVE_UNISTD_H 28 | # include 29 | #endif 30 | #include 31 | 32 | #ifndef errno 33 | extern int errno; 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /libpuzzle/src/puzzle_p.h: -------------------------------------------------------------------------------- 1 | #ifndef __PUZZLE_P_H__ 2 | #define __PUZZLE_P_H__ 1 3 | 4 | #include 5 | #include 6 | 7 | typedef struct PuzzleView_ { 8 | unsigned int width; 9 | unsigned int height; 10 | size_t sizeof_map; 11 | unsigned char *map; 12 | } PuzzleView; 13 | 14 | typedef struct PuzzleAvgLvls_ { 15 | unsigned int lambdas; 16 | size_t sizeof_lvls; 17 | double *lvls; 18 | } PuzzleAvgLvls; 19 | 20 | typedef enum PuzzleImageTypeCode_ { 21 | PUZZLE_IMAGE_TYPE_ERROR, PUZZLE_IMAGE_TYPE_UNKNOWN, PUZZLE_IMAGE_TYPE_JPEG, 22 | PUZZLE_IMAGE_TYPE_GIF, PUZZLE_IMAGE_TYPE_PNG 23 | } PuzzleImageTypeCode; 24 | 25 | typedef struct PuzzleImageType_ { 26 | const size_t sizeof_signature; 27 | const unsigned char *signature; 28 | const PuzzleImageTypeCode image_type_code; 29 | } PuzzleImageType; 30 | 31 | #ifndef SIZE_MAX 32 | # define SIZE_MAX ((size_t) -1) 33 | #endif 34 | 35 | #define PUZZLE_DEFAULT_LAMBDAS 9 36 | #define PUZZLE_DEFAULT_MAX_WIDTH 3000 37 | #define PUZZLE_DEFAULT_MAX_HEIGHT 3000 38 | #define PUZZLE_DEFAULT_NOISE_CUTOFF 2.0 39 | #define PUZZLE_DEFAULT_P_RATIO 2.0 40 | #define PUZZLE_MIN_P 2 41 | #define PUZZLE_PIXEL_FUZZ_SIZE 1 42 | #define PUZZLE_NEIGHBORS 8 43 | #define PUZZLE_MIN_SIZE_FOR_CROPPING 100 44 | #if PUZZLE_MIN_SIZE_FOR_CROPPING < 4 45 | # error PUZZLE_MIN_SIZE_FOR_CROPPING 46 | #endif 47 | #define PUZZLE_DEFAULT_CONTRAST_BARRIER_FOR_CROPPING 0.05 48 | #define PUZZLE_DEFAULT_MAX_CROPPING_RATIO 0.25 49 | #define PUZZLE_DEFAULT_ENABLE_AUTOCROP 1 50 | 51 | #define PUZZLE_VIEW_PIXEL(V, X, Y) (*((V)->map + (V)->width * (Y) + (X))) 52 | #define PUZZLE_AVGLVL(A, X, Y) (*((A)->lvls + (A)->lambdas * (Y) + (X))) 53 | 54 | #define PUZZLE_CONTEXT_MAGIC 0xdeadbeef 55 | 56 | #ifndef MIN 57 | # define MIN(A, B) ((A) < (B) ? (A) : (B)) 58 | #endif 59 | #ifndef MAX 60 | # define MAX(A, B) ((A) > (B) ? (A) : (B)) 61 | #endif 62 | #define SUCC(A) ((A) + 1) 63 | #define PRED(A) ((A) - 1) 64 | 65 | void puzzle_err_bug(const char * const file, const int line); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /libpuzzle/src/regress_1.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle.h" 3 | 4 | #define EXPECTED_RESULT 111444570 5 | 6 | int main(void) 7 | { 8 | PuzzleContext context; 9 | PuzzleCvec cvec; 10 | PuzzleCompressedCvec compressed_cvec; 11 | unsigned int sum; 12 | 13 | puzzle_init_context(&context); 14 | puzzle_init_compressed_cvec(&context, &compressed_cvec); 15 | puzzle_init_cvec(&context, &cvec); 16 | if (puzzle_fill_cvec_from_file(&context, &cvec, 17 | "pics/luxmarket_tshirt01.jpg") != 0) { 18 | fprintf(stderr, "File not found\n"); 19 | exit(0); 20 | } 21 | puzzle_compress_cvec(&context, &compressed_cvec, &cvec); 22 | puzzle_free_cvec(&context, &cvec); 23 | puzzle_init_cvec(&context, &cvec); 24 | puzzle_uncompress_cvec(&context, &compressed_cvec, &cvec); 25 | puzzle_cvec_cksum(&context, &cvec, &sum); 26 | puzzle_free_cvec(&context, &cvec); 27 | puzzle_free_compressed_cvec(&context, &compressed_cvec); 28 | puzzle_free_context(&context); 29 | printf("%u %u\n", sum, (unsigned int) EXPECTED_RESULT); 30 | 31 | return sum != EXPECTED_RESULT; 32 | } 33 | -------------------------------------------------------------------------------- /libpuzzle/src/regress_2.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle.h" 3 | 4 | int main(void) 5 | { 6 | PuzzleContext context; 7 | PuzzleCvec cvec1, cvec2, cvec3, cvec4, cvec5, cvec6; 8 | double d1, d2, d3, d4, d5, d6; 9 | 10 | puzzle_init_context(&context); 11 | puzzle_init_cvec(&context, &cvec1); 12 | puzzle_init_cvec(&context, &cvec2); 13 | puzzle_init_cvec(&context, &cvec3); 14 | puzzle_init_cvec(&context, &cvec4); 15 | puzzle_init_cvec(&context, &cvec5); 16 | puzzle_init_cvec(&context, &cvec6); 17 | if (puzzle_fill_cvec_from_file 18 | (&context, &cvec1, "pics/luxmarket_tshirt01.jpg") != 0) { 19 | fprintf(stderr, "File 1 not found\n"); 20 | exit(0); 21 | } 22 | if (puzzle_fill_cvec_from_file 23 | (&context, &cvec2, "pics/luxmarket_tshirt01_black.jpg") != 0) { 24 | fprintf(stderr, "File 2 not found\n"); 25 | exit(0); 26 | } 27 | if (puzzle_fill_cvec_from_file 28 | (&context, &cvec3, "pics/luxmarket_tshirt01_sal.jpg") != 0) { 29 | fprintf(stderr, "File 3 not found\n"); 30 | exit(0); 31 | } 32 | if (puzzle_fill_cvec_from_file 33 | (&context, &cvec4, "pics/luxmarket_tshirt01_sheum.jpg") != 0) { 34 | fprintf(stderr, "File 4 not found\n"); 35 | exit(0); 36 | } 37 | if (puzzle_fill_cvec_from_file 38 | (&context, &cvec5, "pics/duck.gif") != 0) { 39 | fprintf(stderr, "File 5 not found\n"); 40 | exit(0); 41 | } 42 | if (puzzle_fill_cvec_from_file 43 | (&context, &cvec6, "pics/pic-a-0.jpg") != 0) { 44 | fprintf(stderr, "File 6 not found\n"); 45 | exit(0); 46 | } 47 | d1 = puzzle_vector_normalized_distance(&context, &cvec2, &cvec1, 1); 48 | d2 = puzzle_vector_normalized_distance(&context, &cvec1, &cvec2, 1); 49 | d3 = puzzle_vector_normalized_distance(&context, &cvec1, &cvec3, 1); 50 | d4 = puzzle_vector_normalized_distance(&context, &cvec1, &cvec4, 1); 51 | d5 = puzzle_vector_normalized_distance(&context, &cvec1, &cvec5, 1); 52 | d6 = puzzle_vector_normalized_distance(&context, &cvec1, &cvec6, 1); 53 | printf("%g %g %g %g %g %g\n", d1, d2, d3, d4, d5, d6); 54 | puzzle_free_cvec(&context, &cvec1); 55 | puzzle_free_cvec(&context, &cvec2); 56 | puzzle_free_cvec(&context, &cvec3); 57 | puzzle_free_cvec(&context, &cvec4); 58 | puzzle_free_cvec(&context, &cvec5); 59 | puzzle_free_cvec(&context, &cvec6); 60 | puzzle_free_context(&context); 61 | if ((int) (d1 * 100.0) != (int) (d2 * 100.0)) { 62 | return 1; 63 | } 64 | if (d1 > PUZZLE_CVEC_SIMILARITY_THRESHOLD || 65 | d3 > PUZZLE_CVEC_SIMILARITY_THRESHOLD || 66 | d4 > PUZZLE_CVEC_SIMILARITY_THRESHOLD || 67 | d5 < PUZZLE_CVEC_SIMILARITY_THRESHOLD || 68 | d6 < PUZZLE_CVEC_SIMILARITY_THRESHOLD) { 69 | return 2; 70 | } 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /libpuzzle/src/regress_3.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle.h" 3 | 4 | #define PUZZLE_VECTOR_SLICE 0.6 5 | 6 | int main(void) 7 | { 8 | PuzzleContext context; 9 | PuzzleCvec cvec1, cvec2; 10 | double d1, d2; 11 | 12 | puzzle_init_context(&context); 13 | puzzle_init_cvec(&context, &cvec1); 14 | puzzle_init_cvec(&context, &cvec2); 15 | if (puzzle_fill_cvec_from_file(&context, &cvec1, 16 | "pics/pic-a-0.jpg") != 0) { 17 | fprintf(stderr, "File 1 not found\n"); 18 | exit(0); 19 | } 20 | if (puzzle_fill_cvec_from_file(&context, &cvec2, 21 | "pics/pic-a-1.jpg") != 0) { 22 | fprintf(stderr, "File 2 not found\n"); 23 | exit(0); 24 | } 25 | d1 = puzzle_vector_normalized_distance(&context, &cvec1, &cvec2, 1); 26 | d2 = puzzle_vector_normalized_distance(&context, &cvec1, &cvec2, 0); 27 | printf("%g %g\n", d1, d2); 28 | puzzle_free_cvec(&context, &cvec1); 29 | puzzle_free_cvec(&context, &cvec2); 30 | puzzle_free_context(&context); 31 | if (d1 > PUZZLE_VECTOR_SLICE || d2 > PUZZLE_VECTOR_SLICE) { 32 | return 2; 33 | } 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /libpuzzle/src/tunables.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle_p.h" 3 | #include "puzzle.h" 4 | #include "globals.h" 5 | 6 | int puzzle_set_max_width(PuzzleContext * const context, 7 | const unsigned int width) 8 | { 9 | if (width <= 0U) { 10 | return -1; 11 | } 12 | context->puzzle_max_width = width; 13 | 14 | return 0; 15 | } 16 | 17 | int puzzle_set_max_height(PuzzleContext * const context, 18 | const unsigned int height) 19 | { 20 | if (height <= 0U) { 21 | return -1; 22 | } 23 | context->puzzle_max_height = height; 24 | 25 | return 0; 26 | } 27 | 28 | int puzzle_set_lambdas(PuzzleContext * const context, 29 | const unsigned int lambdas) 30 | { 31 | if (lambdas <= 0U) { 32 | return -1; 33 | } 34 | context->puzzle_lambdas = lambdas; 35 | 36 | return 0; 37 | } 38 | 39 | int puzzle_set_p_ratio(PuzzleContext * const context, const double p_ratio) 40 | { 41 | if (p_ratio < 1.0) { 42 | return -1; 43 | } 44 | context->puzzle_p_ratio = p_ratio; 45 | 46 | return 0; 47 | } 48 | 49 | int puzzle_set_noise_cutoff(PuzzleContext * const context, 50 | const double noise_cutoff) 51 | { 52 | context->puzzle_noise_cutoff = noise_cutoff; 53 | 54 | return 0; 55 | } 56 | 57 | int puzzle_set_contrast_barrier_for_cropping(PuzzleContext * const context, 58 | const double barrier) 59 | { 60 | if (barrier <= 0.0) { 61 | return -1; 62 | } 63 | context->puzzle_contrast_barrier_for_cropping = barrier; 64 | 65 | return 0; 66 | } 67 | 68 | int puzzle_set_max_cropping_ratio(PuzzleContext * const context, 69 | const double ratio) 70 | { 71 | if (ratio <= 0.0) { 72 | return -1; 73 | } 74 | context->puzzle_max_cropping_ratio = ratio; 75 | 76 | return 0; 77 | } 78 | 79 | int puzzle_set_autocrop(PuzzleContext * const context, const int enable) 80 | { 81 | context->puzzle_enable_autocrop = (enable != 0); 82 | 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /libpuzzle/src/vector_ops.c: -------------------------------------------------------------------------------- 1 | #include "puzzle_common.h" 2 | #include "puzzle_p.h" 3 | #include "puzzle.h" 4 | #include "globals.h" 5 | 6 | int puzzle_vector_sub(PuzzleContext * const context, 7 | PuzzleCvec * const cvecr, 8 | const PuzzleCvec * const cvec1, 9 | const PuzzleCvec * const cvec2, 10 | const int fix_for_texts) 11 | { 12 | size_t remaining; 13 | signed char c1, c2, cr; 14 | 15 | (void) context; 16 | if (cvec1->sizeof_vec != cvec2->sizeof_vec || 17 | cvec1->sizeof_vec <= (size_t) 0U) { 18 | puzzle_err_bug(__FILE__, __LINE__); 19 | } 20 | if (cvecr->vec != NULL) { 21 | puzzle_err_bug(__FILE__, __LINE__); 22 | } 23 | cvecr->sizeof_vec = cvec1->sizeof_vec; 24 | if ((cvecr->vec = calloc(cvecr->sizeof_vec, sizeof *cvecr->vec)) == NULL) { 25 | return -1; 26 | } 27 | remaining = cvec1->sizeof_vec; 28 | if (fix_for_texts != 0) { 29 | do { 30 | remaining--; 31 | c1 = cvec1->vec[remaining]; 32 | c2 = cvec2->vec[remaining]; 33 | if ((c1 == 0 && c2 == -2) || (c1 == -2 && c2 == 0)) { 34 | cr = -3; 35 | } else if ((c1 == 0 && c2 == +2) || (c1 == +2 && c2 == 0)) { 36 | cr = +3; 37 | } else { 38 | cr = c1 - c2; 39 | } 40 | cvecr->vec[remaining] = cr; 41 | } while (remaining > (size_t) 0U); 42 | } else { 43 | do { 44 | remaining--; 45 | cvecr->vec[remaining] = 46 | cvec1->vec[remaining] - cvec2->vec[remaining]; 47 | } while (remaining > (size_t) 0U); 48 | } 49 | return 0; 50 | } 51 | 52 | double puzzle_vector_euclidean_length(PuzzleContext * const context, 53 | const PuzzleCvec * const cvec) 54 | { 55 | unsigned long t = 0U; 56 | unsigned long c; 57 | int c2; 58 | size_t remaining; 59 | 60 | (void) context; 61 | if ((remaining = cvec->sizeof_vec) <= (size_t) 0U) { 62 | return 0.0; 63 | } 64 | do { 65 | remaining--; 66 | c2 = (int) cvec->vec[remaining]; 67 | c = (unsigned long) (c2 * c2); 68 | if (ULONG_MAX - t < c) { 69 | puzzle_err_bug(__FILE__, __LINE__); 70 | } 71 | t += c; 72 | } while (remaining > (size_t) 0U); 73 | 74 | return sqrt((double) t); 75 | } 76 | 77 | double puzzle_vector_normalized_distance(PuzzleContext * const context, 78 | const PuzzleCvec * const cvec1, 79 | const PuzzleCvec * const cvec2, 80 | const int fix_for_texts) 81 | { 82 | PuzzleCvec cvecr; 83 | double dt, dr; 84 | 85 | puzzle_init_cvec(context, &cvecr); 86 | puzzle_vector_sub(context, &cvecr, cvec1, cvec2, fix_for_texts); 87 | dt = puzzle_vector_euclidean_length(context, &cvecr); 88 | puzzle_free_cvec(context, &cvecr); 89 | dr = puzzle_vector_euclidean_length(context, cvec1) 90 | + puzzle_vector_euclidean_length(context, cvec2); 91 | if (dr == 0.0) { 92 | return 0.0; 93 | } 94 | return dt / dr; 95 | } 96 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Fake Video (movie) detector 2 | 3 | This script detects any fake videos you may have in your library, based upon an 'index' of screenshots that are taken from a random frame within a fake video. 4 | 5 | It uses 'libpuzzle' to visually recognise similar images - this way if the quality or colour slightly varies between movies you download it should (in theory) still catch and match any fake videos. 6 | 7 | If any matches are found the script will either log, or totally remove the file **(I suggest you analyse your library, and first review the results before you configure it to auto-remove files)**. 8 | 9 | It is intended to be used in combination with your PVR so you can automatically remove any rubbish content that may have been downloaded. 10 | 11 | -------------------------------------------------------- 12 | 13 | **Docker Hub:** [`ultimatepms/fake-video-detector `](https://hub.docker.com/r/ultimatepms/fake-video-detector) 14 | 15 | ![Docker Pulls](https://img.shields.io/docker/pulls/ultimatepms/fake-video-detector.png) 16 | 17 | 18 | #### Some examples of fake videos: 19 | - [Xvid codec required to view video](blacklist-originals/183030964134081.jpg) 20 | - [You're missing CODEC pack to play video (download from some website)](blacklist-originals/222612955025266.jpg) 21 | - [You need x3player to watch this movie](blacklist-originals/483791103037622.jpg) 22 | 23 | ![Alt text](screenshot.gif?raw=true) 24 | 25 | -------------------------------------------------------- 26 | 27 | ## Prerequisites: 28 | The script straight out of the box, you'll require: 29 | 30 | - Docker 31 | 32 | ### Running without Docker: 33 | 34 | If you _must_ run the script without docker, you may use the copy of the script in the [no-docker](https://github.com/ultimate-pms/fake-video-detector/tree/no-docker) branch, however ffmpeg, libpizzle, and mogrify MUST be installed on your system - **The non docker branch is no-longer maintained as of April 2019.** 35 | 36 | ## Installing: 37 | Run `./setup.sh` to install the `'fake-video'` and `'bulk-search'` commands into your local bash/zsh environment... You will also need to provide the FULL path to your media directory so it can be mapped into the docker container. 38 | 39 | If you need to edit the volumes after you have run the install script, you may directly edit the alias in your ~/.zshrc and/or ~/.bashrc files. 40 | 41 | If you are ONLY going to execute this from another process (i.e. Radarr or Sonarr) you may skip this step. 42 | 43 | ## Running: 44 | 45 | The script can be either run as a once-off command by passing a single video file to it _(see all arguments supported below)_, or executed through a third party tool such as your Download Client (qBittorrent etc), or your PVR's "Post Processing" (Radarr, Sonarr, Couchpotato etc). 46 | 47 | ### Running directly, with a single file 48 | 49 | ```bash 50 | docker run -it --rm \ 51 | -v "/path/to/movies:/path/to/movies" \ 52 | ultimatepms/fake-video-detector:latest \ 53 | fake-video --video="/path/to/movies/example.mp4" 54 | ``` 55 | 56 | ### Bulk Searching all your media files 57 | 58 | This is a simple script that searches your directories & find's all video files (set to larger than 30mb) to process through the `fake-video` script. In addition it logs any matched videos to a `fake-movie-paths.txt` file, so you can manually review and remove any matching videos. 59 | 60 | ```bash 61 | docker run -it --rm \ 62 | -v "/path/to/movies:/path/to/movies" \ 63 | ultimatepms/fake-video-detector:latest \ 64 | bulk-search --search-location="/path/to/movies" 65 | ``` 66 | Note that files will not be auto-deleted with bulk-search unless you specify the flag `--remove` after the search-location parameter. 67 | 68 | 69 | ## More advanced, supported arguments: 70 | ### `fake-video`: 71 | 72 | This is the main script which actually processes the video file. If you are integrating to your PVR or BitTorrent client this is the script you will want to execute. Arguments currently supported as as follows: 73 | 74 | ``` 75 | fake-video 76 | -h --help 77 | --video="" Specify full path to video file in double quotes 78 | --threshold="0.20" The threshold (between 0 - 1) that a video is matched against - a value of 0 will be blacklisted, and 1 will count as passing. Suggest leaving at default of 0.20 79 | --build-thumbnails Builds the 'blacklist-resized' directory, this needs to be run if you add or update thumbnails of blacklisted videos 80 | --silent Suppresses all output - Will return an exit code of '1' for any matching fake videos, and '0' for all other scenarios 81 | --log Logs result of each video into file (./fake-video.log) - Works well with --silent flag 82 | --remove If specified, will auto-delete the file 83 | 84 | ``` 85 | 86 | ### `bulk-search`: 87 | 88 | ``` 89 | bulk-search 90 | -h --help 91 | --search-location="" Specify the directory you would like to search - Wildcards are accepted i.e. '/nas/*/Movies' 92 | --remove If specified, will delete any files matched (DOES NOT RUN BY DEFAULT) 93 | ``` 94 | 95 | ## Contributing 96 | 97 | **IF YOU HAVE ANY ADDITIONAL FAKE VIDEO SCREENSHOTS, PLEASE ADD THEM INTO THE REPO AND SUBMIT A PR! - YOU MAY ALSO LOG AS AN ISSUE AND I WILL MANUALLY ADD THEM IN IF YOU'RE LAZY...** 98 | 99 | ### Adding more videos to blacklist: 100 | 101 | Simply add a screenshot of a random point in time of your video into the `blacklist-originals` directory and run `./fake-video --build-thumbnails` to rebuild the thumbnails database. 102 | 103 | Here's a simple one-liner that will take a screenshot from the start of the video and store it in the `blacklist-originals` directory... (Note you will need to update the *$INPUT_FILE* variable with your file name) 104 | 105 | ```bash 106 | ffmpeg -i "$INPUT_FILE" -vcodec mjpeg -vframes 1 -an -f rawvideo -ss 100 -preset veryfast "blacklist-originals/$RANDOM$RANDOM$RANDOM.jpg" 107 | ``` 108 | 109 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-pms/fake-video-detector/32d1535c2a5de8318b74f7af436e8d6df58b47db/screenshot.gif -------------------------------------------------------------------------------- /scripts/bulk-search: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # An easy way to find all existing videos that have been downloaded and run them through the 'fake-video' program. 4 | ################################################################################################ 5 | 6 | LOG_FILE="fake-movie-paths.txt" 7 | MIN_SIZE=30M 8 | REMOVE_FILES=0 9 | SEARCH_LOCATION="/" 10 | FILE_EXTENSIONS=( 11 | "mp4" 12 | "m4a" 13 | "avi" 14 | "m4v" 15 | "mpg" 16 | "mpeg" 17 | "wmv" 18 | "mkv" 19 | ) 20 | 21 | function usage() 22 | { 23 | echo -e "Usage:\n" 24 | echo -e "$0" 25 | echo -e "\t-h --help" 26 | echo -e "\t--search-location=\"\" \t Specify the directory you would like to search - Wildcards are accepted i.e. '/nas/*/Movies'" 27 | echo -e "\t--remove \t\t\t If specified, will delete any files matched (DOES NOT RUN BY DEFAULT)" 28 | echo -e "" 29 | } 30 | 31 | while [ "$1" != "" ]; do 32 | PARAM=`echo $1 | awk -F= '{print $1}'` 33 | VALUE=`echo $1 | awk -F= '{print $2}'` 34 | case $PARAM in 35 | -h | --help) 36 | usage 37 | exit 38 | ;; 39 | --remove) 40 | REMOVE_FILES=1 41 | ;; 42 | --search-location) 43 | SEARCH_LOCATION=$VALUE 44 | ;; 45 | *) 46 | echo "ERROR: unknown parameter \"$PARAM\"" 47 | usage 48 | exit 1 49 | ;; 50 | esac 51 | shift 52 | done 53 | 54 | # No need to edit below here. 55 | ################################################################################################ 56 | 57 | # Progress bar function 58 | prog() { 59 | local w=50 p=$1; shift 60 | printf -v dots "%*s" "$(( $p*$w/100 ))" ""; dots=${dots// /#}; 61 | printf "\r\e[K|%-*s| %3d %% %s" "$w" "$dots" "$p" "$*"; 62 | } 63 | 64 | NAME_ARGS="" 65 | for i in "${FILE_EXTENSIONS[@]}"; do : 66 | if [[ -z "${NAME_ARGS// }" ]]; then 67 | NAME_ARGS="$i" 68 | else 69 | NAME_ARGS="$NAME_ARGS|$i" 70 | fi 71 | done 72 | 73 | echo -e "++ Bulk Fake Video Processor ++\n--------------------------------\n" 74 | echo "Building file list - please be patient..." 75 | 76 | 77 | SEARCH_COMMAND="find $SEARCH_LOCATION -type f -size +$MIN_SIZE -regextype posix-egrep -regex \".*\\.($NAME_ARGS)\$\" -print0" 78 | 79 | process_movies=() 80 | while IFS= read -r -d $'\0'; do 81 | process_movies+=("$REPLY") 82 | done < <(eval $SEARCH_COMMAND | sort -z ) 83 | 84 | count=0 85 | total=`echo ${#process_movies[@]}` 86 | echo -e "\nProcessing results:" 87 | 88 | RED='\033[0;31m' 89 | NC='\033[0m' # No Color 90 | 91 | if [ "$total" -gt "100" ]; then 92 | SLOW_COMMENT="(this will take a while)" 93 | fi 94 | echo "Analyzing video in $total files $SLOW_COMMENT..." 95 | 96 | for filename in "${process_movies[@]}"; do : 97 | 98 | count=$((count + 1)) 99 | taskpercent=$((count*100/total)) 100 | shortName="${filename##*/}" 101 | 102 | prog "$taskpercent" $shortName... 103 | 104 | if [ $REMOVE_FILES = 1 ]; then 105 | fake-video --silent --log --video="$filename" --remove 106 | exitCode=$? 107 | else 108 | fake-video --silent --log --video="$filename" 109 | exitCode=$? 110 | fi 111 | 112 | if [ $exitCode -eq 1 ]; then 113 | 114 | prog "$taskpercent" "" 115 | echo -e "${RED}NOTICE: File was detected as a fake${NC} [$filename]" 116 | 117 | if [ ! -z "$LOG_FILE" ]; then 118 | echo "[$filename]" >> $LOG_FILE 119 | fi 120 | fi 121 | 122 | done 123 | 124 | echo "" 125 | -------------------------------------------------------------------------------- /scripts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Please run via the commands as outlined in README.md" 4 | echo "" 5 | echo "If you're too lazy to read the README, you may run the scripts: 'fake-video', or 'bulk-search'" -------------------------------------------------------------------------------- /scripts/fake-video: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script to detect fake videos, based upon a 'database' of screenshots of fake videos. 4 | # Comments, changes and fixes welcome! 5 | # 6 | # Author: Ned Kelly 7 | # hhttps://github.com/ultimate-pms/fake-video-detector 8 | # 9 | 10 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 11 | 12 | # Default threshold 13 | MATCH_THRESHOLD="0.20" 14 | 15 | # If changing the temp directory, ensure that it is an absolute path and not relative. 16 | PROCESSING_DIRECTORY="/tmp/fake-video-tmp" 17 | 18 | if [ ! -f /.dockerenv ]; then 19 | echo -e "NOTICE:\n\nPlease run this script from within the 'fake-video' docker container...\nSee README.md for further details.\n" 20 | exit 1 21 | fi 22 | 23 | 24 | #################################################################################################### 25 | 26 | MY_PWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 27 | 28 | BLACKLIST_DIR="$MY_PWD/../blacklist-originals" 29 | RESIZED_DIR="/opt/blacklist-resized" 30 | 31 | function title() { 32 | echo -e "Fake Video Detector." 33 | } 34 | 35 | function usage() 36 | { 37 | echo -e "\nThis program attempts to auto-match & delete fake movies that may have been downloaded based on a 'thumbnail' blacklist." 38 | echo -e "Blacklisted videos are detected, based upon a screen-shot which is taken from a random point in time in the video specified." 39 | echo -e "If any matches are found, the script will then either log or totally remove the file." 40 | echo -e "\nAuthor: Ned Kelly\nBugs: https://github.com/ultimate-pms/fake-video-detector/issues" 41 | echo -e "\n\nUsage:\n" 42 | echo -e "$0" 43 | echo -e "\t-h --help" 44 | echo -e "\t--video=\"\" \t Specify full path to video file in double quotes" 45 | echo -e "\t--threshold=\"0.20\" \t The threshold (between 0 - 1) that a video is matched against - a value of 0 will be blacklisted, and 1 will count as passing. Suggest leaving at default of 0.20" 46 | echo -e "\t--build-thumbnails \t Builds the '$RESIZED_DIR' directory, this needs to be run if you add or update thumbnails of blacklisted videos" 47 | echo -e "\t--silent \t\t Suppresses all output - Will return an exit code of '1' for any matching fake videos, and '0' for all other scenarios" 48 | echo -e "\t--log \t\t\t Logs result of each video into file ($0.log) - Works well with --silent flag" 49 | echo -e "\t--remove \t\t If specified, will auto-delete the file" 50 | echo -e "" 51 | } 52 | 53 | ffMpegInstalled() { 54 | if ! [ -x "$(command -v ffmpeg)" ]; then 55 | echo 'Please install ffmpeg on this host before running this tool' >&2 56 | exit 1 57 | fi 58 | } 59 | 60 | buildTumbnails() { 61 | 62 | echo -e "Building thumbnails list to match against...\n" 63 | 64 | # Create directories if they don't exist... 65 | mkdir -p {$RESIZED_DIR,$BLACKLIST_DIR} 66 | 67 | # down-size images (it's more accurate matching images that are smaller) 68 | for image in $BLACKLIST_DIR/*; do 69 | 70 | filename="${image##*/}" 71 | echo $filename 72 | 73 | mogrify -resize x200 -path "$RESIZED_DIR" -format jpg "$BLACKLIST_DIR/$filename" 74 | 75 | done 76 | echo "" 77 | } 78 | 79 | checkBlacklistVideosExists() { 80 | if [ ! -d "$RESIZED_DIR" ]; then 81 | if [ -z "$(ls -A $BLACKLIST_DIR)" ]; then 82 | echo "There are no blacklist originals, please add screenshots of fake videos in the: '$BLACKLIST_DIR' directory to begin." 83 | else 84 | buildTumbnails 85 | fi 86 | fi 87 | 88 | if [ -z "$(ls -A $RESIZED_DIR)" ]; then 89 | if [ -z "$(ls -A $BLACKLIST_DIR)" ]; then 90 | echo "There are no blacklist originals, please add screenshots of fake videos in the: '$BLACKLIST_DIR' directory to begin." 91 | else 92 | buildTumbnails 93 | fi 94 | fi 95 | } 96 | 97 | cleanUp() { 98 | rm -rf "$PROCESSING_DIRECTORY" 99 | } 100 | 101 | REMOVE_FILES=0 102 | SILENT=0 103 | LOG=0 104 | TEMP_MATCH_FILE_NAME="$RANDOM$RANDOM$RANDOM.jpg" 105 | TEMP_MATCH_FILE="$PROCESSING_DIRECTORY/$TEMP_MATCH_FILE_NAME" 106 | 107 | while [ "$1" != "" ]; do 108 | PARAM=`echo $1 | awk -F= '{print $1}'` 109 | VALUE=`echo $1 | awk -F= '{print $2}'` 110 | case $PARAM in 111 | -h | --help) 112 | usage 113 | exit 114 | ;; 115 | --video) 116 | INPUT_FILE=$VALUE 117 | ;; 118 | --remove) 119 | REMOVE_FILES=1 120 | ;; 121 | --silent) 122 | SILENT=1 123 | ;; 124 | --log) 125 | LOG=1 126 | ;; 127 | --threshold) 128 | MATCH_THRESHOLD=$VALUE 129 | ;; 130 | --build-thumbnails) 131 | BUILD_THUMBNAILS=1 132 | ;; 133 | *) 134 | echo "ERROR: unknown parameter \"$PARAM\"" 135 | usage 136 | exit 1 137 | ;; 138 | esac 139 | shift 140 | done 141 | 142 | if [ "$SILENT" -ne 1 ]; then 143 | title 144 | fi 145 | 146 | ffMpegInstalled 147 | 148 | if [ "$BUILD_THUMBNAILS" = 1 ]; then 149 | buildTumbnails 150 | exit 0 151 | fi 152 | 153 | if [ -z "$INPUT_FILE" ]; then 154 | echo -e "Please specify an input video file!\n See --help for further details." 155 | exit 0 156 | fi 157 | 158 | checkBlacklistVideosExists 159 | if [ "$SILENT" -ne 1 ]; then 160 | echo "" 161 | fi 162 | 163 | 164 | mkdir -p $PROCESSING_DIRECTORY/shrunk 165 | DURATION=`ffmpeg -loglevel panic -hide_banner -i "$INPUT_FILE" 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F ':' '{print ($3+$2*60+$1*3600)/2}'` 166 | RANDOM_FRAME=`awk -v min=0 -v max=$DURATION 'BEGIN{srand(); print int(min+rand()*(max-min+1))}'` 167 | 168 | # Take screenshot based on a random frame (in seconds) of the file 169 | if [ "$SILENT" -ne 1 ]; then 170 | echo "Creating video thumbnail..." 171 | fi 172 | 173 | ffmpeg -loglevel panic -hide_banner -i "$INPUT_FILE" -vcodec mjpeg -vframes 1 -an -f rawvideo -ss $RANDOM_FRAME -preset veryfast "$TEMP_MATCH_FILE" 174 | 175 | if [ "$SILENT" -ne 1 ]; then 176 | echo "Downsizing thumbnail..." 177 | fi 178 | # down-size screenshot from video (it's more accurate matching images that are smaller so subtle changes are detected) 179 | mogrify -resize x200 -path "$PROCESSING_DIRECTORY/shrunk" -format jpg "$PROCESSING_DIRECTORY/$TEMP_MATCH_FILE_NAME" 180 | 181 | if [ "$SILENT" -ne 1 ]; then 182 | echo "Comparing against blacklisted videos..." 183 | fi 184 | for image in $RESIZED_DIR/*; do 185 | 186 | # compare blacklisted image with screenshot from movie 187 | filename="${image##*/}" 188 | 189 | # A result of 0 is a match, anything less than about 0.10 will probably be a match... 190 | puzzle-diff "$PROCESSING_DIRECTORY/shrunk/$TEMP_MATCH_FILE_NAME" "$RESIZED_DIR/$filename" > $PROCESSING_DIRECTORY/$TEMP_MATCH_FILE_NAME.result 191 | 192 | MATCH_RESULT=`cat "$PROCESSING_DIRECTORY/$TEMP_MATCH_FILE_NAME.result"` 193 | 194 | if (( $(echo "$MATCH_THRESHOLD $MATCH_RESULT" | awk '{print ($1 > $2)}') )); then 195 | 196 | if [ "$SILENT" -ne 1 ]; then 197 | echo "NOTICE: File was detected as a fake: [$INPUT_FILE]" 198 | fi 199 | 200 | if [ "$LOG" -eq 1 ]; then 201 | echo -e "FAKE:\t$INPUT_FILE" >> $0.log 202 | fi 203 | 204 | if [ $REMOVE_FILES = 1 ]; then 205 | rm -rf "$INPUT_FILE" 206 | fi 207 | 208 | cleanUp 209 | exit 1 210 | else 211 | if [ "$SILENT" -ne 1 ]; then 212 | echo "No matches found in DB - Video is OK." 213 | echo "** If results are incorrect, please report fakes at: https://github.com/ultimate-pms/fake-video-detector **" 214 | fi 215 | fi 216 | done 217 | 218 | if [ "$LOG" -eq 1 ]; then 219 | echo -e "OK:\t$INPUT_FILE" >> $0.log 220 | fi 221 | 222 | cleanUp -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Build docker-container 4 | 5 | dockerInstalled() { 6 | if ! [ -x "$(command -v docker)" ]; then 7 | echo 'Please install docker first, before running this project: https://www.docker.com/' >&2 8 | exit 1 9 | fi 10 | } 11 | 12 | dockerInstalled 13 | 14 | 15 | if [ "$(cat ~/.zshrc | grep fake-video | wc -l)" -eq "0" ] && [ "$(cat ~/.bashrc | grep fake-video | wc -l)" -eq "0" ] ;then 16 | 17 | echo ">>>> Please specify the full path to your media directory" 18 | read -p 'Media Directory: ' MEDIA_DIR 19 | 20 | if [ -z $MEDIA_DIR ] ; then 21 | echo "<<<<<<<<<<<<<<<<<< You configure the 'Media Directory' to continue >>>>>>>>>>>>>>>>>>" 22 | exit 1 23 | fi 24 | 25 | echo -e "\nalias fake-video='docker run -it --rm --privileged -v \"$MEDIA_DIR:$MEDIA_DIR\" fake-video'" | tee -a ~/.bashrc ~/.zshrc 26 | echo -e "\nalias bulk-search='docker run -it --rm --privileged -v \"$MEDIA_DIR:$MEDIA_DIR\" bulk-search'" | tee -a ~/.bashrc ~/.zshrc 27 | 28 | source ~/.bashrc ~/.zshrc 29 | 30 | else 31 | 32 | echo "System is already setup!" 33 | echo "Please remove the alias in your ~/.bashrc and ~/.zshrc files if you wish to re-run the setup process." 34 | 35 | fi 36 | --------------------------------------------------------------------------------