feat: US-003 - Creează catalogul de voci tts_voices.json cu seed inițial
- tts_voices.json (nou): seed 17 intrări — M1-M5/F1-F5 supertonic, Marius 1-3 și Paula 1-3 pockettts (state_path spre models/voices/*.safetensors), alba pockettts (voice_url) - .gitignore: +*.lock pentru sidecar-uri jsonlock - elimină approved-tasks.json.lock din git (sidecar gol, jsonlock îl recreează) - scris prin src/jsonlock.py write_locked() - gates: /review (backend) PASS, db (schema manuală) PASS, pytest 1043 passed (22 eșecuri preexistente neschimbate)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,9 @@ memory.bak/
|
||||
.use_openrouter
|
||||
.gstack/
|
||||
|
||||
# jsonlock sidecar files (src/jsonlock.py) — empty flock handles, never data
|
||||
*.lock
|
||||
|
||||
# Runtime state — auto-modified by dashboard/cron/heartbeat
|
||||
approved-tasks.json
|
||||
dashboard/status.json
|
||||
|
||||
98
tools/pocket_tts_add_voice.py
Executable file
98
tools/pocket_tts_add_voice.py
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export a pocket-tts voice state from a WAV sample into tts_voices.json.
|
||||
|
||||
Must run with <.venv-pockettts>/bin/python (torch + pocket-tts installed
|
||||
there), never with the main .venv:
|
||||
|
||||
.venv-pockettts/bin/python tools/pocket_tts_add_voice.py \\
|
||||
--wav /path/to/sample.wav --name "Marius"
|
||||
|
||||
Determines the next free index for --name by scanning tts_voices.json (an
|
||||
entry counts as free if it's absent, or present but its state_path file
|
||||
doesn't exist yet on disk), exports the voice state to
|
||||
models/voices/<slug>.safetensors, and registers it in tts_voices.json via
|
||||
src.jsonlock.
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
CATALOG_PATH = REPO_ROOT / "tts_voices.json"
|
||||
VOICES_DIR = REPO_ROOT / "models" / "voices"
|
||||
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from src.credential_store import get_secret # noqa: E402
|
||||
from src.jsonlock import read_locked, write_locked # noqa: E402
|
||||
|
||||
|
||||
def _slugify(name: str) -> str:
|
||||
return re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
|
||||
|
||||
|
||||
def _next_free_index(catalog: dict, name: str) -> int:
|
||||
n = 1
|
||||
while True:
|
||||
entry = catalog.get(f"{name} {n}")
|
||||
if entry is None:
|
||||
return n
|
||||
state_path = entry.get("state_path")
|
||||
if state_path and not (REPO_ROOT / state_path).exists():
|
||||
return n
|
||||
n += 1
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--wav", required=True, help="Path to source WAV sample")
|
||||
parser.add_argument("--name", required=True, help="Base voice name, e.g. 'Marius'")
|
||||
args = parser.parse_args()
|
||||
|
||||
wav_path = Path(args.wav).expanduser().resolve()
|
||||
if not wav_path.exists():
|
||||
print(f"[pocket_tts_add_voice] ERROR: wav not found: {wav_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
import os
|
||||
|
||||
hf_token = get_secret("hf_token")
|
||||
if not hf_token:
|
||||
print(
|
||||
"[pocket_tts_add_voice] ERROR: hf_token lipsește din keyring (service echo-core).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
os.environ["HF_TOKEN"] = hf_token
|
||||
|
||||
from pocket_tts import TTSModel, export_model_state
|
||||
|
||||
catalog = read_locked(str(CATALOG_PATH))
|
||||
index = _next_free_index(catalog, args.name)
|
||||
full_name = f"{args.name} {index}"
|
||||
slug = _slugify(full_name)
|
||||
VOICES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
export_path = VOICES_DIR / f"{slug}.safetensors"
|
||||
|
||||
print("[pocket_tts_add_voice] Loading pocket-tts model...")
|
||||
model = TTSModel.load_model()
|
||||
print(f"[pocket_tts_add_voice] Exporting voice state from {wav_path} -> {export_path}")
|
||||
state = model.get_state_for_audio_prompt(audio_conditioning=str(wav_path), truncate=True)
|
||||
export_model_state(state, str(export_path))
|
||||
|
||||
def _register(data: dict) -> dict:
|
||||
data[full_name] = {
|
||||
"engine": "pockettts",
|
||||
"state_path": f"models/voices/{slug}.safetensors",
|
||||
"owner": args.name,
|
||||
}
|
||||
return data
|
||||
|
||||
write_locked(str(CATALOG_PATH), _register)
|
||||
print(f"[pocket_tts_add_voice] Registered voice '{full_name}' -> {export_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
66
tts_voices.json
Normal file
66
tts_voices.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"M1": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"M2": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"M3": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"M4": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"M5": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"F1": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"F2": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"F3": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"F4": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"F5": {
|
||||
"engine": "supertonic"
|
||||
},
|
||||
"Marius 1": {
|
||||
"engine": "pockettts",
|
||||
"state_path": "models/voices/marius-1.safetensors",
|
||||
"owner": "Marius"
|
||||
},
|
||||
"Marius 2": {
|
||||
"engine": "pockettts",
|
||||
"state_path": "models/voices/marius-2.safetensors",
|
||||
"owner": "Marius"
|
||||
},
|
||||
"Marius 3": {
|
||||
"engine": "pockettts",
|
||||
"state_path": "models/voices/marius-3.safetensors",
|
||||
"owner": "Marius"
|
||||
},
|
||||
"Paula 1": {
|
||||
"engine": "pockettts",
|
||||
"state_path": "models/voices/paula-1.safetensors",
|
||||
"owner": "Paula"
|
||||
},
|
||||
"Paula 2": {
|
||||
"engine": "pockettts",
|
||||
"state_path": "models/voices/paula-2.safetensors",
|
||||
"owner": "Paula"
|
||||
},
|
||||
"Paula 3": {
|
||||
"engine": "pockettts",
|
||||
"state_path": "models/voices/paula-3.safetensors",
|
||||
"owner": "Paula"
|
||||
},
|
||||
"alba": {
|
||||
"engine": "pockettts",
|
||||
"voice_url": "alba"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user