diff --git a/web_server.py b/web_server.py index c2cf1e64..a7245169 100644 --- a/web_server.py +++ b/web_server.py @@ -8145,6 +8145,50 @@ def _build_final_path_for_track(context, spotify_artist, album_info, file_ext): new_filename = f"{final_track_name_sanitized}{file_ext}" return os.path.join(single_dir, new_filename), True +def _get_audio_quality_string(file_path): + """ + Read audio file and return a quality descriptor string. + + Returns strings like 'FLAC 16bit', 'MP3-320', 'M4A-256', 'OGG-192'. + Returns empty string on any error. + """ + try: + ext = os.path.splitext(file_path)[1].lower() + + if ext == '.flac': + audio = FLAC(file_path) + bits = audio.info.bits_per_sample + return f"FLAC {bits}bit" + + elif ext == '.mp3': + from mutagen.mp3 import MP3, BitrateMode + audio = MP3(file_path) + bitrate_kbps = audio.info.bitrate // 1000 + if audio.info.bitrate_mode == BitrateMode.VBR: + return "MP3-VBR" + return f"MP3-{bitrate_kbps}" + + elif ext in ('.m4a', '.aac', '.mp4'): + audio = MP4(file_path) + bitrate_kbps = audio.info.bitrate // 1000 + return f"M4A-{bitrate_kbps}" + + elif ext == '.ogg': + audio = OggVorbis(file_path) + bitrate_kbps = audio.info.bitrate // 1000 + return f"OGG-{bitrate_kbps}" + + elif ext == '.opus': + from mutagen.oggopus import OggOpus + audio = OggOpus(file_path) + bitrate_kbps = audio.info.bitrate // 1000 + return f"OPUS-{bitrate_kbps}" + + return '' + except Exception as e: + logger.debug(f"Could not determine audio quality for {file_path}: {e}") + return '' + def _apply_path_template(template: str, context: dict) -> str: """ Apply template to build file path. @@ -8424,6 +8468,16 @@ def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_in # ── Embed source IDs (Spotify, MusicBrainz, etc.) on the same object ── _embed_source_ids(audio_file, metadata) + # ── Embed audio quality tag ── + quality = context.get('_audio_quality', '') + if quality: + if isinstance(audio_file.tags, ID3): + audio_file.tags.add(TXXX(encoding=3, desc='QUALITY', text=[quality])) + elif isinstance(audio_file, (FLAC, OggVorbis)): + audio_file['quality'] = [quality] + elif isinstance(audio_file, MP4): + audio_file['----:com.apple.iTunes:QUALITY'] = [MP4FreeForm(quality.encode('utf-8'))] + # ── Single save for everything ── if isinstance(audio_file.tags, ID3): audio_file.save(v1=0, v2_version=4) @@ -9527,6 +9581,9 @@ def _post_process_matched_download(context_key, context, file_path): print(f"📁 [Playlist Folder Mode] Organizing in playlist folder: {playlist_name}") file_ext = os.path.splitext(file_path)[1] + context['_audio_quality'] = _get_audio_quality_string(file_path) + if context['_audio_quality']: + print(f"🎧 Audio quality detected: {context['_audio_quality']}") final_path, _ = _build_final_path_for_track(context, spotify_artist, None, file_ext) print(f"📁 Playlist mode final path: '{final_path}'") @@ -9645,6 +9702,9 @@ def _post_process_matched_download(context_key, context, file_path): os.makedirs(artist_dir, exist_ok=True) file_ext = os.path.splitext(file_path)[1] + context['_audio_quality'] = _get_audio_quality_string(file_path) + if context['_audio_quality']: + print(f"🎧 Audio quality detected: {context['_audio_quality']}") # 2. Build the final path using GUI-style track naming with multiple fallback sources if album_info and album_info['is_album']: