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>
This commit is contained in:
@@ -122,24 +122,37 @@ class BrowserService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_media_info(file_path: Path) -> dict:
|
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:
|
Args:
|
||||||
file_path: Path to the media file.
|
file_path: Path to the media file.
|
||||||
|
|
||||||
Returns:
|
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:
|
if not HAS_MUTAGEN:
|
||||||
return result
|
return result
|
||||||
try:
|
try:
|
||||||
audio = MutagenFile(str(file_path))
|
audio = MutagenFile(str(file_path), easy=True)
|
||||||
if audio is not None and hasattr(audio, "info"):
|
if audio is not None and hasattr(audio, "info"):
|
||||||
if hasattr(audio.info, "length"):
|
if hasattr(audio.info, "length"):
|
||||||
result["duration"] = round(audio.info.length, 2)
|
result["duration"] = round(audio.info.length, 2)
|
||||||
if hasattr(audio.info, "bitrate") and audio.info.bitrate:
|
if hasattr(audio.info, "bitrate") and audio.info.bitrate:
|
||||||
result["bitrate"] = 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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return result
|
return result
|
||||||
@@ -262,9 +275,11 @@ class BrowserService:
|
|||||||
info = BrowserService.get_media_info(item_path)
|
info = BrowserService.get_media_info(item_path)
|
||||||
item["duration"] = info["duration"]
|
item["duration"] = info["duration"]
|
||||||
item["bitrate"] = info["bitrate"]
|
item["bitrate"] = info["bitrate"]
|
||||||
|
item["title"] = info["title"]
|
||||||
else:
|
else:
|
||||||
item["duration"] = None
|
item["duration"] = None
|
||||||
item["bitrate"] = None
|
item["bitrate"] = None
|
||||||
|
item["title"] = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"folder_id": folder_id,
|
"folder_id": folder_id,
|
||||||
|
|||||||
@@ -1705,10 +1705,10 @@ function renderBrowserList(items, container) {
|
|||||||
}
|
}
|
||||||
row.appendChild(icon);
|
row.appendChild(icon);
|
||||||
|
|
||||||
// Name
|
// Name (show media title if available)
|
||||||
const name = document.createElement('div');
|
const name = document.createElement('div');
|
||||||
name.className = 'browser-list-name';
|
name.className = 'browser-list-name';
|
||||||
name.textContent = item.name;
|
name.textContent = item.title || item.name;
|
||||||
row.appendChild(name);
|
row.appendChild(name);
|
||||||
|
|
||||||
// Bitrate
|
// Bitrate
|
||||||
@@ -1736,9 +1736,9 @@ function renderBrowserList(items, container) {
|
|||||||
row.appendChild(document.createElement('div'));
|
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', () => {
|
row.addEventListener('mouseenter', () => {
|
||||||
if (name.scrollWidth > name.clientWidth) {
|
if (item.title || name.scrollWidth > name.clientWidth) {
|
||||||
row.title = item.name;
|
row.title = item.name;
|
||||||
} else {
|
} else {
|
||||||
row.title = '';
|
row.title = '';
|
||||||
@@ -1820,7 +1820,7 @@ function renderBrowserGrid(items, container) {
|
|||||||
|
|
||||||
const name = document.createElement('div');
|
const name = document.createElement('div');
|
||||||
name.className = 'browser-item-name';
|
name.className = 'browser-item-name';
|
||||||
name.textContent = item.name;
|
name.textContent = item.title || item.name;
|
||||||
info.appendChild(name);
|
info.appendChild(name);
|
||||||
|
|
||||||
if (item.type !== 'folder') {
|
if (item.type !== 'folder') {
|
||||||
@@ -1838,9 +1838,9 @@ function renderBrowserGrid(items, container) {
|
|||||||
|
|
||||||
div.appendChild(info);
|
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', () => {
|
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;
|
div.title = item.name;
|
||||||
} else {
|
} else {
|
||||||
div.title = '';
|
div.title = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user