tts-voice-creator-clone-and.../static/sections/s-voices.html
mARTin-B78 c5181b499a feat: add WPM column + info tooltips to voice table (v1.12.10)
WPM (words per minute) is derived from the benchmark audio duration and
sentence word count, revealing how fast a voice speaks — independent of
GPU speed. 130–180 wpm is comfortable for audiobooks. Column is sortable.
Info (ⓘ) icons on Speed and WPM headers explain both metrics on hover.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 22:41:01 +02:00

312 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="section-head">
<span class="section-icon"><span class="mdi mdi-account-voice"></span></span>
<div class="section-title">
<h2>My Voices</h2>
<p>Pick a voice on the left, edit on the right.</p>
</div>
<div class="section-head-actions">
<input type="search" class="section-search" id="section-voices-search" placeholder="Search voices&#x2026;" autocomplete="off"
oninput="var f=document.getElementById('library-filter-text');if(f){f.value=this.value;f.dispatchEvent(new Event('input'));}">
<button class="btn-primary section-new-voice-btn" onclick="document.getElementById('add-new-voice-btn')?.click()">+ New voice</button>
</div>
</div>
<!-- Workbench: list pane + inspector pane -->
<div class="voices-workbench" id="tab-library">
<!-- LEFT: compact voice list — two cards -->
<div class="voices-list-pane">
<!-- Card 1: TTS synth mode + preview sentence -->
<div class="vl-card vl-card-synth">
<div class="vl-synth-panel">
<div class="vl-synth-mode-row">
<span class="vl-synth-icon"><span class="mdi mdi-play"></span><span class="mdi mdi-play"></span></span>
<span class="vl-synth-label">Synth uses</span>
<div class="vl-synth-seg" id="vl-synth-mode-seg">
<button class="vl-synth-seg-btn active" data-mode="preview"
title="Synthesize the preview sentence — good for comparing voices side by side">Preview text</button>
<button class="vl-synth-seg-btn" data-mode="transcript"
title="Synthesize this voice&#x2019;s saved reference transcript — good for quality check vs. original recording">Reference transcript</button>
</div>
</div>
<div class="vl-preview-text-wrap" id="vl-preview-text-wrap">
<div class="vl-preview-row">
<button class="vl-preview-lang-btn" id="vl-preview-lang-btn"
title="English — click to change sample language"
onclick="openPreviewLangPicker(this)" data-lang="EN">
<span class="fi fi-gb"></span>
</button>
<textarea class="vl-preview-input" id="vl-preview-sample" rows="2"
placeholder="Sample sentence for TTS preview&#x2026;"
oninput="var t=document.getElementById('benchmark-sample-text');if(t){t.value=this.value;t.dispatchEvent(new Event('input'));}"
></textarea>
</div>
</div>
</div>
</div><!-- /vl-card-synth -->
<!-- Card 2: filters + sort + voice list + toolbar -->
<div class="vl-card vl-card-list">
<div class="vl-filters">
<select id="library-filter-lang" title="Language"><option value="">All</option></select>
<select id="library-filter-type" title="Type"><option value="">All types</option></select>
<label class="vl-disabled-label"><input type="checkbox" id="show-disabled-cb"> Disabled</label>
</div>
<div class="vl-sort-bar">
<input type="search" id="library-filter-text" placeholder="&#x1F50D; Search voices&#x2026;" autocomplete="off" class="vl-search vl-search-main">
<span class="vl-sort-label">Sort</span>
<select id="voice-sort-field" title="Sort field">
<option value="id">Name</option>
<option value="flag">Language</option>
<option value="gender">Gender</option>
<option value="benchmark">Speed</option>
<option value="rating">Rating</option>
<option value="duration">Duration</option>
<option value="dbfs">Volume dB</option>
<option value="source">Source</option>
<option value="seed">Seed</option>
<option value="note">Note</option>
<option value="tag">Tags</option>
<option value="enabled">Active</option>
</select>
<button id="voice-sort-dir" class="vl-sort-dir-btn" title="Toggle sort direction"><span class="mdi mdi-arrow-up"></span></button>
<button id="voice-group-tag-btn" class="vl-sort-dir-btn" title="Group into virtual folders by tag"><span class="mdi mdi-folder-outline"></span></button>
<button id="voice-table-view-btn" class="vl-sort-dir-btn" title="Toggle full-width table view"><span class="mdi mdi-table"></span></button>
<button id="voice-table-edit-btn" class="vl-sort-dir-btn" title="Toggle table edit mode" style="display:none"><span class="mdi mdi-pencil-box-outline"></span></button>
</div>
<div class="benchmark-confirm" id="benchmark-confirm" hidden role="group" aria-live="polite">
<strong id="benchmark-confirm-title">Benchmark active voices?</strong>
<span id="benchmark-confirm-text"></span>
<button class="btn-secondary" id="benchmark-confirm-cancel" type="button">Cancel</button>
<button class="benchmark-confirm-start" id="benchmark-confirm-start" type="button">Start benchmark</button>
</div>
<div id="voice-groups-bar" class="voice-groups-bar" hidden></div>
<!-- Bulk-edit toolbar — appears when voices are selected -->
<div class="vl-bulk-bar" id="vl-bulk-bar" hidden>
<span class="vl-bulk-count" id="vl-bulk-count">0 selected</span>
<button class="vl-bulk-btn" id="vl-bulk-select-all" title="Select all visible"><span class="mdi mdi-checkbox-multiple-marked-outline"></span> All</button>
<button class="vl-bulk-btn" id="vl-bulk-deselect" title="Clear selection"><span class="mdi mdi-checkbox-multiple-blank-outline"></span> None</button>
<div class="vl-bulk-sep"></div>
<button class="vl-bulk-btn" id="vl-bulk-tag" title="Add / replace tag on selected voices"><span class="mdi mdi-tag-outline"></span> Set tag</button>
<button class="vl-bulk-btn" id="vl-bulk-source" title="Set source / origin on selected voices"><span class="mdi mdi-database-outline"></span> Set source</button>
<button class="vl-bulk-btn" id="vl-bulk-hide" title="Disable (hide) selected voices"><span class="mdi mdi-eye-off-outline"></span> Hide</button>
<button class="vl-bulk-btn" id="vl-bulk-unhide" title="Enable (unhide) selected voices"><span class="mdi mdi-eye-outline"></span> Unhide</button>
<button class="vl-bulk-btn" id="vl-bulk-rating" title="Set star rating on selected"><span class="mdi mdi-star-outline"></span> Rate</button>
<div class="vl-bulk-sep"></div>
<button class="vl-bulk-btn vl-bulk-danger" id="vl-bulk-delete" title="Delete selected voices permanently"><span class="mdi mdi-delete-outline"></span> Delete</button>
</div>
<div class="vl-table-header" id="vl-table-header">
<div style="display:flex; justify-content:center; align-items:center;"><input type="checkbox" id="vl-select-all-header" title="Select all visible voices"></div>
<div>Img</div>
<div>Play</div>
<div class="vl-th-sortable" data-sort="id">Name <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="flag">Lang <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="gender">Gender <span class="mdi mdi-sort"></span></div>
<div>Type</div>
<div class="vl-th-sortable" data-sort="benchmark">Speed <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Synthesis speed: seconds of audio produced per second of compute (higher = faster GPU). Shown as: render_time · multiplier×"></span></div>
<div class="vl-th-sortable" data-sort="dbfs">dBFS <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="duration">Length <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="wpm">WPM <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Words per minute the voice speaks. 130180 wpm is natural for audiobooks; lower = slower, more deliberate pace."></span></div>
<div class="vl-th-sortable" data-sort="rating">Rating <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="source">Source <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="seed">Seed <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="note">Note <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="tag">Tags <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="enabled">Active <span class="mdi mdi-sort"></span></div>
</div>
<div id="voice-list" tabindex="0" role="region" aria-label="Voice library"></div>
<div class="vl-toolbar">
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list"><span class="mdi mdi-refresh"></span> Refresh</button>
<button class="btn-secondary vl-tb-btn" id="sync-voice-folders-btn" title="Sync active/hidden folders">Sync</button>
<button class="btn-secondary vl-tb-btn" id="calculate-db-btn" title="Calculate dBFS">Calc dB</button>
<button class="btn-secondary vl-tb-btn" id="benchmark-voices-btn" title="Benchmark TTS speed">Benchmark</button>
<button class="btn-secondary vl-tb-btn" id="copy-active-voices-btn" title="Copy active voice names">Copy active</button>
<button class="btn-secondary vl-tb-btn" id="precompute-embeddings-btn" title="Warm all active voices so the TTS engine pre-computes &amp; caches each speaker embedding (.pt) — makes first playback instant"><span class="mdi mdi-flash-outline"></span> Precompute</button>
<button class="btn-secondary vl-tb-btn" id="seed-batch-all-btn" title="Generate &amp; cache Seed Finder samples for every active voice (skips already-cached; resumable)"><span class="mdi mdi-dice-multiple-outline"></span> Batch seeds</button>
</div>
<div class="vl-footer">
<span id="voice-count" class="note"></span>
<button class="vl-add-btn" id="add-new-voice-btn" style="display:none" aria-hidden="true" tabindex="-1" title="Add a new voice to the library">+ Add voice</button>
</div>
</div><!-- /vl-card-list -->
</div><!-- /voices-list-pane -->
<!-- RIGHT: inspector / editor pane -->
<div class="voices-inspector-pane" id="voices-inspector">
<div class="inspector-placeholder">
<span><span class="mdi mdi-account-voice"></span></span>
<p>Pick a voice on the left<br>to edit it here</p>
</div>
</div>
</div><!-- /voices-workbench -->
<!-- Hidden scaffold — IDs required by app.js but not shown in new layout -->
<div id="vl-scaffold" style="display:none" aria-hidden="true">
<select id="library-filter-sex"><option value="">All</option><option value="F">F</option><option value="M">M</option><option value="N">N</option></select>
<select id="library-filter-rating"><option value="">Any</option><option value="5">5 stars</option><option value="4">4+ stars</option><option value="3">3+ stars</option><option value="1">Rated</option><option value="0">Unrated</option></select>
<button id="library-clear-filters" type="button">Clear filters</button>
<div id="library-filter-panel"></div>
<div class="library-insights" id="library-insights"></div>
<div class="disabled-info" id="disabled-info">
<strong>About disabled voices:</strong> The Active toggle moves the voice package between
<code>active_voices</code> and <code>hidden_voices</code>. Qwen3-TTS should scan only <code>active_voices</code>.
</div>
<!-- Audio playback bar -->
<div id="lib-audio-bar">
<div class="lib-audio-label" id="lib-audio-label">-</div>
<audio id="lib-audio" controls></audio>
</div>
<!-- Benchmark config panel -->
<div class="library-benchmark-panel">
<textarea id="benchmark-sample-text" placeholder="Sample sentence for benchmark"></textarea>
<select id="library-tts-backend-select"><option value="">Checking backends...</option></select>
<input type="number" id="library-target-db" value="-20" min="-60" max="-1" step="0.5">
<button id="normalize-volume-btn">Normalize volume</button>
<button id="benchmark-reset-sample-btn">Reset sample</button>
<button id="benchmark-use-preview-btn">Use preview text</button>
</div>
<!-- Benchmark progress -->
<div class="benchmark-progress" id="benchmark-progress" hidden aria-live="polite">
<div class="benchmark-progress-head">
<span id="benchmark-progress-label">Benchmarking voices...</span>
<span id="benchmark-progress-count">0 / 0</span>
</div>
<div class="benchmark-progress-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div id="benchmark-progress-bar"></div>
</div>
<div class="benchmark-live-stats" id="benchmark-live-stats">
<span>Elapsed -</span><span>Avg -</span><span>ETA -</span><span>OK 0</span><span>Slow 0</span><span>Errors 0</span>
</div>
<div class="benchmark-live-last" id="benchmark-live-last"></div>
</div>
<!-- Add new voice panel -->
<div class="lib-add-panel" id="lib-add-panel">
<div class="lib-add-stack">
<div class="lib-add-section">
<h2 style="margin-bottom:10px">1 Load or record source</h2>
<div class="lib-add-input-grid">
<div class="lib-add-import-box">
<input type="file" id="lib-add-file" accept="audio/*,video/*" style="display:none">
<div class="lib-add-drop" id="lib-add-drop">
<strong>Drop an audio / video file here</strong>
<span>WAV &middot; MP3 &middot; OGG &middot; FLAC &middot; M4A &middot; MP4 &middot; MKV &middot; WEBM</span>
<span>or click to browse</span>
</div>
<div>
<label class="note" for="lib-add-url">Audio or video URL</label>
<div class="url-row">
<input type="text" id="lib-add-url" placeholder="Paste a direct MP3/WAV, YouTube, or Aiartes URL">
<button class="btn-primary" id="lib-add-url-btn">Download URL</button>
</div>
</div>
</div>
<div class="lib-add-source-box lib-add-record-box">
<div class="record-head">
<span class="note">Record a fresh sample</span>
<div class="record-head-actions">
<button class="btn-secondary" id="lib-add-mic-help-btn">How to unlock mic</button>
</div>
</div>
<div class="lib-add-recorder-tools">
<div class="lib-add-mic-row">
<div class="mic-buttons">
<button class="btn-secondary" id="lib-add-monitor-btn">Check level</button>
<button class="btn-secondary" id="lib-add-monitor-stop" disabled>Stop monitor</button>
<button class="btn-red" id="lib-add-rec-start"><span class="mdi mdi-record-circle-outline"></span> Record</button>
<button class="btn-secondary" id="lib-add-rec-stop" disabled><span class="mdi mdi-stop-circle-outline"></span> Stop</button>
<span class="mic-timer" id="lib-add-rec-time">0:00</span>
</div>
<div class="mic-monitor-box">
<div class="mic-monitor-head">
<span>Input level</span>
<span class="meter-readout" id="lib-add-db-readout">-<span class="mdi mdi-infinity"></span> dB</span>
</div>
<div class="mic-meter" id="lib-add-mic-meter" aria-hidden="true"></div>
<div class="mic-gain-row">
<label for="lib-add-mic-gain">Mic gain</label>
<input id="lib-add-mic-gain" type="range" min="0" max="2" step="0.05" value="1">
<span class="mic-gain-value" id="lib-add-mic-gain-value">1.00x</span>
</div>
<div class="note" style="margin-top:5px">Best peaks: -18 to -9 dB, never red.</div>
</div>
</div>
<div class="sample-read-box">
<div class="sample-head">
<label for="lib-add-sample-lang">Read sample</label>
<select id="lib-add-sample-lang">
<option value="EN">English</option><option value="DE">Deutsch</option>
<option value="IT">Italiano</option><option value="ES">Espa&ntilde;ol</option>
<option value="FR">Fran&ccedil;ais</option><option value="PT">Portugu&ecirc;s</option>
<option value="NL">Nederlands</option><option value="PL">Polski</option>
</select>
</div>
<textarea class="sample-sentence" id="lib-add-sample-text" spellcheck="true"></textarea>
<div class="sample-actions">
<button class="btn-secondary" id="lib-add-use-sample">Use as transcript</button>
<button class="btn-secondary" id="lib-add-reset-sample">Reset sentence</button>
</div>
</div>
</div>
<div class="mic-help-panel" id="lib-add-mic-help">
<strong>If the microphone is blocked:</strong>
<ul>
<li><strong>Chrome/Brave/Edge:</strong> click the lock icon in the address bar &rarr; Microphone &rarr; Allow, then reload.</li>
<li><strong>Firefox:</strong> click the lock icon &rarr; remove Blocked &rarr; Allow, then reload.</li>
<li><strong>Safari:</strong> Settings &rarr; Websites &rarr; Microphone &rarr; allow this site.</li>
</ul>
</div>
<div class="opt-status" id="lib-add-status">Load a file, paste a URL, or record. Then trim, name, and save.</div>
</div>
</div>
<div class="lib-add-audio-row">
<audio id="lib-add-audio" controls style="display:none"></audio>
<div class="btn-row">
<button class="btn-secondary" id="lib-add-auto-trim">Auto trim</button>
<button class="btn-primary" id="lib-add-save-crop">Crop selection</button>
<button class="btn-secondary" id="lib-add-play">Play crop</button>
</div>
</div>
<canvas class="opt-wave" id="lib-add-wave" style="display:none;margin-top:10px"></canvas>
<div class="opt-controls">
<div class="opt-field"><label>Start</label><input id="lib-add-start" type="number" step="0.01" value="0"></div>
<div class="opt-field"><label>End</label><input id="lib-add-end" type="number" step="0.01" value="0"></div>
<button class="btn-primary" id="lib-add-save-crop-bottom">Crop selection</button>
<span class="crop-duration-hint" id="lib-add-crop-hint">Select 3-20 seconds for best cloning.</span>
</div>
</div>
<div class="lib-add-section">
<h2 style="margin-bottom:10px">2 Name and save voice</h2>
<div class="lib-add-name-grid">
<div class="opt-field"><label>Language</label><select id="lib-add-lang">
<option value="EN">EN</option><option value="DE">DE</option><option value="IT">IT</option>
<option value="ES">ES</option><option value="FR">FR</option><option value="PT">PT</option>
<option value="NL">NL</option><option value="PL">PL</option>
</select></div>
<div class="opt-field"><label>Gender</label><input id="lib-add-gender" value="N"></div>
<div class="opt-field"><label>Voice ID</label><input id="lib-add-voice-id" placeholder="EN_N_NewVoice"></div>
</div>
<textarea id="lib-add-transcript" placeholder="Reference transcript" style="margin-top:10px"></textarea>
<div class="btn-row" style="margin-top:10px">
<button class="btn-secondary" id="lib-add-recognize">Recognise text</button>
<button class="btn-green" id="lib-add-save">Save voice</button>
</div>
</div>
</div>
</div>
</div><!-- /vl-scaffold -->