This repository has no description
5.8 kB
153 lines
1--- a/kokoro/__main__.py 2025-12-21 20:28:56.595838972 +0100
2+++ b/kokoro/__main__.py 2025-12-21 20:50:06.842504063 +0100
3@@ -3,7 +3,10 @@
4 python3 -m kokoro --text "The sky above the port was the color of television, tuned to a dead channel." -o file.wav --debug
5
6 echo "Bom dia mundo, como vão vocês" > text.txt
7-python3 -m kokoro -i text.txt -l p --voice pm_alex > audio.wav
8+python3 -m kokoro -i text.txt -l p --voice pm_alex -o output.wav
9+
10+# Streaming from stdin to stdout (pipe to mpv):
11+ollama run llama3 "tell me a joke" | python3 -m kokoro -o - --stream | mpv --demuxer=rawaudio --demuxer-rawaudio-rate=24000 --demuxer-rawaudio-format=s16le --demuxer-rawaudio-channels=1 -
12
13 Common issues:
14 pip not installed: `uv pip install pip`
15@@ -13,6 +16,7 @@
16 """
17
18 import argparse
19+import sys
20 import wave
21 from pathlib import Path
22 from typing import Generator, TYPE_CHECKING
23@@ -47,22 +51,58 @@
24 yield from pipeline(text, voice=voice, speed=speed, split_pattern=r"\n+")
25
26
27+def write_wav_audio(wav_file, text: str, kokoro_language: str, voice: str, speed=1) -> None:
28+ """Write audio frames to an open wav file handle."""
29+ wav_file.setnchannels(1) # Mono audio
30+ wav_file.setsampwidth(2) # 2 bytes per sample (16-bit audio)
31+ wav_file.setframerate(24000) # Sample rate
32+
33+ for result in generate_audio(
34+ text, kokoro_language=kokoro_language, voice=voice, speed=speed
35+ ):
36+ logger.debug(result.phonemes)
37+ if result.audio is None:
38+ continue
39+ audio_bytes = (result.audio.numpy() * 32767).astype(np.int16).tobytes()
40+ wav_file.writeframes(audio_bytes)
41+
42+
43 def generate_and_save_audio(
44- output_file: Path, text: str, kokoro_language: str, voice: str, speed=1
45+ output_file: str, text: str, kokoro_language: str, voice: str, speed=1
46+) -> None:
47+ if output_file == "-":
48+ # Write to stdout
49+ with wave.open(sys.stdout.buffer, "wb") as wav_file:
50+ write_wav_audio(wav_file, text, kokoro_language, voice, speed)
51+ else:
52+ with wave.open(str(Path(output_file).resolve()), "wb") as wav_file:
53+ write_wav_audio(wav_file, text, kokoro_language, voice, speed)
54+
55+
56+def stream_audio_raw(
57+ input_stream, kokoro_language: str, voice: str, speed: float
58 ) -> None:
59- with wave.open(str(output_file.resolve()), "wb") as wav_file:
60- wav_file.setnchannels(1) # Mono audio
61- wav_file.setsampwidth(2) # 2 bytes per sample (16-bit audio)
62- wav_file.setframerate(24000) # Sample rate
63-
64- for result in generate_audio(
65- text, kokoro_language=kokoro_language, voice=voice, speed=speed
66- ):
67+ """Stream audio as raw PCM s16le to stdout, processing stdin line by line."""
68+ from kokoro import KPipeline
69+
70+ if not voice.startswith(kokoro_language):
71+ logger.warning(f"Voice {voice} is not made for language {kokoro_language}")
72+
73+ pipeline = KPipeline(lang_code=kokoro_language)
74+ output = sys.stdout.buffer
75+
76+ for line in input_stream:
77+ line = line.rstrip('\n\r')
78+ if not line:
79+ continue
80+ logger.debug(f"Processing line: {line!r}")
81+ for result in pipeline(line, voice=voice, speed=speed, split_pattern=r"\n+"):
82 logger.debug(result.phonemes)
83 if result.audio is None:
84 continue
85 audio_bytes = (result.audio.numpy() * 32767).astype(np.int16).tobytes()
86- wav_file.writeframes(audio_bytes)
87+ output.write(audio_bytes)
88+ output.flush()
89
90
91 def main() -> None:
92@@ -83,8 +123,8 @@
93 "-o",
94 "--output-file",
95 "--output_file",
96- type=Path,
97- help="Path to output WAV file",
98+ type=str,
99+ help="Path to output WAV file (use - for stdout)",
100 required=True,
101 )
102 parser.add_argument(
103@@ -107,6 +147,11 @@
104 help="Speech speed",
105 )
106 parser.add_argument(
107+ "--stream",
108+ action="store_true",
109+ help="Stream mode: read stdin line-by-line, output raw PCM s16le 24kHz mono (for piping to mpv)",
110+ )
111+ parser.add_argument(
112 "--debug",
113 action="store_true",
114 help="Print DEBUG messages to console",
115@@ -118,6 +163,20 @@
116
117 lang = args.language or args.voice[0]
118
119+ # Streaming mode: read stdin line by line, output raw PCM
120+ if args.stream:
121+ if args.output_file != "-":
122+ logger.warning("Streaming mode always outputs to stdout, ignoring output file")
123+ if args.text:
124+ raise Exception("Cannot use --text with --stream, use stdin")
125+ if args.input_file:
126+ with open(args.input_file) as f:
127+ stream_audio_raw(f, lang, args.voice, args.speed)
128+ else:
129+ stream_audio_raw(sys.stdin, lang, args.voice, args.speed)
130+ return
131+
132+ # Non-streaming mode: read all input, output WAV
133 if args.text is not None and args.input_file is not None:
134 raise Exception("You cannot specify both 'text' and 'input_file'")
135 elif args.text:
136@@ -126,14 +185,13 @@
137 file: Path = args.input_file
138 text = file.read_text()
139 else:
140- import sys
141- print("Press Ctrl+D to stop reading input and start generating", flush=True)
142+ print("Press Ctrl+D to stop reading input and start generating", file=sys.stderr, flush=True)
143 text = '\n'.join(sys.stdin)
144
145 logger.debug(f"Input text: {text!r}")
146
147- out_file: Path = args.output_file
148- if not out_file.suffix == ".wav":
149+ out_file: str = args.output_file
150+ if out_file != "-" and not out_file.endswith(".wav"):
151 logger.warning("The output file name should end with .wav")
152 generate_and_save_audio(
153 output_file=out_file,