Skip to content

Commit 2168885

Browse files
committed
refactor(core): package for distribution & add stdin
- [refactor] Rename `kokoro-tts` entry point (kokoro_tts/__init__.py) - [refactor] Extract main execution logic into `main()` function (__init__.py:1194) - [refactor] Adjust unknown command-line option loop in `main()` (__init__.py:1204,1206) - [refactor] Update `if __name__ == '__main__':` block to call `main()` (__init__.py:1307-1308) - [add] Create module entry point (kokoro_tts/__main__.py) - [feat] Add `stdin_indicators` parameter and implement stdin reading logic in `convert_text_to_audio()` (__init__.py:796-813,887-891) - [feat] Define and pass `stdin_indicators` to `convert_text_to_audio()` in `main()` (__init__.py:1197-1198,1303) - [feat] Modify input file existence check to allow stdin paths (__init__.py:1287) - [build] Define `kokoro-tts` console script (pyproject.toml:16-17) - [build] Specify `hatchling` as build backend (pyproject.toml:19-21) - [docs] Update Python version prerequisites (README.md) - [docs] Expand installation instructions, move model download info, and add usage notes for stdin (README.md) - [docs] Add `[!TIP]` notes for running commands (README.md:162-164)
1 parent fa15616 commit 2168885

5 files changed

Lines changed: 192 additions & 79 deletions

File tree

README.md

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,45 +34,107 @@ https://github.com/user-attachments/assets/8413e640-59e9-490e-861d-49187e967526
3434

3535
## Prerequisites
3636

37-
- Python 3.12
37+
- Python 3.9-3.12 (Python 3.13+ is not currently supported)
3838

3939
## Installation
4040

41+
### Method 1: Install from Git (Recommended)
42+
43+
The easiest way to install Kokoro TTS is directly from the repository:
44+
45+
```bash
46+
# Using uv (recommended)
47+
uv tool install git+https://github.com/nazdridoy/kokoro-tts
48+
49+
# Using pip
50+
pip install git+https://github.com/nazdridoy/kokoro-tts
51+
```
52+
53+
After installation, you can run:
54+
```bash
55+
kokoro-tts --help
56+
```
57+
58+
### Method 2: Clone and Install Locally
59+
4160
1. Clone the repository:
4261
```bash
4362
git clone https://github.com/nazdridoy/kokoro-tts.git
4463
cd kokoro-tts
4564
```
4665

47-
2. Install required packages:
48-
49-
It is recommended to use a virtual environment to avoid dependency conflicts.
66+
2. Install the package:
5067

5168
**With `uv` (recommended):**
5269
```bash
5370
uv venv
71+
uv pip install -e .
72+
```
73+
74+
**With `pip`:**
75+
```bash
76+
python -m venv .venv
77+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
78+
pip install -e .
79+
```
80+
81+
3. Run the tool:
82+
```bash
83+
# If using uv
84+
uv run kokoro-tts --help
85+
86+
# If using pip with activated venv
87+
kokoro-tts --help
88+
```
89+
90+
### Method 3: Run Without Installation
91+
92+
If you prefer to run without installing:
93+
94+
1. Clone the repository:
95+
```bash
96+
git clone https://github.com/nazdridoy/kokoro-tts.git
97+
cd kokoro-tts
98+
```
99+
100+
2. Install dependencies only:
101+
102+
**With `uv`:**
103+
```bash
104+
uv venv
54105
uv sync
55106
```
107+
56108
**With `pip`:**
57109
```bash
58110
python -m venv .venv
59-
source .venv/bin/activate
111+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
60112
pip install -r requirements.txt
61113
```
62114

63-
Note: You can also use `uv` as a faster alternative to pip for package installation. (This is a uv project)
64-
Note: Python>=3.13 is not currently supported.
115+
3. Run directly:
116+
```bash
117+
# With uv
118+
uv run -m kokoro_tts --help
119+
120+
# With pip (venv activated)
121+
python -m kokoro_tts --help
122+
```
123+
124+
### Download Model Files
125+
126+
After installation, download the required model files to your working directory:
65127

