From d858a7c85f91edd2e1d3dd68b97d917ca19065a4 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Fri, 20 Feb 2026 20:18:01 -0800 Subject: [PATCH] Add $quality template var (filename only) Introduce a new $quality template variable that is only substituted into filenames to avoid splitting album folders when tracks of mixed qualities are present. Updates include: - web_server.py: populate template contexts with 'quality' (from context['_audio_quality']), strip $quality from folder components, substitute it only in the filename, and clean up empty brackets/parentheses/dashes when the variable is empty. - config/config.example.json and config/config.json: document the new variable in the file_organization template variables string. - webui/index.html and webui/static/script.js: update UI help text and client-side template validation to include $quality. This prevents folder fragmentation for albums with mixed-quality files while still allowing quality information in filenames. --- config/config.example.json | 2 +- config/config.json | 2 +- web_server.py | 50 +++++++++++++++++++++++++++++++++----- webui/index.html | 6 ++--- webui/static/script.js | 6 ++--- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/config/config.example.json b/config/config.example.json index dccf5746..b0699ef1 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -48,7 +48,7 @@ }, "file_organization": { "enabled": true, - "_template_variables": "Available: $artist, $albumartist, $album, $title, $track, $year, $playlist", + "_template_variables": "Available: $artist, $albumartist, $album, $title, $track, $year, $playlist, $quality (filename only)", "templates": { "album_path": "$albumartist/$albumartist - $album/$track - $title", "single_path": "$artist/$artist - $title/$title", diff --git a/config/config.json b/config/config.json index dccf5746..b0699ef1 100644 --- a/config/config.json +++ b/config/config.json @@ -48,7 +48,7 @@ }, "file_organization": { "enabled": true, - "_template_variables": "Available: $artist, $albumartist, $album, $title, $track, $year, $playlist", + "_template_variables": "Available: $artist, $albumartist, $album, $title, $track, $year, $playlist, $quality (filename only)", "templates": { "album_path": "$albumartist/$albumartist - $album/$track - $title", "single_path": "$artist/$artist - $title/$title", diff --git a/web_server.py b/web_server.py index 0e029408..23e85d5a 100644 --- a/web_server.py +++ b/web_server.py @@ -8094,7 +8094,8 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): 'title': track_name, 'playlist_name': playlist_name, 'track_number': 1, - 'year': year + 'year': year, + 'quality': context.get('_audio_quality', '') } folder_path, filename_base = _get_file_path_from_template(template_context, 'playlist_path') @@ -8132,7 +8133,8 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): 'album': album_info['album_name'], 'title': clean_track_name, 'track_number': track_number, - 'year': year + 'year': year, + 'quality': context.get('_audio_quality', '') } # Multi-disc album subfolder support @@ -8179,7 +8181,8 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): 'album': album_info.get('album_name', clean_track_name) if album_info else clean_track_name, 'title': clean_track_name, 'track_number': 1, - 'year': year + 'year': year, + 'quality': context.get('_audio_quality', '') } folder_path, filename_base = _get_file_path_from_template(template_context, 'single_path') @@ -8316,20 +8319,55 @@ def _get_file_path_from_template(context: dict, template_type: str = 'album_path # Split into folder and filename path_parts = full_path.split('/') + + # Handle $quality: only substituted in the filename (last component). + # In folder components it becomes empty string to prevent album splits + # when tracks arrive in mixed qualities (e.g., FLAC 16bit vs 24bit). + import re + quality_value = context.get('quality', '') + if len(path_parts) > 1: folder_parts = path_parts[:-1] filename_base = path_parts[-1] + # Strip $quality from folder parts and clean up artifacts + cleaned_folders = [] + for part in folder_parts: + part = part.replace('$quality', '') + part = re.sub(r'\s*\[\s*\]', '', part) # empty [] + part = re.sub(r'\s*\(\s*\)', '', part) # empty () + part = re.sub(r'\s*\{\s*\}', '', part) # empty {} + part = re.sub(r'\s*-\s*$', '', part) # trailing dash + part = re.sub(r'^\s*-\s*', '', part) # leading dash + part = re.sub(r'\s+', ' ', part).strip() + if part: + cleaned_folders.append(part) + + # Substitute $quality in filename only + filename_base = filename_base.replace('$quality', quality_value) + # Clean up empty brackets/parens from any variable that resolved to empty + filename_base = re.sub(r'\s*\[\s*\]', '', filename_base) + filename_base = re.sub(r'\s*\(\s*\)', '', filename_base) + filename_base = re.sub(r'\s*\{\s*\}', '', filename_base) + filename_base = re.sub(r'\s*-\s*$', '', filename_base) + filename_base = re.sub(r'\s+', ' ', filename_base).strip() + # Sanitize each folder component - sanitized_folders = [_sanitize_filename(part) for part in folder_parts] - folder_path = os.path.join(*sanitized_folders) + sanitized_folders = [_sanitize_filename(part) for part in cleaned_folders] + folder_path = os.path.join(*sanitized_folders) if sanitized_folders else '' # Sanitize filename filename = _sanitize_filename(filename_base) return folder_path, filename else: - # Single component, treat as filename + # Single component, treat as filename — substitute $quality + full_path = full_path.replace('$quality', quality_value) + full_path = re.sub(r'\s*\[\s*\]', '', full_path) + full_path = re.sub(r'\s*\(\s*\)', '', full_path) + full_path = re.sub(r'\s*\{\s*\}', '', full_path) + full_path = re.sub(r'\s*-\s*$', '', full_path) + full_path = re.sub(r'\s+', ' ', full_path).strip() return '', _sanitize_filename(full_path) # METADATA & COVER ART HELPERS (Ported from downloads.py) diff --git a/webui/index.html b/webui/index.html index bee4bb01..64bc869c 100644 --- a/webui/index.html +++ b/webui/index.html @@ -3206,21 +3206,21 @@ Variables: $albumartist, $artist, $album, $title, - $track, $year + $track, $year, $quality (filename only)
- Variables: $artist, $title, $album, $year + Variables: $artist, $title, $album, $year, $quality (filename only)
- Variables: $playlist, $artist, $title, $year + Variables: $playlist, $artist, $title, $year, $quality (filename only)
diff --git a/webui/static/script.js b/webui/static/script.js index 28389e9b..e2cff1ba 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -1625,9 +1625,9 @@ function validateFileOrganizationTemplates() { // Valid variables for each template type const validVars = { - album: ['$artist', '$albumartist', '$album', '$title', '$track', '$year'], - single: ['$artist', '$albumartist', '$album', '$title', '$year'], - playlist: ['$artist', '$playlist', '$title', '$year'] + album: ['$artist', '$albumartist', '$album', '$title', '$track', '$year', '$quality'], + single: ['$artist', '$albumartist', '$album', '$title', '$year', '$quality'], + playlist: ['$artist', '$playlist', '$title', '$year', '$quality'] }; // Get template values