first commit
This commit is contained in:
commit
d4b1138482
13
README.txt
Normal file
13
README.txt
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
apt install python3-flask python3-opencv v4l-utils alsa-utils python3-pip mplayer
|
||||
|
||||
pip3 install --upgrade ultralytics --break-system-packages
|
||||
pip3 install --upgrade onnxruntime --break-system-packages
|
||||
|
||||
apt install -y piper
|
||||
apt install -y espeak-ng speech-dispatcher
|
||||
pip3 install --upgrade ultralytics vosk sounddevice
|
||||
|
||||
|
||||
apt install -y libportaudio2 portaudio19-dev libsndfile1
|
||||
pip3 install --upgrade sounddevice --break-system-packages
|
||||
BIN
__pycache__/config.cpython-313.pyc
Normal file
BIN
__pycache__/config.cpython-313.pyc
Normal file
Binary file not shown.
9
alfred.service
Normal file
9
alfred.service
Normal file
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description= Alfred service
|
||||
After=multi-user.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/python3 /data/app.py
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
562
app.py
Normal file
562
app.py
Normal file
@ -0,0 +1,562 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import cv2
|
||||
import time
|
||||
import atexit
|
||||
import threading
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os
|
||||
import queue
|
||||
import json
|
||||
from config import *
|
||||
from io import BytesIO
|
||||
from flask import Flask, Response, render_template, send_file, abort, request, jsonify
|
||||
|
||||
###############################################################################
|
||||
# Caméra / Flask / Audio
|
||||
###############################################################################
|
||||
def _cmd_ok(cmd, **kwargs):
|
||||
try:
|
||||
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
check=True, text=True, **kwargs)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def audio_backend_detect():
|
||||
if AUDIO_BACKEND in ('pactl', 'alsa'):
|
||||
return AUDIO_BACKEND
|
||||
if shutil.which('pactl'):
|
||||
info = _cmd_ok(['pactl', 'info'])
|
||||
if info and 'Server Name' in info.stdout:
|
||||
return 'pactl'
|
||||
if shutil.which('amixer'):
|
||||
return 'alsa'
|
||||
return 'none'
|
||||
|
||||
def get_volume():
|
||||
backend = audio_backend_detect()
|
||||
if backend == 'pactl':
|
||||
info = _cmd_ok(['pactl', 'get-sink-volume', '@DEFAULT_SINK@'])
|
||||
mute = _cmd_ok(['pactl', 'get-sink-mute', '@DEFAULT_SINK@'])
|
||||
if info and mute:
|
||||
line = info.stdout.strip().splitlines()[-1]
|
||||
perc = 0
|
||||
for tok in line.replace('%', ' % ').split():
|
||||
t = tok.strip().rstrip('%')
|
||||
if t.isdigit():
|
||||
val = int(t)
|
||||
if 0 <= val <= 200:
|
||||
perc = val
|
||||
break
|
||||
muted = 'yes' in mute.stdout.lower()
|
||||
return perc, muted, 'pactl'
|
||||
elif backend == 'alsa':
|
||||
out = _cmd_ok(['amixer', '-c', ALSA_CARD, 'get', ALSA_MIXER])
|
||||
if out:
|
||||
import re
|
||||
txt = out.stdout
|
||||
m = re.search(r'\[(\d{1,3})%\]', txt)
|
||||
perc = int(m.group(1)) if m else 0
|
||||
muted = '[off]' in txt
|
||||
return perc, muted, 'alsa'
|
||||
return 0, False, 'none'
|
||||
|
||||
def set_volume(level):
|
||||
try:
|
||||
level = int(level)
|
||||
except Exception:
|
||||
return False
|
||||
level = max(0, min(level, 100))
|
||||
backend = audio_backend_detect()
|
||||
if backend == 'pactl':
|
||||
_cmd_ok(['pactl', 'set-sink-volume', '@DEFAULT_SINK@', f'{level}%'])
|
||||
return True
|
||||
elif backend == 'alsa':
|
||||
_cmd_ok(['amixer', '-c', ALSA_CARD, 'set', ALSA_MIXER, f'{level}%'])
|
||||
return True
|
||||
return False
|
||||
|
||||
def change_volume(delta_percent):
|
||||
vol, muted, _ = get_volume()
|
||||
target = max(0, min(vol + int(delta_percent), 150))
|
||||
return set_volume(target)
|
||||
|
||||
def toggle_mute(force_state=None):
|
||||
backend = audio_backend_detect()
|
||||
if backend == 'pactl':
|
||||
if force_state is None:
|
||||
_cmd_ok(['pactl', 'set-sink-mute', '@DEFAULT_SINK@', 'toggle'])
|
||||
else:
|
||||
_cmd_ok(['pactl', 'set-sink-mute', '@DEFAULT_SINK@', '1' if force_state else '0'])
|
||||
return True
|
||||
elif backend == 'alsa':
|
||||
if force_state is None:
|
||||
_cmd_ok(['amixer', '-c', ALSA_CARD, 'set', ALSA_MIXER, 'toggle'])
|
||||
else:
|
||||
_cmd_ok(['amixer', '-c', ALSA_CARD, 'set', ALSA_MIXER, 'mute' if force_state else 'unmute'])
|
||||
return True
|
||||
return False
|
||||
|
||||
###############################################################################
|
||||
# Détection d’objets (YOLOv8 par défaut, fallback MobileNet-SSD)
|
||||
###############################################################################
|
||||
class ObjectDetector:
|
||||
def __init__(self, backend="yolo", conf=0.3, img_size=640):
|
||||
self.backend = backend
|
||||
self.conf = conf
|
||||
self.img_size = img_size
|
||||
self.ready = False
|
||||
try:
|
||||
from ultralytics import YOLO
|
||||
self.model = YOLO("yolov8n.pt")
|
||||
self.names = self.model.names
|
||||
self.ready = True
|
||||
except Exception as e:
|
||||
print("[DETECT] YOLO init error:", e)
|
||||
|
||||
def annotate(self, frame_bgr):
|
||||
if not self.ready:
|
||||
return frame_bgr
|
||||
h, w = frame_bgr.shape[:2]
|
||||
rlist = self.model.predict(source=frame_bgr, imgsz=self.img_size, conf=self.conf, verbose=False)
|
||||
if rlist:
|
||||
r = rlist[0]
|
||||
names = self.names if hasattr(self, "names") else {}
|
||||
for b in r.boxes:
|
||||
x1, y1, x2, y2 = map(int, b.xyxy[0].tolist())
|
||||
score = float(b.conf[0])
|
||||
cls = int(b.cls[0])
|
||||
label = f"{names.get(cls, str(cls))}"
|
||||
# label = f"{names.get(cls, str(cls))} {score:.2f}"
|
||||
cv2.rectangle(frame_bgr, (x1, y1), (x2, y2), (255,0,0), 1)
|
||||
(tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
|
||||
cv2.rectangle(frame_bgr, (x1, max(0, y1-th-6)), (x1+tw+8, y1), (255,0,0), -1)
|
||||
cv2.putText(frame_bgr, label, (x1+4, y1-6), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,0), 1, cv2.LINE_AA)
|
||||
return frame_bgr
|
||||
|
||||
###############################################################################
|
||||
# Caméra (thread capture + thread inférence)
|
||||
###############################################################################
|
||||
class Camera:
|
||||
def __init__(self, index=0, backend=None, width=None, height=None):
|
||||
self.cap = cv2.VideoCapture(index, backend or cv2.CAP_ANY)
|
||||
if not self.cap.isOpened():
|
||||
raise RuntimeError(f"Impossible d’ouvrir la caméra index={index}")
|
||||
if width and height:
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
|
||||
|
||||
self.detect_enabled = USE_DETECTION_DEFAULT
|
||||
self.show_fps = SHOW_FPS_DEFAULT
|
||||
self.detector = ObjectDetector(DETECTOR_BACKEND, CONF_THRES, IMG_SIZE)
|
||||
|
||||
self.lock = threading.Lock()
|
||||
self.frame = None
|
||||
self.annotated = None
|
||||
|
||||
self.cam_fps = 0.0
|
||||
self.inf_fps = 0.0
|
||||
self._last_cam_t = 0.0
|
||||
|
||||
self.running = True
|
||||
self.reader_t = threading.Thread(target=self._reader, daemon=True)
|
||||
self.reader_t.start()
|
||||
|
||||
self.infer_interval = 1.0 / max(1, INFER_FPS)
|
||||
self.infer_t = threading.Thread(target=self._infer_loop, daemon=True)
|
||||
self.infer_t.start()
|
||||
|
||||
def _ema(self, old, value, alpha=0.2):
|
||||
return value if old == 0.0 else (alpha*value + (1-alpha)*old)
|
||||
|
||||
def _draw_fps_overlay(self, img):
|
||||
if not self.show_fps: return
|
||||
text = f"CAM {self.cam_fps:.1f} FPS"
|
||||
if self.detect_enabled and self.detector and self.detector.ready:
|
||||
text += f" | INF {self.inf_fps:.1f} FPS"
|
||||
cv2.putText(img, text, (10,22), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,0), 3, cv2.LINE_AA)
|
||||
cv2.putText(img, text, (10,22), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 1, cv2.LINE_AA)
|
||||
|
||||
def _reader(self):
|
||||
while self.running:
|
||||
ret, frame = self.cap.read()
|
||||
if not ret:
|
||||
time.sleep(0.01); continue
|
||||
now = time.time()
|
||||
if self._last_cam_t != 0.0:
|
||||
inst = 1.0/max(1e-6, now-self._last_cam_t)
|
||||
self.cam_fps = self._ema(self.cam_fps, inst)
|
||||
self._last_cam_t = now
|
||||
with self.lock:
|
||||
self.frame = frame
|
||||
|
||||
def _infer_loop(self):
|
||||
last = 0.0
|
||||
while self.running:
|
||||
now = time.time()
|
||||
if now - last < self.infer_interval:
|
||||
time.sleep(0.003); continue
|
||||
last = now
|
||||
|
||||
with self.lock:
|
||||
frame = None if self.frame is None else self.frame.copy()
|
||||
detect_on = self.detect_enabled
|
||||
if frame is None: continue
|
||||
|
||||
t0 = time.time()
|
||||
out = self.detector.annotate(frame) if (detect_on and self.detector and self.detector.ready) else frame
|
||||
self._draw_fps_overlay(out)
|
||||
ok, buf = cv2.imencode(".jpg", out, [cv2.IMWRITE_JPEG_QUALITY, 85])
|
||||
if ok:
|
||||
with self.lock:
|
||||
self.annotated = buf.tobytes()
|
||||
dt = time.time()-t0
|
||||
if dt>0: self.inf_fps = self._ema(self.inf_fps, 1.0/dt)
|
||||
|
||||
def get_jpeg(self, quality=85):
|
||||
with self.lock:
|
||||
if self.annotated is not None:
|
||||
return self.annotated
|
||||
if self.frame is None:
|
||||
return None
|
||||
raw = self.frame.copy()
|
||||
if self.show_fps: self._draw_fps_overlay(raw)
|
||||
ok, buf = cv2.imencode(".jpg", raw, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
||||
return buf.tobytes() if ok else None
|
||||
|
||||
def set_detect_enabled(self, state: bool):
|
||||
with self.lock: self.detect_enabled = bool(state)
|
||||
|
||||
def set_show_fps(self, state: bool):
|
||||
with self.lock: self.show_fps = bool(state)
|
||||
|
||||
def get_stats(self):
|
||||
with self.lock:
|
||||
return {
|
||||
"detect_enabled": self.detect_enabled,
|
||||
"show_fps": self.show_fps,
|
||||
"backend": DETECTOR_BACKEND if (self.detector and self.detector.ready) else "none",
|
||||
"cam_fps": round(self.cam_fps, 2),
|
||||
"infer_fps": round(self.inf_fps, 2) if (self.detector and self.detector.ready and self.detect_enabled) else 0.0
|
||||
}
|
||||
|
||||
def release(self):
|
||||
self.running = False
|
||||
try:
|
||||
self.infer_t.join(timeout=1)
|
||||
self.reader_t.join(timeout=1)
|
||||
except Exception:
|
||||
pass
|
||||
self.cap.release()
|
||||
|
||||
camera = Camera(index=CAMERA_INDEX, backend=CAPTURE_BACKEND, width=WIDTH, height=HEIGHT)
|
||||
atexit.register(camera.release)
|
||||
|
||||
###############################################################################
|
||||
# TTS (Piper → espeak → spd-say)
|
||||
###############################################################################
|
||||
|
||||
def tts_backend_detect():
|
||||
if shutil.which("piper") and os.path.exists(PIPER_MODEL):
|
||||
return "piper"
|
||||
if shutil.which("espeak"):
|
||||
return "espeak"
|
||||
if shutil.which("spd-say"):
|
||||
return "spd-say"
|
||||
return "none"
|
||||
|
||||
def speak_text(text):
|
||||
# str.trim n'existe pas en Python, on garde une compat simple :
|
||||
text = (text or "").strip()
|
||||
if not text:
|
||||
return False, "none", "texte vide"
|
||||
if len(text) > 1000:
|
||||
text = text[:1000]
|
||||
|
||||
ab = audio_backend_detect()
|
||||
ttsb = tts_backend_detect()
|
||||
|
||||
try:
|
||||
if ttsb == "piper":
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
||||
wav_path = tmp.name
|
||||
p = _cmd_ok(["piper", "--model", PIPER_MODEL, "--output_file", wav_path], input=text)
|
||||
if not p:
|
||||
return False, ttsb, "échec piper synthèse"
|
||||
if ab == "pactl" and shutil.which("paplay"):
|
||||
play = _cmd_ok(["paplay", wav_path])
|
||||
elif shutil.which("aplay"):
|
||||
play = _cmd_ok(["aplay", "-q", wav_path])
|
||||
else:
|
||||
return False, ttsb, "lecteur (paplay/aplay) introuvable"
|
||||
try:
|
||||
os.unlink(wav_path)
|
||||
except Exception:
|
||||
pass
|
||||
return (play is not None), ttsb, None if play else "lecture échouée"
|
||||
|
||||
elif ttsb == "espeak":
|
||||
args = ["espeak", "-v", "fr", text]
|
||||
if ab == "pactl" and shutil.which("padsp"):
|
||||
args = ["padsp"] + args
|
||||
ok = _cmd_ok(args) is not None
|
||||
return ok, ttsb, None if ok else "espeak a échoué"
|
||||
|
||||
elif ttsb == "spd-say":
|
||||
ok = _cmd_ok(["spd-say", "-l", "fr", text]) is not None
|
||||
return ok, ttsb, None if ok else "spd-say a échoué"
|
||||
|
||||
else:
|
||||
return False, "none", "aucun backend TTS disponible"
|
||||
|
||||
except Exception as e:
|
||||
return False, ttsb, str(e)
|
||||
|
||||
###############################################################################
|
||||
# STT (Vosk – micro → texte)
|
||||
###############################################################################
|
||||
# Chemin MODELE Vosk (intégré selon ta demande)
|
||||
|
||||
stt_lock = threading.Lock()
|
||||
stt_listening = False
|
||||
stt_thread = None
|
||||
stt_partial = ""
|
||||
stt_last_final = ""
|
||||
stt_log = []
|
||||
stt_err = None
|
||||
|
||||
def _stt_worker():
|
||||
global stt_partial, stt_last_final, stt_listening, stt_err
|
||||
try:
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sounddevice as sd
|
||||
except Exception as e:
|
||||
stt_err = f"Import error: {e}"
|
||||
with stt_lock:
|
||||
stt_listening = False
|
||||
return
|
||||
|
||||
if not os.path.exists(VOSK_MODEL_PATH):
|
||||
stt_err = f"Modèle Vosk introuvable: {VOSK_MODEL_PATH}"
|
||||
with stt_lock:
|
||||
stt_listening = False
|
||||
return
|
||||
|
||||
try:
|
||||
model = Model(VOSK_MODEL_PATH)
|
||||
rec = KaldiRecognizer(model, VOSK_SAMPLE_RATE)
|
||||
except Exception as e:
|
||||
stt_err = f"Init Vosk error: {e}"
|
||||
with stt_lock:
|
||||
stt_listening = False
|
||||
return
|
||||
|
||||
stt_err = None
|
||||
q = queue.Queue()
|
||||
|
||||
def audio_cb(indata, frames, time_info, status):
|
||||
if status:
|
||||
pass
|
||||
q.put(bytes(indata))
|
||||
|
||||
try:
|
||||
with sd.RawInputStream(samplerate=VOSK_SAMPLE_RATE, blocksize=8000,
|
||||
dtype='int16', channels=1, callback=audio_cb):
|
||||
while True:
|
||||
with stt_lock:
|
||||
if not stt_listening:
|
||||
break
|
||||
data = q.get()
|
||||
if rec.AcceptWaveform(data):
|
||||
res = json.loads(rec.Result() or "{}")
|
||||
txt = (res.get("text") or "").strip()
|
||||
if txt:
|
||||
stt_last_final = txt
|
||||
stt_log.append(txt)
|
||||
if len(stt_log) > 20:
|
||||
stt_log.pop(0)
|
||||
stt_partial = ""
|
||||
else:
|
||||
pres = json.loads(rec.PartialResult() or "{}")
|
||||
stt_partial = pres.get("partial", "")
|
||||
except Exception as e:
|
||||
stt_err = f"Stream error: {e}"
|
||||
finally:
|
||||
with stt_lock:
|
||||
stt_listening = False
|
||||
|
||||
def stt_start():
|
||||
global stt_listening, stt_thread, stt_err
|
||||
with stt_lock:
|
||||
if stt_listening:
|
||||
return True, None
|
||||
stt_listening = True
|
||||
stt_err = None
|
||||
th = threading.Thread(target=_stt_worker, daemon=True)
|
||||
th.start()
|
||||
stt_thread = th
|
||||
return True, None
|
||||
|
||||
def stt_stop():
|
||||
global stt_listening, stt_thread
|
||||
with stt_lock:
|
||||
stt_listening = False
|
||||
if stt_thread:
|
||||
stt_thread.join(timeout=1.5)
|
||||
stt_thread = None
|
||||
return True
|
||||
|
||||
def stt_status():
|
||||
with stt_lock:
|
||||
return {
|
||||
"listening": stt_listening,
|
||||
"partial": stt_partial,
|
||||
"last_final": stt_last_final,
|
||||
"error": stt_err,
|
||||
"model": VOSK_MODEL_PATH if os.path.exists(VOSK_MODEL_PATH) else "(absent)"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Flask
|
||||
###############################################################################
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
vol, muted, backend = get_volume()
|
||||
stats = camera.get_stats()
|
||||
return render_template(
|
||||
"index.html",
|
||||
initial_volume=vol,
|
||||
initial_muted=muted,
|
||||
audio_backend=backend,
|
||||
alsa_card=ALSA_CARD,
|
||||
alsa_mixer=ALSA_MIXER,
|
||||
detect_enabled=stats["detect_enabled"],
|
||||
show_fps=stats["show_fps"],
|
||||
detector_backend=stats["backend"],
|
||||
tts_backend=tts_backend_detect(),
|
||||
piper_model=os.path.basename(PIPER_MODEL) if os.path.exists(PIPER_MODEL) else "(aucun)",
|
||||
stt_model=os.path.basename(VOSK_MODEL_PATH) if os.path.exists(VOSK_MODEL_PATH) else "(absent)"
|
||||
)
|
||||
|
||||
# --------- Flux vidéo
|
||||
def mjpeg_generator():
|
||||
frame_interval = (1.0 / FPS_LIMIT) if FPS_LIMIT and FPS_LIMIT > 0 else 0.0
|
||||
last_time = 0.0
|
||||
while True:
|
||||
if frame_interval:
|
||||
now = time.time()
|
||||
delta = now - last_time
|
||||
if delta < frame_interval:
|
||||
time.sleep(frame_interval - delta)
|
||||
last_time = time.time()
|
||||
jpg = camera.get_jpeg()
|
||||
if jpg is None:
|
||||
time.sleep(0.02); continue
|
||||
yield (b"--frame\r\n"
|
||||
b"Content-Type: image/jpeg\r\n"
|
||||
b"Content-Length: " + str(len(jpg)).encode() + b"\r\n\r\n" +
|
||||
jpg + b"\r\n")
|
||||
|
||||
@app.route("/video_feed")
|
||||
def video_feed():
|
||||
return Response(mjpeg_generator(), mimetype="multipart/x-mixed-replace; boundary=frame")
|
||||
|
||||
@app.route("/snapshot")
|
||||
def snapshot():
|
||||
jpg = camera.get_jpeg(quality=95)
|
||||
if jpg is None:
|
||||
abort(503, description="Aucune image disponible")
|
||||
return send_file(BytesIO(jpg), mimetype="image/jpeg", as_attachment=False, download_name="snapshot.jpg")
|
||||
|
||||
# --------- Volume
|
||||
@app.get("/volume")
|
||||
def api_get_volume():
|
||||
vol, muted, backend = get_volume()
|
||||
return jsonify({"volume": vol, "muted": muted, "backend": backend})
|
||||
|
||||
@app.post("/volume/set")
|
||||
def api_set_volume():
|
||||
level = request.args.get("level")
|
||||
if level is None:
|
||||
return jsonify({"ok": False, "error": "level manquant"}), 400
|
||||
ok = set_volume(level)
|
||||
vol, muted, backend = get_volume()
|
||||
return jsonify({"ok": ok, "volume": vol, "muted": muted, "backend": backend})
|
||||
|
||||
@app.post("/volume/up")
|
||||
def api_volume_up():
|
||||
ok = change_volume(+VOLUME_STEP)
|
||||
vol, muted, backend = get_volume()
|
||||
return jsonify({"ok": ok, "volume": vol, "muted": muted, "backend": backend})
|
||||
|
||||
@app.post("/volume/down")
|
||||
def api_volume_down():
|
||||
ok = change_volume(-VOLUME_STEP)
|
||||
vol, muted, backend = get_volume()
|
||||
return jsonify({"ok": ok, "volume": vol, "muted": muted, "backend": backend})
|
||||
|
||||
@app.post("/volume/mute")
|
||||
def api_volume_mute():
|
||||
state = request.args.get("state")
|
||||
force = None
|
||||
if state in ("on","true","1"): force = True
|
||||
elif state in ("off","false","0"): force = False
|
||||
ok = toggle_mute(force)
|
||||
vol, muted, backend = get_volume()
|
||||
return jsonify({"ok": ok, "volume": vol, "muted": muted, "backend": backend})
|
||||
|
||||
# --------- Détection / FPS
|
||||
@app.get("/stats")
|
||||
def api_stats():
|
||||
return jsonify(camera.get_stats())
|
||||
|
||||
@app.post("/detect/toggle")
|
||||
def api_detect_toggle():
|
||||
st = camera.get_stats()
|
||||
camera.set_detect_enabled(not st["detect_enabled"])
|
||||
return jsonify({"ok": True, **camera.get_stats()})
|
||||
|
||||
@app.post("/fps/show")
|
||||
def api_fps_show():
|
||||
state = request.args.get("state")
|
||||
if state is None:
|
||||
return jsonify({"ok": False, "error": "state manquant (true/false)"}), 400
|
||||
val = state.lower() in ("1","true","on","yes")
|
||||
camera.set_show_fps(val)
|
||||
return jsonify({"ok": True, **camera.get_stats()})
|
||||
|
||||
# --------- TTS
|
||||
@app.post("/tts/say")
|
||||
def api_tts_say():
|
||||
data = request.get_json(silent=True) or {}
|
||||
text = data.get("text") or request.form.get("text") or ""
|
||||
ok, backend, err = speak_text(text)
|
||||
return jsonify({"ok": ok, "backend": backend, "error": err})
|
||||
|
||||
# --------- STT (Vosk)
|
||||
@app.get("/stt/status")
|
||||
def api_stt_status():
|
||||
return jsonify(stt_status())
|
||||
|
||||
@app.post("/stt/start")
|
||||
def api_stt_start():
|
||||
ok, err = stt_start()
|
||||
st = stt_status()
|
||||
st["ok"] = ok
|
||||
if err: st["error"] = err
|
||||
return jsonify(st)
|
||||
|
||||
@app.post("/stt/stop")
|
||||
def api_stt_stop():
|
||||
ok = stt_stop()
|
||||
st = stt_status()
|
||||
st["ok"] = ok
|
||||
return jsonify(st)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, threaded=True)
|
||||
26
config.py
Normal file
26
config.py
Normal file
@ -0,0 +1,26 @@
|
||||
import cv2
|
||||
|
||||
# Configuration de la Caméra
|
||||
CAMERA_INDEX = 0
|
||||
WIDTH, HEIGHT = 1280, 720
|
||||
FPS_LIMIT = 30
|
||||
CAPTURE_BACKEND = cv2.CAP_V4L2 # Linux
|
||||
|
||||
# Configuration de l'Audio
|
||||
AUDIO_BACKEND = 'auto' # 'auto' | 'pactl' | 'alsa'
|
||||
ALSA_CARD = '1'
|
||||
ALSA_MIXER = 'Master'
|
||||
VOLUME_STEP = 5
|
||||
|
||||
# Configuration de la détéction d'objet (Yolo v8)
|
||||
USE_DETECTION_DEFAULT = True
|
||||
DETECTOR_BACKEND = "yolo"
|
||||
CONF_THRES = 0.30
|
||||
IMG_SIZE = 640
|
||||
INFER_FPS = 8
|
||||
SHOW_FPS_DEFAULT = True
|
||||
|
||||
# Configuration TTS (Piper) et STT (Vosk)
|
||||
PIPER_MODEL = "/data/piper_model/fr_FR-siwis-low.onnx"
|
||||
VOSK_MODEL_PATH = "/data/vosk-model/vosk-model-fr-0.22"
|
||||
VOSK_SAMPLE_RATE = 16000
|
||||
BIN
piper/piper/espeak-ng-data/af_dict
Normal file
BIN
piper/piper/espeak-ng-data/af_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/am_dict
Normal file
BIN
piper/piper/espeak-ng-data/am_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/an_dict
Normal file
BIN
piper/piper/espeak-ng-data/an_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ar_dict
Normal file
BIN
piper/piper/espeak-ng-data/ar_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/as_dict
Normal file
BIN
piper/piper/espeak-ng-data/as_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/az_dict
Normal file
BIN
piper/piper/espeak-ng-data/az_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ba_dict
Normal file
BIN
piper/piper/espeak-ng-data/ba_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/be_dict
Normal file
BIN
piper/piper/espeak-ng-data/be_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/bg_dict
Normal file
BIN
piper/piper/espeak-ng-data/bg_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/bn_dict
Normal file
BIN
piper/piper/espeak-ng-data/bn_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/bpy_dict
Normal file
BIN
piper/piper/espeak-ng-data/bpy_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/bs_dict
Normal file
BIN
piper/piper/espeak-ng-data/bs_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ca_dict
Normal file
BIN
piper/piper/espeak-ng-data/ca_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/chr_dict
Normal file
BIN
piper/piper/espeak-ng-data/chr_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/cmn_dict
Normal file
BIN
piper/piper/espeak-ng-data/cmn_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/cs_dict
Normal file
BIN
piper/piper/espeak-ng-data/cs_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/cv_dict
Normal file
BIN
piper/piper/espeak-ng-data/cv_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/cy_dict
Normal file
BIN
piper/piper/espeak-ng-data/cy_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/da_dict
Normal file
BIN
piper/piper/espeak-ng-data/da_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/de_dict
Normal file
BIN
piper/piper/espeak-ng-data/de_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/el_dict
Normal file
BIN
piper/piper/espeak-ng-data/el_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/en_dict
Normal file
BIN
piper/piper/espeak-ng-data/en_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/eo_dict
Normal file
BIN
piper/piper/espeak-ng-data/eo_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/es_dict
Normal file
BIN
piper/piper/espeak-ng-data/es_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/et_dict
Normal file
BIN
piper/piper/espeak-ng-data/et_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/eu_dict
Normal file
BIN
piper/piper/espeak-ng-data/eu_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/fa_dict
Normal file
BIN
piper/piper/espeak-ng-data/fa_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/fi_dict
Normal file
BIN
piper/piper/espeak-ng-data/fi_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/fr_dict
Normal file
BIN
piper/piper/espeak-ng-data/fr_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ga_dict
Normal file
BIN
piper/piper/espeak-ng-data/ga_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/gd_dict
Normal file
BIN
piper/piper/espeak-ng-data/gd_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/gn_dict
Normal file
BIN
piper/piper/espeak-ng-data/gn_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/grc_dict
Normal file
BIN
piper/piper/espeak-ng-data/grc_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/gu_dict
Normal file
BIN
piper/piper/espeak-ng-data/gu_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/hak_dict
Normal file
BIN
piper/piper/espeak-ng-data/hak_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/haw_dict
Normal file
BIN
piper/piper/espeak-ng-data/haw_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/he_dict
Normal file
BIN
piper/piper/espeak-ng-data/he_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/hi_dict
Normal file
BIN
piper/piper/espeak-ng-data/hi_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/hr_dict
Normal file
BIN
piper/piper/espeak-ng-data/hr_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ht_dict
Normal file
BIN
piper/piper/espeak-ng-data/ht_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/hu_dict
Normal file
BIN
piper/piper/espeak-ng-data/hu_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/hy_dict
Normal file
BIN
piper/piper/espeak-ng-data/hy_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ia_dict
Normal file
BIN
piper/piper/espeak-ng-data/ia_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/id_dict
Normal file
BIN
piper/piper/espeak-ng-data/id_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/intonations
Normal file
BIN
piper/piper/espeak-ng-data/intonations
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/io_dict
Normal file
BIN
piper/piper/espeak-ng-data/io_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/is_dict
Normal file
BIN
piper/piper/espeak-ng-data/is_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/it_dict
Normal file
BIN
piper/piper/espeak-ng-data/it_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ja_dict
Normal file
BIN
piper/piper/espeak-ng-data/ja_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/jbo_dict
Normal file
BIN
piper/piper/espeak-ng-data/jbo_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ka_dict
Normal file
BIN
piper/piper/espeak-ng-data/ka_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/kk_dict
Normal file
BIN
piper/piper/espeak-ng-data/kk_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/kl_dict
Normal file
BIN
piper/piper/espeak-ng-data/kl_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/kn_dict
Normal file
BIN
piper/piper/espeak-ng-data/kn_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ko_dict
Normal file
BIN
piper/piper/espeak-ng-data/ko_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/kok_dict
Normal file
BIN
piper/piper/espeak-ng-data/kok_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ku_dict
Normal file
BIN
piper/piper/espeak-ng-data/ku_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/ky_dict
Normal file
BIN
piper/piper/espeak-ng-data/ky_dict
Normal file
Binary file not shown.
BIN
piper/piper/espeak-ng-data/la_dict
Normal file
BIN
piper/piper/espeak-ng-data/la_dict
Normal file
Binary file not shown.
8
piper/piper/espeak-ng-data/lang/aav/vi
Normal file
8
piper/piper/espeak-ng-data/lang/aav/vi
Normal file
@ -0,0 +1,8 @@
|
||||
name Vietnamese (Northern)
|
||||
language vi
|
||||
|
||||
words 1 2
|
||||
pitch 95 175
|
||||
|
||||
|
||||
tone 100 225 800 100 2000 50 5400 75 8000 200
|
||||
9
piper/piper/espeak-ng-data/lang/aav/vi-VN-x-central
Normal file
9
piper/piper/espeak-ng-data/lang/aav/vi-VN-x-central
Normal file
@ -0,0 +1,9 @@
|
||||
name Vietnamese (Central)
|
||||
language vi-vn-x-central
|
||||
phonemes vi-hue
|
||||
dictrules 1
|
||||
|
||||
words 1
|
||||
pitch 82 118 //80 118
|
||||
voicing 90 //18
|
||||
flutter 20
|
||||
9
piper/piper/espeak-ng-data/lang/aav/vi-VN-x-south
Normal file
9
piper/piper/espeak-ng-data/lang/aav/vi-VN-x-south
Normal file
@ -0,0 +1,9 @@
|
||||
name Vietnamese (Southern)
|
||||
language vi-vn-x-south
|
||||
phonemes vi-sgn
|
||||
dictrules 2
|
||||
|
||||
words 1
|
||||
pitch 82 118 //80 118
|
||||
voicing 90 //18
|
||||
flutter 20
|
||||
4
piper/piper/espeak-ng-data/lang/art/eo
Normal file
4
piper/piper/espeak-ng-data/lang/art/eo
Normal file
@ -0,0 +1,4 @@
|
||||
name Esperanto
|
||||
language eo
|
||||
|
||||
apostrophe 2
|
||||
2
piper/piper/espeak-ng-data/lang/art/ia
Normal file
2
piper/piper/espeak-ng-data/lang/art/ia
Normal file
@ -0,0 +1,2 @@
|
||||
name Interlingua
|
||||
language ia
|
||||
5
piper/piper/espeak-ng-data/lang/art/io
Normal file
5
piper/piper/espeak-ng-data/lang/art/io
Normal file
@ -0,0 +1,5 @@
|
||||
name Ido
|
||||
language io
|
||||
phonemes eo
|
||||
status testing
|
||||
|
||||
4
piper/piper/espeak-ng-data/lang/art/jbo
Normal file
4
piper/piper/espeak-ng-data/lang/art/jbo
Normal file
@ -0,0 +1,4 @@
|
||||
name Lojban
|
||||
language jbo
|
||||
|
||||
speed 80 // speed adjustment, percentage
|
||||
8
piper/piper/espeak-ng-data/lang/art/lfn
Normal file
8
piper/piper/espeak-ng-data/lang/art/lfn
Normal file
@ -0,0 +1,8 @@
|
||||
name Lingua Franca Nova
|
||||
language lfn
|
||||
|
||||
phonemes base2
|
||||
l_unpronouncable 0
|
||||
numbers 2 3
|
||||
|
||||
stressLength 150 140 180 180 0 0 200 200
|
||||
5
piper/piper/espeak-ng-data/lang/art/piqd
Normal file
5
piper/piper/espeak-ng-data/lang/art/piqd
Normal file
@ -0,0 +1,5 @@
|
||||
name Klingon
|
||||
language piqd
|
||||
status testing
|
||||
stressRule 3
|
||||
|
||||
7
piper/piper/espeak-ng-data/lang/art/py
Normal file
7
piper/piper/espeak-ng-data/lang/art/py
Normal file
@ -0,0 +1,7 @@
|
||||
name Pyash
|
||||
language py
|
||||
maintainer Logan Streondj <logan@liberit.ca>
|
||||
status testing
|
||||
|
||||
speed 80 // speed adjustment, percentage
|
||||
stressRule 0
|
||||
6
piper/piper/espeak-ng-data/lang/art/qdb
Normal file
6
piper/piper/espeak-ng-data/lang/art/qdb
Normal file
@ -0,0 +1,6 @@
|
||||
name Lang Belta
|
||||
language qdb
|
||||
|
||||
numbers 4 3
|
||||
|
||||
replace 1 t ?
|
||||
4
piper/piper/espeak-ng-data/lang/art/qya
Normal file
4
piper/piper/espeak-ng-data/lang/art/qya
Normal file
@ -0,0 +1,4 @@
|
||||
name Quenya
|
||||
language qya
|
||||
stressRule 2
|
||||
// rule=penultimate, with qya_rules for light penultimate syllables to move primary stress to the preceding (antepenultimate) syllable
|
||||
4
piper/piper/espeak-ng-data/lang/art/sjn
Normal file
4
piper/piper/espeak-ng-data/lang/art/sjn
Normal file
@ -0,0 +1,4 @@
|
||||
name Sindarin
|
||||
language sjn
|
||||
stressRule 2
|
||||
// rule=penultimate, with sjn_rules for light penultimate syllables to move primary stress to the preceding (antepenultimate) syllable
|
||||
6
piper/piper/espeak-ng-data/lang/azc/nci
Normal file
6
piper/piper/espeak-ng-data/lang/azc/nci
Normal file
@ -0,0 +1,6 @@
|
||||
name Nahuatl (Classical)
|
||||
language nci
|
||||
|
||||
intonation 3
|
||||
stressRule 2
|
||||
stressLength 190 190 200 200 0 0 220 240
|
||||
2
piper/piper/espeak-ng-data/lang/bat/lt
Normal file
2
piper/piper/espeak-ng-data/lang/bat/lt
Normal file
@ -0,0 +1,2 @@
|
||||
name Lithuanian
|
||||
language lt
|
||||
12
piper/piper/espeak-ng-data/lang/bat/ltg
Normal file
12
piper/piper/espeak-ng-data/lang/bat/ltg
Normal file
@ -0,0 +1,12 @@
|
||||
name Latgalian
|
||||
language ltg
|
||||
maintainer Valdis Vitolins <valdis.vitolins@odo.lv>
|
||||
status testing
|
||||
phonemes lv
|
||||
dictionary lv
|
||||
dictrules 2 // Setting for Latgalian pronunciation
|
||||
words 0 2
|
||||
pitch 64 118
|
||||
tone 60 150 204 100 400 255 700 10 3000 255
|
||||
stressAmp 12 10 8 8 0 0 15 16
|
||||
stressLength 160 140 200 140 0 0 240 160
|
||||
9
piper/piper/espeak-ng-data/lang/bat/lv
Normal file
9
piper/piper/espeak-ng-data/lang/bat/lv
Normal file
@ -0,0 +1,9 @@
|
||||
name Latvian
|
||||
language lv
|
||||
maintainer Valdis Vitolins <valdis.vitolins@odo.lv>
|
||||
status mature
|
||||
words 0 2
|
||||
pitch 67 123
|
||||
tone 60 150 204 100 400 255 700 10 3000 255
|
||||
stressAmp 11 8 11 9 0 0 14 12
|
||||
stressLength 160 120 200 130 0 0 230 180
|
||||
4
piper/piper/espeak-ng-data/lang/bnt/sw
Normal file
4
piper/piper/espeak-ng-data/lang/bnt/sw
Normal file
@ -0,0 +1,4 @@
|
||||
name Swahili
|
||||
language sw
|
||||
|
||||
status testing
|
||||
4
piper/piper/espeak-ng-data/lang/bnt/tn
Normal file
4
piper/piper/espeak-ng-data/lang/bnt/tn
Normal file
@ -0,0 +1,4 @@
|
||||
name Setswana
|
||||
language tn
|
||||
|
||||
status testing
|
||||
3
piper/piper/espeak-ng-data/lang/ccs/ka
Normal file
3
piper/piper/espeak-ng-data/lang/ccs/ka
Normal file
@ -0,0 +1,3 @@
|
||||
name Georgian
|
||||
language ka
|
||||
lowercaseSentence // A period followed by a lowercase letter is considered a sentence (mkhedruli)
|
||||
4
piper/piper/espeak-ng-data/lang/cel/cy
Normal file
4
piper/piper/espeak-ng-data/lang/cel/cy
Normal file
@ -0,0 +1,4 @@
|
||||
name Welsh
|
||||
language cy
|
||||
|
||||
intonation 4
|
||||
4
piper/piper/espeak-ng-data/lang/cel/ga
Normal file
4
piper/piper/espeak-ng-data/lang/cel/ga
Normal file
@ -0,0 +1,4 @@
|
||||
name Gaelic (Irish)
|
||||
language ga
|
||||
|
||||
dictrules 1 // fix for eclipsis
|
||||
4
piper/piper/espeak-ng-data/lang/cel/gd
Normal file
4
piper/piper/espeak-ng-data/lang/cel/gd
Normal file
@ -0,0 +1,4 @@
|
||||
name Gaelic (Scottish)
|
||||
language gd
|
||||
|
||||
status testing
|
||||
4
piper/piper/espeak-ng-data/lang/cus/om
Normal file
4
piper/piper/espeak-ng-data/lang/cus/om
Normal file
@ -0,0 +1,4 @@
|
||||
name Oromo
|
||||
language om
|
||||
|
||||
status testing
|
||||
5
piper/piper/espeak-ng-data/lang/dra/kn
Normal file
5
piper/piper/espeak-ng-data/lang/dra/kn
Normal file
@ -0,0 +1,5 @@
|
||||
name Kannada
|
||||
language kn
|
||||
|
||||
intonation 2
|
||||
//consonants 80
|
||||
5
piper/piper/espeak-ng-data/lang/dra/ml
Normal file
5
piper/piper/espeak-ng-data/lang/dra/ml
Normal file
@ -0,0 +1,5 @@
|
||||
name Malayalam
|
||||
language ml
|
||||
|
||||
intonation 2
|
||||
//consonants 80
|
||||
5
piper/piper/espeak-ng-data/lang/dra/ta
Normal file
5
piper/piper/espeak-ng-data/lang/dra/ta
Normal file
@ -0,0 +1,5 @@
|
||||
name Tamil
|
||||
language ta
|
||||
|
||||
intonation 2
|
||||
consonants 80
|
||||
7
piper/piper/espeak-ng-data/lang/dra/te
Normal file
7
piper/piper/espeak-ng-data/lang/dra/te
Normal file
@ -0,0 +1,7 @@
|
||||
name Telugu
|
||||
language te
|
||||
|
||||
status testing
|
||||
|
||||
intonation 2
|
||||
//consonants 80
|
||||
3
piper/piper/espeak-ng-data/lang/esx/kl
Normal file
3
piper/piper/espeak-ng-data/lang/esx/kl
Normal file
@ -0,0 +1,3 @@
|
||||
name Greenlandic
|
||||
language kl
|
||||
|
||||
5
piper/piper/espeak-ng-data/lang/eu
Normal file
5
piper/piper/espeak-ng-data/lang/eu
Normal file
@ -0,0 +1,5 @@
|
||||
name Basque
|
||||
language eu
|
||||
|
||||
status testing
|
||||
stressRule 15
|
||||
4
piper/piper/espeak-ng-data/lang/gmq/da
Normal file
4
piper/piper/espeak-ng-data/lang/gmq/da
Normal file
@ -0,0 +1,4 @@
|
||||
name Danish
|
||||
language da
|
||||
|
||||
tunes s2 c2 q2 e2
|
||||
2
piper/piper/espeak-ng-data/lang/gmq/is
Normal file
2
piper/piper/espeak-ng-data/lang/gmq/is
Normal file
@ -0,0 +1,2 @@
|
||||
name Icelandic
|
||||
language is
|
||||
7
piper/piper/espeak-ng-data/lang/gmq/nb
Normal file
7
piper/piper/espeak-ng-data/lang/gmq/nb
Normal file
@ -0,0 +1,7 @@
|
||||
name Norwegian Bokmål
|
||||
language nb
|
||||
language no
|
||||
phonemes no
|
||||
dictionary no
|
||||
|
||||
intonation 4
|
||||
2
piper/piper/espeak-ng-data/lang/gmq/sv
Normal file
2
piper/piper/espeak-ng-data/lang/gmq/sv
Normal file
@ -0,0 +1,2 @@
|
||||
name Swedish
|
||||
language sv
|
||||
8
piper/piper/espeak-ng-data/lang/gmw/af
Normal file
8
piper/piper/espeak-ng-data/lang/gmw/af
Normal file
@ -0,0 +1,8 @@
|
||||
name Afrikaans
|
||||
language af
|
||||
|
||||
maintainer Christo de Klerk <christodeklerk@gmail.com>
|
||||
status mature
|
||||
|
||||
roughness 0
|
||||
pitch 63 120
|
||||
3
piper/piper/espeak-ng-data/lang/gmw/de
Normal file
3
piper/piper/espeak-ng-data/lang/gmw/de
Normal file
@ -0,0 +1,3 @@
|
||||
name German
|
||||
language de
|
||||
tunes s4 c1 q4 e1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user