diff --git a/core/pe/matchblock.py b/core/pe/matchblock.py index e8b299b4..f312b2ec 100644 --- a/core/pe/matchblock.py +++ b/core/pe/matchblock.py @@ -118,7 +118,7 @@ def get_match(first, second, percentage): return Match(first, second, percentage) -def async_compare(ref_ids, other_ids, dbname, threshold, picinfo): +def async_compare(ref_ids, other_ids, dbname, threshold, picinfo, match_rotated=False): # The list of ids in ref_ids have to be compared to the list of ids in other_ids. other_ids # can be None. In this case, ref_ids has to be compared with itself # picinfo is a dictionary {pic_id: (dimensions, is_ref)} @@ -136,27 +136,33 @@ def async_compare(ref_ids, other_ids, dbname, threshold, picinfo): other_dimensions, other_is_ref = picinfo[other_id] if ref_is_ref and other_is_ref: continue - rotated_ref_dimensions = (ref_dimensions[1], ref_dimensions[0]) - if ref_dimensions != other_dimensions and rotated_ref_dimensions != other_dimensions: - continue - for orientation_ref in range(8): - for orientation_other in range(8): - try: - diff = avgdiff(ref_blocks[orientation_ref], other_blocks[orientation_other], limit, MIN_ITERATIONS) - percentage = 100 - diff - except (DifferentBlockCountError, NoBlocksError): - percentage = 0 - if percentage >= threshold: - results.append((ref_id, other_id, percentage)) - break + if ref_dimensions != other_dimensions: + if match_rotated: + rotated_ref_dimensions = (ref_dimensions[1], ref_dimensions[0]) + if rotated_ref_dimensions != other_dimensions: + continue else: continue - break + + orientation_range = 1 + if match_rotated: + orientation_range = 8 + + for orientation_ref in range(orientation_range): + try: + diff = avgdiff(ref_blocks[orientation_ref], other_blocks[0], limit, MIN_ITERATIONS) + percentage = 100 - diff + except (DifferentBlockCountError, NoBlocksError): + percentage = 0 + if percentage >= threshold: + results.append((ref_id, other_id, percentage)) + break + cache.close() return results -def getmatches(pictures, cache_path, threshold, match_scaled=False, j=job.nulljob): +def getmatches(pictures, cache_path, threshold, match_scaled=False, match_rotated=False, j=job.nulljob): def get_picinfo(p): if match_scaled: return ((None, None), p.is_ref) @@ -211,7 +217,7 @@ def getmatches(pictures, cache_path, threshold, match_scaled=False, j=job.nulljo picinfo.update({p.cache_id: get_picinfo(p) for p in other_chunk}) else: other_ids = None - args = (ref_ids, other_ids, cache_path, threshold, picinfo) + args = (ref_ids, other_ids, cache_path, threshold, picinfo, match_rotated) async_results.append(pool.apply_async(async_compare, args)) collect_results() collect_results(collect_all=True) diff --git a/core/pe/scanner.py b/core/pe/scanner.py index e58c4a90..8a1d53d2 100644 --- a/core/pe/scanner.py +++ b/core/pe/scanner.py @@ -14,6 +14,7 @@ from core.pe import matchblock, matchexif class ScannerPE(Scanner): cache_path = None match_scaled = False + match_rotated = False @staticmethod def get_scan_options(): @@ -29,6 +30,7 @@ class ScannerPE(Scanner): cache_path=self.cache_path, threshold=self.min_match_percentage, match_scaled=self.match_scaled, + match_rotated=self.match_rotated, j=j, ) elif self.scan_type == ScanType.EXIFTIMESTAMP: diff --git a/help/en/preferences.rst b/help/en/preferences.rst index eadc4f3c..0a1423d5 100644 --- a/help/en/preferences.rst +++ b/help/en/preferences.rst @@ -14,6 +14,10 @@ Preferences If you check this box, pictures of different dimensions will be allowed in the same duplicate group. +**Match pictures of different rotations:** + If you check this box, pictures of different rotations will be allowed in the same + duplicate group. + .. _filter-hardness: **Filter Hardness:** diff --git a/locale/en/LC_MESSAGES/ui.po b/locale/en/LC_MESSAGES/ui.po index 7ef3b1d9..f77da262 100644 --- a/locale/en/LC_MESSAGES/ui.po +++ b/locale/en/LC_MESSAGES/ui.po @@ -307,6 +307,10 @@ msgstr "Debug mode (restart required)" msgid "Match pictures of different dimensions" msgstr "Match pictures of different dimensions" +#: qt/pe/preferences_dialog.py:19 cocoa/en.lproj/Localizable.strings:0 +msgid "Match pictures of different rotations" +msgstr "Match pictures of different rotations" + #: qt/preferences_dialog.py:43 msgid "Filter Hardness:" msgstr "Filter Hardness:" diff --git a/locale/es/LC_MESSAGES/ui.po b/locale/es/LC_MESSAGES/ui.po index d4de1b12..7a21279d 100644 --- a/locale/es/LC_MESSAGES/ui.po +++ b/locale/es/LC_MESSAGES/ui.po @@ -316,6 +316,10 @@ msgstr "Mode de depuración (se requiere reinicio)" msgid "Match pictures of different dimensions" msgstr "Coincidencia de imágenes de distintas dimensiones" +#: qt/pe/preferences_dialog.py:19 cocoa/en.lproj/Localizable.strings:0 +msgid "Match pictures of different rotations" +msgstr "Coincidencia de imágenes de distintas rotaciones" + #: qt/preferences_dialog.py:43 msgid "Filter Hardness:" msgstr "Dureza del Filtro:" diff --git a/locale/pt_BR/LC_MESSAGES/ui.po b/locale/pt_BR/LC_MESSAGES/ui.po index 534ee1a3..b45c3a65 100644 --- a/locale/pt_BR/LC_MESSAGES/ui.po +++ b/locale/pt_BR/LC_MESSAGES/ui.po @@ -314,6 +314,10 @@ msgstr "Modo de Depuração (requer reinício)" msgid "Match pictures of different dimensions" msgstr "Coincidir fotos de dimensões diferentes" +#: qt/pe/preferences_dialog.py:19 cocoa/en.lproj/Localizable.strings:0 +msgid "Match pictures of different rotations" +msgstr "Coincidir fotos de rotações diferentes" + #: qt/preferences_dialog.py:43 msgid "Filter Hardness:" msgstr "Pressão do Filtro:" diff --git a/qt/app.py b/qt/app.py index 5e6271c0..30312cd8 100644 --- a/qt/app.py +++ b/qt/app.py @@ -192,6 +192,7 @@ class DupeGuru(QObject): scanned_tags.add("year") self.model.options["scanned_tags"] = scanned_tags self.model.options["match_scaled"] = self.prefs.match_scaled + self.model.options["match_rotated"] = self.prefs.match_rotated self.model.options["include_exists_check"] = self.prefs.include_exists_check self.model.options["rehash_ignore_mtime"] = self.prefs.rehash_ignore_mtime diff --git a/qt/pe/preferences_dialog.py b/qt/pe/preferences_dialog.py index 375cc779..735d4eda 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -21,6 +21,8 @@ class PreferencesDialog(PreferencesDialogBase): self.widgetsVLayout.addLayout(self.filterHardnessHLayout) self._setupAddCheckbox("matchScaledBox", tr("Match pictures of different dimensions")) self.widgetsVLayout.addWidget(self.matchScaledBox) + self._setupAddCheckbox("matchRotatedBox", tr("Match pictures of different rotations")) + self.widgetsVLayout.addWidget(self.matchRotatedBox) self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind")) self.widgetsVLayout.addWidget(self.mixFileKindBox) self._setupAddCheckbox("useRegexpBox", tr("Use regular expressions when filtering")) @@ -57,6 +59,7 @@ show scrollbars to span the view around" def _load(self, prefs, setchecked, section): setchecked(self.matchScaledBox, prefs.match_scaled) + setchecked(self.matchRotatedBox, prefs.match_rotated) # Update UI state based on selected scan type scan_type = prefs.get_scan_type(AppMode.PICTURE) @@ -67,5 +70,6 @@ show scrollbars to span the view around" def _save(self, prefs, ischecked): prefs.match_scaled = ischecked(self.matchScaledBox) + prefs.match_rotated = ischecked(self.matchRotatedBox) prefs.details_dialog_override_theme_icons = ischecked(self.details_dialog_override_theme_icons) prefs.details_dialog_viewers_show_scrollbars = ischecked(self.details_dialog_viewers_show_scrollbars) diff --git a/qt/preferences.py b/qt/preferences.py index 17ae3bf9..1f88cc31 100644 --- a/qt/preferences.py +++ b/qt/preferences.py @@ -225,6 +225,7 @@ class Preferences(PreferencesBase): self.scan_tag_genre = get("ScanTagGenre", self.scan_tag_genre) self.scan_tag_year = get("ScanTagYear", self.scan_tag_year) self.match_scaled = get("MatchScaled", self.match_scaled) + self.match_rotated = get("MatchRotated", self.match_rotated) def reset(self): self.filter_hardness = 95 @@ -277,6 +278,7 @@ class Preferences(PreferencesBase): self.scan_tag_genre = False self.scan_tag_year = False self.match_scaled = False + self.match_rotated = False def _save_values(self, settings): set_ = self.set_value @@ -330,6 +332,7 @@ class Preferences(PreferencesBase): set_("ScanTagGenre", self.scan_tag_genre) set_("ScanTagYear", self.scan_tag_year) set_("MatchScaled", self.match_scaled) + set_("MatchRotated", self.match_rotated) # scan_type is special because we save it immediately when we set it. def get_scan_type(self, app_mode):