exislow / tidal-dl-ng

TIDAL Media Downloader Next Generation! Up to HiRes Lossless / TIDAL MAX 24-bit, 192 kHz.
GNU Affero General Public License v3.0
354 stars 34 forks source link

[Feature] Sort Albums by Volumes/CDs #90

Closed motlaghreyhan closed 5 months ago

motlaghreyhan commented 5 months ago

Current Situation

85

Currently if you download an album with the tracklist separated into "Volumes", you get duplicate tracks numbers.

Current behavior:

tidal-dl-ng dl https://tidal.com/browse/album/118390427?u
ls The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/
...
'03 - The Beatles - Come And Get It (Studio Demo).m4a'
'03 - The Beatles - Maxwell'\''s Silver Hammer (2019 Mix).m4a'
'03 - The Beatles - Something (Studio Demo).m4a'
...

Settings:

"format_album": "{album_artist}/{album_title} [{album_year}]/{album_track_num} - {artist_name} - {track_title}"

"format_track": "{album_artist}/{album_title} [{album_year}]/{album_track_num} - {artist_name} - {track_title}"

Suggestion / Feature Request

There should be a variable in the path format config like {volume_num} that uses track.volume_num (I think) from tidal. I believe tidal-dl somehow figures this out automatically, because the CD1/CD2 directories appear automatically when needed and don't exist otherwise. But a {volume_num} variable would be all that's needed.

Resulting files should be structured like shown, with :

The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/CD1/'03 - The Beatles - Maxwell'\''s Silver Hammer (2019 Mix).m4a'
The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/CD2/'03 - The Beatles - Something (Studio Demo).m4a'
The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/CD3/'03 - The Beatles - Come And Get It (Studio Demo).m4a'

Example Settings:

"format_album": "{album_artist}/{album_title} [{album_year}]/{volume_num}/{album_track_num} - {artist_name} - {track_title}"

"format_track": "{album_artist}/{album_title} [{album_year}]/{volume_num}/{album_track_num} - {artist_name} - {track_title}"
exislow commented 5 months ago

album_num_volumes and track_volume_num is implemented in the master branch.

motlaghreyhan commented 5 months ago

Thanks! I do get errors when using latest master and having those variables in my path. It looks like they need to be strings not integers?

