Show bitrate in browser, remove type labels and Play All text

- Extract bitrate alongside duration in browse_directory via get_media_info
- Display bitrate in large card view metadata (duration · bitrate · size)
- Replace Audio/Video type badge with bitrate column in list view
- Remove Play All button text, keep icon only
- Add formatBitrate helper function

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 03:37:13 +03:00
parent 5f474d6c9f
commit 4c13322936
4 changed files with 32 additions and 34 deletions

View File

@@ -121,24 +121,28 @@ class BrowserService:
return "other"
@staticmethod
def get_duration(file_path: Path) -> Optional[float]:
"""Get duration of a media file in seconds (header-only read).
def get_media_info(file_path: Path) -> dict:
"""Get duration and bitrate of a media file (header-only read).
Args:
file_path: Path to the media file.
Returns:
Duration in seconds, or None if unavailable.
Dict with 'duration' (float or None) and 'bitrate' (int or None).
"""
result = {"duration": None, "bitrate": None}
if not HAS_MUTAGEN:
return None
return result
try:
audio = MutagenFile(str(file_path))
if audio is not None and hasattr(audio, "info") and hasattr(audio.info, "length"):
return round(audio.info.length, 2)
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
except Exception:
pass
return None
return result
@staticmethod
def browse_directory(
@@ -255,9 +259,12 @@ class BrowserService:
item["modified"] = None
if item["is_media"]:
item["duration"] = BrowserService.get_duration(item_path)
info = BrowserService.get_media_info(item_path)
item["duration"] = info["duration"]
item["bitrate"] = info["bitrate"]
else:
item["duration"] = None
item["bitrate"] = None
return {
"folder_id": folder_id,

View File

@@ -1542,23 +1542,12 @@
min-width: 0;
}
.browser-list-type {
font-size: 0.625rem;
text-transform: uppercase;
font-weight: 600;
color: var(--text-secondary);
padding: 0.15rem 0.5rem;
background: var(--bg-primary);
border-radius: 4px;
.browser-list-bitrate {
font-size: 0.75rem;
color: var(--text-muted);
white-space: nowrap;
}
.browser-list-type.audio {
color: var(--accent);
}
.browser-list-type.video {
color: #3b82f6;
min-width: 55px;
text-align: right;
}
.browser-list-duration {

View File

@@ -193,7 +193,6 @@
</button>
<button class="browser-play-all-btn" id="playAllBtn" onclick="playAllFolder()" data-i18n-title="browser.play_all" title="Play All" style="display: none;">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M8 5v14l11-7z"/></svg>
<span data-i18n="browser.play_all">Play All</span>
</button>
</div>
<div class="browser-search-wrapper" id="browserSearchWrapper" style="display: none;">

View File

@@ -1711,15 +1711,11 @@ function renderBrowserList(items, container) {
name.textContent = item.name;
row.appendChild(name);
// Type badge
if (item.type !== 'folder') {
const typeBadge = document.createElement('div');
typeBadge.className = `browser-list-type ${item.type}`;
typeBadge.textContent = item.type;
row.appendChild(typeBadge);
} else {
row.appendChild(document.createElement('div'));
}
// Bitrate
const br = document.createElement('div');
br.className = 'browser-list-bitrate';
br.textContent = formatBitrate(item.bitrate) || '';
row.appendChild(br);
// Duration
const dur = document.createElement('div');
@@ -1833,6 +1829,8 @@ function renderBrowserGrid(items, container) {
const parts = [];
const duration = formatDuration(item.duration);
if (duration) parts.push(duration);
const bitrate = formatBitrate(item.bitrate);
if (bitrate) parts.push(bitrate);
if (item.size !== null) parts.push(formatFileSize(item.size));
meta.textContent = parts.join(' \u00B7 ');
if (parts.length) info.appendChild(meta);
@@ -1902,6 +1900,11 @@ function formatDuration(seconds) {
return `${m}:${String(s).padStart(2, '0')}`;
}
function formatBitrate(bps) {
if (bps == null || bps <= 0) return null;
return Math.round(bps / 1000) + ' kbps';
}
async function loadThumbnail(imgElement, fileName) {
try {
const token = localStorage.getItem('media_server_token');