From a8935ae8f590bf808132764539a63f982bee04cf Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Fri, 2 Jan 2026 08:24:43 -0800 Subject: [PATCH] Add $year variable support to file path templates Introduces the $year variable for album, single, and playlist path templates, allowing users to include the release year in file organization. Updates the backend to extract and provide the year, adjusts the web UI to document the new variable, and updates validation logic to recognize $year as valid in templates. --- config/config.example.json | 1 + web_server.py | 18 +++++++++++++++--- webui/index.html | 6 +++--- webui/static/script.js | 9 +++++---- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/config/config.example.json b/config/config.example.json index 692aaae9..dccf5746 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -48,6 +48,7 @@ }, "file_organization": { "enabled": true, + "_template_variables": "Available: $artist, $albumartist, $album, $title, $track, $year, $playlist", "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 fbc080a1..d42e2727 100644 --- a/web_server.py +++ b/web_server.py @@ -6948,6 +6948,14 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): original_search = context.get("original_search_result", {}) playlist_folder_mode = track_info.get("_playlist_folder_mode", False) + # Extract year from spotify_album for template use (safe for all modes) + year = 'Unknown' + spotify_album = context.get("spotify_album", {}) + if spotify_album and spotify_album.get('release_date'): + release_date = spotify_album['release_date'] + if release_date and len(release_date) >= 4: + year = release_date[:4] + # Determine which template type to use if playlist_folder_mode: # PLAYLIST MODE @@ -6960,7 +6968,8 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): 'album': track_name, 'title': track_name, 'playlist_name': playlist_name, - 'track_number': 1 + 'track_number': 1, + 'year': year } folder_path, filename_base = _get_file_path_from_template(template_context, 'playlist_path') @@ -6997,7 +7006,8 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): 'albumartist': spotify_artist["name"] if isinstance(spotify_artist, dict) else spotify_artist.name, 'album': album_info['album_name'], 'title': clean_track_name, - 'track_number': track_number + 'track_number': track_number, + 'year': year } folder_path, filename_base = _get_file_path_from_template(template_context, 'album_path') @@ -7032,7 +7042,8 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): 'albumartist': spotify_artist["name"] if isinstance(spotify_artist, dict) else spotify_artist.name, 'album': album_info.get('album_name', clean_track_name) if album_info else clean_track_name, 'title': clean_track_name, - 'track_number': 1 + 'track_number': 1, + 'year': year } folder_path, filename_base = _get_file_path_from_template(template_context, 'single_path') @@ -7076,6 +7087,7 @@ def _apply_path_template(template: str, context: dict) -> str: result = result.replace('$album', context.get('album', 'Unknown Album')) result = result.replace('$title', context.get('title', 'Unknown Track')) result = result.replace('$track', f"{context.get('track_number', 1):02d}") + result = result.replace('$year', str(context.get('year', 'Unknown'))) return result diff --git a/webui/index.html b/webui/index.html index bdd5ffbe..53b1d3f7 100644 --- a/webui/index.html +++ b/webui/index.html @@ -2772,19 +2772,19 @@
- Variables: $albumartist, $artist, $album, $title, $track + Variables: $albumartist, $artist, $album, $title, $track, $year
- Variables: $artist, $title, $album + Variables: $artist, $title, $album, $year
- Variables: $playlist, $artist, $title + Variables: $playlist, $artist, $title, $year
diff --git a/webui/static/script.js b/webui/static/script.js index 77ec8000..c2716788 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -1484,9 +1484,9 @@ function validateFileOrganizationTemplates() { // Valid variables for each template type const validVars = { - album: ['$artist', '$albumartist', '$album', '$title', '$track'], - single: ['$artist', '$albumartist', '$album', '$title'], - playlist: ['$artist', '$playlist', '$title'] + album: ['$artist', '$albumartist', '$album', '$title', '$track', '$year'], + single: ['$artist', '$albumartist', '$album', '$title', '$year'], + playlist: ['$artist', '$playlist', '$title', '$year'] }; // Get template values @@ -7219,7 +7219,8 @@ async function startMissingTracksProcess(playlistId) { }; // If this is an artist album download, use album name and include full context - if (playlistId.startsWith('artist_album_')) { + // Match both 'artist_album_' and 'enhanced_search_album_' prefixes + if (playlistId.startsWith('artist_album_') || playlistId.startsWith('enhanced_search_album_')) { requestBody.playlist_name = process.album?.name || process.playlist.name; requestBody.is_album_download = true; requestBody.album_context = process.album; // Full Spotify album object