summaryrefslogtreecommitdiff
path: root/tidal_dl_ng/cli.py
diff options
context:
space:
mode:
Diffstat (limited to 'tidal_dl_ng/cli.py')
-rw-r--r--tidal_dl_ng/cli.py240
1 files changed, 178 insertions, 62 deletions
diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py
index 5abda11..54cbb5e 100644
--- a/tidal_dl_ng/cli.py
+++ b/tidal_dl_ng/cli.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from collections.abc import Callable
from pathlib import Path
from typing import Annotated, Optional
@@ -23,6 +24,13 @@ 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):
@@ -31,6 +39,92 @@ def version_callback(value: bool):
raise typer.Exit()
+def _download(ctx: typer.Context, urls: list[str], try_login: bool = True) -> bool:
+ """Invokes download function and tracks progress.
+
+ :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.
+ 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)
+ 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,
@@ -54,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).
"""
@@ -136,90 +231,111 @@ def download(
help="List with URLs to download. One per line",
),
] = None,
-):
+) -> bool:
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).")
raise typer.Abort()
- # Call login method to validate the token.
- ctx.invoke(login, ctx)
+ return _download(ctx, urls)
- # 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)))
+@dl_fav_group.command(
+ name="tracks",
+ help="Download your favorite track collection.",
+)
+def download_fav_tracks(ctx: typer.Context) -> bool:
+ """Download your favorite track collection.
- urls_pos_last = len(urls) - 1
+ :param ctx: Typer context object.
+ :type ctx: typer.Context
+ :return: Download result.
+ :rtype: bool
+ """
+ # Method name
+ func_name_favorites: str = "tracks"
- for item in urls:
- media_type: MediaType | bool = False
+ return _download_fav_factory(ctx, func_name_favorites)
- # 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}")
+@dl_fav_group.command(
+ name="artists",
+ help="Download your favorite artist collection.",
+)
+def download_fav_artists(ctx: typer.Context) -> bool:
+ """Download your favorite artist collection.
- continue
+ :param ctx: Typer context object.
+ :type ctx: typer.Context
+ :return: Download result.
+ :rtype: bool
+ """
+ # Method name
+ func_name_favorites: str = "artists"
- # 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)
+ return _download_fav_factory(ctx, func_name_favorites)
- 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)
+@dl_fav_group.command(
+ name="albums",
+ help="Download your favorite album collection.",
+)
+def download_fav_albums(ctx: typer.Context) -> bool:
+ """Download your favorite album collection.
- 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,
- )
+ :param ctx: Typer context object.
+ :type ctx: typer.Context
+ :return: Download result.
+ :rtype: bool
+ """
+ # Method name
+ func_name_favorites: str = "albums"
- # Stop Progress display.
- progress.stop()
+ return _download_fav_factory(ctx, func_name_favorites)
- return True
+
+@dl_fav_group.command(
+ name="videos",
+ help="Download your favorite video collection.",
+)
+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) -> 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: Callable = getattr(ctx.obj[CTX_TIDAL].session.user.favorites, func_name_favorites)
+ # Get favorite videos
+ media_urls: [str] = [media.share_url for media in func_favorites()]
+
+ return _download(ctx, media_urls, try_login=False)
@app.command()