diff --git a/spotdl/__init__.py b/spotdl/__init__.py index ce2549ba7..8a34499b4 100644 --- a/spotdl/__init__.py +++ b/spotdl/__init__.py @@ -99,7 +99,12 @@ def search(self, query: List[str]) -> List[Song]: - query can be a list of song titles, urls, uris """ - return parse_query(query, self.downloader.settings["threads"], self.downloader.settings["ytm_data"], self.downloader.settings["playlist_numbering"]) + return parse_query( + query, + self.downloader.settings["threads"], + self.downloader.settings["ytm_data"], + self.downloader.settings["playlist_numbering"], + ) def get_download_urls(self, songs: List[Song]) -> List[Optional[str]]: """ diff --git a/spotdl/console/download.py b/spotdl/console/download.py index a879c7bd8..f14199dd9 100644 --- a/spotdl/console/download.py +++ b/spotdl/console/download.py @@ -22,7 +22,11 @@ def download( """ # Parse the query - songs = get_simple_songs(query, use_ytm_data=downloader.settings["ytm_data"], playlist_numbering=downloader.settings["playlist_numbering"]) + songs = get_simple_songs( + query, + use_ytm_data=downloader.settings["ytm_data"], + playlist_numbering=downloader.settings["playlist_numbering"], + ) # Download the songs downloader.download_multiple_songs(songs) diff --git a/spotdl/console/save.py b/spotdl/console/save.py index 665b8c003..72ba9f13a 100644 --- a/spotdl/console/save.py +++ b/spotdl/console/save.py @@ -39,7 +39,12 @@ def save( raise DownloaderError("Save file is not specified") # Parse the query - songs = parse_query(query, downloader.settings["threads"], downloader.settings["ytm_data"], downloader.settings["playlist_numbering"]) + songs = parse_query( + query, + downloader.settings["threads"], + downloader.settings["ytm_data"], + downloader.settings["playlist_numbering"], + ) save_data = [song.json for song in songs] def process_song(song: Song): diff --git a/spotdl/console/sync.py b/spotdl/console/sync.py index 9333e866f..9cf313df3 100644 --- a/spotdl/console/sync.py +++ b/spotdl/console/sync.py @@ -49,7 +49,12 @@ def sync( ) # Parse the query - songs_list = parse_query(query, downloader.settings["threads"], downloader.settings["ytm_data"], downloader.settings["playlist_numbering"]) + songs_list = parse_query( + query, + downloader.settings["threads"], + downloader.settings["ytm_data"], + downloader.settings["playlist_numbering"], + ) # Create sync file with open(save_path, "w", encoding="utf-8") as save_file: @@ -91,7 +96,12 @@ def sync( raise ValueError("Sync file is not a valid sync file.") # Parse the query - songs_playlist = parse_query(sync_data["query"], downloader.settings["threads"], downloader.settings["ytm_data"], downloader.settings["playlist_numbering"]) + songs_playlist = parse_query( + sync_data["query"], + downloader.settings["threads"], + downloader.settings["ytm_data"], + downloader.settings["playlist_numbering"], + ) # Get the names and URLs of previously downloaded songs from the sync file old_files = [] diff --git a/spotdl/console/url.py b/spotdl/console/url.py index d8807bd11..cd92a095a 100644 --- a/spotdl/console/url.py +++ b/spotdl/console/url.py @@ -27,7 +27,12 @@ def url( """ # Parse the query - songs = parse_query(query, downloader.settings["threads"], downloader.settings["ytm_data"], downloader.settings["playlist_numbering"]) + songs = parse_query( + query, + downloader.settings["threads"], + downloader.settings["ytm_data"], + downloader.settings["playlist_numbering"], + ) def process_song(song: Song): try: diff --git a/spotdl/download/downloader.py b/spotdl/download/downloader.py index d5a44c8f5..c3cc12dc2 100644 --- a/spotdl/download/downloader.py +++ b/spotdl/download/downloader.py @@ -279,6 +279,14 @@ def download_multiple_songs( for error in self.errors: logger.error(error) + if self.settings["save_errors"]: + with open( + self.settings["save_errors"], "w", encoding="utf-8" + ) as error_file: + error_file.write("\n".join(self.errors)) + + logger.info("Saved errors to %s", self.settings["save_errors"]) + # Save archive if self.settings["archive"]: for result in results: diff --git a/spotdl/types/options.py b/spotdl/types/options.py index 00d3869f1..70f38cd8b 100644 --- a/spotdl/types/options.py +++ b/spotdl/types/options.py @@ -76,6 +76,7 @@ class DownloaderOptions(TypedDict): max_filename_length: Optional[int] yt_dlp_args: Optional[str] detect_formats: Optional[str] + save_errors: Optional[str] class WebOptions(TypedDict): @@ -153,6 +154,7 @@ class DownloaderOptionalOptions(TypedDict, total=False): max_filename_length: Optional[int] yt_dlp_args: Optional[str] detect_formats: Optional[str] + save_errors: Optional[str] class WebOptionalOptions(TypedDict, total=False): diff --git a/spotdl/utils/arguments.py b/spotdl/utils/arguments.py index 2d25b55df..c2433a21f 100644 --- a/spotdl/utils/arguments.py +++ b/spotdl/utils/arguments.py @@ -406,6 +406,13 @@ def parse_output_options(parser: _ArgumentGroup): help="Print errors (wrong songs, failed downloads etc) on exit, useful for long playlist", ) + # Option to save errors to a file + parser.add_argument( + "--save-errors", + type=str, + help="Save errors (wrong songs, failed downloads etc) to a file", + ) + # Option to use sponsor block parser.add_argument( "--sponsor-block", diff --git a/spotdl/utils/config.py b/spotdl/utils/config.py index cb21a2ee4..d83629463 100644 --- a/spotdl/utils/config.py +++ b/spotdl/utils/config.py @@ -296,6 +296,7 @@ def modernize_settings(options: DownloaderOptions): "max_filename_length": None, "yt_dlp_args": None, "detect_formats": None, + "save_errors": None, } WEB_OPTIONS: WebOptions = { diff --git a/spotdl/utils/search.py b/spotdl/utils/search.py index f4d789227..29816ee80 100644 --- a/spotdl/utils/search.py +++ b/spotdl/utils/search.py @@ -92,7 +92,9 @@ def parse_query( - List of song objects """ - songs: List[Song] = get_simple_songs(query, use_ytm_data=use_ytm_data, playlist_numbering=playlist_numbering) + songs: List[Song] = get_simple_songs( + query, use_ytm_data=use_ytm_data, playlist_numbering=playlist_numbering + ) results = [] with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: