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: ref: extract CLI download logic to helper function --- tidal_dl_ng/cli.py | 158 ++++++++++++++++++++++++++++------------------------- 1 file changed, 85 insertions(+), 73 deletions(-) (limited to 'tidal_dl_ng/cli.py') 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() -- cgit v1.3.1 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: 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(+) (limited to 'tidal_dl_ng/cli.py') 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): -- cgit v1.3.1 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: feat: add dl_fav tracks/artists/albums/videos subcommands --- tidal_dl_ng/cli.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'tidal_dl_ng/cli.py') 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 -- cgit v1.3.1 From 3e93c17d1d1c2dc2591263170167b8e7467b9b68 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Sat, 4 Jan 2025 13:42:53 +0100 Subject: 👷 Refactored favorite download methods to factory. Removed duplicate and falsely named code. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/cli.py | 55 +++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 25 deletions(-) (limited to 'tidal_dl_ng/cli.py') diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index cdd1409..7a95f31 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -28,6 +28,7 @@ dl_fav_group = typer.Typer( add_completion=True, help="Download from a favorites collection.", ) + app.add_typer(dl_fav_group, name="dl_fav") @@ -37,7 +38,7 @@ def version_callback(value: bool): raise typer.Exit() -def _download(ctx: typer.Context, urls: list[str], try_login: bool = True): +def _download(ctx: typer.Context, urls: list[str], try_login: bool = True) -> bool: """ Shared logic for downloading a list of URLs. @@ -64,7 +65,7 @@ def _download(ctx: typer.Context, urls: list[str], try_login: bool = True): 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 + fn_logger=fn_logger, progress=progress, ) progress_table = Table.grid() @@ -226,7 +227,7 @@ def download( help="List with URLs to download. One per line", ), ] = None, -): +) -> bool: if not urls: # Read the text file provided. if file_urls: @@ -242,54 +243,58 @@ def download( @dl_fav_group.command( name="tracks", - help="Download your saved, favorite tracks collection.", + help="Download your favorite tracks collection.", ) def download_fav_tracks(ctx: typer.Context): - # Call login method to validate the token. - ctx.invoke(login, ctx) + # Method name + func_name_favorites: str = "tracks" - # 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) + return _download_fav_factory(ctx, func_name_favorites) @dl_fav_group.command( name="artists", - help="Download your saved, favorite artists collection.", + help="Download your favorite artists collection.", ) def download_fav_artists(ctx: typer.Context): - # Call login method to validate the token. - ctx.invoke(login, ctx) + # Method name + func_name_favorites: str = "artists" - # 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) + return _download_fav_factory(ctx, func_name_favorites) @dl_fav_group.command( name="albums", - help="Download your saved, favorite albums collection.", + help="Download your favorite albums collection.", ) def download_fav_albums(ctx: typer.Context): - # Call login method to validate the token. - ctx.invoke(login, ctx) + # Method name + func_name_favorites: str = "albums" - # 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) + return _download_fav_factory(ctx, func_name_favorites) @dl_fav_group.command( name="videos", - help="Download your saved, favorite videos collection.", + help="Download your favorite videos collection.", ) def download_fav_videos(ctx: typer.Context): + # Method name + func_name_favorites: str = "videos" + + return _download_fav_factory(ctx, func_name_favorites) + + +def _download_fav_factory(ctx: typer.Context, func_name_favorites: str): # 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) + # Get the method from the module + func_favorites = getattr(ctx.obj[CTX_TIDAL].session.user.favorites, func_name_favorites) + # Get favorite videos + media_urls = [media.share_url for media in func_favorites()] + + return _download(ctx, media_urls, try_login=False) @app.command() -- cgit v1.3.1 From a1e22acacce12780db0bec4faabea01ebf9f6ef1 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Sat, 4 Jan 2025 13:53:49 +0100 Subject: 👷 Added docstrings and types. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/cli.py | 63 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 13 deletions(-) (limited to 'tidal_dl_ng/cli.py') diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index 7a95f31..fedbe9a 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated, Optional, Callable import typer from rich.live import Live @@ -231,7 +231,7 @@ def download( if not urls: # Read the text file provided. if file_urls: - text = file_urls.read_text() + text: str = file_urls.read_text() urls = text.splitlines() else: print("Provide either URLs, IDs or a file containing URLs (one per line).") @@ -243,9 +243,16 @@ def download( @dl_fav_group.command( name="tracks", - help="Download your favorite tracks collection.", + help="Download your favorite track collection.", ) -def download_fav_tracks(ctx: typer.Context): +def download_fav_tracks(ctx: typer.Context) -> bool: + """Download your favorite track collection. + + :param ctx: Typer context object. + :type ctx: typer.Context + :return: Download result. + :rtype: bool + """ # Method name func_name_favorites: str = "tracks" @@ -254,9 +261,16 @@ def download_fav_tracks(ctx: typer.Context): @dl_fav_group.command( name="artists", - help="Download your favorite artists collection.", + help="Download your favorite artist collection.", ) -def download_fav_artists(ctx: typer.Context): +def download_fav_artists(ctx: typer.Context) -> bool: + """Download your favorite artist collection. + + :param ctx: Typer context object. + :type ctx: typer.Context + :return: Download result. + :rtype: bool + """ # Method name func_name_favorites: str = "artists" @@ -265,9 +279,16 @@ def download_fav_artists(ctx: typer.Context): @dl_fav_group.command( name="albums", - help="Download your favorite albums collection.", + help="Download your favorite album collection.", ) -def download_fav_albums(ctx: typer.Context): +def download_fav_albums(ctx: typer.Context) -> bool: + """Download your favorite album collection. + + :param ctx: Typer context object. + :type ctx: typer.Context + :return: Download result. + :rtype: bool + """ # Method name func_name_favorites: str = "albums" @@ -276,23 +297,39 @@ def download_fav_albums(ctx: typer.Context): @dl_fav_group.command( name="videos", - help="Download your favorite videos collection.", + help="Download your favorite video collection.", ) -def download_fav_videos(ctx: typer.Context): +def download_fav_videos(ctx: typer.Context) -> bool: + """Download your favorite video collection. + + :param ctx: Typer context object. + :type ctx: typer.Context + :return: Download result. + :rtype: bool + """ # Method name func_name_favorites: str = "videos" return _download_fav_factory(ctx, func_name_favorites) -def _download_fav_factory(ctx: typer.Context, func_name_favorites: str): +def _download_fav_factory(ctx: typer.Context, func_name_favorites: str) -> bool: + """Factory which helps to download items from the favorites collections. + + :param ctx: Typer context object. + :type ctx: typer.Context + :param func_name_favorites: Method name to call from `tidalapi` favorites object. + :type func_name_favorites: str + :return: Download result. + :rtype: bool + """ # Call login method to validate the token. ctx.invoke(login, ctx) # Get the method from the module - func_favorites = getattr(ctx.obj[CTX_TIDAL].session.user.favorites, func_name_favorites) + func_favorites: Callable = getattr(ctx.obj[CTX_TIDAL].session.user.favorites, func_name_favorites) # Get favorite videos - media_urls = [media.share_url for media in func_favorites()] + media_urls: [str] = [media.share_url for media in func_favorites()] return _download(ctx, media_urls, try_login=False) -- cgit v1.3.1 From e78194353a1caca3b2aa3d38c1c3ff5f08ebe2c3 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Sat, 4 Jan 2025 13:58:15 +0100 Subject: 👷 Formatting and docstrings. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/cli.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'tidal_dl_ng/cli.py') diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index fedbe9a..54cbb5e 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -1,6 +1,7 @@ #!/usr/bin/env python +from collections.abc import Callable from pathlib import Path -from typing import Annotated, Optional, Callable +from typing import Annotated, Optional import typer from rich.live import Live @@ -39,14 +40,16 @@ def version_callback(value: bool): def _download(ctx: typer.Context, urls: list[str], try_login: bool = True) -> bool: - """ - Shared logic for downloading a list of URLs. + """Invokes download function and tracks progress. - :param ctx: The typer context + :param ctx: The typer context object. :type ctx: typer.Context :param urls: The list of URLs to download. :type urls: list[str] + :param try_login: If true, attempts to login to TIDAL. + :type try_login: bool :return: True if ran successfully. + :rtype: bool """ if try_login: # Call login method to validate the token. @@ -145,6 +148,7 @@ def settings_management( If only one argument is given, the value will be printed for this option. To set a value for an option simply pass the value as the second argument + :param editor: If set, your favorite system editor will be opened. :param names: (Optional) None (list all options), one (list the value only for this option) or two arguments (set the value for the option). """ -- cgit v1.3.1