1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-03-12 03:31:37 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
f26b515286 More testing with pyproject.toml 2025-12-31 21:22:46 -06:00
9f83018a1a feat: Moving to pyproject.toml for most project configuration
Still have some build migration to do and other cleanup.
2025-12-31 20:33:12 -06:00
Alexander Gee
8f197ea7e1 feat: Create longest and shortest path criteria (#1242)
* Create longest and shortest path criteria
2024-08-23 18:31:46 -05:00
3a97ba941a ci: Merge artifacts
- Merge the resulting artifacts
- Use only the .so files from build
2024-05-11 01:21:58 -07:00
e3bcf9d686 chore: Update VS Code configuration 2024-05-11 00:12:19 -07:00
a81069be61 fix: Photo matching fixes
- Correct bad query introduced in rotation matching
- Promote get_orientation from "private" on photo class
- Fix prepare_pictures to only generate the needed blocks, add check for missing blocks when rotation matchin is true
- Fix cache test inputs to match schema
2024-05-11 00:11:27 -07:00
17 changed files with 216 additions and 206 deletions

View File

@@ -52,4 +52,14 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: modules ${{ matrix.python-version }}
path: ${{ github.workspace }}/**/*.so
path: build/**/*.so
merge-artifacts:
needs: [test]
runs-on: ubuntu-latest
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: modules
pattern: modules*
delete-merged: true

2
.vscode/launch.json vendored
View File

@@ -6,7 +6,7 @@
"configurations": [
{
"name": "DupuGuru",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "run.py",
"console": "integratedTerminal",

View File

@@ -12,5 +12,6 @@
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.black-formatter"
}
},
"python.testing.pytestEnabled": true
}

View File

@@ -158,7 +158,7 @@ class SqliteCache:
ids = ",".join(map(str, rowids))
sql = (
"select rowid, blocks, blocks2, blocks3, blocks4, blocks5, blocks6, blocks7, blocks8 "
f"from pictures where rowid in {ids}"
f"from pictures where rowid in ({ids})"
)
cur = self.con.execute(sql)
return (

View File

@@ -54,7 +54,7 @@ def get_cache(cache_path, readonly=False):
return SqliteCache(cache_path, readonly=readonly)
def prepare_pictures(pictures, cache_path, with_dimensions, j=job.nulljob):
def prepare_pictures(pictures, cache_path, with_dimensions, match_rotated, j=job.nulljob):
# The MemoryError handlers in there use logging without first caring about whether or not
# there is enough memory left to carry on the operation because it is assumed that the
# MemoryError happens when trying to read an image file, which is freed from memory by the
@@ -76,8 +76,14 @@ def prepare_pictures(pictures, cache_path, with_dimensions, j=job.nulljob):
if with_dimensions:
picture.dimensions # pre-read dimensions
try:
if picture.unicode_path not in cache:
if picture.unicode_path not in cache or (
match_rotated and any(block == [] for block in cache[picture.unicode_path])
):
if match_rotated:
blocks = [picture.get_blocks(BLOCK_COUNT_PER_SIDE, orientation) for orientation in range(1, 9)]
else:
blocks = [[]] * 8
blocks[max(picture.get_orientation() - 1, 0)] = picture.get_blocks(BLOCK_COUNT_PER_SIDE)
cache[picture.unicode_path] = blocks
prepared.append(picture)
except (OSError, ValueError) as e:
@@ -187,7 +193,7 @@ def getmatches(pictures, cache_path, threshold, match_scaled=False, match_rotate
j.set_progress(comparison_count, progress_msg)
j = j.start_subjob([3, 7])
pictures = prepare_pictures(pictures, cache_path, with_dimensions=not match_scaled, j=j)
pictures = prepare_pictures(pictures, cache_path, not match_scaled, match_rotated, j=j)
j = j.start_subjob([9, 1], tr("Preparing for matching"))
cache = get_cache(cache_path)
id2picture = {}

View File

@@ -37,7 +37,7 @@ class Photo(fs.File):
def _plat_get_blocks(self, block_count_per_side, orientation):
raise NotImplementedError()
def _get_orientation(self):
def get_orientation(self):
if not hasattr(self, "_cached_orientation"):
try:
with self.path.open("rb") as fp:
@@ -95,13 +95,13 @@ class Photo(fs.File):
fs.File._read_info(self, field)
if field == "dimensions":
self.dimensions = self._plat_get_dimensions()
if self._get_orientation() in {5, 6, 7, 8}:
if self.get_orientation() in {5, 6, 7, 8}:
self.dimensions = (self.dimensions[1], self.dimensions[0])
elif field == "exif_timestamp":
self.exif_timestamp = self._get_exif_timestamp()
def get_blocks(self, block_count_per_side, orientation: int = None):
if orientation is None:
return self._plat_get_blocks(block_count_per_side, self._get_orientation())
return self._plat_get_blocks(block_count_per_side, self.get_orientation())
else:
return self._plat_get_blocks(block_count_per_side, orientation)

View File

@@ -96,6 +96,8 @@ class FilenameCategory(CriterionCategory):
DOESNT_END_WITH_NUMBER = 1
LONGEST = 2
SHORTEST = 3
LONGEST_PATH = 4
SHORTEST_PATH = 5
def format_criterion_value(self, value):
return {
@@ -103,6 +105,8 @@ class FilenameCategory(CriterionCategory):
self.DOESNT_END_WITH_NUMBER: tr("Doesn't end with number"),
self.LONGEST: tr("Longest"),
self.SHORTEST: tr("Shortest"),
self.LONGEST_PATH: tr("Longest Path"),
self.SHORTEST_PATH: tr("Shortest Path"),
}[value]
def extract_value(self, dupe):
@@ -116,6 +120,10 @@ class FilenameCategory(CriterionCategory):
return 0 if ends_with_digit else 1
else:
return 1 if ends_with_digit else 0
elif crit_value == self.LONGEST_PATH:
return len(str(dupe.folder_path)) * -1
elif crit_value == self.SHORTEST_PATH:
return len(str(dupe.folder_path))
else:
value = len(value)
if crit_value == self.LONGEST:
@@ -130,6 +138,8 @@ class FilenameCategory(CriterionCategory):
self.DOESNT_END_WITH_NUMBER,
self.LONGEST,
self.SHORTEST,
self.LONGEST_PATH,
self.SHORTEST_PATH,
]
]

