2 iPod Shuffle database access
5 - http://ipodlinux.org/ITunesDB#iTunesSD_file and further
7 Author: Artem Baguinski
10 import struct
, os
, sys
17 def pack_uint24(i
,bigendian
):
18 "Pack an unsigned integer to a string of three bytes"
20 return struct
.pack(">I",i
)[1:4]
22 return struct
.pack("<I",i
)[0:3]
24 def unpack_uint24(s
,bigendian
):
25 "Unpack an unsigned integer from first three bytes of a string"
27 return struct
.unpack('>I','\x00' + s
[0:3])[0]
29 return struct
.unpack('<I',s
[0:3] + '\x00')[0]
31 def pack_int24(i
,bigendian
):
32 "Pack a signed integer to a string of three bytes"
34 return struct
.pack(">i",i
)[1:4]
36 return struct
.pack("<i",i
)[0:3]
38 def unpack_int24(s
,bigendian
):
39 "Unpack a signed integer from first three bytes of a string"
40 u
= unpack_uint24(s
,bigendian
)
42 return - ((~u
+ 1) & 0xfff)
58 def __init__(self
, filename
=None):
59 if filename
is not None:
60 self
.set_filename(filename
)
62 def set_filename(self
, filename
):
63 self
.filename
= filename
64 if filename
.endswith((".mp3",".aa")):
66 elif filename
.endswith((".m4a", ".m4b", ".m4p")):
68 elif filename
.endswith(".wav"):
71 raise "%s: unsupported file type" % (filename
)
72 if filename
.endswith((".aa",".m4b")):
73 self
.bookmarkflag
= True
75 self
.bookmarkflag
= False
76 self
.shuffleflag
= not self
.bookmarkflag
79 s
= "%s\n vol: %d " % (self
.filename
, self
.volume
)
80 if self
.starttime
!= 0 or self
.stoptime
!= 0:
81 s
+= "%5.3fs-%5.3fs " % (self
.starttime
*0.256, self
.stoptime
*0.256)
83 bm
= self
.bookmarktime
86 s
+= "bookmark: %5.3fs " % (bm
*0.256)
89 s
+= "played: %d skipped: %d" % (self
.playcount
, self
.skippedcount
)
99 if self
.file is not None:
100 self
.file.seek(n
, os
.SEEK_CUR
)
103 if self
.file is None:
104 raise "No current file"
105 return self
.file.read(n
)
107 def write(self
, buf
):
108 if self
.file is None:
109 raise "No current file"
112 def read_u24(self
, check
= None):
113 u24
= unpack_uint24( self
.read(3), self
.endian
)
114 if check
is not None and u24
!= check
:
118 def read_i24(self
, check
= None):
119 i24
= unpack_int24( self
.read(3), self
.endian
)
120 if check
is not None and i24
!= check
:
124 def write_u24(self
, u
):
125 self
.write( pack_uint24(u
, self
.endian
) )
127 def write_i24(self
, u
):
128 self
.write( pack_int24(u
, self
.endian
) )
131 return self
.read(1) != '\x00'
133 def write_bool(self
, v
):
139 def read_string(self
, raw_len
):
140 return self
.read(raw_len
).decode("UTF-16-le").rstrip("\x00")
142 def write_string(self
, string
, pad_to
):
143 self
.write( string
.encode("UTF-16-le").ljust(pad_to
, '\x00') )
146 def open_file(self
, fname
, endian
, mode
=READ
):
147 if self
.file is not None:
148 if self
.mode
== WRITE
:
152 if fname
is not None:
153 self
.file = open(fname
,mode
)
158 def close_file(self
):
159 self
.open_file(None, BIG_ENDIAN
)
161 def write_iTunesSD(self
, tracks
):
162 self
.open_file('iTunesSD', BIG_ENDIAN
, WRITE
)
163 self
.write_u24(len(tracks
))
164 self
.write_u24(0x010800) # like iTunes 7.2 does
165 self
.write_u24(18) # header size
168 self
.write_iTunesSD_track(t
)
171 def write_iTunesSD_track(self
, t
):
172 self
.write_u24( 558 ) # record length
174 self
.write_u24( t
.starttime
)
176 self
.write_u24( t
.stoptime
)
178 self
.write_u24( t
.volume
)
179 self
.write_u24( t
.file_type
)
181 self
.write_string( t
.filename
, 522)
182 self
.write_bool( t
.shuffleflag
)
183 self
.write_bool( t
.bookmarkflag
)
186 def read_iTunesSD(self
):
187 self
.open_file('iTunesSD', BIG_ENDIAN
)
188 num_tracks
= self
.read_u24()
189 self
.skip(15) # skip the rest of the header
191 for n
in xrange(0, num_tracks
):
192 tracks
.append( self
.read_iTunesSD_track() )
196 def read_iTunesSD_track(self
):
198 self
.read_u24( 558 ) # sanity check (record length)
200 t
.starttime
= self
.read_u24()
202 t
.stoptime
= self
.read_u24()
204 t
.volume
= self
.read_u24()
205 t
.file_type
= self
.read_u24()
207 t
.filename
= self
.read_string(522)
208 t
.shuffleflag
= self
.read_bool()
209 t
.bookmarkflag
= self
.read_bool()
213 def write_iTunesStats(self
, tracks
):
214 self
.open_file('iTunesStats', LITTLE_ENDIAN
, WRITE
)
215 self
.write_u24( len(tracks
) )
218 self
.write_iTunesStats_track(t
)
221 def write_iTunesStats_track(self
, t
):
222 self
.write_u24( 18 ) # record length
223 self
.write_i24( t
.bookmarktime
)
225 self
.write_u24( t
.playcount
)
226 self
.write_u24( t
.skippedcount
)
228 def read_iTunesStats(self
, tracks
):
229 self
.open_file('iTunesStats', LITTLE_ENDIAN
)
230 num_tracks
= self
.read_u24()
231 if num_tracks
!= len(tracks
):
232 raise "Inconsistent iTunesSD and iTunesStats"
235 self
.read_iTunesStats_track(t
)
238 def read_iTunesStats_track(self
, t
):
239 self
.read_u24( 18 ) # sanity check (record length)
240 t
.bookmarktime
= self
.read_i24()
242 t
.playcount
= self
.read_u24()
243 t
.skippedcount
= self
.read_u24()
245 def write_iTunesPState(self
, pstate
):
246 self
.open_file('iTunesPState', LITTLE_ENDIAN
, WRITE
)
247 self
.write_u24( pstate
['volume'] )
248 self
.write_u24( pstate
['shufflepos'] )
249 self
.write_u24( pstate
['trackno'] )
250 self
.write_u24( pstate
['shuffleflag'] )
251 self
.write_u24( pstate
['trackpos'] )
256 def read_iTunesPState(self
):
258 self
.open_file('iTunesPState', LITTLE_ENDIAN
)
259 pstate
['volume'] = self
.read_u24()
260 pstate
['shufflepos'] = self
.read_u24()
261 pstate
['trackno'] = self
.read_u24()
262 pstate
['shuffleflag'] = self
.read_u24()
263 pstate
['trackpos'] = self
.read_u24()
269 tracks
= self
.read_iTunesSD()
270 self
.read_iTunesStats(tracks
)
271 pstate
= self
.read_iTunesPState()
272 return (tracks
, pstate
)
274 def write_all(self
, tracks
, pstate
):
275 self
.write_iTunesSD(tracks
)
276 self
.write_iTunesStats(tracks
)
277 self
.write_iTunesPState(pstate
)
279 #####################################################################
280 if __name__
== '__main__':
282 b100
= pack_uint24(100,True)
283 l100
= pack_uint24(100,False)
284 print b100
== '\x00\x00d'
285 print l100
== 'd\x00\x00'
286 print unpack_uint24(b100
,True) == 100
287 print unpack_uint24(l100
,False) == 100
289 b100
= pack_int24(-100,True)
290 l100
= pack_int24(-100,False)
291 print b100
== '\xff\xff\x9c'
292 print l100
== '\x9c\xff\xff'
293 print unpack_int24(b100
,True) == -100
294 print unpack_int24(l100
,False) == -100
301 if len(sys
.argv
) > 1:
302 start_dir
= os
.getcwd()
303 os
.chdir(sys
.argv
[1])
305 tracks
, pstate
= db
.read_all()
310 if len(sys
.argv
) > 2:
312 os
.chdir(sys
.argv
[2])
313 db
.write_all(tracks
, pstate
)