-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathiPhotoExporter.py
executable file
·302 lines (204 loc) · 8.36 KB
/
iPhotoExporter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Appsthru.com
# http://appsthru.com
# Contact : [email protected]
#
# IPhotoExporter is a python script that exports and synchronizes
# events or iPhoto albums (MacOSX) simply in folders.
#
__version__ = "0.1"
from xml.dom.minidom import parse, parseString, Node
from optparse import OptionParser
import os, time, stat, shutil, sys,codecs,locale,unicodedata,datetime
def findChildElementsByName(parent, name):
result = []
for child in parent.childNodes:
if child.nodeName == name:
result.append(child)
return result
def getElementText(element):
if element is None: return None
if len(element.childNodes) == 0: return None
else: return element.childNodes[0].nodeValue
def getValueElementForKey(parent, keyName):
for key in findChildElementsByName(parent, "key"):
if getElementText(key) == keyName:
sib = key.nextSibling
while(sib is not None and sib.nodeType != Node.ELEMENT_NODE):
sib = sib.nextSibling
return sib
def unormalize (text ) :
if type(text) is not unicode :
#print ">>converting text",text,"(repr:", repr(text) , " )to unicode"
text = text.decode("utf-8")
return unicodedata.normalize("NFC",text)
def copyImage( sourceImageFilePath, targetFilePath , doCopy = True ) :
bCopyFile = False
basename = os.path.basename(targetFilePath)
if os.path.exists(targetFilePath):
# if file already exists, compare modification dates
targetStat = os.stat(targetFilePath)
modifiedStat = os.stat(sourceImageFilePath)
printv( "\t\tFile exists : %s , compare : " % (basename) )
printv( "\t\t - modified: %d %d" % (modifiedStat[stat.ST_MTIME], modifiedStat[stat.ST_SIZE]) )
printv( "\t\t - target : %d %d" % (targetStat[stat.ST_MTIME], targetStat[stat.ST_SIZE]) )
#why oh why is modified time not getting copied over exactly the same?
if abs(targetStat[stat.ST_MTIME] - modifiedStat[stat.ST_MTIME]) > 10 or targetStat[stat.ST_SIZE] != modifiedStat[stat.ST_SIZE]:
printv( "\t\t --> File modified" )
bCopyFile = True
else :
printv( "\t\t --> File identical" )
else:
bCopyFile = True
if bCopyFile :
print "\t\tCopy of %s" % ( basename )
if doCopy:
shutil.copy2(sourceImageFilePath, targetFilePath)
return
def printv(*args):
if verbose :
# Print each argument separately so caller doesn't need to
# stuff everything to be printed into a single string
for arg in args:
print arg,
print
# ----- MAIN ----------
usage = "Usage: %prog [options] <iPhoto Library dir> <destination dir>"
version = "iPhotoExporter version %s" % __version__
option_parser = OptionParser(usage=usage, version=version)
option_parser.set_defaults(
albums=False,
test=False,
verbose=False,
original=True
)
option_parser.add_option("-a", "--albums",
action="store_true", dest="albums",
help="use albums instead of events"
)
option_parser.add_option("-t", "--test",
action="store_true", dest="test",
help="don't actually copy files or create folders"
)
option_parser.add_option("-v", "--verbose",
action="store_true", dest="verbose",
help="display most of the actions"
)
option_parser.add_option("-o", "--original",
action="store_true", dest="original",
help="also copy original photos"
)
(options, args) = option_parser.parse_args()
if len(args) != 2:
option_parser.error(
"Please specify an iPhoto library and a destination."
)
iPhotoLibrary = unormalize( args[0] )
targetDir = unormalize( args[1] )
doCopy = not options.test
useEvents = not options.albums
verbose = options.verbose
copyOriginal = options.original
albumDataXml = os.path.join( iPhotoLibrary , "AlbumData.xml")
print ("Parsing AlbumData.xml")
startTime = time.time()
#minidom.parse produce Unicode strings
albumDataDom = parse(albumDataXml)
topElement = albumDataDom.documentElement
topMostDict = topElement.getElementsByTagName('dict')[0]
masterImageListDict = getValueElementForKey(topMostDict, "Master Image List")
folderList = []
if useEvents:
listOfSomethingArray = getValueElementForKey(topMostDict, "List of Rolls")
else:
listOfSomethingArray = getValueElementForKey(topMostDict, "List of Albums")
#walk through all the rolls (events) / albums
for folderDict in findChildElementsByName(listOfSomethingArray, 'dict'):
if useEvents:
folderName = getElementText(getValueElementForKey(folderDict, "RollName"))
else:
folderName = getElementText(getValueElementForKey(folderDict, "AlbumName"))
if folderName == 'Photos':
continue
#walk through all the images in this roll/event/album
imageIdArray = getValueElementForKey(folderDict, "KeyList")
#add this event/album in the folderList for later root dir cleaning
folderName = unormalize( folderName )
folderList.append( folderName )
print "\n\n*Processing folder : %s" % (folderName)
#print repr(folderName)
#print repr(targetDir)
#create event/album folder
targetFileDir = os.path.join(targetDir, folderName)
if not os.path.exists(targetFileDir) :
printv( "\t*Directory does not exist - Creating: %s" % targetFileDir )
if doCopy:
os.makedirs(targetFileDir)
#image list for later folder cleaning
imageList = []
for imageIdElement in findChildElementsByName(imageIdArray, 'string'):
imageId = getElementText(imageIdElement)
imageDict = getValueElementForKey(masterImageListDict, imageId)
modifiedFilePath = getElementText(getValueElementForKey(imageDict, "ImagePath"))
originalFilePath = getElementText(getValueElementForKey(imageDict, "OriginalPath"))
caption = getElementText(getValueElementForKey(imageDict, "Caption"))
sourceImageFilePath = modifiedFilePath
basename = os.path.basename(sourceImageFilePath)
#basename = unormalize( basename )
spname, spext = os.path.splitext(basename)
# use the caption name if exists
if spname != caption :
basename = spname + " [" + caption.strip() + "]" + spext
basename = unormalize( basename )
print "\t*Processing image '%s' , ID : %s , Caption : %s" % ( spname, imageId, caption )
targetFilePath = os.path.join(targetFileDir , basename )
#add this image to the imageList for later folder cleaning
imageList.append( basename )
#print repr(basename)
copyImage ( sourceImageFilePath, targetFilePath , doCopy )
# check if there is an original image
if copyOriginal and originalFilePath != None :
printv( "\t *There is an original image")
basename = os.path.basename(originalFilePath)
spname, spext = os.path.splitext(basename)
targetName = spname + " [original]" + spext
targetName = unormalize( targetName )
targetFilePath = os.path.join(targetFileDir , targetName )
imageList.append( targetName )
copyImage ( originalFilePath, targetFilePath , doCopy )
# Cleaning of this folder
#searches the directory for files and compare to imageList
#delete files not present in imageList
print "\n\t*Cleaning of the folder :"
for root, dirs, files in os.walk( targetFileDir ):
for name in files:
#print "file ", name
#print "type : ",type(name)
#print "repr : ",repr(name)
name = unormalize(name)
#print repr(name)
if name not in imageList :
printv( "\t - remove image : ", name)
os.remove( targetFileDir + "/" + name )
print "\tcleaning done."
#Cleaning Root Folder
print "\n===================\n"
print "Cleaning Root folder :"
for root, dirs,files in os.walk( targetDir ):
for name in dirs:
#print "folder ", name
#print "type : ",type(name)
#print "repr : ",repr(name)
name = unormalize(name)
if name not in folderList :
printv( "- remove '%s' " % name)
#print repr(name)
shutil.rmtree( os.path.join( targetDir, name ) )
print "cleaning done."
print ""
albumDataDom.unlink()
stopTime = time.time()
elapsedTime = stopTime - startTime
print "Elapsed time : ", datetime.timedelta(seconds=elapsedTime )
print ""