View File

@@ -59,13 +59,13 @@ class BaseTestCaseCache:
def test_set_then_retrieve_blocks(self):
c = self.get_cache()
b = [(0, 0, 0), (1, 2, 3)]
b = [[(0, 0, 0), (1, 2, 3)]] * 8
c["foo"] = b
eq_(b, c["foo"])
def test_delitem(self):
c = self.get_cache()
c["foo"] = ""
c["foo"] = [[]] * 8
del c["foo"]
assert "foo" not in c
with raises(KeyError):
@@ -74,16 +74,16 @@ class BaseTestCaseCache:
def test_persistance(self, tmpdir):
DBNAME = tmpdir.join("hstest.db")
c = self.get_cache(str(DBNAME))
c["foo"] = [(1, 2, 3)]
c["foo"] = [[(1, 2, 3)]] * 8
del c
c = self.get_cache(str(DBNAME))
eq_([(1, 2, 3)], c["foo"])
eq_([[(1, 2, 3)]] * 8, c["foo"])
def test_filter(self):
c = self.get_cache()
c["foo"] = ""
c["bar"] = ""
c["baz"] = ""
c["foo"] = [[]] * 8
c["bar"] = [[]] * 8
c["baz"] = [[]] * 8
c.filter(lambda p: p != "bar") # only 'bar' is removed
eq_(2, len(c))
assert "foo" in c
@@ -92,9 +92,9 @@ class BaseTestCaseCache:
def test_clear(self):
c = self.get_cache()
c["foo"] = ""
c["bar"] = ""
c["baz"] = ""
c["foo"] = [[]] * 8
c["bar"] = [[]] * 8
c["baz"] = [[]] * 8
c.clear()
eq_(0, len(c))
assert "foo" not in c
@@ -104,7 +104,7 @@ class BaseTestCaseCache:
def test_by_id(self):
# it's possible to use the cache by referring to the files by their row_id
c = self.get_cache()
b = [(0, 0, 0), (1, 2, 3)]
b = [[(0, 0, 0), (1, 2, 3)]] * 8
c["foo"] = b
foo_id = c.get_id("foo")
eq_(c[foo_id], b)
@@ -127,10 +127,10 @@ class TestCaseSqliteCache(BaseTestCaseCache):
fp.write("invalid sqlite content")
fp.close()
c = self.get_cache(dbname) # should not raise a DatabaseError
c["foo"] = [(1, 2, 3)]
c["foo"] = [[(1, 2, 3)]] * 8
del c
c = self.get_cache(dbname)
eq_(c["foo"], [(1, 2, 3)])
eq_(c["foo"], [[(1, 2, 3)]] * 8)
class TestCaseCacheSQLEscape:
@@ -152,7 +152,7 @@ class TestCaseCacheSQLEscape:
def test_delitem(self):
c = self.get_cache()
c["foo'bar"] = []
c["foo'bar"] = [[]] * 8
try:
del c["foo'bar"]
except KeyError:

