Merge pull request #297 from V3ntus/master

feat: allow downloading favorite tracks, artists, albums, and videos collections
This commit is contained in:
exislow
2025-01-04 13:15:49 +01:00
committed by GitHub
2 changed files with 145 additions and 63 deletions
+14 -2
View File
@@ -28,6 +28,7 @@ $ tidal-dl-ng --help
│ for this option. To set a value for an option simply pass the value │ │ for this option. To set a value for an option simply pass the value │
│ as the second argument │ │ as the second argument │
│ dl │ │ dl │
│ dl_fav Download from a favorites collection. │
│ gui │ │ gui │
│ login │ │ login │
╰──────────────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────╯
@@ -48,7 +49,9 @@ pip install --upgrade tidal-dl-ng
pip install --upgrade tidal-dl-ng[gui] 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 ```bash
tidal-dl-ng dl https://tidal.com/browse/track/46755209 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 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: But also the GUI:
```bash ```bash
@@ -71,7 +83,7 @@ If you like to have the GUI version only as a binary, have a look at the
## 🧁 Features ## 🧁 Features
- Download Tracks, Videos, Albums, Playlists etc. - Download Tracks, Videos, Albums, Playlists, Favorite Tracks/Artists/Albums/Videos, etc.
- Multithreaded and multi-chunked downloads - Multithreaded and multi-chunked downloads
- Metadata for songs - Metadata for songs
- Adjustable audio and video download quality. - Adjustable audio and video download quality.
+131 -61
View File
@@ -23,6 +23,12 @@ from tidal_dl_ng.helper.wrapper import LoggerWrapped
from tidal_dl_ng.model.cfg import HelpSettings from tidal_dl_ng.model.cfg import HelpSettings
app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}, add_completion=False) 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): def version_callback(value: bool):
@@ -31,6 +37,90 @@ def version_callback(value: bool):
raise typer.Exit() 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() @app.callback()
def callback_app( def callback_app(
ctx: typer.Context, ctx: typer.Context,
@@ -147,79 +237,59 @@ def download(
raise typer.Abort() raise typer.Abort()
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. # Call login method to validate the token.
ctx.invoke(login, ctx) ctx.invoke(login, ctx)
# Create initial objects. # Get favorite tracks
settings: Settings = Settings() track_urls = [track.share_url for track in ctx.obj[CTX_TIDAL].session.user.favorites.tracks()]
progress: Progress = Progress( return _download(ctx, track_urls, try_login=False)
"{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 @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)
for item in urls: # Get favorite artists
media_type: MediaType | bool = False artist_urls = [artist.share_url for artist in ctx.obj[CTX_TIDAL].session.user.favorites.artists()]
return _download(ctx, artist_urls, try_login=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. @dl_fav_group.command(
if not media_type: name="albums",
print(f"It seems like that you have supplied an invalid URL: {item}") 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)
continue # 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)
# 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( @dl_fav_group.command(
media_id=item_id, media_type=media_type, file_template=file_template, download_delay=download_delay name="videos",
) help="Download your saved, favorite videos collection.",
elif media_type in [MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX, MediaType.ARTIST]: )
item_ids: [int] = [] def download_fav_videos(ctx: typer.Context):
# Call login method to validate the token.
ctx.invoke(login, ctx)
if media_type == MediaType.ARTIST: # Get favorite artists
media = instantiate_media(ctx.obj[CTX_TIDAL].session, media_type, item_id) artist_urls = [artist.share_url for artist in ctx.obj[CTX_TIDAL].session.user.favorites.videos()]
media_type = MediaType.ALBUM return _download(ctx, artist_urls, try_login=False)
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.command() @app.command()