Organize repository: create scripts/ and docs/ directories
- Move utility scripts to scripts/ directory: - add_voice.py (add custom voices) - say.py (CLI TTS client) - test_voices.sh (voice testing) - download_samples.sh (OpenAI samples) - Remove Windows batch files (.bat) - Linux/Docker focus - startup.bat - download_samples.bat - download_voices_tts-1.bat - download_voices_tts-1-hd.bat - Create docs/ directory with AUDIT.md: - Complete repository file audit - Document 10+ abandoned TTS models to integrate - Plan for binary mirror strategy - Outline future refactoring to src/ structure Raccoon mission: Scripts are in scripts/, docs reference them. No code belongs in docs/ - only documentation. 🦝 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
91862d4f82
commit
e8183b4585
9 changed files with 340 additions and 30 deletions
63
scripts/add_voice.py
Executable file
63
scripts/add_voice.py
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import yaml
|
||||
|
||||
print("!! WARNING EXPERIMENTAL !! - THIS TOOL WILL ERASE ALL COMMENTS FROM THE CONFIG FILES .. OR WORSE!!")
|
||||
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('sample', action='store', help="Set the wav sample file")
|
||||
parser.add_argument('-n', '--name', action='store', help="Set the name for the voice (by default will use the WAV file name)")
|
||||
parser.add_argument('-l', '--language', action='store', default="auto", help="Set the language for the voice",
|
||||
choices=['auto', 'en', 'es', 'fr', 'de', 'it', 'pt', 'pl', 'tr', 'ru', 'nl', 'cs', 'ar', 'zh-cn', 'ja', 'hu', 'ko', 'hi'])
|
||||
parser.add_argument('--openai-model', action='store', default="tts-1-hd", help="Set the openai model for the voice")
|
||||
parser.add_argument('--xtts-model', action='store', default="xtts", help="Set the xtts model for the voice (if using a custom model, also set model_path)")
|
||||
parser.add_argument('--model-path', action='store', default=None, help="Set the path for a custom xtts model")
|
||||
parser.add_argument('--config-path', action='store', default="config/voice_to_speaker.yaml", help="Set the config file path")
|
||||
parser.add_argument('--voice-path', action='store', default="voices", help="Set the default voices file path")
|
||||
parser.add_argument('--default-path', action='store', default="voice_to_speaker.default.yaml", help="Set the default config file path")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
basename = os.path.basename(args.sample)
|
||||
name_noext, ext = os.path.splitext(basename)
|
||||
|
||||
if not args.name:
|
||||
args.name = name_noext
|
||||
else:
|
||||
basename = f"{args.name}.wav"
|
||||
|
||||
dest_file = os.path.join(args.voice_path, basename)
|
||||
if args.sample != dest_file:
|
||||
shutil.copy2(args.sample, dest_file)
|
||||
|
||||
if not os.path.exists(args.config_path):
|
||||
shutil.copy2(args.default_path, args.config_path)
|
||||
|
||||
with open(args.config_path, 'r', encoding='utf8') as file:
|
||||
voice_map = yaml.safe_load(file)
|
||||
|
||||
model_conf = voice_map.get(args.openai_model, {})
|
||||
model_conf[args.name] = {
|
||||
'model': args.xtts_model,
|
||||
'speaker': os.path.join(args.voice_path, basename),
|
||||
'language': args.language,
|
||||
}
|
||||
if args.model_path:
|
||||
model_conf[args.name]['model_path'] = args.model_path
|
||||
voice_map[args.openai_model] = model_conf
|
||||
|
||||
with open(args.config_path, 'w', encoding='utf8') as ofile:
|
||||
yaml.safe_dump(voice_map, ofile, default_flow_style=False, allow_unicode=True)
|
||||
|
||||
print(f"Updated: {args.config_path}")
|
||||
print(f"Added voice: {args.openai_model}/{args.name}")
|
||||
print(f"Added section:")
|
||||
print(f"{args.openai_model}:")
|
||||
print(f" {args.name}:")
|
||||
print(f" model: {model_conf[args.name]['model']}")
|
||||
print(f" speaker: {model_conf[args.name]['speaker']}")
|
||||
print(f" language: {model_conf[args.name]['language']}")
|
||||
4
scripts/download_samples.sh
Executable file
4
scripts/download_samples.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
for i in alloy echo fable onyx nova shimmer; do
|
||||
[ ! -e "voices/$i.wav" ] && curl -s https://cdn.openai.com/API/docs/audio/$i.wav | ffmpeg -loglevel error -i - -ar 22050 -ac 1 voices/$i.wav
|
||||
done
|
||||
96
scripts/say.py
Executable file
96
scripts/say.py
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import os
|
||||
import atexit
|
||||
import tempfile
|
||||
import argparse
|
||||
|
||||
try:
|
||||
import dotenv
|
||||
dotenv.load_dotenv(override=True)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from playsound import playsound
|
||||
except ImportError:
|
||||
playsound = None
|
||||
|
||||
import openai
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Text to speech using the OpenAI API',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument("-m", "--model", type=str, default="tts-1", help="The model to use")#, choices=["tts-1", "tts-1-hd"])
|
||||
parser.add_argument("-v", "--voice", type=str, default="alloy", help="The voice of the speaker")#, choices=["alloy", "echo", "fable", "onyx", "nova", "shimmer"])
|
||||
parser.add_argument("-f", "--format", type=str, default="mp3", choices=["mp3", "aac", "opus", "flac"], help="The output audio format")
|
||||
parser.add_argument("-s", "--speed", type=float, default=1.0, help="playback speed, 0.25-4.0")
|
||||
parser.add_argument("-t", "--text", type=str, default=None, help="Provide text to read on the command line")
|
||||
parser.add_argument("-i", "--input", type=str, default=None, help="Read text from a file (default is to read from stdin)")
|
||||
|
||||
if playsound is None:
|
||||
parser.add_argument("-o", "--output", type=str, help="The filename to save the output to") # required
|
||||
parser.add_argument("-p", "--playsound", type=None, default=None, help="python playsound not found. pip install playsound")
|
||||
else:
|
||||
parser.add_argument("-o", "--output", type=str, default=None, help="The filename to save the output to") # not required
|
||||
parser.add_argument("-p", "--playsound", action="store_true", help="Play the audio")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args(sys.argv[1:])
|
||||
|
||||
if args.playsound and playsound is None:
|
||||
print("playsound module not found, audio will not be played, use -o <filename> to save output to a file. pip install playsound")
|
||||
sys.exit(1)
|
||||
|
||||
if not args.playsound and not args.output:
|
||||
print("Must select one of playsound (-p) or output file name (-o)")
|
||||
sys.exit(1)
|
||||
|
||||
if args.input is None and args.text is None:
|
||||
text = sys.stdin.read()
|
||||
elif args.text:
|
||||
text = args.text
|
||||
elif args.input:
|
||||
if os.path.exists(args.input):
|
||||
with open(args.input, 'r') as f:
|
||||
text = f.read()
|
||||
else:
|
||||
print(f"Warning! File not found: {args.input}\nFalling back to old behavior for -i")
|
||||
text = args.input
|
||||
|
||||
client = openai.OpenAI(
|
||||
# This part is not needed if you set these environment variables before import openai
|
||||
# export OPENAI_API_KEY=sk-11111111111
|
||||
# export OPENAI_BASE_URL=http://localhost:8000/v1
|
||||
api_key = os.environ.get("OPENAI_API_KEY", "sk-ip"),
|
||||
base_url = os.environ.get("OPENAI_BASE_URL", "http://localhost:8000/v1"),
|
||||
)
|
||||
|
||||
if args.playsound and args.output is None:
|
||||
_, args.output = tempfile.mkstemp(suffix='.wav')
|
||||
|
||||
def cleanup():
|
||||
os.unlink(args.output)
|
||||
|
||||
atexit.register(cleanup)
|
||||
|
||||
with client.audio.speech.with_streaming_response.create(
|
||||
model=args.model,
|
||||
voice=args.voice,
|
||||
speed=args.speed,
|
||||
response_format=args.format,
|
||||
input=text,
|
||||
) as response:
|
||||
response.stream_to_file(args.output)
|
||||
|
||||
if args.playsound:
|
||||
playsound(args.output)
|
||||
67
scripts/test_voices.sh
Executable file
67
scripts/test_voices.sh
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
|
||||
URL=${1:-http://localhost:8000/v1/audio/speech}
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1\",
|
||||
\"input\": \"I'm going to play you the original voice, followed by the piper voice and finally the X T T S version 2 voice\",
|
||||
\"voice\": \"echo\",
|
||||
\"speed\": 1.0
|
||||
}" | mpv --really-quiet -
|
||||
|
||||
for voice in alloy echo fable onyx nova shimmer ; do
|
||||
|
||||
echo $voice
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1\",
|
||||
\"input\": \"original\",
|
||||
\"voice\": \"echo\",
|
||||
\"speed\": 1.0
|
||||
}" | mpv --really-quiet -
|
||||
|
||||
curl -s https://cdn.openai.com/API/docs/audio/$voice.wav | mpv --really-quiet -
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1\",
|
||||
\"input\": \"The quick brown fox jumped over the lazy dog. This voice is called $voice, how do you like this voice?\",
|
||||
\"voice\": \"$voice\",
|
||||
\"speed\": 1.0
|
||||
}" | mpv --really-quiet -
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1-hd\",
|
||||
\"input\": \"The quick brown fox jumped over the lazy dog. This HD voice is called $voice, how do you like this voice?\",
|
||||
\"voice\": \"$voice\",
|
||||
\"speed\": 1.0
|
||||
}" | mpv --really-quiet -
|
||||
|
||||
done
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1\",
|
||||
\"input\": \"the slowest voice\",
|
||||
\"voice\": \"onyx\",
|
||||
\"speed\": 0.25
|
||||
}" | mpv --really-quiet -
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1-hd\",
|
||||
\"input\": \"the slowest HD voice\",
|
||||
\"voice\": \"onyx\",
|
||||
\"speed\": 0.25
|
||||
}" | mpv --really-quiet -
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1\",
|
||||
\"input\": \"And this is how fast it can go, the fastest voice\",
|
||||
\"voice\": \"nova\",
|
||||
\"speed\": 4.0
|
||||
}" | mpv --really-quiet -
|
||||
|
||||
curl -s $URL -H "Content-Type: application/json" -d "{
|
||||
\"model\": \"tts-1-hd\",
|
||||
\"input\": \"And this is how fast it can go, the fastest HD voice\",
|
||||
\"voice\": \"nova\",
|
||||
\"speed\": 4.0
|
||||
}" | mpv --really-quiet -
|
||||
Loading…
Add table
Add a link
Reference in a new issue