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:
@@ -121,24 +121,28 @@ class BrowserService:
|
|||||||
return "other"
|
return "other"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_duration(file_path: Path) -> Optional[float]:
|
def get_media_info(file_path: Path) -> dict:
|
||||||
"""Get duration of a media file in seconds (header-only read).
|
"""Get duration and bitrate 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:
|
||||||
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:
|
if not HAS_MUTAGEN:
|
||||||
return None
|
return result
|
||||||
try:
|
try:
|
||||||
audio = MutagenFile(str(file_path))
|
audio = MutagenFile(str(file_path))
|
||||||
if audio is not None and hasattr(audio, "info") and hasattr(audio.info, "length"):
|
if audio is not None and hasattr(audio, "info"):
|
||||||
return round(audio.info.length, 2)
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def browse_directory(
|
def browse_directory(
|
||||||
@@ -255,9 +259,12 @@ class BrowserService:
|
|||||||
item["modified"] = None
|
item["modified"] = None
|
||||||
|
|
||||||
if item["is_media"]:
|
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:
|
else:
|
||||||
item["duration"] = None
|
item["duration"] = None
|
||||||
|
item["bitrate"] = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"folder_id": folder_id,
|
"folder_id": folder_id,
|
||||||
|
|||||||
@@ -1542,23 +1542,12 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser-list-type {
|
.browser-list-bitrate {
|
||||||
font-size: 0.625rem;
|
font-size: 0.75rem;
|
||||||
text-transform: uppercase;
|
color: var(--text-muted);
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
padding: 0.15rem 0.5rem;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border-radius: 4px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
min-width: 55px;
|
||||||
|
text-align: right;
|
||||||
.browser-list-type.audio {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.browser-list-type.video {
|
|
||||||
color: #3b82f6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser-list-duration {
|
.browser-list-duration {
|
||||||
|
|||||||
@@ -193,7 +193,6 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="browser-play-all-btn" id="playAllBtn" onclick="playAllFolder()" data-i18n-title="browser.play_all" title="Play All" style="display: none;">
|
<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>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="browser-search-wrapper" id="browserSearchWrapper" style="display: none;">
|
<div class="browser-search-wrapper" id="browserSearchWrapper" style="display: none;">
|
||||||
|
|||||||
@@ -1711,15 +1711,11 @@ function renderBrowserList(items, container) {
|
|||||||
name.textContent = item.name;
|
name.textContent = item.name;
|
||||||
row.appendChild(name);
|
row.appendChild(name);
|
||||||
|
|
||||||
// Type badge
|
// Bitrate
|
||||||
if (item.type !== 'folder') {
|
const br = document.createElement('div');
|
||||||
const typeBadge = document.createElement('div');
|
br.className = 'browser-list-bitrate';
|
||||||
typeBadge.className = `browser-list-type ${item.type}`;
|
br.textContent = formatBitrate(item.bitrate) || '';
|
||||||
typeBadge.textContent = item.type;
|
row.appendChild(br);
|
||||||
row.appendChild(typeBadge);
|
|
||||||
} else {
|
|
||||||
row.appendChild(document.createElement('div'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration
|
// Duration
|
||||||
const dur = document.createElement('div');
|
const dur = document.createElement('div');
|
||||||
@@ -1833,6 +1829,8 @@ function renderBrowserGrid(items, container) {
|
|||||||
const parts = [];
|
const parts = [];
|
||||||
const duration = formatDuration(item.duration);
|
const duration = formatDuration(item.duration);
|
||||||
if (duration) parts.push(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));
|
if (item.size !== null) parts.push(formatFileSize(item.size));
|
||||||
meta.textContent = parts.join(' \u00B7 ');
|
meta.textContent = parts.join(' \u00B7 ');
|
||||||
if (parts.length) info.appendChild(meta);
|
if (parts.length) info.appendChild(meta);
|
||||||
@@ -1902,6 +1900,11 @@ function formatDuration(seconds) {
|
|||||||
return `${m}:${String(s).padStart(2, '0')}`;
|
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) {
|
async function loadThumbnail(imgElement, fileName) {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('media_server_token');
|
const token = localStorage.getItem('media_server_token');
|
||||||
|
|||||||
Reference in New Issue
Block a user