1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-25 08:01:39 +00:00

Compare commits

..

22 Commits

Author SHA1 Message Date
Virgil Dupras
526bcf2566 pe v2.2.1 2011-06-15 14:20:34 -04:00
Virgil Dupras
56207f4dbb [#161 state:fixed] Fixed folder sorting. 2011-06-15 11:58:33 -04:00
Virgil Dupras
cd9fd3a10b [#162 state:fixed] Apply the 'Match scaled pictures' option to exif timestamp scan type. 2011-06-15 10:13:03 -04:00
Virgil Dupras
4399fe9d17 Fixed a bug where corrupt exif tags would make the analysis process stall. 2011-06-14 15:05:33 -04:00
Virgil Dupras
2a6f524a5b [#159 state:fixed] Added 'orf' to the list of supported extensions under cocoa (they work fine, I tried em). 2011-06-14 13:54:50 -04:00
Virgil Dupras
caee5e37f0 [#164 state:fixed] When I added the scan type combobox in pe's pref panel under cocoa, I forgot the little label next to the hardness slider, so it ended up under the newly added combobox. fixed it. 2011-06-14 13:44:59 -04:00
Virgil Dupras
bbd9d68dfd Added a --loc option to the build script for times when you only want to refresh localizations. 2011-06-14 13:43:29 -04:00
Virgil Dupras
16b1b00906 Added tag pe2.2.0 for changeset e44d5127ed60 2011-06-02 14:21:49 -04:00
Virgil Dupras
7183408535 Oops, fixed release date in changelog. 2011-06-02 10:53:02 -04:00
Virgil Dupras
591b4c7c6a pe v2.2.0 2011-06-02 10:32:48 -04:00
Virgil Dupras
8b1170a82b When an exif tag can't be read, log the event as 'info' rather than 'warning'. We don't want to fill the user's console with these messages, which will be very common. 2011-06-02 10:14:08 -04:00
Virgil Dupras
1f26fbeacc [#154 state:fixed] Added exif orientation support. 2011-05-31 10:05:12 -04:00
Virgil Dupras
cc7ccff48e [#154] Created the cross-platform unit core_pe.photo in prep for rotation support. 2011-05-29 10:18:03 -04:00
Virgil Dupras
a0809333c1 [#157 state:fixed] Straightened up extension glitches during result load/save under Qt 2011-05-29 09:12:24 -04:00
Virgil Dupras
8975f78a5f updated jobprogress version in README 2011-05-29 08:51:19 -04:00
Virgil Dupras
56bc1c1373 Added gotchas section to README 2011-05-28 16:44:23 -04:00
Virgil Dupras
417233a47f [#155 state:fixed] Added dg edition name in results window. 2011-04-22 11:37:53 +02:00
Virgil Dupras
59eaf5305a [#156 state:fixed] Fixed a visual glitch in Cocoa's result window colors when a row is selected. 2011-04-22 11:07:54 +02:00
Virgil Dupras
275c6be108 Added the EXIF Timestamp scan type in dgpe.
--HG--
rename : core_pe/matchbase.py => core_pe/matchblock.py
2011-04-21 17:17:19 +02:00
Virgil Dupras
a0e2b11663 Added core_pe.exif to read exif data from pictures 2011-04-20 15:19:12 +02:00
Virgil Dupras
de23ce90d8 Deduplicated scan type combobox creation code between SE and ME (soon to be shared by PE) (Qt). 2011-04-20 15:18:21 +02:00
Virgil Dupras
9d5f3029d0 Added tag se3.1.0 for changeset 97893f37d7d0 2011-04-16 15:48:50 +02:00
48 changed files with 1337 additions and 254 deletions

View File

@@ -47,3 +47,5 @@ f1d40b556c01f32c58f9ef9f9acac5b78e01ba7a pe2.0.0
ff43c6d9feb388f103b7857eaa6f7809185f78ec before-pluginbuilder
d274bcb98f2d02b86470a04cd62e718eff33b74f pe2.1.0
77e169f757195c11e9c1c5febeb2db8eb3589510 se3.0.2
97893f37d7d0767b5aedf1b4b40de57ee36d426b se3.1.0
e44d5127ed605daa7a17a01eee65d0a157de20c0 pe2.2.0

41
README
View File

@@ -29,7 +29,7 @@ General dependencies
- Python 3.1 (3.2 on Mac OS X) (http://www.python.org)
- Send2Trash3k (http://hg.hardcoded.net/send2trash)
- hsaudiotag3k 1.1.0 (for ME) (http://hg.hardcoded.net/hsaudiotag)
- jobprogress (http://hg.hardcoded.net/jobprogress)
- jobprogress 1.0.1 (http://hg.hardcoded.net/jobprogress)
- Sphinx 1.0.6 (http://sphinx.pocoo.org/)
- pytest 2.0.0, to run unit tests. (http://pytest.org/)
@@ -54,6 +54,45 @@ Windows prerequisites
- cx_Freeze, if you want to build a exe. You don't need it if you just want to run dupeGuru. (http://cx-freeze.sourceforge.net/)
- Advanced Installer, if you want to build the installer file. (http://www.advancedinstaller.com/)
Linux prerequisites
-------------------
- PyQt 4.7.5 (http://www.riverbankcomputing.co.uk/news)
Prerequisite gotchas
--------------------
Correctly installing the prerequisites is tricky. Make sure you have at least the version number
required for each prerequisite.
If you didn't use mercurial to download this source, you probably have an incomplete source folder!
External projects (hscommon, qtlib, cocoalib) need to be at the root of the dupeGuru project folder.
You'll have to download those separately. Or use mercurial, it's much easier.
As far as I can tell, you don't *have* to compile/install everything manually and you can normally
use `easy_install` to install python dependencies. However, be aware that compiling/installing
manually from the repositories of each project is what I personally do, so if you hit a snag
somewhere, you might want to try the manual way.
PyObjC's website is badly outdated. Also, as far as I can tell, the package installable with
`easy_install` has good chances of not working. Your best bet is to download the latest tagged
version from the repository and compile it from source.
Also, on OS X, don't try to use the built-in python 2.x to install Sphinx on (the only pre-requisite
that doesn't run on python 3 yet). There's some weird error popping up when dupeGuru tries to build
its help file. Install your own framework version of python 2.7, and then install Sphinx on that.
When Sphinx supports Python 3, things will be easier because you'll be able to install sphinx on the
same Python version you build dupeGuru with.
Another one on OS X: I wouldn't use macports/fink/whatever. Whenever I tried using those, I always
ended up with problems.
Also, I don't know yet if it's possible to compile dupeGuru with XCode 4, I haven't tried it yet.
It's safer to use XCode 3.x. However, I don't see why it wouldn't work, so it very well might work.
Whenever you have a problem, always double-check that you're running the correct python version.
You'll probably have to tweak your $PATH.
Building dupeGuru
=================

View File

@@ -24,17 +24,15 @@ def parse_args():
parser = OptionParser(usage=usage)
parser.add_option('--clean', action='store_true', dest='clean',
help="Clean build folder before building")
parser.add_option('--only-help', action='store_true', dest='only_help',
help="Build only help file")
parser.add_option('--doc', action='store_true', dest='doc',
help="Build only the help file")
parser.add_option('--loc', action='store_true', dest='loc',
help="Build only localization")
(options, args) = parser.parse_args()
return options
def build_cocoa(edition, dev):
from pluginbuilder import build_plugin
build_all_cocoa_locs('cocoalib')
build_all_cocoa_locs(op.join('cocoa', 'base'))
build_all_cocoa_locs(op.join('cocoa', edition))
print("Building dg_cocoa.plugin")
if not dev:
specific_packages = {
@@ -83,8 +81,6 @@ def build_cocoa(edition, dev):
open('run.py', 'wt').write(run_contents)
def build_qt(edition, dev):
print("Building .ts files")
build_all_qt_locs(op.join('qt', 'lang'), extradirs=[op.join('qtlib', 'lang')])
print("Building Qt stuff")
print_and_do("pyrcc4 -py3 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
print("Creating the run.py file")
@@ -104,6 +100,16 @@ def build_help(edition):
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage}
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl)
def build_localizations(ui, edition):
print("Building localizations")
if ui == 'cocoa':
build_all_cocoa_locs('cocoalib')
build_all_cocoa_locs(op.join('cocoa', 'base'))
build_all_cocoa_locs(op.join('cocoa', edition))
elif ui == 'qt':
print("Building .ts files")
build_all_qt_locs(op.join('qt', 'lang'), extradirs=[op.join('qtlib', 'lang')])
def build_pe_modules(ui):
def move(src, dst):
if not op.exists(src):
@@ -144,6 +150,7 @@ def build_normal(edition, ui, dev):
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
add_to_pythonpath('.')
build_help(edition)
build_localizations(ui, edition)
print("Building dupeGuru")
if edition == 'pe':
build_pe_modules(ui)
@@ -165,8 +172,10 @@ def main():
shutil.rmtree('build')
if not op.exists('build'):
os.mkdir('build')
if options.only_help:
if options.doc:
build_help(edition)
elif options.loc:
build_localizations(ui, edition)
else:
build_normal(edition, ui, dev)

View File

@@ -19,6 +19,7 @@ http://www.hardcoded.net/licenses/bsd_license
[self window];
_app = aParentApp;
_py = [_app py];
[[self window] setTitle:[_py appName]];
_alwaysShowPopUp = NO;
[self fillPopUpMenu];
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];

View File

@@ -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;

View File

@@ -118,6 +118,7 @@ http://www.hardcoded.net/licenses/bsd_license
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row
{
BOOL isSelected = [tableView isRowSelected:row];
BOOL isMarkable = n2b([[self py] valueForColumn:@"markable" row:row]);
if ([[column identifier] isEqual:@"marked"]) {
[cell setEnabled:isMarkable];
@@ -126,20 +127,22 @@ http://www.hardcoded.net/licenses/bsd_license
[cell setImagePosition:pos];
}
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
// Determine if the text color will be blue due to directory being reference.
NSTextFieldCell *textCell = cell;
if (isMarkable) {
[textCell setTextColor:[NSColor blackColor]];
NSColor *color = [NSColor textColor];
if (isSelected) {
color = [NSColor selectedTextColor];
}
else if (isMarkable) {
if ([self deltaValuesMode]) {
NSInteger i = [[column identifier] integerValue];
if ([_deltaColumns containsIndex:i]) {
[textCell setTextColor:[NSColor orangeColor]];
color = [NSColor orangeColor];
}
}
}
else {
[textCell setTextColor:[NSColor blueColor]];
color = [NSColor blueColor];
}
[(NSTextFieldCell *)cell setTextColor:color];
}
}

View File

@@ -19,6 +19,7 @@ http://www.hardcoded.net/licenses/bsd_license
self = [super initWithWindowNibName:@"ResultWindow"];
app = aApp;
py = [app py];
[[self window] setTitle:fmt(@"%@ Results", [py appName])];
columnsMenu = [app columnsMenu];
/* Put a cute iTunes-like bottom bar */
[[self window] setContentBorderThickness:28 forEdge:NSMinYEdge];

View File

@@ -13,7 +13,6 @@ http://www.hardcoded.net/licenses/bsd_license
- (id)initWithParentApp:(id)aParentApp
{
self = [super initWithParentApp:aParentApp];
[[self window] setTitle:@"dupeGuru Music Edition"];
_alwaysShowPopUp = YES;
return self;
}

View File

@@ -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;

View File

@@ -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/";

View File

@@ -13,7 +13,6 @@ http://www.hardcoded.net/licenses/bsd_license
- (id)initWithParentApp:(id)aParentApp
{
self = [super initWithParentApp:aParentApp];
[[self window] setTitle:@"dupeGuru Picture Edition"];
_alwaysShowPopUp = YES;
return self;
}

View File

@@ -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"])];

View File

@@ -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

View File

@@ -2,17 +2,17 @@
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1050</int>
<string key="IBDocument.SystemVersion">10J567</string>
<string key="IBDocument.InterfaceBuilderVersion">823</string>
<string key="IBDocument.SystemVersion">10J4138</string>
<string key="IBDocument.InterfaceBuilderVersion">851</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">462.00</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">823</string>
<string key="NS.object.0">851</string>
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="3"/>
<integer value="63"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -34,10 +34,6 @@
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSUserDefaultsController" id="455472712">
<object class="NSMutableArray" key="NSDeclaredKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>DebugMode</string>
</object>
<bool key="NSSharedInstance">YES</bool>
</object>
<object class="NSWindowTemplate" id="809668081">
@@ -103,7 +99,7 @@
<object class="NSSlider" id="266372855">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{117, 140}, {181, 21}}</string>
<string key="NSFrame">{{117, 107}, {181, 21}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSliderCell" key="NSCell" id="453640282">
@@ -131,7 +127,7 @@
<object class="NSTextField" id="869007847">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{119, 123}, {80, 13}}</string>
<string key="NSFrame">{{119, 90}, {80, 13}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="106025161">
@@ -167,7 +163,7 @@
<object class="NSTextField" id="171701149">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">289</int>
<string key="NSFrame">{{216, 123}, {80, 13}}</string>
<string key="NSFrame">{{216, 90}, {80, 13}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="397705219">
@@ -183,7 +179,7 @@
<object class="NSTextField" id="638371207">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{14, 145}, {100, 14}}</string>
<string key="NSFrame">{{14, 112}, {100, 14}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="812365472">
@@ -203,7 +199,7 @@
<object class="NSButton" id="488256664">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 79}, {316, 18}}</string>
<string key="NSFrame">{{15, 46}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="401283671">
@@ -226,13 +222,13 @@
<object class="NSButton" id="722670516">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 99}, {316, 18}}</string>
<string key="NSFrame">{{15, 66}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="911281323">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">0</int>
<string key="NSContents">Match scaled pictures together</string>
<string key="NSContents">Match pictures of different dimensions</string>
<reference key="NSSupport" ref="26"/>
<reference key="NSControlView" ref="722670516"/>
<int key="NSButtonFlags">1211912703</int>
@@ -247,7 +243,7 @@
<object class="NSButton" id="472028782">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 39}, {316, 18}}</string>
<string key="NSFrame">{{15, 6}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="2297113">
@@ -268,7 +264,7 @@
<object class="NSButton" id="279087998">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 59}, {316, 18}}</string>
<string key="NSFrame">{{15, 26}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="287383961">
@@ -289,7 +285,7 @@
<object class="NSTextField" id="403531548">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{301, 145}, {31, 14}}</string>
<string key="NSFrame">{{301, 112}, {31, 14}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="983190380">
@@ -367,6 +363,87 @@
<reference key="NSTextColor" ref="538152464"/>
</object>
</object>
<object class="NSTextField" id="536472926">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{14, 145}, {85, 13}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="359086043">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">272629760</int>
<string key="NSContents">Scan type:</string>
<reference key="NSSupport" ref="649492068"/>
<reference key="NSControlView" ref="536472926"/>
<reference key="NSBackgroundColor" ref="71910056"/>
<reference key="NSTextColor" ref="538152464"/>
</object>
</object>
<object class="NSPopUpButton" id="337614813">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{113, 135}, {219, 26}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSPopUpButtonCell" key="NSCell" id="697629846">
<int key="NSCellFlags">-2076049856</int>
<int key="NSCellFlags2">2048</int>
<reference key="NSSupport" ref="882799568"/>
<reference key="NSControlView" ref="337614813"/>
<int key="NSButtonFlags">109199615</int>
<int key="NSButtonFlags2">1</int>
<reference key="NSAlternateImage" ref="882799568"/>
<string key="NSAlternateContents"/>
<object class="NSMutableString" key="NSKeyEquivalent">
<characters key="NS.bytes"/>
</object>
<int key="NSPeriodicDelay">400</int>
<int key="NSPeriodicInterval">75</int>
<object class="NSMenuItem" key="NSMenuItem" id="1038855957">
<reference key="NSMenu" ref="958971008"/>
<string key="NSTitle">Contents</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<int key="NSState">1</int>
<object class="NSCustomResource" key="NSOnImage" id="875822430">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuCheckmark</string>
</object>
<object class="NSCustomResource" key="NSMixedImage" id="731403416">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuMixedState</string>
</object>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="697629846"/>
</object>
<bool key="NSMenuItemRespectAlignment">YES</bool>
<object class="NSMenu" key="NSMenu" id="958971008">
<object class="NSMutableString" key="NSTitle">
<characters key="NS.bytes">OtherViews</characters>
</object>
<object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="1038855957"/>
<object class="NSMenuItem" id="820923003">
<reference key="NSMenu" ref="958971008"/>
<string key="NSTitle">EXIF Timestamp</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="697629846"/>
</object>
</object>
</object>
<int key="NSPreferredEdge">3</int>
<bool key="NSUsesItemFromMenu">YES</bool>
<bool key="NSAltersState">YES</bool>
<int key="NSArrowPosition">1</int>
</object>
</object>
</object>
<string key="NSFrame">{{10, 33}, {346, 162}}</string>
<reference key="NSSuperview" ref="211771207"/>
@@ -504,14 +581,8 @@
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<int key="NSState">1</int>
<object class="NSCustomResource" key="NSOnImage" id="867788054">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuCheckmark</string>
</object>
<object class="NSCustomResource" key="NSMixedImage" id="554538570">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuMixedState</string>
</object>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="601288025"/>
</object>
@@ -529,8 +600,8 @@
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="867788054"/>
<reference key="NSMixedImage" ref="554538570"/>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="601288025"/>
</object>
@@ -540,8 +611,8 @@
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="867788054"/>
<reference key="NSMixedImage" ref="554538570"/>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="601288025"/>
</object>
@@ -877,6 +948,42 @@
</object>
<int key="connectionID">78</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">selectedIndex: values.scanType</string>
<reference key="source" ref="337614813"/>
<reference key="destination" ref="455472712"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="337614813"/>
<reference key="NSDestination" ref="455472712"/>
<string key="NSLabel">selectedIndex: values.scanType</string>
<string key="NSBinding">selectedIndex</string>
<string key="NSKeyPath">values.scanType</string>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">96</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">enabled: values.scanType</string>
<reference key="source" ref="266372855"/>
<reference key="destination" ref="455472712"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="266372855"/>
<reference key="NSDestination" ref="455472712"/>
<string key="NSLabel">enabled: values.scanType</string>
<string key="NSBinding">enabled</string>
<string key="NSKeyPath">values.scanType</string>
<object class="NSDictionary" key="NSOptions">
<string key="NS.key.0">NSValueTransformerName</string>
<string key="NS.object.0">vtScanTypeIsFuzzy</string>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">98</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
@@ -993,15 +1100,17 @@
<reference key="object" ref="1073354031"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="722670516"/>
<reference ref="488256664"/>
<reference ref="638371207"/>
<reference ref="171701149"/>
<reference ref="869007847"/>
<reference ref="266372855"/>
<reference ref="869007847"/>
<reference ref="171701149"/>
<reference ref="638371207"/>
<reference ref="488256664"/>
<reference ref="722670516"/>
<reference ref="279087998"/>
<reference ref="403531548"/>
<reference ref="472028782"/>
<reference ref="337614813"/>
<reference ref="536472926"/>
<reference ref="403531548"/>
</object>
<reference key="parent" ref="700068878"/>
</object>
@@ -1268,6 +1377,58 @@
<reference key="object" ref="100803310"/>
<reference key="parent" ref="606836304"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">87</int>
<reference key="object" ref="536472926"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="359086043"/>
</object>
<reference key="parent" ref="1073354031"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">88</int>
<reference key="object" ref="337614813"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="697629846"/>
</object>
<reference key="parent" ref="1073354031"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">89</int>
<reference key="object" ref="697629846"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="958971008"/>
</object>
<reference key="parent" ref="337614813"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">90</int>
<reference key="object" ref="958971008"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="820923003"/>
<reference ref="1038855957"/>
</object>
<reference key="parent" ref="697629846"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">92</int>
<reference key="object" ref="820923003"/>
<reference key="parent" ref="958971008"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">93</int>
<reference key="object" ref="1038855957"/>
<reference key="parent" ref="958971008"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">94</int>
<reference key="object" ref="359086043"/>
<reference key="parent" ref="536472926"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
@@ -1281,16 +1442,22 @@
<string>10.IBViewBoundsToFrameTransform</string>
<string>10.ImportedFromIB2</string>
<string>11.IBPluginDependency</string>
<string>11.IBViewBoundsToFrameTransform</string>
<string>11.ImportedFromIB2</string>
<string>12.IBPluginDependency</string>
<string>12.IBViewBoundsToFrameTransform</string>
<string>12.ImportedFromIB2</string>
<string>13.IBPluginDependency</string>
<string>13.IBViewBoundsToFrameTransform</string>
<string>13.ImportedFromIB2</string>
<string>14.IBPluginDependency</string>
<string>14.IBViewBoundsToFrameTransform</string>
<string>14.ImportedFromIB2</string>
<string>15.IBPluginDependency</string>
<string>15.IBViewBoundsToFrameTransform</string>
<string>15.ImportedFromIB2</string>
<string>16.IBPluginDependency</string>
<string>16.IBViewBoundsToFrameTransform</string>
<string>16.ImportedFromIB2</string>
<string>17.IBPluginDependency</string>
<string>18.IBPluginDependency</string>
@@ -1326,6 +1493,7 @@
<string>4.IBPluginDependency</string>
<string>4.ImportedFromIB2</string>
<string>5.IBPluginDependency</string>
<string>5.IBViewBoundsToFrameTransform</string>
<string>5.ImportedFromIB2</string>
<string>59.IBPluginDependency</string>
<string>6.IBPluginDependency</string>
@@ -1346,6 +1514,7 @@
<string>69.IBViewBoundsToFrameTransform</string>
<string>69.ImportedFromIB2</string>
<string>7.IBPluginDependency</string>
<string>7.IBViewBoundsToFrameTransform</string>
<string>7.ImportedFromIB2</string>
<string>70.IBPluginDependency</string>
<string>74.IBPluginDependency</string>
@@ -1355,9 +1524,24 @@
<string>8.IBPluginDependency</string>
<string>8.IBViewBoundsToFrameTransform</string>
<string>8.ImportedFromIB2</string>
<string>87.IBPluginDependency</string>
<string>87.IBViewBoundsToFrameTransform</string>
<string>87.ImportedFromIB2</string>
<string>88.IBPluginDependency</string>
<string>88.IBViewBoundsToFrameTransform</string>
<string>88.ImportedFromIB2</string>
<string>89.IBPluginDependency</string>
<string>9.IBPluginDependency</string>
<string>9.IBViewBoundsToFrameTransform</string>
<string>9.ImportedFromIB2</string>
<string>90.IBEditorWindowLastContentRect</string>
<string>90.IBPluginDependency</string>
<string>90.ImportedFromIB2</string>
<string>92.IBPluginDependency</string>
<string>92.ImportedFromIB2</string>
<string>93.IBPluginDependency</string>
<string>93.ImportedFromIB2</string>
<string>94.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -1370,23 +1554,41 @@
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABDloAAwxAAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwr4AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBYAAAwx0AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABDWAAAwwYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABC7gAAwwYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABC6gAAwx8AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{88, 591}, {392, 254}}</string>
<string>{{413, 591}, {392, 254}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{88, 591}, {392, 254}}</string>
<string>{{413, 591}, {392, 254}}</string>
<boolean value="YES"/>
<boolean value="YES"/>
<string>{213, 107}</string>
@@ -1415,11 +1617,14 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwpYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwfAAAA</bytes>
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwlwAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -1443,6 +1648,9 @@
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwuYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -1457,10 +1665,29 @@
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBYAAAwx0AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABCbAAAwx8AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBYAAAwjwAAA</bytes>
</object>
<boolean value="YES"/>
<string>{{213, 762}, {216, 43}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
@@ -1479,7 +1706,7 @@
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">78</int>
<int key="maxID">100</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">

View File

@@ -29,7 +29,7 @@
/* Class = "NSMenuItem"; title = "Right in destination"; ObjectID = "30"; */
"30.title" = "Directement à la destination";
/* Class = "NSButtonCell"; title = "Match scaled pictures together"; ObjectID = "31"; */
/* Class = "NSButtonCell"; title = "Match pictures of different dimensions"; ObjectID = "31"; */
"31.title" = "Comparer les images de tailles différentes";
/* Class = "NSButtonCell"; title = "Automatically check for updates"; ObjectID = "32"; */

View File

@@ -2,13 +2,13 @@
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1050</int>
<string key="IBDocument.SystemVersion">10J567</string>
<string key="IBDocument.InterfaceBuilderVersion">823</string>
<string key="IBDocument.SystemVersion">10J4138</string>
<string key="IBDocument.InterfaceBuilderVersion">851</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">462.00</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">823</string>
<string key="NS.object.0">851</string>
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -98,7 +98,7 @@
<object class="NSSlider" id="266372855">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{117, 140}, {181, 21}}</string>
<string key="NSFrame">{{117, 107}, {181, 21}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSliderCell" key="NSCell" id="453640282">
@@ -126,7 +126,7 @@
<object class="NSTextField" id="869007847">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{119, 123}, {80, 13}}</string>
<string key="NSFrame">{{119, 90}, {80, 13}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="106025161">
@@ -162,7 +162,7 @@
<object class="NSTextField" id="171701149">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">289</int>
<string key="NSFrame">{{216, 123}, {80, 13}}</string>
<string key="NSFrame">{{216, 90}, {80, 13}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="397705219">
@@ -178,7 +178,7 @@
<object class="NSTextField" id="638371207">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{14, 145}, {100, 14}}</string>
<string key="NSFrame">{{14, 112}, {100, 14}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="812365472">
@@ -198,7 +198,7 @@
<object class="NSButton" id="488256664">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 79}, {316, 18}}</string>
<string key="NSFrame">{{15, 46}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="401283671">
@@ -209,7 +209,7 @@
<reference key="NSControlView" ref="488256664"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<object class="NSCustomResource" key="NSNormalImage" id="283442644">
<object class="NSCustomResource" key="NSNormalImage" id="949163782">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSSwitch</string>
</object>
@@ -225,7 +225,7 @@
<object class="NSButton" id="722670516">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 99}, {316, 18}}</string>
<string key="NSFrame">{{15, 66}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="911281323">
@@ -236,7 +236,7 @@
<reference key="NSControlView" ref="722670516"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="283442644"/>
<reference key="NSNormalImage" ref="949163782"/>
<reference key="NSAlternateImage" ref="990345653"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
@@ -247,7 +247,7 @@
<object class="NSButton" id="472028782">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 39}, {316, 18}}</string>
<string key="NSFrame">{{15, 6}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="2297113">
@@ -258,7 +258,7 @@
<reference key="NSControlView" ref="472028782"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="283442644"/>
<reference key="NSNormalImage" ref="949163782"/>
<reference key="NSAlternateImage" ref="990345653"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
@@ -269,7 +269,7 @@
<object class="NSButton" id="279087998">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{15, 59}, {316, 18}}</string>
<string key="NSFrame">{{15, 26}, {316, 18}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="287383961">
@@ -280,7 +280,7 @@
<reference key="NSControlView" ref="279087998"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="283442644"/>
<reference key="NSNormalImage" ref="949163782"/>
<reference key="NSAlternateImage" ref="990345653"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
@@ -291,7 +291,7 @@
<object class="NSTextField" id="403531548">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{301, 145}, {31, 14}}</string>
<string key="NSFrame">{{301, 112}, {31, 14}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="983190380">
@@ -369,6 +369,87 @@
<reference key="NSTextColor" ref="538152464"/>
</object>
</object>
<object class="NSTextField" id="536472926">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{14, 145}, {85, 13}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="359086043">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">272629760</int>
<string key="NSContents">Scan type:</string>
<reference key="NSSupport" ref="649492068"/>
<reference key="NSControlView" ref="536472926"/>
<reference key="NSBackgroundColor" ref="71910056"/>
<reference key="NSTextColor" ref="538152464"/>
</object>
</object>
<object class="NSPopUpButton" id="337614813">
<reference key="NSNextResponder" ref="1073354031"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{113, 135}, {219, 26}}</string>
<reference key="NSSuperview" ref="1073354031"/>
<bool key="NSEnabled">YES</bool>
<object class="NSPopUpButtonCell" key="NSCell" id="697629846">
<int key="NSCellFlags">-2076049856</int>
<int key="NSCellFlags2">2048</int>
<reference key="NSSupport" ref="882799568"/>
<reference key="NSControlView" ref="337614813"/>
<int key="NSButtonFlags">109199615</int>
<int key="NSButtonFlags2">1</int>
<reference key="NSAlternateImage" ref="882799568"/>
<string key="NSAlternateContents"/>
<object class="NSMutableString" key="NSKeyEquivalent">
<characters key="NS.bytes"/>
</object>
<int key="NSPeriodicDelay">400</int>
<int key="NSPeriodicInterval">75</int>
<object class="NSMenuItem" key="NSMenuItem" id="1038855957">
<reference key="NSMenu" ref="958971008"/>
<string key="NSTitle">Contents</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<int key="NSState">1</int>
<object class="NSCustomResource" key="NSOnImage" id="875822430">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuCheckmark</string>
</object>
<object class="NSCustomResource" key="NSMixedImage" id="731403416">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuMixedState</string>
</object>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="697629846"/>
</object>
<bool key="NSMenuItemRespectAlignment">YES</bool>
<object class="NSMenu" key="NSMenu" id="958971008">
<object class="NSMutableString" key="NSTitle">
<characters key="NS.bytes">OtherViews</characters>
</object>
<object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="1038855957"/>
<object class="NSMenuItem" id="820923003">
<reference key="NSMenu" ref="958971008"/>
<string key="NSTitle">EXIF Timestamp</string>
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="697629846"/>
</object>
</object>
</object>
<int key="NSPreferredEdge">3</int>
<bool key="NSUsesItemFromMenu">YES</bool>
<bool key="NSAltersState">YES</bool>
<int key="NSArrowPosition">1</int>
</object>
</object>
</object>
<string key="NSFrame">{{10, 33}, {346, 162}}</string>
<reference key="NSSuperview" ref="211771207"/>
@@ -398,7 +479,7 @@
<reference key="NSControlView" ref="1018598123"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="283442644"/>
<reference key="NSNormalImage" ref="949163782"/>
<reference key="NSAlternateImage" ref="990345653"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
@@ -420,7 +501,7 @@
<reference key="NSControlView" ref="519470955"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="283442644"/>
<reference key="NSNormalImage" ref="949163782"/>
<reference key="NSAlternateImage" ref="990345653"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
@@ -442,7 +523,7 @@
<reference key="NSControlView" ref="606836304"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="283442644"/>
<reference key="NSNormalImage" ref="949163782"/>
<reference key="NSAlternateImage" ref="990345653"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
@@ -509,14 +590,8 @@
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<int key="NSState">1</int>
<object class="NSCustomResource" key="NSOnImage" id="867788054">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuCheckmark</string>
</object>
<object class="NSCustomResource" key="NSMixedImage" id="554538570">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuMixedState</string>
</object>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="601288025"/>
</object>
@@ -534,8 +609,8 @@
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="867788054"/>
<reference key="NSMixedImage" ref="554538570"/>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="601288025"/>
</object>
@@ -545,8 +620,8 @@
<string key="NSKeyEquiv"/>
<int key="NSKeyEquivModMask">1048576</int>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="867788054"/>
<reference key="NSMixedImage" ref="554538570"/>
<reference key="NSOnImage" ref="875822430"/>
<reference key="NSMixedImage" ref="731403416"/>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="601288025"/>
</object>
@@ -881,6 +956,42 @@
</object>
<int key="connectionID">78</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">selectedIndex: values.scanType</string>
<reference key="source" ref="337614813"/>
<reference key="destination" ref="455472712"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="337614813"/>
<reference key="NSDestination" ref="455472712"/>
<string key="NSLabel">selectedIndex: values.scanType</string>
<string key="NSBinding">selectedIndex</string>
<string key="NSKeyPath">values.scanType</string>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">96</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">enabled: values.scanType</string>
<reference key="source" ref="266372855"/>
<reference key="destination" ref="455472712"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="266372855"/>
<reference key="NSDestination" ref="455472712"/>
<string key="NSLabel">enabled: values.scanType</string>
<string key="NSBinding">enabled</string>
<string key="NSKeyPath">values.scanType</string>
<object class="NSDictionary" key="NSOptions">
<string key="NS.key.0">NSValueTransformerName</string>
<string key="NS.object.0">vtScanTypeIsFuzzy</string>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">98</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
@@ -997,15 +1108,17 @@
<reference key="object" ref="1073354031"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="722670516"/>
<reference ref="488256664"/>
<reference ref="638371207"/>
<reference ref="171701149"/>
<reference ref="869007847"/>
<reference ref="266372855"/>
<reference ref="869007847"/>
<reference ref="171701149"/>
<reference ref="638371207"/>
<reference ref="488256664"/>
<reference ref="722670516"/>
<reference ref="279087998"/>
<reference ref="403531548"/>
<reference ref="472028782"/>
<reference ref="337614813"/>
<reference ref="536472926"/>
<reference ref="403531548"/>
</object>
<reference key="parent" ref="700068878"/>
</object>
@@ -1272,6 +1385,58 @@
<reference key="object" ref="100803310"/>
<reference key="parent" ref="606836304"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">87</int>
<reference key="object" ref="536472926"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="359086043"/>
</object>
<reference key="parent" ref="1073354031"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">88</int>
<reference key="object" ref="337614813"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="697629846"/>
</object>
<reference key="parent" ref="1073354031"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">89</int>
<reference key="object" ref="697629846"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="958971008"/>
</object>
<reference key="parent" ref="337614813"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">90</int>
<reference key="object" ref="958971008"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="820923003"/>
<reference ref="1038855957"/>
</object>
<reference key="parent" ref="697629846"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">92</int>
<reference key="object" ref="820923003"/>
<reference key="parent" ref="958971008"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">93</int>
<reference key="object" ref="1038855957"/>
<reference key="parent" ref="958971008"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">94</int>
<reference key="object" ref="359086043"/>
<reference key="parent" ref="536472926"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
@@ -1285,16 +1450,22 @@
<string>10.IBViewBoundsToFrameTransform</string>
<string>10.ImportedFromIB2</string>
<string>11.IBPluginDependency</string>
<string>11.IBViewBoundsToFrameTransform</string>
<string>11.ImportedFromIB2</string>
<string>12.IBPluginDependency</string>
<string>12.IBViewBoundsToFrameTransform</string>
<string>12.ImportedFromIB2</string>
<string>13.IBPluginDependency</string>
<string>13.IBViewBoundsToFrameTransform</string>
<string>13.ImportedFromIB2</string>
<string>14.IBPluginDependency</string>
<string>14.IBViewBoundsToFrameTransform</string>
<string>14.ImportedFromIB2</string>
<string>15.IBPluginDependency</string>
<string>15.IBViewBoundsToFrameTransform</string>
<string>15.ImportedFromIB2</string>
<string>16.IBPluginDependency</string>
<string>16.IBViewBoundsToFrameTransform</string>
<string>16.ImportedFromIB2</string>
<string>17.IBPluginDependency</string>
<string>18.IBPluginDependency</string>
@@ -1331,6 +1502,7 @@
<string>4.IBPluginDependency</string>
<string>4.ImportedFromIB2</string>
<string>5.IBPluginDependency</string>
<string>5.IBViewBoundsToFrameTransform</string>
<string>5.ImportedFromIB2</string>
<string>59.IBPluginDependency</string>
<string>6.IBPluginDependency</string>
@@ -1351,6 +1523,7 @@
<string>69.IBViewBoundsToFrameTransform</string>
<string>69.ImportedFromIB2</string>
<string>7.IBPluginDependency</string>
<string>7.IBViewBoundsToFrameTransform</string>
<string>7.ImportedFromIB2</string>
<string>70.IBPluginDependency</string>
<string>74.IBPluginDependency</string>
@@ -1360,9 +1533,24 @@
<string>8.IBPluginDependency</string>
<string>8.IBViewBoundsToFrameTransform</string>
<string>8.ImportedFromIB2</string>
<string>87.IBPluginDependency</string>
<string>87.IBViewBoundsToFrameTransform</string>
<string>87.ImportedFromIB2</string>
<string>88.IBPluginDependency</string>
<string>88.IBViewBoundsToFrameTransform</string>
<string>88.ImportedFromIB2</string>
<string>89.IBPluginDependency</string>
<string>9.IBPluginDependency</string>
<string>9.IBViewBoundsToFrameTransform</string>
<string>9.ImportedFromIB2</string>
<string>90.IBEditorWindowLastContentRect</string>
<string>90.IBPluginDependency</string>
<string>90.ImportedFromIB2</string>
<string>92.IBPluginDependency</string>
<string>92.ImportedFromIB2</string>
<string>93.IBPluginDependency</string>
<string>93.ImportedFromIB2</string>
<string>94.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -1375,23 +1563,41 @@
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABDloAAwxAAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwr4AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBYAAAwx0AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABDWAAAwwYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABC7gAAwwYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABC6gAAwx8AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{88, 591}, {392, 254}}</string>
<string>{{413, 591}, {392, 254}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{88, 591}, {392, 254}}</string>
<string>{{413, 591}, {392, 254}}</string>
<boolean value="YES"/>
<boolean value="YES"/>
<string>{1.79769e+308, 1.79769e+308}</string>
@@ -1421,11 +1627,14 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwpYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwfAAAA</bytes>
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwlwAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -1449,6 +1658,9 @@
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBcAAAwuYAAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -1463,10 +1675,29 @@
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBYAAAwx0AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABCbAAAwx8AAA</bytes>
</object>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBYAAAwjwAAA</bytes>
</object>
<boolean value="YES"/>
<string>{{213, 762}, {216, 43}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
@@ -1485,7 +1716,7 @@
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">78</int>
<int key="maxID">100</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">

View File

@@ -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

View File

@@ -92,10 +92,13 @@ class DupeGuru(RegistrableApplication, Broadcaster):
logging.warning("Exception on GetDisplayInfo for %s: %s", str(dupe.path), str(e))
return ['---'] * len(self.data.COLUMNS)
def _create_file(self, path):
# We add fs.Folder to fileclasses in case the file we're loading contains folder paths.
return fs.get_file(path, self.directories.fileclasses + [fs.Folder])
def _get_file(self, str_path):
path = Path(str_path)
# We add fs.Folder to fileclasses in case the file we're loading contains folder paths.
f = fs.get_file(path, self.directories.fileclasses + [fs.Folder])
f = self._create_file(path)
if f is None:
return None
try:

View File

@@ -14,9 +14,6 @@ import time
Column = namedtuple('Column', 'attr display')
def format_path(p):
return str(p[:-1])
def format_timestamp(t, delta):
if delta:
return format_time_decimal(t)

View File

@@ -149,6 +149,10 @@ class File:
def name(self):
return self.path[-1]
@property
def folder_path(self):
return self.path[:-1]
class Folder(File):
"""A wrapper around a folder path.

View File

@@ -328,15 +328,13 @@ class Results(Markable):
def sort_dupes(self, key, asc=True, delta=False):
if not self.__dupes:
self.__get_dupe_list()
self.__dupes.sort(key=lambda d: self.data.GetDupeSortKey(d, lambda: self.get_group_of_duplicate(d), key, delta))
if not asc:
self.__dupes.reverse()
keyfunc = lambda d: self.data.GetDupeSortKey(d, lambda: self.get_group_of_duplicate(d), key, delta)
self.__dupes.sort(key=keyfunc, reverse=not asc)
self.__dupes_sort_descriptor = (key,asc,delta)
def sort_groups(self,key,asc=True):
self.groups.sort(key=lambda g: self.data.GetGroupSortKey(g, key))
if not asc:
self.groups.reverse()
keyfunc = lambda g: self.data.GetGroupSortKey(g, key)
self.groups.sort(key=keyfunc, reverse=not asc)
self.__groups_sort_descriptor = (key,asc)
#---Properties

View File

@@ -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
@@ -26,6 +30,10 @@ class ScanType:
Contents = 5
ContentsAudio = 6
#PE
FuzzyBlock = 10
ExifTimestamp = 11
SCANNABLE_TAGS = ['track', 'artist', 'album', 'title', 'genre', 'year']
RE_DIGIT_ENDING = re.compile(r'\d+|\(\d+\)|\[\d+\]|{\d+}')

View File

@@ -9,11 +9,11 @@
# data module for tests
from hscommon.util import format_size
from ..data import format_path, cmp_value, Column
from ..data import cmp_value, Column
COLUMNS = [
Column('name', 'Filename'),
Column('path', 'Directory'),
Column('folder_path', 'Directory'),
Column('size', 'Size (KB)'),
Column('extension', 'Kind'),
]
@@ -29,7 +29,7 @@ def GetDisplayInfo(dupe, group, delta):
size -= r.size
return [
dupe.name,
format_path(dupe.path),
str(dupe.folder_path),
format_size(size, 0, 1, False),
dupe.extension if hasattr(dupe, 'extension') else '---',
]

View File

@@ -27,6 +27,11 @@ class NamedObject(engine_test.NamedObject):
def __bool__(self):
return False #Make sure that operations are made correctly when the bool value of files is false.
@property
def folder_path(self):
return self.path[:-1]
# Returns a group set that looks like that:
# "foo bar" (1)
# "bar bleh" (1024)

View File

@@ -8,14 +8,14 @@
from hscommon.util import format_time, format_size
from hscommon.trans import tr as trbase
from core.data import (format_path, format_timestamp, format_words, format_perc,
format_dupe_count, cmp_value, Column)
from core.data import (format_timestamp, format_words, format_perc, format_dupe_count, cmp_value,
Column)
tr = lambda s: trbase(s, 'columns')
COLUMNS = [
Column('name', tr("Filename")),
Column('path', tr("Folder")),
Column('folder_path', tr("Folder")),
Column('size', tr("Size (MB)")),
Column('duration', tr("Time")),
Column('bitrate', tr("Bitrate")),
@@ -63,7 +63,7 @@ def GetDisplayInfo(dupe, group, delta):
dupe_count = len(group.dupes)
return [
dupe.name,
format_path(dupe.path),
str(dupe.folder_path),
format_size(size, 2, 2, False),
format_time(duration, with_hours=False),
str(bitrate),

View File

@@ -1,2 +1,2 @@
__version__ = '2.1.0'
__version__ = '2.2.1'
__appname__ = 'dupeGuru Picture Edition'

View File

@@ -14,37 +14,28 @@ import re
from appscript import app, its, CommandError, ApplicationNotFoundError
from hscommon import io
from hscommon.util import get_file_ext, remove_invalid_xml
from hscommon.util import remove_invalid_xml
from hscommon.path import Path
from hscommon.cocoa.objcmin import NSUserDefaults, NSURL
from hscommon.trans import tr
from core import fs
from core import app_cocoa, directories
from . import data, _block_osx
from .photo import Photo as PhotoBase
from .scanner import ScannerPE
IPHOTO_PATH = Path('iPhoto Library')
class Photo(fs.File):
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
INITIAL_INFO.update({
'dimensions': (0,0),
})
HANDLED_EXTS = {'png', 'jpg', 'jpeg', 'gif', 'psd', 'bmp', 'tiff', 'tif', 'nef', 'cr2'}
class Photo(PhotoBase):
HANDLED_EXTS = PhotoBase.HANDLED_EXTS.copy()
HANDLED_EXTS.update({'psd', 'nef', 'cr2', 'orf'})
@classmethod
def can_handle(cls, path):
return fs.File.can_handle(path) and get_file_ext(path[-1]) in cls.HANDLED_EXTS
def _plat_get_dimensions(self):
return _block_osx.get_image_size(str(self.path))
def _read_info(self, field):
fs.File._read_info(self, field)
if field == 'dimensions':
self.dimensions = _block_osx.get_image_size(str(self.path))
def get_blocks(self, block_count_per_side):
def _plat_get_blocks(self, block_count_per_side, orientation):
try:
blocks = _block_osx.getblocks(str(self.path), block_count_per_side)
blocks = _block_osx.getblocks(str(self.path), block_count_per_side, orientation)
except Exception as e:
raise IOError('The reading of "%s" failed with "%s"' % (str(self.path), str(e)))
if not blocks:
@@ -54,8 +45,8 @@ class Photo(fs.File):
class IPhoto(Photo):
@property
def display_path(self):
return Path(('iPhoto Library', self.name))
def display_folder_path(self):
return IPHOTO_PATH
def get_iphoto_database_path():
ud = NSUserDefaults.standardUserDefaults()
@@ -175,11 +166,10 @@ class DupeGuruPE(app_cocoa.DupeGuru):
else:
app_cocoa.DupeGuru._do_delete_dupe(self, dupe, replace_with_hardlinks)
def _get_file(self, str_path):
p = Path(str_path)
if (self.directories.iphoto_libpath is not None) and (p in self.directories.iphoto_libpath[:-1]):
return IPhoto(p)
return app_cocoa.DupeGuru._get_file(self, str_path)
def _create_file(self, path):
if (self.directories.iphoto_libpath is not None) and (path in self.directories.iphoto_libpath[:-1]):
return IPhoto(path)
return app_cocoa.DupeGuru._create_file(self, path)
def copy_or_move(self, dupe, copy, destination, dest_type):
if isinstance(dupe, IPhoto):

View File

@@ -8,7 +8,7 @@
from hscommon.util import format_size
from hscommon.trans import tr as trbase
from core.data import format_path, format_timestamp, format_perc, format_dupe_count, cmp_value, Column
from core.data import format_timestamp, format_perc, format_dupe_count, cmp_value, Column
tr = lambda s: trbase(s, 'columns')
@@ -17,7 +17,7 @@ def format_dimensions(dimensions):
COLUMNS = [
Column('name', tr("Filename")),
Column('path', tr("Folder")),
Column('folder_path', tr("Folder")),
Column('size', tr("Size (KB)")),
Column('extension', tr("Kind")),
Column('dimensions', tr("Dimensions")),
@@ -26,6 +26,7 @@ COLUMNS = [
Column('dupe_count', tr("Dupe Count")),
]
FOLDER_COL = 1
MATCHPERC_COL = 6
DUPECOUNT_COL = 7
DELTA_COLUMNS = {2, 4, 5}
@@ -53,10 +54,10 @@ def GetDisplayInfo(dupe,group,delta=False):
else:
percentage = group.percentage
dupe_count = len(group.dupes)
dupe_path = getattr(dupe, 'display_path', dupe.path)
dupe_folder_path = getattr(dupe, 'display_folder_path', dupe.folder_path)
return [
dupe.name,
format_path(dupe_path),
str(dupe_folder_path),
format_size(size, 0, 1, False),
dupe.extension,
format_dimensions(dimensions),
@@ -71,6 +72,9 @@ def GetDupeSortKey(dupe, get_group, key, delta):
return m.percentage
if key == DUPECOUNT_COL:
return 0
if key == FOLDER_COL:
dupe_folder_path = getattr(dupe, 'display_folder_path', dupe.folder_path)
return cmp_value(str(dupe_folder_path))
r = cmp_value(getattr(dupe, COLUMNS[key].attr, ''))
if delta and (key in DELTA_COLUMNS):
ref_value = cmp_value(getattr(get_group().ref, COLUMNS[key].attr, ''))
@@ -85,5 +89,8 @@ def GetGroupSortKey(group, key):
return group.percentage
if key == DUPECOUNT_COL:
return len(group)
if key == FOLDER_COL:
dupe_folder_path = getattr(group.ref, 'display_folder_path', group.ref.folder_path)
return cmp_value(str(dupe_folder_path))
return cmp_value(getattr(group.ref, COLUMNS[key].attr, ''))

335
core_pe/exif.py Normal file
View File

@@ -0,0 +1,335 @@
# 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
# Heavily based on http://topo.math.u-psud.fr/~bousch/exifdump.py by Thierry Bousch (Public Domain)
import logging
EXIF_TAGS = {
0x0100: "ImageWidth",
0x0101: "ImageLength",
0x0102: "BitsPerSample",
0x0103: "Compression",
0x0106: "PhotometricInterpretation",
0x010A: "FillOrder",
0x010D: "DocumentName",
0x010E: "ImageDescription",
0x010F: "Make",
0x0110: "Model",
0x0111: "StripOffsets",
0x0112: "Orientation",
0x0115: "SamplesPerPixel",
0x0116: "RowsPerStrip",
0x0117: "StripByteCounts",
0x011A: "XResolution",
0x011B: "YResolution",
0x011C: "PlanarConfiguration",
0x0128: "ResolutionUnit",
0x012D: "TransferFunction",
0x0131: "Software",
0x0132: "DateTime",
0x013B: "Artist",
0x013E: "WhitePoint",
0x013F: "PrimaryChromaticities",
0x0156: "TransferRange",
0x0200: "JPEGProc",
0x0201: "JPEGInterchangeFormat",
0x0202: "JPEGInterchangeFormatLength",
0x0211: "YCbCrCoefficients",
0x0212: "YCbCrSubSampling",
0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite",
0x828F: "BatteryLevel",
0x8298: "Copyright",
0x829A: "ExposureTime",
0x829D: "FNumber",
0x83BB: "IPTC/NAA",
0x8769: "ExifIFDPointer",
0x8773: "InterColorProfile",
0x8822: "ExposureProgram",
0x8824: "SpectralSensitivity",
0x8825: "GPSInfoIFDPointer",
0x8827: "ISOSpeedRatings",
0x8828: "OECF",
0x9000: "ExifVersion",
0x9003: "DateTimeOriginal",
0x9004: "DateTimeDigitized",
0x9101: "ComponentsConfiguration",
0x9102: "CompressedBitsPerPixel",
0x9201: "ShutterSpeedValue",
0x9202: "ApertureValue",
0x9203: "BrightnessValue",
0x9204: "ExposureBiasValue",
0x9205: "MaxApertureValue",
0x9206: "SubjectDistance",
0x9207: "MeteringMode",
0x9208: "LightSource",
0x9209: "Flash",
0x920A: "FocalLength",
0x9214: "SubjectArea",
0x927C: "MakerNote",
0x9286: "UserComment",
0x9290: "SubSecTime",
0x9291: "SubSecTimeOriginal",
0x9292: "SubSecTimeDigitized",
0xA000: "FlashPixVersion",
0xA001: "ColorSpace",
0xA002: "PixelXDimension",
0xA003: "PixelYDimension",
0xA004: "RelatedSoundFile",
0xA005: "InteroperabilityIFDPointer",
0xA20B: "FlashEnergy", # 0x920B in TIFF/EP
0xA20C: "SpatialFrequencyResponse", # 0x920C - -
0xA20E: "FocalPlaneXResolution", # 0x920E - -
0xA20F: "FocalPlaneYResolution", # 0x920F - -
0xA210: "FocalPlaneResolutionUnit", # 0x9210 - -
0xA214: "SubjectLocation", # 0x9214 - -
0xA215: "ExposureIndex", # 0x9215 - -
0xA217: "SensingMethod", # 0x9217 - -
0xA300: "FileSource",
0xA301: "SceneType",
0xA302: "CFAPattern", # 0x828E in TIFF/EP
0xA401: "CustomRendered",
0xA402: "ExposureMode",
0xA403: "WhiteBalance",
0xA404: "DigitalZoomRatio",
0xA405: "FocalLengthIn35mmFilm",
0xA406: "SceneCaptureType",
0xA407: "GainControl",
0xA408: "Contrast",
0xA409: "Saturation",
0xA40A: "Sharpness",
0xA40B: "DeviceSettingDescription",
0xA40C: "SubjectDistanceRange",
0xA420: "ImageUniqueID",
}
INTR_TAGS = {
0x0001: "InteroperabilityIndex",
0x0002: "InteroperabilityVersion",
0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageWidth",
0x1002: "RelatedImageLength",
}
GPS_TA0GS = {
0x00: "GPSVersionID",
0x01: "GPSLatitudeRef",
0x02: "GPSLatitude",
0x03: "GPSLongitudeRef",
0x04: "GPSLongitude",
0x05: "GPSAltitudeRef",
0x06: "GPSAltitude",
0x07: "GPSTimeStamp",
0x08: "GPSSatellites",
0x09: "GPSStatus",
0x0A: "GPSMeasureMode",
0x0B: "GPSDOP",
0x0C: "GPSSpeedRef",
0x0D: "GPSSpeed",
0x0E: "GPSTrackRef",
0x0F: "GPSTrack",
0x10: "GPSImgDirectionRef",
0x11: "GPSImgDirection",
0x12: "GPSMapDatum",
0x13: "GPSDestLatitudeRef",
0x14: "GPSDestLatitude",
0x15: "GPSDestLongitudeRef",
0x16: "GPSDestLongitude",
0x17: "GPSDestBearingRef",
0x18: "GPSDestBearing",
0x19: "GPSDestDistanceRef",
0x1A: "GPSDestDistance",
0x1B: "GPSProcessingMethod",
0x1C: "GPSAreaInformation",
0x1D: "GPSDateStamp",
0x1E: "GPSDifferential"
}
INTEL_ENDIAN = ord('I')
MOTOROLA_ENDIAN = ord('M')
# About MAX_COUNT: It's possible to have corrupted exif tags where the entry count is way too high
# and thus makes us loop, not endlessly, but for heck of a long time for nothing. Therefore, we put
# an arbitrary limit on the entry count we'll allow ourselves to read and any IFD reporting more
# entries than that will be considered corrupt.
MAX_COUNT = 0xffff
def s2n_motorola(bytes):
x = 0
for c in bytes:
x = (x << 8) | c
return x
def s2n_intel(bytes):
x = 0
y = 0
for c in bytes:
x = x | (c << y)
y = y + 8
return x
class Fraction:
def __init__(self, num, den):
self.num = num
self.den = den
def __repr__(self):
return '%d/%d' % (self.num, self.den)
class TIFF_file:
def __init__(self, data):
self.data = data
self.endian = data[0]
self.s2nfunc = s2n_intel if self.endian == INTEL_ENDIAN else s2n_motorola
def s2n(self, offset, length, signed=0, debug=False):
slice = self.data[offset:offset+length]
val = self.s2nfunc(slice)
# Sign extension ?
if signed:
msb = 1 << (8*length - 1)
if val & msb:
val = val - (msb << 1)
if debug:
logging.debug(self.endian)
logging.debug("Slice for offset %d length %d: %r and value: %d", offset, length, slice, val)
return val
def first_IFD(self):
return self.s2n(4, 4)
def next_IFD(self, ifd):
entries = self.s2n(ifd, 2)
return self.s2n(ifd + 2 + 12 * entries, 4)
def list_IFDs(self):
i = self.first_IFD()
a = []
while i:
a.append(i)
i = self.next_IFD(i)
return a
def dump_IFD(self, ifd):
entries = self.s2n(ifd, 2)
logging.debug("Entries for IFD %d: %d", ifd, entries)
if entries > MAX_COUNT:
logging.debug("Probably corrupt. Aborting.")
return []
a = []
for i in range(entries):
entry = ifd + 2 + 12*i
tag = self.s2n(entry, 2)
type = self.s2n(entry+2, 2)
if not 1 <= type <= 10:
continue # not handled
typelen = [ 1, 1, 2, 4, 8, 1, 1, 2, 4, 8 ] [type-1]
count = self.s2n(entry+4, 4)
if count > MAX_COUNT:
logging.debug("Probably corrupt. Aborting.")
return []
offset = entry+8
if count*typelen > 4:
offset = self.s2n(offset, 4)
if type == 2:
# Special case: nul-terminated ASCII string
values = str(self.data[offset:offset+count-1], encoding='latin-1')
else:
values = []
signed = (type == 6 or type >= 8)
for j in range(count):
if type in {5, 10}:
# The type is either 5 or 10
value_j = Fraction(self.s2n(offset, 4, signed),
self.s2n(offset+4, 4, signed))
else:
# Not a fraction
value_j = self.s2n(offset, typelen, signed)
values.append(value_j)
offset = offset + typelen
# Now "values" is either a string or an array
a.append((tag,type,values))
return a
def read_exif_header(fp):
# If `fp`'s first bytes are not exif, it tries to find it in the next 4kb
def isexif(data):
return data[0:4] == b'\377\330\377\341' and data[6:10] == b'Exif'
data = fp.read(12)
if isexif(data):
return data
# ok, not exif, try to find it
large_data = fp.read(4096)
try:
index = large_data.index(b'Exif')
data = large_data[index-6:index+6]
# 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)
return data
except ValueError:
raise ValueError("Not an Exif file")
def get_fields(fp):
data = read_exif_header(fp)
length = data[4] * 256 + data[5]
logging.debug("Exif header length: %d bytes", length)
data = fp.read(length-8)
data_format = data[0]
logging.debug("%s format", {INTEL_ENDIAN:'Intel', MOTOROLA_ENDIAN:'Motorola'}[data_format])
T = TIFF_file(data)
# There may be more than one IFD per file, but we only read the first one because others are
# most likely thumbnails.
main_IFD_offset = T.first_IFD()
result = {}
def add_tag_to_result(tag, values):
try:
stag = EXIF_TAGS[tag]
except KeyError:
stag = '0x%04X' % tag
if stag in result:
return # don't overwrite data
result[stag] = values
logging.debug("IFD at offset %d", main_IFD_offset)
IFD = T.dump_IFD(main_IFD_offset)
exif_off = gps_off = 0
for tag, type, values in IFD:
if tag == 0x8769:
exif_off = values[0]
continue
if tag == 0x8825:
gps_off = values[0]
continue
add_tag_to_result(tag, values)
if exif_off:
logging.debug("Exif SubIFD at offset %d:", exif_off)
IFD = T.dump_IFD(exif_off)
# Recent digital cameras have a little subdirectory
# here, pointed to by tag 0xA005. Apparently, it's the
# "Interoperability IFD", defined in Exif 2.1 and DCF.
intr_off = 0
for tag, type, values in IFD:
if tag == 0xA005:
intr_off = values[0]
continue
add_tag_to_result(tag, values)
if intr_off:
logging.debug("Exif Interoperability SubSubIFD at offset %d:", intr_off)
IFD = T.dump_IFD(intr_off)
for tag, type, values in IFD:
add_tag_to_result(tag, values)
if gps_off:
logging.debug("GPS SubIFD at offset %d:", gps_off)
IFD = T.dump_IFD(gps_off)
for tag, type, values in IFD:
add_tag_to_result(tag, values)
return result

37
core_pe/matchexif.py Normal file
View File

@@ -0,0 +1,37 @@
# 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, match_scaled, 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.info("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():
for p1, p2 in combinations(pictures, 2):
if (not match_scaled) and (p1.dimensions != p2.dimensions):
continue
matches.append(Match(p1, p2, 100))
return matches

View File

@@ -11,6 +11,8 @@
#import <Foundation/Foundation.h>
#define RADIANS( degrees ) ( degrees * M_PI / 180 )
static CFStringRef
pystring2cfstring(PyObject *pystring)
{
@@ -149,12 +151,10 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
CFURLRef image_url;
CGImageSourceRef source;
CGImageRef image;
size_t width, height;
int block_count, block_width, block_height, i;
size_t width, height, image_width, image_height;
int block_count, block_width, block_height, orientation, i;
width = 0;
height = 0;
if (!PyArg_ParseTuple(args, "Oi", &path, &block_count)) {
if (!PyArg_ParseTuple(args, "Oii", &path, &block_count, &orientation)) {
return NULL;
}
@@ -163,6 +163,10 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
return NULL;
}
if ((orientation > 8) || (orientation < 0)) {
orientation = 0; // simplifies checks later since we can only have values in 0-8
}
image_path = pystring2cfstring(path);
if (image_path == NULL) {
return PyErr_NoMemory();
@@ -182,13 +186,61 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
return PyErr_NoMemory();
}
width = CGImageGetWidth(image);
height = CGImageGetHeight(image);
CGContextRef myContext = MyCreateBitmapContext(width, height);
CGRect myBoundingBox = CGRectMake(0, 0, width, height);
CGContextDrawImage(myContext, myBoundingBox, image);
unsigned char *bitmapData = CGBitmapContextGetData(myContext);
CGContextRelease(myContext);
width = image_width = CGImageGetWidth(image);
height = image_height = CGImageGetHeight(image);
if (orientation >= 5) {
// orientations 5-8 rotate the photo sideways, so we have to swap width and height
width = image_height;
height = image_width;
}
CGContextRef context = MyCreateBitmapContext(width, height);
if (orientation == 2) {
// Flip X
CGContextTranslateCTM(context, width, 0);
CGContextScaleCTM(context, -1, 1);
}
else if (orientation == 3) {
// Rot 180
CGContextTranslateCTM(context, width, height);
CGContextRotateCTM(context, RADIANS(180));
}
else if (orientation == 4) {
// Flip Y
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, 1, -1);
}
else if (orientation == 5) {
// Flip X + Rot CW 90
CGContextTranslateCTM(context, width, 0);
CGContextScaleCTM(context, -1, 1);
CGContextTranslateCTM(context, 0, height);
CGContextRotateCTM(context, RADIANS(-90));
}
else if (orientation == 6) {
// Rot CW 90
CGContextTranslateCTM(context, 0, height);
CGContextRotateCTM(context, RADIANS(-90));
}
else if (orientation == 7) {
// Rot CCW 90 + Flip X
CGContextTranslateCTM(context, width, 0);
CGContextScaleCTM(context, -1, 1);
CGContextTranslateCTM(context, width, 0);
CGContextRotateCTM(context, RADIANS(90));
}
else if (orientation == 8) {
// Rot CCW 90
CGContextTranslateCTM(context, width, 0);
CGContextRotateCTM(context, RADIANS(90));
}
CGRect myBoundingBox = CGRectMake(0, 0, image_width, image_height);
CGContextDrawImage(context, myBoundingBox, image);
unsigned char *bitmapData = CGBitmapContextGetData(context);
CGContextRelease(context);
CGImageRelease(image);
CFRelease(source);
if (bitmapData == NULL) {

53
core_pe/photo.py Normal file
View File

@@ -0,0 +1,53 @@
# Created By: Virgil Dupras
# Created On: 2011-05-29
# 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
from hscommon import io
from hscommon.util import get_file_ext
from core import fs
from . import exif
class Photo(fs.File):
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
INITIAL_INFO.update({
'dimensions': (0,0),
})
# These extensions are supported on all platforms
HANDLED_EXTS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'tif'}
def _plat_get_dimensions(self):
raise NotImplementedError()
def _plat_get_blocks(self, block_count_per_side, orientation):
raise NotImplementedError()
def _get_orientation(self):
if not hasattr(self, '_cached_orientation'):
try:
with io.open(self.path, 'rb') as fp:
exifdata = exif.get_fields(fp)
# the value is a list (probably one-sized) of ints
orientations = exifdata['Orientation']
self._cached_orientation = orientations[0]
except Exception: # Couldn't read EXIF data, no transforms
self._cached_orientation = 0
return self._cached_orientation
@classmethod
def can_handle(cls, path):
return fs.File.can_handle(path) and get_file_ext(path[-1]) in cls.HANDLED_EXTS
def _read_info(self, field):
fs.File._read_info(self, field)
if field == 'dimensions':
self.dimensions = self._plat_get_dimensions()
if self._get_orientation() in {5, 6, 7, 8}:
self.dimensions = (self.dimensions[1], self.dimensions[0])
def get_blocks(self, block_count_per_side):
return self._plat_get_blocks(block_count_per_side, self._get_orientation())

View File

@@ -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, self.match_scaled, j)
else:
raise Exception("Invalid scan type")
def clear_picture_cache(self):
cache = Cache(self.cache_path)

View File

@@ -8,14 +8,14 @@
from hscommon.util import format_size
from hscommon.trans import tr as trbase
from core.data import (format_path, format_timestamp, format_words, format_perc,
format_dupe_count, cmp_value, Column)
from core.data import (format_timestamp, format_words, format_perc, format_dupe_count, cmp_value,
Column)
tr = lambda s: trbase(s, 'columns')
COLUMNS = [
Column('name', tr("Filename")),
Column('path', tr("Folder")),
Column('folder_path', tr("Folder")),
Column('size', tr("Size (KB)")),
Column('extension', tr("Kind")),
Column('mtime', tr("Modification")),
@@ -46,7 +46,7 @@ def GetDisplayInfo(dupe, group, delta):
dupe_count = len(group.dupes)
return [
dupe.name,
format_path(dupe.path),
str(dupe.folder_path),
format_size(size, 0, 1, False),
dupe.extension,
format_timestamp(mtime, delta and m),

View File

@@ -1,3 +1,21 @@
=== 2.2.1 (2011-06-15)
* Brought back the filter hardness label which disappeared in 2.2.0. [Mac OS X] (#164)
* Added support for Olympus RAW files (.orf). [Mac OS X] (#159)
* Fixed a bug causing the picture analysis to stall on some corrupt EXIF data.
* Make the "Match scaled pictures" option apply to the new EXIF timestamp scan. (#162)
* Fixed glitch in the sorting feature of the Folder column. (#161)
=== 2.2.0 (2011-06-02)
* Added the EXIF Timestamp scan type.
* Correctly rotate pictures before analysis when EXIF orientation indicates it. (#154)
* Make sure that saved results have the ".dupeguru" extension. [Linux] (#157)
* Fixed a text coloring glitch in the results. (#156)
* Fixed a refresh bug in directory panel. (#153)
* Fixed a crash on saving results. (#149)
* Improved reliability of the "Send to Trash" operation. [Linux]
=== 2.1.0 (2011-03-07)
* Improved scanning speed, especially for big scans with a lot of CPU cores.

View File

@@ -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. **Warning:** Modified pictures often keep the same EXIF timestamp, so watch out for false positives when you use that scan type.
**Match scaled pictures together:** If you check this box, pictures of different dimensions will be allowed in the same duplicate group.
**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 pictures of different dimensions:** 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!

View File

@@ -32,7 +32,9 @@ 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. **Attention:** Les photos modifiées gardent souvent le même timestamp, donc faites attention aux faux doublons si vous utilisez cette méthode.
**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.

View File

@@ -198,7 +198,7 @@ class DirectoriesDialog(QMainWindow):
def loadResultsTriggered(self):
title = trmsg("SelectResultToLoadMsg")
files = tr("dupeGuru Results (*.dupeguru)")
files = ';;'.join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")])
destination = QFileDialog.getOpenFileName(self, title, '', files)
if destination:
self.app.load_from(destination)

View File

@@ -25,6 +25,19 @@ class PreferencesDialogBase(QDialog):
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
def _setupScanTypeBox(self, labels):
self.scanTypeHLayout = QHBoxLayout()
self.scanTypeLabel = QLabel(self)
self.scanTypeLabel.setText(tr("Scan Type:"))
self.scanTypeLabel.setMinimumSize(QSize(100, 0))
self.scanTypeLabel.setMaximumSize(QSize(100, 16777215))
self.scanTypeHLayout.addWidget(self.scanTypeLabel)
self.scanTypeComboBox = QComboBox(self)
for label in labels:
self.scanTypeComboBox.addItem(label)
self.scanTypeHLayout.addWidget(self.scanTypeComboBox)
self.widgetsVLayout.addLayout(self.scanTypeHLayout)
def _setupFilterHardnessBox(self):
self.filterHardnessHLayout = QHBoxLayout()
self.filterHardnessLabel = QLabel(self)

View File

@@ -165,7 +165,7 @@ class ResultWindow(QMainWindow):
self.actionActions.setMenu(actionMenu)
def _setupUi(self):
self.setWindowTitle(tr("dupeGuru Results"))
self.setWindowTitle(tr("{} Results").format(self.app.NAME))
self.resize(630, 514)
self.centralwidget = QWidget(self)
self.verticalLayout_2 = QVBoxLayout(self.centralwidget)
@@ -335,6 +335,8 @@ class ResultWindow(QMainWindow):
files = tr("dupeGuru Results (*.dupeguru)")
destination = QFileDialog.getSaveFileName(self, title, '', files)
if destination:
if not destination.endswith('.dupeguru'):
destination = '{}.dupeguru'.format(destination)
self.app.save_as(destination)
self.app.recentResults.insertItem(destination)

View File

@@ -235,6 +235,10 @@
<source>dupeGuru Results (*.dupeguru)</source>
<translation>Résultats dupeGuru (*.dupeguru)</translation>
</message>
<message>
<source>All Files (*.*)</source>
<translation>Tout les fichiers (*.*)</translation>
</message>
<message>
<source>Start a new scan</source>
<translation>Commencer un nouveau scan</translation>
@@ -388,8 +392,8 @@
<translation>Réinitialiser</translation>
</message>
<message>
<source>dupeGuru Results</source>
<translation>dupeGuru (Résultats)</translation>
<source>{} Results</source>
<translation>{} (Résultats)</translation>
</message>
<message>
<source>Delete duplicates</source>
@@ -526,7 +530,7 @@
<translation>Année</translation>
</message>
<message>
<source>Match scaled pictures together</source>
<source>Match pictures of different dimensions</source>
<translation>Comparer les images de tailles différentes</translation>
</message>
<message>

View File

@@ -7,9 +7,9 @@
# http://www.hardcoded.net/licenses/bsd_license
import sys
from PyQt4.QtCore import SIGNAL, QSize
from PyQt4.QtGui import (QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QSizePolicy, QSpacerItem,
QWidget, QApplication)
from PyQt4.QtCore import QSize
from PyQt4.QtGui import (QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget,
QApplication)
from hscommon.trans import tr
from core.scanner import ScanType
@@ -30,24 +30,12 @@ class PreferencesDialog(PreferencesDialogBase):
def __init__(self, parent, app):
PreferencesDialogBase.__init__(self, parent, app)
self.connect(self.scanTypeComboBox, SIGNAL('currentIndexChanged(int)'), self.scanTypeChanged)
self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
def _setupPreferenceWidgets(self):
self.horizontalLayout = QHBoxLayout()
self.label_2 = QLabel(self)
self.label_2.setText(tr("Scan Type:"))
self.label_2.setMinimumSize(QSize(100, 0))
self.label_2.setMaximumSize(QSize(100, 16777215))
self.horizontalLayout.addWidget(self.label_2)
self.scanTypeComboBox = QComboBox(self)
self.scanTypeComboBox.addItem(tr("Filename"))
self.scanTypeComboBox.addItem(tr("Filename - Fields"))
self.scanTypeComboBox.addItem(tr("Filename - Fields (No Order)"))
self.scanTypeComboBox.addItem(tr("Tags"))
self.scanTypeComboBox.addItem(tr("Contents"))
self.scanTypeComboBox.addItem(tr("Audio Contents"))
self.horizontalLayout.addWidget(self.scanTypeComboBox)
self.widgetsVLayout.addLayout(self.horizontalLayout)
scanTypeLabels = [tr(s) for s in ["Filename", "Filename - Fields",
"Filename - Fields (No Order)", "Tags", "Contents", "Audio Contents"]]
self._setupScanTypeBox(scanTypeLabels)
self._setupFilterHardnessBox()
self.widgetsVLayout.addLayout(self.filterHardnessHLayout)
self.widget = QWidget(self)

View File

@@ -9,12 +9,10 @@
import os.path as op
import logging
from PyQt4.QtGui import QImage, QImageReader
from PyQt4.QtGui import QImage, QImageReader, QTransform
from hscommon.util import get_file_ext
from core import fs
from core_pe import data as data_pe, __appname__
from core_pe.photo import Photo as PhotoBase
from core_pe.scanner import ScannerPE
from ..base.app import DupeGuru as DupeGuruBase
@@ -24,34 +22,46 @@ from .result_window import ResultWindow
from .preferences import Preferences
from .preferences_dialog import PreferencesDialog
class File(fs.File):
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
INITIAL_INFO.update({
'dimensions': (0,0),
})
HANDLED_EXTS = set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'tif'])
class File(PhotoBase):
def _plat_get_dimensions(self):
try:
ir = QImageReader(str(self.path))
size = ir.size()
if size.isValid():
return (size.width(), size.height())
else:
return (0, 0)
except EnvironmentError:
logging.warning("Could not read image '%s'", str(self.path))
return (0, 0)
@classmethod
def can_handle(cls, path):
return fs.File.can_handle(path) and get_file_ext(path[-1]) in cls.HANDLED_EXTS
def _read_info(self, field):
fs.File._read_info(self, field)
if field == 'dimensions':
try:
ir = QImageReader(str(self.path))
size = ir.size()
if size.isValid():
self.dimensions = (size.width(), size.height())
else:
self.dimensions = (0, 0)
except EnvironmentError:
self.dimensions = (0, 0)
logging.warning("Could not read image '%s'", str(self.path))
def get_blocks(self, block_count_per_side):
def _plat_get_blocks(self, block_count_per_side, orientation):
image = QImage(str(self.path))
image = image.convertToFormat(QImage.Format_RGB888)
# MYSTERY TO SOLVE: For reasons I cannot explain, orientations 5 and 7 don't work for
# duplicate scanning. The transforms seems to work fine (if I try to save the image after
# the transform, we see that the image has been correctly flipped and rotated), but the
# analysis part yields wrong blocks. I spent enought time with this feature, so I'll leave
# like that for now. (by the way, orientations 5 and 7 work fine under Cocoa)
if 2 <= orientation <= 8:
t = QTransform()
if orientation == 2:
t.scale(-1, 1)
elif orientation == 3:
t.rotate(180)
elif orientation == 4:
t.scale(1, -1)
elif orientation == 5:
t.scale(-1, 1)
t.rotate(90)
elif orientation == 6:
t.rotate(90)
elif orientation == 7:
t.scale(-1, 1)
t.rotate(270)
elif orientation == 8:
t.rotate(270)
image = image.transformed(t)
return getblocks(image, block_count_per_side)
@@ -71,6 +81,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

View File

@@ -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)

View File

@@ -10,15 +10,29 @@ 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"))
self._setupAddCheckbox('matchScaledBox', tr("Match pictures of different dimensions"))
self.widgetsVLayout.addWidget(self.matchScaledBox)
self._setupAddCheckbox('mixFileKindBox', tr("Can mix file kind"))
self.widgetsVLayout.addWidget(self.mixFileKindBox)
@@ -33,14 +47,23 @@ 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)
if __name__ == '__main__':
from ..testapp import TestApp

View File

@@ -8,8 +8,8 @@
import sys
from PyQt4.QtCore import QSize
from PyQt4.QtGui import (QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QSizePolicy, QSpacerItem,
QWidget, QLineEdit, QApplication)
from PyQt4.QtGui import (QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget,
QLineEdit, QApplication)
from hscommon.trans import tr
from hscommon.util import tryint
@@ -32,17 +32,8 @@ class PreferencesDialog(PreferencesDialogBase):
self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
def _setupPreferenceWidgets(self):
self.horizontalLayout = QHBoxLayout()
self.label_2 = QLabel(self)
self.label_2.setText(tr("Scan Type:"))
self.label_2.setMinimumSize(QSize(100, 0))
self.label_2.setMaximumSize(QSize(100, 16777215))
self.horizontalLayout.addWidget(self.label_2)
self.scanTypeComboBox = QComboBox(self)
for label in [tr("Filename"), tr("Contents"), tr("Folders")]:
self.scanTypeComboBox.addItem(label)
self.horizontalLayout.addWidget(self.scanTypeComboBox)
self.widgetsVLayout.addLayout(self.horizontalLayout)
scanTypeLabels = [tr(s) for s in ["Filename", "Contents", "Folders"]]
self._setupScanTypeBox(scanTypeLabels)
self._setupFilterHardnessBox()
self.widgetsVLayout.addLayout(self.filterHardnessHLayout)
self.widget = QWidget(self)