def write_tracks(tracks, text_file): log.info(u'Writing {0} tracks to {1}'.format(tracks['total'], text_file)) track_urls = [] 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'] log.debug(track_url) file_out.write(track_url + '\n') track_urls.append(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 return track_urls
def stop(self): log.info("Stopping PlayListDownloader") if self.future: return self.future.cancel() else: return False
def _download_songs(folder, songs, progress_func): log.info(u'Preparing to download {} songs'.format(len(songs))) downloaded_songs = [] for number, raw_song in enumerate(songs): try: progress_func({"current": number, "total": len(songs)}) _download_single(folder, raw_song, number=number) # token expires after 1 hour except spotipy.client.SpotifyException: # refresh token when it expires log.debug('Token expired, generating new one and authorizing') spotify_tools.init() _download_single(folder, raw_song, number=number) # detect network problems except (urllib.request.URLError, TypeError, IOError): songs.append(raw_song) log.warning( 'Failed to download song. Will retry after other songs\n', exc_info=True) # wait 0.5 sec to avoid infinite looping time.sleep(0.5) continue downloaded_songs.append(raw_song) return downloaded_songs
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 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() else: exit_code = convert.with_ffmpeg() return exit_code return 0
def embed(music_file, meta_tags): """ Embed metadata. """ embed = EmbedMetadata(music_file, meta_tags) if music_file.endswith('.m4a'): log.info('Applying metadata') return embed.as_m4a() elif music_file.endswith('.mp3'): log.info('Applying metadata') return embed.as_mp3() else: log.warning('Cannot embed metadata into given output extension') return False
def get_settings(): log.info("Retrieving settings") try: settings = { 'settings': config.get_config_dict() } return jsonify(settings) except Exception: log.error("Error Retrieving settings", exc_info=True) abort(500, 'Error Retrieving settings')
def input_link(links): """ Let the user input a choice. """ while True: try: log.info('Choose your number:') the_chosen_one = int(input('> ')) if 1 <= the_chosen_one <= len(links): return links[the_chosen_one - 1] elif the_chosen_one == 0: return None else: log.warning('Choose a valid number!') except ValueError: log.warning('Choose a valid number!')
def put_settings(): if not request.json or 'settings' not in request.json: abort(400, 'Error Saving settings: settings obligatory') log.info("Saving settings") try: config.save_config(request.json['settings']) config.init_config() return jsonify({'status': 'OK'}) except Exception: log.error("Error Saving settings", exc_info=True) abort(500, 'Error Saving settings')
def info(): if not request.json or 'url' not in request.json: abort(400, 'Error getting playlist info: url obligatory') try: url = request.json['url'] log.info("Get playlist info[%s]", url) info = spotdl.fetch_info(url) return jsonify(info) except Exception: log.error("Error getting playlist info", exc_info=True) abort(500, 'Error getting playlist info')
def run(self): log.info("Init Downloader") try: self.init_date = time.localtime() self.download() self.end_date = time.localtime() log.info("Finished Downloader") except Exception as e: log.error("Error in Downloader", exc_info=True) self.end_date = time.localtime() raise Exception("Error in Downloader")
def search(): if not request.json or 'query' not in request.json: abort(400, 'Error searching: query obligatory') try: query = request.json['query'] log.info("Searching[%s]", query) items = spotdl.search(query, max_results_per_type=int( const.config.search_max_results)) return jsonify(items) except Exception: log.error("Error searching", exc_info=True) abort(500, 'Error searching')
def youtube(): if 'url' not in request.args: abort(400, 'Error getting playlist redirecting to youtube: url obligatory') try: url = request.args['url'] log.info("Redirecting to youtube[%s]", url) yt_url = spotdl.fetch_yt_url(url) return redirect(yt_url, code=302) except Exception: log.error("Error redirecting to youtube", exc_info=True) abort(500, 'Error redirecting to youtube')
def fetch_playlist(playlist): splits = internals.get_splits(playlist) try: username = splits[-3] except IndexError: # Wrong format, in either case log.error('The provided playlist URL is not in a recognized format!') sys.exit(10) playlist_id = splits[-1] try: results = spotify.user_playlist(username, playlist_id, fields='tracks,next,name') 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) return results
def _fetch_playlist(playlist): splits = internals.get_splits(playlist) try: username = splits[-3] except IndexError: # Wrong format, in either case log.error('The provided playlist URL is not in a recognized format!') return None playlist_id = splits[-1] try: results = _getClient().user_playlist( username, playlist_id, fields='tracks,next,name,images,artists,description') except spotipy.client.SpotifyException: log.error('Unable to find playlist', exc_info=True) log.info('Make sure the playlist is set to ' + 'publicly visible and then try again') return None return results
def download_history(): log.info("Retrieving download_history") try: items = list(map(lambda d: { 'url': d[0], 'name': d[1].get_name(), 'status': d[1].get_status().__dict__, 'init_date': d[1].get_init_date(format="%H:%M:%S"), 'end_date': d[1].get_end_date(format="%H:%M:%S"), }, current_downloads.items())) response = { 'items': items } return jsonify(response) except Exception: log.error("Error retrieving download_history", exc_info=True) abort(500, 'Error retrieving download_history')
def run(self): log.info("Init Downloader") self.status = "Init Downloader" try: self.init_date = time.localtime() self.download() self.end_date = time.localtime() log.info("Finished Downloader") self.status = "Finished Downloader" except Exception as e: log.error("Error in Downloader", exc_info=True) self.status = "Error in Downloader: {0}".format(str(e)) self.end_date = time.localtime() raise Exception("Error in Downloader")
def post_download(): if not request.json or 'url' not in request.json: abort(400, 'Error downloading playlist info: url obligatory') try: url = request.json['url'] log.info("Downloading url[%s]", url) if url not in current_downloads: downloader = SpotifyDownloader(url) downloader.start() current_downloads[url] = downloader return jsonify({'status': 'OK'}) else: return jsonify({'status': 'ALREADY_ADDED'}) except Exception: log.error("Error downloading playlist info", exc_info=True) abort(400, 'Error downloading playlist info')
def get_playlists(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'])) playlist_url = playlist['external_urls']['spotify'] log.debug(playlist_url) links.append(playlist_url) check += 1 if playlists['next']: playlists = spotify.next(playlists) else: break return links
def _check_exists(folder, 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)) if not os.path.isdir(folder): os.mkdir(folder) songs = os.listdir(folder) for song in songs: if song.endswith('.temp'): os.remove(os.path.join(folder, song)) continue # check if a song with the same name is # already present in the given folder if os.path.splitext(song)[0] == music_file: log.debug('Found an already existing song: "{}"'.format(song)) 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(folder, song), meta_tags) log.debug( 'Checking if it is already tagged correctly? {}'.format( already_tagged)) if not already_tagged: os.remove(os.path.join(folder, song)) return False log.warning('"{}" already exists'.format(song)) if const.config.overwrite == 'force': os.remove(os.path.join(folder, song)) log.info('Overwriting "{}"'.format(song)) return False elif const.config.overwrite == 'skip': log.info('Skipping "{}"'.format(song)) return True return False
def main(): init() from api.routes import app as routes log.info("Launching Flash app") app = Flask(__name__, static_url_path='') SECRET_KEY = os.urandom(32) app.config['SECRET_KEY'] = SECRET_KEY app.register_blueprint(routes, url_prefix='/') log.info("Loaded routes") log.info(app.url_map) threaded = True if os.getenv('LOCAL_MODE', "false").lower() == "true": from flask_cors import CORS CORS(app) threaded = False app.run(host='0.0.0.0', threaded=threaded)
def init(): log.info("Launching Spotify-Downloader") config.init_config()
def _download_single(folder, raw_song, number=None): """ Logic behind downloading a song. """ if internals.is_youtube(raw_song): log.debug('Input song is a YouTube URL') content = youtube_tools.go_pafy(raw_song, meta_tags=None) raw_song = slugify(content.title).replace('-', ' ') meta_tags = spotify_tools.generate_metadata(raw_song) meta_tags['number'] = number else: meta_tags = spotify_tools.generate_metadata(raw_song) meta_tags['number'] = number content = youtube_tools.go_pafy(raw_song, meta_tags) if content is None: log.debug('Found no matching video') return if const.config.download_only_metadata and meta_tags is None: log.info('Found no metadata. Skipping the download') return # "[number]. [artist] - [song]" if downloading from list # otherwise "[artist] - [song]" 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 = internals.format_string(const.config.file_format, meta_tags, slugification=True) log.debug('Refining songname from "{0}" to "{1}"'.format( songname, refined_songname)) if not refined_songname == ' - ': songname = refined_songname else: log.warning('Could not find metadata') songname = internals.sanitize(songname) if not _check_exists(folder, songname, raw_song, meta_tags): # deal with file formats containing slashes to non-existent directories songpath = os.path.join(folder, os.path.dirname(songname)) os.makedirs(songpath, exist_ok=True) input_song = songname + const.config.input_ext output_song = songname + const.config.output_ext if youtube_tools.download_song(songpath, input_song, content): try: convert.song(input_song, output_song, folder, avconv=const.config.avconv, trim_silence=const.config.trim_silence) except FileNotFoundError: encoder = 'avconv' if const.config.avconv else 'ffmpeg' log.warning( 'Could not find {0}, skipping conversion'.format(encoder)) const.config.output_ext = const.config.input_ext output_song = songname + const.config.output_ext if not const.config.input_ext == const.config.output_ext: os.remove(os.path.join(folder, input_song)) if not const.config.no_metadata and meta_tags is not None: metadata.embed(os.path.join(folder, output_song), meta_tags) return True else: log.exception('Error downloading song {}'.format(raw_song))
def start(self): log.info("Starting PlayListDownloader") self.future = _downloaders_thread_pool.submit(self.run)
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_songs(folder, songs, progress_func, max_retries=3): log.info(u'Preparing to download {} songs'.format(len(songs))) downloaded_songs = [] failed = 0 pending = len(songs) retrying = 0 total = len(songs) failed_songs = {} songs_enumeration = list(enumerate(songs)) for number, raw_song in songs_enumeration: try: progress_func({ "retrying": retrying, "failed": failed, "pending": pending, "total": total, }) _download_single(folder, raw_song, number=number) downloaded_songs.append(raw_song) pending = pending - 1 if failed_songs.get(str(number), 0) > 0: retrying = retrying - 1 # token expires after 1 hour except spotipy.client.SpotifyException: # refresh token when it expires log.debug('Token expired, generating new one and authorizing') spotify_tools.init() _download_single(folder, raw_song, number=number) # Retry if possible except Exception: if failed_songs.get(str(number), 0) == max_retries: log.exception('Error downloading song {}'.format(raw_song)) failed = failed + 1 retrying = retrying - 1 pending = pending - 1 else: retry = failed_songs.get(str(number), 0) if retry == 0: retrying = retrying + 1 log.warning( 'Failed to download song. Will retry after other songs\n', exc_info=True) failed_songs[str(number)] = retry + 1 songs_enumeration.append((number, raw_song)) time.sleep(0.5) progress_func({ "retrying": retrying, "failed": failed, "pending": pending, "total": total, }) return downloaded_songs