switching to high quality piper tts and added label translations

This commit is contained in:
Matthias Hinrichs
2026-01-29 23:48:19 +01:00
commit d80c619df9
3934 changed files with 1451600 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
from fastapi import FastAPI, UploadFile, File, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, FileResponse
import cv2
import os
import time
import socket
import sys
from .vision import analyze_image_with_yolo
app = FastAPI(title="Home-Bro Brain")
import subprocess
def play_voice(text: str):
"""Generates speech using Piper and plays it on Mac."""
def _speak():
try:
print(f"DEBUG: Piper TTS für: '{text}'")
# Pfad zum Piper Binary im Python 3.12 venv (funktioniert auf M-Mac)
piper_path = "./venv_piper/bin/piper"
model_path = "./piper/models/de_DE-thorsten-high.onnx"
# Piper auf Mac nutzt afplay für die Ausgabe
# Wir streamen stdout direkt zu afplay
command = (
f"echo '{text}' | "
f"{piper_path} --model {model_path} --output-raw | "
f"afplay --channels 1 --rate 22050 --format linear_pcm --bits 16"
)
# Da afplay kein 'raw' Format direkt von stdin im richtigen Takt nimmt,
# ist es sicherer, kurz ein WAV zu schreiben.
wav_path = "snapshots/speech.wav"
gen_command = (
f"echo '{text}' | "
f"{piper_path} --model {model_path} --output_file {wav_path}"
)
subprocess.run(gen_command, shell=True, check=True)
if sys.platform == "darwin":
subprocess.run(["afplay", wav_path], check=True)
except Exception as e:
print(f"Piper TTS Fehler: {e}")
# In einem separaten Thread ausführen
import threading
threading.Thread(target=_speak).start()
# Statische Dateien (Frontend)
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/snapshots", StaticFiles(directory="snapshots"), name="snapshots")
# In-Memory Speicher für den letzten Status
latest_status = {
"room": "Warten...",
"comment": "Noch keine Daten empfangen.",
"timestamp": None,
"image_url": None
}
@app.get("/", response_class=HTMLResponse)
async def get_index():
return FileResponse("static/index.html")
@app.get("/api/latest")
async def get_latest():
return latest_status
@app.get("/api/info")
async def get_info():
hostname = socket.gethostname()
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()
except Exception:
local_ip = "127.0.0.1"
return {
"hostname": hostname,
"ip": local_ip,
"port": 8000
}
@app.post("/analyze/pi")
async def analyze_pi(file: UploadFile = File(...)):
global latest_status
# Bild vom Pi speichern
path = "snapshots/pi_last.jpg"
content = await file.read()
with open(path, "wb") as f:
f.write(content)
result = analyze_image_with_yolo(path)
play_voice(result)
# Status aktualisieren
latest_status = {
"room": "Wohnzimmer (Pi)",
"comment": result,
"timestamp": time.strftime("%H:%M:%S"),
"image_url": f"/snapshots/pi_last.jpg?t={int(time.time())}" # Cache busting
}
return {"room": "Wohnzimmer", "comment": result}
@app.get("/analyze/tapo/{room_name}")
async def analyze_tapo(room_name: str, ip: str):
global latest_status
# RTSP Zugriff auf die Tapo
user = os.getenv("TAPO_USER")
pw = os.getenv("TAPO_PASSWORD")
url = f"rtsp://{user}:{pw}@{ip}:554/stream1"
cap = cv2.VideoCapture(url)
ret, frame = cap.read()
if ret:
path = f"snapshots/{room_name}_tapo.jpg"
cv2.imwrite(path, frame)
result = analyze_image_with_yolo(path)
play_voice(result)
cap.release()
latest_status = {
"room": room_name,
"comment": result,
"timestamp": time.strftime("%H:%M:%S"),
"image_url": f"/snapshots/{room_name}_tapo.jpg?t={int(time.time())}"
}
return {"room": room_name, "comment": result}
cap.release()
return {"error": "Tapo nicht erreichbar"}