feat: added pad_number_width, which determines the width of the padding when pad_tracks is set to True. It has an auto mode that automatically determines padding based on parent context (playlist/auto)

This commit is contained in:
Xoconoch
2025-08-20 10:30:08 -05:00
parent e4a948cd2a
commit b844e8f739
6 changed files with 99 additions and 34 deletions

View File

@@ -381,6 +381,18 @@ class EASY_DW:
custom_track_format = getattr(self.__preferences, 'custom_track_format', None)
pad_tracks = getattr(self.__preferences, 'pad_tracks', True)
self.__song_metadata_dict['artist_separator'] = getattr(self.__preferences, 'artist_separator', '; ')
# Determine pad width (supports 'auto' mode) based on context
pad_number_width = None
try:
pnw = getattr(self.__preferences, 'pad_number_width', None)
if isinstance(pnw, str) and pnw.lower() == 'auto':
total = getattr(self.__preferences, 'total_tracks', None)
if isinstance(total, int) and total and total > 0:
pad_number_width = max(2, len(str(total)))
elif isinstance(pnw, int) and pnw >= 1:
pad_number_width = pnw
except Exception:
pad_number_width = None
# Inject playlist placeholders if in playlist context
try:
if self.__parent == 'playlist' and hasattr(self.__preferences, 'json_data') and self.__preferences.json_data:
@@ -404,7 +416,8 @@ class EASY_DW:
self.__file_format,
custom_dir_format=custom_dir_format,
custom_track_format=custom_track_format,
pad_tracks=pad_tracks
pad_tracks=pad_tracks,
pad_number_width=pad_number_width
)
def __set_episode_path(self) -> None:
@@ -1282,6 +1295,8 @@ class DW_ALBUM:
c_preferences.track_number = a + 1 # For progress reporting only
c_preferences.total_tracks = total_tracks
c_preferences.link = f"https://deezer.com/track/{c_preferences.ids}"
# Propagate pad width preference
c_preferences.pad_number_width = getattr(self.__preferences, 'pad_number_width', 'auto')
# Inject Spotify per-track object if available without extra API calls
if self.__use_spotify and spotify_tracks_in_order:
spo_track_for_tag = None
@@ -1456,6 +1471,8 @@ class DW_PLAYLIST:
c_preferences.total_tracks = total_tracks
c_preferences.json_data = self.__preferences.json_data
c_preferences.link = f"https://deezer.com/track/{c_preferences.ids}"
# Propagate pad width preference
c_preferences.pad_number_width = getattr(self.__preferences, 'pad_number_width', 'auto')
current_track_object = None
try:

View File