View File

@@ -44,7 +44,7 @@ msgstr "Dossier"
#: core\me\result_table.py:21
msgid "Size (MB)"
msgstr "Taille (Mo)"
msgstr "Taille (MB)"
#: core\me\result_table.py:22
msgid "Time"
@@ -52,7 +52,7 @@ msgstr "Temps"
#: core\me\result_table.py:24
msgid "Sample Rate"
msgstr "Échantillonnage"
msgstr "Sample Rate"
#: core\me\result_table.py:25 core\pe\result_table.py:22 core\prioritize.py:65
#: core\se\result_table.py:22
@@ -86,7 +86,7 @@ msgstr "Année"
#: core\me\result_table.py:32
msgid "Track Number"
msgstr "Numéro de piste"
msgstr "Track"
#: core\me\result_table.py:33
msgid "Comment"
@@ -95,16 +95,16 @@ msgstr "Commentaire"
#: core\me\result_table.py:34 core\pe\result_table.py:26
#: core\se\result_table.py:24
msgid "Match %"
msgstr "Correspond à %"
msgstr "Match %"
#: core\me\result_table.py:35 core\se\result_table.py:25
msgid "Words Used"
msgstr "Mots utilisés"
msgstr "Mots"
#: core\me\result_table.py:36 core\pe\result_table.py:27
#: core\se\result_table.py:26
msgid "Dupe Count"
msgstr "Nombre de doublons"
msgstr "Nombre de Doublons"
#: core\pe\prioritize.py:23 core\pe\result_table.py:23
msgid "Dimensions"
@@ -112,7 +112,7 @@ msgstr "Dimensions"
#: core\pe\result_table.py:21 core\se\result_table.py:21
msgid "Size (KB)"
msgstr "Taille (Ko)"
msgstr "Taille (KB)"
#: core\pe\result_table.py:24
msgid "EXIF Timestamp"

View File

@@ -52,8 +52,8 @@ msgid ""
"A previous action is still hanging in there. You can't start a new one yet. "
"Wait a few seconds, then try again."
msgstr ""
"Une action précédente est encore en cours. Vous ne pouvez pas encore en démarrer une nouvelle. "
"Attendez quelques secondes et essayer à nouveau."
"Une action précédente est encore en cours. Attendez quelques secondes avant "
"d'en repartir une nouvelle."
#: core\app.py:300
msgid "No duplicates found."
@@ -69,7 +69,7 @@ msgstr "Tous les fichiers marqués ont été déplacés correctement."
#: core\app.py:319
msgid "All marked files were deleted successfully."
msgstr "Tous les fichiers marqués ont bien été supprimés."
msgstr ""
#: core\app.py:321
msgid "All marked files were successfully sent to Trash."
@@ -137,11 +137,11 @@ msgstr "%s (%d hors-groupe)"
#: core\directories.py:191
msgid "Collected {} files to scan"
msgstr "{} fichiers collectés pour scanner"
msgstr ""
#: core\directories.py:207
msgid "Collected {} folders to scan"
msgstr "{} dossiers collectés pour scanner"
msgstr ""
#: core\engine.py:27
msgid "%d matches found from %d groups"
@@ -183,7 +183,7 @@ msgstr "Contenu"
#: core\pe\matchblock.py:66
msgid "Analyzed %d/%d pictures"
msgstr "Analysé %d/%d images"
msgstr "Analyzé %d/%d images"
#: core\pe\matchblock.py:183
msgid "Performed %d/%d chunk matches"
@@ -215,7 +215,7 @@ msgstr "Chiffres à la fin"
#: core\prioritize.py:103
msgid "Doesn't end with number"
msgstr "Pas de chiffres à la fin"
msgstr "Pas de chiffres à la finr"
#: core\prioritize.py:104
msgid "Longest"

View File

