Files
audio-transcription/app/routers/transcribe.py
T
JiriUhlir 7a3f61c125 first
2026-07-10 09:46:49 +02:00

169 lines
6.1 KiB
Python

"""Endpointy pro přepis audia.
- POST /transcribe/dual → paralelní přepis Deepgram + OpenAI Whisper (dva texty)
- POST /transcribe/combined → dual + sloučení do jednoho co nejlepšího přepisu (merge)
Audio přichází jako proměnná (upload `file` ve form-data). Všechny ostatní parametry
jsou v POST těle (form fields). API klíče jsou v X- hlavičkách (viz credentials.py).
Výstup je vždy JSON.
"""
import asyncio
from fastapi import APIRouter, Depends, File, Form, UploadFile
from pydantic import BaseModel, Field
from ..clients import deepgram_client, openai_client
from ..config import (
DEFAULT_CHAT_MODEL,
DEFAULT_DEEPGRAM_MODEL,
DEFAULT_LANGUAGE,
DEFAULT_WHISPER_MODEL,
)
from ..credentials import Credentials, get_credentials
from ..errors import BadRequestError
from ..logging_config import get_logger
from ..prompts import DEFAULT_COMBINE_PROMPT
log = get_logger("audio-transcription.transcribe")
router = APIRouter(tags=["transcribe"])
class DualResult(BaseModel):
text1: str = Field(..., description="Přepis z Deepgramu.")
text2: str = Field(..., description="Přepis z OpenAI Whisper.")
deepgram_model: str
whisper_model: str
language: str
class CombinedResult(DualResult):
merged: str = Field(..., description="Sloučený, co nejpřesnější výsledný přepis.")
chat_model: str
async def _read_upload(file: UploadFile) -> tuple[bytes, str, str]:
"""Načte upload do paměti a vrátí (bytes, filename, content_type)."""
audio = await file.read()
if not audio:
raise BadRequestError("Nahraný soubor je prázdný.")
filename = file.filename or "audio.mp3"
content_type = file.content_type or "application/octet-stream"
return audio, filename, content_type
async def _transcribe_dual(
creds: Credentials,
audio: bytes,
filename: str,
content_type: str,
deepgram_model: str,
whisper_model: str,
language: str,
diarize: bool,
smart_format: bool,
) -> tuple[str, str]:
"""Spustí oba přepisy paralelně; vrátí (deepgram_text, whisper_text)."""
deepgram_key = creds.require_deepgram()
openai_key = creds.require_openai()
text1, text2 = await asyncio.gather(
deepgram_client.transcribe(
api_key=deepgram_key,
audio=audio,
content_type=content_type,
model=deepgram_model,
language=language,
diarize=diarize,
smart_format=smart_format,
),
openai_client.transcribe(
api_key=openai_key,
audio=audio,
filename=filename,
content_type=content_type,
model=whisper_model,
language=language,
),
)
return text1, text2
@router.post(
"/transcribe/dual",
response_model=DualResult,
summary="Paralelní přepis Deepgram + OpenAI Whisper",
description="Přijme audio soubor a vrátí dva nezávislé přepisy: "
"`text1` z Deepgramu a `text2` z OpenAI Whisper.",
)
async def transcribe_dual(
file: UploadFile = File(..., description="Audio soubor k přepisu (proměnná)."),
deepgram_model: str = Form(DEFAULT_DEEPGRAM_MODEL, description="Deepgram model."),
whisper_model: str = Form(DEFAULT_WHISPER_MODEL, description="OpenAI přepisový model."),
language: str = Form(DEFAULT_LANGUAGE, description="Jazyk audia (ISO kód, např. cs)."),
diarize: bool = Form(True, description="Deepgram diarizace (rozlišení mluvčích)."),
smart_format: bool = Form(True, description="Deepgram smart formatting."),
creds: Credentials = Depends(get_credentials),
) -> DualResult:
audio, filename, content_type = await _read_upload(file)
text1, text2 = await _transcribe_dual(
creds, audio, filename, content_type,
deepgram_model, whisper_model, language, diarize, smart_format,
)
return DualResult(
text1=text1,
text2=text2,
deepgram_model=deepgram_model,
whisper_model=whisper_model,
language=language,
)
@router.post(
"/transcribe/combined",
response_model=CombinedResult,
summary="Dual přepis + AI sloučení do nejlepšího přepisu",
description="Přijme audio soubor, vytvoří přepis z Deepgramu i Whisperu a poté je "
"sloučí přes OpenAI Chat podle `combine_prompt` do jediného, co nejpřesnějšího "
"českého přepisu. Vrací `text1`, `text2` i výsledný `merged`.",
)
async def transcribe_combined(
file: UploadFile = File(..., description="Audio soubor k přepisu (proměnná)."),
combine_prompt: str = Form(
DEFAULT_COMBINE_PROMPT,
description="System prompt pro sloučení. Nezadáš-li, použije se výchozí "
"'Czech Transcript Merger'.",
),
deepgram_model: str = Form(DEFAULT_DEEPGRAM_MODEL, description="Deepgram model."),
whisper_model: str = Form(DEFAULT_WHISPER_MODEL, description="OpenAI přepisový model."),
chat_model: str = Form(DEFAULT_CHAT_MODEL, description="OpenAI chat model pro sloučení."),
language: str = Form(DEFAULT_LANGUAGE, description="Jazyk audia (ISO kód, např. cs)."),
diarize: bool = Form(True, description="Deepgram diarizace (rozlišení mluvčích)."),
smart_format: bool = Form(True, description="Deepgram smart formatting."),
creds: Credentials = Depends(get_credentials),
) -> CombinedResult:
audio, filename, content_type = await _read_upload(file)
text1, text2 = await _transcribe_dual(
creds, audio, filename, content_type,
deepgram_model, whisper_model, language, diarize, smart_format,
)
prompt = combine_prompt.strip() if combine_prompt and combine_prompt.strip() else DEFAULT_COMBINE_PROMPT
merged = await openai_client.merge_transcripts(
api_key=creds.require_openai(),
model=chat_model,
system_prompt=prompt,
text_deepgram=text1,
text_whisper=text2,
)
return CombinedResult(
text1=text1,
text2=text2,
merged=merged,
deepgram_model=deepgram_model,
whisper_model=whisper_model,
chat_model=chat_model,
language=language,
)