From 70e29771b930fe1a862fdacf58b244f227e017f2 Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Mon, 30 Jun 2025 16:33:32 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Directory=20length=20li?= =?UTF-8?q?mit=20is=20checked=20and=20adapted=20correctly=20now.=20Fixes?= =?UTF-8?q?=20#447=20&=20#436?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tidal_dl_ng/constants.py | 1 + tidal_dl_ng/download.py | 11 +++++++---- tidal_dl_ng/helper/path.py | 31 +++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/tidal_dl_ng/constants.py b/tidal_dl_ng/constants.py index 802fedf..0e0bb2c 100644 --- a/tidal_dl_ng/constants.py +++ b/tidal_dl_ng/constants.py @@ -12,6 +12,7 @@ CHUNK_SIZE: int = BLOCK_SIZE * BLOCKS PLAYLIST_EXTENSION: str = ".m3u" PLAYLIST_PREFIX: str = "_" FILENAME_LENGTH_MAX: int = 255 +FORMAT_TEMPLATE_EXPLICIT: str = " (Explicit)" class QualityVideo(StrEnum): diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index c7c4af7..9b13053 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -12,6 +12,7 @@ from uuid import uuid4 import m3u8 import requests from ffmpeg import FFmpeg +from pathvalidate import sanitize_filename from requests.adapters import HTTPAdapter, Retry from requests.exceptions import HTTPError from rich.progress import Progress, TaskID @@ -36,7 +37,6 @@ from tidal_dl_ng.helper.path import ( check_file_exists, format_path_media, path_file_sanitize, - sanitize_filename, url_to_filename, ) from tidal_dl_ng.helper.tidal import ( @@ -437,9 +437,12 @@ class Download: elif isinstance(media, Video): file_extension = AudioExtensions.MP4 if self.settings.data.video_convert_mp4 else VideoExtensions.TS - # Compute file name, sanitize once again and create destination directory - path_media_dst = path_media_dst.with_suffix(file_extension) - path_media_dst = pathlib.Path(path_file_sanitize(path_media_dst, adapt=True)) + # If file extension was guessed wrong in the beginning + if path_media_dst.suffix != file_extension: + # Compute file name, sanitize once again, because file extension could have been replaced after guessing it first and create destination directory + path_media_dst = path_media_dst.with_suffix(file_extension) + path_media_dst = pathlib.Path(path_file_sanitize(path_media_dst, adapt=True)) + os.makedirs(path_media_dst.parent, exist_ok=True) if not skip_download: diff --git a/tidal_dl_ng/helper/path.py b/tidal_dl_ng/helper/path.py index 5f8695a..db0a17c 100644 --- a/tidal_dl_ng/helper/path.py +++ b/tidal_dl_ng/helper/path.py @@ -13,7 +13,13 @@ from tidalapi import Album, Mix, Playlist, Track, UserPlaylist, Video from tidalapi.media import AudioExtensions from tidal_dl_ng import __name_display__ -from tidal_dl_ng.constants import FILENAME_LENGTH_MAX, FILENAME_SANITIZE_PLACEHOLDER, UNIQUIFY_THRESHOLD, MediaType +from tidal_dl_ng.constants import ( + FILENAME_LENGTH_MAX, + FILENAME_SANITIZE_PLACEHOLDER, + FORMAT_TEMPLATE_EXPLICIT, + UNIQUIFY_THRESHOLD, + MediaType, +) from tidal_dl_ng.helper.tidal import name_builder_album_artist, name_builder_artist, name_builder_title @@ -68,7 +74,11 @@ def format_path_media( result_fmt = format_str_media(match.group(1), media, album_track_num_pad_min, list_pos, list_total) if result_fmt != match.group(1): - value = sanitize_filename(result_fmt) + # Sanitize here, in case of the filename has slashes or something, which will be recognized later as a directory separator. + # Do not sanitize if value is the FORMAT_TEMPLATE_EXPLICIT placeholder, since it has a leading whitespace which otherwise gets removed. + value = ( + sanitize_filename(result_fmt) if result_fmt != FORMAT_TEMPLATE_EXPLICIT else FORMAT_TEMPLATE_EXPLICIT + ) result = result.replace(template_str, value) return result @@ -164,10 +174,10 @@ def format_str_media( result = ", ".join(tag for tag in media.media_metadata_tags) case "track_explicit": if isinstance(media, Track | Video): - result = " (Explicit)" if media.explicit else "" + result = FORMAT_TEMPLATE_EXPLICIT if media.explicit else "" case "album_explicit": if isinstance(media, Album): - result = " (Explicit)" if media.explicit else "" + result = FORMAT_TEMPLATE_EXPLICIT if media.explicit else "" case "album_num_volumes": if isinstance(media, Album): result = str(media.num_volumes) @@ -264,6 +274,19 @@ def path_file_sanitize(path_file: pathlib.Path, adapt: bool = False, uniquify: b raise # Sanitize the path. + # First sanitize sanitize each part of the path. Each part of the path is not allowed to be longer then 'PC_NAME_MAX'. + sanitized_parts = [] + + for part in sanitized_path.parts: + if part in sanitized_path.root: + sanitized_parts.append(part) + else: + sanitized_parts.append( + sanitize_filename(part, replacement_text="_", validate_after_sanitize=True, platform="auto") + ) + sanitized_path = pathlib.Path(*sanitized_parts) + + # Then sanitize the whole path itself. The whole path is not allowed to be longer than 'PC_NAME_MAX'. try: sanitized_path: pathlib.Path = sanitize_filepath( sanitized_path, replacement_text="_", validate_after_sanitize=True, platform="auto"