@@ -66,16 +66,16 @@ msgid ""
"After having deleted a duplicate, place a link targeting the reference file "
"to replace the deleted file."
msgstr ""
"Après avoir effacé un fichier dupliqué, remplacer celui-ci par un lien vers le "
"Après avoir effacé un fichier, remplacer celui-ci par un lien vers le "
"fichier référence."
#: qt/deletion_options.py:44
msgid "Hardlink"
msgstr "Lien en dur"
msgstr "Hardlink"
#: qt/deletion_options.py:44
msgid "Symlink"
msgstr "Lien symbolique"
msgstr "Symlink"
#: qt/deletion_options.py:48
msgid " (unsupported)"
@@ -91,7 +91,7 @@ msgid ""
"usually used as a workaround when the normal deletion method doesn't work."
msgstr ""
"Au lieu de passer par la corbeille, supprimer directement. Cette option "
"n'est généralement utilisée qu'en cas d'échec avec la métode de suppression normale."
"n'est généralement utilisée qu'en cas de problème."
#: qt/deletion_options.py:59 cocoa/en.lproj/Localizable.strings:0
msgid "Proceed"
@@ -254,7 +254,7 @@ msgstr "Tags à scanner:"
#: qt/me/preferences_dialog.py:36 cocoa/en.lproj/Localizable.strings:0
msgid "Track"
msgstr "Piste"
msgstr "Track"
#: qt/me/preferences_dialog.py:38 cocoa/en.lproj/Localizable.strings:0
msgid "Artist"
@@ -299,12 +299,12 @@ msgstr "Utiliser les expressions régulières pour les filtres"
#: qt/me/preferences_dialog.py:58 qt/pe/preferences_dialog.py:25
#: qt/se/preferences_dialog.py:38 cocoa/en.lproj/Localizable.strings:0
msgid "Remove empty folders on delete or move"
msgstr "Effacer les dossiers vides après une suppression ou un déplacement"
msgstr "Effacer les dossiers vides après un déplacement"
#: qt/me/preferences_dialog.py:60 qt/pe/preferences_dialog.py:27
#: qt/se/preferences_dialog.py:59 cocoa/en.lproj/Localizable.strings:0
msgid "Ignore duplicates hardlinking to the same file"
msgstr "Ignorer doublons avec un lien en dur vers le même fichier"
msgstr "Ignorer doublons avec hardlink vers le même fichier"
#: qt/me/preferences_dialog.py:62 qt/pe/preferences_dialog.py:29
#: qt/se/preferences_dialog.py:62 cocoa/en.lproj/Localizable.strings:0
@@ -337,7 +337,7 @@ msgstr "Langue :"
#: qt/preferences_dialog.py:91 cocoa/en.lproj/Localizable.strings:0
msgid "Copy and Move:"
msgstr "Copie et déplacements de fichiers :"
msgstr "Déplacements de fichiers:"
#: qt/preferences_dialog.py:94 cocoa/en.lproj/Localizable.strings:0
msgid "Right in destination"
@@ -369,8 +369,8 @@ msgid ""
" the best to these criteria to their respective group's reference position. "
"Read the help file for more information."
msgstr ""
"Ajoutez des critères dans la liste de droite et cliquez sur OK pour envoyer les doublons qui "
"correspondent le plus à ces critères à la position de référence. Une lecture "
"Ajoutez des critères dans la liste de droite pour envoyer les doublons qui "
"correspondent le plus à ces critère à la position de référence. Une lecture "
"préalable du fichier d'aide est conseillée."
#: qt/problem_dialog.py:33 cocoa/en.lproj/Localizable.strings:0
@@ -383,13 +383,13 @@ msgid ""
"these problems are described in the table below. Those files were not "
"removed from your results."
msgstr ""
"Des problèmes ont été rencontrés lors du traitement de certains fichiers (ou tous). La"
"Des problèmes ont été rencontrés lors du traitement de certains fichiers. La"
" nature de ces problèmes est décrite dans la liste ci-dessous. Ces fichiers "
"n'ont pas été retirés des résultats."
#: qt/problem_dialog.py:56
msgid "Reveal Selected"
msgstr "Révéler la selection"
msgstr "Révéler Fichier"
#: qt/result_window.py:57 qt/result_window.py:104 qt/result_window.py:167
#: qt/result_window.py:191 cocoa/en.lproj/Localizable.strings:0
@@ -406,19 +406,19 @@ msgstr "Montrer les valeurs en tant que delta"
#: qt/result_window.py:60
msgid "Send Marked to Recycle Bin..."
msgstr "Envoyer les fichiers marqués à la corbeille..."
msgstr "Envoyer marqués à la corbeille..."
#: qt/result_window.py:61 cocoa/en.lproj/Localizable.strings:0
msgid "Move Marked to..."
msgstr "Déplacer les fichiers marqués vers..."
msgstr "Déplacer marqués vers..."
#: qt/result_window.py:62 cocoa/en.lproj/Localizable.strings:0
msgid "Copy Marked to..."
msgstr "Copier les fichiers marqués vers..."
msgstr "Copier marqués vers..."
#: qt/result_window.py:63 cocoa/en.lproj/Localizable.strings:0
msgid "Remove Marked from Results"
msgstr "Retirer les fichiers marqués des résultats"
msgstr "Retirer marqués des résultats"
#: qt/result_window.py:64 cocoa/en.lproj/Localizable.strings:0
msgid "Re-Prioritize Results..."
@@ -426,19 +426,19 @@ msgstr "Re-prioriser les résultats"
#: qt/result_window.py:67 cocoa/en.lproj/Localizable.strings:0
msgid "Remove Selected from Results"
msgstr "Retirer les fichiers sélectionnés des résultats"
msgstr "Retirer sélectionnés des résultats"
#: qt/result_window.py:71 cocoa/en.lproj/Localizable.strings:0
msgid "Add Selected to Ignore List"
msgstr "Ajouter les fichiers sélectionnés à la liste de fichiers ignorés"
msgstr "Ajouter sélectionnés à la liste de fichiers ignorés"
#: qt/result_window.py:75 cocoa/en.lproj/Localizable.strings:0
msgid "Make Selected into Reference"
msgstr "Transformer les fichiers sélectionnés en références"
msgstr "Transformer sélectionnés en références"
#: qt/result_window.py:77 cocoa/en.lproj/Localizable.strings:0
msgid "Open Selected with Default Application"
msgstr "Ouvrir les fichiers sélectionnés avec l'application par défaut"
msgstr "Ouvrir sélectionné avec l'application par défaut"
#: qt/result_window.py:80
msgid "Open Containing Folder of Selected"
@@ -466,19 +466,19 @@ msgstr "Marquer sélectionnés"
#: qt/result_window.py:87
msgid "Export To HTML"
msgstr "Exporter en HTML"
msgstr "Exporter vers HTML"
#: qt/result_window.py:88
msgid "Export To CSV"
msgstr "Exporter en CSV"
msgstr "Exporter vers CSV"
#: qt/result_window.py:89 cocoa/en.lproj/Localizable.strings:0
msgid "Save Results..."
msgstr "Sauvegarder les résultats..."
msgstr "Sauvegarder résultats..."
#: qt/result_window.py:90 cocoa/en.lproj/Localizable.strings:0
msgid "Invoke Custom Command"
msgstr "Invoquer une commande personnalisée"
msgstr "Invoquer commande personnalisée"
#: qt/result_window.py:102
msgid "Mark"
@@ -498,7 +498,7 @@ msgstr "{} résultats"
#: qt/result_window.py:193 cocoa/en.lproj/Localizable.strings:0
msgid "Dupes Only"
msgstr "Dupliqués seulement"
msgstr "Sans réf."
#: qt/result_window.py:194
msgid "Delta Values"
@@ -514,7 +514,7 @@ msgstr "Ignorer les fichiers plus petits que"
#: qt/se/preferences_dialog.py:52 cocoa/en.lproj/Localizable.strings:0
msgid "KB"
msgstr "Ko"
msgstr "KB"
#: cocoa/en.lproj/Localizable.strings:0
msgid "%@ Results"
@@ -526,7 +526,7 @@ msgstr "Action"
#: cocoa/en.lproj/Localizable.strings:0
msgid "Add New Folder..."
msgstr "Ajouter un nouveau dossier..."
msgstr "Ajouter dossier..."
#: cocoa/en.lproj/Localizable.strings:0
msgid "Advanced"
@@ -546,7 +546,7 @@ msgstr "Tout ramener au premier plan"
#: cocoa/en.lproj/Localizable.strings:0
msgid "Check for update..."
msgstr "Recherche de mise à jour..."
msgstr "Mise à jour..."
#: cocoa/en.lproj/Localizable.strings:0
msgid "Close Window"
@@ -602,11 +602,11 @@ msgstr "Édition"
#: cocoa/en.lproj/Localizable.strings:0
msgid "Export Results to CSV"
msgstr "Exporter les résultats en CSV"
msgstr "Exporter les résultats vers CSV"
#: cocoa/en.lproj/Localizable.strings:0
msgid "Export Results to XHTML"
msgstr "Exporter les résultats en XHTML"
msgstr "Exporter les résultats vers HTML"
#: cocoa/en.lproj/Localizable.strings:0
msgid "Fewer results"
@@ -658,7 +658,7 @@ msgstr "Mode"
#: cocoa/en.lproj/Localizable.strings:0
msgid "More results"
msgstr "Plus de doublons"
msgstr "plus de doublons"
#: cocoa/en.lproj/Localizable.strings:0
msgid "Ok"
@@ -674,7 +674,7 @@ msgstr "Préférences..."
#: cocoa/en.lproj/Localizable.strings:0
msgid "Quick Look"
msgstr "Regard rapide"
msgstr "Quick Look"
#: cocoa/en.lproj/Localizable.strings:0
msgid "Quit dupeGuru"
@@ -798,11 +798,11 @@ msgstr "Erreur de compilation:"
#: qt\pe\image_viewer.py:56
msgid "Increase zoom"
msgstr "Zoomer"
msgstr "Accroître zoom"
#: qt\pe\image_viewer.py:66
msgid "Decrease zoom"
msgstr "Dézoomer"
msgstr "Décroître zoom"
#: qt\pe\image_viewer.py:71
msgid "Ctrl+/"
@@ -945,61 +945,59 @@ msgstr "Affichage"
#: qt\se\preferences_dialog.py:70
msgid "Partially hash files bigger than"
msgstr Hacher partiellement les fichiers plus gros que"
msgstr ""
#: qt\se\preferences_dialog.py:80
msgid "MB"
msgstr "Mo"
msgstr ""
#: qt\preferences_dialog.py:163
msgid "Use native OS dialogs"
msgstr "Utiliser les dialogues natifs de l'OS"
msgstr ""
#: qt\preferences_dialog.py:166
msgid ""
"For actions such as file/folder selection use the OS native dialogs.\n"
"Some native dialogs have limited functionality."
msgstr "Pour les actions telles que la sélection de fichiers/dossiers, utilisez les boîtes de dialogue natives du système d'exploitation.\n"
"Des boites de dialogues ont des fonctionnalités limitées."
msgstr ""
#: qt\se\preferences_dialog.py:68
msgid "Ignore files larger than"
msgstr "Ignorer les fichiers plus gros que"
msgstr ""
#: qt\app.py:135 qt\app.py:293
msgid "Clear Cache"
msgstr "Vider le cache"
msgstr ""
#: qt\app.py:294
msgid ""
"Do you really want to clear the cache? This will remove all cached file "
"hashes and picture analysis."
msgstr "Voulez-vous réellement vider le cacher ? Cela supprimera tous les hash de fichiers dans le cache"
" et les analyses d'image."
msgstr ""
#: qt\app.py:299
msgid "Cache cleared."
msgstr "Cache vidé."
msgstr ""
#: qt\preferences_dialog.py:173
msgid "Use dark style"
msgstr "Utliliser le style sombre"
msgstr ""
#: qt\preferences_dialog.py:241
msgid "Profile scan operation"
msgstr "Opération de balayage de profil"
msgstr ""
#: qt\preferences_dialog.py:242
msgid "Profile the scan operation and save logs for optimization."
msgstr "Profilage de l'opération de balayage et enregistrement des journaux pour optimisation"
msgstr ""
#: qt\preferences_dialog.py:246
msgid "Logs located in: <a href=\"{}\">{}</a>"
msgstr "Les logs se trouvent dans : <a href=\"{}\">{}</a>"
msgstr ""
#: qt\preferences_dialog.py:291
msgid "Debug"
msgstr "Débogage"
msgstr ""
#: qt\about_box.py:31
msgid "About {}"
@@ -1011,7 +1009,7 @@ msgstr "Version {}"
#: qt\about_box.py:49 qt\about_box.py:75
msgid "Checking for updates..."
msgstr "Recherche des mises à jour..."
msgstr ""
#: qt\about_box.py:54
msgid "Licensed under GPLv3"
@@ -1019,11 +1017,11 @@ msgstr "Sous licence GPLv3"
#: qt\about_box.py:68
msgid "No update available."
msgstr "Pas de mise à jour disponible."
msgstr ""
#: qt\about_box.py:71
msgid "New version {} available, download <a href=\"{}\">here</a>."
msgstr "Une nouvelle version ({}) est disponible, téléchagez-là <a href=\"{}\">ici</a>."
msgstr ""
#: qt\error_report_dialog.py:50
msgid "Error Report"
@@ -1143,36 +1141,31 @@ msgstr "Recherche..."
msgid ""
"These options are for advanced users or for very specific situations, most "
"users should not have to modify these."
msgstr "Ces options sont destinées aux utilisateurs avancés ou pour des situation bien spécifiques, "
"la plupart des utilisateurs ne devraient pas avoir à les modifier."
msgstr ""
#: qt\preferences_dialog.py:225
msgid "Include existence check after scan completion"
msgstr "Inclure un contrôle d'existence à la fin de l'analyse"
msgstr ""
#: qt\preferences_dialog.py:227
msgid "Ignore difference in mtime when loading cached digests"
msgstr "Ignorer la différence dans le mtime en chargent les résumés en cache"
msgstr ""
#: qt\progress_window.py:64
msgid "Cancel?"
msgstr "Annuler ?"
msgstr ""
#: qt\progress_window.py:65
msgid "Are you sure you want to cancel? All progress will be lost."
msgstr "Êtes-vous sûr de vouloir annuler ? Tout ce qui a déjà été fait va être perdu."
msgstr ""
#: qt\exclude_list_dialog.py:161
msgid ""
"These (case sensitive) python regular expressions will filter out files during scans.<br>Directores will also have their <strong>default state</strong> set to Excluded in the Directories tab if their name happens to match one of the selected regular expressions.<br>For each file collected, two tests are performed to determine whether or not to completely ignore it:<br><li>1. Regular expressions with no path separator in them will be compared to the file name only.</li>\n"
"<li>2. Regular expressions with at least one path separator in them will be compared to the full path to the file.</li><br>Example: if you want to filter out .PNG files from the \"My Pictures\" directory only:<br><code>.*My\\sPictures\\\\.*\\.png</code><br><br>You can test the regular expression with the \"test string\" button after pasting a fake path in the test field:<br><code>C:\\\\User\\My Pictures\\test.png</code><br><br>\n"
"Matching regular expressions will be highlighted.<br>If there is at least one highlight, the path or filename tested will be ignored during scans.<br><br>Directories and files starting with a period '.' are filtered out by default.<br><br>"
msgstr "Cette expression régulière python (sensible à la casse) va filtrer les fichiers pendant les scans.<br>"
"Ces expressions régulières python (sensible aux majuscules) peuvent ignorer les fichiers pendant les scans. Les dossiers auront également leur <strong>état par défaut</strong> mis sur Exclus dans l'onglet Dossiers si leur nom correspond à une des expressions régulières sélectionnées<br>Pour chaque fichier collecté, deux tests sont faits pour déterminer s'il doit être totalement ignoré:<br><li>1. Les expressions régulières sans séparateur de chemin sont comparées au nom de fichier seul.</li>\n"
"<li>2. Les expressions régulières avec au moins un séparateur de chemin sont comparées au chemin complet vers le fichier.</li><br>\n"
"Exemple: si vous voulez uniquement ignorer les fichiers .PNG du dossier \"Mes Images\":<br><code>.*Mes\\sImages\\\\.*\\.png</code><br><br>Vous pouvez tester l'expression régulière via le bouton \"Tester la chaîne de caractères\" après avoir tapé un faux chemin de fichier dans le champs correspondant:<br><code>C:\\\\Utilisateur\\Mes Images\\test.png</code><br><br>\n"
"Les expressions régulières qui fonctionnent seront surlignées.<br>S'il y a au moins un surlignage, le chemin ou nom de fichier testé sera ignoré durant les scans.<br><br>Les dossiers et fichiers commençant par un point '.' sont ignorés par défaut.<br><br>"
msgstr ""
#: qt\pe\preferences_dialog.py:24
msgid "Match pictures of different rotations"
msgstr "Associer des images de différentes rotations"
msgstr ""

