/
artwork.py
435 lines (378 loc) · 18.9 KB
/
artwork.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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
'''
Created on 14.02.2015
'''
import os
import logging
import urllib.request
import shutil
import sys
import configparser
import pickle
import re
DEBUG = False
logger = logging.getLogger('artwork_downloader')
ch = logging.StreamHandler()
if DEBUG:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
logger.info('Hello')
def install_package(package):
import pip
try:
pip.main(['install', '--upgrade', package])
except Exception as e:
sys.exit("Couldn't install {} with pip - {}".format(package, e))
try:
import requests
except ImportError:
logger.warning("Requests is not installed - will try to install it now via pip")
install_package("requests")
try:
import requests
except ImportError as e:
sys.exit("Failed to install requests - {}".format(e))
try:
from PIL import Image
except ImportError:
logger.warning("Pillow is not installed - will try to install it now via pip")
install_package("Pillow")
try:
from PIL import Image
except ImportError as e:
sys.exit("Failed to install Pillow - {}".format(e))
try:
import whatapi
except ImportError:
logger.warning("WhatAPI is not installed - will try to install it now via pip")
install_package("https://github.com/capital-G/whatapi/tarball/master")
try:
import whatapi
except ImportError as e:
sys.exit("Failed to install WhatAPI - {}".format(e))
try:
from mutagen.flac import FLAC
from mutagen.mp4 import MP4
from mutagen.easyid3 import EasyID3 as MP3
except ImportError:
logger.warning("mutagen is not installed - will try to install it now via pip")
install_package("mutagen")
try:
from mutagen.flac import FLAC
from mutagen.mp4 import MP4
from mutagen.easyid3 import EasyID3 as MP3
except ImportError as e:
sys.exit("Failed to install mutagen - {}".format(e))
class ArtworkFinder():
def __init__(self, **kwargs):
self._config = kwargs.get('config')
self._whatcd_api = kwargs.get('whatcdapi')
self._artist = kwargs.get('artist', None)
self._album = kwargs.get('album', None)
self._entity = kwargs.get('entity', 'album')
if self._config['iTunes'].getboolean('use'):
countries_string = self._config['iTunes']['countries']
self._countries = countries_string.split(',')
self._path = os.path.dirname(kwargs.get('file'))
self._source = None
delete_chars = ("various", "artists", "{", "}", "[", "]", "the ", ",", "(", ")",
"(", ")", "CD", "Deluxe", "Edition", "Remaster", "Remastered", "SACD", "MP3", "FLAC", "!", "#",
"disk", "disq", "disc", "bonus", "dvd")
for char in delete_chars:
self._artist = self._artist.lower().replace(char.lower(), " ")
if "various artists" in self._path.lower():
self._artist = ""
self._album = ''.join([i for i in self._album if not i.isdigit()])
self._name = self._artist+" "+self._album
def get_artwork(self):
if self._config['iTunes'].getboolean('use'):
itunes_api_results = self.itunes_api()
if itunes_api_results:
store_success = self.store()
if store_success is False:
logger.info("No Artwork found in iTunes for {} - {} in {}".format(self._artist.title(),
self._album.title(),
self._path))
return False
logger.info("Successfully saved the iTunes Artwork for {} - {} in the folder {}".format(self._artist,
self._album,
self._path))
return True
else:
logger.info("No Artwork found in iTunes for {} - {} in {}".format(self._artist.title(),
self._album.title(),
self._path))
if self._config['what-cd'].getboolean('use'):
whatcdapi_results = self.whatcd_api()
if whatcdapi_results:
store_success = self.store()
if store_success is False:
logger.info("No Artwork found in what.CD for {} - {} in {}".format(self._artist.title(),
self._album.title(),
self._path))
return False
logger.info("Sucessfully saved the what.CD Artwork for {} - {} in the folder {}".format(self._artist,
self._album,
self._path))
return True
else:
logger.info("No Artwork found in what.CD for {} - {} in {}".format(self._artist.title(),
self._album.title(),
self._path))
return False
def itunes_api(self):
for country in self._countries:
request_params = {'term': self._name,
'entity': self._entity,
'country': country}
try:
request = requests.get("http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/wa/wsSearch",
params=request_params)
except requests.exceptions.Timeout:
logger.error("No connection to iTunes API - check your Internet connection")
return False
if request.status_code != 200:
logger.error("Error from iTunes API - Error Code {} - {}".format(request.status_code, request.text))
return False
if len(request.json()['results']) == 0:
logger.info("No search results in iTunes {} for {}".format(country, self._name))
return False
else:
logger.debug("Found iTunes results for {} in {} iTunes Store".format(self._name, country))
break
self._highres_url = request.json()['results'][0].get('artworkUrl100').replace("100x100", "1200x1200")
self._normres_url = request.json()['results'][0].get('artworkUrl100').replace("100x100", "600x600")
logger.info("Found iTunes Artwork for {}, - {}".format(self._name, self._highres_url))
self._source = "iTunes"
return True
def whatcd_api(self):
what_request = self._whatcd_api.request("browse",searchstr=self._name)
if len(what_request['response']['results']) == 0:
logger.info("No What.CD results for {} - {}".format(self._artist, self._album))
return False
try:
self._highres_url = what_request['response']['results'][0]['cover']
except KeyError:
logger.info("Found a What-CD Release but without cover")
return False
if self._highres_url == "":
logger.info("Found a What-CD Release but without cover")
return False
logger.info("Found what.CD Artwork for {}, - {}".format(self._name, self._highres_url))
self._source = "whatCD"
return True
def store(self):
logger.debug("Cover-URL: {}".format(self._highres_url))
downloader = urllib.request.build_opener()
downloader.addheaders = [('User-agent', 'Mozilla/5.0')] # avoid problems with whatIMG
# todo: avoid problems with obscure file-extensions on servers - fix this with a regex!
file_name = self._highres_url.split('?')[0]
try:
with downloader.open(self._highres_url) as \
response, open(os.path.join(self._path, file_name.split("/")[-1]), "wb") as out_file:
shutil.copyfileobj(response, out_file)
except Exception as e:
logger.error("Couldn't write HighRes Artwork for {} - URL is {}: {}".format(self._path,
self._highres_url,
e))
return False
if os.path.splitext(file_name.split("/")[-1])[1] not in (".jpg", ".jpeg"):
logger.info("The downloaded Artwork {} is not .jpg or .jpeg, "
"so we will convert it to .jpg now".format(file_name.split("/")[-1]))
im = Image.open(os.path.join(self._path, file_name.split("/")[-1])).convert('RGB')
im.save(os.path.join(self._path, self._config['folder']['jpgname']))
os.remove(os.path.join(self._path, file_name.split("/")[-1]))
else:
os.rename(os.path.join(self._path, file_name.split("/")[-1]),
os.path.join(self._path, self._config['folder']['jpgname']))
def main():
os.chdir(os.path.split(os.path.abspath(__file__))[0])
logger.info("Welcome to artwork.py")
config = configparser.ConfigParser()
if os.path.isfile('artwork.ini') is False:
if os.path.isfile('artwork.dat') is True:
os.remove("artwork.dat")
logger.info("SETUP")
while True:
user_input = input("Do you want to use iTunes as Database? (Y/N) ")
if user_input.lower() == "y":
countries = input("Do you want to use anything else except only the American iTunes Store? "
"If so, enter the country codes separated by comma without any spaces. "
"If you only want to use the US Store, hit Enter ")
if countries == '':
countries = 'us'
config['iTunes'] = {'use': True,
'countries': countries}
break
elif user_input.lower() == "n":
config['iTunes'] = {'use': False}
break
while True:
user_input = input("Do you want to use What.CD as Database? (Y/N)")
if user_input.lower() == "y":
logger.info("OK, for that we will need your What-CD login")
whatcd_user = input("What.cd Username:")
whatcd_pw = input("What.cd Password:")
config['what-cd'] = {'use': True,
'username': whatcd_user,
'password': whatcd_pw}
break
if user_input.lower() == "n":
config['what-cd'] = {'use': False}
break
if config['iTunes'].getboolean('use') is False and config['what-cd'].getboolean('use') is False:
sys.exit("Sorry, you must select at least one of the two choices - Goobye")
while True:
folder_input = input("Do you want to use something else as folder.jpg? (Y/N) ")
if folder_input.lower() == "n":
config['folder'] = {'jpgname': 'folder.jpg'}
break
if folder_input.lower() == "y":
jpg_name = input("Ok, then enter your desired name: ")
if not "." in jpg_name:
jpg_name += ".jpg"
config['folder'] = {'jpgname': jpg_name}
break
with open('artwork.ini', 'w') as configfile:
config.write(configfile)
logger.info("Setup successful - if you want to run this setup again, "
"simply delete the artwork.ini file or modify it for your needs")
else:
config.read('artwork.ini')
if config['what-cd'].getboolean('use') is True and os.path.isfile("artwork.dat") is False:
logger.info("There's no cookie for your What.cd Login - so we will have to login you")
login_tryouts = 0
while True:
try:
whatcd_api = whatapi.WhatAPI(username=config['what-cd']['username'],
password=config['what-cd']['password'])
pickle.dump(whatcd_api.session.cookies, open('artwork.dat', 'wb'))
break
except Exception as e:
login_tryouts += 1
if login_tryouts > 3:
logger.error("It seems What-CD is down, so we won't use that now this time")
config['what-cd'] = {'use':False}
if config['iTunes'].getboolean('use') is False:
sys.exit("No iTunes and what.CD search is now active - so we're closing the program")
break
logger.error("There was a LogIn Error {e} - You have still have {}/3 chances. "
"Please Re Enter Your LogIn Data".format(e, login_tryouts))
whatcd_user = input("What.cd Username:")
whatcd_pw = input("What.cd Password:")
config['what-cd'] = {'use': True,
'username': whatcd_user,
'password': whatcd_pw}
with open('artwork.ini', 'w') as configfile:
config.write(configfile)
whatcd_api = ""
if config['what-cd'].getboolean('use') is True and os.path.isfile("artwork.dat") is True:
whatcd_cookies = pickle.load(open('artwork.dat', 'rb'))
login_tryouts = 0
while True:
try:
whatcd_api = whatapi.WhatAPI(username=config['what-cd']['username'],
password=config['what-cd']['password'],
cookies=whatcd_cookies)
logger.info("LogIn into What.CD was sucessful")
break
except Exception as e:
if login_tryouts >= 3:
logger.info("There seems to be some kind of error during logger in : {} - "
"we won't use What.cd this time".format(e))
config['what-cd']['use'] = False
if config['iTunes'].getboolean('use') is False:
sys.exit("No iTunes and what.CD search is now active - so we're closing the script")
break
login_tryouts += 1
try:
path = sys.argv[1]
logger.info("Scans {}".format(path))
folders_to_check = [path]
except:
path = input("Please Enter your Path you want to scan: ")
folders_to_check = list((os.path.join(root, folder)
for root, folders, files in os.walk(path) for folder in folders))
folders_to_check.append(path)
for folder in folders_to_check:
audio = False
artwork = False
for file in os.listdir(os.path.join(path, folder)):
if os.path.splitext(file)[1] in (".mp3", ".flac", ".m4a"):
audio = True
check_file = os.path.join(path, folder, file)
if file.lower() in (config['folder']['jpgname'].lower()):
artwork = True
if audio is True and artwork is True:
break
if audio is True and artwork is True:
logger.info("There's already artwork for {}".format(folder))
continue
if audio is True and artwork is False:
if os.path.splitext(check_file)[1] == ".flac":
try:
artist = FLAC(check_file)["artist"][0]
except:
logger.error("Artist not tagged in folder {}".format(check_file))
artist = ""
try:
album = FLAC(check_file)["album"][0]
except:
logger.error("Album not tagged in folder {}".format(check_file))
album = ""
if os.path.splitext(check_file)[1] == ".mp3":
try:
artist = MP3(check_file)["artist"][0]
except:
logger.error("Artist not tagged in folder {}".format(check_file))
artist = ""
try:
album = MP3(check_file)["album"][0]
except:
logger.error("Album not tagged in folder {}".format(check_file))
album = ""
if os.path.splitext(check_file)[1] == ".m4a":
try:
artist = MP4(check_file)["\xa9ART"]
except KeyError:
logger.warning("Artist not tagged in folder {}".format(check_file))
artist = ""
try:
album = MP4(check_file)["\xa9alb"]
except:
logger.warning("Album not tagged in folder {}".format(check_file))
album = ""
if album == "" and artist == "":
logger.warning("Album isn't tagged at all, will skip: {}".format(check_file))
continue
logger.debug("Found the following information for {}: Artist: {} and Album: {}".format(check_file,
artist,
album))
artwork_finder = ArtworkFinder(artist=str(artist),
album=str(album),
file=check_file,
config=config,
whatcdapi=whatcd_api)
artwork_finder.get_artwork()
logger.info("Done")
input("Press Enter To Exit")
sys.exit(0)
if __name__ == '__main__':
if DEBUG:
main()
else:
try:
main()
except KeyError as e:
logger.info("Your config file is outdated for the current version of this script - "
"it's recommended to delete the 'artwork.ini' file to force the setup once again:{}".format(e))
logger.info("Press Enter to Exit")
sys.exit(1)
except Exception as e:
logger.info(Exception)
logger.info("Unexpected Error - {}".format(e))
input("Press Enter To Exit")
sys.exit(1)