From 13df69adb4149ec919b7d893a6de41e0685c3ece Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Mon, 9 Feb 2026 03:42:32 +0300 Subject: [PATCH] =?UTF-8?q?Show=20media=20title=20(Artist=20=E2=80=93=20Ti?= =?UTF-8?q?tle)=20instead=20of=20filename=20when=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract title and artist tags via mutagen easy=True in get_media_info - Display "Artist – Title" in both grid and list views, fall back to filename - Show original filename in tooltip on hover Co-Authored-By: Claude Opus 4.6 --- media_server/services/browser_service.py | 23 +++++++++++++++++++---- media_server/static/js/app.js | 14 +++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/media_server/services/browser_service.py b/media_server/services/browser_service.py index bdf18fd..7f073cd 100644 --- a/media_server/services/browser_service.py +++ b/media_server/services/browser_service.py @@ -122,24 +122,37 @@ class BrowserService: @staticmethod def get_media_info(file_path: Path) -> dict: - """Get duration and bitrate of a media file (header-only read). + """Get duration, bitrate, and title of a media file (header-only read). Args: file_path: Path to the media file. Returns: - Dict with 'duration' (float or None) and 'bitrate' (int or None). + Dict with 'duration' (float or None), 'bitrate' (int or None), + and 'title' (str or None). """ - result = {"duration": None, "bitrate": None} + result = {"duration": None, "bitrate": None, "title": None} if not HAS_MUTAGEN: return result try: - audio = MutagenFile(str(file_path)) + audio = MutagenFile(str(file_path), easy=True) if audio is not None and hasattr(audio, "info"): if hasattr(audio.info, "length"): result["duration"] = round(audio.info.length, 2) if hasattr(audio.info, "bitrate") and audio.info.bitrate: result["bitrate"] = audio.info.bitrate + if audio is not None and hasattr(audio, "tags") and audio.tags: + tags = audio.tags + title = None + artist = None + if "title" in tags: + title = tags["title"][0] if isinstance(tags["title"], list) else tags["title"] + if "artist" in tags: + artist = tags["artist"][0] if isinstance(tags["artist"], list) else tags["artist"] + if artist and title: + result["title"] = f"{artist} \u2013 {title}" + elif title: + result["title"] = title except Exception: pass return result @@ -262,9 +275,11 @@ class BrowserService: info = BrowserService.get_media_info(item_path) item["duration"] = info["duration"] item["bitrate"] = info["bitrate"] + item["title"] = info["title"] else: item["duration"] = None item["bitrate"] = None + item["title"] = None return { "folder_id": folder_id, diff --git a/media_server/static/js/app.js b/media_server/static/js/app.js index e108902..e0164cf 100644 --- a/media_server/static/js/app.js +++ b/media_server/static/js/app.js @@ -1705,10 +1705,10 @@ function renderBrowserList(items, container) { } row.appendChild(icon); - // Name + // Name (show media title if available) const name = document.createElement('div'); name.className = 'browser-list-name'; - name.textContent = item.name; + name.textContent = item.title || item.name; row.appendChild(name); // Bitrate @@ -1736,9 +1736,9 @@ function renderBrowserList(items, container) { row.appendChild(document.createElement('div')); } - // Tooltip on row when name is ellipsed + // Tooltip: show filename when title is displayed, or when name is ellipsed row.addEventListener('mouseenter', () => { - if (name.scrollWidth > name.clientWidth) { + if (item.title || name.scrollWidth > name.clientWidth) { row.title = item.name; } else { row.title = ''; @@ -1820,7 +1820,7 @@ function renderBrowserGrid(items, container) { const name = document.createElement('div'); name.className = 'browser-item-name'; - name.textContent = item.name; + name.textContent = item.title || item.name; info.appendChild(name); if (item.type !== 'folder') { @@ -1838,9 +1838,9 @@ function renderBrowserGrid(items, container) { div.appendChild(info); - // Tooltip on card when name is ellipsed + // Tooltip: show filename when title is displayed, or when name is ellipsed div.addEventListener('mouseenter', () => { - if (name.scrollWidth > name.clientWidth || name.scrollHeight > name.clientHeight) { + if (item.title || name.scrollWidth > name.clientWidth || name.scrollHeight > name.clientHeight) { div.title = item.name; } else { div.title = '';