View File

@@ -1,9 +1,86 @@
[build-system]
requires = ["setuptools"]
requires = ["setuptools >= 75.3.1"]
build-backend = "setuptools.build_meta"
[project]
name = "dupeGuru"
description = "dupeGuru is a tool to find duplicate files on your computer."
authors = [
{name = "Andrew Senetar", email = "arsenetar@voltaicideas.net"}
]
readme = "README.md"
license = "GPL-3.0-or-later"
license-files = ["LICENSE"]
keywords = ["deduplication"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: End Users/Desktop",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Desktop Environment :: File Managers",
]
requires-python = ">=3.7, <3.13"
dynamic = ["version"]
dependencies = [
"distro>=1.8.0,<2.0.0",
"mutagen>=1.46.0,<2.0.0",
"polib>=1.1.0,<2.0.0",
"PyQt5 >=5.15.0,<6.0; sys_platform != 'linux'",
"pywin32>=304; sys_platform == 'win32'",
"semantic-version>=2.0.0,<3.0.0",
"Send2Trash>=1.8.2",
"xxhash>=3.0.0,<4.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7,<8",
"flake8",
"black",
]
build = [
"dupeGuru[dev]",
"sphinx>=5.3.0,<8.0.0",
"pyinstaller>=5.6,<6.0; sys_platform != 'linux'"
]
[project.urls]
Homepage = "https://dupeguru.voltaicideas.net/"
Documentation = "https://dupeguru.voltaicideas.net/help/en/"
Repository = "https://github.com/arsenetar/dupeguru.git"
Issues = "https://github.com/arsenetar/dupeguru/issues"
Releases = "https://github.com/arsenetar/dupeguru/releases"
[project.gui-scripts]
dupeguru = "dupeguru.__main__:main"
[tool.black]
line-length = 120
[tool.isort]
# make it compatible with black
profile = "black"
skip_gitignore = true
[tool.setuptools.packages.find]
include = ["core", "hscommon", "qt"]
[tool.setuptools.dynamic]
version = {attr = "core.__version__"}
[tool.setuptools]
ext-modules = [
{name = "core.pe._block", sources = ["core/pe/modules/block.c", "core/pe/modules/common.c"], include-dirs = ["core/pe/modules"]},
{name = "core.pe._cache", sources = ["core/pe/modules/cache.c", "core/pe/modules/common.c"], include-dirs = ["core/pe/modules"]},
{name = "qt.pe._block_qt", sources = ["qt/pe/modules/block.c"]},
]

