Add XTTS-v2 as first TTS backend, extensible for more
Mirrors the ASR backend pattern exactly: a BACKENDS dict in
tts/router.py maps a backend name to its worker URL, so adding the
next backend is just a new worker file + venv + supervisord entry +
one dict line. XTTS-v2 runs in its own venv (--system-site-packages,
inherits base image torch/CUDA) as a new supervisord program on
port 8005.
XTTS is zero-shot voice cloning, so a reference-voice library was
added (/tts/voices CRUD, stored under /srv/tts/voices) — synthesis
requires picking a previously uploaded voice. Results and model
cache live under /srv/tts/{results,models-cache}, new quadlet
volumes, owned by the same 983:983 user as the existing /srv/asr
dirs.
Fixed two environment issues uncovered while getting XTTS to
actually run inside the container (non-root user, root-built venvs):
- coqui-tts only pins transformers>=4.57 with no ceiling, so pip
installed an incompatible 5.x; pinned to the last 4.x release.
- HOME defaults to /app (owned by root) for the container's runtime
user, so numba/matplotlib/torch cache writes failed; HOME is now
forced to /tmp in all three workers (faster_whisper, qwen3, xtts)
before any of those libraries get imported.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -320,17 +320,97 @@
|
||||
|
||||
<div id="tab-tts-synth" class="tab-panel">
|
||||
<section class="card">
|
||||
<h2>TTS 기능 준비 중</h2>
|
||||
<p class="muted">음성 합성 백엔드는 아직 연동되지 않았습니다. API 구조(<code>/tts/*</code>)는
|
||||
준비되어 있으며, 엔진이 연결되면 이 화면에서 텍스트를 음성으로 변환할 수 있습니다.</p>
|
||||
<h2>보이스</h2>
|
||||
<p class="muted small">XTTS는 제로샷 보이스 클로닝 모델이라, 합성하기 전에 참조 음성을
|
||||
먼저 등록해야 합니다(5~15초 분량의 깨끗한 음성 권장).</p>
|
||||
<div id="tts-voice-list" style="margin:10px 0">
|
||||
<div class="muted small">불러오는 중...</div>
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<label>보이스 이름
|
||||
<input type="text" id="tts-voice-name" placeholder="예: my-voice">
|
||||
</label>
|
||||
<label>참조 음성 파일
|
||||
<input type="file" id="tts-voice-file" accept="audio/*">
|
||||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="button" id="tts-voice-upload-btn">보이스 업로드</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<form id="tts-form">
|
||||
<label>텍스트
|
||||
<textarea id="tts-text" rows="5" placeholder="음성으로 변환할 텍스트를 입력하세요." required></textarea>
|
||||
</label>
|
||||
<div class="grid3">
|
||||
<label>백엔드
|
||||
<select id="tts-backend" name="backend"></select>
|
||||
</label>
|
||||
<label>언어
|
||||
<select id="tts-language" name="language"></select>
|
||||
</label>
|
||||
<label>보이스
|
||||
<select id="tts-voice-select" name="voice"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" id="tts-submit-btn">합성 실행</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>상태</h2>
|
||||
<pre id="tts-status">대기 중</pre>
|
||||
</section>
|
||||
|
||||
<section class="card" id="tts-result-section" style="display:none">
|
||||
<h2>결과</h2>
|
||||
<audio controls class="media-player" id="tts-result-audio"></audio>
|
||||
<div class="actions" style="margin-top:10px">
|
||||
<button type="button" id="tts-dl-btn">오디오 다운로드</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="tab-tts-history" class="tab-panel" style="display:none">
|
||||
<section class="card">
|
||||
<h2>처리 내역</h2>
|
||||
<div class="muted" style="padding:16px">아직 처리된 내역이 없습니다.</div>
|
||||
<div class="history-toolbar">
|
||||
<h2 style="margin:0">처리 내역</h2>
|
||||
<button type="button" id="tts-history-refresh-btn" class="btn-secondary">새로고침</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="tts-history-list">
|
||||
<div class="muted" style="padding:16px">내역을 불러오는 중...</div>
|
||||
</div>
|
||||
|
||||
<div id="tts-history-detail" style="display:none">
|
||||
<section class="card">
|
||||
<div class="detail-header">
|
||||
<button type="button" id="tts-history-back-btn" class="btn-secondary">← 목록으로</button>
|
||||
<span id="tts-detail-title" class="detail-title"></span>
|
||||
<button type="button" id="tts-detail-delete-btn" class="btn-danger">삭제</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>오디오</h2>
|
||||
<audio controls class="media-player" id="tts-detail-audio"></audio>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>정보</h2>
|
||||
<div id="tts-detail-meta" class="meta-grid"></div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>텍스트</h2>
|
||||
<textarea id="tts-detail-text" rows="8" readonly></textarea>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user