mirror of
				https://github.com/KevinMidboe/spotify-downloader.git
				synced 2025-10-29 18:00:15 +00:00 
			
		
		
		
	Setup coloredlogs to remove logzero
This commit is contained in:
		
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -34,7 +34,7 @@ setup( | ||||
|         "mutagen >= 1.41.1", | ||||
|         "beautifulsoup4 >= 4.6.3", | ||||
|         "unicode-slugify >= 0.1.3", | ||||
|         "logzero >= 1.3.1", | ||||
|         "coloredlogs >= 14.0", | ||||
|         "lyricwikia >= 0.1.8", | ||||
|         "PyYAML >= 3.13", | ||||
|         "appdirs >= 1.4.3", | ||||
|   | ||||
| @@ -1,19 +1,23 @@ | ||||
| import logging | ||||
| for module in ("urllib3", "spotipy", "pytube",): | ||||
| import coloredlogs | ||||
|  | ||||
| import sys | ||||
|  | ||||
| # hardcode loglevel for dependencies so that they do not spew generic | ||||
| # log messages along with spotdl. | ||||
| for module in ("urllib3", "spotipy", "pytube"): | ||||
|     logging.getLogger(module).setLevel(logging.CRITICAL) | ||||
|  | ||||
| import coloredlogs | ||||
| coloredlogs.DEFAULT_FIELD_STYLES = { | ||||
|     "levelname": {"bold": True, "color": "yellow"}, | ||||
|     "name": {"color": "blue"}, | ||||
|     "lineno": {"color": "magenta"}, | ||||
| } | ||||
|  | ||||
| import sys | ||||
|  | ||||
| def set_logger(level): | ||||
|     if level == logging.DEBUG: | ||||
|         fmt = "%(levelname)s:%(name)s:%(lineno)d:\n%(message)s" | ||||
|         fmt = "%(levelname)s:%(name)s:%(lineno)d:\n%(message)s\n" | ||||
|     else: | ||||
|         fmt = "%(levelname)s: %(message)s" | ||||
|     logging.basicConfig(format=fmt, level=level) | ||||
| @@ -25,12 +29,14 @@ def set_logger(level): | ||||
| def main(): | ||||
|     from spotdl.command_line.arguments import get_arguments | ||||
|     arguments = get_arguments() | ||||
|     logger = set_logger(arguments["log_level"]) | ||||
|     logger = set_logger(arguments.parsed.log_level) | ||||
|     arguments = arguments.run_errands() | ||||
|     from spotdl.command_line.lib import Spotdl | ||||
|     spotdl = Spotdl(arguments) | ||||
|     try: | ||||
|         spotdl.match_arguments() | ||||
|     except KeyboardInterrupt as e: | ||||
|         print("", file=sys.stderr) | ||||
|         logger.exception(e) | ||||
|         sys.exit(2) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import appdirs | ||||
|  | ||||
| import logging | ||||
| import argparse | ||||
| import mimetypes | ||||
| import os | ||||
| @@ -10,9 +9,13 @@ import shutil | ||||
| import spotdl.util | ||||
| import spotdl.config | ||||
|  | ||||
| from collections.abc import Sequence | ||||
| import logging | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| _LOG_LEVELS_STR = ("INFO", "WARNING", "ERROR", "DEBUG") | ||||
|  | ||||
|  | ||||
| def log_leveller(log_level_str): | ||||
|     logging_levels = [logging.INFO, logging.WARNING, logging.ERROR, logging.DEBUG] | ||||
|     log_level_str_index = _LOG_LEVELS_STR.index(log_level_str) | ||||
| @@ -31,19 +34,18 @@ def override_config(config_file, parser, argv=None): | ||||
|     return parser.parse_args(argv) | ||||
|  | ||||
|  | ||||
| def get_arguments(argv=None, to_merge=True): | ||||
| def get_arguments(argv=None, base_config_file=spotdl.config.default_config_file): | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description="Download and convert tracks from Spotify, Youtube etc.", | ||||
|         formatter_class=argparse.ArgumentDefaultsHelpFormatter, | ||||
|     ) | ||||
|  | ||||
|     config_file = spotdl.config.default_config_file | ||||
|     if to_merge: | ||||
|         config_dir = os.path.dirname(spotdl.config.default_config_file) | ||||
|         os.makedirs(os.path.dirname(spotdl.config.default_config_file), exist_ok=True) | ||||
|     if base_config_file: | ||||
|         config_dir = os.path.dirname(base_config_file) | ||||
|         os.makedirs(os.path.dirname(base_config_file), exist_ok=True) | ||||
|         config = spotdl.util.merge( | ||||
|             spotdl.config.DEFAULT_CONFIGURATION["spotify-downloader"], | ||||
|             spotdl.config.get_config(config_file) | ||||
|             spotdl.config.get_config(base_config_file) | ||||
|         ) | ||||
|     else: | ||||
|         config = spotdl.config.DEFAULT_CONFIGURATION["spotify-downloader"] | ||||
| @@ -240,7 +242,7 @@ def get_arguments(argv=None, to_merge=True): | ||||
|     parser.add_argument( | ||||
|         "-c", | ||||
|         "--config", | ||||
|         default=config_file, | ||||
|         default=base_config_file, | ||||
|         help="path to custom config.yml file" | ||||
|     ) | ||||
|     parser.add_argument( | ||||
| @@ -252,63 +254,70 @@ def get_arguments(argv=None, to_merge=True): | ||||
|  | ||||
|     parsed = parser.parse_args(argv) | ||||
|  | ||||
|     if parsed.config is not None and to_merge: | ||||
|     if base_config_file and parsed.config is not None: | ||||
|         parsed = override_config(parsed.config, parser) | ||||
|  | ||||
|     return run_errands(parser, parsed, config) | ||||
|  | ||||
|  | ||||
| def run_errands(parser, parsed, config): | ||||
|     if (parsed.list | ||||
|         and not mimetypes.MimeTypes().guess_type(parsed.list)[0] == "text/plain" | ||||
|     ): | ||||
|         parser.error( | ||||
|             "{0} is not of a valid argument to --list, argument must be plain text file".format( | ||||
|                 parsed.list | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     if parsed.write_m3u and not parsed.list: | ||||
|         parser.error("--write-m3u can only be used with --list") | ||||
|  | ||||
|     if parsed.trim_silence and not "ffmpeg" in parsed.encoder: | ||||
|         parser.error("--trim-silence can only be used with FFmpeg") | ||||
|  | ||||
|     if parsed.write_to and not ( | ||||
|         parsed.playlist or parsed.album or parsed.all_albums or parsed.username | ||||
|     ): | ||||
|         parser.error( | ||||
|             "--write-to can only be used with --playlist, --album, --all-albums, or --username" | ||||
|         ) | ||||
|  | ||||
|     encoder_exists = shutil.which(parsed.encoder) | ||||
|     if not encoder_exists: | ||||
|         logger.warn("Specified encoder () was not found. Will not encode to specified " | ||||
|                  "output format".format(parsed.encoder)) | ||||
|         parsed.encoder = "null" | ||||
|  | ||||
|     if parsed.output_file == "-" and parsed.no_metadata is False: | ||||
|         logger.warn( | ||||
|             "Cannot write metadata when target file is STDOUT. Pass " | ||||
|             "--no-metadata explicitly to hide this warning." | ||||
|         ) | ||||
|         parsed.no_metadata = True | ||||
|     elif os.path.isdir(parsed.output_file): | ||||
|         adjusted_output_file = os.path.join( | ||||
|             parsed.output_file, | ||||
|             config["output-file"] | ||||
|         ) | ||||
|         logger.warn( | ||||
|             "Specified output file is a directory. Will write the filename as in " | ||||
|             "default file format. Pass --output-file={} to hide this warning".format( | ||||
|                 adjusted_output_file | ||||
|         )) | ||||
|         parsed.output_file = adjusted_output_file | ||||
|  | ||||
|     parsed.log_level = log_leveller(parsed.log_level) | ||||
|     return Arguments(parser, parsed) | ||||
|  | ||||
|     # We're done dealing with configuration file here and don't need to use it later | ||||
|     del parsed.config | ||||
|  | ||||
|     return parsed.__dict__ | ||||
| class Arguments: | ||||
|     def __init__(self, parser, parsed): | ||||
|         self.parser = parser | ||||
|         self.parsed = parsed | ||||
|  | ||||
|     def run_errands(self): | ||||
|         if (self.parsed.list | ||||
|             and not mimetypes.MimeTypes().guess_type(self.parsed.list)[0] == "text/plain" | ||||
|         ): | ||||
|             self.parser.error( | ||||
|                 "{0} is not of a valid argument to --list, argument must be plain text file.".format( | ||||
|                     self.parsed.list | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|         if self.parsed.write_m3u and not self.parsed.list: | ||||
|             self.parser.error("--write-m3u can only be used with --list") | ||||
|  | ||||
|         if self.parsed.trim_silence and not "ffmpeg" in self.parsed.encoder: | ||||
|             self.parser.error("--trim-silence can only be used with FFmpeg") | ||||
|  | ||||
|         if self.parsed.write_to and not ( | ||||
|             self.parsed.playlist or self.parsed.album or self.parsed.all_albums or self.parsed.username | ||||
|         ): | ||||
|             self.parser.error( | ||||
|                 "--write-to can only be used with --playlist, --album, --all-albums, or --username" | ||||
|             ) | ||||
|  | ||||
|         encoder_exists = shutil.which(self.parsed.encoder) | ||||
|         if not self.parsed.encoder == "null" and not encoder_exists: | ||||
|             logger.warn( | ||||
|                 'Specified encoder "{}" was not found in PATH. Will not encode to specified ' | ||||
|                 'output format.'.format(self.parsed.encoder)) | ||||
|             self.parsed.encoder = "null" | ||||
|  | ||||
|         if self.parsed.output_file == "-" and self.parsed.no_metadata is False: | ||||
|             logger.warn( | ||||
|                 "Cannot write metadata when target file is STDOUT. Pass " | ||||
|                 "--no-metadata explicitly to hide this warning." | ||||
|             ) | ||||
|             self.parsed.no_metadata = True | ||||
|         elif os.path.isdir(self.parsed.output_file): | ||||
|             adjusted_output_file = os.path.join( | ||||
|                 self.parsed.output_file, | ||||
|                 self.parser.get_default("output_file") | ||||
|             ) | ||||
|             logger.warn( | ||||
|                 "Given output file is a directory. Will download tracks in this directory with " | ||||
|                 "their filename as per the default file format. Pass '--output-file=\"{}\"' to hide this " | ||||
|                 "warning.".format( | ||||
|                     adjusted_output_file | ||||
|                 ) | ||||
|             ) | ||||
|             self.parsed.output_file = adjusted_output_file | ||||
|  | ||||
|         # We're done dealing with configuration file here and don't need to use it later | ||||
|         del self.parsed.config | ||||
|  | ||||
|         return self.parsed.__dict__ | ||||
|  | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import spotdl.config | ||||
|  | ||||
| from spotdl.command_line.exceptions import NoYouTubeVideoError | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| import urllib.request | ||||
|  | ||||
| @@ -45,18 +46,21 @@ def search_metadata_on_spotify(query): | ||||
|  | ||||
|  | ||||
| def prompt_for_youtube_search_result(videos): | ||||
|     urls = [] | ||||
|     print("0. Skip downloading this track") | ||||
|     print("0. Skip downloading this track", file=sys.stderr) | ||||
|     for index, video in enumerate(videos, 1): | ||||
|         video_repr = "{index}. {title} [{duration}] ({url})".format( | ||||
|         video_repr = "{index}. {title} ({url}) [{duration}]".format( | ||||
|             index=index, | ||||
|             title=video["title"], | ||||
|             url=video["url"], | ||||
|             duration=video["duration"], | ||||
|             url=video["url"] | ||||
|         ) | ||||
|         print(video_repr) | ||||
|         urls.append(video["url"]) | ||||
|     return spotdl.util.prompt_user_for_selection(urls) | ||||
|         print(video_repr, file=sys.stderr) | ||||
|  | ||||
|     selection = spotdl.util.prompt_user_for_selection(range(1, len(videos)+1)) | ||||
|  | ||||
|     if selection is None: | ||||
|         return None | ||||
|     return videos[selection-1] | ||||
|  | ||||
|  | ||||
| def search_metadata(track, search_format="{artist} - {track-name} lyrics", manual=False): | ||||
| @@ -80,12 +84,15 @@ def search_metadata(track, search_format="{artist} - {track-name} lyrics", manua | ||||
|         if manual: | ||||
|             youtube_video = prompt_for_youtube_search_result(youtube_videos) | ||||
|         else: | ||||
|             youtube_video = youtube_videos.bestmatch()["url"] | ||||
|         youtube_metadata = youtube.from_url(youtube_video) | ||||
|         metadata = spotdl.util.merge( | ||||
|             youtube_metadata, | ||||
|             spotify_metadata | ||||
|         ) | ||||
|             youtube_video = youtube_videos.bestmatch() | ||||
|         if youtube_video is None: | ||||
|             metadata = spotify_metadata | ||||
|         else: | ||||
|             youtube_metadata = youtube.from_url(youtube_video["url"]) | ||||
|             metadata = spotdl.util.merge( | ||||
|                 youtube_metadata, | ||||
|                 spotify_metadata | ||||
|             ) | ||||
|  | ||||
|     elif spotdl.util.is_youtube(track): | ||||
|         metadata = youtube.from_url(track) | ||||
| @@ -106,15 +113,26 @@ def search_metadata(track, search_format="{artist} - {track-name} lyrics", manua | ||||
|             raise NoYouTubeVideoError( | ||||
|                 'No videos found for the search query: "{}"'.format(track) | ||||
|             ) | ||||
|             return | ||||
|         if manual: | ||||
|             youtube_video = prompt_for_youtube_search_result(youtube_videos) | ||||
|         else: youtube_video = youtube_videos.bestmatch()["url"] | ||||
|         youtube_metadata = youtube.from_url(youtube_video) | ||||
|         metadata = spotdl.util.merge( | ||||
|             youtube_metadata, | ||||
|             spotify_metadata.join() | ||||
|         ) | ||||
|             if youtube_video is None: | ||||
|                 return | ||||
|         else: | ||||
|             youtube_video = youtube_videos.bestmatch() | ||||
|         if youtube_video is None: | ||||
|             metadata = spotify_metadata | ||||
|         else: | ||||
|             youtube_metadata = youtube.from_url(youtube_video["url"]) | ||||
|             metadata = spotdl.util.merge( | ||||
|                 youtube_metadata, | ||||
|                 spotify_metadata.join() | ||||
|             ) | ||||
|  | ||||
|     logger.debug("Matched with: {title} ({url}) [{duration}]".format( | ||||
|         title=youtube_video["title"], | ||||
|         url=youtube_video["url"], | ||||
|         duration=youtube_video["duration"] | ||||
|     )) | ||||
|  | ||||
|     metadata["lyrics"] = spotdl.util.ThreadWithReturnValue( | ||||
|         target=search_lyrics, | ||||
| @@ -152,7 +170,7 @@ class Spotdl: | ||||
|             client_id=self.arguments["spotify_client_id"], | ||||
|             client_secret=self.arguments["spotify_client_secret"] | ||||
|         ) | ||||
|         logger.debug(self.arguments) | ||||
|         logger.debug("Received arguments: {}".format(self.arguments)) | ||||
|  | ||||
|         # youtube_tools.set_api_key() | ||||
|         if self.arguments["song"]: | ||||
| @@ -205,24 +223,17 @@ class Spotdl: | ||||
|             search_format=self.arguments["search_format"], | ||||
|             manual=self.arguments["manual"], | ||||
|         ) | ||||
|         log_fmt = spotdl.metadata.format_string( | ||||
|             self.arguments["output_file"], | ||||
|             metadata, | ||||
|             output_extension=self.arguments["output_ext"], | ||||
|         ) | ||||
|         logger.info(log_fmt) | ||||
|         self.download_track_from_metadata(metadata) | ||||
|         if "streams" in metadata: | ||||
|             self.download_track_from_metadata(metadata) | ||||
|  | ||||
|     def download_track_from_metadata(self, metadata): | ||||
|         # TODO: Add `-m` flag | ||||
|         track = Track(metadata, cache_albumart=(not self.arguments["no_metadata"])) | ||||
|         print(metadata["name"]) | ||||
|  | ||||
|         stream = metadata["streams"].get( | ||||
|             quality=self.arguments["quality"], | ||||
|             preftype=self.arguments["input_ext"], | ||||
|         ) | ||||
|         logger.info(stream) | ||||
|         logger.debug("Stream information: {}".format(stream)) | ||||
|  | ||||
|         Encoder = { | ||||
|             "ffmpeg": EncoderFFmpeg, | ||||
| @@ -242,16 +253,18 @@ class Spotdl: | ||||
|                 s, spaces_to_underscores=self.arguments["no_spaces"] | ||||
|             ) | ||||
|         ) | ||||
|         print(filename) | ||||
|         logger.info(filename) | ||||
|         logger.info('Downloading to "{filename}"'.format(filename=filename)) | ||||
|  | ||||
|         to_skip = self.arguments["dry_run"] | ||||
|         if not to_skip and os.path.isfile(filename): | ||||
|             if self.arguments["overwrite"] == "force": | ||||
|                 to_skip = False | ||||
|                 logger.info("A file with target filename already exists. Forcing overwrite.") | ||||
|             elif self.arguments["overwrite"] == "prompt": | ||||
|                 to_skip = not input("overwrite? (y/N)").lower() == "y" | ||||
|                 overwrite_msg = "A file with target filename already exists. Overwrite? (y/N): " | ||||
|                 to_skip = not input(overwrite_msg).lower() == "y" | ||||
|             else: | ||||
|                 logger.info("A file with target filename already exists. Skipping download.") | ||||
|                 to_skip = True | ||||
|  | ||||
|         if to_skip: | ||||
| @@ -270,14 +283,14 @@ class Spotdl: | ||||
|         if not self.arguments["no_metadata"]: | ||||
|             track.metadata["lyrics"] = track.metadata["lyrics"].join() | ||||
|             try: | ||||
|                 logger.info("Applying metadata") | ||||
|                 track.apply_metadata(filename, encoding=output_extension) | ||||
|             except TypeError: | ||||
|                 logger.warning("Cannot write metadata to given file") | ||||
|                 pass | ||||
|                 logger.warning("Cannot apply metadata on provided output format.") | ||||
|  | ||||
|     def download_tracks_from_file(self, path): | ||||
|         logger.info( | ||||
|             "Checking and removing any duplicate tracks in {}".format(path) | ||||
|             "Checking and removing any duplicate tracks in {}.".format(path) | ||||
|         ) | ||||
|         with open(path, "r") as fin: | ||||
|             # Read tracks into a list and remove any duplicates | ||||
| @@ -298,8 +311,8 @@ class Spotdl: | ||||
|         for number, track in enumerate(tracks, 1): | ||||
|             try: | ||||
|                 metadata = search_metadata(track, self.arguments["search_format"]) | ||||
|                 log_fmt=(str(number) + ". {artist} - {track-name}") | ||||
|                 logger.info(log_fmt) | ||||
|                 log_track_query = str(number) + ". {artist} - {track-name}" | ||||
|                 logger.info(log_track_query) | ||||
|                 self.download_track_from_metadata(metadata) | ||||
|             except (urllib.request.URLError, TypeError, IOError) as e: | ||||
|                 logger.exception(e.args[0]) | ||||
| @@ -320,7 +333,7 @@ class Spotdl: | ||||
|         # FIXME: Can we make this function cleaner? | ||||
|  | ||||
|         logger.info( | ||||
|             "Checking and removing any duplicate tracks in {}".format(path) | ||||
|             "Checking and removing any duplicate tracks in {}.".format(path) | ||||
|         ) | ||||
|         with open(path, "r") as fin: | ||||
|             # Read tracks into a list and remove any duplicates | ||||
| @@ -354,8 +367,8 @@ class Spotdl: | ||||
|             metadata["current_track"] = metadata["next_track"].join() | ||||
|             metadata["next_track"] = None | ||||
|             try: | ||||
|                 print(tracks_count) | ||||
|                 print(tracks) | ||||
|                 print(tracks_count, file=sys.stderr) | ||||
|                 print(tracks, file=sys.stderr) | ||||
|                 if tracks_count > 1: | ||||
|                     current_track = next_track | ||||
|                     next_track = tracks.pop(0) | ||||
| @@ -365,12 +378,12 @@ class Spotdl: | ||||
|                     ) | ||||
|                     metadata["next_track"].start() | ||||
|  | ||||
|                 log_fmt=(str(current_iteration) + ". {artist} - {track-name}") | ||||
|                 logger.info(log_fmt) | ||||
|                 log_track_query = str(current_iteration) + ". {artist} - {track-name}" | ||||
|                 logger.info(log_track_query) | ||||
|                 if metadata["current_track"] is None: | ||||
|                     logger.warning("Something went wrong. Will retry after downloading remaining tracks") | ||||
|                     logger.warning("Something went wrong. Will retry after downloading remaining tracks.") | ||||
|                     pass | ||||
|                 print(metadata["current_track"]["name"]) | ||||
|                 print(metadata["current_track"]["name"], file=sys.stderr) | ||||
|                 # self.download_track_from_metadata(metadata["current_track"]) | ||||
|             except (urllib.request.URLError, TypeError, IOError) as e: | ||||
|                 logger.exception(e.args[0]) | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import subprocess | ||||
| import os | ||||
| # from logzero import logger as log | ||||
| import logging | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -11,8 +10,10 @@ from spotdl.encode.exceptions import AvconvNotFoundError | ||||
|  | ||||
| class EncoderAvconv(EncoderBase): | ||||
|     def __init__(self, encoder_path="avconv"): | ||||
|         print("Using EncoderAvconv is deprecated and will be removed", | ||||
|               "in future versions. Use EncoderFFmpeg instead.") | ||||
|         logger.warn( | ||||
|             "Using EncoderAvconv is deprecated and will be removed", | ||||
|             "in future versions. Use EncoderFFmpeg instead." | ||||
|         ) | ||||
|         encoder_path = encoder_path | ||||
|         _loglevel = "-loglevel 0" | ||||
|         _additional_arguments = ["-ab", "192k"] | ||||
|   | ||||
| @@ -99,7 +99,7 @@ class YouTubeSearch: | ||||
|     def search(self, query, limit=10): | ||||
|         """ Search and scrape YouTube to return a list of matching videos. """ | ||||
|         search_url = self.generate_search_url(query) | ||||
|         logger.debug("Opening URL: {0}".format(search_url)) | ||||
|         logger.debug("Fetching YouTube results for search URL: {0}".format(search_url)) | ||||
|         html = self._fetch_response_html(search_url) | ||||
|  | ||||
|         videos = self._fetch_search_results(html) | ||||
|   | ||||
| @@ -52,9 +52,9 @@ def merge(base, overrider): | ||||
|  | ||||
| def prompt_user_for_selection(items): | ||||
|     """ Let the user input a choice. """ | ||||
|     logger.info("Choose your number:") | ||||
|     while True: | ||||
|         try: | ||||
|             logger.info("Choose your number:") | ||||
|             the_chosen_one = int(input("> ")) | ||||
|             if 1 <= the_chosen_one <= len(items): | ||||
|                 return items[the_chosen_one - 1] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user