View File

@@ -1,4 +0,0 @@
pytest>=7,<8
flake8
black
pyinstaller>=5.6,<6.0; sys_platform != 'linux'

View File

@@ -1,9 +0,0 @@
distro>=1.8.0,<2.0.0
mutagen>=1.46.0,<2.0.0
polib>=1.1.0,<2.0.0
PyQt5 >=5.15.0,<6.0; sys_platform != 'linux'
pywin32>=304; sys_platform == 'win32'
semantic-version>=2.0.0,<3.0.0
Send2Trash>=1.8.2,<2.0.0
sphinx>=5.3.0,<8.0.0
xxhash>=3.0.0,<4.0.0

View File

@@ -1,48 +0,0 @@
[metadata]
name = dupeGuru
version = attr: core.__version__
url = https://github.com/arsenetar/dupeguru
project_urls =
Bug Reports = https://github.com/arsenetar/dupeguru/issues
author = Andrew Senetar
author_email = arsenetar@voltaicideas.net
license = GPLv3
license_files = license
description = dupeGuru is a tool to find duplicate files on your computer.
long_description = file:README.md
long_description_content_type = text/markdown
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: End Users/Desktop
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Operating System :: MacOS :: MacOS X
Operating System :: Microsoft :: Windows
Operating System :: POSIX
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3 :: Only
Topic :: Desktop Environment :: File Managers
[options]
packages = find:
python_requires = >=3.7
install_requires =
Send2Trash>=1.8.2,<2.0.0
mutagen>=1.46.0,<2.0.0
distro>=1.8.0,<2.0.0
PyQt5 >=5.15.0,<6.0; sys_platform != 'linux'
pywin32>=228; sys_platform == 'win32'
semantic-version>=2.0.0,<3.0.0
xxhash>=3.0.0,<4.0.0
setup_requires =
sphinx>=3.0.0
polib>=1.1.0
tests_require =
pytest >=6,<7
include_package_data = true
[options.entry_points]
console_scripts =
dupeguru = run.py

View File

@@ -1,26 +0,0 @@
from setuptools import setup, Extension
from pathlib import Path
exts = [
Extension(
"core.pe._block",
[
str(Path("core", "pe", "modules", "block.c")),
str(Path("core", "pe", "modules", "common.c")),
],
include_dirs=[str(Path("core", "pe", "modules"))],
),
Extension(
"core.pe._cache",
[
str(Path("core", "pe", "modules", "cache.c")),
str(Path("core", "pe", "modules", "common.c")),
],
include_dirs=[str(Path("core", "pe", "modules"))],
),
Extension("qt.pe._block_qt", [str(Path("qt", "pe", "modules", "block.c"))]),
]
headers = [str(Path("core", "pe", "modules", "common.h"))]
setup(ext_modules=exts, headers=headers)