From ca0f696119708eaf28fba26c5cce54a5976910c4 Mon Sep 17 00:00:00 2001 From: V3ntus <29584644+V3ntus@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:20:02 +0000 Subject: [PATCH 1/4] ref: extract CLI download logic to helper function --- tidal_dl_ng/cli.py | 158 ++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 73 deletions(-) diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index 5abda11..4c33dcf 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -31,6 +31,90 @@ def version_callback(value: bool): raise typer.Exit() +def _download(ctx: typer.Context, urls: list[str], try_login: bool = True): + """ + Shared logic for downloading a list of URLs. + + :param ctx: The typer context + :type ctx: typer.Context + :param urls: The list of URLs to download. + :type urls: list[str] + :return: True if ran successfully. + """ + if try_login: + # Call login method to validate the token. + ctx.invoke(login, ctx) + + # Create initial objects. + settings: Settings = Settings() + progress: Progress = Progress( + "{task.description}", + SpinnerColumn(), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + ) + fn_logger = LoggerWrapped(progress.print) + dl = Download( + session=ctx.obj[CTX_TIDAL].session, + skip_existing=ctx.obj[CTX_TIDAL].settings.data.skip_existing, + path_base=settings.data.download_base_path, + fn_logger=fn_logger, # noqa: type + progress=progress, + ) + progress_table = Table.grid() + + # Style Progress display. + progress_table.add_row(Panel.fit(progress, title="Download Progress", border_style="green", padding=(2, 2))) + + urls_pos_last = len(urls) - 1 + + for item in urls: + media_type: MediaType | bool = False + + # Extract media name and id from link. + if "http" in item: + media_type = get_tidal_media_type(item) + item_id = get_tidal_media_id(item) + file_template = get_format_template(media_type, settings) + else: + print(f"It seems like that you have supplied an invalid URL: {item}") + + continue + + # Create Live display for Progress. + with Live(progress_table, refresh_per_second=10): + # Download media. + if media_type in [MediaType.TRACK, MediaType.VIDEO]: + download_delay: bool = bool(settings.data.download_delay and urls.index(item) < urls_pos_last) + + dl.item( + media_id=item_id, media_type=media_type, file_template=file_template, download_delay=download_delay + ) + elif media_type in [MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX, MediaType.ARTIST]: + item_ids: [int] = [] + + if media_type == MediaType.ARTIST: + media = instantiate_media(ctx.obj[CTX_TIDAL].session, media_type, item_id) + media_type = MediaType.ALBUM + item_ids = item_ids + all_artist_album_ids(media) + else: + item_ids.append(item_id) + + for item_id in item_ids: + dl.items( + media_id=item_id, + media_type=media_type, + file_template=file_template, + video_download=ctx.obj[CTX_TIDAL].settings.data.video_download, + download_delay=settings.data.download_delay, + ) + + # Stop Progress display. + progress.stop() + + return True + + @app.callback() def callback_app( ctx: typer.Context, @@ -147,79 +231,7 @@ def download( raise typer.Abort() - # Call login method to validate the token. - ctx.invoke(login, ctx) - - # Create initial objects. - settings: Settings = Settings() - progress: Progress = Progress( - "{task.description}", - SpinnerColumn(), - BarColumn(), - TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), - ) - fn_logger = LoggerWrapped(progress.print) - dl = Download( - session=ctx.obj[CTX_TIDAL].session, - skip_existing=ctx.obj[CTX_TIDAL].settings.data.skip_existing, - path_base=settings.data.download_base_path, - fn_logger=fn_logger, - progress=progress, - ) - progress_table = Table.grid() - - # Style Progress display. - progress_table.add_row(Panel.fit(progress, title="Download Progress", border_style="green", padding=(2, 2))) - - urls_pos_last = len(urls) - 1 - - for item in urls: - media_type: MediaType | bool = False - - # Extract media name and id from link. - if "http" in item: - media_type = get_tidal_media_type(item) - item_id = get_tidal_media_id(item) - file_template = get_format_template(media_type, settings) - - # If url is invalid skip to next url in list. - if not media_type: - print(f"It seems like that you have supplied an invalid URL: {item}") - - continue - - # Create Live display for Progress. - with Live(progress_table, refresh_per_second=10): - # Download media. - if media_type in [MediaType.TRACK, MediaType.VIDEO]: - download_delay: bool = bool(settings.data.download_delay and urls.index(item) < urls_pos_last) - - dl.item( - media_id=item_id, media_type=media_type, file_template=file_template, download_delay=download_delay - ) - elif media_type in [MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX, MediaType.ARTIST]: - item_ids: [int] = [] - - if media_type == MediaType.ARTIST: - media = instantiate_media(ctx.obj[CTX_TIDAL].session, media_type, item_id) - media_type = MediaType.ALBUM - item_ids = item_ids + all_artist_album_ids(media) - else: - item_ids.append(item_id) - - for item_id in item_ids: - dl.items( - media_id=item_id, - media_type=media_type, - file_template=file_template, - video_download=ctx.obj[CTX_TIDAL].settings.data.video_download, - download_delay=settings.data.download_delay, - ) - - # Stop Progress display. - progress.stop() - - return True + return _download(ctx, urls) @app.command() From 31b260f690ebc476585bb1334dbfadef0d409b50 Mon Sep 17 00:00:00 2001 From: V3ntus <29584644+V3ntus@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:23:42 +0000 Subject: [PATCH 2/4] ref: add a `dl_fav` typer command group This will be used for downloading from favorite collections (artists, albums, tracks, videos) --- tidal_dl_ng/cli.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index 4c33dcf..92e36cf 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -23,6 +23,12 @@ from tidal_dl_ng.helper.wrapper import LoggerWrapped from tidal_dl_ng.model.cfg import HelpSettings app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}, add_completion=False) +dl_fav_group = typer.Typer( + context_settings={"help_option_names": ["-h", "--help"]}, + add_completion=True, + help="Download from a favorites collection.", +) +app.add_typer(dl_fav_group, name="dl_fav") def version_callback(value: bool): From 6f21eaee76d32e74f43ad857b6f3e059b3ef00a6 Mon Sep 17 00:00:00 2001 From: V3ntus <29584644+V3ntus@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:28:32 +0000 Subject: [PATCH 3/4] feat: add dl_fav tracks/artists/albums/videos subcommands --- tidal_dl_ng/cli.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index 92e36cf..cdd1409 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -240,6 +240,58 @@ def download( return _download(ctx, urls) +@dl_fav_group.command( + name="tracks", + help="Download your saved, favorite tracks collection.", +) +def download_fav_tracks(ctx: typer.Context): + # Call login method to validate the token. + ctx.invoke(login, ctx) + + # Get favorite tracks + track_urls = [track.share_url for track in ctx.obj[CTX_TIDAL].session.user.favorites.tracks()] + return _download(ctx, track_urls, try_login=False) + + +@dl_fav_group.command( + name="artists", + help="Download your saved, favorite artists collection.", +) +def download_fav_artists(ctx: typer.Context): + # Call login method to validate the token. + ctx.invoke(login, ctx) + + # Get favorite artists + artist_urls = [artist.share_url for artist in ctx.obj[CTX_TIDAL].session.user.favorites.artists()] + return _download(ctx, artist_urls, try_login=False) + + +@dl_fav_group.command( + name="albums", + help="Download your saved, favorite albums collection.", +) +def download_fav_albums(ctx: typer.Context): + # Call login method to validate the token. + ctx.invoke(login, ctx) + + # Get favorite artists + artist_urls = [artist.share_url for artist in ctx.obj[CTX_TIDAL].session.user.favorites.albums()] + return _download(ctx, artist_urls, try_login=False) + + +@dl_fav_group.command( + name="videos", + help="Download your saved, favorite videos collection.", +) +def download_fav_videos(ctx: typer.Context): + # Call login method to validate the token. + ctx.invoke(login, ctx) + + # Get favorite artists + artist_urls = [artist.share_url for artist in ctx.obj[CTX_TIDAL].session.user.favorites.videos()] + return _download(ctx, artist_urls, try_login=False) + + @app.command() def gui(ctx: typer.Context): from tidal_dl_ng.gui import gui_activate From bed812d69e14cb5211ae1380d6d7dfa244d7edcd Mon Sep 17 00:00:00 2001 From: V3ntus <29584644+V3ntus@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:34:02 +0000 Subject: [PATCH 4/4] doc: update README with new dl_fav usages --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c820b3b..cd21b90 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ $ tidal-dl-ng --help │ for this option. To set a value for an option simply pass the value │ │ as the second argument │ │ dl │ +│ dl_fav Download from a favorites collection. │ │ gui │ │ login │ ╰──────────────────────────────────────────────────────────────────────────────╯ @@ -48,7 +49,9 @@ pip install --upgrade tidal-dl-ng pip install --upgrade tidal-dl-ng[gui] ``` -You can use the command line (CLI) version to download media: +## ⌨️ Usage + +You can use the command line (CLI) version to download media by URL: ```bash tidal-dl-ng dl https://tidal.com/browse/track/46755209 @@ -56,6 +59,15 @@ tidal-dl-ng dl https://tidal.com/browse/track/46755209 tdn dl https://tidal.com/browse/track/46755209 ``` +Or by your favorites collections: + +```bash +tidal-dl-ng dl_fav tracks +tidal-dl-ng dl_fav artists +tidal-dl-ng dl_fav albums +tidal-dl-ng dl_fav videos +``` + But also the GUI: ```bash @@ -71,7 +83,7 @@ If you like to have the GUI version only as a binary, have a look at the ## 🧁 Features -- Download Tracks, Videos, Albums, Playlists etc. +- Download Tracks, Videos, Albums, Playlists, Favorite Tracks/Artists/Albums/Videos, etc. - Multithreaded and multi-chunked downloads - Metadata for songs - Adjustable audio and video download quality.