1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

wen3-TTSとは何か〜3秒の音声でボイスクローンできる最新TTSをGoogle Colabで使い切る〜

1
Posted at

この記事の対象読者

  • 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ノートブック完結を設計目標とする。

処理フロー

  1. 参照音声をアップロード
  2. Whisper(任意)で文字起こし
  3. VoiceClone 用プロンプトを作成
  4. テキストを入力して音声生成
  5. WAV を保存
  6. Colab上で再生
  7. 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)
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?