Compare commits

...

1 Commits

Author SHA1 Message Date
13df69adb4 Show media title (Artist – Title) instead of filename when available
- 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 <noreply@anthropic.com>
2026-02-09 03:42:32 +03:00
2 changed files with 26 additions and 11 deletions

View File

@@ -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,

View File

@@ -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 = '';