diff --git a/cocoa/base/PyDupeGuru.h b/cocoa/base/PyDupeGuru.h index 5ef903b5..1ac8f6d4 100644 --- a/cocoa/base/PyDupeGuru.h +++ b/cocoa/base/PyDupeGuru.h @@ -48,6 +48,7 @@ http://www.hardcoded.net/licenses/bsd_license - (NSArray *)deltaColumns; //Scanning options +- (void)setScanType:(NSNumber *)scan_type; - (void)setMinMatchPercentage:(NSNumber *)percentage; - (void)setMixFileKind:(BOOL)mix_file_kind; - (void)setEscapeFilterRegexp:(BOOL)escape_filter_regexp; diff --git a/cocoa/me/PyDupeGuru.h b/cocoa/me/PyDupeGuru.h index ac114c28..17680731 100644 --- a/cocoa/me/PyDupeGuru.h +++ b/cocoa/me/PyDupeGuru.h @@ -11,7 +11,6 @@ http://www.hardcoded.net/licenses/bsd_license @interface PyDupeGuru : PyDupeGuruBase //Scanning options -- (void)setScanType:(NSNumber *)scan_type; - (void)setMinWordCount:(NSNumber *)word_count; - (void)setMinWordLength:(NSNumber *)word_length; - (void)setWordWeighting:(NSNumber *)words_are_weighted; diff --git a/cocoa/pe/AppDelegate.m b/cocoa/pe/AppDelegate.m index e4254b10..dfdc379f 100644 --- a/cocoa/pe/AppDelegate.m +++ b/cocoa/pe/AppDelegate.m @@ -20,14 +20,15 @@ http://www.hardcoded.net/licenses/bsd_license { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:10]; - [d setObject:[NSNumber numberWithInt:95] forKey:@"minMatchPercentage"]; - [d setObject:[NSNumber numberWithInt:1] forKey:@"recreatePathType"]; - [d setObject:[NSNumber numberWithBool:NO] forKey:@"matchScaled"]; - [d setObject:[NSNumber numberWithBool:YES] forKey:@"mixFileKind"]; - [d setObject:[NSNumber numberWithBool:NO] forKey:@"useRegexpFilter"]; - [d setObject:[NSNumber numberWithBool:NO] forKey:@"ignoreHardlinkMatches"]; - [d setObject:[NSNumber numberWithBool:NO] forKey:@"removeEmptyFolders"]; - [d setObject:[NSNumber numberWithBool:NO] forKey:@"debug"]; + [d setObject:i2n(0) forKey:@"scanType"]; + [d setObject:i2n(95) forKey:@"minMatchPercentage"]; + [d setObject:i2n(1) forKey:@"recreatePathType"]; + [d setObject:b2n(NO) forKey:@"matchScaled"]; + [d setObject:b2n(YES) forKey:@"mixFileKind"]; + [d setObject:b2n(NO) forKey:@"useRegexpFilter"]; + [d setObject:b2n(NO) forKey:@"ignoreHardlinkMatches"]; + [d setObject:b2n(NO) forKey:@"removeEmptyFolders"]; + [d setObject:b2n(NO) forKey:@"debug"]; [d setObject:[NSArray array] forKey:@"recentDirectories"]; [d setObject:[NSArray array] forKey:@"columnsOrder"]; [d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"]; @@ -35,6 +36,15 @@ http://www.hardcoded.net/licenses/bsd_license [ud registerDefaults:d]; } +- (id)init +{ + self = [super init]; + NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:0]; + VTIsIntIn *vtScanTypeIsFuzzy = [[[VTIsIntIn alloc] initWithValues:i reverse:NO] autorelease]; + [NSValueTransformer setValueTransformer:vtScanTypeIsFuzzy forName:@"vtScanTypeIsFuzzy"]; + return self; +} + - (NSString *)homepageURL { return @"http://www.hardcoded.net/dupeguru_pe/"; diff --git a/cocoa/pe/ResultWindow.m b/cocoa/pe/ResultWindow.m index 7bd864cd..7ff41d86 100644 --- a/cocoa/pe/ResultWindow.m +++ b/cocoa/pe/ResultWindow.m @@ -34,6 +34,7 @@ http://www.hardcoded.net/licenses/bsd_license { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; PyDupeGuru *_py = (PyDupeGuru *)py; + [_py setScanType:[ud objectForKey:@"scanType"]]; [_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]]; [_py setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])]; [_py setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])]; diff --git a/cocoa/pe/dg_cocoa.py b/cocoa/pe/dg_cocoa.py index 0c224f44..94411bc6 100644 --- a/cocoa/pe/dg_cocoa.py +++ b/cocoa/pe/dg_cocoa.py @@ -9,6 +9,7 @@ install_cocoa_trans() from core.app_cocoa_inter import PyDupeGuruBase, PyDetailsPanel from core_pe import app_cocoa as app_pe_cocoa, __appname__ +from core.scanner import ScanType class PyDupeGuru(PyDupeGuruBase): def init(self): @@ -27,6 +28,15 @@ class PyDupeGuru(PyDupeGuruBase): return str(self.py.selected_dupe_ref_path()) #---Properties + def setScanType_(self, scan_type): + try: + self.py.scanner.scan_type = [ + ScanType.FuzzyBlock, + ScanType.ExifTimestamp, + ][scan_type] + except IndexError: + pass + def setMatchScaled_(self,match_scaled): self.py.scanner.match_scaled = match_scaled diff --git a/cocoa/pe/en.lproj/Preferences.xib b/cocoa/pe/en.lproj/Preferences.xib index 6f98a515..607c6bfb 100644 --- a/cocoa/pe/en.lproj/Preferences.xib +++ b/cocoa/pe/en.lproj/Preferences.xib @@ -2,17 +2,17 @@ 1050 - 10J567 - 823 + 10J869 + 851 1038.35 - 462.00 + 461.00 com.apple.InterfaceBuilder.CocoaPlugin - 823 + 851 YES - + YES @@ -36,7 +36,7 @@ YES - DebugMode + scanType YES @@ -103,7 +103,7 @@ 292 - {{117, 140}, {181, 21}} + {{117, 107}, {181, 21}} YES @@ -131,7 +131,7 @@ 292 - {{119, 123}, {80, 13}} + {{119, 90}, {80, 13}} YES @@ -167,7 +167,7 @@ 289 - {{216, 123}, {80, 13}} + {{216, 90}, {80, 13}} YES @@ -183,7 +183,7 @@ 292 - {{14, 145}, {100, 14}} + {{14, 112}, {100, 14}} YES @@ -203,7 +203,7 @@ 256 - {{15, 79}, {316, 18}} + {{15, 46}, {316, 18}} YES @@ -226,7 +226,7 @@ 256 - {{15, 99}, {316, 18}} + {{15, 66}, {316, 18}} YES @@ -247,7 +247,7 @@ 256 - {{15, 39}, {316, 18}} + {{15, 6}, {316, 18}} YES @@ -268,7 +268,7 @@ 256 - {{15, 59}, {316, 18}} + {{15, 26}, {316, 18}} YES @@ -367,6 +367,87 @@ + + + 292 + {{14, 145}, {85, 13}} + + YES + + 67239424 + 272629760 + Scan type: + + + + + + + + + 292 + {{113, 135}, {216, 26}} + + YES + + -2076049856 + 2048 + + + 109199615 + 1 + + + + + + 400 + 75 + + + Contents + + 1048576 + 2147483647 + 1 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + _popUpItemAction: + + + YES + + + OtherViews + + + YES + + + + EXIF Timestamp + + 1048576 + 2147483647 + + + _popUpItemAction: + + + + + 3 + YES + YES + 1 + + {{10, 33}, {346, 162}} @@ -504,14 +585,8 @@ 1048576 2147483647 1 - - NSImage - NSMenuCheckmark - - - NSImage - NSMenuMixedState - + + _popUpItemAction: @@ -529,8 +604,8 @@ 1048576 2147483647 - - + + _popUpItemAction: @@ -540,8 +615,8 @@ 1048576 2147483647 - - + + _popUpItemAction: @@ -877,6 +952,62 @@ 78 + + + selectedIndex: values.scanType + + + + + + selectedIndex: values.scanType + selectedIndex + values.scanType + 2 + + + 96 + + + + enabled: values.scanType + + + + + + enabled: values.scanType + enabled + values.scanType + + NSValueTransformerName + vtScanTypeIsFuzzy + + 2 + + + 98 + + + + enabled: values.scanType + + + + + + enabled: values.scanType + enabled + values.scanType + + NSValueTransformerName + vtScanTypeIsFuzzy + + 2 + + + 100 + @@ -993,15 +1124,17 @@ YES - - - - - - - + + + + + + + + + @@ -1268,6 +1401,58 @@ + + 87 + + + YES + + + + + + 88 + + + YES + + + + + + 89 + + + YES + + + + + + 90 + + + YES + + + + + + + 92 + + + + + 93 + + + + + 94 + + + @@ -1283,14 +1468,19 @@ 11.IBPluginDependency 11.ImportedFromIB2 12.IBPluginDependency + 12.IBViewBoundsToFrameTransform 12.ImportedFromIB2 13.IBPluginDependency + 13.IBViewBoundsToFrameTransform 13.ImportedFromIB2 14.IBPluginDependency + 14.IBViewBoundsToFrameTransform 14.ImportedFromIB2 15.IBPluginDependency + 15.IBViewBoundsToFrameTransform 15.ImportedFromIB2 16.IBPluginDependency + 16.IBViewBoundsToFrameTransform 16.ImportedFromIB2 17.IBPluginDependency 18.IBPluginDependency @@ -1326,6 +1516,7 @@ 4.IBPluginDependency 4.ImportedFromIB2 5.IBPluginDependency + 5.IBViewBoundsToFrameTransform 5.ImportedFromIB2 59.IBPluginDependency 6.IBPluginDependency @@ -1346,6 +1537,7 @@ 69.IBViewBoundsToFrameTransform 69.ImportedFromIB2 7.IBPluginDependency + 7.IBViewBoundsToFrameTransform 7.ImportedFromIB2 70.IBPluginDependency 74.IBPluginDependency @@ -1355,9 +1547,24 @@ 8.IBPluginDependency 8.IBViewBoundsToFrameTransform 8.ImportedFromIB2 + 87.IBPluginDependency + 87.IBViewBoundsToFrameTransform + 87.ImportedFromIB2 + 88.IBPluginDependency + 88.IBViewBoundsToFrameTransform + 88.ImportedFromIB2 + 89.IBPluginDependency 9.IBPluginDependency 9.IBViewBoundsToFrameTransform 9.ImportedFromIB2 + 90.IBEditorWindowLastContentRect + 90.IBPluginDependency + 90.ImportedFromIB2 + 92.IBPluginDependency + 92.ImportedFromIB2 + 93.IBPluginDependency + 93.ImportedFromIB2 + 94.IBPluginDependency YES @@ -1372,14 +1579,29 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBcAAAwr4AAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBYAAAwx0AAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABDWAAAwwYAAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABC7gAAwwYAAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABC6gAAwx8AAA + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -1415,11 +1637,14 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBcAAAwpYAAA + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - P4AAAL+AAABBcAAAwfAAAA + P4AAAL+AAABBcAAAwlwAAA com.apple.InterfaceBuilder.CocoaPlugin @@ -1443,6 +1668,9 @@ com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBcAAAwuYAAA + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -1457,10 +1685,29 @@ com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBYAAAwx0AAA + + + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABCbAAAwx8AAA + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABBYAAAwjwAAA + {{213, 762}, {216, 43}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin @@ -1479,7 +1726,7 @@ - 78 + 100 diff --git a/cocoa/pe/fr.lproj/Preferences.xib b/cocoa/pe/fr.lproj/Preferences.xib index 9d08bf8b..ca3cb5c7 100644 --- a/cocoa/pe/fr.lproj/Preferences.xib +++ b/cocoa/pe/fr.lproj/Preferences.xib @@ -2,13 +2,13 @@ 1050 - 10J567 - 823 + 10J869 + 851 1038.35 - 462.00 + 461.00 com.apple.InterfaceBuilder.CocoaPlugin - 823 + 851 YES @@ -98,7 +98,7 @@ 292 - {{117, 140}, {181, 21}} + {{117, 107}, {181, 21}} YES @@ -126,7 +126,7 @@ 292 - {{119, 123}, {80, 13}} + {{119, 90}, {80, 13}} YES @@ -162,7 +162,7 @@ 289 - {{216, 123}, {80, 13}} + {{216, 90}, {80, 13}} YES @@ -178,7 +178,7 @@ 292 - {{14, 145}, {100, 14}} + {{14, 112}, {100, 14}} YES @@ -198,7 +198,7 @@ 256 - {{15, 79}, {316, 18}} + {{15, 46}, {316, 18}} YES @@ -209,7 +209,7 @@ 1211912703 2 - + NSImage NSSwitch @@ -225,7 +225,7 @@ 256 - {{15, 99}, {316, 18}} + {{15, 66}, {316, 18}} YES @@ -236,7 +236,7 @@ 1211912703 2 - + @@ -247,7 +247,7 @@ 256 - {{15, 39}, {316, 18}} + {{15, 6}, {316, 18}} YES @@ -258,7 +258,7 @@ 1211912703 2 - + @@ -269,7 +269,7 @@ 256 - {{15, 59}, {316, 18}} + {{15, 26}, {316, 18}} YES @@ -280,7 +280,7 @@ 1211912703 2 - + @@ -369,6 +369,87 @@ + + + 292 + {{14, 145}, {85, 13}} + + YES + + 67239424 + 272629760 + Scan type: + + + + + + + + + 292 + {{113, 135}, {216, 26}} + + YES + + -2076049856 + 2048 + + + 109199615 + 1 + + + + + + 400 + 75 + + + Contents + + 1048576 + 2147483647 + 1 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + _popUpItemAction: + + + YES + + + OtherViews + + + YES + + + + EXIF Timestamp + + 1048576 + 2147483647 + + + _popUpItemAction: + + + + + 3 + YES + YES + 1 + + {{10, 33}, {346, 162}} @@ -398,7 +479,7 @@ 1211912703 2 - + @@ -420,7 +501,7 @@ 1211912703 2 - + @@ -442,7 +523,7 @@ 1211912703 2 - + @@ -509,14 +590,8 @@ 1048576 2147483647 1 - - NSImage - NSMenuCheckmark - - - NSImage - NSMenuMixedState - + + _popUpItemAction: @@ -534,8 +609,8 @@ 1048576 2147483647 - - + + _popUpItemAction: @@ -545,8 +620,8 @@ 1048576 2147483647 - - + + _popUpItemAction: @@ -881,6 +956,62 @@ 78 + + + selectedIndex: values.scanType + + + + + + selectedIndex: values.scanType + selectedIndex + values.scanType + 2 + + + 96 + + + + enabled: values.scanType + + + + + + enabled: values.scanType + enabled + values.scanType + + NSValueTransformerName + vtScanTypeIsFuzzy + + 2 + + + 98 + + + + enabled: values.scanType + + + + + + enabled: values.scanType + enabled + values.scanType + + NSValueTransformerName + vtScanTypeIsFuzzy + + 2 + + + 100 + @@ -997,15 +1128,17 @@ YES - - - - - - - + + + + + + + + + @@ -1272,6 +1405,58 @@ + + 87 + + + YES + + + + + + 88 + + + YES + + + + + + 89 + + + YES + + + + + + 90 + + + YES + + + + + + + 92 + + + + + 93 + + + + + 94 + + + @@ -1287,14 +1472,19 @@ 11.IBPluginDependency 11.ImportedFromIB2 12.IBPluginDependency + 12.IBViewBoundsToFrameTransform 12.ImportedFromIB2 13.IBPluginDependency + 13.IBViewBoundsToFrameTransform 13.ImportedFromIB2 14.IBPluginDependency + 14.IBViewBoundsToFrameTransform 14.ImportedFromIB2 15.IBPluginDependency + 15.IBViewBoundsToFrameTransform 15.ImportedFromIB2 16.IBPluginDependency + 16.IBViewBoundsToFrameTransform 16.ImportedFromIB2 17.IBPluginDependency 18.IBPluginDependency @@ -1331,6 +1521,7 @@ 4.IBPluginDependency 4.ImportedFromIB2 5.IBPluginDependency + 5.IBViewBoundsToFrameTransform 5.ImportedFromIB2 59.IBPluginDependency 6.IBPluginDependency @@ -1351,6 +1542,7 @@ 69.IBViewBoundsToFrameTransform 69.ImportedFromIB2 7.IBPluginDependency + 7.IBViewBoundsToFrameTransform 7.ImportedFromIB2 70.IBPluginDependency 74.IBPluginDependency @@ -1360,9 +1552,24 @@ 8.IBPluginDependency 8.IBViewBoundsToFrameTransform 8.ImportedFromIB2 + 87.IBPluginDependency + 87.IBViewBoundsToFrameTransform + 87.ImportedFromIB2 + 88.IBPluginDependency + 88.IBViewBoundsToFrameTransform + 88.ImportedFromIB2 + 89.IBPluginDependency 9.IBPluginDependency 9.IBViewBoundsToFrameTransform 9.ImportedFromIB2 + 90.IBEditorWindowLastContentRect + 90.IBPluginDependency + 90.ImportedFromIB2 + 92.IBPluginDependency + 92.ImportedFromIB2 + 93.IBPluginDependency + 93.ImportedFromIB2 + 94.IBPluginDependency YES @@ -1377,14 +1584,29 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBcAAAwr4AAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBYAAAwx0AAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABDWAAAwwYAAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABC7gAAwwYAAA + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABC6gAAwx8AAA + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -1421,11 +1643,14 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBcAAAwpYAAA + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - P4AAAL+AAABBcAAAwfAAAA + P4AAAL+AAABBcAAAwlwAAA com.apple.InterfaceBuilder.CocoaPlugin @@ -1449,6 +1674,9 @@ com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBcAAAwuYAAA + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -1463,10 +1691,29 @@ com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABBYAAAwx0AAA + + + com.apple.InterfaceBuilder.CocoaPlugin + + P4AAAL+AAABCbAAAwx8AAA + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABBYAAAwjwAAA + {{213, 762}, {216, 43}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin @@ -1485,7 +1732,7 @@ - 78 + 100 diff --git a/cocoa/se/PyDupeGuru.h b/cocoa/se/PyDupeGuru.h index d546e046..17e8a826 100644 --- a/cocoa/se/PyDupeGuru.h +++ b/cocoa/se/PyDupeGuru.h @@ -11,7 +11,6 @@ http://www.hardcoded.net/licenses/bsd_license @interface PyDupeGuru : PyDupeGuruBase //Scanning options -- (void)setScanType:(NSNumber *)scan_type; - (void)setWordWeighting:(NSNumber *)words_are_weighted; - (void)setMatchSimilarWords:(NSNumber *)match_similar_words; @end diff --git a/core/scanner.py b/core/scanner.py index d39652ad..2beadb17 100644 --- a/core/scanner.py +++ b/core/scanner.py @@ -17,6 +17,10 @@ from hscommon.trans import tr from . import engine from .ignore import IgnoreList +# It's quite ugly to have scan types from all editions all put in the same class, but because there's +# there will be some nasty bugs popping up (ScanType is used in core when in should exclusively be +# used in core_*). One day I'll clean this up. + class ScanType: Filename = 0 Fields = 1 @@ -25,6 +29,10 @@ class ScanType: Folders = 4 Contents = 5 ContentsAudio = 6 + + #PE + FuzzyBlock = 10 + ExifTimestamp = 11 SCANNABLE_TAGS = ['track', 'artist', 'album', 'title', 'genre', 'year'] diff --git a/core_pe/exif.py b/core_pe/exif.py index 16f2480b..c91fbfb6 100644 --- a/core_pe/exif.py +++ b/core_pe/exif.py @@ -8,8 +8,6 @@ # Heavily based on http://topo.math.u-psud.fr/~bousch/exifdump.py by Thierry Bousch (Public Domain) -import os -import sys import logging EXIF_TAGS = { @@ -260,7 +258,6 @@ def read_exif_header(fp): try: index = large_data.index(b'Exif') data = large_data[index-6:index+6] - print('hello!', data) # large_data omits the first 12 bytes, and the index is at the middle of the header, so we # must seek index + 18 fp.seek(index+18) @@ -324,25 +321,3 @@ def get_fields(fp): for tag, type, values in IFD: add_tag_to_result(tag, values) return result - -def main(): - # logging.getLogger().setLevel(logging.DEBUG) - if len(sys.argv) < 2: - filenames = os.listdir('.') - else: - filenames = sys.argv[1:] - for filename in filenames: - print(filename+':') - try: - file = open(filename, 'rb') - fields = get_fields(file) - if 'DateTime' in fields: - print(fields['DateTime']) - else: - print(repr(fields)) - except (IOError, ValueError): - print(' Cannot open file') - sys.exit(0) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/core_pe/matchbase.py b/core_pe/matchblock.py similarity index 100% rename from core_pe/matchbase.py rename to core_pe/matchblock.py diff --git a/core_pe/matchexif.py b/core_pe/matchexif.py new file mode 100644 index 00000000..399871f9 --- /dev/null +++ b/core_pe/matchexif.py @@ -0,0 +1,34 @@ +# Created By: Virgil Dupras +# Created On: 2011-04-20 +# Copyright 2011 Hardcoded Software (http://www.hardcoded.net) +# +# This software is licensed under the "BSD" License as described in the "LICENSE" file, +# which should be included with this package. The terms are also available at +# http://www.hardcoded.net/licenses/bsd_license + +import logging +from collections import defaultdict +from itertools import combinations + +from hscommon import io +from hscommon.trans import tr + +from core.engine import Match +from . import exif + +def getmatches(files, j): + timestamp2pic = defaultdict(set) + for picture in j.iter_with_progress(files, tr("Read EXIF of %d/%d pictures")): + try: + with io.open(picture.path, 'rb') as fp: + exifdata = exif.get_fields(fp) + timestamp = exifdata['DateTimeOriginal'] + timestamp2pic[timestamp].add(picture) + except Exception: + logging.warning("Couldn't read EXIF of picture: %s", picture.path) + if '0000:00:00 00:00:00' in timestamp2pic: # very likely false matches + del timestamp2pic['0000:00:00 00:00:00'] + matches = [] + for pictures in timestamp2pic.values(): + matches += [Match(p1, p2, 100) for p1, p2 in combinations(pictures, 2)] + return matches \ No newline at end of file diff --git a/core_pe/scanner.py b/core_pe/scanner.py index 73829a99..0ecd11cf 100644 --- a/core_pe/scanner.py +++ b/core_pe/scanner.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Created By: Virgil Dupras # Created On: 2009-10-18 # Copyright 2011 Hardcoded Software (http://www.hardcoded.net) @@ -7,9 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license -from core.scanner import Scanner +from core.scanner import Scanner, ScanType -from . import matchbase +from . import matchblock, matchexif from .cache import Cache class ScannerPE(Scanner): @@ -18,7 +17,12 @@ class ScannerPE(Scanner): threshold = 75 def _getmatches(self, files, j): - return matchbase.getmatches(files, self.cache_path, self.threshold, self.match_scaled, j) + if self.scan_type == ScanType.FuzzyBlock: + return matchblock.getmatches(files, self.cache_path, self.threshold, self.match_scaled, j) + elif self.scan_type == ScanType.ExifTimestamp: + return matchexif.getmatches(files, j) + else: + raise Exception("Invalid scan type") def clear_picture_cache(self): cache = Cache(self.cache_path) diff --git a/help/en/preferences.rst b/help/en/preferences.rst index 13ea8c9b..70846f43 100644 --- a/help/en/preferences.rst +++ b/help/en/preferences.rst @@ -32,9 +32,11 @@ Preferences .. only:: edition_pe - **Filter Hardness:** The higher is this setting, the "harder" is the filter (In other words, the less results you get). Most pictures of the same quality match at 100% even if the format is different (PNG and JPG for example.). However, if you want to make a PNG match with a lower quality JPG, you will have to set the filer hardness to lower than 100. The default, 95, is a sweet spot. + **Scan Type:** This option determines the type of scan that will be made on your pictures. The **Contents** scan type compares the actual contents of the pictures in a fuzzy way (making it possible to find not only exact duplicates, but also similar ones). The **EXIF Timestamp** scan type looks at the EXIF metadata of the picture (if it exists) and matches pictures that have the same one. It's much faster than the Contents scan. + + **Filter Hardness:** *Contents scan type only.* The higher is this setting, the "harder" is the filter (In other words, the less results you get). Most pictures of the same quality match at 100% even if the format is different (PNG and JPG for example.). However, if you want to make a PNG match with a lower quality JPG, you will have to set the filer hardness to lower than 100. The default, 95, is a sweet spot. - **Match scaled pictures together:** If you check this box, pictures of different dimensions will be allowed in the same duplicate group. + **Match scaled pictures together:** *Contents scan type only.* If you check this box, pictures of different dimensions will be allowed in the same duplicate group. **Can mix file kind:** If you check this box, duplicate groups are allowed to have files with different extensions. If you don't check it, well, they aren't! diff --git a/help/fr/preferences.rst b/help/fr/preferences.rst index 8355fcd3..75b3604f 100644 --- a/help/fr/preferences.rst +++ b/help/fr/preferences.rst @@ -32,9 +32,11 @@ Préférences .. only:: edition_pe - **Seuil du filtre:** Plus il est élevé, plus les images doivent être similaires pour être considérées comme des doublons. Le défaut de 95% permet quelques petites différence, comme par exemple une différence de qualité ou bien une légère modification des couleurs. + **Type de scan:** Détermine le type de scan qui sera fait sur vos images. Le type **Contenu** compare le contenu des images de façon "fuzzy", rendant possible de trouver non seulement les doublons exactes, mais aussi les similaires. Le type **EXIF Timestamp** compare les métadonnées EXIF des images (si existantes) et détermine si le "timestamp" (moment de prise de la photo) est pareille. C'est beaucoup plus rapide que le scan par Contenu. + + **Seuil du filtre:** *Scan par Contenu seulement.* Plus il est élevé, plus les images doivent être similaires pour être considérées comme des doublons. Le défaut de 95% permet quelques petites différence, comme par exemple une différence de qualité ou bien une légère modification des couleurs. - **Comparer les images de tailles différentes:** Le nom dit tout. Sans cette option, les images de tailles différentes ne sont pas comparées. + **Comparer les images de tailles différentes:** *Scan par Contenu seulement.* Le nom dit tout. Sans cette option, les images de tailles différentes ne sont pas comparées. **Comparer les fichiers de différents types:** Sans cette option, seulement les fichiers du même type seront comparés. diff --git a/qt/pe/app.py b/qt/pe/app.py index 46507fa0..699c9382 100644 --- a/qt/pe/app.py +++ b/qt/pe/app.py @@ -71,6 +71,7 @@ class DupeGuru(DupeGuruBase): def _update_options(self): DupeGuruBase._update_options(self) + self.scanner.scan_type = self.prefs.scan_type self.scanner.match_scaled = self.prefs.match_scaled self.scanner.threshold = self.prefs.filter_hardness diff --git a/qt/pe/preferences.py b/qt/pe/preferences.py index 8ae318fb..effa1126 100644 --- a/qt/pe/preferences.py +++ b/qt/pe/preferences.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license -from PyQt4.QtCore import QSettings, QVariant +from core.scanner import ScanType from ..base.preferences import Preferences as PreferencesBase @@ -24,12 +24,17 @@ class Preferences(PreferencesBase): ] def _load_specific(self, settings): - self.match_scaled = self.get_value('MatchScaled', self.match_scaled) + get = self.get_value + self.scan_type = get('ScanType', self.scan_type) + self.match_scaled = get('MatchScaled', self.match_scaled) def _reset_specific(self): + self.scan_type = ScanType.FuzzyBlock self.filter_hardness = 95 self.match_scaled = False def _save_specific(self, settings): - self.set_value('MatchScaled', self.match_scaled) + set_ = self.set_value + set_('ScanType', self.scan_type) + set_('MatchScaled', self.match_scaled) diff --git a/qt/pe/preferences_dialog.py b/qt/pe/preferences_dialog.py index 4114b4af..9a1e56e4 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -10,12 +10,26 @@ import sys from PyQt4.QtGui import QLabel, QApplication from hscommon.trans import tr +from core.scanner import ScanType from ..base.preferences_dialog import PreferencesDialogBase from . import preferences + +SCAN_TYPE_ORDER = [ + ScanType.FuzzyBlock, + ScanType.ExifTimestamp, +] + class PreferencesDialog(PreferencesDialogBase): + def __init__(self, parent, app): + PreferencesDialogBase.__init__(self, parent, app) + + self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged) + def _setupPreferenceWidgets(self): + scanTypeLabels = [tr(s) for s in ["Contents", "EXIF Timestamp"]] + self._setupScanTypeBox(scanTypeLabels) self._setupFilterHardnessBox() self.widgetsVLayout.addLayout(self.filterHardnessHLayout) self._setupAddCheckbox('matchScaledBox', tr("Match scaled pictures together")) @@ -33,14 +47,24 @@ class PreferencesDialog(PreferencesDialogBase): self._setupBottomPart() def _load(self, prefs, setchecked): + scan_type_index = SCAN_TYPE_ORDER.index(prefs.scan_type) + self.scanTypeComboBox.setCurrentIndex(scan_type_index) setchecked(self.matchScaledBox, prefs.match_scaled) def _save(self, prefs, ischecked): + prefs.scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()] prefs.match_scaled = ischecked(self.matchScaledBox) def resetToDefaults(self): self.load(preferences.Preferences()) + #--- Events + def scanTypeChanged(self, index): + scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()] + fuzzy_scan = scan_type == ScanType.FuzzyBlock + self.filterHardnessSlider.setEnabled(fuzzy_scan) + self.matchScaledBox.setEnabled(fuzzy_scan) + if __name__ == '__main__': from ..testapp import TestApp