Cap Opus bitrate at 256kbps, fix lossy copy flavor text, redesign artist action buttons

This commit is contained in:
Broque Thomas 2026-03-22 16:34:36 -07:00
parent 21d7e65986
commit 93005298ee
5 changed files with 128 additions and 27 deletions

View file

@ -1924,6 +1924,9 @@ class RepairWorker:
if self._config_manager:
codec = self._config_manager.get('lossy_copy.codec', 'mp3').lower()
bitrate = self._config_manager.get('lossy_copy.bitrate', '320')
# Opus max per-channel bitrate is 256kbps — cap to avoid encoding failures
if codec == 'opus' and int(bitrate) > 256:
bitrate = '256'
quality_label = f'{codec.upper()}-{bitrate}'
codec_configs = {

View file

@ -14783,6 +14783,10 @@ def _create_lossy_copy(final_path):
codec = config_manager.get('lossy_copy.codec', 'mp3').lower()
bitrate = config_manager.get('lossy_copy.bitrate', '320')
# Opus max per-channel bitrate is 256kbps — cap to avoid encoding failures
if codec == 'opus' and int(bitrate) > 256:
bitrate = '256'
# Codec configuration: (ffmpeg_codec, extension, quality_label, extra_args)
codec_map = {
'mp3': ('libmp3lame', '.mp3', f'MP3-{bitrate}', ['-id3v2_version', '3']),

View file

@ -4652,7 +4652,7 @@
<div id="lossy-copy-options" style="display: none;">
<div class="form-group">
<label>Codec:</label>
<select id="lossy-copy-codec">
<select id="lossy-copy-codec" onchange="updateLossyBitrateOptions()">
<option value="mp3">MP3</option>
<option value="opus">Opus</option>
<option value="aac">AAC (M4A)</option>
@ -4679,7 +4679,7 @@
</div>
<div class="help-text" style="color: rgba(255, 100, 100, 0.8);">
Warning: The original high-quality file will be permanently deleted.
Only the MP3 copy will remain.
Only the lossy copy will remain.
</div>
</div>
</div>

View file

@ -5401,6 +5401,20 @@ function loadHybridSourceOrder(settings) {
buildHybridSourceList();
}
function updateLossyBitrateOptions() {
const codec = document.getElementById('lossy-copy-codec')?.value || 'mp3';
const bitrateSelect = document.getElementById('lossy-copy-bitrate');
if (!bitrateSelect) return;
const opt320 = bitrateSelect.querySelector('option[value="320"]');
if (codec === 'opus') {
// Opus max is 256kbps per channel — hide 320 option
if (opt320) opt320.disabled = true;
if (bitrateSelect.value === '320') bitrateSelect.value = '256';
} else {
if (opt320) opt320.disabled = false;
}
}
async function loadSettingsData() {
try {
const response = await fetch(API.settings);
@ -5591,6 +5605,7 @@ async function loadSettingsData() {
document.getElementById('lossy-copy-enabled').checked = settings.lossy_copy?.enabled === true;
document.getElementById('lossy-copy-codec').value = settings.lossy_copy?.codec || 'mp3';
document.getElementById('lossy-copy-bitrate').value = settings.lossy_copy?.bitrate || '320';
updateLossyBitrateOptions();
document.getElementById('lossy-copy-delete-original').checked = settings.lossy_copy?.delete_original === true;
// Populate Listening Stats settings

View file

@ -16815,43 +16815,87 @@ body {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 18px;
font-size: 14px;
padding: 10px 20px;
font-size: 13px;
font-weight: 600;
color: #e0e0e0;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
letter-spacing: 0.02em;
color: rgba(255, 255, 255, 0.85);
background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.02) 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
position: relative;
overflow: hidden;
backdrop-filter: blur(8px);
}
.library-artist-watchlist-btn::before {
content: '';
position: absolute;
inset: 0;
border-radius: 12px;
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.2) 0%, rgba(var(--accent-rgb), 0.05) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.library-artist-watchlist-btn:hover:not(:disabled)::before {
opacity: 1;
}
.library-artist-watchlist-btn:hover:not(:disabled) {
background: rgba(var(--accent-rgb), 0.15);
color: #ffffff;
border-color: rgba(var(--accent-rgb), 0.3);
transform: translateY(-1px);
border-color: rgba(var(--accent-rgb), 0.35);
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(var(--accent-rgb), 0.2), 0 0 0 1px rgba(var(--accent-rgb), 0.1);
}
.library-artist-watchlist-btn:active:not(:disabled) {
transform: translateY(0);
box-shadow: none;
}
.library-artist-watchlist-btn .watchlist-icon {
font-size: 14px;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
z-index: 1;
}
.library-artist-watchlist-btn:hover:not(:disabled) .watchlist-icon {
transform: scale(1.15);
}
.library-artist-watchlist-btn .watchlist-text {
position: relative;
z-index: 1;
}
.library-artist-watchlist-btn.watching {
background: rgba(255, 193, 7, 0.15);
background: linear-gradient(135deg, rgba(255, 193, 7, 0.15) 0%, rgba(255, 193, 7, 0.05) 100%);
color: #ffc107;
border-color: rgba(255, 193, 7, 0.3);
border-color: rgba(255, 193, 7, 0.25);
box-shadow: 0 0 12px rgba(255, 193, 7, 0.08);
}
.library-artist-watchlist-btn.watching::before {
background: linear-gradient(135deg, rgba(255, 193, 7, 0.25) 0%, rgba(255, 193, 7, 0.08) 100%);
}
.library-artist-watchlist-btn.watching:hover:not(:disabled) {
background: rgba(255, 193, 7, 0.25);
color: #ffffff;
border-color: rgba(255, 193, 7, 0.5);
box-shadow: 0 4px 20px rgba(255, 193, 7, 0.25), 0 0 0 1px rgba(255, 193, 7, 0.15);
}
.library-artist-watchlist-btn:disabled {
opacity: 0.6;
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* ─── Enhance Quality Button ─── */
@ -16860,28 +16904,63 @@ body {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 18px;
font-size: 14px;
padding: 10px 20px;
font-size: 13px;
font-weight: 600;
color: #4fc3f7;
background: rgba(79, 195, 247, 0.08);
border: 1px solid rgba(79, 195, 247, 0.2);
border-radius: 16px;
letter-spacing: 0.02em;
color: rgba(79, 195, 247, 0.9);
background: linear-gradient(135deg, rgba(79, 195, 247, 0.08) 0%, rgba(79, 195, 247, 0.02) 100%);
border: 1px solid rgba(79, 195, 247, 0.15);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
position: relative;
overflow: hidden;
backdrop-filter: blur(8px);
}
.library-artist-enhance-btn::before {
content: '';
position: absolute;
inset: 0;
border-radius: 12px;
background: linear-gradient(135deg, rgba(79, 195, 247, 0.22) 0%, rgba(100, 220, 255, 0.08) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.library-artist-enhance-btn:hover::before {
opacity: 1;
}
.library-artist-enhance-btn:hover {
background: rgba(79, 195, 247, 0.18);
color: #ffffff;
border-color: rgba(79, 195, 247, 0.4);
transform: translateY(-1px);
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(79, 195, 247, 0.2), 0 0 0 1px rgba(79, 195, 247, 0.1);
}
.library-artist-enhance-btn:active {
transform: translateY(0);
box-shadow: none;
}
.library-artist-enhance-btn .enhance-icon {
font-size: 16px;
font-size: 14px;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
z-index: 1;
}
.library-artist-enhance-btn:hover .enhance-icon {
transform: scale(1.2) rotate(8deg);
}
.library-artist-enhance-btn .enhance-text {
position: relative;
z-index: 1;
}
.library-artist-enhance-btn.hidden {