diff options
Diffstat (limited to 'src/load_itunes.py')
-rw-r--r-- | src/load_itunes.py | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/load_itunes.py b/src/load_itunes.py new file mode 100644 index 0000000..f4131b1 --- /dev/null +++ b/src/load_itunes.py | |||
@@ -0,0 +1,326 @@ | |||
1 | import sqlite3 | ||
2 | import plistlib | ||
3 | |||
4 | library = plistlib.readPlist('iTunes-Music-Library.xml') | ||
5 | |||
6 | |||
7 | class BasicLibraryItem(object): | ||
8 | |||
9 | def get_insert_query(self): | ||
10 | return 'INSERT INTO {} VALUES ({})'.format( | ||
11 | self.TABLE_NAME, self.get_bind_string(self.COLUMN_COUNT)) | ||
12 | |||
13 | @staticmethod | ||
14 | def get_bind_string(count): | ||
15 | return ('?,' * count)[:-1] | ||
16 | |||
17 | @staticmethod | ||
18 | def as_data_string(value): | ||
19 | if not value: | ||
20 | return None | ||
21 | else: | ||
22 | return value.asBase64() | ||
23 | |||
24 | @staticmethod | ||
25 | def as_boolean(value): | ||
26 | return 1 if value else 0 | ||
27 | |||
28 | @staticmethod | ||
29 | def format_name(name): | ||
30 | return name.lower().replace(' ', '_') | ||
31 | |||
32 | @classmethod | ||
33 | def from_plist_entry(cls, entry): | ||
34 | self = cls() | ||
35 | |||
36 | for key, value in entry.items(): | ||
37 | name = cls.format_name(key) | ||
38 | |||
39 | if not hasattr(self, name): | ||
40 | raise Exception("No attribute named %r" % name) | ||
41 | |||
42 | setattr(self, name, value) | ||
43 | |||
44 | return self | ||
45 | |||
46 | |||
47 | class Playlist(BasicLibraryItem): | ||
48 | |||
49 | COLUMN_COUNT = 21 | ||
50 | TABLE_NAME = 'playlist' | ||
51 | |||
52 | def __init__(self): | ||
53 | self.playlist_id = None | ||
54 | self.playlist_persistent_id = None | ||
55 | self.parent_persistent_id = None | ||
56 | self.distinguished_kind = None | ||
57 | self.genius_track_id = None | ||
58 | self.name = None | ||
59 | |||
60 | self.master = False | ||
61 | self.visible = False | ||
62 | self.all_items = False | ||
63 | self.folder = False | ||
64 | |||
65 | self.music = False | ||
66 | self.movies = False | ||
67 | self.tv_shows = False | ||
68 | self.podcasts = False | ||
69 | self.itunesu = False | ||
70 | self.audiobooks = False | ||
71 | self.books = False | ||
72 | self.purchased_music = False | ||
73 | |||
74 | self.smart_info = None | ||
75 | self.smart_criteria = None | ||
76 | |||
77 | self.items = [] | ||
78 | |||
79 | def as_dbrow_tuple(self): | ||
80 | return ( | ||
81 | self.playlist_id, #int | ||
82 | 1, # user_id | ||
83 | self.playlist_persistent_id, | ||
84 | self.parent_persistent_id, | ||
85 | self.distinguished_kind, #int | ||
86 | self.genius_track_id, #int | ||
87 | self.name, | ||
88 | |||
89 | self.as_boolean(self.master), | ||
90 | self.as_boolean(self.visible), | ||
91 | self.as_boolean(self.all_items), | ||
92 | self.as_boolean(self.folder), | ||
93 | self.as_boolean(self.music), | ||
94 | self.as_boolean(self.movies), | ||
95 | self.as_boolean(self.tv_shows), | ||
96 | self.as_boolean(self.podcasts), | ||
97 | self.as_boolean(self.itunesu), | ||
98 | self.as_boolean(self.audiobooks), | ||
99 | self.as_boolean(self.books), | ||
100 | self.as_boolean(self.purchased_music), | ||
101 | |||
102 | self.as_data_string(self.smart_info), | ||
103 | self.as_data_string(self.smart_criteria) | ||
104 | ) | ||
105 | |||
106 | @classmethod | ||
107 | def from_plist_entry(cls, entry): | ||
108 | self = cls() | ||
109 | |||
110 | for key, value in entry.items(): | ||
111 | if key == 'Playlist Items': | ||
112 | for item in value: | ||
113 | self.items.append(item['Track ID']) | ||
114 | |||
115 | continue | ||
116 | |||
117 | name = cls.format_name(key) | ||
118 | |||
119 | if not hasattr(self, name): | ||
120 | raise Exception("No attribute named %r" % name) | ||
121 | |||
122 | setattr(self, name, value) | ||
123 | |||
124 | return self | ||
125 | |||
126 | |||
127 | class Track(BasicLibraryItem): | ||
128 | |||
129 | COLUMN_COUNT = 53 | ||
130 | TABLE_NAME = 'track' | ||
131 | |||
132 | def __init__(self): | ||
133 | self.album_rating = None | ||
134 | self.album_rating_computed = False | ||
135 | self.comments = None | ||
136 | self.volume_adjustment = None | ||
137 | self.unplayed = False | ||
138 | self.date_added = None | ||
139 | self.date_modified = None | ||
140 | self.disabled = False | ||
141 | self.play_count = None | ||
142 | self.play_date = None | ||
143 | self.play_date_utc = None | ||
144 | self.rating = None | ||
145 | self.skip_count = None | ||
146 | self.skip_date = None | ||
147 | self.track_id = None | ||
148 | self.persistent_id = None | ||
149 | self.library_folder_count = None | ||
150 | self.file_folder_count = None | ||
151 | self.location = None | ||
152 | self.track_type = None | ||
153 | self.file_type = None | ||
154 | self.artwork_count = None | ||
155 | self.hd = False | ||
156 | self.has_video = False | ||
157 | self.itunesu = False | ||
158 | self.tv_show = False | ||
159 | self.podcast = False | ||
160 | self.protected = False | ||
161 | self.purchased = False | ||
162 | self.movie = False | ||
163 | self.music_video = False | ||
164 | self.bpm = None | ||
165 | self.bit_rate = None | ||
166 | self.sample_rate = None | ||
167 | self.size = None | ||
168 | self.total_time = None | ||
169 | self.kind = None | ||
170 | self.video_height = None | ||
171 | self.video_width = None | ||
172 | self.sort_album = None | ||
173 | self.sort_album_artist = None | ||
174 | self.sort_artist = None | ||
175 | self.sort_composer = None | ||
176 | self.sort_name = None | ||
177 | self.sort_series = None | ||
178 | self.album = None | ||
179 | self.album_artist = None | ||
180 | self.artist = None | ||
181 | self.clean = False | ||
182 | self.compilation = False | ||
183 | self.composer = None | ||
184 | self.content_rating = None | ||
185 | self.disc_count = None | ||
186 | self.disc_number = None | ||
187 | self.episode = None | ||
188 | self.episode_order = None | ||
189 | self.explicit = False | ||
190 | self.genre = None | ||
191 | self.grouping = None | ||
192 | self.name = None | ||
193 | self.part_of_gapless_album = False | ||
194 | self.release_date = None | ||
195 | self.season = None | ||
196 | self.series = None | ||
197 | self.track_count = None | ||
198 | self.track_number = None | ||
199 | self.year = None | ||
200 | |||
201 | def as_dbrow_tuple(self): | ||
202 | return ( | ||
203 | self.track_id, | ||
204 | self.persistent_id, | ||
205 | self.library_folder_count, | ||
206 | self.file_folder_count, | ||
207 | self.location, | ||
208 | self.track_type, | ||
209 | self.file_type, | ||
210 | self.artwork_count, | ||
211 | self.as_boolean(self.hd), | ||
212 | self.as_boolean(self.has_video), | ||
213 | self.as_boolean(self.itunesu), | ||
214 | self.as_boolean(self.tv_show), | ||
215 | self.as_boolean(self.podcast), | ||
216 | self.as_boolean(self.protected), | ||
217 | self.as_boolean(self.purchased), | ||
218 | self.as_boolean(self.movie), | ||
219 | self.as_boolean(self.music_video), | ||
220 | self.bpm, | ||
221 | self.bit_rate, | ||
222 | self.sample_rate, | ||
223 | self.size, | ||
224 | self.total_time, | ||
225 | self.kind, | ||
226 | self.video_height, | ||
227 | self.video_width, | ||
228 | self.sort_album, | ||
229 | self.sort_album_artist, | ||
230 | self.sort_artist, | ||
231 | self.sort_composer, | ||
232 | self.sort_name, | ||
233 | self.sort_series, | ||
234 | self.album, | ||
235 | self.album_artist, | ||
236 | self.artist, | ||
237 | self.as_boolean(self.clean), | ||
238 | self.as_boolean(self.compilation), | ||
239 | self.composer, | ||
240 | self.content_rating, | ||
241 | self.disc_count, | ||
242 | self.disc_number, | ||
243 | self.episode, | ||
244 | self.episode_order, | ||
245 | self.as_boolean(self.explicit), | ||
246 | self.genre, | ||
247 | self.grouping, | ||
248 | self.name, | ||
249 | self.as_boolean(self.part_of_gapless_album), | ||
250 | self.release_date, | ||
251 | self.season, | ||
252 | self.series, | ||
253 | self.track_count, | ||
254 | self.track_number, | ||
255 | self.year | ||
256 | ) | ||
257 | |||
258 | def usermeta_as_dbrow_tuple(self): | ||
259 | return ( | ||
260 | 1, | ||
261 | self.track_id, | ||
262 | self.album_rating, | ||
263 | self.as_boolean(self.album_rating_computed), | ||
264 | self.comments, | ||
265 | self.volume_adjustment, | ||
266 | self.as_boolean(self.unplayed), | ||
267 | self.date_added, | ||
268 | self.date_modified, | ||
269 | self.as_boolean(self.disabled), | ||
270 | self.play_count, | ||
271 | self.play_date, | ||
272 | self.play_date_utc, | ||
273 | self.rating, | ||
274 | self.skip_count, | ||
275 | self.skip_date | ||
276 | ) | ||
277 | |||
278 | USERMETA_COLUMN_COUNT = 16 | ||
279 | USERMETA_TABLE_NAME = 'user_track_metadata' | ||
280 | |||
281 | def get_usermeta_insert_query(self): | ||
282 | return 'INSERT INTO {} VALUES ({})'.format( | ||
283 | self.USERMETA_TABLE_NAME, | ||
284 | self.get_bind_string(self.USERMETA_COLUMN_COUNT)) | ||
285 | |||
286 | |||
287 | assert library['Major Version'] == 1, "Invalid major version" | ||
288 | assert library['Minor Version'] == 1, "Invalid minor version" | ||
289 | |||
290 | def insert_playlists(db, library): | ||
291 | playlists = [Playlist.from_plist_entry(entry) | ||
292 | for entry in library['Playlists']] | ||
293 | |||
294 | curs = db.cursor() | ||
295 | track_insert_query = playlists[0].get_insert_query() | ||
296 | |||
297 | for playlist in playlists: | ||
298 | curs.execute(track_insert_query, playlist.as_dbrow_tuple()) | ||
299 | |||
300 | for item in playlist.items: | ||
301 | curs.execute('INSERT INTO playlist_track VALUES (?, ?)', | ||
302 | (playlist.playlist_id, item)) | ||
303 | |||
304 | db.commit() | ||
305 | |||
306 | |||
307 | def insert_tracks(db, library): | ||
308 | music_folder = library['Music Folder'] | ||
309 | tracks = [Track.from_plist_entry(entry) | ||
310 | for entry in library['Tracks'].values()] | ||
311 | |||
312 | curs = db.cursor() | ||
313 | track_insert_query = tracks[0].get_insert_query() | ||
314 | usermeta_insert_query = tracks[0].get_usermeta_insert_query() | ||
315 | |||
316 | for track in tracks: | ||
317 | curs.execute(track_insert_query, track.as_dbrow_tuple()) | ||
318 | curs.execute(usermeta_insert_query, track.usermeta_as_dbrow_tuple()) | ||
319 | |||
320 | db.commit() | ||
321 | |||
322 | |||
323 | db = sqlite3.connect('iTunesLibrary.db') | ||
324 | #insert_playlists(db, library) | ||
325 | insert_tracks(db, library) | ||
326 | db.close() | ||