@@ -139,7 +139,8 @@ class DeeLogin:
market=stock_market,
playlist_context=None,
artist_separator: str = "; ",
spotify_metadata: bool = False
spotify_metadata: bool = False,
pad_number_width: int | str = 'auto'
) -> Track:
link_is_valid(link_track)
@@ -215,6 +216,7 @@ class DeeLogin:
preferences.artist_separator = artist_separator
preferences.spotify_metadata = bool(spotify_metadata)
preferences.spotify_track_obj = playlist_context.get('spotify_track_obj') if (playlist_context and playlist_context.get('spotify_track_obj')) else None
preferences.pad_number_width = pad_number_width
if playlist_context:
preferences.json_data = playlist_context.get('json_data')
@@ -252,7 +254,8 @@ class DeeLogin:
playlist_context=None,
artist_separator: str = "; ",
spotify_metadata: bool = False,
spotify_album_obj=None
spotify_album_obj=None,
pad_number_width: int | str = 'auto'
) -> Album:
link_is_valid(link_album)
@@ -302,6 +305,7 @@ class DeeLogin:
preferences.artist_separator = artist_separator
preferences.spotify_metadata = bool(spotify_metadata)
preferences.spotify_album_obj = spotify_album_obj
preferences.pad_number_width = pad_number_width
if playlist_context:
preferences.json_data = playlist_context['json_data']
@@ -335,7 +339,8 @@ class DeeLogin:
bitrate=None,
save_cover=stock_save_cover,
market=stock_market,
artist_separator: str = "; "
artist_separator: str = "; ",
pad_number_width: int | str = 'auto'
) -> Playlist:
link_is_valid(link_playlist)
@@ -370,6 +375,7 @@ class DeeLogin:
preferences.save_cover = save_cover
preferences.market = market
preferences.artist_separator = artist_separator
preferences.pad_number_width = pad_number_width
playlist = DW_PLAYLIST(preferences).dw()
@@ -388,7 +394,8 @@ class DeeLogin:
convert_to=None,
bitrate=None,
save_cover=stock_save_cover,
market=stock_market
market=stock_market,
pad_number_width: int | str = 'auto'
) -> list[Track]:
link_is_valid(link_artist)
@@ -408,7 +415,8 @@ class DeeLogin:
convert_to=convert_to,
bitrate=bitrate,
save_cover=save_cover,
market=market
market=market,
pad_number_width=pad_number_width
)
for track in top_tracks_json
]
@@ -574,7 +582,8 @@ class DeeLogin:
market=stock_market,
playlist_context=None,
artist_separator: str = "; ",
spotify_metadata: bool = False
spotify_metadata: bool = False,
pad_number_width: int | str = 'auto'
) -> Track:
link_dee = self.convert_spoty_to_dee_link_track(link_track)
@@ -612,7 +621,8 @@ class DeeLogin:
market=market,
playlist_context=playlist_context,
artist_separator=artist_separator,
spotify_metadata=spotify_metadata
spotify_metadata=spotify_metadata,
pad_number_width=pad_number_width
)
return track
@@ -637,7 +647,9 @@ class DeeLogin:
market=stock_market,
playlist_context=None,
artist_separator: str = "; ",
spotify_metadata: bool = False
spotify_metadata: bool = False,
spotify_album_obj=None,
pad_number_width: int | str = 'auto'
) -> Album:
link_dee = self.convert_spoty_to_dee_link_album(link_album)
@@ -672,7 +684,8 @@ class DeeLogin:
playlist_context=playlist_context,
artist_separator=artist_separator,
spotify_metadata=spotify_metadata,
spotify_album_obj=spotify_album_obj
spotify_album_obj=spotify_album_obj,
pad_number_width=pad_number_width
)
return album
@@ -696,7 +709,8 @@ class DeeLogin:
save_cover=stock_save_cover,
market=stock_market,
artist_separator: str = "; ",
spotify_metadata: bool = False
spotify_metadata: bool = False,
pad_number_width: int | str = 'auto'
) -> Playlist:
link_is_valid(link_playlist)
@@ -832,11 +846,7 @@ class DeeLogin:
'spotify_url': link_track
}
# Attach Spotify track object for tagging if requested
if spotify_metadata:
try:
from deezspot.spotloader.__spo_api__ import json_to_track_playlist_object
playlist_ctx['spotify_track_obj'] = json_to_track_playlist_object(track_info)
except Exception:
if False: # placeholder, will be handled via spotify_metadata in download_trackspo
pass
downloaded_track = self.download_trackspo(
link_track,
@@ -847,7 +857,8 @@ class DeeLogin:
initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase,
max_retries=max_retries, convert_to=convert_to, bitrate=bitrate,
save_cover=save_cover, market=market, playlist_context=playlist_ctx,
artist_separator=artist_separator, spotify_metadata=spotify_metadata
artist_separator=artist_separator, spotify_metadata=False,
pad_number_width=pad_number_width
)
tracks.append(downloaded_track)
@@ -918,7 +929,8 @@ class DeeLogin:
bitrate=None,
save_cover=stock_save_cover,
market=stock_market,
artist_separator: str = "; "
artist_separator: str = "; ",
pad_number_width: int | str = 'auto'
) -> Track:
query = f"track:{song} artist:{artist}"
@@ -949,7 +961,8 @@ class DeeLogin:
bitrate=bitrate,
save_cover=save_cover,
market=market,
artist_separator=artist_separator
artist_separator=artist_separator,
pad_number_width=pad_number_width
)
return track

