chatterbox-onestep / python /extract_voice_embedding.py
inoryQwQ's picture
Upload python/extract_voice_embedding.py with huggingface_hub
a36ca15 verified
Raw
History Blame Contribute Delete
3.42 kB
#!/usr/bin/env python3
"""从参考音频提取音色条件(宿主侧,torch + 官方 S3Gen.embed_ref)。
输出三个文件(clone 完整克隆模型 model_clone.axmodel 需要全部三个):
ref_embedding.npy 192 维 xvector(基础模型 / clone 模型均需要)
ref_prompt_token.npy S3 prompt token [1,157](clone 模型需要)
ref_prompt_feat.npy prompt mel [1,314,80](clone 模型需要)
基础模型(model.axmodel)为 embedding 级克隆;clone 模型(model_clone.axmodel,带 prompt
条件)为完整官方克隆路径。
用法:
python3 extract_voice_embedding.py --wav ref.wav --ckpt-dir /path/to/chatterbox_models \
--out-dir .
(ckpt-dir 需含 s3gen.safetensors;参考音频会 pad/截断到固定 6.28s,对应静态
prompt 314 mel 帧 / 157 token。)
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import librosa
import numpy as np
def main():
p = argparse.ArgumentParser(description="提取参考音频的音色条件(embedding + prompt)")
p.add_argument("--wav", required=True, help="参考音频(任意采样率,内部重采样到 24k)")
p.add_argument("--ckpt-dir", required=True, help="Chatterbox 模型目录(含 s3gen.safetensors)")
p.add_argument("--out-dir", default=".", help="输出目录(embedding + prompt token/feat)")
args = p.parse_args()
import torch
from chatterbox.models.s3gen import S3Gen, S3GEN_SR
from safetensors.torch import load_file
s3gen = S3Gen()
s3gen.load_state_dict(load_file(Path(args.ckpt_dir) / "s3gen.safetensors"), strict=False)
s3gen.eval()
wav, _ = librosa.load(args.wav, sr=S3GEN_SR, mono=True)
prompt_samples = int(6.28 * S3GEN_SR)
if len(wav) > prompt_samples:
wav = wav[:prompt_samples]
elif len(wav) < prompt_samples:
wav = np.pad(wav, (0, prompt_samples - len(wav)))
with torch.inference_mode():
ref_dict = s3gen.embed_ref(wav, S3GEN_SR, device="cpu")
emb = ref_dict["embedding"].numpy().astype(np.float32) # (1,192)
p_tok = ref_dict["prompt_token"].numpy().astype(np.int32) # (1,T)
p_feat = ref_dict["prompt_feat"].numpy().astype(np.float32) # (1,T',80)
# 静态对齐:prompt_token [1,157]、prompt_feat [1,314,80]
out = Path(args.out_dir)
out.mkdir(parents=True, exist_ok=True)
pt = np.zeros((1, 157), dtype=np.int32)
pt[:, : min(p_tok.shape[1], 157)] = p_tok[:, :157]
pf = np.zeros((1, 314, 80), dtype=np.float32)
pf[:, : min(p_feat.shape[1], 314)] = p_feat[:, :314]
np.save(out / "ref_embedding.npy", emb)
np.save(out / "ref_prompt_token.npy", pt)
np.save(out / "ref_prompt_feat.npy", pf)
print(f"OK: embedding {emb.shape}, prompt_token {pt.shape}, prompt_feat {pf.shape} -> {out}")
print("板端 OpenAI 调用示例(clone 模型):")
print(
" curl -X POST http://<board>:8000/v1/audio/speech -H 'Content-Type: application/json' "
"-d " + json.dumps({"input": [12, 34, 56],
"voice": {"embedding": emb.reshape(-1).tolist(),
"prompt_token": pt.reshape(-1).tolist(),
"prompt_feat": pf.reshape(-1).tolist()},
"response_format": "wav"})[:200] + " ..."
)
if __name__ == "__main__":
main()