167 lines
5.0 KiB
Python
167 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
SRC_ROOT = REPO_ROOT / "src"
|
|
|
|
for import_root in (REPO_ROOT, SRC_ROOT):
|
|
import_root_str = str(import_root)
|
|
if import_root_str not in sys.path:
|
|
sys.path.insert(0, import_root_str)
|
|
|
|
import soundfile as sf # noqa: E402
|
|
import torch # noqa: E402
|
|
from loguru import logger # noqa: E402
|
|
|
|
from dots_tts.utils.logging import configure_logging # noqa: E402
|
|
from dots_tts.runtime_double_streaming import ( # noqa: E402
|
|
DotsTtsRuntimeDoubleStreaming,
|
|
)
|
|
from dots_tts.utils.text import normalize_text # noqa: E402
|
|
from dots_tts.utils.util import seed_everything # noqa: E402
|
|
|
|
|
|
def parse_args(argv=None):
|
|
parser = argparse.ArgumentParser(
|
|
description="Temporary example for dots.tts double streaming session API."
|
|
)
|
|
parser.add_argument(
|
|
"--model-name-or-path",
|
|
required=True,
|
|
help="Local pretrained directory or Hugging Face repo id",
|
|
)
|
|
parser.add_argument("--text", required=True, help="Input text")
|
|
parser.add_argument("--output", default="double_streaming.wav", help="Output wav path")
|
|
parser.add_argument(
|
|
"--prompt-audio",
|
|
default=None,
|
|
help="Optional reference audio for ref_audio_only speaker conditioning",
|
|
)
|
|
parser.add_argument("--revision", default=None, help="Optional Hugging Face revision")
|
|
parser.add_argument("--cache-dir", default=None, help="Optional Hugging Face cache dir")
|
|
parser.add_argument("--precision", default="bfloat16", help="Inference precision")
|
|
parser.add_argument(
|
|
"--optimize",
|
|
action="store_true",
|
|
help="Enable inference optimization and warmup",
|
|
)
|
|
parser.add_argument(
|
|
"--seed",
|
|
type=int,
|
|
default=42,
|
|
help="Random seed.",
|
|
)
|
|
parser.add_argument("--ode-method", default="euler", help="ODE solver method")
|
|
parser.add_argument("--num-steps", type=int, default=10, help="Diffusion sampling steps")
|
|
parser.add_argument(
|
|
"--guidance-scale",
|
|
type=float,
|
|
default=1.2,
|
|
help="Classifier-free guidance scale",
|
|
)
|
|
parser.add_argument(
|
|
"--eos-threshold",
|
|
type=float,
|
|
default=0.8,
|
|
help="EOS stop threshold for finish_text() tail decode",
|
|
)
|
|
parser.add_argument(
|
|
"--max-generate-length",
|
|
type=int,
|
|
default=500,
|
|
help="Maximum number of decoded audio patches in double streaming",
|
|
)
|
|
parser.add_argument(
|
|
"--normalize-text",
|
|
action="store_true",
|
|
help="Normalize text before tokenizer encode",
|
|
)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def _prepare_text(text: str, *, normalize: bool) -> str:
|
|
prepared = text.strip()
|
|
if normalize:
|
|
prepared = normalize_text(prepared)
|
|
if not prepared:
|
|
raise ValueError("Input text is empty after preprocessing.")
|
|
return prepared
|
|
|
|
|
|
def main(argv=None):
|
|
configure_logging()
|
|
args = parse_args(argv)
|
|
seed_everything(args.seed)
|
|
|
|
output_path = Path(args.output)
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
runtime = DotsTtsRuntimeDoubleStreaming.from_pretrained(
|
|
args.model_name_or_path,
|
|
revision=args.revision,
|
|
cache_dir=args.cache_dir,
|
|
precision=args.precision,
|
|
optimize=args.optimize,
|
|
max_generate_length=args.max_generate_length,
|
|
)
|
|
prepared_text = _prepare_text(args.text, normalize=args.normalize_text)
|
|
text_token_ids = runtime.model.tokenizer.encode(
|
|
prepared_text,
|
|
add_special_tokens=False,
|
|
)
|
|
if not text_token_ids:
|
|
raise ValueError("Tokenizer produced no text tokens.")
|
|
|
|
logger.info(
|
|
"Double streaming example started: text_len={} text_token_count={} output={}",
|
|
len(prepared_text),
|
|
len(text_token_ids),
|
|
output_path,
|
|
)
|
|
|
|
session = runtime.start_double_streaming(
|
|
prompt_audio_path=args.prompt_audio,
|
|
ode_method=args.ode_method,
|
|
num_steps=args.num_steps,
|
|
guidance_scale=args.guidance_scale,
|
|
eos_threshold=args.eos_threshold,
|
|
)
|
|
|
|
chunks: list[torch.Tensor] = []
|
|
for index, token_id in enumerate(text_token_ids, start=1):
|
|
chunk = session.push_text_token(token_id)
|
|
logger.info(
|
|
"Double streaming step: token_index={} token_id={} emitted_audio={}",
|
|
index,
|
|
token_id,
|
|
chunk is not None,
|
|
)
|
|
if chunk is not None:
|
|
chunks.append(chunk.detach().cpu())
|
|
|
|
for chunk in session.finish_text():
|
|
chunks.append(chunk.detach().cpu())
|
|
|
|
if not chunks:
|
|
raise RuntimeError("Double streaming produced no audio chunks.")
|
|
|
|
audio = torch.cat(chunks, dim=-1)
|
|
sf.write(
|
|
output_path,
|
|
audio.float().squeeze().numpy(),
|
|
runtime.sample_rate,
|
|
)
|
|
logger.info(
|
|
"Double streaming example completed: output={} chunk_count={} samples={}",
|
|
output_path,
|
|
len(chunks),
|
|
audio.shape[-1],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|