View File

@@ -135,7 +135,7 @@ def what_kind(link):
def __get_tronc(string):
return string[:len(string) - 1]
def apply_custom_format(format_str, metadata: dict, pad_tracks=True) -> str:
def apply_custom_format(format_str, metadata: dict, pad_tracks=True, pad_number_width: int | None = None) -> str:
def replacer(match):
full_key = match.group(1) # e.g., "artist", "ar_album_1"
@@ -183,7 +183,7 @@ def apply_custom_format(format_str, metadata: dict, pad_tracks=True) -> str:
# Handle None values safely
if value is None:
if full_key in ['tracknum', 'discnum']:
if full_key in ['tracknum', 'discnum', 'playlistnum']:
value = '1' if full_key == 'discnum' else '0'
else:
value = ''
@@ -196,14 +196,18 @@ def apply_custom_format(format_str, metadata: dict, pad_tracks=True) -> str:
if pad_tracks and full_key in ['tracknum', 'discnum', 'playlistnum']:
str_value = str(value)
# Pad with leading zero if it's a single digit
if str_value.isdigit() and len(str_value) == 1:
if str_value.isdigit():
if isinstance(pad_number_width, int) and pad_number_width >= 1:
return str_value.zfill(pad_number_width)
# Default legacy behavior: pad single digits to width 2
if len(str_value) == 1:
return str_value.zfill(2)
return str_value
return str(value)
return re.sub(r'%([^%]+)%', replacer, format_str)
def __get_dir(song_metadata, output_dir, custom_dir_format=None, pad_tracks=True):
def __get_dir(song_metadata, output_dir, custom_dir_format=None, pad_tracks=True, pad_number_width: int | None = None):
# If custom_dir_format is explicitly empty or None, use output_dir directly
if not custom_dir_format:
# Ensure output_dir itself exists, as __check_dir won't be called on a subpath
@@ -214,7 +218,7 @@ def __get_dir(song_metadata, output_dir, custom_dir_format=None, pad_tracks=True
# create directories; slashes from data are sanitized inside components.
format_parts = custom_dir_format.split("/")
formatted_parts = [
apply_custom_format(part, song_metadata, pad_tracks) for part in format_parts
apply_custom_format(part, song_metadata, pad_tracks, pad_number_width) for part in format_parts
]
sanitized_path_segment = "/".join(
sanitize_name(part) for part in formatted_parts
@@ -233,14 +237,16 @@ def set_path(
is_episode=False,
custom_dir_format=None,
custom_track_format=None,
pad_tracks=True
pad_tracks=True,
pad_number_width: int | None = None
):
# Determine the directory for the song
directory = __get_dir(
song_metadata,
output_dir,
custom_dir_format=custom_dir_format,
pad_tracks=pad_tracks
pad_tracks=pad_tracks,
pad_number_width=pad_number_width
)
# Determine the filename for the song
@@ -262,7 +268,7 @@ def set_path(
# Apply the custom format string for the track filename.
# pad_tracks is passed along for track/disc numbers in filename.
track_filename_base = apply_custom_format(custom_track_format, effective_metadata, pad_tracks)
track_filename_base = apply_custom_format(custom_track_format, effective_metadata, pad_tracks, pad_number_width)
track_filename_base = sanitize_name(track_filename_base)
# Add file format (extension) to the filename

View File

@@ -17,6 +17,7 @@ class Preferences:
self.custom_dir_format = None
self.custom_track_format = None
self.pad_tracks = True # Default to padded track numbers (01, 02, etc.)
self.pad_number_width = 'auto' # New: number of digits for padding; 'auto' uses total tracks
self.initial_retry_delay = 30 # Default initial retry delay in seconds
self.retry_delay_increase = 30 # Default increase in delay between retries in seconds
self.max_retries = 5 # Default maximum number of retries per track

View File

@@ -145,6 +145,25 @@ class EASY_DW:
pad_tracks = getattr(self.__preferences, 'pad_tracks', True)
# Ensure the separator is available to formatting utils for indexed placeholders
self.__song_metadata_dict['artist_separator'] = getattr(self.__preferences, 'artist_separator', '; ')
# Determine pad width (supports 'auto' mode)
pad_number_width = None
try:
pnw = getattr(self.__preferences, 'pad_number_width', None)
if isinstance(pnw, str) and pnw.lower() == 'auto':
total = None
if self.__parent == 'album' and hasattr(self.__song_metadata, 'album') and getattr(self.__song_metadata.album, 'total_tracks', None):
total = self.__song_metadata.album.total_tracks
elif self.__parent == 'playlist' and hasattr(self.__preferences, 'json_data') and self.__preferences.json_data:
try:
total = self.__preferences.json_data.get('tracks', {}).get('total')
except Exception:
total = None
if isinstance(total, int) and total and total > 0:
pad_number_width = max(2, len(str(total)))
elif isinstance(pnw, int) and pnw >= 1:
pad_number_width = pnw
except Exception:
pad_number_width = None
# Inject playlist placeholders if in playlist context
try:
if self.__parent == 'playlist' and hasattr(self.__preferences, 'json_data') and self.__preferences.json_data:
@@ -166,7 +185,8 @@ class EASY_DW:
self.__file_format,
custom_dir_format=custom_dir_format,
custom_track_format=custom_track_format,
pad_tracks=pad_tracks
pad_tracks=pad_tracks,
pad_number_width=pad_number_width
)
def __set_episode_path(self) -> None:
@@ -1088,6 +1108,7 @@ class DW_ALBUM:
c_preferences.link = f"https://open.spotify.com/track/{c_preferences.ids}"
# Set album position for progress reporting (not for metadata - that comes from API)
c_preferences.track_number = a + 1
c_preferences.pad_number_width = getattr(self.__preferences, 'pad_number_width', 'auto')
track = EASY_DW(c_preferences, parent='album').easy_dw()
@@ -1241,6 +1262,7 @@ class DW_PLAYLIST:
c_preferences.json_data = self.__json_data
c_preferences.track_number = idx + 1
c_preferences.link = f"https://open.spotify.com/track/{c_preferences.ids}" if c_preferences.ids else None
c_preferences.pad_number_width = getattr(self.__preferences, 'pad_number_width', 'auto')
easy_dw_instance = EASY_DW(c_preferences, parent='playlist')

View File

@@ -100,7 +100,8 @@ class SpoLogin:
bitrate=None,
save_cover=stock_save_cover,
market: list[str] | None = stock_market,
artist_separator: str = "; "
artist_separator: str = "; ",
pad_number_width: int | str = 'auto'
) -> Track:
song_metadata = None
try:
@@ -140,6 +141,7 @@ class SpoLogin:
preferences.save_cover = save_cover
preferences.market = market
preferences.artist_separator = artist_separator
preferences.pad_number_width = pad_number_width
track = DW_TRACK(preferences).dw()
@@ -186,7 +188,8 @@ class SpoLogin:
bitrate=None,
save_cover=stock_save_cover,
market: list[str] | None = stock_market,
artist_separator: str = "; "
artist_separator: str = "; ",
pad_number_width: int | str = 'auto'
) -> Album:
try:
link_is_valid(link_album)
@@ -230,6 +233,7 @@ class SpoLogin:
preferences.save_cover = save_cover
preferences.market = market
preferences.artist_separator = artist_separator
preferences.pad_number_width = pad_number_width
album = DW_ALBUM(preferences).dw()
@@ -262,7 +266,8 @@ class SpoLogin:
bitrate=None,
save_cover=stock_save_cover,
market: list[str] | None = stock_market,
artist_separator: str = "; "
artist_separator: str = "; ",
pad_number_width: int | str = 'auto'
) -> Playlist:
try:
link_is_valid(link_playlist)
@@ -340,6 +345,7 @@ class SpoLogin:
preferences.save_cover = save_cover
preferences.market = market
preferences.artist_separator = artist_separator
preferences.pad_number_width = pad_number_width
playlist = DW_PLAYLIST(preferences).dw()