Prompt48 commited on
Commit
a5855ca
·
verified ·
1 Parent(s): 84324b5

Upload edit\Qwen3-TTS-test\voice-clone-test.py with huggingface_hub

Browse files
edit//Qwen3-TTS-test//voice-clone-test.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+ import torch
6
+ import soundfile as sf
7
+ from scipy import signal
8
+ import pyloudnorm as pyln
9
+
10
+ from qwen_tts import Qwen3TTSModel
11
+
12
+
13
+ # --- Audio post-processing ---
14
+ # Highpass 80 Hz | Compression 4:1 | Treble +5 dB | Loudness -13 LUFS
15
+
16
+ def _highpass(audio: np.ndarray, sr: int, cutoff_hz: float = 80.0) -> np.ndarray:
17
+ sos = signal.butter(4, cutoff_hz, btype="high", fs=sr, output="sos")
18
+ return signal.sosfilt(sos, audio)
19
+
20
+ def _compress(audio: np.ndarray, threshold_db: float = -8.0, ratio: float = 2.5) -> np.ndarray:
21
+ threshold_lin = 10 ** (threshold_db / 20)
22
+ abs_audio = np.abs(audio)
23
+ mask = abs_audio > threshold_lin
24
+ gain = np.ones_like(audio)
25
+ gain[mask] = (threshold_lin + (abs_audio[mask] - threshold_lin) / ratio) / abs_audio[mask]
26
+ return audio * gain
27
+
28
+ def _treble_boost(audio: np.ndarray, sr: int, gain_db: float = 2.0, shelf_hz: float = 4000.0) -> np.ndarray:
29
+ gain_lin = 10 ** (gain_db / 20)
30
+ sos = signal.butter(2, shelf_hz, btype="high", fs=sr, output="sos")
31
+ high = signal.sosfilt(sos, audio)
32
+ return audio + high * (gain_lin - 1)
33
+
34
+ def _loudness_normalize(audio: np.ndarray, sr: int, target_lufs: float = -20.0) -> np.ndarray:
35
+ meter = pyln.Meter(sr)
36
+ loudness = meter.integrated_loudness(audio)
37
+ if np.isinf(loudness):
38
+ return audio
39
+ return pyln.normalize.loudness(audio, loudness, target_lufs)
40
+
41
+ def process_audio(audio: np.ndarray, sr: int) -> np.ndarray:
42
+ audio = audio.astype(np.float64)
43
+ audio = _highpass(audio, sr)
44
+ audio = _loudness_normalize(audio, sr) # normalize first, then shape
45
+ audio = _compress(audio)
46
+ audio = _treble_boost(audio, sr)
47
+ peak = np.max(np.abs(audio)) # true peak limiter — never exceed -1 dBFS
48
+ if peak > 0.891:
49
+ audio = audio * (0.891 / peak)
50
+ return audio.astype(np.float32)
51
+
52
+
53
+ ROOT = Path(__file__).parent
54
+ REF_WAV = ROOT / "reference" / "reference.WAV"
55
+ REF_TXT = ROOT / "reference" / "reference.txt"
56
+ SCRIPTS = ROOT / "scripts.json"
57
+ OUT_DIR = ROOT / "out"
58
+
59
+
60
+ def main():
61
+ if not REF_WAV.exists():
62
+ raise SystemExit(f"Missing reference audio: {REF_WAV}")
63
+ if not REF_TXT.exists():
64
+ raise SystemExit(f"Missing reference text: {REF_TXT}")
65
+ if not SCRIPTS.exists():
66
+ raise SystemExit(f"Missing scripts file: {SCRIPTS}")
67
+
68
+ OUT_DIR.mkdir(exist_ok=True)
69
+
70
+ # Clear out folder before a fresh run
71
+ wavs_to_delete = list(OUT_DIR.glob("*.wav"))
72
+ if wavs_to_delete:
73
+ print(f"Clearing {len(wavs_to_delete)} existing WAV(s) from out/...")
74
+ failed = []
75
+ for f in wavs_to_delete:
76
+ try:
77
+ f.unlink()
78
+ except Exception as e:
79
+ failed.append((f.name, e))
80
+ if failed:
81
+ for name, err in failed:
82
+ print(f" Could not delete {name}: {err}")
83
+ input("Close any app using these files, then press Enter to continue (or Ctrl+C to abort)...")
84
+ for f in OUT_DIR.glob("*.wav"):
85
+ try:
86
+ f.unlink()
87
+ except Exception as e:
88
+ raise SystemExit(f"Still cannot delete {f.name}: {e}")
89
+ print("out/ cleared.")
90
+
91
+ ref_text = REF_TXT.read_text(encoding="utf-8").strip()
92
+ scripts = json.loads(SCRIPTS.read_text(encoding="utf-8"))["scripts"]
93
+
94
+ device = "cuda:0" if torch.cuda.is_available() else "cpu"
95
+ print(f"Device: {device}")
96
+
97
+ model = Qwen3TTSModel.from_pretrained(
98
+ "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
99
+ device_map=device,
100
+ dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
101
+ attn_implementation="sdpa" if torch.cuda.is_available() else None,
102
+ low_cpu_mem_usage=True,
103
+ )
104
+
105
+ for item in scripts:
106
+ sid = item["id"]
107
+ text = item["text"]
108
+ out_path = OUT_DIR / f"{sid}.wav"
109
+
110
+ if out_path.exists():
111
+ print(f"[{sid}] skip (exists)")
112
+ continue
113
+
114
+ print(f"[{sid}] {text[:60]}...")
115
+ wavs, sr = model.generate_voice_clone(
116
+ text=text,
117
+ language="English",
118
+ ref_audio=str(REF_WAV),
119
+ ref_text=ref_text,
120
+ )
121
+ processed = process_audio(wavs[0], sr)
122
+ sf.write(out_path, processed, sr)
123
+ print(f"[{sid}] -> {out_path.name}")
124
+
125
+ print("Done.")
126
+
127
+
128
+ if __name__ == "__main__":
129
+ main()