diff --git a/database/music_database.py b/database/music_database.py index 023177c0..71510a87 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -1156,6 +1156,15 @@ class MusicDatabase: cursor.execute("CREATE INDEX IF NOT EXISTS idx_td_file_path ON track_downloads (file_path)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_td_source ON track_downloads (source_username, source_filename)") + # Migration: Add audio detail columns to track_downloads + cursor.execute("PRAGMA table_info(track_downloads)") + td_columns = [c[1] for c in cursor.fetchall()] + if 'bit_depth' not in td_columns: + cursor.execute("ALTER TABLE track_downloads ADD COLUMN bit_depth INTEGER") + cursor.execute("ALTER TABLE track_downloads ADD COLUMN sample_rate INTEGER") + cursor.execute("ALTER TABLE track_downloads ADD COLUMN bitrate INTEGER") + logger.info("Added audio detail columns (bit_depth, sample_rate, bitrate) to track_downloads") + # Discovery artist blacklist — artists users never want to see in discovery cursor.execute(""" CREATE TABLE IF NOT EXISTS discovery_artist_blacklist ( @@ -8626,7 +8635,8 @@ class MusicDatabase: def record_track_download(self, file_path: str, source_service: str, source_username: str, source_filename: str, source_size: int = 0, audio_quality: str = '', track_title: str = '', track_artist: str = '', track_album: str = '', - status: str = 'completed', track_id: str = None) -> Optional[int]: + status: str = 'completed', track_id: str = None, + bit_depth: int = None, sample_rate: int = None, bitrate: int = None) -> Optional[int]: """Record a download with full source provenance. Returns the record ID.""" try: conn = self._get_connection() @@ -8642,10 +8652,12 @@ class MusicDatabase: cursor.execute(""" INSERT INTO track_downloads (track_id, file_path, source_service, source_username, source_filename, - source_size, audio_quality, track_title, track_artist, track_album, status) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + source_size, audio_quality, track_title, track_artist, track_album, status, + bit_depth, sample_rate, bitrate) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, (track_id, file_path, source_service, source_username, source_filename, - source_size, audio_quality, track_title, track_artist, track_album, status)) + source_size, audio_quality, track_title, track_artist, track_album, status, + bit_depth, sample_rate, bitrate)) conn.commit() return cursor.lastrowid except Exception as e: diff --git a/web_server.py b/web_server.py index a18f7e8b..f2d69dd1 100644 --- a/web_server.py +++ b/web_server.py @@ -1728,6 +1728,21 @@ def _record_download_provenance(context): quality = context.get('_audio_quality', '') size = search_result.get('size', 0) + # Read audio details from the file for provenance (survives transcoding) + bit_depth = None + sample_rate = None + bitrate = None + try: + if file_path and os.path.isfile(file_path): + from mutagen import File as MutagenFile + audio = MutagenFile(file_path) + if audio and audio.info: + sample_rate = getattr(audio.info, 'sample_rate', None) + bitrate = getattr(audio.info, 'bitrate', None) + bit_depth = getattr(audio.info, 'bits_per_sample', None) + except Exception: + pass + db = get_database() db.record_track_download( file_path=file_path, @@ -1739,6 +1754,9 @@ def _record_download_provenance(context): track_title=title, track_artist=artist_name, track_album=album_name, + bit_depth=bit_depth, + sample_rate=sample_rate, + bitrate=bitrate, ) except Exception: pass # Non-critical, never block download flow diff --git a/webui/static/script.js b/webui/static/script.js index e759cf1c..2e29f258 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -44658,6 +44658,10 @@ async function showTrackSourceInfo(track, anchorEl) { Quality ${_esc(dl.audio_quality)} ` : ''} + ${dl.bit_depth || dl.sample_rate || dl.bitrate ? `
+ Audio + ${[dl.bit_depth ? `${dl.bit_depth}-bit` : '', dl.sample_rate ? `${(dl.sample_rate / 1000).toFixed(1)}kHz` : '', dl.bitrate ? `${Math.round(dl.bitrate / 1000)}kbps` : ''].filter(Boolean).join(' · ')} +
` : ''} ${dateStr ? `
Downloaded ${dateStr}