- New light UI: fixed 220px sidebar, single scrolling page, 8 named sections - Static files split by concern: style.css, app.js, loader.js, nav.js - Each page section is its own partial in static/sections/s-*.html - loader.js fetches all section partials in parallel, then loads app.js and nav.js - All original functionality, element IDs, and API endpoints preserved Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
251 lines
16 KiB
HTML
251 lines
16 KiB
HTML
<div class="section-head">
|
|
<span class="section-icon">🔊</span>
|
|
<div class="section-title">
|
|
<h2>My Voice Library</h2>
|
|
<p>All your cloned and designed voices in one place. Play, manage, benchmark, and export them.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content" id="tab-library">
|
|
<div class="card" style="padding-bottom:8px">
|
|
<div class="lib-toolbar">
|
|
<h2 style="margin:0">Voice library</h2>
|
|
<span id="voice-count" class="note"></span>
|
|
<div class="spacer"></div>
|
|
</div>
|
|
<div class="library-benchmark-panel">
|
|
<div class="field">
|
|
<label>Sample sentence for generated playback and benchmarks</label>
|
|
<textarea id="benchmark-sample-text" placeholder="Sentence every voice should speak for preview and benchmark"></textarea>
|
|
</div>
|
|
<div class="field play-mode-field">
|
|
<label>Select TTS Engine</label>
|
|
<select id="library-tts-backend-select"><option value="">Checking backends...</option></select>
|
|
</div>
|
|
<div class="benchmark-actions volume-actions">
|
|
<label class="target-db-field" title="Target volume for volume normalization">
|
|
<span>Target dBFS</span>
|
|
<input type="number" id="library-target-db" value="-20" min="-60" max="-1" step="0.5" aria-label="Target dBFS">
|
|
</label>
|
|
<button class="btn-secondary" id="normalize-volume-btn" title="Normalize visible WAV voices to the target volume">Normalize volume</button>
|
|
<button class="btn-secondary" id="benchmark-reset-sample-btn">Reset sample</button>
|
|
<button class="btn-secondary" id="benchmark-use-preview-btn">Use preview text</button>
|
|
</div>
|
|
</div>
|
|
<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>
|
|
|
|
<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 · MP3 · OGG · FLAC · M4A · MP4 · MKV · 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 cloned-voice 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">● Record</button>
|
|
<button class="btn-secondary" id="lib-add-rec-stop" disabled>■ 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">-∞ 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ñol</option>
|
|
<option value="FR">Français</option>
|
|
<option value="PT">Portuguê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>
|
|
<span class="note">Use this as the spoken script if you record.</span>
|
|
</div>
|
|
<div class="recording-tips">
|
|
<span class="recording-tip">Quiet room</span>
|
|
<span class="recording-tip">20 cm from mic</span>
|
|
<span class="recording-tip">No clipping</span>
|
|
<span class="recording-tip">Natural pace</span>
|
|
</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/tune icon in the address bar, set Microphone to Allow, then reload.</li>
|
|
<li><strong>Firefox:</strong> click the microphone or lock icon in the address bar, remove Blocked or choose Allow, then reload.</li>
|
|
<li><strong>Safari:</strong> open Safari Settings, Websites, Microphone, then allow this site.</li>
|
|
<li><strong>Requested device not found:</strong> choose or enable a microphone in your OS input settings, then reload.</li>
|
|
<li>Browsers require localhost or HTTPS for microphone access.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="opt-status" id="lib-add-status">Load a file, paste a URL, or record a sample. Then trim, name, and save the voice.</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: paste or recognise the exact words spoken in the source" 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 class="library-control-panel" id="library-filter-panel">
|
|
<div class="field">
|
|
<label>Filter voices</label>
|
|
<input type="search" id="library-filter-text" placeholder="Search name, reference, note..." autocomplete="off">
|
|
</div>
|
|
<div class="field">
|
|
<label>Language</label>
|
|
<select id="library-filter-lang"><option value="">All languages</option></select>
|
|
</div>
|
|
<div class="field">
|
|
<label>Sex</label>
|
|
<select id="library-filter-sex"><option value="">All</option></select>
|
|
</div>
|
|
<div class="field">
|
|
<label>Filetype</label>
|
|
<select id="library-filter-type"><option value="">All</option></select>
|
|
</div>
|
|
<div class="field">
|
|
<label>Rating</label>
|
|
<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>
|
|
</div>
|
|
<button class="btn-secondary" id="library-clear-filters" type="button">Clear filters</button>
|
|
</div>
|
|
<div class="library-list-title"><strong>Voices</strong></div>
|
|
<div class="library-action-row" aria-label="Voice library actions">
|
|
<div class="voice-action-group action-add">
|
|
<button class="btn-primary" id="add-new-voice-btn">Add new voice</button>
|
|
</div>
|
|
<div class="voice-action-group action-refresh">
|
|
<button class="btn-secondary" id="refresh-voices-btn">↻ Refresh</button>
|
|
<button class="btn-secondary" id="sync-voice-folders-btn">Sync folders</button>
|
|
</div>
|
|
<div class="voice-action-group action-db">
|
|
<button class="btn-secondary" id="calculate-db-btn" title="Calculate dBFS for each voice">Calc dB</button>
|
|
</div>
|
|
<div class="voice-action-group action-benchmark">
|
|
<button class="btn-secondary" id="benchmark-voices-btn" title="Measure TTS synthesis speed for active voices">Benchmark</button>
|
|
</div>
|
|
<div class="voice-action-group action-copy">
|
|
<button class="btn-secondary" id="copy-active-voices-btn" title="Copy active voices as comma-separated names">Copy active voices</button>
|
|
</div>
|
|
<label class="show-disabled-control action-show-disabled">
|
|
<input type="checkbox" id="show-disabled-cb"> Show disabled
|
|
</label>
|
|
</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 class="disabled-info" id="disabled-info" style="display:none">
|
|
<strong>About disabled voices:</strong> The Active toggle moves the complete voice package between
|
|
<code>active_voices</code> and <code>hidden_voices</code>. Qwen3-TTS should scan only <code>active_voices</code>.
|
|
To permanently remove a voice, delete its <code>.wav</code> and <code>.reference.txt</code> files directly.
|
|
</div>
|
|
<div class="vl-grid vl-header">
|
|
<div data-sort="has_picture" title="Sort by photo">Image</div>
|
|
<div data-sort="id" title="Sort by language, sex, and name" class="sort-asc">Language / Sex / Name</div>
|
|
<div data-sort="file_type" title="Sort by file type">Filetype</div>
|
|
<div data-sort="duration" title="Sort by audio length">Length</div>
|
|
<div data-sort="dbfs" title="Sort by loudness">dB</div>
|
|
<div data-sort="benchmark" title="Sort by synthesis benchmark">Benchmark</div>
|
|
<div data-sort="rating" title="Sort by rating">Rating</div>
|
|
<div class="no-sort" title="Original and synthesized playback">Play / Pause</div>
|
|
<div class="no-sort" title="Edit audio">Edit</div>
|
|
</div>
|
|
<div id="voice-list" style="display:flex;flex-direction:column;gap:4px;flex:1;min-height:260px;overflow-y:auto;padding-right:2px"></div>
|
|
<div class="library-insights" id="library-insights" aria-live="polite"></div>
|
|
<div id="lib-audio-bar" style="display:none">
|
|
<div class="lib-audio-label" id="lib-audio-label">-</div>
|
|
<audio id="lib-audio" controls></audio>
|
|
</div>
|
|
</div>
|
|
</div><!-- /tab-library -->
|