1. Sample text: expose initCloneSampleText as a window function and call it from nav.js runSideEffects when the clone section is activated, ensuring the textarea is always populated even if the IIFE ran before the element existed. 2. Better sample texts: all 8 languages rewritten to ~38 words / ~15 s, first-person, phonetically rich, proper Unicode diacritics. 3. Live mic monitor: a level-meter (18-bar) + scrolling oscilloscope canvas (ring-buffer, 300 px, colour-coded) added to the microphone card. "Check level" / "Stop monitor" buttons start/stop it independently; clicking Record starts it automatically. Uses raw mic constraints (no echo-cancel / AGC) for cleaner voice clone audio. Mic gain slider and dB readout included. 4. Recording quality: MediaRecorder now requests audioBitsPerSecond:256000 in both voice-clone.js and stt.js. 5. STT engine picker: Recognition engine <select> + Refresh button added above the Auto-transcribe button in Step 3. refreshSttBackends() now syncs both stt-tts-stt-backend and clone-stt-backend. The transcribe call passes the chosen backend to /api/transcribe. 6. File input: explicit extension list added to accept= for OGG/OPUS. 7. CSS: .mic-live-wave style added (dark/light theme variants). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
155 lines
8.7 KiB
HTML
155 lines
8.7 KiB
HTML
<div class="section-head">
|
||
<span class="section-icon"><span class="mdi mdi-microphone-variant"></span></span>
|
||
<div class="section-title">
|
||
<h2>Clone a Voice</h2>
|
||
<p>Upload or record 3-20 seconds of audio, trim it, then save it as a reusable voice clone.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 1: Load audio -->
|
||
<div class="tab-content" id="tab-source">
|
||
<div class="card">
|
||
<h2>Step 1 — Load audio</h2>
|
||
<p class="card-subtitle">Drop a file, paste a URL, or record directly from your microphone.</p>
|
||
<div id="drop-zone">
|
||
<strong>Drop an audio / video file here</strong>
|
||
<span>WAV · MP3 · OGG · FLAC · M4A · MP4 · MKV · WEBM</span>
|
||
<span style="margin-top:10px;font-size:12px;color:var(--subtext)">or click to browse</span>
|
||
<input type="file" id="file-input" accept=".wav,.mp3,.ogg,.opus,.flac,.m4a,.mp4,.mkv,.webm,.mov,.avi,audio/*,video/*">
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>YouTube / URL</h2>
|
||
<p class="card-subtitle">Download audio from a direct link or YouTube video.</p>
|
||
<div class="url-row">
|
||
<input type="text" id="yt-url" placeholder="https://www.youtube.com/watch?v=...">
|
||
<button class="btn-primary" id="yt-btn">Download</button>
|
||
</div>
|
||
<div id="yt-progress"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Microphone</h2>
|
||
<p class="card-subtitle">Record a fresh sample with live input level monitoring.</p>
|
||
<div class="sample-read-box" style="margin-bottom:12px">
|
||
<div class="sample-head">
|
||
<label for="clone-sample-lang">Read aloud</label>
|
||
<select id="clone-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="clone-sample-text" spellcheck="true"></textarea>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-secondary" id="clone-monitor-btn"><span class="mdi mdi-waveform"></span> Check level</button>
|
||
<button class="btn-secondary" id="clone-monitor-stop" disabled>Stop monitor</button>
|
||
<button class="btn-red" id="rec-start-btn"><span class="mdi mdi-record-circle-outline"></span> Record</button>
|
||
<button class="btn-secondary" id="rec-stop-btn" disabled><span class="mdi mdi-stop-circle-outline"></span> Stop</button>
|
||
<div id="rec-indicator"><div id="rec-dot"></div><span id="rec-time">0:00</span></div>
|
||
</div>
|
||
<div class="mic-monitor-box" id="clone-monitor-box" style="margin-top:10px">
|
||
<div class="mic-monitor-head">
|
||
<span>Input level</span>
|
||
<span class="meter-readout" id="clone-db-readout">-∞ dB</span>
|
||
</div>
|
||
<div class="mic-meter" id="clone-mic-meter" aria-hidden="true"></div>
|
||
<canvas id="clone-live-wave" class="mic-live-wave" width="300" height="48" aria-hidden="true"></canvas>
|
||
<div class="mic-gain-row">
|
||
<label for="clone-mic-gain">Mic gain</label>
|
||
<input id="clone-mic-gain" type="range" min="0" max="2" step="0.05" value="1">
|
||
<span class="mic-gain-value" id="clone-mic-gain-value">1.00x</span>
|
||
</div>
|
||
<div class="note" style="margin-top:4px">Best peaks: −18 to −9 dB. Never red.</div>
|
||
</div>
|
||
</div>
|
||
<div class="card" id="waveform-card" style="display:none">
|
||
<h2>Step 2 — Trim selection</h2>
|
||
<p class="card-subtitle">Select the best 3–20 s of clean speech from the waveform.</p>
|
||
<div id="waveform-wrap"><div id="waveform"></div></div>
|
||
<div class="time-row">
|
||
<label>Start (s) <input type="number" id="trim-start" value="0" min="0" step="0.1"></label>
|
||
<label>End (s) <input type="number" id="trim-end" value="0" min="0" step="0.1"></label>
|
||
<span id="trim-duration" class="dur-ok">0.0 s</span>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-primary" id="trim-btn">Confirm trim → Name & Save</button>
|
||
<button class="btn-secondary" id="auto-trim-btn">Auto trim</button>
|
||
<button class="btn-secondary" id="play-btn"><span class="mdi mdi-play"></span> Play</button>
|
||
<button class="btn-secondary" id="play-selection-btn"><span class="mdi mdi-play"></span> Play selection</button>
|
||
</div>
|
||
</div>
|
||
</div><!-- /tab-source -->
|
||
|
||
<!-- Step 3: Name and save -->
|
||
<div class="tab-content" id="tab-save">
|
||
<div class="card">
|
||
<h2>Step 3 — Name the voice</h2>
|
||
<p class="card-subtitle">Assign a unique ID, language, and gender — e.g. <code>EN_F_Anna</code>.</p>
|
||
<div class="voice-id-row">
|
||
<input type="text" id="voice-id-input" placeholder="e.g. EN_F_Anna or MyCustomVoice" spellcheck="false">
|
||
</div>
|
||
<div id="voice-id-hint" class="note" style="min-height:1.2em"></div>
|
||
<details class="helper">
|
||
<summary>Build from parts (LANG · GENDER · Name helper)</summary>
|
||
<div class="helper-body">
|
||
<div class="field">
|
||
<label>Language</label>
|
||
<select id="lang-select">
|
||
<option>EN</option><option>DE</option><option>ZH</option><option>FR</option>
|
||
<option>ES</option><option>JA</option><option>KO</option><option>IT</option>
|
||
<option>PT</option><option>RU</option><option>AR</option><option>PL</option>
|
||
<option>NL</option><option>SV</option><option>TR</option><option>HI</option>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>Gender</label>
|
||
<select id="gender-select">
|
||
<option value="F">F — Female</option>
|
||
<option value="M">M — Male</option>
|
||
<option value="N">N — Neutral</option>
|
||
</select>
|
||
</div>
|
||
<div class="field" style="flex:1;min-width:120px">
|
||
<label>Name (no spaces)</label>
|
||
<input type="text" id="name-input" placeholder="Anna">
|
||
</div>
|
||
<button class="btn-secondary" id="helper-apply-btn" style="align-self:flex-end">Apply →</button>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Transcript (reference text)</h2>
|
||
<p class="card-subtitle">The spoken text that matches this audio — type it or auto-transcribe.</p>
|
||
<div class="btn-row" style="gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:8px">
|
||
<label style="font-size:13px;color:var(--subtext);white-space:nowrap">Recognition engine:</label>
|
||
<select id="clone-stt-backend" style="flex:1;min-width:160px"><option value="configured">Configured Whisper/STT</option></select>
|
||
<button class="btn-secondary" id="clone-refresh-stt-btn" type="button">Refresh</button>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-secondary" id="transcribe-btn"><span class="mdi mdi-message-text-outline"></span> Auto-transcribe</button>
|
||
<span id="transcribe-status" style="font-size:13px;color:var(--subtext)"></span>
|
||
</div>
|
||
<textarea id="transcript-area" placeholder="Type or auto-transcribe the spoken text…"></textarea>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Audio preview</h2>
|
||
<p class="card-subtitle">Play back the trimmed audio before saving to the library.</p>
|
||
<audio id="trim-audio" controls style="display:none"></audio>
|
||
<p id="no-audio-hint" class="note">No audio loaded yet. Load a file above (Step 1) or use Voice Design below.</p>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Step 4 — Save to library</h2>
|
||
<p class="card-subtitle">Write the cloned voice to your active voices folder.</p>
|
||
<div class="btn-row">
|
||
<button class="btn-green" id="save-btn"><span class="mdi mdi-memory"></span> Save to Voice Library</button>
|
||
</div>
|
||
<div id="save-result" class="note" style="display:none"></div>
|
||
</div>
|
||
</div><!-- /tab-save -->
|