Merge pull request #297 from V3ntus/master
feat: allow downloading favorite tracks, artists, albums, and videos collections
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
+133
-63
@@ -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(),
|
@dl_fav_group.command(
|
||||||
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
name="artists",
|
||||||
|
help="Download your saved, favorite artists collection.",
|
||||||
)
|
)
|
||||||
fn_logger = LoggerWrapped(progress.print)
|
def download_fav_artists(ctx: typer.Context):
|
||||||
dl = Download(
|
# Call login method to validate the token.
|
||||||
session=ctx.obj[CTX_TIDAL].session,
|
ctx.invoke(login, ctx)
|
||||||
skip_existing=ctx.obj[CTX_TIDAL].settings.data.skip_existing,
|
|
||||||
path_base=settings.data.download_base_path,
|
# Get favorite artists
|
||||||
fn_logger=fn_logger,
|
artist_urls = [artist.share_url for artist in ctx.obj[CTX_TIDAL].session.user.favorites.artists()]
|
||||||
progress=progress,
|
return _download(ctx, artist_urls, try_login=False)
|
||||||
|
|
||||||
|
|
||||||
|
@dl_fav_group.command(
|
||||||
|
name="albums",
|
||||||
|
help="Download your saved, favorite albums collection.",
|
||||||
)
|
)
|
||||||
progress_table = Table.grid()
|
def download_fav_albums(ctx: typer.Context):
|
||||||
|
# Call login method to validate the token.
|
||||||
|
ctx.invoke(login, ctx)
|
||||||
|
|
||||||
# Style Progress display.
|
# Get favorite artists
|
||||||
progress_table.add_row(Panel.fit(progress, title="Download Progress", border_style="green", padding=(2, 2)))
|
artist_urls = [artist.share_url for artist in ctx.obj[CTX_TIDAL].session.user.favorites.albums()]
|
||||||
|
return _download(ctx, artist_urls, try_login=False)
|
||||||
|
|
||||||
urls_pos_last = len(urls) - 1
|
|
||||||
|
|
||||||
for item in urls:
|
@dl_fav_group.command(
|
||||||
media_type: MediaType | bool = False
|
name="videos",
|
||||||
|
help="Download your saved, favorite videos collection.",
|
||||||
# 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]:
|
def download_fav_videos(ctx: typer.Context):
|
||||||
item_ids: [int] = []
|
# 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()
|
||||||
|
|||||||
Reference in New Issue
Block a user