From 122a6999b30903ec6112135e02fef12d831a8b8e Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 19 Apr 2026 16:57:36 -0700 Subject: [PATCH] Add customizable music video naming and fix slskd log spam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Video naming: new path template in Settings → Paths & Organization with $artist, $artistletter, $title, $year variables. Default unchanged ($artist/$title-video → Artist/Title-video.mp4) so existing Plex setups aren't affected. Users can remove the -video suffix or reorganize however they like. slskd logs: the Clean Search History automation now skips when Soulseek is not the active download source, eliminating noisy connection error logs for users who don't use Soulseek. --- web_server.py | 42 +++++++++++++++++++++++++++++++++++------- webui/index.html | 7 +++++++ webui/static/helper.js | 2 ++ webui/static/script.js | 11 ++++++++--- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/web_server.py b/web_server.py index 64803836..19eb1fef 100644 --- a/web_server.py +++ b/web_server.py @@ -1608,6 +1608,15 @@ def _register_automation_handlers(): def _auto_clean_search_history(config): """Remove old searches from Soulseek.""" automation_id = config.get('_automation_id') + # Skip if soulseek is not the active download source or in hybrid order + dl_mode = config_manager.get('download_source.mode', 'hybrid') + hybrid_order = config_manager.get('download_source.hybrid_order', ['hifi', 'youtube', 'soulseek']) + soulseek_active = (dl_mode == 'soulseek' or + (dl_mode == 'hybrid' and 'soulseek' in hybrid_order)) + if not soulseek_active or not soulseek_client or not soulseek_client.base_url: + _update_automation_progress(automation_id, + log_line='Soulseek not active — skipped', log_type='skip') + return {'status': 'skipped'} if not config_manager.get('soulseek.auto_clear_searches', True): _update_automation_progress(automation_id, log_line='Auto-clear disabled in settings', log_type='skip') @@ -9360,6 +9369,7 @@ def download_music_video(): _music_video_downloads[video_id]['status'] = 'matching' artist_name = raw_channel track_title = raw_title + year = '' # Strip common YouTube suffixes for cleaner search import re as _re @@ -9384,6 +9394,8 @@ def download_music_video(): if best and best_score >= 0.5: artist_name = best.artists[0] if best.artists else raw_channel track_title = best.name + if hasattr(best, 'release_date') and best.release_date: + year = str(best.release_date)[:4] print(f"[Music Video] Matched to: {artist_name} - {track_title} (confidence: {best_score:.2f})") else: # Parse artist from video title: "Artist - Title" pattern @@ -9403,13 +9415,27 @@ def download_music_video(): def _sanitize(s): return _re.sub(r'[<>:"/\\|?*]', '_', s).strip().rstrip('.') - artist_folder = _sanitize(artist_name) - video_filename = f"{_sanitize(track_title)}-video" - - # Build output path: MusicVideos/Artist/Title-video - artist_dir = os.path.join(music_videos_path, artist_folder) - os.makedirs(artist_dir, exist_ok=True) - output_path = os.path.join(artist_dir, video_filename) + # Apply video path template + video_template = config_manager.get('file_organization.templates', {}).get('video_path', '$artist/$title-video') + if not video_template or not video_template.strip(): + video_template = '$artist/$title-video' + safe_artist = _sanitize(artist_name) + video_path = video_template + video_path = video_path.replace('$artistletter', safe_artist[0].upper() if safe_artist else 'A') + video_path = video_path.replace('$artist', safe_artist) + video_path = video_path.replace('$title', _sanitize(track_title)) + video_path = video_path.replace('$year', str(year) if year else '') + # Clean up empty segments from missing variables + video_path = _re.sub(r'//+', '/', video_path).strip('/') + # Split into folder and filename + path_parts = video_path.rsplit('/', 1) + if len(path_parts) == 2: + folder_part, file_part = path_parts + else: + folder_part, file_part = '', path_parts[0] + output_dir = os.path.join(music_videos_path, folder_part) if folder_part else music_videos_path + os.makedirs(output_dir, exist_ok=True) + output_path = os.path.join(output_dir, file_part) # Step 2: Download _music_video_downloads[video_id]['status'] = 'downloading' @@ -22503,6 +22529,8 @@ def get_version_info(): "• Reject Qobuz 30-second sample/preview downloads", "• Fix library page crash on All filter — non-string soul_id broke card rendering", "• Auto Wing It fallback for failed discovery — unmatched tracks download via Soulseek with raw metadata", + "• Customizable music video naming — path template with $artist, $title, $year variables", + "• Fix soulseek log spam when not configured as download source", ], }, { diff --git a/webui/index.html b/webui/index.html index e8b268e5..5f8dd41c 100644 --- a/webui/index.html +++ b/webui/index.html @@ -5085,6 +5085,13 @@ Variables: $playlist, $albumartist, $artist, $artistletter, $title, $year, $quality (filename only) +
+ + + Variables: $artist, $artistletter, $title, $year. Default: $artist/$title-video → Artist/Title-video.mp4 +
+