"""Endpoint pro přepis audia. POST /transcribe — udělá vše najednou: audio přepíše paralelně Deepgramem i OpenAI (whisper-1) a oba texty sloučí přes OpenAI Chat podle `combine_prompt` do jednoho co nejlepšího přepisu. 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: {"gpt": ..., "deepgram": ..., "merge": ..., "errors": [...]}. Selhání jednoho z přepisů (Deepgram / OpenAI) nebo sloučení neshodí celý request — jeho část zůstane prázdná a chyba se přidá do pole `errors`. """ 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 TranscribeResult(BaseModel): gpt: str = Field(..., description="Přepis z OpenAI (model whisper-1), plynulý, bez mluvčích.") deepgram: str = Field( ..., description="Přepis z Deepgramu. Při zapnuté diarizaci se štítky mluvčích " "(`Mluvčí 0: ...`).", ) merge: str = Field( ..., description="Sloučený, co nejpřesnější a úplný přepis se štítky mluvčích.", ) errors: list[str] = Field( default_factory=list, description="Chyby dílčích kroků (Deepgram / OpenAI přepis / sloučení). " "Prázdné, pokud vše proběhlo v pořádku. Selhavší část je pak prázdná.", ) 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 @router.post( "/dual-with-merge", response_model=TranscribeResult, summary="Přepis audia (Deepgram + OpenAI) a AI sloučení — vše najednou", description="Přijme audio soubor, vytvoří paralelně přepis z Deepgramu i z OpenAI " "(whisper-1) a poté je sloučí přes OpenAI Chat podle `combine_prompt` do jediného, " "co nejpřesnějšího českého přepisu. Vrací JSON `{gpt, deepgram, merge}`.", ) async def transcribe( 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. Při zapnutí vrací `deepgram` " "přepis se štítky `Mluvčí N:` (interně zapne i utterances).", ), smart_format: bool = Form(True, description="Deepgram smart formatting."), creds: Credentials = Depends(get_credentials), ) -> TranscribeResult: deepgram_key = creds.require_deepgram() openai_key = creds.require_openai() audio, filename, content_type = await _read_upload(file) errors: list[str] = [] # Oba přepisy paralelně; selhání jednoho neshodí druhý ani celý request. result_deepgram, result_gpt = 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_exceptions=True, ) if isinstance(result_deepgram, BaseException): log.exception("Deepgram přepis selhal", exc_info=result_deepgram) errors.append(f"Deepgram přepis selhal: {result_deepgram}") text_deepgram = "" else: text_deepgram = result_deepgram if isinstance(result_gpt, BaseException): log.exception("OpenAI (whisper) přepis selhal", exc_info=result_gpt) errors.append(f"OpenAI (whisper) přepis selhal: {result_gpt}") text_gpt = "" else: text_gpt = result_gpt # Sloučení má smysl jen když je aspoň jeden přepis k dispozici. merged = "" if text_deepgram or text_gpt: prompt = combine_prompt.strip() if combine_prompt and combine_prompt.strip() else DEFAULT_COMBINE_PROMPT try: merged = await openai_client.merge_transcripts( api_key=openai_key, model=chat_model, system_prompt=prompt, text_deepgram=text_deepgram, text_whisper=text_gpt, ) except Exception as exc: log.exception("Sloučení přepisů selhalo") errors.append(f"Sloučení přepisů selhalo: {exc}") return TranscribeResult( gpt=text_gpt, deepgram=text_deepgram, merge=merged, errors=errors, )