riffle is simpler and works better
[riffle.git] / shuffle.py
blob5533c7485dfb14c4e37c5aba6303d75887a35a48
1 """
2 iPod Shuffle database access
4 File format documentation:
5 - http://ipodlinux.org/ITunesDB#iTunesSD_file and further
7 Author: Artem Baguinski
8 """
10 from __future__ import with_statement
11 import os, sys
12 from records import *
14 READ = 'rb'
15 WRITE = 'w+b'
17 iTunesSD_track = Record([
18 Field(Uint24(), '%reclen%', check = True),
19 Skip(3),
20 Field(Uint24(), 'starttime', default = 0),
21 Skip(6),
22 Field(Uint24(), 'stoptime', default = 0),
23 Skip(6),
24 Field(Uint24(), 'volume', default = 0x64),
25 # for the following fileds there is no sensible default
26 Field(Uint24(), 'file_type'),
27 Skip(3),
28 Field(ZeroPaddedString(522, 'UTF-16-LE'), 'filename'),
29 Field(Bool8(), 'shuffleflag'),
30 Field(Bool8(), 'bookmarkflag'),
31 Skip(1)])
33 iTunesSD = Record([
34 Record([
35 Field(Uint24(), 'len(tracks)'),
36 Field(Uint24(), const = 0x010800),
37 Field(Uint24(), '%reclen%', check = True),
38 Skip(9)]),
39 ListField('tracks', iTunesSD_track)],
40 BIG_ENDIAN)
42 iTunesStats_track = Record([
43 Field( Uint24(), '%reclen%', check = True),
44 Field( Int24(), 'bookmarktime', default = -1),
45 Skip(6),
46 Field( Uint24(), 'playcount', default = 0),
47 Field( Uint24(), 'skippedcount', default = 0)])
49 iTunesStats = Record([
50 Record([
51 Field( Uint24(), 'len(tracks)'),
52 Skip(3)]),
53 ListField('tracks', iTunesStats_track)],
54 LITTLE_ENDIAN)
56 # Here I disagree with the format from URL, but I'm not sure about nothing
57 iTunesPState = Record([
58 Field( Uint8(), 'volume', default = 29 ),
59 Field( Uint24(), 'shufflepos', default = 0 ),
60 Field( Uint24(), 'trackno', default = 0 ),
61 Field( Bool24(), 'shuffleflag', default = False),
62 Field( Uint24(), 'trackpos', default = 0),
63 Skip(19)],
64 LITTLE_ENDIAN)
66 class ShuffleException(Exception):
67 pass
69 class ShuffleDB:
70 supported_file_types = (".mp3", ".aa", ".m4a", ".m4b", ".m4p", ".wav")
72 def write_iTunesSD(self, tracks):
73 with open('iTunesSD', WRITE) as file:
74 iTunesSD.write(file, {'tracks':tracks})
75 file.truncate()
77 def read_iTunesSD(self):
78 with open('iTunesSD', READ) as file:
79 return iTunesSD.read(file)['tracks']
81 def write_iTunesStats(self, tracks):
82 with open('iTunesStats', WRITE) as file:
83 iTunesStats.write(file, {'tracks':tracks})
84 file.truncate()
86 def read_iTunesStats(self, tracks):
87 with open('iTunesStats', READ) as file:
88 iTunesStats.read(file,{'tracks':tracks})['tracks']
90 def write_iTunesPState(self, pstate):
91 with open('iTunesPState', WRITE) as file:
92 iTunesPState.write(file, pstate)
93 file.truncate()
95 def read_iTunesPState(self):
96 with open('iTunesPState', READ) as file:
97 return iTunesPState.read(file)
99 def read_all(self, control_dir):
100 cwd = os.getcwd()
101 os.chdir(control_dir)
102 try:
103 tracks = self.read_iTunesSD()
104 self.read_iTunesStats(tracks)
105 pstate = self.read_iTunesPState()
106 return (tracks, pstate)
107 except FormatError:
108 return ([], iTunesPState.make_default())
109 finally:
110 os.chdir(cwd)
112 def write_all(self, control_dir, tracks, pstate):
113 cwd = os.getcwd()
114 os.chdir(control_dir)
115 try:
116 self.write_iTunesSD(tracks)
117 self.write_iTunesStats(tracks)
118 self.write_iTunesPState(pstate)
119 if os.path.exists("iTunesShuffle"):
120 os.remove("iTunesShuffle") # let the player recreate it
121 finally:
122 os.chdir(cwd)
124 def set_old_tracks(self, lst):
125 self.old_tracks = {}
126 for i in xrange(len(lst)):
127 t = lst[i]
128 self.old_tracks[t['filename']] = t
129 self.old_tracks[i] = t
131 def make_track_record(self, filename):
132 if self.old_tracks.has_key(filename):
133 return self.old_tracks[filename]
134 else:
135 dict = {}
136 iTunesSD_track.make_default(dict)
137 iTunesStats_track.make_default(dict)
139 dict['filename'] = filename
140 if filename.endswith((".mp3",".aa")):
141 dict['file_type'] = 1
142 elif filename.endswith((".m4a", ".m4b", ".m4p")):
143 dict['file_type'] = 2
144 elif filename.endswith(".wav"):
145 dict['file_type'] = 4
146 else:
147 raise ShuffleException(
148 "%s: unsupported file type" % (filename))
149 dict['bookmarkflag'] = filename.endswith((".aa",".m4b"))
150 dict['shuffleflag'] = not dict['bookmarkflag']
151 return dict
153 #####################################################################
154 if __name__ == '__main__':
155 import logging
156 logging.basicConfig(level=logging.DEBUG,filename='shuffle-debug.log')
158 def print_list(xs):
159 for x in xs:
160 print x
162 if len(sys.argv) > 1:
163 # try reading
164 db = ShuffleDB()
165 tracks, pstate = db.read_all(sys.argv[1])
166 print_list( tracks )
167 print pstate
169 #if len(sys.argv) > 2:
170 # try writing
171 #os.chdir(sys.argv[2])
172 #db.write_all(tracks, pstate)