mirror of
				https://github.com/KevinMidboe/spotify-downloader.git
				synced 2025-10-29 18:00:15 +00:00 
			
		
		
		
	Accept additional command-line options
This commit is contained in:
		| @@ -16,12 +16,16 @@ def match_arguments(arguments): | ||||
|                 track_file=arguments.list | ||||
|             ) | ||||
|         else: | ||||
|             list_dl = downloader.ListDownloader( | ||||
|                 tracks_file=arguments.list, | ||||
|                 skip_file=arguments.skip, | ||||
|                 write_successful_file=arguments.write_successful, | ||||
|             command_line.helpers.download_tracks_from_file( | ||||
|                 arguments.list, | ||||
|                 arguments, | ||||
|             ) | ||||
|             list_dl.download_list() | ||||
|             # list_dl = downloader.ListDownloader( | ||||
|             #     tracks_file=arguments.list, | ||||
|             #     skip_file=arguments.skip, | ||||
|             #     write_successful_file=arguments.write_successful, | ||||
|             # ) | ||||
|             # list_dl.download_list() | ||||
|     elif arguments.playlist: | ||||
|         spotify_tools.write_playlist( | ||||
|             playlist_url=arguments.playlist, text_file=arguments.write_to | ||||
|   | ||||
| @@ -134,6 +134,7 @@ def get_arguments(argv=None, to_merge=True): | ||||
|         "-e", | ||||
|         "--encoder", | ||||
|         default=config["encoder"], | ||||
|         choices={"ffmpeg", "avconv", "null"}, | ||||
|         help="use this encoder for conversion", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
| @@ -145,21 +146,29 @@ def get_arguments(argv=None, to_merge=True): | ||||
|     parser.add_argument( | ||||
|         "--overwrite", | ||||
|         default=config["overwrite"], | ||||
|         help="change the overwrite policy", | ||||
|         choices={"prompt", "force", "skip"}, | ||||
|         help="change the overwrite policy", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-q", | ||||
|         "--quality", | ||||
|         default=config["quality"], | ||||
|         choices={"best", "worst"}, | ||||
|         help="preferred audio quality", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-i", | ||||
|         "--input-ext", | ||||
|         default=config["input-ext"], | ||||
|         help="preferred input format 'm4a' or 'webm' (Opus)", | ||||
|         choices={"m4a", "webm"}, | ||||
|         choices={"automatic", "m4a", "opus"}, | ||||
|         help="preferred input format", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-o", | ||||
|         "--output-ext", | ||||
|         default=config["output-ext"], | ||||
|         help="preferred output format: 'mp3', 'm4a' (AAC), 'flac', etc.", | ||||
|         choices={"mp3", "m4a", "flac"}, | ||||
|         help="preferred output format", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "--write-to", | ||||
| @@ -310,7 +319,7 @@ def run_errands(parser, parsed): | ||||
|     if not encoder_exists: | ||||
|         # log.warn("Specified encoder () was not found. Will not encode to specified " | ||||
|         #          "output format".format(parsed.encoder)) | ||||
|         parsed.output_ext = parsed.input_ext | ||||
|         parsed.encoder = "null" | ||||
|  | ||||
|     song_parameter_passed = parsed.song is not None and parsed.tracks is None | ||||
|     if song_parameter_passed: | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from spotdl.metadata.providers import ProviderSpotify | ||||
| from spotdl.metadata.providers import ProviderYouTube | ||||
| from spotdl.metadata.embedders import EmbedderDefault | ||||
| from spotdl.encode.encoders import EncoderFFmpeg, EncoderAvconv | ||||
| from spotdl.lyrics.providers import LyricWikia | ||||
| from spotdl.lyrics.providers import Genius | ||||
|  | ||||
| @@ -8,6 +9,7 @@ from spotdl.track import Track | ||||
|  | ||||
| import spotdl.util | ||||
|  | ||||
| import os | ||||
| import urllib.request | ||||
| import threading | ||||
|  | ||||
| @@ -46,34 +48,57 @@ def download_track_from_metadata(metadata, arguments): | ||||
|     track = Track(metadata, cache_albumart=(not arguments.no_metadata)) | ||||
|     # log.info(log_fmt) | ||||
|  | ||||
|     stream = metadata["streams"].get( | ||||
|         quality=arguments.quality, | ||||
|         preftype=arguments.input_ext, | ||||
|     ) | ||||
|     # log.info(stream) | ||||
|  | ||||
|     Encoder = { | ||||
|         "ffmpeg": EncoderFFmpeg, | ||||
|         "avconv": EncoderAvconv, | ||||
|     }.get(arguments.encoder) | ||||
|  | ||||
|     if Encoder is None: | ||||
|         output_extension = stream["encoding"] | ||||
|     else: | ||||
|         output_extension = arguments.output_ext | ||||
|  | ||||
|     filename = spotdl.util.format_string( | ||||
|         arguments.file_format, | ||||
|         metadata, | ||||
|         output_extension=arguments.output_ext | ||||
|         output_extension=output_extension | ||||
|     ) | ||||
|     # log.info(filename) | ||||
|  | ||||
|     if arguments.dry_run: | ||||
|         return | ||||
|  | ||||
|     if os.path.isfile(filename): | ||||
|         if arguments.overwrite == "skip": | ||||
|             to_skip = True | ||||
|     to_skip = arguments.dry_run | ||||
|     if not to_skip and os.path.isfile(filename): | ||||
|         if arguments.overwrite == "force": | ||||
|             to_skip = False | ||||
|         elif arguments.overwrite == "prompt": | ||||
|             to_skip = not input("overwrite? (y/N)").lower() == "y" | ||||
|         else: | ||||
|             to_skip = True | ||||
|  | ||||
|     if to_skip: | ||||
|         return | ||||
|  | ||||
|     if arguments.no_encode: | ||||
|         track.download(filename) | ||||
|     if Encoder is None: | ||||
|         track.download(stream, filename) | ||||
|     else: | ||||
|         track.download_while_re_encoding( | ||||
|             stream, | ||||
|             filename, | ||||
|             target_encoding=arguments.output_ext | ||||
|             target_encoding=output_extension, | ||||
|             encoder=Encoder() | ||||
|         ) | ||||
|  | ||||
|     if not arguments.no_metadata: | ||||
|         track.apply_metadata(filename, encoding=arguments.output_ext) | ||||
|         try: | ||||
|             track.apply_metadata(filename, encoding=output_extension) | ||||
|         except TypeError: | ||||
|             # log.warning("Cannot write metadata to given file") | ||||
|             pass | ||||
|  | ||||
|  | ||||
| def download_tracks_from_file(path, arguments): | ||||
| @@ -142,9 +167,9 @@ def download_tracks_from_file(path, arguments): | ||||
|             # log.warning("Failed. Will retry after other songs\n") | ||||
|             tracks.append(current_track) | ||||
|         else: | ||||
|             # TODO: CONFIG.YML | ||||
|             #       Write track to config.write_sucessful | ||||
|             pass | ||||
|             if arguments.write_successful: | ||||
|                 with open(arguments.write_successful, "a") as fout: | ||||
|                     fout.write(current_track) | ||||
|         finally: | ||||
|             with open(path, "w") as fout: | ||||
|                 fout.writelines(tracks) | ||||
|   | ||||
| @@ -14,7 +14,8 @@ DEFAULT_CONFIGURATION = { | ||||
|         "encoder": "ffmpeg", | ||||
|         "directory": spotdl.util.get_music_dir(), | ||||
|         "overwrite": "prompt", | ||||
|         "input-ext": "m4a", | ||||
|         "quality": "best", | ||||
|         "input-ext": "automatic", | ||||
|         "output-ext": "mp3", | ||||
|         "write-to": None, | ||||
|         "trim-silence": False, | ||||
| @@ -22,8 +23,8 @@ DEFAULT_CONFIGURATION = { | ||||
|         "dry-run": False, | ||||
|         "music-videos-only": False, | ||||
|         "no-spaces": False, | ||||
|         "file-format": "{artist} - {track_name}.{output_ext}", | ||||
|         "search-format": "{artist} - {track_name} lyrics", | ||||
|         "file-format": "{artist} - {track-name}.{output-ext}", | ||||
|         "search-format": "{artist} - {track-name} lyrics", | ||||
|         "youtube-api-key": None, | ||||
|         "skip": None, | ||||
|         "write-successful": None, | ||||
|   | ||||
| @@ -9,7 +9,7 @@ from spotdl.encode.exceptions import FFmpegNotFoundError | ||||
| RULES = { | ||||
|     "m4a": { | ||||
|         "mp3": "-codec:v copy -codec:a libmp3lame -ar 48000", | ||||
|         "webm": "-codec:a libopus -vbr on", | ||||
|         "opus": "-codec:a libopus -vbr on", | ||||
|         "m4a": "-acodec copy", | ||||
|         "flac": "-codec:a flac -ar 48000", | ||||
|     }, | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class TestEncodingDefaults: | ||||
|  | ||||
|     @pytest.mark.parametrize("files, expected_command", [ | ||||
|         (("test.m4a", "test.mp3"), encode_command("test.m4a", "test.mp3")), | ||||
|         (("abc.m4a", "cba.webm"), encode_command("abc.m4a", "cba.webm")), | ||||
|         (("abc.m4a", "cba.opus"), encode_command("abc.m4a", "cba.opus")), | ||||
|         (("bla bla.m4a", "ble ble.m4a"), encode_command("bla bla.m4a", "ble ble.m4a")), | ||||
|         (("😛.m4a", "• tongue.flac"), encode_command("😛.m4a", "• tongue.flac")), | ||||
|     ]) | ||||
| @@ -47,7 +47,7 @@ class TestEncodingInDebugMode: | ||||
|  | ||||
|     @pytest.mark.parametrize("files, expected_command", [ | ||||
|         (("test.m4a", "test.mp3"), debug_encode_command("test.m4a", "test.mp3")), | ||||
|         (("abc.m4a", "cba.webm"), debug_encode_command("abc.m4a", "cba.webm")), | ||||
|         (("abc.m4a", "cba.opus"), debug_encode_command("abc.m4a", "cba.opus")), | ||||
|         (("bla bla.m4a", "ble ble.m4a"), debug_encode_command("bla bla.m4a", "ble ble.m4a")), | ||||
|         (("😛.m4a", "• tongue.flac"), debug_encode_command("😛.m4a", "• tongue.flac")), | ||||
|     ]) | ||||
|   | ||||
| @@ -28,7 +28,7 @@ class TestEncodingDefaults: | ||||
|         ] | ||||
|         return command | ||||
|  | ||||
|     def m4a_to_webm_encoder(input_path, target_path): | ||||
|     def m4a_to_opus_encoder(input_path, target_path): | ||||
|         command = [ | ||||
|             'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic', | ||||
|             '-i', input_path, | ||||
| @@ -36,7 +36,7 @@ class TestEncodingDefaults: | ||||
|             '-vbr', 'on', | ||||
|             '-b:a', '192k', | ||||
|             '-vn', | ||||
|             '-f', 'webm', | ||||
|             '-f', 'opus', | ||||
|             target_path | ||||
|         ] | ||||
|         return command | ||||
| @@ -68,7 +68,7 @@ class TestEncodingDefaults: | ||||
|  | ||||
|     @pytest.mark.parametrize("files, expected_command", [ | ||||
|         (("test.m4a", "test.mp3"), m4a_to_mp3_encoder("test.m4a", "test.mp3")), | ||||
|         (("abc.m4a", "cba.webm"), m4a_to_webm_encoder("abc.m4a", "cba.webm")), | ||||
|         (("abc.m4a", "cba.opus"), m4a_to_opus_encoder("abc.m4a", "cba.opus")), | ||||
|         (("bla bla.m4a", "ble ble.m4a"), m4a_to_m4a_encoder("bla bla.m4a", "ble ble.m4a")), | ||||
|         (("😛.m4a", "• tongue.flac"), m4a_to_flac_encoder("😛.m4a", "• tongue.flac")), | ||||
|     ]) | ||||
| @@ -92,7 +92,7 @@ class TestEncodingInDebugMode: | ||||
|         ] | ||||
|         return command | ||||
|  | ||||
|     def m4a_to_webm_encoder_with_debug(input_path, target_path): | ||||
|     def m4a_to_opus_encoder_with_debug(input_path, target_path): | ||||
|         command = [ | ||||
|             'ffmpeg', '-y', '-nostdin', '-loglevel', 'debug', | ||||
|             '-i', input_path, | ||||
| @@ -100,7 +100,7 @@ class TestEncodingInDebugMode: | ||||
|             '-vbr', 'on', | ||||
|             '-b:a', '192k', | ||||
|             '-vn', | ||||
|             '-f', 'webm', | ||||
|             '-f', 'opus', | ||||
|              target_path | ||||
|         ] | ||||
|         return command | ||||
| @@ -132,7 +132,7 @@ class TestEncodingInDebugMode: | ||||
|  | ||||
|     @pytest.mark.parametrize("files, expected_command", [ | ||||
|         (("test.m4a", "test.mp3"), m4a_to_mp3_encoder_with_debug("test.m4a", "test.mp3")), | ||||
|         (("abc.m4a", "cba.webm"), m4a_to_webm_encoder_with_debug("abc.m4a", "cba.webm")), | ||||
|         (("abc.m4a", "cba.opus"), m4a_to_opus_encoder_with_debug("abc.m4a", "cba.opus")), | ||||
|         (("bla bla.m4a", "ble ble.m4a"), m4a_to_m4a_encoder_with_debug("bla bla.m4a", "ble ble.m4a")), | ||||
|         (("😛.m4a", "• tongue.flac"), m4a_to_flac_encoder_with_debug("😛.m4a", "• tongue.flac")), | ||||
|     ]) | ||||
| @@ -158,7 +158,7 @@ class TestEncodingAndTrimSilence: | ||||
|         ] | ||||
|         return command | ||||
|  | ||||
|     def m4a_to_webm_encoder_and_trim_silence(input_path, target_path): | ||||
|     def m4a_to_opus_encoder_and_trim_silence(input_path, target_path): | ||||
|         command = [ | ||||
|             'ffmpeg', '-y', '-nostdin', '-hide_banner', '-nostats', '-v', 'panic', | ||||
|             '-i', input_path, | ||||
| @@ -167,7 +167,7 @@ class TestEncodingAndTrimSilence: | ||||
|             '-b:a', '192k', | ||||
|             '-vn', | ||||
|             '-af', 'silenceremove=start_periods=1', | ||||
|             '-f', 'webm', | ||||
|             '-f', 'opus', | ||||
|             target_path | ||||
|         ] | ||||
|         return command | ||||
| @@ -201,7 +201,7 @@ class TestEncodingAndTrimSilence: | ||||
|  | ||||
|     @pytest.mark.parametrize("files, expected_command", [ | ||||
|         (("test.m4a", "test.mp3"), m4a_to_mp3_encoder_and_trim_silence("test.m4a", "test.mp3")), | ||||
|         (("abc.m4a", "cba.webm"), m4a_to_webm_encoder_and_trim_silence("abc.m4a", "cba.webm")), | ||||
|         (("abc.m4a", "cba.opus"), m4a_to_opus_encoder_and_trim_silence("abc.m4a", "cba.opus")), | ||||
|         (("bla bla.m4a", "ble ble.m4a"), m4a_to_m4a_encoder_and_trim_silence("bla bla.m4a", "ble ble.m4a")), | ||||
|         (("😛.m4a", "• tongue.flac"), m4a_to_flac_encoder_and_trim_silence("😛.m4a", "• tongue.flac")), | ||||
|     ]) | ||||
|   | ||||
| @@ -77,7 +77,7 @@ class TestMethods: | ||||
|     @pytest.mark.parametrize("filename, encoding", [ | ||||
|         ("example.m4a", "m4a"), | ||||
|         ("exampley.mp3", "mp3"), | ||||
|         ("test 123.webm", "webm"), | ||||
|         ("test 123.opus", "opus"), | ||||
|         ("flakey.flac", "flac"), | ||||
|     ]) | ||||
|     def test_get_encoding(self, encoderkid, filename, encoding): | ||||
|   | ||||
| @@ -10,7 +10,7 @@ class EmbedderBase(ABC): | ||||
|     The subclass must define the supported media file encoding | ||||
|     formats here using a static variable - such as: | ||||
|  | ||||
|     >>> supported_formats = ("mp3", "opus", "flac") | ||||
|     >>> supported_formats = ("mp3", "m4a", "flac") | ||||
|     """ | ||||
|     supported_formats = () | ||||
|  | ||||
| @@ -72,9 +72,9 @@ class EmbedderBase(ABC): | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def as_opus(self, path, metadata, cached_albumart=None): | ||||
|     def as_m4a(self, path, metadata, cached_albumart=None): | ||||
|         """ | ||||
|         Method for opus support. This method might be defined in | ||||
|         Method for m4a support. This method might be defined in | ||||
|         a subclass. | ||||
|  | ||||
|         Other methods for additional supported formats must also | ||||
|   | ||||
| @@ -37,7 +37,7 @@ for key in M4A_TAG_PRESET.keys(): | ||||
|  | ||||
|  | ||||
| class EmbedderDefault(EmbedderBase): | ||||
|     supported_formats = ("mp3", "opus", "flac") | ||||
|     supported_formats = ("mp3", "m4a", "flac") | ||||
|  | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
| @@ -102,10 +102,10 @@ class EmbedderDefault(EmbedderBase): | ||||
|  | ||||
|         audiofile.save(v2_version=3) | ||||
|  | ||||
|     def as_opus(self, path, cached_albumart=None): | ||||
|     def as_m4a(self, path, metadata, cached_albumart=None): | ||||
|         """ Embed metadata to M4A files. """ | ||||
|         audiofile = MP4(path) | ||||
|         self._embed_basic_metadata(audiofile, metadata, "opus", preset=M4A_TAG_PRESET) | ||||
|         self._embed_basic_metadata(audiofile, metadata, "m4a", preset=M4A_TAG_PRESET) | ||||
|         if metadata["year"]: | ||||
|             audiofile[M4A_TAG_PRESET["year"]] = metadata["year"] | ||||
|         provider = metadata["provider"] | ||||
|   | ||||
| @@ -117,11 +117,27 @@ class YouTubeStreams(StreamsBase): | ||||
|             request.add_header(*header) | ||||
|         return urllib.request.urlopen(request) | ||||
|  | ||||
|     def getbest(self): | ||||
|         return self.all[0] | ||||
|     def get(self, quality="best", preftype="automatic"): | ||||
|         if quality == "best": | ||||
|             return self.getbest(preftype=preftype) | ||||
|         elif quality == "worst": | ||||
|             return self.getworst(preftype=preftype) | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def getworst(self): | ||||
|         return self.all[-1] | ||||
|     def getbest(self, preftype="automatic"): | ||||
|         if preftype == "automatic": | ||||
|             return self.all[0] | ||||
|         for stream in self.all: | ||||
|             if stream["encoding"] == preftype: | ||||
|                 return stream | ||||
|  | ||||
|     def getworst(self, preftype="automatic"): | ||||
|         if preftype == "automatic": | ||||
|             return self.all[-1] | ||||
|         for stream in self.all[::-1]: | ||||
|             if stream["encoding"] == preftype: | ||||
|                 return stream | ||||
|  | ||||
|  | ||||
| class ProviderYouTube(ProviderBase): | ||||
|   | ||||
| @@ -62,7 +62,7 @@ class TestMethods: | ||||
|  | ||||
|     @pytest.mark.parametrize("fmt_method_suffix", ( | ||||
|         "as_mp3", | ||||
|         "as_opus", | ||||
|         "as_m4a", | ||||
|         "as_flac", | ||||
|     )) | ||||
|     def test_embed_formats(self, fmt_method_suffix, embedderkid): | ||||
|   | ||||
| @@ -37,9 +37,8 @@ class Track: | ||||
|     def _calculate_total_chunks(self, filesize): | ||||
|         return (filesize // self._chunksize) + 1 | ||||
|  | ||||
|     def download_while_re_encoding(self, target_path, target_encoding=None, | ||||
|     def download_while_re_encoding(self, stream, target_path, target_encoding=None, | ||||
|                                    encoder=EncoderFFmpeg(), show_progress=True): | ||||
|         stream = self.metadata["streams"].getbest() | ||||
|         total_chunks = self._calculate_total_chunks(stream["filesize"]) | ||||
|         process = encoder.re_encode_from_stdin( | ||||
|             stream["encoding"], | ||||
| @@ -54,8 +53,7 @@ class Track: | ||||
|         process.stdin.close() | ||||
|         process.wait() | ||||
|  | ||||
|     def download(self, target_path, show_progress=True): | ||||
|         stream = self.metadata["streams"].getbest() | ||||
|     def download(self, stream, target_path, show_progress=True): | ||||
|         total_chunks = self._calculate_total_chunks(stream["filesize"]) | ||||
|         response = stream["connection"] | ||||
|         with open(target_path, "wb") as fout: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user