pixmatch 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pixmatch might be problematic. Click here for more details.

pixmatch/__main__.py CHANGED
@@ -1,48 +1,48 @@
1
- import argparse
2
- import logging
3
- import platform
4
-
5
- from pathlib import Path
6
-
7
- from PySide6 import QtWidgets
8
-
9
- from pixmatch.gui import MainWindow
10
-
11
-
12
- if __name__ == "__main__":
13
- parser = argparse.ArgumentParser(
14
- description="Process zero or more file paths."
15
- )
16
- parser.add_argument(
17
- "folders",
18
- nargs="*",
19
- type=Path,
20
- help="Folders to load into the selected file path display (to speed up testing).",
21
- )
22
- parser.add_argument('--verbose', action='store_true', help="More detailed logging")
23
- args = parser.parse_args()
24
-
25
- logging.basicConfig(
26
- level=logging.DEBUG if args.verbose else logging.INFO,
27
- format='%(module)s::%(funcName)s::%(lineno)d %(levelname)s %(asctime)s - %(message)s',
28
- )
29
-
30
- if platform.system() == "Windows":
31
- # Need to tell Windows to not use the Python app icon and use the Window icon isntead...
32
- # I'm not sure on the specifics but calling this method with any string seems to do the trick....
33
- # https://stackoverflow.com/questions/1551605
34
- import ctypes
35
- ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('company.app.1')
36
-
37
- app = QtWidgets.QApplication([])
38
- # Basic stylesheet for subtle polish without complexity.
39
- app.setStyleSheet(
40
- """
41
- QToolBar { spacing: 8px; }
42
- QLabel#GroupTitle { padding: 4px 0; }
43
- QFrame#ImageTile { border: 1px solid #444; border-radius: 6px; padding: 6px; }
44
- """
45
- )
46
- w = MainWindow(args.folders)
47
- w.show()
48
- app.exec()
1
+ import argparse
2
+ import logging
3
+ import platform
4
+
5
+ from pathlib import Path
6
+
7
+ from PySide6 import QtWidgets
8
+
9
+ from pixmatch.gui import MainWindow
10
+
11
+
12
+ if __name__ == "__main__":
13
+ parser = argparse.ArgumentParser(
14
+ description="Process zero or more file paths."
15
+ )
16
+ parser.add_argument(
17
+ "folders",
18
+ nargs="*",
19
+ type=Path,
20
+ help="Folders to load into the selected file path display (to speed up testing).",
21
+ )
22
+ parser.add_argument('--verbose', action='store_true', help="More detailed logging")
23
+ args = parser.parse_args()
24
+
25
+ logging.basicConfig(
26
+ level=logging.DEBUG if args.verbose else logging.INFO,
27
+ format='%(module)s::%(funcName)s::%(lineno)d %(levelname)s %(asctime)s - %(message)s',
28
+ )
29
+
30
+ if platform.system() == "Windows":
31
+ # Need to tell Windows to not use the Python app icon and use the Window icon isntead...
32
+ # I'm not sure on the specifics but calling this method with any string seems to do the trick....
33
+ # https://stackoverflow.com/questions/1551605
34
+ import ctypes
35
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('company.app.1')
36
+
37
+ app = QtWidgets.QApplication([])
38
+ # Basic stylesheet for subtle polish without complexity.
39
+ app.setStyleSheet(
40
+ """
41
+ QToolBar { spacing: 8px; }
42
+ QLabel#GroupTitle { padding: 4px 0; }
43
+ QFrame#ImageTile { border: 1px solid #444; border-radius: 6px; padding: 6px; }
44
+ """
45
+ )
46
+ w = MainWindow(args.folders)
47
+ w.show()
48
+ app.exec()
pixmatch/utils.py CHANGED
@@ -1,36 +1,36 @@
1
- from typing import Iterable
2
-
3
-
4
- def human_bytes(
5
- n: int,
6
- *,
7
- base: int = 1000,
8
- decimals: int = 0,
9
- units: Iterable[str] = ("b", "kb", "mb", "gb", "tb", "pb", "eb", "zb", "yb")
10
- ) -> str:
11
- """
12
- Convert a byte count to a human-readable string.
13
-
14
- Args:
15
- n: Byte count (e.g., from os.stat().st_size).
16
- base: 1000 for SI (kb, mb, ...), 1024 for IEC-like step size.
17
- decimals: Decimal places for non-byte units (0 -> '66kb', 1 -> '1.5gb').
18
- units: Unit suffixes to use. Defaults to lowercase ('kb'); swap for ('B','kB','MB',...) if preferred.
19
-
20
- Returns:
21
- A compact string like '66kb', '1mb', '1.5gb', or '999b'.
22
- """
23
- if n < 0:
24
- raise ValueError("Byte size cannot be negative")
25
-
26
- i = 0
27
- max_i = len(tuple(units)) - 1
28
- while n >= base and i < max_i:
29
- n /= base
30
- i += 1
31
-
32
- if i == 0 or decimals == 0:
33
- # Bytes or integer formatting requested
34
- return f"{int(n if i else n)}{tuple(units)[i]}"
35
-
36
- return f"{n:.{decimals}f}{tuple(units)[i]}".rstrip("0").rstrip(".")
1
+ from typing import Iterable
2
+
3
+
4
+ def human_bytes(
5
+ n: int,
6
+ *,
7
+ base: int = 1000,
8
+ decimals: int = 0,
9
+ units: Iterable[str] = ("b", "kb", "mb", "gb", "tb", "pb", "eb", "zb", "yb")
10
+ ) -> str:
11
+ """
12
+ Convert a byte count to a human-readable string.
13
+
14
+ Args:
15
+ n: Byte count (e.g., from os.stat().st_size).
16
+ base: 1000 for SI (kb, mb, ...), 1024 for IEC-like step size.
17
+ decimals: Decimal places for non-byte units (0 -> '66kb', 1 -> '1.5gb').
18
+ units: Unit suffixes to use. Defaults to lowercase ('kb'); swap for ('B','kB','MB',...) if preferred.
19
+
20
+ Returns:
21
+ A compact string like '66kb', '1mb', '1.5gb', or '999b'.
22
+ """
23
+ if n < 0:
24
+ raise ValueError("Byte size cannot be negative")
25
+
26
+ i = 0
27
+ max_i = len(tuple(units)) - 1
28
+ while n >= base and i < max_i:
29
+ n /= base
30
+ i += 1
31
+
32
+ if i == 0 or decimals == 0:
33
+ # Bytes or integer formatting requested
34
+ return f"{int(n if i else n)}{tuple(units)[i]}"
35
+
36
+ return f"{n:.{decimals}f}{tuple(units)[i]}".rstrip("0").rstrip(".")
@@ -1,93 +1,93 @@
1
- Metadata-Version: 2.4
2
- Name: pixmatch
3
- Version: 0.0.1
4
- Summary: A modern VisiPics replacement.
5
- Author-email: Ryan Heard <ryanwheard@gmail.com>
6
- Project-URL: Repository, https://github.com/rheard/pixmatch
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: Programming Language :: Python :: 3.9
9
- Classifier: Programming Language :: Python :: 3.10
10
- Classifier: Programming Language :: Python :: 3.11
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: Programming Language :: Python :: 3.13
13
- Classifier: Programming Language :: Python :: Implementation :: CPython
14
- Requires-Python: >=3.9
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: Pillow
18
- Requires-Dist: imagehash
19
- Provides-Extra: gui
20
- Requires-Dist: PySide6; extra == "gui"
21
- Dynamic: license-file
22
-
23
- # PixMatch
24
-
25
- PixMatch is a modern, cross-platform duplicate-image finder inspired by VisiPics, built with PySide6.
26
-
27
- ![Basic view of the application](https://github.com/rheard/markdown/blob/main/pixmatch/basic.jpg?raw=true)
28
-
29
- PixMatch scans folders (and ZIP archives) for visually similar images, groups matches,
30
- and lets you quickly keep, ignore, or delete files from a clean GUI.
31
- Rotated, mirrored or recompressed imgaes are no match for PixMatch!
32
- PixMatch can even detect visually similar GIFs and animated WebP files.
33
- Files inside ZIPs are treated as read-only “sources of truth”
34
- —never deleted—so you can safely compare against archived libraries.
35
-
36
-
37
- Supported extensions: `.jpg`, `.jpeg`, `.png`, `.webp`, `.tif`, `.tiff`, `.bmp`, `.gif`, `.zip`.
38
-
39
-
40
- ## Install
41
-
42
- PixMatch is a standard Python app (GUI via PySide6).
43
-
44
- ```bash
45
- python -m pip install pixmatch[gui]
46
- ```
47
-
48
- ## Running
49
-
50
- ```bash
51
- python -m pixmatch
52
- ```
53
-
54
- ### Usage
55
-
56
- Simply select some folders to parse and then click begin.
57
-
58
- Once duplicate groups begin to appear in the duplicates view,
59
- you can start to select actions for them and then execute those actions.
60
- Clicking on a tile will cycle through actions, with red being delete, yellow being ignore, and green being no action.
61
-
62
- Images which are in zips and cannot be deleted will have a rar icon to denote such,
63
- and they cannot be marked for deletion.
64
-
65
- The status bar under each image shows the full path, the file size, the uncompressed file size,
66
- the frames in the image if it is an animated image, the image dimensions and the last modified date.
67
-
68
- Basic status bar example:
69
-
70
- ![Example of the status bar with a basic image loaded](https://github.com/rheard/markdown/blob/main/pixmatch/basic_status.jpg?raw=true)
71
-
72
- Animated image status bar example:
73
-
74
- ![Example of the status bar with an animated image loaded](https://github.com/rheard/markdown/blob/main/pixmatch/gif_status.jpg?raw=true)
75
-
76
- #### Notes
77
- * An exact match checkbox is provided. If strength is 10 and this checkbox is checked,
78
- SHA-256 file hashes will be used instead of perceptual hashes.
79
-
80
- #### Optional Args:
81
- ```markdown
82
- positional arguments:
83
- folders Folders to load into the selected file path display (to speed up testing).
84
-
85
- options:
86
- --verbose More detailed logging
87
- ```
88
-
89
- ## Acknowledgements
90
-
91
- * Thanks to anyone who supported this effort, including the teams behind PySide6, Pillow, PyPI, and many other projects.
92
- * Thanks to Johannes Buchner and the team behind imagehash, which serves as a large backbone in this application and saved me a lot of time.
93
- * Thanks to Guillaume Fouet (aka Ozone) for VisiPics and the inspiration. Please don't be mad, I just wanted some new features like better gif and zip support.
1
+ Metadata-Version: 2.4
2
+ Name: pixmatch
3
+ Version: 0.0.2
4
+ Summary: A modern VisiPics replacement.
5
+ Author-email: Ryan Heard <ryanwheard@gmail.com>
6
+ Project-URL: Repository, https://github.com/rheard/pixmatch
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: Implementation :: CPython
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: Pillow
18
+ Requires-Dist: imagehash
19
+ Provides-Extra: gui
20
+ Requires-Dist: PySide6; extra == "gui"
21
+ Dynamic: license-file
22
+
23
+ # PixMatch
24
+
25
+ PixMatch is a modern, cross-platform duplicate-image finder inspired by VisiPics, built with PySide6.
26
+
27
+ ![Basic view of the application](https://github.com/rheard/markdown/blob/main/pixmatch/basic.jpg?raw=true)
28
+
29
+ PixMatch scans folders (and ZIP archives) for visually similar images, groups matches,
30
+ and lets you quickly keep, ignore, or delete files from a clean GUI.
31
+ Rotated, mirrored or recompressed imgaes are no match for PixMatch!
32
+ PixMatch can even detect visually similar GIFs and animated WebP files.
33
+ Files inside ZIPs are treated as read-only “sources of truth”
34
+ —never deleted—so you can safely compare against archived libraries.
35
+
36
+
37
+ Supported extensions: `.jpg`, `.jpeg`, `.png`, `.webp`, `.tif`, `.tiff`, `.bmp`, `.gif`, `.zip`.
38
+
39
+
40
+ ## Install
41
+
42
+ PixMatch is a standard Python app (GUI via PySide6).
43
+
44
+ ```bash
45
+ python -m pip install pixmatch[gui]
46
+ ```
47
+
48
+ ## Running
49
+
50
+ ```bash
51
+ python -m pixmatch
52
+ ```
53
+
54
+ ### Usage
55
+
56
+ Simply select some folders to parse and then click begin.
57
+
58
+ Once duplicate groups begin to appear in the duplicates view,
59
+ you can start to select actions for them and then execute those actions.
60
+ Clicking on a tile will cycle through actions, with red being delete, yellow being ignore, and green being no action.
61
+
62
+ Images which are in zips and cannot be deleted will have a rar icon to denote such,
63
+ and they cannot be marked for deletion.
64
+
65
+ The status bar under each image shows the full path, the file size, the uncompressed file size,
66
+ the frames in the image if it is an animated image, the image dimensions and the last modified date.
67
+
68
+ Basic status bar example:
69
+
70
+ ![Example of the status bar with a basic image loaded](https://github.com/rheard/markdown/blob/main/pixmatch/basic_status.jpg?raw=true)
71
+
72
+ Animated image status bar example:
73
+
74
+ ![Example of the status bar with an animated image loaded](https://github.com/rheard/markdown/blob/main/pixmatch/gif_status.jpg?raw=true)
75
+
76
+ #### Notes
77
+ * An exact match checkbox is provided. If strength is 10 and this checkbox is checked,
78
+ SHA-256 file hashes will be used instead of perceptual hashes.
79
+
80
+ #### Optional Args:
81
+ ```markdown
82
+ positional arguments:
83
+ folders Folders to load into the selected file path display (to speed up testing).
84
+
85
+ options:
86
+ --verbose More detailed logging
87
+ ```
88
+
89
+ ## Acknowledgements
90
+
91
+ * Thanks to anyone who supported this effort, including the teams behind PySide6, Pillow, PyPI, and many other projects.
92
+ * Thanks to Johannes Buchner and the team behind imagehash, which serves as a large backbone in this application and saved me a lot of time.
93
+ * Thanks to Guillaume Fouet (aka Ozone) for VisiPics and the inspiration. Please don't be mad, I just wanted some new features like better gif and zip support.
@@ -0,0 +1,8 @@
1
+ pixmatch/__init__.py,sha256=5llMGx2o7LwmFuwnVvUnHULWovyotWGWwGoViEmrC0Y,15608
2
+ pixmatch/__main__.py,sha256=cLcDXW228kPcAH5b66MP5eIEFHz6WNuOgqDpPchUke0,1547
3
+ pixmatch/utils.py,sha256=TLYFeMg35B62EUafErq3yaA9YC0O6Kcd3Ao4fSpTwoE,1090
4
+ pixmatch-0.0.2.dist-info/licenses/LICENSE,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
5
+ pixmatch-0.0.2.dist-info/METADATA,sha256=RD_WY654DXph_CwCfO019X4To2MAdaap33rzDANz5DY,3540
6
+ pixmatch-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ pixmatch-0.0.2.dist-info/top_level.txt,sha256=u-67zafU4VFT-oIM4mdGvf9KrHZvD64QjjtNzVxBj7E,9
8
+ pixmatch-0.0.2.dist-info/RECORD,,
@@ -1,19 +1,19 @@
1
- Copyright (c) 2018 The Python Packaging Authority
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy
4
- of this software and associated documentation files (the "Software"), to deal
5
- in the Software without restriction, including without limitation the rights
6
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- copies of the Software, and to permit persons to whom the Software is
8
- furnished to do so, subject to the following conditions:
9
-
10
- The above copyright notice and this permission notice shall be included in all
11
- copies or substantial portions of the Software.
12
-
13
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  SOFTWARE.