╭───────────────────────────── Traceback (most recent call last) ─────────────────────────────╮
│ /home/rmotlagh/tidal-dl-ng/tidal_dl_ng/cli.py:184 in download                               │
│                                                                                             │
│   181 │   │   │   │   │   media_id=item_id, media_type=media_type, file_template=file_templ │
│   182 │   │   │   │   )                                                                     │
│   183 │   │   │   elif media_type in [MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX]:  │
│ ❱ 184 │   │   │   │   dl.items(                                                             │
│   185 │   │   │   │   │   media_id=item_id,                                                 │
│   186 │   │   │   │   │   media_type=media_type,                                            │
│   187 │   │   │   │   │   file_template=file_template,                                      │
│                                                                                             │
│ ╭──────────────────────────────────────── locals ─────────────────────────────────────────╮ │
│ │            ctx = <click.core.Context object at 0x7f9938573e50>                          │ │
│ │             dl = <tidal_dl_ng.download.Download object at 0x7f993858d4d0>               │ │
│ │  file_template = '{album_artist}/{album_title}                                          │ │
│ │                  [{album_year}]/{album_num_volumes}/{album_track_num'+33                │ │
│ │      file_urls = None                                                                   │ │
│ │      fn_logger = <tidal_dl_ng.helper.wrapper.LoggerWrapped object at 0x7f993858e890>    │ │
│ │           item = 'https://tidal.com/browse/album/118390427?u'                           │ │
│ │        item_id = '118390427'                                                            │ │
│ │     media_type = <MediaType.ALBUM: 'album'>                                             │ │
│ │       progress = <rich.progress.Progress object at 0x7f9938572ed0>                      │ │
│ │ progress_table = <rich.table.Table object at 0x7f9938254250>                            │ │
│ │       settings = <tidal_dl_ng.config.Settings object at 0x7f9938241150>                 │ │
│ │           urls = ['https://tidal.com/browse/album/118390427?u']                         │ │
│ │  urls_pos_last = 0                                                                      │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                             │
│ /home/rmotlagh/tidal-dl-ng/tidal_dl_ng/download.py:355 in items                             │
│                                                                                             │
│   352 │   │   │   raise MediaMissing                                                        │
│   353 │   │                                                                                 │
│   354 │   │   # Create file name and path                                                   │
│ ❱ 355 │   │   file_name_relative = format_path_media(file_template, media)                  │
│   356 │   │                                                                                 │
│   357 │   │   # Get the name of the list and check, if videos should be included.           │
│   358 │   │   videos_include: bool = True                                                   │
│                                                                                             │
│ ╭──────────────────────────────────────── locals ─────────────────────────────────────────╮ │
│ │ download_delay = True                                                                   │ │
│ │  file_template = '{album_artist}/{album_title}                                          │ │
│ │                  [{album_year}]/{album_num_volumes}/{album_track_num'+33                │ │
│ │          media = <tidalapi.album.Album object at 0x7f993859f010>                        │ │
│ │       media_id = '118390427'                                                            │ │
│ │     media_type = <MediaType.ALBUM: 'album'>                                             │ │
│ │           self = <tidal_dl_ng.download.Download object at 0x7f993858d4d0>               │ │
│ │ video_download = False                                                                  │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                             │
│ /home/rmotlagh/tidal-dl-ng/tidal_dl_ng/helper/path.py:58 in format_path_media               │
│                                                                                             │
│    55 │   │   result_fmt = format_str_media(match.group(1), media)                          │
│    56 │   │                                                                                 │
│    57 │   │   if result_fmt != match.group(1):                                              │
│ ❱  58 │   │   │   value = sanitize_filename(result_fmt)                                     │
│    59 │   │   │   result = result.replace(template_str, value)                              │
│    60 │                                                                                     │
│    61 │   return result                                                                     │
│                                                                                             │
│ ╭──────────────────────────────────────── locals ─────────────────────────────────────────╮ │
│ │    _matchNum = 4                                                                        │ │
│ │ fmt_template = '{album_artist}/{album_title}                                            │ │
│ │                [{album_year}]/{album_num_volumes}/{album_track_num'+33                  │ │
│ │        match = <re.Match object; span=(44, 63), match='{album_num_volumes}'>            │ │
│ │      matches = <callable_iterator object at 0x7f993821f700>                             │ │
│ │        media = <tidalapi.album.Album object at 0x7f993859f010>                          │ │
│ │        regex = '\\{(.+?)\\}'                                                            │ │
│ │       result = 'The Beatles/Abbey Road (Super Deluxe Edition)                           │ │
│ │                [1969]/{album_num_volumes}/{album_'+42                                   │ │
│ │   result_fmt = 3                                                                        │ │
│ │ template_str = '{album_num_volumes}'                                                    │ │
│ │        value = '1969'                                                                   │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                             │
│ /home/rmotlagh/tidal-dl-ng/.venv/lib/python3.11/site-packages/pathvalidate/_filename.py:466 │
│ in sanitize_filename                                                                        │
│                                                                                             │
│   463 │   │   reserved_name_handler=reserved_name_handler,                                  │
│   464 │   │   additional_reserved_names=additional_reserved_names,                          │
│   465 │   │   validate_after_sanitize=validate_after_sanitize,                              │
│ ❱ 466 │   ).sanitize(filename, replacement_text)                                            │
│   467                                                                                       │
│                                                                                             │
│ ╭───────────── locals ──────────────╮                                                       │
│ │ additional_reserved_names = None  │                                                       │
│ │            check_reserved = None  │                                                       │
│ │                  filename = 3     │                                                       │
│ │               fs_encoding = None  │                                                       │
│ │                   max_len = 255   │                                                       │
│ │        null_value_handler = None  │                                                       │
│ │                  platform = None  │                                                       │
│ │          replacement_text = ''    │                                                       │
│ │     reserved_name_handler = None  │                                                       │
│ │   validate_after_sanitize = False │                                                       │
│ ╰───────────────────────────────────╯                                                       │
│                                                                                             │
│ /home/rmotlagh/tidal-dl-ng/.venv/lib/python3.11/site-packages/pathvalidate/_filename.py:68  │
│ in sanitize                                                                                 │
│                                                                                             │
│    65 │                                                                                     │
│    66 │   def sanitize(self, value: PathType, replacement_text: str = "") -> PathType:      │
│    67 │   │   try:                                                                          │
│ ❱  68 │   │   │   validate_pathtype(value, allow_whitespaces=not self._is_windows(include_u │
│    69 │   │   except ValidationError as e:                                                  │
│    70 │   │   │   if e.reason == ErrorReason.NULL_NAME:                                     │
│    71 │   │   │   │   if isinstance(value, PurePath):                                       │
│                                                                                             │
│ ╭──────────────────────────────────────── locals ────────────────────────────────────────╮  │
│ │ replacement_text = ''                                                                  │  │
│ │             self = <pathvalidate._filename.FileNameSanitizer object at 0x7f9938255610> │  │
│ │            value = 3                                                                   │  │
│ ╰────────────────────────────────────────────────────────────────────────────────────────╯  │
│                                                                                             │
│ /home/rmotlagh/tidal-dl-ng/.venv/lib/python3.11/site-packages/pathvalidate/_common.py:32 in │
│ validate_pathtype                                                                           │
│                                                                                             │
│    29 │   if is_null_string(text):                                                          │
│    30 │   │   raise ValidationError(reason=ErrorReason.NULL_NAME)                           │
│    31 │                                                                                     │
│ ❱  32 │   raise TypeError(f"text must be a string: actual={type(text)}")                    │
│    33                                                                                       │
│    34                                                                                       │
│    35 def to_str(name: PathType) -> str:                                                    │
│                                                                                             │
│ ╭───────────────────────────── locals ─────────────────────────────╮                        │
│ │ allow_whitespaces = False                                        │                        │
│ │         error_msg = None                                         │                        │
│ │       ErrorReason = <enum 'ErrorReason'>                         │                        │
│ │              text = 3                                            │                        │
│ │   ValidationError = <class 'pathvalidate.error.ValidationError'> │                        │
│ ╰──────────────────────────────────────────────────────────────────╯                        │
╰─────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: text must be a string: actual=<class 'int'>
exislow commented 5 months ago

That's right. Just pushed a fix to master.

motlaghreyhan commented 5 months ago

That fixed it thanks!

One question though, doesn't the album implementation just list the number of volumes in the album? Not the volume number that the particular track is a part of? So if I want to download an entire album into "{album_artist}/{album_title} [{album_year}]/{album_num_volumes}/{album_track_num} - {artist_name} - {track_title}" all the files go into The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/3/. because the album has 3 volumes total

I would expect each song to belong to it's own volume, and get sent to each of these directories, even when downloading a whole album and not a single track: The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/1/. The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/2/. The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/3/.

I do think the track functionality works as I'd expect, and for my purposes I just added a | Album to line 149 of paths.py and use track_volume_num for everything.

Thanks for your help here!

exislow commented 5 months ago

The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/1/. The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/2/. The\ Beatles/Abbey\ Road\ \(Super\ Deluxe\ Edition\)\ \[1969\]/3/.

If you like to use it this way, you need to use track_volume_num.