この記事の対象読者
- Python の基本文法を理解している
- 音声合成(TTS)に興味がある
- GPU 環境を自前で用意せず、まず試してみたい
- Google Colab を使った実験・検証が好き
この記事で得られること
- Qwen3-TTS の VoiceClone(3秒ボイスクローン) の理解
- Google Colab 上での 実行可能な構成
- Whisper を用いた 参照音声の自動文字起こし
-
GUI操作のみで
「音声アップロード → 生成 → 再生 → ZIPダウンロード」
を完結させる方法
この記事で扱わないこと(重要)
- RTX 5090 / Blackwell 世代の最適化
- FlashAttention 2 による高速化
- 常駐Webサーバ(localhost:8000)構成
- モデルの再学習・LoRA・微調整
これらは ローカルGPU向けの話題であり、Colabでは扱わない。
1. Qwen3-TTSとは何か
Qwen3-TTS は、Alibaba Cloud の Qwen チームが公開した
最新世代の音声合成(Text-to-Speech)モデルである。
最大の特徴は次の一点に集約される。
わずか数秒の参照音声から、その人の声質を再現できる
従来のボイスクローンでは、数十分〜数時間の音声データが必要だった。
Qwen3-TTS Base は、この常識を覆した。
2. Google Colabで何ができるのか
結論を先に述べる。
Qwen3-TTS の「Base(VoiceClone)」は Google Colab で問題なく使える。
Colabで可能な範囲は以下。
- Baseモデル(0.6B / 1.7B)
- 日本語音声生成
- 参照音声 + 参照テキストによる VoiceClone
- Whisper による自動文字起こし
- WAV 保存 / 再生 / ZIP ダウンロード
- GUI(ipywidgets)操作
一方、次は Colabでは不可。
- RTX 5090 固有の高速設定
- FlashAttention 2 前提の運用
- 常時起動の Web GUI
3. Colab版の全体構成
Colab版では、1ノートブック完結を設計目標とする。
処理フロー
- 参照音声をアップロード
- Whisper(任意)で文字起こし
- VoiceClone 用プロンプトを作成
- テキストを入力して音声生成
- WAV を保存
- Colab上で再生
- ZIP化してダウンロード
学習やモデル更新は一切行わない。
条件付け生成のみを扱う。
4. 使用モデル(Colab対応)
| モデル | 用途 | Colab対応 |
|---|---|---|
| Qwen3-TTS-12Hz-0.6B-Base | 軽量・検証向け | ○ |
| Qwen3-TTS-12Hz-1.7B-Base | 高品質 | ○ |
まずは 0.6B を推奨する。
5. 参照音声の条件
VoiceClone の品質は、参照音声に強く依存する。
推奨条件:
- 長さ:3〜30秒
- モノラル(ステレオでも自動でモノ化)
- クリップなし
- はっきりした発話
完璧な録音は不要。
スマートフォン録音でも十分に機能する。
6. Whisperによる自動文字起こし
Colab版では、参照音声の文字起こしを Whisperで自動化できる。
推奨モデル:
- tiny / base / small
large 系は VRAM 使用量が増え、Colabでは不安定になりやすい。
ASRは「下書き」として使い、
必要なら UI 上で修正する運用が現実的。
7. Colab版GUIの特徴
UIでできること
- モデル選択
- 言語選択(Japanese / English / 他)
- 参照音声アップロード
- ASR実行
- 生成テキスト入力(複数行対応)
- 音声生成
- 即時再生
- ZIPダウンロード
操作はすべてGUI
Pythonコードを直接編集する必要はない。
実験・検証に特化したUIになっている。
8. 出力形式
生成結果は次の形で保存される。
-
WAVファイル
YYYYMMDD_HHMMSS_VoiceClone_Japanese_000.wav -
メタ情報JSON
- 使用モデル
- GPU有無
- 生成時間
- 出力ファイル一覧
-
ZIPファイル
- out/ 配下を一括圧縮
9. よくあるエラー(Colab)
| 症状 | 原因 | 対処 |
|---|---|---|
| CUDA OOM | 1.7Bが重い | 0.6Bに切替 |
| ASR失敗 | Whisper未導入 | ASR実行時に自動導入 |
| 音声が出ない | 参照音声不適 | 録り直し |
10. Colab版の限界(正直な話)
Colabは「試す」環境としては非常に優秀だが、限界もある。
- GPUは選べない
- 実行時間制限がある
- 常時サービス運用は不可
本格運用・高速化・大量生成を行う場合は
ローカルGPU環境へ移行するのが正解。
機能要件定義
Qwen3-TTS VoiceClone Google Colab UI
対象:
qwen3_tts_voiceclone_colab_ui.py
目的:
Google Colab 上で、参照音声(ユーザー音声)と参照文字起こしを条件として
Qwen3-TTS VoiceClone を用いた音声生成を行い、
生成結果を再生・保存・ZIP化・ダウンロードまで
GUI 操作のみで完結させる。
本仕様は「最初から最後まで」を対象とし、
学習(重み更新)は行わない。
────────────────────────────────────────────────────────────
0. システム概要
────────────────────────────────────────────────────────────
本ノートブックは、Google Colab 環境上で動作する単一セル実行型 UI である。
ユーザーは以下を行える。
・参照音声データの入力(複数方式対応)
・参照音声に対応する文字起こしの入力またはASR生成
・Qwen3-TTS Base(VoiceClone)による条件付き音声生成
・生成音声の即時再生および波形確認
・生成音声(複数件)のWAV保存
・生成結果のZIP化およびダウンロード
本システムは推論専用であり、
モデルの再学習・LoRA・重み保存は扱わない。
────────────────────────────────────────────────────────────
1. 目的
────────────────────────────────────────────────────────────
FR-1 GUI操作のみでVoiceClone音声生成を完結させる
FR-2 参照音声入力方法を柔軟に指定できる
FR-3 生成音声を即時に再生・確認できる
FR-4 生成結果をファイルとして取得できる
FR-5 エラー時に原因を追跡できるログを残す
────────────────────────────────────────────────────────────
2. 想定ユーザー
────────────────────────────────────────────────────────────
・生成AI/音声合成の実験利用者
・Google Colab利用経験者
・Pythonコードは読めるが、CLI操作は最小限にしたい利用者
────────────────────────────────────────────────────────────
3. 想定利用フロー(標準)
────────────────────────────────────────────────────────────
Flow-STD
1) ノートブックを単一セル実行
2) GPU状態確認(自動表示)
3) 参照音声入力方法を選択
4) 参照音声を指定・確定
5) モデルをロード
6) 参照文字起こしを入力(またはASR)
7) VoiceClone Promptを生成
8) 生成テキストを入力(複数行可)
9) 音声生成
10) プレビュー再生
11) ZIP化・ダウンロード
────────────────────────────────────────────────────────────
4. UI要件
────────────────────────────────────────────────────────────
UI-1 GPU状態表示
・GPU有無
・device文字列
UI-2 モデル選択
・Qwen/Qwen3-TTS-12Hz-1.7B-Base
・Qwen/Qwen3-TTS-12Hz-0.6B-Base
UI-3 言語選択
・Japanese / English / Chinese / Korean / German / French
・Russian / Portuguese / Spanish / Italian / Auto
UI-4 参照音声入力方式選択
・Upload
・Local path
・Google Drive path
・URL
UI-5 参照音声指定UI
・FileUpload(Upload時)
・Path入力欄(Path/Drive/URL時)
UI-6 参照音声候補選択
・tmp_dir配下の音声ファイル一覧Dropdown
・Refreshボタン
UI-7 参照音声確定ボタン
・押下で参照音声を確定
UI-8 参照音声情報表示
・path / duration / sample rate / RMS
・警告表示(長時間等)
UI-9 参照文字起こし入力欄
・Textarea
・複数行可
UI-10 x-vector-only チェック
・ON時は文字起こし不要
UI-11 ASR操作
・Use Whisper ASR(Checkbox)
・Run ASR(Button)
・ASR状態表示
UI-12 生成テキスト入力欄
・Textarea
・複数行可(1行=1生成)
UI-13 操作ボタン
・Load Model
・Build Prompt
・Generate
・Zip + Download
・Clear Output
UI-14 ステータスログ
・成功/警告/失敗
・traceback表示
UI-15 出力表示領域
・参照音声再生
・生成音声再生
・波形表示
・出力ファイル一覧
────────────────────────────────────────────────────────────
5. 機能要件
────────────────────────────────────────────────────────────
5.1 セットアップ
FR-Setup-1 必要ライブラリをpipで導入
FR-Setup-2 GPU有無に応じてdtypeを切替
FR-Setup-3 出力ディレクトリ作成
FR-Setup-4 乱数seed固定
5.2 参照音声入力
FR-Ref-1 入力方式に応じてref_audio_pathを決定
FR-Ref-2 音声存在チェック
FR-Ref-3 音声読み込み・モノラル化
FR-Ref-4 duration / sr / rms算出
FR-Ref-5 長時間音声警告
5.3 ASR
FR-ASR-1 ASRは参照音声確定後のみ実行可能
FR-ASR-2 Whisperは必要時のみ導入
FR-ASR-3 認識結果をRef textに反映
5.4 モデルロード
FR-Load-1 Load Modelでモデルをロード
FR-Load-2 GPU優先でdevice選択
FR-Load-3 既存prompt状態をリセット
5.5 Prompt作成
FR-Prompt-1 モデル+参照音声必須
FR-Prompt-2 x-vector-only OFF時は文字起こし必須
FR-Prompt-3 Promptをセッションにキャッシュ
FR-Prompt-4 参照音声プレビュー表示
5.6 音声生成
FR-Gen-1 複数行入力対応
FR-Gen-2 行単位で生成
FR-Gen-3 生成時間計測
FR-Gen-4 WAV保存
FR-Gen-5 メタJSON保存
FR-Gen-6 先頭音声を自動再生
5.7 ZIP化
FR-ZIP-1 out_dir全体をZIP化
FR-ZIP-2 ダウンロード開始
5.8 出力クリア
FR-CLR-1 Output領域クリア
FR-CLR-2 ログ初期化
────────────────────────────────────────────────────────────
6. 非機能要件
────────────────────────────────────────────────────────────
NFR-1 単一セルでUI表示
NFR-2 GPUなしでも動作
NFR-3 依存は最小限
NFR-4 失敗時に原因が追跡可能
NFR-5 ファイル命名規則が再現可能
────────────────────────────────────────────────────────────
7. エラー要件
────────────────────────────────────────────────────────────
・モデル未ロード
・参照音声未確定
・文字起こし未入力
・生成テキスト空
・音声読込失敗
・ZIP対象なし
すべて status に traceback を表示する。
────────────────────────────────────────────────────────────
8. 受け入れ条件
────────────────────────────────────────────────────────────
・UIが表示され操作可能
・参照音声指定が可能
・音声生成が成功
・音声が再生できる
・ZIPをDLできる
────────────────────────────────────────────────────────────
9. 仕様外
────────────────────────────────────────────────────────────
・モデル学習/重み保存
・品質評価
・複数参照音声の合成
・認証付きURL
以上
# Program Name: qwen3_tts_voiceclone_colab_ui.py
# Creation Date: 20260203
# Purpose: Google Colab UI for Qwen3-TTS VoiceClone (reference audio + transcript -> synth -> zip download)
# =========================
# PARAM_INIT (single source of truth)
# =========================
PARAM_INIT = {
"seed": 1234,
"out_dir": "/content/out",
"zip_prefix": "generated_audio",
"default_model_id": "Qwen/Qwen3-TTS-12Hz-1.7B-Base", # or 0.6B-Base
"default_language": "Japanese", # "English", "Chinese", ... or "Auto"
"dtype_cuda": "bfloat16", # "float16" or "bfloat16"
"dtype_cpu": "float32",
"attn_impl": None, # None, "flash_attention_2", or "sdpa" (flash_attention_2 needs extra install)
"max_ref_seconds_warn": 60,
"max_text_chars_warn": 2000,
"asr_enabled_default": False, # optional Whisper ASR
"whisper_model": "small", # tiny/base/small/medium/large-v3 (GPU recommended)
}
# =========================
# Imports / installs (Colab-safe)
# =========================
import os
import sys
import time
import json
import math
import shutil
import zipfile
import random
import traceback
from datetime import datetime
# Mandatory libs per user requirement (even if lightly used)
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, Audio, clear_output
from numba import njit
def _pip_install(pkgs):
"""Inputs: list[str]
Outputs: None
Process: run pip install from current interpreter
"""
import subprocess
try:
cmd = [sys.executable, "-m", "pip", "install", "-U"] + pkgs
print("Running:", " ".join(cmd))
subprocess.check_call(cmd)
except Exception as e:
print("pip install failed:", e)
raise
# Install required packages (idempotent-ish in Colab)
try:
# qwen-tts + soundfile
_pip_install(["qwen-tts", "soundfile"])
# torch is usually present in Colab; skip forced reinstall unless missing
import torch # noqa
except Exception:
_pip_install(["torch"])
import torch # noqa
import soundfile as sf
# Optional ASR (Whisper) support
WHISPER_AVAILABLE = False
try:
import whisper
WHISPER_AVAILABLE = True
except Exception:
# do not auto-install unless user uses ASR toggle
WHISPER_AVAILABLE = False
# Qwen TTS
from qwen_tts import Qwen3TTSModel # noqa
# =========================
# Utilities
# =========================
def _now_tag():
return datetime.now().strftime("%Y%m%d_%H%M%S")
def _ensure_dir(p):
os.makedirs(p, exist_ok=True)
def _set_seed(seed: int):
"""Inputs: seed
Outputs: None
Process: set RNG seeds
"""
random.seed(seed)
np.random.seed(seed)
try:
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
except Exception:
pass
def _device_map():
return "cuda:0" if torch.cuda.is_available() else "cpu"
def _dtype():
if torch.cuda.is_available():
return getattr(torch, PARAM_INIT["dtype_cuda"])
return getattr(torch, PARAM_INIT["dtype_cpu"])
def _read_audio_any(path_or_bytes):
"""Inputs: filepath
Outputs: (wav_np, sr, duration_sec)
Process: read audio with soundfile; fallback to ffmpeg if needed
"""
try:
wav, sr = sf.read(path_or_bytes, always_2d=False)
if wav.ndim == 2:
wav = wav.mean(axis=1) # stereo -> mono
dur = float(len(wav)) / float(sr)
return wav.astype(np.float32), int(sr), dur
except Exception as e:
raise RuntimeError(f"Audio read failed: {e}")
def _plot_wave(wav, sr, title="Waveform"):
"""Inputs: wav, sr
Outputs: None (matplotlib figure)
Process: simple waveform plot
"""
t = np.arange(len(wav)) / float(sr)
plt.figure()
plt.plot(t, wav)
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.title(title)
plt.grid(True)
plt.show()
@njit
def _rms_numba(x):
s = 0.0
for i in range(x.shape[0]):
s += x[i] * x[i]
return math.sqrt(s / max(1, x.shape[0]))
def _zip_out(out_dir, zip_path):
"""Inputs: out_dir, zip_path
Outputs: zip_path
Process: zip directory
"""
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for root, _, files in os.walk(out_dir):
for fn in files:
fp = os.path.join(root, fn)
arc = os.path.relpath(fp, start=os.path.dirname(out_dir))
zf.write(fp, arcname=arc)
return zip_path
def _colab_download(path):
"""Inputs: filepath
Outputs: None
Process: trigger browser download in Colab
"""
try:
from google.colab import files
files.download(path)
except Exception as e:
print("Download helper failed (non-Colab or blocked):", e)
print("File is saved at:", path)
# =========================
# Core: load model + build prompt + generate
# =========================
class VoiceCloneSession:
def __init__(self):
self.model = None
self.model_id = None
self.voice_clone_prompt = None
self.ref_audio_info = None # dict
self.last_sr = None
def load_model(self, model_id: str):
"""Inputs: model_id
Outputs: None
Process: load Qwen3TTSModel
"""
device = _device_map()
dt = _dtype()
kwargs = {}
if PARAM_INIT["attn_impl"] is not None:
kwargs["attn_implementation"] = PARAM_INIT["attn_impl"]
print(f"[Load] model={model_id} device={device} dtype={dt} attn={PARAM_INIT['attn_impl']}")
t0 = time.time()
self.model = Qwen3TTSModel.from_pretrained(
model_id,
device_map=device,
dtype=dt,
**kwargs,
)
self.model_id = model_id
self.voice_clone_prompt = None
self.ref_audio_info = None
print(f"[Load] done in {time.time()-t0:.2f}s")
def build_prompt(self, ref_audio_path: str, ref_text: str, x_vector_only_mode: bool):
"""Inputs: ref_audio_path, ref_text, x_vector_only_mode
Outputs: prompt_items (internal)
Process: create reusable voice clone prompt
"""
if self.model is None:
raise RuntimeError("Model not loaded")
wav, sr, dur = _read_audio_any(ref_audio_path)
rms = float(_rms_numba(wav.astype(np.float64)))
self.ref_audio_info = {"path": ref_audio_path, "sr": sr, "duration_sec": dur, "rms": rms}
if (dur > PARAM_INIT["max_ref_seconds_warn"]):
print(f"[Warn] ref audio is long: {dur:.1f}s")
if (not x_vector_only_mode) and (ref_text.strip() == ""):
raise ValueError("ref_text is required unless x_vector_only_mode=True")
t0 = time.time()
prompt_items = self.model.create_voice_clone_prompt(
ref_audio=ref_audio_path, # path is supported
ref_text=ref_text,
x_vector_only_mode=bool(x_vector_only_mode),
)
self.voice_clone_prompt = prompt_items
print(f"[Prompt] built in {time.time()-t0:.2f}s")
return prompt_items
def generate(self, texts, languages):
"""Inputs: texts (list[str]), languages (list[str])
Outputs: (wavs, sr)
Process: generate voice clone outputs using cached prompt
"""
if self.model is None:
raise RuntimeError("Model not loaded")
if self.voice_clone_prompt is None:
raise RuntimeError("Prompt not built (upload ref audio + transcript first)")
t0 = time.time()
wavs, sr = self.model.generate_voice_clone(
text=texts,
language=languages,
voice_clone_prompt=self.voice_clone_prompt,
)
print(f"[Gen] done in {time.time()-t0:.2f}s for {len(texts)} item(s)")
self.last_sr = sr
return wavs, sr
SESSION = VoiceCloneSession()
# =========================
# UI (ipywidgets)
# =========================
_ensure_dir(PARAM_INIT["out_dir"])
_set_seed(PARAM_INIT["seed"])
gpu_label = widgets.HTML(value=f"<b>GPU:</b> {torch.cuda.is_available()} | <b>Device:</b> {_device_map()}")
model_id_dd = widgets.Dropdown(
options=[
"Qwen/Qwen3-TTS-12Hz-1.7B-Base",
"Qwen/Qwen3-TTS-12Hz-0.6B-Base",
],
value=PARAM_INIT["default_model_id"],
description="Model",
layout=widgets.Layout(width="520px"),
)
lang_dd = widgets.Dropdown(
options=["Japanese", "English", "Chinese", "Korean", "German", "French", "Russian", "Portuguese", "Spanish", "Italian", "Auto"],
value=PARAM_INIT["default_language"],
description="Language",
layout=widgets.Layout(width="320px"),
)
xvec_chk = widgets.Checkbox(value=False, description="x-vector-only (no transcript)", indent=False)
upload = widgets.FileUpload(accept=".wav,.mp3,.m4a,.flac,.ogg", multiple=False, description="Upload ref audio")
ref_text_area = widgets.Textarea(
value="今日はとても晴れた日で、風は少し冷たく感じます。\n朝はパンとコーヒーを用意して、ゆっくりニュースを読みました。",
description="Ref text",
layout=widgets.Layout(width="720px", height="110px"),
)
texts_area = widgets.Textarea(
value="こんにちは。これは音声クローンのテストです。\n次の行も読み上げます。",
description="Text(s)",
layout=widgets.Layout(width="720px", height="130px"),
)
use_asr_chk = widgets.Checkbox(value=PARAM_INIT["asr_enabled_default"], description="Use Whisper ASR (optional)", indent=False)
asr_btn = widgets.Button(description="Run ASR", button_style="")
asr_status = widgets.Label(value="")
load_btn = widgets.Button(description="Load Model", button_style="primary")
prompt_btn = widgets.Button(description="Build Prompt", button_style="info")
gen_btn = widgets.Button(description="Generate", button_style="success")
zip_btn = widgets.Button(description="Zip + Download", button_style="warning")
clear_btn = widgets.Button(description="Clear Output", button_style="")
status = widgets.Textarea(value="", description="Status", layout=widgets.Layout(width="720px", height="120px"))
out_box = widgets.Output()
def _log(msg):
status.value = (status.value + msg + "\n")[-4000:]
def _save_uploaded_file():
if len(upload.value) == 0:
return None
item = next(iter(upload.value.values()))
fname = item["metadata"]["name"]
data = item["content"]
save_path = os.path.join("/content", fname)
with open(save_path, "wb") as f:
f.write(data)
return save_path
def _maybe_install_whisper():
global WHISPER_AVAILABLE
if WHISPER_AVAILABLE:
return
_log("[ASR] installing whisper...")
_pip_install(["openai-whisper"])
import whisper as _w # noqa
WHISPER_AVAILABLE = True
def on_clear(_):
with out_box:
clear_output()
status.value = ""
def on_load(_):
try:
_set_seed(PARAM_INIT["seed"])
SESSION.load_model(model_id_dd.value)
_log(f"[OK] Model loaded: {model_id_dd.value}")
except Exception:
_log("[ERR] Load failed:\n" + traceback.format_exc())
def on_asr(_):
try:
if not use_asr_chk.value:
asr_status.value = "ASR toggle is OFF"
return
ref_path = _save_uploaded_file()
if ref_path is None:
asr_status.value = "Upload ref audio first"
return
_maybe_install_whisper()
asr_status.value = "Running ASR..."
with out_box:
print("[ASR] loading whisper model:", PARAM_INIT["whisper_model"])
model = whisper.load_model(PARAM_INIT["whisper_model"])
result = model.transcribe(ref_path)
ref_text_area.value = result.get("text", "").strip()
asr_status.value = "ASR done (paste fixed text if needed)"
_log("[OK] ASR completed")
except Exception:
asr_status.value = "ASR failed"
_log("[ERR] ASR failed:\n" + traceback.format_exc())
def on_prompt(_):
try:
ref_path = _save_uploaded_file()
if ref_path is None:
raise ValueError("Upload ref audio first")
if SESSION.model is None:
raise RuntimeError("Load model first")
rt = ref_text_area.value.strip()
xvec = bool(xvec_chk.value)
SESSION.build_prompt(ref_path, rt, xvec)
info = SESSION.ref_audio_info or {}
_log(f"[OK] Prompt built | ref_dur={info.get('duration_sec', None)}s sr={info.get('sr', None)} rms={info.get('rms', None)}")
with out_box:
clear_output()
wav, sr, dur = _read_audio_any(ref_path)
print("[Ref] path:", ref_path)
print("[Ref] duration_sec:", f"{dur:.2f}", "sr:", sr, "rms:", float(_rms_numba(wav.astype(np.float64))))
display(Audio(wav, rate=sr))
_plot_wave(wav, sr, title="Reference waveform")
except Exception:
_log("[ERR] Prompt build failed:\n" + traceback.format_exc())
def on_generate(_):
try:
if SESSION.model is None:
raise RuntimeError("Load model first")
if SESSION.voice_clone_prompt is None:
raise RuntimeError("Build prompt first")
raw = texts_area.value.strip()
if raw == "":
raise ValueError("Text(s) is empty")
if len(raw) > PARAM_INIT["max_text_chars_warn"]:
_log(f"[Warn] long text chars: {len(raw)}")
lines = [ln.strip() for ln in raw.splitlines() if ln.strip() != ""]
if len(lines) == 0:
raise ValueError("No valid lines found")
lang = lang_dd.value
texts = lines
langs = [lang] * len(texts)
tag = _now_tag()
t0 = time.time()
wavs, sr = SESSION.generate(texts, langs)
gen_sec = time.time() - t0
saved = []
for i, w in enumerate(wavs):
out_fn = f"{tag}_VoiceClone_{lang}_{i:03d}.wav"
out_fp = os.path.join(PARAM_INIT["out_dir"], out_fn)
sf.write(out_fp, w, sr)
saved.append(out_fp)
meta = {
"timestamp": tag,
"model_id": SESSION.model_id,
"gpu": bool(torch.cuda.is_available()),
"device": _device_map(),
"language": lang,
"num_items": len(texts),
"ref_audio": SESSION.ref_audio_info,
"gen_seconds_total": gen_sec,
"outputs": saved,
}
meta_fp = os.path.join(PARAM_INIT["out_dir"], f"{tag}_meta.json")
with open(meta_fp, "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
_log(f"[OK] Generated {len(saved)} wav | sr={sr} | time={gen_sec:.2f}s")
_log(f"[OK] Saved meta: {meta_fp}")
with out_box:
clear_output()
print("[Gen] outputs:")
for p in saved:
print(" -", p)
print("[Meta]", meta_fp)
# preview the first one
wav0, sr0, _ = _read_audio_any(saved[0])
display(Audio(wav0, rate=sr0))
_plot_wave(wav0, sr0, title="Generated waveform (first item)")
except Exception:
_log("[ERR] Generate failed:\n" + traceback.format_exc())
def on_zip(_):
try:
if not os.path.isdir(PARAM_INIT["out_dir"]):
raise RuntimeError("out_dir missing")
tag = _now_tag()
zip_path = f"/content/{PARAM_INIT['zip_prefix']}_{tag}.zip"
_zip_out(PARAM_INIT["out_dir"], zip_path)
_log(f"[OK] ZIP created: {zip_path}")
_colab_download(zip_path)
except Exception:
_log("[ERR] Zip/Download failed:\n" + traceback.format_exc())
load_btn.on_click(on_load)
prompt_btn.on_click(on_prompt)
gen_btn.on_click(on_generate)
zip_btn.on_click(on_zip)
clear_btn.on_click(on_clear)
asr_btn.on_click(on_asr)
# Layout
row1 = widgets.HBox([gpu_label])
row2 = widgets.HBox([model_id_dd, lang_dd, xvec_chk])
row3 = widgets.HBox([upload])
row4 = widgets.HBox([use_asr_chk, asr_btn, asr_status])
row5 = widgets.HBox([load_btn, prompt_btn, gen_btn, zip_btn, clear_btn])
ui = widgets.VBox([row1, row2, row3, row4, ref_text_area, texts_area, row5, status, out_box])
display(ui)