drugi commit

This commit is contained in:
Mirek Sobczak
2026-08-03 22:48:27 +02:00
parent 53b58363f6
commit 8d3823327a
5 changed files with 7106 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: MIT
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Models
*.pt
*.savedmodel
install/
# Ignore backup files.
*~
# Ignore Vim swap files.
.*.swp
# Ignore files generated by IDEs.
/.classpath
/.factorypath
/.idea/
/.ijwb/
/.project
/.settings
/.vscode/
# Ignore outputs generated during Bazel bootstrapping.
/output/
# Ignore jekyll build output.
/production
/.sass-cache
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
tests/integration/asr/outputs
tests/integration/nlp/outputs
tests/integration/tts/outputs
riva/client/proto/*_pb2.py
riva/client/proto/*_pb2_grpc.py
File diff suppressed because it is too large Load Diff
Executable
+7
View File
@@ -0,0 +1,7 @@
touch README.md
git init
git checkout -b main
git add README.md
git commit -m "first commit"
git remote add origin https://gitea.sic.pl/ms/nvidia-chatterbox.git
git push -u origin main
Executable
+7
View File
@@ -0,0 +1,7 @@
python3 python-clients/scripts/tts/talk.py --server 0.0.0.0:50051 \
--language-code pl-PL \
--voice Chatterbox-Multilingual.pl-PL.Male \
--text "Jesień to czas, kiedy przyroda zwalnia tempo. Dni stają się coraz krótsze, a liście na drzewach zmieniają swoje kolory na żółte, czerwone i brązowe. W chłodniejsze wieczory przyjemnie jest usiąść w fotelu z kubkiem gorącej herbaty i dobrą książką. To także idealny moment na długie spacery po parku i zbieranie kolorowych kasztanów." \
--stream \
--output output_polish.wav
+279
View File
@@ -0,0 +1,279 @@
#!/usr/bin/env python3
"""Convert a long text file to MP3 using the existing Riva TTS client.
The script splits input text into smaller chunks, synthesizes each chunk with
`python-clients/scripts/tts/talk.py`, joins all WAV chunks, and finally converts
the result to MP3 using ffmpeg.
"""
from __future__ import annotations
import argparse
import re
import shutil
import subprocess
import sys
import tempfile
import wave
from pathlib import Path
from typing import List
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Convert a long TXT file to MP3 using NVIDIA Riva TTS.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("--input", type=Path, required=True, help="Path to input .txt file.")
parser.add_argument("--output", type=Path, required=True, help="Path to output .mp3 file.")
parser.add_argument("--server", default="0.0.0.0:50051", help="Riva server address.")
parser.add_argument("--language-code", default="pl-PL", help="Language code for TTS.")
parser.add_argument(
"--voice",
default="Chatterbox-Multilingual.pl-PL.Male",
help="Riva voice name.",
)
parser.add_argument(
"--talk-script",
type=Path,
default=Path("python-clients/scripts/tts/talk.py"),
help="Path to existing talk.py script.",
)
parser.add_argument(
"--max-chars",
type=int,
default=700,
help="Maximum characters per TTS chunk.",
)
parser.add_argument(
"--sample-rate-hz",
type=int,
default=22050,
help="Output sample rate used by talk.py.",
)
parser.add_argument(
"--keep-wav",
action="store_true",
help="Keep final merged WAV next to MP3.",
)
return parser.parse_args()
def normalize_whitespace(text: str) -> str:
text = text.replace("\r\n", "\n").replace("\r", "\n")
lines = [line.strip() for line in text.split("\n")]
return "\n".join(lines)
def split_sentence_if_needed(sentence: str, max_chars: int) -> List[str]:
if len(sentence) <= max_chars:
return [sentence]
words = sentence.split()
parts: List[str] = []
current = ""
for word in words:
candidate = f"{current} {word}".strip() if current else word
if len(candidate) <= max_chars:
current = candidate
else:
if current:
parts.append(current)
# Single very long token fallback.
if len(word) > max_chars:
for i in range(0, len(word), max_chars):
parts.append(word[i : i + max_chars])
current = ""
else:
current = word
if current:
parts.append(current)
return parts
def chunk_text(text: str, max_chars: int) -> List[str]:
paragraphs = [p.strip() for p in re.split(r"\n\s*\n", text) if p.strip()]
sentence_splitter = re.compile(r"(?<=[.!?…])\s+")
chunks: List[str] = []
current = ""
for paragraph in paragraphs:
sentences = [s.strip() for s in sentence_splitter.split(paragraph) if s.strip()]
expanded_sentences: List[str] = []
for s in sentences:
expanded_sentences.extend(split_sentence_if_needed(s, max_chars))
for sentence in expanded_sentences:
candidate = f"{current} {sentence}".strip() if current else sentence
if len(candidate) <= max_chars:
current = candidate
else:
if current:
chunks.append(current)
current = sentence
if current:
chunks.append(current)
current = ""
if current:
chunks.append(current)
if not chunks:
raise ValueError("Input text is empty after preprocessing.")
return chunks
def synthesize_chunk(
talk_script: Path,
server: str,
language_code: str,
voice: str,
sample_rate_hz: int,
text: str,
output_wav: Path,
) -> None:
cmd = [
sys.executable,
str(talk_script),
"--server",
server,
"--language-code",
language_code,
"--voice",
voice,
"--text",
text,
"--stream",
"--sample-rate-hz",
str(sample_rate_hz),
"--output",
str(output_wav),
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(
"TTS synthesis failed.\n"
f"Command: {' '.join(cmd)}\n"
f"stdout:\n{result.stdout}\n"
f"stderr:\n{result.stderr}"
)
def merge_wav_files(wav_files: List[Path], output_wav: Path) -> None:
if not wav_files:
raise ValueError("No WAV files to merge.")
with wave.open(str(wav_files[0]), "rb") as first:
params = first.getparams()
frames = [first.readframes(first.getnframes())]
for wav_path in wav_files[1:]:
with wave.open(str(wav_path), "rb") as wf:
if (
wf.getnchannels() != params.nchannels
or wf.getsampwidth() != params.sampwidth
or wf.getframerate() != params.framerate
or wf.getcomptype() != params.comptype
):
raise RuntimeError(f"Incompatible WAV params in {wav_path}")
frames.append(wf.readframes(wf.getnframes()))
with wave.open(str(output_wav), "wb") as out:
out.setparams(params)
for chunk in frames:
out.writeframes(chunk)
def convert_wav_to_mp3(input_wav: Path, output_mp3: Path) -> None:
ffmpeg = shutil.which("ffmpeg")
if ffmpeg is None:
raise RuntimeError(
"ffmpeg not found in PATH. Install ffmpeg to generate MP3 output."
)
cmd = [
ffmpeg,
"-y",
"-i",
str(input_wav),
"-codec:a",
"libmp3lame",
"-q:a",
"2",
str(output_mp3),
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(
"MP3 conversion failed.\n"
f"Command: {' '.join(cmd)}\n"
f"stdout:\n{result.stdout}\n"
f"stderr:\n{result.stderr}"
)
def main() -> int:
args = parse_args()
if args.max_chars < 50:
raise ValueError("--max-chars should be at least 50.")
input_path = args.input.expanduser().resolve()
output_mp3 = args.output.expanduser().resolve()
talk_script = args.talk_script.expanduser().resolve()
if not input_path.is_file():
raise FileNotFoundError(f"Input file not found: {input_path}")
if not talk_script.is_file():
raise FileNotFoundError(f"talk.py not found: {talk_script}")
output_mp3.parent.mkdir(parents=True, exist_ok=True)
merged_wav = output_mp3.with_suffix(".wav")
text = input_path.read_text(encoding="utf-8")
text = normalize_whitespace(text)
chunks = chunk_text(text, args.max_chars)
print(f"Input split into {len(chunks)} chunks (max {args.max_chars} chars each).")
with tempfile.TemporaryDirectory(prefix="tts_chunks_") as tmp_dir:
tmp_path = Path(tmp_dir)
wav_paths: List[Path] = []
for idx, chunk in enumerate(chunks, start=1):
chunk_wav = tmp_path / f"chunk_{idx:05d}.wav"
print(f"[{idx}/{len(chunks)}] Synthesizing chunk...")
synthesize_chunk(
talk_script=talk_script,
server=args.server,
language_code=args.language_code,
voice=args.voice,
sample_rate_hz=args.sample_rate_hz,
text=chunk,
output_wav=chunk_wav,
)
wav_paths.append(chunk_wav)
print("Merging WAV chunks...")
merge_wav_files(wav_paths, merged_wav)
print("Converting WAV to MP3...")
convert_wav_to_mp3(merged_wav, output_mp3)
if not args.keep_wav and merged_wav.exists():
merged_wav.unlink()
print(f"Done: {output_mp3}")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc: # pragma: no cover
print(f"Error: {exc}", file=sys.stderr)
raise SystemExit(1)