switching to high quality piper tts and added label translations
This commit is contained in:
+138
@@ -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"}
|
||||
Reference in New Issue
Block a user