diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e5145..46fd5ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.12.13] — 2026-06-27 + +### Changed +- **Speed column split into Factor + Time**: the old `21.8s · 1.30x` cell is now two separate sortable columns — **Factor** (`1.30x`, audio÷render, colour-coded green/amber/red) and **Time** (`21.8s`, total render time). Column order: Length · Duration · Factor · Time · WPM · Seed · dBFS · Type · Source · Rating · Tags · Note · Active. + +--- + ## [1.12.12] — 2026-06-27 ### Added diff --git a/static/index.html b/static/index.html index 17ca153..4bf26af 100644 --- a/static/index.html +++ b/static/index.html @@ -26,7 +26,7 @@ - + @@ -336,7 +336,7 @@ - + diff --git a/static/js/voice-library.js b/static/js/voice-library.js index 2344cf8..4bbd861 100644 --- a/static/js/voice-library.js +++ b/static/js/voice-library.js @@ -116,7 +116,9 @@ function getSortValue(v, field) { case 'file_type': return voiceFileType(v); case 'duration': return v.duration || 0; case 'dbfs': return voiceDbfs(v) ?? -999; - case 'benchmark': { const b = voiceBenchmark(v); return b && b.speed != null ? -Number(b.speed) : 999; } + case 'benchmark': + case 'factor': return -(voiceFactor(v) ?? -999); + case 'elapsed': return voiceBenchmarkElapsed(v) ?? 999; case 'bench_audio': return voiceBenchmarkAudioSec(v) ?? 999; case 'wpm': return voiceWpm(v) ?? -1; case 'transcript': return (v.transcript || '').toLowerCase(); @@ -379,14 +381,28 @@ function voiceBenchmarkElapsed(v) { return value == null || Number.isNaN(Number(value)) ? null : Number(value); } -function fmtBenchmark(v) { +function voiceFactor(v) { const b = voiceBenchmark(v); - if (!b) return '-'; - if (!b.ok) return 'ERR'; - const elapsed = voiceBenchmarkElapsed(v); - if (elapsed == null) return '-'; - const speed = b.speed != null ? ` · ${Number(b.speed).toFixed(2)}x` : ''; - return elapsed.toFixed(1) + 's' + speed; + return b && b.ok && b.speed != null ? Number(b.speed) : null; +} + +function fmtFactor(v) { + const f = voiceFactor(v); + return f != null ? f.toFixed(2) + 'x' : '-'; +} + +function fmtElapsed(v) { + const e = voiceBenchmarkElapsed(v); + const b = voiceBenchmark(v); + if (!b || !b.ok) return b && !b.ok ? 'ERR' : '-'; + return e != null ? e.toFixed(1) + 's' : '-'; +} + +function fmtBenchmark(v) { + const elapsed = fmtElapsed(v); + const factor = fmtFactor(v); + if (elapsed === '-' && factor === '-') return '-'; + return [elapsed, factor].filter(x => x !== '-').join(' · '); } function benchmarkClass(v) { @@ -865,23 +881,15 @@ async function runVoiceBenchmarkBatch() { stats.last = `${voice.id}: ${Number(b.elapsed_sec || 0).toFixed(1)}s${b.speed != null ? ` · ${Number(b.speed).toFixed(2)}x` : ''}${b.realtime_ok === false ? ' · slow' : ''}`; // Live-update the row cells so results appear immediately if (row) { - const benchCell = row.querySelector('.vl-tbl-bench'); - const durCell = row.querySelector('.vl-tbl-dur'); - const wpmCell = row.querySelector('.vl-tbl-wpm'); - if (benchCell) { - const bt = fmtBenchmark(voice), bc = benchmarkClass(voice), btitle = benchmarkTitle(voice); - benchCell.innerHTML = bt !== '-' ? ` ${escHtml(bt)}` : '-'; - } - if (durCell) { - const sec = voiceBenchmarkAudioSec(voice); - durCell.textContent = fmtBenchmarkAudio(voice); - durCell.title = sec != null ? `${fmtBenchmarkAudio(voice)} of synthesised audio for the benchmark sentence` : ''; - } - if (wpmCell) { - const wpm = voiceWpm(voice); - wpmCell.textContent = fmtWpm(voice); - wpmCell.title = wpm != null ? `${wpm} wpm — 130–180 wpm is natural for long listening` : ''; - } + const bc = benchmarkClass(voice), btitle = benchmarkTitle(voice); + const durCell = row.querySelector('.vl-tbl-dur'); + const factorCell = row.querySelector('.vl-tbl-factor'); + const timeCell = row.querySelector('.vl-tbl-time'); + const wpmCell = row.querySelector('.vl-tbl-wpm'); + if (durCell) { durCell.textContent = fmtBenchmarkAudio(voice); durCell.title = `${fmtBenchmarkAudio(voice)} — length of synthesised benchmark audio`; } + if (factorCell) { factorCell.textContent = fmtFactor(voice); factorCell.className = `vl-tbl-factor ${bc}`; factorCell.title = btitle; } + if (timeCell) { timeCell.textContent = fmtElapsed(voice); timeCell.className = `vl-tbl-time ${bc}`; timeCell.title = btitle; } + if (wpmCell) { const wpm = voiceWpm(voice); wpmCell.textContent = fmtWpm(voice); wpmCell.title = wpm != null ? `${wpm} wpm — 130–180 wpm is natural for long listening` : ''; } } } else { stats.errors++; @@ -2010,12 +2018,13 @@ function makeVoiceRow(v) { ? `` : `${genderMap[gender]||'?'} ${escHtml(genderLabel[gender]||'—')}`} -
${isClone ? 'Clone' : 'Design'}
-
${benchText !== '-' ? ` ${escHtml(benchText)}` : '-'}
-
${dbfs} dB
${fmtDuration(v.duration)}
-
${escHtml(fmtBenchmarkAudio(v))}
+
${escHtml(fmtBenchmarkAudio(v))}
+
${escHtml(fmtFactor(v))}
+
${escHtml(fmtElapsed(v))}
${escHtml(fmtWpm(v))}
+
${dbfs} dB
+
${isClone ? 'Clone' : 'Design'}
${window._voiceTableEditMode ? `` @@ -2981,23 +2990,15 @@ function makeVoiceRow(v) { // Also update the table row cells const tblRow = document.querySelector(`.vl-row[data-id="${CSS.escape(v.id)}"]`); if (tblRow) { - const tblBench = tblRow.querySelector('.vl-tbl-bench'); - const tblDur = tblRow.querySelector('.vl-tbl-dur'); - const tblWpm = tblRow.querySelector('.vl-tbl-wpm'); - if (tblBench) { - const bt = fmtBenchmark(v), bc = benchmarkClass(v), btitle = benchmarkTitle(v); - tblBench.innerHTML = bt !== '-' ? ` ${escHtml(bt)}` : '-'; - } - if (tblDur) { - const sec = voiceBenchmarkAudioSec(v); - tblDur.textContent = fmtBenchmarkAudio(v); - tblDur.title = sec != null ? `${fmtBenchmarkAudio(v)} of synthesised audio for the benchmark sentence` : ''; - } - if (tblWpm) { - const wpm = voiceWpm(v); - tblWpm.textContent = fmtWpm(v); - tblWpm.title = wpm != null ? `${wpm} wpm — 130–180 wpm is natural for long listening` : ''; - } + const bc = benchmarkClass(v), btitle = benchmarkTitle(v); + const tblDur = tblRow.querySelector('.vl-tbl-dur'); + const tblFactor = tblRow.querySelector('.vl-tbl-factor'); + const tblTime = tblRow.querySelector('.vl-tbl-time'); + const tblWpm = tblRow.querySelector('.vl-tbl-wpm'); + if (tblDur) { tblDur.textContent = fmtBenchmarkAudio(v); tblDur.title = `${fmtBenchmarkAudio(v)} — length of synthesised benchmark audio`; } + if (tblFactor) { tblFactor.textContent = fmtFactor(v); tblFactor.className = `vl-tbl-factor ${bc}`; tblFactor.title = btitle; } + if (tblTime) { tblTime.textContent = fmtElapsed(v); tblTime.className = `vl-tbl-time ${bc}`; tblTime.title = btitle; } + if (tblWpm) { const wpm = voiceWpm(v); tblWpm.textContent = fmtWpm(v); tblWpm.title = wpm != null ? `${wpm} wpm — 130–180 wpm is natural for long listening` : ''; } } setBenchmarkProgress(1, 1, `Finished ${v.id}`); toast('Benchmarked ' + v.id, 'success'); diff --git a/static/sections/s-voices.html b/static/sections/s-voices.html index 0ed015c..7330ad2 100644 --- a/static/sections/s-voices.html +++ b/static/sections/s-voices.html @@ -109,9 +109,10 @@
Lang
Gender
Length
-
Duration
+
Duration
+
Factor
+
Time
WPM
-
Speed
Seed
dBFS
Type
diff --git a/static/style.css b/static/style.css index b1f1f27..293aaab 100644 --- a/static/style.css +++ b/static/style.css @@ -1516,7 +1516,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .vl-dbfs { display: none; } /* Table View mode */ -.vl-tbl-lang, .vl-tbl-gender, .vl-tbl-type, .vl-tbl-bench, .vl-tbl-dbfs, .vl-tbl-len, .vl-tbl-dur, .vl-tbl-wpm, .vl-tbl-rating, .vl-tbl-source, .vl-tbl-seed, .vl-tbl-note, .vl-tbl-tag, .vl-tbl-active { display: none; } +.vl-tbl-lang, .vl-tbl-gender, .vl-tbl-type, .vl-tbl-bench, .vl-tbl-dbfs, .vl-tbl-len, .vl-tbl-dur, .vl-tbl-factor, .vl-tbl-time, .vl-tbl-wpm, .vl-tbl-rating, .vl-tbl-source, .vl-tbl-seed, .vl-tbl-note, .vl-tbl-tag, .vl-tbl-active { display: none; } .vl-table-header { display: none; } .voices-workbench.table-view .voices-list-pane { width: 100% !important; max-width: 100%; } @@ -1524,8 +1524,8 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .voices-workbench.table-view .vl-table-header { display: grid; - grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 60px 70px 55px 55px 55px 80px 55px 70px 60px 90px 70px minmax(80px, 1fr) minmax(120px, 1fr) 50px; - gap: 10px; + grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 55px 65px 50px 55px 55px 55px 55px 50px 65px 55px 85px 65px minmax(80px, 1fr) minmax(120px, 1fr) 50px; + gap: 8px; align-items: center; padding: 8px 10px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--subtext); @@ -1543,8 +1543,8 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .voices-workbench.table-view .vl-compact { display: grid; - grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 60px 70px 55px 55px 55px 80px 55px 70px 60px 90px 70px minmax(80px, 1fr) minmax(120px, 1fr) 50px; - gap: 10px; + grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 55px 65px 50px 55px 55px 55px 55px 50px 65px 55px 85px 65px minmax(80px, 1fr) minmax(120px, 1fr) 50px; + gap: 8px; align-items: center; } .voices-workbench.table-view .vl-bulk-cb { order: 0; margin: 0; justify-self: center; } @@ -1558,17 +1558,22 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .voices-workbench.table-view .vl-tbl-gender { display: block; order: 5; font-size: 12px; } .voices-workbench.table-view .vl-tbl-len { display: block; order: 6; font-size: 12px; font-family: monospace; } .voices-workbench.table-view .vl-tbl-dur { display: block; order: 7; font-size: 12px; font-family: monospace; } -.voices-workbench.table-view .vl-tbl-wpm { display: block; order: 8; font-size: 12px; font-family: monospace; } -.voices-workbench.table-view .vl-tbl-bench { display: block; order: 9; font-size: 12px; } -.voices-workbench.table-view .vl-tbl-seed { display: block; order: 10; font-size: 12px; font-family: monospace; } -.voices-workbench.table-view .vl-tbl-dbfs { display: block; order: 11; font-size: 12px; font-family: monospace; } -.voices-workbench.table-view .vl-tbl-type { display: block; order: 12; font-size: 12px; } -.voices-workbench.table-view .vl-tbl-source { display: block; order: 13; font-size: 12px; overflow: hidden; text-overflow: ellipsis; } -.voices-workbench.table-view .vl-tbl-rating { display: flex; order: 14; font-size: 14px; color: var(--subtext); } +.voices-workbench.table-view .vl-tbl-factor { display: block; order: 8; font-size: 12px; font-family: monospace; font-weight: 600; } +.voices-workbench.table-view .vl-tbl-time { display: block; order: 9; font-size: 12px; font-family: monospace; } +.voices-workbench.table-view .vl-tbl-wpm { display: block; order: 10; font-size: 12px; font-family: monospace; } +.voices-workbench.table-view .vl-tbl-seed { display: block; order: 11; font-size: 12px; font-family: monospace; } +.voices-workbench.table-view .vl-tbl-dbfs { display: block; order: 12; font-size: 12px; font-family: monospace; } +.voices-workbench.table-view .vl-tbl-type { display: block; order: 13; font-size: 12px; } +.voices-workbench.table-view .vl-tbl-source { display: block; order: 14; font-size: 12px; overflow: hidden; text-overflow: ellipsis; } +.voices-workbench.table-view .vl-tbl-rating { display: flex; order: 15; font-size: 14px; color: var(--subtext); } .voices-workbench.table-view .vl-tbl-rating .star.on { color: #f59e0b; } -.voices-workbench.table-view .vl-tbl-tag { display: block; order: 15; font-size: 12px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; } -.voices-workbench.table-view .vl-tbl-note { display: block; order: 16; font-size: 12px; color: var(--subtext); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: italic; } -.voices-workbench.table-view .vl-tbl-active { display: block; order: 17; font-size: 16px; text-align: center; } +.voices-workbench.table-view .vl-tbl-tag { display: block; order: 16; font-size: 12px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; } +.voices-workbench.table-view .vl-tbl-note { display: block; order: 17; font-size: 12px; color: var(--subtext); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: italic; } +.voices-workbench.table-view .vl-tbl-active { display: block; order: 18; font-size: 16px; text-align: center; } +.voices-workbench.table-view .vl-tbl-factor.bench-ok { color: var(--success, #16a34a); } +.voices-workbench.table-view .vl-tbl-factor.bench-warn { color: #d97706; } +.voices-workbench.table-view .vl-tbl-factor.bench-bad { color: var(--error, #dc2626); } +.voices-workbench.table-view .vl-tbl-time.bench-bad { color: var(--error, #dc2626); } .voices-workbench.table-edit-mode .vl-compact { background: rgba(37,99,235,0.03);