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:
@@ -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:
|
||||
|
||||
@@ -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,12 +846,8 @@ 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:
|
||||
pass
|
||||
if False: # placeholder, will be handled via spotify_metadata in download_trackspo
|
||||
pass
|
||||
downloaded_track = self.download_trackspo(
|
||||
link_track,
|
||||
output_dir=output_dir, quality_download=quality_download,
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
return str_value.zfill(2)
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user