66-
3. Download the required model files:
67128
```bash
68-
# Download either voices.json or voices.bin (bin is preferred)
129+
# Download voice data (bin format is preferred)
69130
wget https://github.com/nazdridoy/kokoro-tts/releases/download/v1.0.0/voices-v1.0.bin
70131

71132
# Download the model
72133
wget https://github.com/nazdridoy/kokoro-tts/releases/download/v1.0.0/kokoro-v1.0.onnx
73134
```
74-
Note: The script will automatically use voices.bin if present, falling back to voices.json if bin is not available.
75135

136+
> [!NOTE]
137+
> The script will automatically use `voices-v1.0.bin` if present, falling back to `voices.json` if the bin file is not available. Place these files in the same directory where you run the `kokoro-tts` command.
76138
77139
## Supported voices:
78140

@@ -86,16 +148,18 @@ Note: The script will automatically use voices.bin if present, falling back to v
86148
| 🇯🇵 | jf\_alpha, jf\_gongitsune, jf\_nezumi, jf\_tebukuro, jm\_kumo | **ja** |
87149
| 🇨🇳 | zf\_xiaobei, zf\_xiaoni, zf\_xiaoxiao, zf\_xiaoyi, zm\_yunjian, zm\_yunxi, zm\_yunxia, zm\_yunyang | **cmn** |
88150

89-
90151
## Usage
91152

92-
Basic usage:
153+
### Basic Usage
154+
93155
```bash
94-
./kokoro-tts <input_text_file> [<output_audio_file>] [options]
156+
kokoro-tts <input_text_file> [<output_audio_file>] [options]
95157
```
96158

97159
> [!NOTE]
98-
> If you have installed the dependencies in a virtual environment, you need to either activate it first (e.g., `source .venv/bin/activate`) or use `uv run` to execute the commands.
160+
> - If you installed via Method 1 (git install), use `kokoro-tts` directly
161+
> - If you installed via Method 2 (local install), use `uv run kokoro-tts` or activate your virtual environment first
162+
> - If you're using Method 3 (no install), use `uv run -m kokoro_tts` or `python -m kokoro_tts` with activated venv
99163
100164
### Commands
101165

@@ -121,6 +185,7 @@ Basic usage:
121185
- `.txt`: Text file input
122186
- `.epub`: EPUB book input (will process chapters)
123187
- `.pdf`: PDF document input (extracts chapters from TOC or content)
188+
- `-` or `/dev/stdin` (Linux/macOS) or `CONIN$` (Windows): Standard input (stdin)
124189

125190
### Examples
126191

@@ -129,11 +194,13 @@ Basic usage:
129194
kokoro-tts input.txt output.wav --speed 1.2 --lang en-us --voice af_sarah
130195

131196
# Read from standard input (stdin)
132-
echo "Hello World" | uv run kokoro-tts /dev/stdin --stream
133-
cat input.txt | kokoro-tts /dev/stdin output.wav
197+
echo "Hello World" | kokoro-tts - --stream
198+
cat input.txt | kokoro-tts - output.wav
134199

135-
# Use voice blending (60-40 mix) (with uv run)
136-
uv run kokoro-tts input.txt output.wav --voice "af_sarah:60,am_adam:40"
200+
# Cross-platform stdin support:
201+
# Linux/macOS: echo "text" | kokoro-tts - --stream
202+
# Windows: echo "text" | kokoro-tts - --stream
203+
# All platforms also support: kokoro-tts /dev/stdin --stream (Linux/macOS) or kokoro-tts CONIN$ --stream (Windows)
137204

138205
# Use voice blending (60-40 mix)
139206
kokoro-tts input.txt output.wav --voice "af_sarah:60,am_adam:40"
@@ -155,13 +222,18 @@ kokoro-tts input.epub --split-output ./chunks/ --debug
155222

156223
# Process PDF and split into chapters
157224
kokoro-tts input.pdf --split-output ./chunks/ --format mp3
225+
158226
# List available voices
159227
kokoro-tts --help-voices
160228

