Загрузка данных


from pathlib import Path
import sys
import numpy as np
import torch
import soundfile as sf

# ============================================================
# PATHS
# ============================================================

COSYVOICE_ROOT = Path(r"C:\Users\Geodezik\image_generation\CosyVoice_official").resolve()
MATCHA_ROOT = COSYVOICE_ROOT / "third_party" / "Matcha-TTS"
OLD_BAD_ROOT = Path(r"C:\Users\Geodezik\image_generation\cosyvoice").resolve()

MODEL_DIR = COSYVOICE_ROOT / "pretrained_models" / "Fun-CosyVoice3-0.5B"
QWEN_DIR = MODEL_DIR / "CosyVoice-BlankEN"
PROMPT_WAV = COSYVOICE_ROOT / "asset" / "zero_shot_prompt.wav"

OUT_DIR = COSYVOICE_ROOT / "_sanity_outputs"
OUT_DIR.mkdir(parents=True, exist_ok=True)

# ============================================================
# CLEAN IMPORTS
# ============================================================

sys.path = [
    p for p in sys.path
    if str(OLD_BAD_ROOT).lower() not in str(Path(p).resolve()).lower()
]

for name in list(sys.modules.keys()):
    if (
        name == "cosyvoice"
        or name.startswith("cosyvoice.")
        or name == "wetext"
        or name.startswith("wetext.")
    ):
        del sys.modules[name]

sys.path.insert(0, str(MATCHA_ROOT))
sys.path.insert(0, str(COSYVOICE_ROOT))

# ============================================================
# BASIC CHECKS
# ============================================================

print("=" * 80)
print("ENV")
print("=" * 80)
print("python:", sys.executable)
print("torch:", torch.__version__)
print("cuda:", torch.cuda.is_available())
if torch.cuda.is_available():
    print("cuda device:", torch.cuda.get_device_name(0))
    print("torch cuda:", torch.version.cuda)

print("\nPATHS")
print("COSYVOICE_ROOT:", COSYVOICE_ROOT)
print("MODEL_DIR:", MODEL_DIR)
print("QWEN_DIR:", QWEN_DIR)
print("PROMPT_WAV:", PROMPT_WAV)

for p in [
    MODEL_DIR / "cosyvoice3.yaml",
    MODEL_DIR / "llm.pt",
    MODEL_DIR / "flow.pt",
    MODEL_DIR / "hift.pt",
    QWEN_DIR / "config.json",
    QWEN_DIR / "model.safetensors",
    QWEN_DIR / "tokenizer_config.json",
    QWEN_DIR / "vocab.json",
    QWEN_DIR / "merges.txt",
    PROMPT_WAV,
]:
    print("exists:", p, p.exists(), f"{p.stat().st_size / 1024 / 1024:.2f} MB" if p.exists() and p.is_file() else "")
    if not p.exists():
        raise FileNotFoundError(p)

# ============================================================
# TOKENIZER CHECK
# ============================================================

print("\n" + "=" * 80)
print("TOKENIZER CHECK")
print("=" * 80)

from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained(
    str(QWEN_DIR),
    trust_remote_code=True,
    local_files_only=True,
)

print("tokenizer:", tok.__class__)
print("vocab_size:", getattr(tok, "vocab_size", None))
print("len:", len(tok))
print("special_tokens_map:", tok.special_tokens_map)
print("all_special_tokens:", tok.all_special_tokens)

for s in [
    "<|endofprompt|>",
    "<|im_start|>",
    "<|im_end|>",
    "[breath]",
    "<|zh|>",
    "<|en|>",
    "<|ja|>",
    "<|ko|>",
    "<|yue|>",
]:
    tid = tok.convert_tokens_to_ids(s)
    print(repr(s), "->", tid)

ids = tok("<|endofprompt|>", add_special_tokens=False)["input_ids"]
tokens = tok.convert_ids_to_tokens(ids)

print("encode <|endofprompt|> ids:", ids)
print("encode <|endofprompt|> tokens:", tokens)

if len(ids) != 1:
    raise RuntimeError(
        "<|endofprompt|> is NOT a single token. "
        f"ids={ids}, tokens={tokens}. "
        "Модель/tokenizer всё ещё некорректны для CosyVoice3."
    )

# ============================================================
# NO FFMPEG / NO TORCHCODEC LOADER
# ============================================================

def resample_np(audio_1d: np.ndarray, sr: int, target_sr: int) -> np.ndarray:
    audio_1d = audio_1d.astype(np.float32, copy=False)

    if sr == target_sr:
        return audio_1d

    try:
        import soxr
        return soxr.resample(audio_1d, sr, target_sr).astype(np.float32, copy=False)
    except Exception:
        from scipy.signal import resample_poly
        from math import gcd
        g = gcd(sr, target_sr)
        return resample_poly(audio_1d, target_sr // g, sr // g).astype(np.float32, copy=False)


def load_wav_no_torchcodec(wav, target_sr, min_sr=16000):
    audio, sr = sf.read(str(wav), dtype="float32", always_2d=True)
    mono = audio.mean(axis=1).astype(np.float32, copy=False)

    if sr < min_sr:
        raise ValueError(f"sample rate {sr} is lower than min_sr {min_sr}")

    mono = resample_np(mono, sr, target_sr)
    return torch.from_numpy(mono).unsqueeze(0)

# ============================================================
# COSYVOICE IMPORT + PATCH
# ============================================================

print("\n" + "=" * 80)
print("COSYVOICE IMPORT")
print("=" * 80)

import cosyvoice
import cosyvoice.cli.cosyvoice as cosyvoice_cli
import cosyvoice.utils.file_utils as file_utils
import cosyvoice.cli.frontend as frontend

file_utils.load_wav = load_wav_no_torchcodec
frontend.load_wav = load_wav_no_torchcodec

print("cosyvoice:", cosyvoice.__file__)
print("cosyvoice cli:", cosyvoice_cli.__file__)
print("frontend:", frontend.__file__)

expected = str(COSYVOICE_ROOT / "cosyvoice").lower()
actual = str(Path(cosyvoice.__file__).parent).lower()

if not actual.startswith(expected):
    raise RuntimeError(f"Wrong cosyvoice import: {actual}")

# wetext check
try:
    import wetext
    from wetext import Normalizer
    print("wetext:", wetext.__file__)
    print("wetext Normalizer:", Normalizer)
except Exception as e:
    raise RuntimeError(f"wetext import failed: {e}")

# ============================================================
# MODEL LOAD
# ============================================================

print("\n" + "=" * 80)
print("LOAD MODEL")
print("=" * 80)

from cosyvoice.cli.cosyvoice import AutoModel

cosyvoice_model = AutoModel(
    model_dir=str(MODEL_DIR),
    fp16=False,
)

print("model:", type(cosyvoice_model))
print("sample_rate:", cosyvoice_model.sample_rate)
print("frontend:", type(cosyvoice_model.frontend))
print("frontend text_frontend:", getattr(cosyvoice_model.frontend, "text_frontend", None))

if getattr(cosyvoice_model.frontend, "text_frontend", None) != "wetext":
    raise RuntimeError("frontend.text_frontend is not 'wetext'")

# ============================================================
# SAVE HELPER
# ============================================================

def save_outs(name, outs):
    outs = list(outs)
    if not outs:
        raise RuntimeError("No output chunks")

    wav = torch.cat([o["tts_speech"] for o in outs], dim=1)
    audio = wav.detach().float().cpu().squeeze(0).numpy()
    audio = np.clip(audio, -1.0, 1.0)

    out_path = OUT_DIR / f"{name}.wav"
    sf.write(str(out_path), audio, cosyvoice_model.sample_rate, subtype="PCM_16")

    print("\nSAVED:", out_path)
    print("shape:", tuple(wav.shape))
    print("duration:", wav.shape[1] / cosyvoice_model.sample_rate)
    print("min/max/std:", wav.min().item(), wav.max().item(), wav.std().item())

    if wav.shape[1] / cosyvoice_model.sample_rate < 1.0:
        raise RuntimeError(f"{name} output too short")

    return out_path

# ============================================================
# TEST 1: OFFICIAL ZH ZERO-SHOT
# ============================================================

print("\n" + "=" * 80)
print("TEST 1: OFFICIAL ZH ZERO-SHOT")
print("=" * 80)

TEXT_ZH = "八百标兵奔北坡,北坡炮兵并排跑,炮兵怕把标兵碰,标兵怕碰炮兵炮。"
PROMPT_TEXT_ZH = "You are a helpful assistant.<|endofprompt|>希望你以后能够做的比我还好呦。"

out1 = save_outs(
    "01_official_zh_zero_shot",
    cosyvoice_model.inference_zero_shot(
        TEXT_ZH,
        PROMPT_TEXT_ZH,
        str(PROMPT_WAV),
        stream=False,
        speed=1.0,
    )
)

# ============================================================
# TEST 2: EN ZERO-SHOT WITH SAME CHINESE PROMPT TRANSCRIPT
# ============================================================

print("\n" + "=" * 80)
print("TEST 2: EN ZERO-SHOT")
print("=" * 80)

TEXT_EN = (
    "Hello. This is a simple English test. "
    "I am checking whether the speech is clear, continuous, and understandable."
)

out2 = save_outs(
    "02_en_zero_shot",
    cosyvoice_model.inference_zero_shot(
        TEXT_EN,
        PROMPT_TEXT_ZH,
        str(PROMPT_WAV),
        stream=False,
        speed=1.0,
    )
)

print("\n" + "=" * 80)
print("DONE")
print("=" * 80)
print("Listen files:")
print(out1)
print(out2)