Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-09-28 15:54:49 +02:00
parent 0fb66f2cee
commit 01f2a92a84
6 changed files with 118 additions and 3 deletions
+85
View File
@@ -1,9 +1,11 @@
import asyncio
import logging
import os
import re
import sqlite3
import subprocess
import tempfile
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
@@ -18,6 +20,10 @@ AUDIO_DIR = DATA_DIR / "audio"
DB_PATH = DATA_DIR / "faerro-kb.sqlite3"
WHISPER_SERVER_URL = os.environ.get("WHISPER_SERVER_URL", "").strip()
FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend"
WHISPER_TEST_AUDIO_PATH = Path(__file__).resolve().parent.parent / "audio test.wav"
TRANSCRIPTION_TASKS: set[asyncio.Task[None]] = set()
logger = logging.getLogger(__name__)
logging.getLogger("httpx").setLevel(logging.WARNING)
DATA_DIR.mkdir(parents=True, exist_ok=True)
AUDIO_DIR.mkdir(parents=True, exist_ok=True)
@@ -120,6 +126,85 @@ async def transcribe_note(note_id: str, audio_path: Path) -> None:
remove_transcribed_audio(note_id)
async def test_whisper_connection() -> bool:
if not WHISPER_SERVER_URL:
logger.warning("Whisper startup test skipped: WHISPER_SERVER_URL is not configured")
return False
if not WHISPER_TEST_AUDIO_PATH.is_file():
logger.error("Whisper startup test failed: bundled test audio is missing")
return False
started_at = time.monotonic()
try:
async with httpx.AsyncClient(timeout=httpx.Timeout(120, connect=5)) as client:
with WHISPER_TEST_AUDIO_PATH.open("rb") as audio_file:
response = await client.post(
WHISPER_SERVER_URL,
params={"encode": "true", "task": "transcribe", "output": "json"},
files={"audio_file": (WHISPER_TEST_AUDIO_PATH.name, audio_file, "audio/wav")},
)
response.raise_for_status()
result = response.json()
transcript = result.get("text") if isinstance(result, dict) else None
if not isinstance(transcript, str) or not transcript.strip():
logger.error(
"Whisper startup test failed: HTTP %s returned no transcript (%.1fs)",
response.status_code,
time.monotonic() - started_at,
)
return False
language = result.get("language", "unknown")
if not isinstance(language, str) or not re.fullmatch(r"[A-Za-z-]{1,12}", language):
language = "unknown"
segments = result.get("segments", [])
segment_count = len(segments) if isinstance(segments, list) else 0
logger.info(
"Whisper startup test passed: HTTP %s, language=%s, segments=%d, elapsed=%.1fs",
response.status_code,
language,
segment_count,
time.monotonic() - started_at,
)
return True
except httpx.HTTPStatusError as error:
logger.error("Whisper startup test failed: HTTP %s", error.response.status_code)
except Exception as error:
logger.error("Whisper startup test failed: %s", type(error).__name__)
return False
@app.on_event("startup")
async def resume_pending_transcriptions() -> None:
with connect_db() as connection:
connection.execute(
"UPDATE notes SET status = 'awaiting_transcription' WHERE status = 'transcribing'"
)
if not await test_whisper_connection():
return
with connect_db() as connection:
notes = connection.execute(
"""SELECT id, audio_path FROM notes
WHERE status IN ('awaiting_transcription', 'transcribing', 'transcription_failed')"""
).fetchall()
for note in notes:
audio_path = Path(note["audio_path"])
if audio_path.is_file():
with connect_db() as connection:
connection.execute(
"UPDATE notes SET status = 'transcribing' WHERE id = ?",
(note["id"],),
)
task = asyncio.create_task(transcribe_note(note["id"], audio_path))
TRANSCRIPTION_TASKS.add(task)
task.add_done_callback(TRANSCRIPTION_TASKS.discard)
else:
with connect_db() as connection:
connection.execute(
"UPDATE notes SET status = 'transcription_failed' WHERE id = ?",
(note["id"],),
)
@app.get("/api/health")
def health() -> dict[str, str]:
return {"status": "ok"}