diff --git a/spotdl/command_line/__main__.py b/spotdl/command_line/__main__.py index 39521e5..234d59e 100644 --- a/spotdl/command_line/__main__.py +++ b/spotdl/command_line/__main__.py @@ -8,7 +8,10 @@ def match_arguments(arguments): for track in arguments.tracks: if track == "-": for line in sys.stdin: - command_line.helpers.download_track(line, arguments) + command_line.helpers.download_track( + line, + arguments + ) else: command_line.helpers.download_track(track, arguments) elif arguments.list: diff --git a/spotdl/command_line/arguments.py b/spotdl/command_line/arguments.py index 9c2382b..feca6a8 100644 --- a/spotdl/command_line/arguments.py +++ b/spotdl/command_line/arguments.py @@ -136,12 +136,6 @@ def get_arguments(argv=None, to_merge=True): choices={"ffmpeg", "avconv", "null"}, help="use this encoder for conversion", ) - parser.add_argument( - "-f", - "--directory", - default=os.path.abspath(config["directory"]), - help="path to directory where downloaded tracks will be stored in", - ) parser.add_argument( "--overwrite", default=config["overwrite"], @@ -175,11 +169,11 @@ def get_arguments(argv=None, to_merge=True): help="write tracks from Spotify playlist, album, etc. to this file", ) parser.add_argument( - "-ff", - "--file-format", - default=config["file-format"], - help="file format to save the downloaded track with, each tag " - "is surrounded by curly braces. Possible formats: " + "-f", + "--output-file", + default=config["output-file"], + help="path where to write the downloaded track to, special tags " + "are to be surrounded by curly braces. Possible tags: " # "{}".format([spotdl.util.formats[x] for x in spotdl.util.formats]), ) parser.add_argument( @@ -192,8 +186,8 @@ def get_arguments(argv=None, to_merge=True): "-sf", "--search-format", default=config["search-format"], - help="search format to search for on YouTube, each tag " - "is surrounded by curly braces. Possible formats: " + help="search format to search for on YouTube, special tags " + "are to be surrounded by curly braces. Possible tags: " # "{}".format([spotdl.util.formats[x] for x in spotdl.util.formats]), ) parser.add_argument( @@ -289,10 +283,10 @@ def get_arguments(argv=None, to_merge=True): if parsed.config is not None and to_merge: parsed = override_config(parsed.config, parser) - return run_errands(parser, parsed) + return run_errands(parser, parsed, config) -def run_errands(parser, parsed): +def run_errands(parser, parsed, config): if (parsed.list and not mimetypes.MimeTypes().guess_type(parsed.list)[0] == "text/plain" ): @@ -334,12 +328,23 @@ def run_errands(parser, parsed): setattr(parsed, "tracks", parsed.song) del parsed.song - if parsed.file_format == "-" and parsed.no_metadata is False: + if parsed.output_file == "-" and parsed.no_metadata is False: # log.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"] + ) + # log.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) diff --git a/spotdl/command_line/helpers.py b/spotdl/command_line/helpers.py index 8417c5a..9dc5403 100644 --- a/spotdl/command_line/helpers.py +++ b/spotdl/command_line/helpers.py @@ -3,6 +3,7 @@ from spotdl.metadata.providers import ProviderYouTube from spotdl.metadata.providers import YouTubeSearch from spotdl.metadata.embedders import EmbedderDefault from spotdl.metadata.exceptions import SpotifyMetadataNotFoundError +import spotdl.metadata from spotdl.encode.encoders import EncoderFFmpeg from spotdl.encode.encoders import EncoderAvconv @@ -46,17 +47,16 @@ def search_metadata(track, search_format="{artist} - {track-name} lyrics", manua if spotdl.util.is_spotify(track): spotify = ProviderSpotify() spotify_metadata = spotify.from_url(track) - lyric_query = spotdl.util.format_string( + lyric_query = spotdl.metadata.format_string( "{artist} - {track-name}", spotify_metadata, ) - search_query = spotdl.util.format_string(search_format, spotify_metadata) + search_query = spotdl.metadata.format_string(search_format, spotify_metadata) youtube_urls = youtube_searcher.search(search_query) if not youtube_urls: - # raise NoYouTubeVideoError( - # 'No videos found for the search query: "{}"'.format(search_query) - # ) - return + raise NoYouTubeVideoError( + 'No videos found for the search query: "{}"'.format(search_query) + ) if manual: pass else: @@ -69,7 +69,7 @@ def search_metadata(track, search_format="{artist} - {track-name} lyrics", manua elif spotdl.util.is_youtube(track): metadata = youtube.from_url(track) - lyric_query = spotdl.util.format_string( + lyric_query = spotdl.metadata.format_string( "{artist} - {track-name}", metadata, ) @@ -107,11 +107,14 @@ def search_metadata(track, search_format="{artist} - {track-name} lyrics", manua def download_track(track, arguments): + track_splits = track.split(":") + if len(track_splits) == 2: + youtube_track, spotify_track = track_splits metadata = search_metadata(track, search_format=arguments.search_format) - log_fmt = spotdl.util.format_string( - arguments.file_format, + log_fmt = spotdl.metadata.format_string( + arguments.output_file, metadata, - output_extension=arguments.output_ext + output_extension=arguments.output_ext, ) # log.info(log_fmt) download_track_from_metadata(metadata, arguments) @@ -119,7 +122,6 @@ def download_track(track, arguments): def download_track_from_metadata(metadata, arguments): # TODO: Add `-m` flag - track = Track(metadata, cache_albumart=(not arguments.no_metadata)) print(metadata["name"]) @@ -139,11 +141,15 @@ def download_track_from_metadata(metadata, arguments): else: output_extension = arguments.output_ext - filename = spotdl.util.format_string( - arguments.file_format, + filename = spotdl.metadata.format_string( + arguments.output_file, metadata, - output_extension=output_extension + output_extension=output_extension, + sanitizer=lambda s: spotdl.util.sanitize( + s, spaces_to_underscores=arguments.no_spaces + ) ) + print(filename) # log.info(filename) to_skip = arguments.dry_run diff --git a/spotdl/config.py b/spotdl/config.py index 9dbd5d5..15c5bf6 100644 --- a/spotdl/config.py +++ b/spotdl/config.py @@ -11,7 +11,6 @@ DEFAULT_CONFIGURATION = { "no-fallback-metadata": False, "avconv": False, "encoder": "ffmpeg", - "directory": spotdl.util.get_music_dir(), "overwrite": "prompt", "quality": "best", "input-ext": "automatic", @@ -23,7 +22,7 @@ DEFAULT_CONFIGURATION = { "music-videos-only": False, "no-spaces": False, "processor": "synchronous", - "file-format": "{artist} - {track-name}.{output-ext}", + "output-file": "{artist} - {track-name}.{output-ext}", "search-format": "{artist} - {track-name} lyrics", "youtube-api-key": None, "skip": None, diff --git a/spotdl/metadata/__init__.py b/spotdl/metadata/__init__.py index e8e5d15..63d804f 100644 --- a/spotdl/metadata/__init__.py +++ b/spotdl/metadata/__init__.py @@ -7,3 +7,5 @@ from spotdl.metadata.exceptions import YouTubeMetadataNotFoundError from spotdl.metadata.embedder_base import EmbedderBase +from spotdl.metadata.formatter import format_string + diff --git a/spotdl/metadata/formatter.py b/spotdl/metadata/formatter.py new file mode 100644 index 0000000..86f20ca --- /dev/null +++ b/spotdl/metadata/formatter.py @@ -0,0 +1,24 @@ +def format_string(string, metadata, output_extension="", sanitizer=lambda s: s): + formats = { + "{track-name}" : metadata["name"], + "{artist}" : metadata["artists"][0]["name"], + "{album}" : metadata["album"]["name"], + "{album-artist}" : metadata["artists"][0]["name"], + "{genre}" : metadata["genre"], + "{disc-number}" : metadata["disc_number"], + "{duration}" : metadata["duration"], + "{year}" : metadata["year"], + "{original-date}": metadata["release_date"], + "{track-number}" : metadata["track_number"], + "{total-tracks}" : metadata["total_tracks"], + "{isrc}" : metadata["external_ids"]["isrc"], + # TODO: Call `str.zfill` fill on track-id + "{track-id}" : metadata.get("id", ""), + "{output-ext}" : output_extension, + } + + for key, value in formats.items(): + string = string.replace(key, sanitizer(str(value))) + + return string + diff --git a/spotdl/tests/test_util.py b/spotdl/tests/test_util.py index 48bae57..277ebec 100644 --- a/spotdl/tests/test_util.py +++ b/spotdl/tests/test_util.py @@ -21,7 +21,7 @@ def test_default_music_directory(): @pytest.fixture(scope="module") def directory_fixture(tmpdir_factory): - dir_path = os.path.join(str(tmpdir_factory.mktemp("tmpdir")), "filter_this_folder") + dir_path = os.path.join(str(tmpdir_factory.mktemp("tmpdir")), "filter_this_directory") return dir_path diff --git a/spotdl/util.py b/spotdl/util.py index c5a9844..6d32028 100644 --- a/spotdl/util.py +++ b/spotdl/util.py @@ -80,43 +80,15 @@ def is_youtube(raw_song): return status -def format_string(string, metadata, output_extension=""): - formats = { - "{track-name}" : metadata["name"], - "{artist}" : metadata["artists"][0]["name"], - "{album}" : metadata["album"]["name"], - "{album-artist}" : metadata["artists"][0]["name"], - "{genre}" : metadata["genre"], - "{disc-number}" : metadata["disc_number"], - "{duration}" : metadata["duration"], - "{year}" : metadata["year"], - "{original-date}": metadata["release_date"], - "{track-number}" : metadata["track_number"], - "{total-tracks}" : metadata["total_tracks"], - "{isrc}" : metadata["external_ids"]["isrc"], - # TODO: Call `str.zfill` fill on track-id - "{track-id}" : metadata.get("id", ""), - "{output-ext}" : output_extension, - } - - for key, value in formats.items(): - string = string.replace(key, str(value)) - - return string - - -def sanitize_title(title, ok="-_()[]{}"): +def sanitize(string, ok="-_()[]{}", spaces_to_underscores=False): """ Generate filename of the song to be downloaded. """ - - if const.args.no_spaces: - title = title.replace(" ", "_") - - # replace slashes with "-" to avoid folder creation errors - title = title.replace("/", "-").replace("\\", "-") - + if spaces_to_underscores: + string = string.replace(" ", "_") + # replace slashes with "-" to avoid directory creation errors + string = string.replace("/", "-").replace("\\", "-") # slugify removes any special characters - title = slugify(title, ok=ok, lower=False, spaces=True) - return title + string = slugify(string, ok=ok, lower=False, spaces=True) + return string def filter_path(path): @@ -162,7 +134,7 @@ def get_sec(time_str): def get_music_dir(): home = os.path.expanduser("~") - # On Linux, the localized folder names are the actual ones. + # On Linux, the localized directory names are the actual ones. # It's a freedesktop standard though. if sys.platform.startswith("linux"): for file_item in (".config/user-dirs.dirs", "user-dirs.dirs"): @@ -176,7 +148,7 @@ def get_music_dir(): ) # Windows / Cygwin - # Queries registry for 'My Music' folder path (as this can be changed) + # Queries registry for 'My Music' directory path (as this can be changed) if "win" in sys.platform: try: key = winreg.OpenKey( @@ -189,7 +161,7 @@ def get_music_dir(): except (FileNotFoundError, NameError): pass - # On both Windows and macOS, the localized folder names you see in + # On both Windows and macOS, the localized directory names you see in # Explorer and Finder are actually in English on the file system. # So, defaulting to C:\Users\\Music or /Users//Music # respectively is sufficient.