diff --git a/core/const.py b/core/const.py new file mode 100644 index 0000000..03f7f69 --- /dev/null +++ b/core/const.py @@ -0,0 +1,8 @@ +import logzero + +_log_format = ("%(color)s%(levelname)s:%(end_color)s %(message)s") +formatter = logzero.LogFormatter(fmt=_log_format) + +# options +log = logzero.setup_logger(formatter=formatter) +args = None diff --git a/core/convert.py b/core/convert.py index 95a4704..d93dbc1 100644 --- a/core/convert.py +++ b/core/convert.py @@ -1,6 +1,6 @@ import subprocess import os -from core.logger import log +from core.const import log """What are the differences and similarities between ffmpeg, libav, and avconv? @@ -18,58 +18,60 @@ https://trac.ffmpeg.org/wiki/Encode/AAC def song(input_song, output_song, folder, avconv=False): """ Do the audio format conversion. """ if not input_song == output_song: + convert = Converter(input_song, output_song, folder) log.info('Converting {0} to {1}'.format( input_song, output_song.split('.')[-1])) if avconv: - exit_code = convert_with_avconv(input_song, output_song, folder) + exit_code = convert.with_avconv() else: - exit_code = convert_with_ffmpeg(input_song, output_song, folder) + exit_code = convert.with_ffmpeg() return exit_code return 0 -def convert_with_avconv(input_song, output_song, folder): - """ Convert the audio file using avconv. """ - if log.level == 10: - level = 'debug' - else: - level = '0' +class Converter: + def __init__(self, input_song, output_song, folder): + self.input_song = input_song + self.output_song = output_song + self.folder = folder - command = ['avconv', '-loglevel', level, '-i', - os.path.join(folder, input_song), '-ab', '192k', - os.path.join(folder, output_song)] + def with_avconv(self): + if log.level == 10: + level = 'debug' + else: + level = '0' - log.debug(command) + command = ['avconv', '-loglevel', level, '-i', + os.path.join(self.folder, self.input_song), '-ab', '192k', + os.path.join(self.folder, self.output_song)] - return subprocess.call(command) + log.debug(command) + return subprocess.call(command) + def with_ffmpeg(self): + ffmpeg_pre = 'ffmpeg -y ' -def convert_with_ffmpeg(input_song, output_song, folder): - """ Convert the audio file using FFmpeg. """ - ffmpeg_pre = 'ffmpeg -y ' + if not log.level == 10: + ffmpeg_pre += '-hide_banner -nostats -v panic ' - if not log.level == 10: - ffmpeg_pre += '-hide_banner -nostats -v panic ' + input_ext = self.input_song.split('.')[-1] + output_ext = self.output_song.split('.')[-1] - input_ext = input_song.split('.')[-1] - output_ext = output_song.split('.')[-1] + if input_ext == 'm4a': + if output_ext == 'mp3': + ffmpeg_params = '-codec:v copy -codec:a libmp3lame -q:a 2 ' + elif output_ext == 'webm': + ffmpeg_params = '-c:a libopus -vbr on -b:a 192k -vn ' - if input_ext == 'm4a': - if output_ext == 'mp3': - ffmpeg_params = '-codec:v copy -codec:a libmp3lame -q:a 2 ' - elif output_ext == 'webm': - ffmpeg_params = '-c:a libopus -vbr on -b:a 192k -vn ' + elif input_ext == 'webm': + if output_ext == 'mp3': + ffmpeg_params = ' -ab 192k -ar 44100 -vn ' + elif output_ext == 'm4a': + ffmpeg_params = '-cutoff 20000 -c:a libfdk_aac -b:a 192k -vn ' - elif input_ext == 'webm': - if output_ext == 'mp3': - ffmpeg_params = ' -ab 192k -ar 44100 -vn ' - elif output_ext == 'm4a': - ffmpeg_params = '-cutoff 20000 -c:a libfdk_aac -b:a 192k -vn ' + command = '{0}-i {1} {2}{3}'.format( + ffmpeg_pre, os.path.join(self.folder, self.input_song), + ffmpeg_params, os.path.join(self.folder, self.output_song)).split(' ') - command = '{0}-i {1} {2}{3}'.format( - ffmpeg_pre, os.path.join(folder, input_song), - ffmpeg_params, os.path.join(folder, output_song)).split(' ') - - log.debug(command) - - return subprocess.call(command) + log.debug(command) + return subprocess.call(command) diff --git a/core/handle.py b/core/handle.py new file mode 100644 index 0000000..68e32cd --- /dev/null +++ b/core/handle.py @@ -0,0 +1,80 @@ +import logging +import argparse +import os +import sys + + +_LOG_LEVELS_STR = ['INFO', 'WARNING', 'ERROR', 'DEBUG'] + +def log_leveller(log_level_str): + loggin_levels = [logging.INFO, logging.WARNING, logging.ERROR, logging.DEBUG] + log_level_str_index = _LOG_LEVELS_STR.index(log_level_str) + loggin_level = loggin_levels[log_level_str_index] + return loggin_level + + +def get_arguments(to_group=True, raw_args=None): + parser = argparse.ArgumentParser( + description='Download and convert songs from Spotify, Youtube etc.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + if to_group: + group = parser.add_mutually_exclusive_group(required=True) + + group.add_argument( + '-s', '--song', help='download song by spotify link or name') + group.add_argument( + '-l', '--list', help='download songs from a file') + group.add_argument( + '-p', '--playlist', help='load songs from playlist URL into .txt') + group.add_argument( + '-b', '--album', help='load songs from album URL into .txt') + group.add_argument( + '-u', '--username', + help="load songs from user's playlist into .txt") + + parser.add_argument( + '-m', '--manual', default=False, + help='choose the song to download manually', action='store_true') + parser.add_argument( + '-nm', '--no-metadata', default=False, + help='do not embed metadata in songs', action='store_true') + parser.add_argument( + '-a', '--avconv', default=False, + help='Use avconv for conversion otherwise set defaults to ffmpeg', + action='store_true') + parser.add_argument( + '-f', '--folder', default=(os.path.join(sys.path[0], 'Music')), + help='path to folder where files will be stored in') + parser.add_argument( + '--overwrite', default='prompt', + help='change the overwrite policy', + choices={'prompt', 'force', 'skip'}) + parser.add_argument( + '-i', '--input-ext', default='.m4a', + help='prefered input format .m4a or .webm (Opus)') + parser.add_argument( + '-o', '--output-ext', default='.mp3', + help='prefered output extension .mp3 or .m4a (AAC)') + parser.add_argument( + '-dm', '--download-only-metadata', default=False, + help='download songs for which metadata is found', + action='store_true') + parser.add_argument( + '-d', '--dry-run', default=False, + help='Show only track title and YouTube URL', + action='store_true') + parser.add_argument( + '-mo', '--music-videos-only', default=False, + help='Search only for music on Youtube', + action='store_true') + parser.add_argument( + '-ll', '--log-level', default='INFO', + choices=_LOG_LEVELS_STR, + type=str.upper, + help='set log verbosity') + + parsed = parser.parse_args(raw_args) + parsed.log_level = log_leveller(parsed.log_level) + + return parsed diff --git a/core/internals.py b/core/internals.py index 41bc8e8..ca7f6ed 100755 --- a/core/internals.py +++ b/core/internals.py @@ -1,11 +1,7 @@ -import argparse -import spotipy.oauth2 as oauth2 -from urllib.request import quote from slugify import SLUG_OK, slugify +from core.const import log -import sys import os -from core.logger import log, log_leveller, _LOG_LEVELS_STR def input_link(links): @@ -24,78 +20,14 @@ def input_link(links): log.warning('Choose a valid number!') -def trim_song(file): +def trim_song(text_file): """ Remove the first song from file. """ - with open(file, 'r') as file_in: + with open(text_file, 'r') as file_in: data = file_in.read().splitlines(True) - with open(file, 'w') as file_out: + with open(text_file, 'w') as file_out: file_out.writelines(data[1:]) -def get_arguments(): - parser = argparse.ArgumentParser( - description='Download and convert songs from Spotify, Youtube etc.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - group = parser.add_mutually_exclusive_group(required=True) - - group.add_argument( - '-s', '--song', help='download song by spotify link or name') - group.add_argument( - '-l', '--list', help='download songs from a file') - group.add_argument( - '-p', '--playlist', help='load songs from playlist URL into .txt') - group.add_argument( - '-b', '--album', help='load songs from album URL into .txt') - group.add_argument( - '-u', '--username', - help="load songs from user's playlist into .txt") - parser.add_argument( - '-m', '--manual', default=False, - help='choose the song to download manually', action='store_true') - parser.add_argument( - '-nm', '--no-metadata', default=False, - help='do not embed metadata in songs', action='store_true') - parser.add_argument( - '-a', '--avconv', default=False, - help='Use avconv for conversion otherwise set defaults to ffmpeg', - action='store_true') - parser.add_argument( - '-f', '--folder', default=(os.path.join(sys.path[0], 'Music')), - help='path to folder where files will be stored in') - parser.add_argument( - '--overwrite', default='prompt', - help='change the overwrite policy', - choices={'prompt', 'force', 'skip'}) - parser.add_argument( - '-i', '--input-ext', default='.m4a', - help='prefered input format .m4a or .webm (Opus)') - parser.add_argument( - '-o', '--output-ext', default='.mp3', - help='prefered output extension .mp3 or .m4a (AAC)') - parser.add_argument( - '-d', '--dry-run', default=False, - help='Show only track title and YouTube URL', - action='store_true') - parser.add_argument( - '-mo', '--music-videos-only', default=False, - help='Search only for music on Youtube', - action='store_true') - parser.add_argument( - '-ll', '--log-level', default='INFO', - choices=_LOG_LEVELS_STR, - type=str.upper, - help='set log verbosity') - parser.add_argument( - '-dm', '--download-only-metadata', default=False, - help='download songs for which metadata is found', - action='store_true') - - parsed = parser.parse_args() - parsed.log_level = log_leveller(parsed.log_level) - - return parsed - - def is_spotify(raw_song): """ Check if the input song is a Spotify link. """ status = len(raw_song) == 22 and raw_song.replace(" ", "%20") == raw_song @@ -111,6 +43,12 @@ def is_youtube(raw_song): return status +def generate_songname(tags): + """ Generate a string of the format '[artist] - [song]' for the given spotify song. """ + raw_song = u'{0} - {1}'.format(tags['artists'][0]['name'], tags['name']) + return raw_song + + def sanitize_title(title): """ Generate filename of the song to be downloaded. """ title = title.replace(' ', '_') @@ -121,15 +59,6 @@ def sanitize_title(title): return title -def generate_token(): - """ Generate the token. Please respect these credentials :) """ - credentials = oauth2.SpotifyClientCredentials( - client_id='4fe3fecfe5334023a1472516cc99d805', - client_secret='0f02b7c483c04257984695007a4a8d5c') - token = credentials.get_access_token() - return token - - def filter_path(path): if not os.path.exists(path): os.makedirs(path) diff --git a/core/logger.py b/core/logger.py deleted file mode 100644 index 558e0a2..0000000 --- a/core/logger.py +++ /dev/null @@ -1,16 +0,0 @@ -import logzero -import logging - -_LOG_LEVELS_STR = ['INFO', 'WARNING', 'ERROR', 'DEBUG'] - -def log_leveller(log_level_str): - loggin_levels = [logging.INFO, logging.WARNING, logging.ERROR, logging.DEBUG] - log_level_str_index = _LOG_LEVELS_STR.index(log_level_str) - loggin_level = loggin_levels[log_level_str_index] - return loggin_level - - -log_format = ("%(color)s%(levelname)s:%(end_color)s %(message)s") -formatter = logzero.LogFormatter(fmt=log_format) -# create a default logger -log = logzero.setup_logger(formatter=formatter) diff --git a/core/metadata.py b/core/metadata.py index d8f02ce..ca295ec 100755 --- a/core/metadata.py +++ b/core/metadata.py @@ -1,151 +1,153 @@ from mutagen.easyid3 import EasyID3 from mutagen.id3 import ID3, TORY, TYER, TPUB, APIC, USLT, COMM from mutagen.mp4 import MP4, MP4Cover -from core.logger import log +from core.const import log import urllib.request def compare(music_file, metadata): """Check if the input music file title matches the expected title.""" - already_tagged = False try: if music_file.endswith('.mp3'): audiofile = EasyID3(music_file) - # fetch track title metadata already_tagged = audiofile['title'][0] == metadata['name'] elif music_file.endswith('.m4a'): - tags = {'title': '\xa9nam'} audiofile = MP4(music_file) - # fetch track title metadata already_tagged = audiofile[tags['title']] == metadata['name'] except (KeyError, TypeError): - pass + already_tagged = False + return already_tagged def embed(music_file, meta_tags): """ Embed metadata. """ - if meta_tags is None: - log.warning('Could not find metadata') - return None - elif music_file.endswith('.m4a'): + embed = EmbedMetadata(music_file, meta_tags) + if music_file.endswith('.m4a'): log.info('Applying metadata') - return embed_m4a(music_file, meta_tags) + return embed.as_m4a() elif music_file.endswith('.mp3'): log.info('Applying metadata') - return embed_mp3(music_file, meta_tags) + return embed.as_mp3() else: log.warning('Cannot embed metadata into given output extension') return False -def embed_mp3(music_file, meta_tags): - """ Embed metadata to MP3 files. """ - # EasyID3 is fun to use ;) - # For supported easyid3 tags: - # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py - # Check out somewhere at end of above linked file - audiofile = EasyID3(music_file) - audiofile['artist'] = meta_tags['artists'][0]['name'] - audiofile['albumartist'] = meta_tags['artists'][0]['name'] - audiofile['album'] = meta_tags['album']['name'] - audiofile['title'] = meta_tags['name'] - audiofile['tracknumber'] = [meta_tags['track_number'], - meta_tags['total_tracks']] - audiofile['discnumber'] = [meta_tags['disc_number'], 0] - audiofile['date'] = meta_tags['release_date'] - audiofile['originaldate'] = meta_tags['release_date'] - audiofile['media'] = meta_tags['type'] - audiofile['author'] = meta_tags['artists'][0]['name'] - audiofile['lyricist'] = meta_tags['artists'][0]['name'] - audiofile['arranger'] = meta_tags['artists'][0]['name'] - audiofile['performer'] = meta_tags['artists'][0]['name'] - audiofile['website'] = meta_tags['external_urls']['spotify'] - audiofile['length'] = str(meta_tags['duration_ms'] / 1000.0) +class EmbedMetadata: + def __init__(self, music_file, meta_tags): + self.music_file = music_file + self.meta_tags = meta_tags - if meta_tags['publisher']: - audiofile['encodedby'] = meta_tags['publisher'] - if meta_tags['genre']: - audiofile['genre'] = meta_tags['genre'] - if meta_tags['copyright']: - audiofile['copyright'] = meta_tags['copyright'] - if meta_tags['external_ids']['isrc']: - audiofile['isrc'] = meta_tags['external_ids']['isrc'] - audiofile.save(v2_version=3) + def as_mp3(self): + """ Embed metadata to MP3 files. """ + music_file = self.music_file + meta_tags = self.meta_tags + # EasyID3 is fun to use ;) + # For supported easyid3 tags: + # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py + # Check out somewhere at end of above linked file + audiofile = EasyID3(music_file) + audiofile['artist'] = meta_tags['artists'][0]['name'] + audiofile['albumartist'] = meta_tags['artists'][0]['name'] + audiofile['album'] = meta_tags['album']['name'] + audiofile['title'] = meta_tags['name'] + audiofile['tracknumber'] = [meta_tags['track_number'], + meta_tags['total_tracks']] + audiofile['discnumber'] = [meta_tags['disc_number'], 0] + audiofile['date'] = meta_tags['release_date'] + audiofile['originaldate'] = meta_tags['release_date'] + audiofile['media'] = meta_tags['type'] + audiofile['author'] = meta_tags['artists'][0]['name'] + audiofile['lyricist'] = meta_tags['artists'][0]['name'] + audiofile['arranger'] = meta_tags['artists'][0]['name'] + audiofile['performer'] = meta_tags['artists'][0]['name'] + audiofile['website'] = meta_tags['external_urls']['spotify'] + audiofile['length'] = str(meta_tags['duration_ms'] / 1000.0) + if meta_tags['publisher']: + audiofile['encodedby'] = meta_tags['publisher'] + if meta_tags['genre']: + audiofile['genre'] = meta_tags['genre'] + if meta_tags['copyright']: + audiofile['copyright'] = meta_tags['copyright'] + if meta_tags['external_ids']['isrc']: + audiofile['isrc'] = meta_tags['external_ids']['isrc'] + audiofile.save(v2_version=3) - # For supported id3 tags: - # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py - # Each class represents an id3 tag - audiofile = ID3(music_file) - print(meta_tags['release_date'].split('-')[0]) - year, *_ = meta_tags['release_date'].split('-') - audiofile['TORY'] = TORY(encoding=3, text=year) - audiofile['TYER'] = TYER(encoding=3, text=year) - audiofile['TPUB'] = TPUB(encoding=3, text=meta_tags['publisher']) - audiofile['COMM'] = COMM(encoding=3, text=meta_tags['external_urls']['spotify']) - if meta_tags['lyrics']: - audiofile['USLT'] = USLT(encoding=3, desc=u'Lyrics', text=meta_tags['lyrics']) - try: - albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url']) - audiofile['APIC'] = APIC(encoding=3, mime='image/jpeg', type=3, - desc=u'Cover', data=albumart.read()) - albumart.close() - except IndexError: - pass + # For supported id3 tags: + # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py + # Each class represents an id3 tag + audiofile = ID3(music_file) + year, *_ = meta_tags['release_date'].split('-') + audiofile['TORY'] = TORY(encoding=3, text=year) + audiofile['TYER'] = TYER(encoding=3, text=year) + audiofile['TPUB'] = TPUB(encoding=3, text=meta_tags['publisher']) + audiofile['COMM'] = COMM(encoding=3, text=meta_tags['external_urls']['spotify']) + if meta_tags['lyrics']: + audiofile['USLT'] = USLT(encoding=3, desc=u'Lyrics', text=meta_tags['lyrics']) + try: + albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url']) + audiofile['APIC'] = APIC(encoding=3, mime='image/jpeg', type=3, + desc=u'Cover', data=albumart.read()) + albumart.close() + except IndexError: + pass - audiofile.save(v2_version=3) - return True + audiofile.save(v2_version=3) + return True + def as_m4a(self): + """ Embed metadata to M4A files. """ + music_file = self.music_file + meta_tags = self.meta_tags + # Apple has specific tags - see mutagen docs - + # http://mutagen.readthedocs.io/en/latest/api/mp4.html + tags = { 'album' : '\xa9alb', + 'artist' : '\xa9ART', + 'date' : '\xa9day', + 'title' : '\xa9nam', + 'year' : '\xa9day', + 'originaldate' : 'purd', + 'comment' : '\xa9cmt', + 'group' : '\xa9grp', + 'writer' : '\xa9wrt', + 'genre' : '\xa9gen', + 'tracknumber' : 'trkn', + 'albumartist' : 'aART', + 'disknumber' : 'disk', + 'cpil' : 'cpil', + 'albumart' : 'covr', + 'copyright' : 'cprt', + 'tempo' : 'tmpo', + 'lyrics' : '\xa9lyr' } -def embed_m4a(music_file, meta_tags): - """ Embed metadata to M4A files. """ - # Apple has specific tags - see mutagen docs - - # http://mutagen.readthedocs.io/en/latest/api/mp4.html - tags = { 'album' : '\xa9alb', - 'artist' : '\xa9ART', - 'date' : '\xa9day', - 'title' : '\xa9nam', - 'year' : '\xa9day', - 'originaldate' : 'purd', - 'comment' : '\xa9cmt', - 'group' : '\xa9grp', - 'writer' : '\xa9wrt', - 'genre' : '\xa9gen', - 'tracknumber' : 'trkn', - 'albumartist' : 'aART', - 'disknumber' : 'disk', - 'cpil' : 'cpil', - 'albumart' : 'covr', - 'copyright' : 'cprt', - 'tempo' : 'tmpo', - 'lyrics' : '\xa9lyr' } + audiofile = MP4(music_file) + audiofile[tags['artist']] = meta_tags['artists'][0]['name'] + audiofile[tags['albumartist']] = meta_tags['artists'][0]['name'] + audiofile[tags['album']] = meta_tags['album']['name'] + audiofile[tags['title']] = meta_tags['name'] + audiofile[tags['tracknumber']] = [(meta_tags['track_number'], + meta_tags['total_tracks'])] + audiofile[tags['disknumber']] = [(meta_tags['disc_number'], 0)] + audiofile[tags['date']] = meta_tags['release_date'] + year, *_ = meta_tags['release_date'].split('-') + audiofile[tags['year']] = year + audiofile[tags['originaldate']] = meta_tags['release_date'] + audiofile[tags['comment']] = meta_tags['external_urls']['spotify'] + if meta_tags['genre']: + audiofile[tags['genre']] = meta_tags['genre'] + if meta_tags['copyright']: + audiofile[tags['copyright']] = meta_tags['copyright'] + if meta_tags['lyrics']: + audiofile[tags['lyrics']] = meta_tags['lyrics'] + try: + albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url']) + audiofile[tags['albumart']] = [MP4Cover( + albumart.read(), imageformat=MP4Cover.FORMAT_JPEG)] + albumart.close() + except IndexError: + pass - audiofile = MP4(music_file) - audiofile[tags['artist']] = meta_tags['artists'][0]['name'] - audiofile[tags['albumartist']] = meta_tags['artists'][0]['name'] - audiofile[tags['album']] = meta_tags['album']['name'] - audiofile[tags['title']] = meta_tags['name'] - audiofile[tags['tracknumber']] = [(meta_tags['track_number'], - meta_tags['total_tracks'])] - audiofile[tags['disknumber']] = [(meta_tags['disc_number'], 0)] - audiofile[tags['date']] = meta_tags['release_date'] - year, *_ = meta_tags['release_date'].split('-') - audiofile[tags['year']] = year - audiofile[tags['originaldate']] = meta_tags['release_date'] - audiofile[tags['comment']] = meta_tags['external_urls']['spotify'] - if meta_tags['genre']: - audiofile[tags['genre']] = meta_tags['genre'] - if meta_tags['copyright']: - audiofile[tags['copyright']] = meta_tags['copyright'] - if meta_tags['lyrics']: - audiofile[tags['lyrics']] = meta_tags['lyrics'] - try: - albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url']) - audiofile[tags['albumart']] = [MP4Cover( - albumart.read(), imageformat=MP4Cover.FORMAT_JPEG)] - albumart.close() - except IndexError: - pass - audiofile.save() - return True + audiofile.save() + return True diff --git a/core/spotify_tools.py b/core/spotify_tools.py new file mode 100644 index 0000000..22d9b92 --- /dev/null +++ b/core/spotify_tools.py @@ -0,0 +1,151 @@ +import spotipy +import spotipy.oauth2 as oauth2 +import lyricwikia +from titlecase import titlecase + +from core import internals +from core.const import log + +import pprint + + +def generate_token(): + """ Generate the token. Please respect these credentials :) """ + credentials = oauth2.SpotifyClientCredentials( + client_id='4fe3fecfe5334023a1472516cc99d805', + client_secret='0f02b7c483c04257984695007a4a8d5c') + token = credentials.get_access_token() + return token + +token = generate_token() +spotify = spotipy.Spotify(auth=token) + + +def generate_metadata(raw_song): + """ Fetch a song's metadata from Spotify. """ + if internals.is_spotify(raw_song): + # fetch track information directly if it is spotify link + log.debug('Fetching metadata for given track URL') + meta_tags = spotify.track(raw_song) + else: + # otherwise search on spotify and fetch information from first result + log.debug('Searching for "{}" on Spotify'.format(raw_song)) + try: + meta_tags = spotify.search(raw_song, limit=1)['tracks']['items'][0] + except IndexError: + return None + artist = spotify.artist(meta_tags['artists'][0]['id']) + album = spotify.album(meta_tags['album']['id']) + + try: + meta_tags[u'genre'] = titlecase(artist['genres'][0]) + except IndexError: + meta_tags[u'genre'] = None + try: + meta_tags[u'copyright'] = album['copyrights'][0]['text'] + except IndexError: + meta_tags[u'copyright'] = None + try: + meta_tags[u'external_ids'][u'isrc'] + except KeyError: + meta_tags[u'external_ids'][u'isrc'] = None + + meta_tags[u'release_date'] = album['release_date'] + meta_tags[u'publisher'] = album['label'] + meta_tags[u'total_tracks'] = album['tracks']['total'] + + log.debug('Fetching lyrics') + + try: + meta_tags['lyrics'] = lyricwikia.get_lyrics( + meta_tags['artists'][0]['name'], + meta_tags['name']) + except lyricwikia.LyricsNotFound: + meta_tags['lyrics'] = None + + # remove unused clutter when debug meta_tags + del meta_tags['available_markets'] + del meta_tags['album']['available_markets'] + + log.debug(pprint.pformat(meta_tags)) + return meta_tags + + +def feed_playlist(username): + """ Fetch user playlists when using the -u option. """ + playlists = spotify.user_playlists(username) + links = [] + check = 1 + + while True: + for playlist in playlists['items']: + # in rare cases, playlists may not be found, so playlists['next'] + # is None. Skip these. Also see Issue #91. + if playlist['name'] is not None: + log.info(u'{0:>5}. {1:<30} ({2} tracks)'.format( + check, playlist['name'], + playlist['tracks']['total'])) + log.debug(playlist['external_urls']['spotify']) + links.append(playlist) + check += 1 + if playlists['next']: + playlists = spotify.next(playlists) + else: + break + + playlist = internals.input_link(links) + write_playlist(playlist['owner']['id'], playlist['id']) + + +def write_tracks(text_file, tracks): + with open(text_file, 'a') as file_out: + while True: + for item in tracks['items']: + if 'track' in item: + track = item['track'] + else: + track = item + try: + track_url = track['external_urls']['spotify'] + file_out.write(track_url + '\n') + log.debug(track_url) + except KeyError: + log.warning(u'Skipping track {0} by {1} (local only?)'.format( + track['name'], track['artists'][0]['name'])) + # 1 page = 50 results + # check if there are more pages + if tracks['next']: + tracks = spotify.next(tracks) + else: + break + +def write_playlist(username, playlist_id): + results = spotify.user_playlist(username, playlist_id, + fields='tracks,next,name') + text_file = u'{0}.txt'.format(slugify(results['name'], ok='-_()[]{}')) + log.info(u'Writing {0} tracks to {1}'.format( + results['tracks']['total'], text_file)) + tracks = results['tracks'] + write_tracks(text_file, tracks) + + +def write_album(album): + tracks = spotify.album_tracks(album['id']) + text_file = u'{0}.txt'.format(slugify(album['name'], ok='-_()[]{}')) + log.info(u'writing {0} tracks to {1}'.format( + tracks['total'], text_file)) + write_tracks(text_file, tracks) + + +def grab_album(album): + if '/' in album: + if album.endswith('/'): + playlist = playlist[:-1] + splits = album.split('/') + else: + splits = album.split(':') + + album_id = splits[-1] + album = spotify.album(album_id) + + write_album(album) diff --git a/core/youtube_tools.py b/core/youtube_tools.py new file mode 100644 index 0000000..9651ab3 --- /dev/null +++ b/core/youtube_tools.py @@ -0,0 +1,139 @@ +import pafy + +from core import internals +from core import const + +import os +import pprint + +log = const.log + + +def go_pafy(raw_song, meta_tags=None): + """ Parse track from YouTube. """ + if internals.is_youtube(raw_song): + track_info = pafy.new(raw_song) + else: + track_url = generate_youtube_url(raw_song, meta_tags) + + if track_url: + track_info = pafy.new(track_url) + else: + track_info = None + + return track_info + + +def get_youtube_title(content, number=None): + """ Get the YouTube video's title. """ + title = content.title + if number: + return '{0}. {1}'.format(number, title) + else: + return title + + +def download_song(file_name, content): + """ Download the audio file from YouTube. """ + if const.args.input_ext in (".webm", ".m4a"): + link = content.getbestaudio(preftype=const.args.input_ext[1:]) + else: + return False + + if link: + log.debug('Downloading from URL: ' + link.url) + filepath = '{0}{1}'.format(os.path.join(const.args.folder, file_name), + const.args.input_ext) + log.debug('Saving to: ' + filepath) + link.download(filepath=filepath) + return True + else: + return False + + +def generate_youtube_url(raw_song, meta_tags, tries_remaining=5): + """ Search for the song on YouTube and generate a URL to its video. """ + # prevents an infinite loop but allows for a few retries + if tries_remaining == 0: + log.debug('No tries left. I quit.') + return + + query = { 'part' : 'snippet', + 'maxResults' : 50, + 'type' : 'video' } + + if const.args.music_videos_only: + query['videoCategoryId'] = '10' + + if not meta_tags: + song = raw_song + query['q'] = song + else: + song = internals.generate_songname(meta_tags) + query['q'] = song + log.debug('query: {0}'.format(query)) + + data = pafy.call_gdata('search', query) + query_results = {'part': 'contentDetails,snippet,statistics', + 'maxResults': 50, + 'id': ','.join(i['id']['videoId'] for i in data['items'])} + log.debug('query_results: {0}'.format(query_results)) + + vdata = pafy.call_gdata('videos', query_results) + + videos = [] + for x in vdata['items']: + duration_s = pafy.playlist.parseISO8591(x['contentDetails']['duration']) + youtubedetails = {'link': x['id'], 'title': x['snippet']['title'], + 'videotime':internals.videotime_from_seconds(duration_s), + 'seconds': duration_s} + videos.append(youtubedetails) + if not meta_tags: + break + + if not videos: + return None + + if const.args.manual: + log.info(song) + log.info('0. Skip downloading this song.\n') + # fetch all video links on first page on YouTube + for i, v in enumerate(videos): + log.info(u'{0}. {1} {2} {3}'.format(i+1, v['title'], v['videotime'], + "http://youtube.com/watch?v="+v['link'])) + # let user select the song to download + result = internals.input_link(videos) + if not result: + return None + else: + if not meta_tags: + # if the metadata could not be acquired, take the first result + # from Youtube because the proper song length is unknown + result = videos[0] + log.debug('Since no metadata found on Spotify, going with the first result') + else: + # filter out videos that do not have a similar length to the Spotify song + duration_tolerance = 10 + max_duration_tolerance = 20 + possible_videos_by_duration = list() + + ''' + start with a reasonable duration_tolerance, and increment duration_tolerance + until one of the Youtube results falls within the correct duration or + the duration_tolerance has reached the max_duration_tolerance + ''' + while len(possible_videos_by_duration) == 0: + possible_videos_by_duration = list(filter(lambda x: abs(x['seconds'] - (int(meta_tags['duration_ms'])/1000)) <= duration_tolerance, videos)) + duration_tolerance += 1 + if duration_tolerance > max_duration_tolerance: + log.error("{0} by {1} was not found.\n".format(meta_tags['name'], meta_tags['artists'][0]['name'])) + return None + + result = possible_videos_by_duration[0] + + if result: + url = "http://youtube.com/watch?v=" + result['link'] + else: + url = None + + return url diff --git a/spotdl.py b/spotdl.py index f678805..d3252f6 100755 --- a/spotdl.py +++ b/spotdl.py @@ -1,303 +1,31 @@ #!/usr/bin/env python3 # -*- coding: UTF-8 -*- -from core import logger +from core import const +from core import handle from core import metadata from core import convert from core import internals -from titlecase import titlecase +from core import spotify_tools +from core import youtube_tools from slugify import slugify import spotipy -import pafy -import lyricwikia import urllib.request import os import sys import time -import sys import platform import pprint -def generate_songname(tags): - """ Generate a string of the format '[artist] - [song]' for the given spotify song. """ - raw_song = u'{0} - {1}'.format(tags['artists'][0]['name'], tags['name']) - return raw_song - - -def generate_metadata(raw_song): - """ Fetch a song's metadata from Spotify. """ - if internals.is_spotify(raw_song): - # fetch track information directly if it is spotify link - log.debug('Fetching metadata for given track URL') - meta_tags = spotify.track(raw_song) - else: - # otherwise search on spotify and fetch information from first result - log.debug('Searching for "{}" on Spotify'.format(raw_song)) - try: - meta_tags = spotify.search(raw_song, limit=1)['tracks']['items'][0] - except IndexError: - return None - artist = spotify.artist(meta_tags['artists'][0]['id']) - album = spotify.album(meta_tags['album']['id']) - - try: - meta_tags[u'genre'] = titlecase(artist['genres'][0]) - except IndexError: - meta_tags[u'genre'] = None - try: - meta_tags[u'copyright'] = album['copyrights'][0]['text'] - except IndexError: - meta_tags[u'copyright'] = None - try: - meta_tags[u'external_ids'][u'isrc'] - except KeyError: - meta_tags[u'external_ids'][u'isrc'] = None - - meta_tags[u'release_date'] = album['release_date'] - meta_tags[u'publisher'] = album['label'] - meta_tags[u'total_tracks'] = album['tracks']['total'] - - log.debug('Fetching lyrics') - - try: - meta_tags['lyrics'] = lyricwikia.get_lyrics( - meta_tags['artists'][0]['name'], - meta_tags['name']) - except lyricwikia.LyricsNotFound: - meta_tags['lyrics'] = None - - # remove unused clutter when debug meta_tags - del meta_tags['available_markets'] - del meta_tags['album']['available_markets'] - - log.debug(pprint.pformat(meta_tags)) - return meta_tags - - -def is_video(result): - # ensure result is not a channel - not_video = result.find('channel') is not None or \ - 'yt-lockup-channel' in result.parent.attrs['class'] or \ - 'yt-lockup-channel' in result.attrs['class'] - - # ensure result is not a mix/playlist - not_video = not_video or \ - 'yt-lockup-playlist' in result.parent.attrs['class'] - - # ensure video result is not an advertisement - not_video = not_video or \ - result.find('googleads') is not None - - video = not not_video - return video - - -def generate_youtube_url(raw_song, meta_tags, tries_remaining=5): - """ Search for the song on YouTube and generate a URL to its video. """ - # prevents an infinite loop but allows for a few retries - if tries_remaining == 0: - log.debug('No tries left. I quit.') - return - - query = {'part': 'snippet', - 'maxResults': 50, - 'type': 'video'} - - if args.music_videos_only: - query['videoCategoryId'] = '10' - - if meta_tags is None: - song = raw_song - query['q'] = song - else: - song = generate_songname(meta_tags) - query['q'] = song - log.debug('query: {0}'.format(query)) - - data = pafy.call_gdata('search', query) - query_results = {'part': 'contentDetails,snippet,statistics', - 'maxResults': 50, - 'id': ','.join(i['id']['videoId'] for i in data['items'])} - log.debug('query_results: {0}'.format(query_results)) - - vdata = pafy.call_gdata('videos', query_results) - - videos = [] - for x in vdata['items']: - duration_s = pafy.playlist.parseISO8591(x['contentDetails']['duration']) - youtubedetails = {'link': x['id'], 'title': x['snippet']['title'], - 'videotime':internals.videotime_from_seconds(duration_s), - 'seconds': duration_s} - videos.append(youtubedetails) - if meta_tags is None: - break - - if not videos: - return None - - if args.manual: - log.info(song) - log.info('0. Skip downloading this song.\n') - # fetch all video links on first page on YouTube - for i, v in enumerate(videos): - log.info(u'{0}. {1} {2} {3}'.format(i+1, v['title'], v['videotime'], - "http://youtube.com/watch?v="+v['link'])) - # let user select the song to download - result = internals.input_link(videos) - if result is None: - return None - else: - if meta_tags is None: - # if the metadata could not be acquired, take the first result - # from Youtube because the proper song length is unknown - result = videos[0] - log.debug('Since no metadata found on Spotify, going with the first result') - else: - # filter out videos that do not have a similar length to the Spotify song - duration_tolerance = 10 - max_duration_tolerance = 20 - possible_videos_by_duration = list() - - ''' - start with a reasonable duration_tolerance, and increment duration_tolerance - until one of the Youtube results falls within the correct duration or - the duration_tolerance has reached the max_duration_tolerance - ''' - while len(possible_videos_by_duration) == 0: - possible_videos_by_duration = list(filter(lambda x: abs(x['seconds'] - (int(meta_tags['duration_ms'])/1000)) <= duration_tolerance, videos)) - duration_tolerance += 1 - if duration_tolerance > max_duration_tolerance: - log.error("{0} by {1} was not found.\n".format(meta_tags['name'],meta_tags['artists'][0]['name'])) - return None - - result = possible_videos_by_duration[0] - - if result: - url = "http://youtube.com/watch?v=" + result['link'] - else: - url = None - - return url - - -def go_pafy(raw_song, meta_tags=None): - """ Parse track from YouTube. """ - if internals.is_youtube(raw_song): - track_info = pafy.new(raw_song) - else: - track_url = generate_youtube_url(raw_song, meta_tags) - - if track_url is None: - track_info = None - else: - track_info = pafy.new(track_url) - - return track_info - - -def get_youtube_title(content, number=None): - """ Get the YouTube video's title. """ - title = content.title - if number is None: - return title - else: - return '{0}. {1}'.format(number, title) - - -def feed_playlist(username): - """ Fetch user playlists when using the -u option. """ - playlists = spotify.user_playlists(username) - links = [] - check = 1 - - while True: - for playlist in playlists['items']: - # in rare cases, playlists may not be found, so playlists['next'] - # is None. Skip these. Also see Issue #91. - if playlist['name'] is not None: - log.info(u'{0:>5}. {1:<30} ({2} tracks)'.format( - check, playlist['name'], - playlist['tracks']['total'])) - log.debug(playlist['external_urls']['spotify']) - links.append(playlist) - check += 1 - if playlists['next']: - playlists = spotify.next(playlists) - else: - break - - playlist = internals.input_link(links) - write_playlist(playlist['owner']['id'], playlist['id']) - - -def write_tracks(text_file, tracks): - with open(text_file, 'a') as file_out: - while True: - for item in tracks['items']: - if 'track' in item: - track = item['track'] - else: - track = item - try: - track_url = track['external_urls']['spotify'] - file_out.write(track_url + '\n') - log.debug(track_url) - except KeyError: - log.warning(u'Skipping track {0} by {1} (local only?)'.format( - track['name'], track['artists'][0]['name'])) - # 1 page = 50 results - # check if there are more pages - if tracks['next']: - tracks = spotify.next(tracks) - else: - break - - -def write_playlist(username, playlist_id): - results = spotify.user_playlist(username, playlist_id, - fields='tracks,next,name') - text_file = u'{0}.txt'.format(slugify(results['name'], ok='-_()[]{}')) - log.info(u'Writing {0} tracks to {1}'.format( - results['tracks']['total'], text_file)) - tracks = results['tracks'] - write_tracks(text_file, tracks) - - -def write_album(album): - tracks = spotify.album_tracks(album['id']) - text_file = u'{0}.txt'.format(slugify(album['name'], ok='-_()[]{}')) - log.info(u'writing {0} tracks to {1}'.format( - tracks['total'], text_file)) - write_tracks(text_file, tracks) - - -def download_song(file_name, content): - """ Download the audio file from YouTube. """ - if args.input_ext in (".webm", ".m4a"): - link = content.getbestaudio(preftype=args.input_ext[1:]) - else: - return False - - if link is None: - return False - else: - log.debug('Downloading from URL: ' + link.url) - filepath = '{0}{1}'.format(os.path.join(args.folder, file_name), - args.input_ext) - log.debug('Saving to: ' + filepath) - link.download(filepath=filepath) - return True - - def check_exists(music_file, raw_song, meta_tags): """ Check if the input song already exists in the given folder. """ log.debug('Cleaning any temp files and checking ' 'if "{}" already exists'.format(music_file)) - songs = os.listdir(args.folder) + songs = os.listdir(const.args.folder) for song in songs: if song.endswith('.temp'): - os.remove(os.path.join(args.folder, song)) + os.remove(os.path.join(const.args.folder, song)) continue # check if any song with similar name is already present in the given folder file_name = internals.sanitize_title(music_file) @@ -306,29 +34,29 @@ def check_exists(music_file, raw_song, meta_tags): if internals.is_spotify(raw_song): # check if the already downloaded song has correct metadata # if not, remove it and download again without prompt - already_tagged = metadata.compare(os.path.join(args.folder, song), + already_tagged = metadata.compare(os.path.join(const.args.folder, song), meta_tags) log.debug('Checking if it is already tagged correctly? {}', already_tagged) if not already_tagged: - os.remove(os.path.join(args.folder, song)) + os.remove(os.path.join(const.args.folder, song)) return False log.warning('"{}" already exists'.format(song)) - if args.overwrite == 'prompt': + if const.args.overwrite == 'prompt': log.info('"{}" has already been downloaded. ' 'Re-download? (y/N): '.format(song)) prompt = input('> ') if prompt.lower() == 'y': - os.remove(os.path.join(args.folder, song)) + os.remove(os.path.join(const.args.folder, song)) return False else: return True - elif args.overwrite == 'force': - os.remove(os.path.join(args.folder, song)) + elif const.args.overwrite == 'force': + os.remove(os.path.join(const.args.folder, song)) log.info('Overwriting "{}"'.format(song)) return False - elif args.overwrite == 'skip': + elif const.args.overwrite == 'skip': log.info('Skipping "{}"'.format(song)) return True return False @@ -354,9 +82,8 @@ def grab_list(text_file): except spotipy.client.SpotifyException: # refresh token when it expires log.debug('Token expired, generating new one and authorizing') - new_token = internals.generate_token() - global spotify - spotify = spotipy.Spotify(auth=new_token) + new_token = spotify_tools.generate_token() + spotify_tools.spotify = spotipy.Spotify(auth=new_token) grab_single(raw_song, number=number) # detect network problems except (urllib.request.URLError, TypeError, IOError): @@ -392,37 +119,28 @@ def grab_playlist(playlist): sys.exit(10) playlist_id = splits[-1] try: - write_playlist(username, playlist_id) + spotify_tools.write_playlist(username, playlist_id) except spotipy.client.SpotifyException: log.error('Unable to find playlist') log.info('Make sure the playlist is set to publicly visible and then try again') sys.exit(11) -def grab_album(album): - if '/' in album: - if album.endswith('/'): - playlist = playlist[:-1] - splits = album.split('/') - else: - splits = album.split(':') - - album_id = splits[-1] - album = spotify.album(album_id) - - write_album(album) - - def grab_single(raw_song, number=None): """ Logic behind downloading a song. """ if internals.is_youtube(raw_song): log.debug('Input song is a YouTube URL') - content = go_pafy(raw_song, meta_tags=None) + content = youtube_tools.go_pafy(raw_song, meta_tags=None) raw_song = slugify(content.title).replace('-', ' ') - meta_tags = generate_metadata(raw_song) + meta_tags = spotify_tools.generate_metadata(raw_song) else: - meta_tags = generate_metadata(raw_song) - content = go_pafy(raw_song, meta_tags) + meta_tags = spotify_tools.generate_metadata(raw_song) + content = youtube_tools.go_pafy(raw_song, meta_tags) + + if const.args.download_only_metadata: + if meta_tags is None: + log.info('Found No metadata. Skipping the download') + return if args.download_only_metadata: if meta_tags is None: @@ -435,41 +153,45 @@ def grab_single(raw_song, number=None): # "[number]. [artist] - [song]" if downloading from list # otherwise "[artist] - [song]" - youtube_title = get_youtube_title(content, number) + youtube_title = youtube_tools.get_youtube_title(content, number) log.info('{} ({})'.format(youtube_title, content.watchv_url)) # generate file name of the song to download songname = content.title - if meta_tags is not None: - refined_songname = generate_songname(meta_tags) + if meta_tags: + refined_songname = internals.generate_songname(meta_tags) log.debug('Refining songname from "{0}" to "{1}"'.format(songname, refined_songname)) if not refined_songname == ' - ': songname = refined_songname - if args.dry_run: + if const.args.dry_run: return file_name = internals.sanitize_title(songname) if not check_exists(file_name, raw_song, meta_tags): - if download_song(file_name, content): - input_song = file_name + args.input_ext - output_song = file_name + args.output_ext + if youtube_tools.download_song(file_name, content): + input_song = file_name + const.args.input_ext + output_song = file_name + const.args.output_ext print('') try: - convert.song(input_song, output_song, args.folder, - avconv=args.avconv) + convert.song(input_song, output_song, const.args.folder, + avconv=const.args.avconv) except FileNotFoundError: - encoder = 'avconv' if args.avconv else 'ffmpeg' + encoder = 'avconv' if const.args.avconv else 'ffmpeg' log.warning('Could not find {0}, skipping conversion'.format(encoder)) - args.output_ext = args.input_ext - output_song = file_name + args.output_ext + const.args.output_ext = const.args.input_ext + output_song = file_name + const.args.output_ext - if not args.input_ext == args.output_ext: - os.remove(os.path.join(args.folder, input_song)) - if not args.no_metadata: - metadata.embed(os.path.join(args.folder, output_song), meta_tags) + if not const.args.input_ext == const.args.output_ext: + os.remove(os.path.join(const.args.folder, input_song)) + + if not const.args.no_metadata: + if metadata: + metadata.embed(os.path.join(const.args.folder, output_song), meta_tags) + else: + log.warning('Could not find metadata') else: log.error('No audio streams available') @@ -477,31 +199,31 @@ def grab_single(raw_song, number=None): # token is mandatory when using Spotify's API # https://developer.spotify.com/news-stories/2017/01/27/removing-unauthenticated-calls-to-the-web-api/ -token = internals.generate_token() +token = spotify_tools.generate_token() spotify = spotipy.Spotify(auth=token) if __name__ == '__main__': - args = internals.get_arguments() - internals.filter_path(args.folder) + const.args = handle.get_arguments() + internals.filter_path(const.args.folder) - logger.log = logger.logzero.setup_logger(formatter=logger.formatter, - level=args.log_level) - log = logger.log + const.log = const.logzero.setup_logger(formatter=const.formatter, + level=const.args.log_level) + log = const.log log.debug('Python version: {}'.format(sys.version)) log.debug('Platform: {}'.format(platform.platform())) - log.debug(pprint.pformat(args.__dict__)) + log.debug(pprint.pformat(const.args.__dict__)) try: - if args.song: - grab_single(raw_song=args.song) - elif args.list: - grab_list(text_file=args.list) - elif args.playlist: - grab_playlist(playlist=args.playlist) - elif args.album: - grab_album(album=args.album) - elif args.username: - feed_playlist(username=args.username) + if const.args.song: + grab_single(raw_song=const.args.song) + elif const.args.list: + grab_list(text_file=const.args.list) + elif const.args.playlist: + grab_playlist(playlist=const.args.playlist) + elif const.args.album: + spotify_tools.grab_album(album=const.args.album) + elif const.args.username: + spotify_tools.feed_playlist(username=const.args.username) # actually we don't necessarily need this, but yeah... # explicit is better than implicit! diff --git a/test/test_simple.py b/test/test_simple.py index 4deb0aa..6077b93 100644 --- a/test/test_simple.py +++ b/test/test_simple.py @@ -1,31 +1,26 @@ # -*- coding: UTF-8 -*- -from spotdl import logger +from spotdl import const +from spotdl import handle import spotdl + import os +const.args = handle.get_arguments(to_group=False, raw_args='') +const.args.folder = 'test' +const.args.overwrite = 'skip' +const.args.log_level = handle.logging.DEBUG + +spotdl.args = const.args +spotdl.log = const.logzero.setup_logger(formatter=const.formatter, + level=const.args.log_level) + + raw_song = "Tony's Videos VERY SHORT VIDEO 28.10.2016" - -class TestArgs: - manual = False - input_ext = '.m4a' - output_ext = '.mp3' - folder = 'test' - log_level = logger.logging.DEBUG - overwrite = 'skip' - music_videos_only = False - -test_args = TestArgs() -setattr(spotdl, "args", test_args) - -spotdl.log = logger.logzero.setup_logger(formatter=logger.formatter, - level=spotdl.args.log_level) - - def test_youtube_url(): expect_url = 'http://youtube.com/watch?v=qOOcy2-tmbk' - url = spotdl.generate_youtube_url(raw_song, meta_tags=None) + url = spotdl.youtube_tools.generate_youtube_url(raw_song, meta_tags=None) assert url == expect_url @@ -33,8 +28,8 @@ def test_youtube_title(): global content global title expect_title = "Tony's Videos VERY SHORT VIDEO 28.10.2016" - content = spotdl.go_pafy(raw_song, meta_tags=None) - title = spotdl.get_youtube_title(content) + content = spotdl.youtube_tools.go_pafy(raw_song, meta_tags=None) + title = spotdl.youtube_tools.get_youtube_title(content) assert title == expect_title def test_check_exists(): @@ -49,7 +44,7 @@ def test_download(): expect_download = True # prerequisites for determining filename file_name = spotdl.internals.sanitize_title(title) - download = spotdl.download_song(file_name, content) + download = spotdl.youtube_tools.download_song(file_name, content) assert download == expect_download @@ -60,19 +55,23 @@ def test_convert(): file_name = spotdl.internals.sanitize_title(title) global input_song global output_song - input_song = file_name + spotdl.args.input_ext - output_song = file_name + spotdl.args.output_ext - convert = spotdl.convert.song(input_song, output_song, spotdl.args.folder) + input_song = file_name + const.args.input_ext + output_song = file_name + const.args.output_ext + convert = spotdl.convert.song(input_song, output_song, const.args.folder) assert convert == expect_convert def test_metadata(): expect_metadata = None # prerequisites for determining filename - meta_tags = spotdl.generate_metadata(raw_song) + meta_tags = spotdl.spotify_tools.generate_metadata(raw_song) file_name = spotdl.internals.sanitize_title(title) - metadata_output = spotdl.metadata.embed(os.path.join(spotdl.args.folder, output_song), meta_tags) - metadata_input = spotdl.metadata.embed(os.path.join(spotdl.args.folder, input_song), meta_tags) + if meta_tags: + metadata_output = spotdl.metadata.embed(os.path.join(const.args.folder, output_song), meta_tags) + metadata_input = spotdl.metadata.embed(os.path.join(const.args.folder, input_song), meta_tags) + else: + metadata_input = None + metadata_output = None assert (metadata_output == expect_metadata) and (metadata_input == expect_metadata) @@ -80,7 +79,7 @@ def test_check_exists2(): expect_check = True # prerequisites for determining filename file_name = spotdl.internals.sanitize_title(title) - os.remove(os.path.join(spotdl.args.folder, input_song)) + os.remove(os.path.join(const.args.folder, input_song)) check = spotdl.check_exists(file_name, raw_song, meta_tags=None) - os.remove(os.path.join(spotdl.args.folder, output_song)) + os.remove(os.path.join(const.args.folder, output_song)) assert check == expect_check diff --git a/test/test_spotify.py b/test/test_spotify.py index 73cecf9..175197e 100644 --- a/test/test_spotify.py +++ b/test/test_spotify.py @@ -1,54 +1,48 @@ # -*- coding: UTF-8 -*- -from spotdl import logger +from spotdl import const +from spotdl import handle import spotdl + import os +const.args = handle.get_arguments(to_group=False, raw_args='') +const.args.folder = 'test' +const.args.overwrite = 'skip' +const.args.log_level = handle.logging.DEBUG + +spotdl.args = const.args +spotdl.log = const.logzero.setup_logger(formatter=const.formatter, + level=const.args.log_level) + + raw_song = 'http://open.spotify.com/track/0JlS7BXXD07hRmevDnbPDU' - -class TestArgs: - manual = False - input_ext = '.m4a' - output_ext = '.mp3' - folder = 'test' - log_level = 'DEBUG' - overwrite = 'skip' - music_videos_only = False - -test_args = TestArgs() -setattr(spotdl, "args", test_args) - -spotdl.log = logger.logzero.setup_logger(formatter=logger.formatter, - level=spotdl.args.log_level) -spotdl.internals.filter_path(spotdl.args.folder) - - def test_spotify_title(): expect_title = 'David André Østby - Intro' global meta_tags - meta_tags = spotdl.generate_metadata(raw_song) - title = spotdl.generate_songname(meta_tags) + meta_tags = spotdl.spotify_tools.generate_metadata(raw_song) + title = spotdl.internals.generate_songname(meta_tags) assert title == expect_title -def youtube_url(): - expect_url = 'youtube.com/watch?v=rg1wfcty0BA' - url = spotdl.generate_youtube_url(raw_song, meta_tags) +def test_youtube_url(): + expect_url = 'http://youtube.com/watch?v=rg1wfcty0BA' + url = spotdl.youtube_tools.generate_youtube_url(raw_song, meta_tags) assert url == expect_url -def youtube_title(): +def test_youtube_title(): expect_title = 'Intro - David André Østby' - content = spotdl.go_pafy(raw_song, meta_tags) - title = spotdl.get_youtube_title(content) + content = spotdl.youtube_tools.go_pafy(raw_song, meta_tags) + title = spotdl.youtube_tools.get_youtube_title(content) assert title == expect_title def test_check_exists(): expect_check = False # prerequisites for determining filename - songname = spotdl.generate_songname(meta_tags) + songname = spotdl.internals.generate_songname(meta_tags) global file_name file_name = spotdl.internals.sanitize_title(songname) check = spotdl.check_exists(file_name, raw_song, meta_tags) @@ -58,8 +52,8 @@ def test_check_exists(): def test_download(): expect_download = True # prerequisites for determining filename - content = spotdl.go_pafy(raw_song, meta_tags) - download = spotdl.download_song(file_name, content) + content = spotdl.youtube_tools.go_pafy(raw_song, meta_tags) + download = spotdl.youtube_tools.download_song(file_name, content) assert download == expect_download