diff --git a/tiddl/cli/download/__init__.py b/tiddl/cli/download/__init__.py index 25f8e5e..2d4286c 100644 --- a/tiddl/cli/download/__init__.py +++ b/tiddl/cli/download/__init__.py @@ -96,10 +96,17 @@ from typing import List, Union help="Enable downloading videos", ) @click.option( - "--scan_path", + "--scan-path", "SCAN_PATH", type=str, - help="Base music directory to scan for existing. Default is 'path'", + help="Base directory to scan for existing tracks. Default is 'path'", +) +@click.option( + "--save-m3u", + "-m3u", + "SAVE_M3U", + is_flag=True, + help="Save M3U file for playlists.", ) @passContext def DownloadCommand( @@ -113,6 +120,7 @@ def DownloadCommand( EMBED_LYRICS: bool, DOWNLOAD_VIDEO: bool, SCAN_PATH: str | None, + SAVE_M3U: bool, ): """Download resources""" DOWNLOAD_VIDEO = DOWNLOAD_VIDEO or ctx.obj.config.download.download_video @@ -131,6 +139,7 @@ def DownloadCommand( EMBED_LYRICS, DOWNLOAD_VIDEO, SCAN_PATH, + SAVE_M3U, ) ) @@ -371,7 +380,7 @@ def DownloadCommand( offset += album_items.limit def handleResource(resource: TidalResource) -> None: - logging.debug(f"Handling Resource '{resource}'") + logging.debug(f"'{resource}'") match resource.type: case "track": @@ -428,7 +437,7 @@ def DownloadCommand( case "playlist": playlist = api.getPlaylist(resource.id) - logging.info(f"Playlist {playlist.title!r}") + logging.info(f"downloading playlist {playlist.title!r}") offset = 0 playlist_path = None playlist_tracks: dict[str, Track] = {} @@ -462,7 +471,7 @@ def DownloadCommand( path = Path(PATH) if PATH else ctx.obj.config.download.path - if playlist_path: + if playlist_path and SAVE_M3U: savePlaylistM3U( playlist_tracks=playlist_tracks, path=path / playlist_path, diff --git a/tiddl/config.py b/tiddl/config.py index 5ca007f..60a224e 100644 --- a/tiddl/config.py +++ b/tiddl/config.py @@ -31,6 +31,7 @@ class DownloadConfig(BaseModel): embed_lyrics: bool = False download_video: bool = False scan_path: Path | None = path + save_playlist_m3u: bool = False class AuthConfig(BaseModel): diff --git a/tiddl/utils.py b/tiddl/utils.py index f976957..76ee06d 100644 --- a/tiddl/utils.py +++ b/tiddl/utils.py @@ -3,6 +3,7 @@ import os import logging from ffmpeg_asyncio import FFmpeg +from ffmpeg_asyncio.types import Option as FFmpegOption from pydantic import BaseModel from urllib.parse import urlparse @@ -184,7 +185,6 @@ def findTrackFilename( return full_file_name - async def convertFileExtension( source_file: Path, extension: str, @@ -210,9 +210,11 @@ async def convertFileExtension( logging.debug("Conversion not required, already %s", extension) return source_file - ffmpeg_args = {"loglevel": "error"} + ffmpeg_args: dict[str, FFmpegOption | None] = {"loglevel": "error"} + if copy_audio: ffmpeg_args["acodec"] = "copy" + if is_video: ffmpeg_args["vcodec"] = "copy" @@ -220,21 +222,23 @@ async def convertFileExtension( logging.debug("Trying conversion") ffmpeg = FFmpeg().option("y") ffmpeg.input(str(source_file)) - ffmpeg.output(str(output_file), **ffmpeg_args) + ffmpeg.output(str(output_file), ffmpeg_args) @ffmpeg.on("completed") def on_completed(): - logging.debug("Conversion successful for: %s", output_file) + logging.debug(f"converted {output_file}") if remove_source: try: os.remove(source_file) except OSError as e: - logging.error(f"Error removing source file {source_file}: {e}") + logging.error(f"can't remove source file {source_file}: {e}") await ffmpeg.execute() + except Exception as e: - logging.error(f"FFMPEG Error during conversion of {source_file}: {e}") + logging.error(f"can't convert file {source_file}: {e}") return source_file + return output_file @@ -242,13 +246,23 @@ def savePlaylistM3U( playlist_tracks: dict[str, Track], path: Path, filename="playlist.m3u" ): file = path / filename + logging.debug(f"saving m3u file at {file}") if not playlist_tracks: + logging.warning(f"playlist {file} is empty") return - with file.open("w", encoding="utf-8") as f: - f.write("#EXTM3U\n") - for track_path, track in playlist_tracks.items(): - f.write( - f"#EXTINF:{track.duration},{track.artist.name} - {track.title}\n{track_path}\n" + try: + with file.open("w", encoding="utf-8") as f: + f.write("#EXTM3U\n") + for track_path, track in playlist_tracks.items(): + f.write( + f"#EXTINF:{track.duration},{track.artist.name if track.artist else ''} - {track.title}\n{track_path}\n" + ) + + logging.debug( + f"saved m3u file as {file} with {len(playlist_tracks)} tracks" ) + + except Exception as e: + logging.error(f"can't save playlist m3u file: {e}")