161229
# List supported languages
162230
kokoro-tts --help-languages
163231
```
164232

233+
> [!TIP]
234+
> If you're using Method 2, replace `kokoro-tts` with `uv run kokoro-tts` in the examples above.
235+
> If you're using Method 3, replace `kokoro-tts` with `uv run -m kokoro_tts` or `python -m kokoro_tts` in the examples above.
236+
165237
## Features in Detail
166238

167239
### EPUB Processing

kokoro-tts renamed to kokoro_tts/__init__.py

Lines changed: 80 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -779,8 +779,13 @@ def process_chunk_sequential(chunk: str, kokoro: Kokoro, voice: str, speed: floa
779779
return None, None
780780

781781
def convert_text_to_audio(input_file, output_file=None, voice=None, speed=1.0, lang="en-us",
782-
stream=False, split_output=None, format="wav", debug=False):
782+
stream=False, split_output=None, format="wav", debug=False, stdin_indicators=None):
783783
global stop_spinner
784+
785+
# Define stdin indicators if not provided
786+
if stdin_indicators is None:
787+
stdin_indicators = ['/dev/stdin', '-', 'CONIN$'] # CONIN$ is Windows stdin
788+
784789
# Load Kokoro model
785790
try:
786791
kokoro = Kokoro("kokoro-v1.0.onnx", "voices-v1.0.bin")
@@ -792,48 +797,53 @@ def convert_text_to_audio(input_file, output_file=None, voice=None, speed=1.0, l
792797
if voice:
793798
voice = validate_voice(voice, kokoro)
794799
else:
795-
# Interactive voice selection
796-
voices = list_available_voices(kokoro)
797-
print("\nHow to choose a voice:")
798-
print("You can use either a single voice or blend two voices together.")
799-
print("\nFor a single voice:")
800-
print(" • Just enter one number (example: '7')")
801-
print("\nFor blending two voices:")
802-
print(" • Enter two numbers separated by comma")
803-
print(" • Optionally add weights after each number using ':weight'")
804-
print("\nExamples:")
805-
print(" • '7' - Use voice #7 only")
806-
print(" • '7,11' - Mix voices #7 and #11 equally (50% each)")
807-
print(" • '7:60,11:40' - Mix 60% of voice #7 with 40% of voice #11")
808-
try:
809-
voice_input = input("Choose voice(s) by number: ")
810-
if ',' in voice_input:
811-
# Handle blended voices
812-
pairs = []
813-
for pair in voice_input.split(','):
814-
if ':' in pair:
815-
num, weight = pair.strip().split(':')
816-
voice_idx = int(num.strip()) - 1
817-
if not (0 <= voice_idx < len(voices)):
818-
raise ValueError(f"Invalid voice number: {int(num)}")
819-
pairs.append(f"{voices[voice_idx]}:{weight}")
820-
else:
821-
voice_idx = int(pair.strip()) - 1
822-
if not (0 <= voice_idx < len(voices)):
823-
raise ValueError(f"Invalid voice number: {int(pair)}")
824-
pairs.append(voices[voice_idx])
825-
voice = ','.join(pairs)
826-
else:
827-
# Single voice
828-
voice_choice = int(voice_input) - 1
829-
if not (0 <= voice_choice < len(voices)):
830-
raise ValueError("Invalid choice")
831-
voice = voices[voice_choice]
832-
# Validate and potentially convert to blend
833-
voice = validate_voice(voice, kokoro)
834-
except (ValueError, IndexError):
835-
print("Invalid choice. Using default voice.")
800+
# Check if we're using stdin (can't do interactive input)
801+
if input_file in stdin_indicators:
802+
print("Using stdin - automatically selecting default voice (af_sarah)")
836803
voice = "af_sarah" # default voice
804+
else:
805+
# Interactive voice selection
806+
voices = list_available_voices(kokoro)
807+
print("\nHow to choose a voice:")
808+
print("You can use either a single voice or blend two voices together.")
809+
print("\nFor a single voice:")
810+
print(" • Just enter one number (example: '7')")
811+
print("\nFor blending two voices:")
812+
print(" • Enter two numbers separated by comma")
813+
print(" • Optionally add weights after each number using ':weight'")
814+
print("\nExamples:")
815+
print(" • '7' - Use voice #7 only")
816+
print(" • '7,11' - Mix voices #7 and #11 equally (50% each)")
817+
print(" • '7:60,11:40' - Mix 60% of voice #7 with 40% of voice #11")
818+
try:
819+
voice_input = input("Choose voice(s) by number: ")
820+
if ',' in voice_input:
821+
# Handle blended voices
822+
pairs = []
823+
for pair in voice_input.split(','):
824+
if ':' in pair:
825+
num, weight = pair.strip().split(':')
826+
voice_idx = int(num.strip()) - 1
827+
if not (0 <= voice_idx < len(voices)):
828+
raise ValueError(f"Invalid voice number: {int(num)}")
829+
pairs.append(f"{voices[voice_idx]}:{weight}")
830+
else:
831+
voice_idx = int(pair.strip()) - 1
832+
if not (0 <= voice_idx < len(voices)):
833+
raise ValueError(f"Invalid voice number: {int(pair)}")
834+
pairs.append(voices[voice_idx])
835+
voice = ','.join(pairs)
836+
else:
837+
# Single voice
838+
voice_choice = int(voice_input) - 1
839+
if not (0 <= voice_choice < len(voices)):
840+
raise ValueError("Invalid choice")
841+
voice = voices[voice_choice]
842+
# Validate and potentially convert to blend
843+
voice = validate_voice(voice, kokoro)
844+
except (ValueError, IndexError):
845+
print("Invalid choice. Using default voice.")
846+
voice = "af_sarah" # default voice
837847
except ValueError as e:
838848
print(f"Error: {e}")
839849
sys.exit(1)
@@ -875,8 +885,12 @@ def convert_text_to_audio(input_file, output_file=None, voice=None, speed=1.0, l
875885
parser = PdfParser(input_file, debug=debug)
876886
chapters = parser.get_chapters()
877887
else:
878-
with open(input_file, 'r', encoding='utf-8') as file:
879-
text = file.read()
888+
# Handle stdin specially (cross-platform)
889+
if input_file in stdin_indicators:
890+
text = sys.stdin.read()
891+
else:
892+
with open(input_file, 'r', encoding='utf-8') as file:
893+
text = file.read()
880894
# Treat single text file as one chapter
881895
chapters = [{'title': 'Chapter 1', 'content': text}]
882896

@@ -1194,22 +1208,27 @@ def get_valid_options():
11941208
'--debug' # Add debug option
11951209
}
11961210

1197-
if __name__ == "__main__":
1198-
# Validate command line options first
1211+
1212+
1213+
1214+
def main():
1215+
"""Main entry point for the kokoro-tts CLI tool."""
1216+
# Define stdin indicators once (cross-platform)
1217+
stdin_indicators = ['/dev/stdin', '-', 'CONIN$'] # CONIN$ is Windows stdin
1218+
1219+
# Validate command line arguments
11991220
valid_options = get_valid_options()
1200-
unknown_options = []
12011221

12021222
# Check for unknown options
1203-
i = 1
1223+
unknown_options = []
1224+
i = 0
12041225
while i < len(sys.argv):
12051226
arg = sys.argv[i]
1206-
if arg.startswith('--') or arg.startswith('-'):
1207-
# Check if it's a valid option
1208-
if arg not in valid_options:
1209-
unknown_options.append(arg)
1227+
if arg.startswith('--') and arg not in valid_options:
1228+
unknown_options.append(arg)
12101229
# Skip the next argument if it's a value for an option that takes parameters
1211-
elif arg in {'--speed', '--lang', '--voice', '--split-output', '--format'}:
1212-
i += 1
1230+
elif arg in {'--speed', '--lang', '--voice', '--split-output', '--format'}:
1231+
i += 1
12131232
i += 1
12141233

12151234
# If unknown options were found, show error and help
@@ -1287,8 +1306,8 @@ def get_valid_options():
12871306
print_usage()
12881307
sys.exit(1)
12891308

1290-
# Ensure the input file exists
1291-
if not os.access(input_file, os.R_OK):
1309+
# Ensure the input file exists (skip check for stdin)
1310+
if input_file not in stdin_indicators and not os.access(input_file, os.R_OK):
12921311
print(f"Error: Cannot read from {input_file}. File may not exist or you may not have permission to read it.")
12931312
sys.exit(1)
12941313

@@ -1303,5 +1322,9 @@ def get_valid_options():
13031322
# Convert text to audio with debug flag
13041323
convert_text_to_audio(input_file, output_file, voice=voice, stream=stream,
13051324
speed=speed, lang=lang, split_output=split_output,
1306-
format=format, debug=debug)
1325+
format=format, debug=debug, stdin_indicators=stdin_indicators)
1326+
1327+
1328+
if __name__ == '__main__':
1329+
main()
13071330

kokoro_tts/__main__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Entry point for running kokoro-tts as a module.
5+
This allows users to run: python -m kokoro_tts
6+
"""
7+
8+
from . import main
9+
10+
if __name__ == "__main__":
11+
main()

0 commit comments

Comments
 (0)