Update app.py
Browse files
app.py
CHANGED
|
@@ -866,8 +866,8 @@ class GeminiSubtitleRequest(BaseModel):
|
|
| 866 |
context: Optional[str] = "N/A"
|
| 867 |
model: Optional[str] = "flash" # 'flash' or 'thinking'
|
| 868 |
|
| 869 |
-
@app.post("/subtitle
|
| 870 |
-
async def
|
| 871 |
"""
|
| 872 |
Endpoint PRINCIPAL:
|
| 873 |
1. Baixa e Processa áudio (Demucs opcional + Filtros FFmpeg)
|
|
@@ -907,7 +907,7 @@ async def generate_subtitle_gemini(request: GeminiSubtitleRequest):
|
|
| 907 |
# 3. Montar Prompt
|
| 908 |
processed_context = request.context if request.context else "N/A"
|
| 909 |
|
| 910 |
-
prompt = f
|
| 911 |
Traduza essa legenda pro português do Brasil, corrija qualquer erro de formatação, pontuação e mantenha timestamps e os textos nos seus respectivos blocos de legenda.
|
| 912 |
|
| 913 |
Deve traduzir exatamente o texto da legenda observando o contexto, não é pra migrar, por exemplo, textos de um bloco de legenda pra outro. Deve traduzir exatamente o texto de cada bloco de legenda, manter sempre as palavras, nunca retirar.
|
|
@@ -942,155 +942,3 @@ INSTRUÇÕES/CONTEXTO DO USUÁRIO: {processed_context}
|
|
| 942 |
import traceback
|
| 943 |
traceback.print_exc()
|
| 944 |
raise HTTPException(status_code=500, detail=str(e))
|
| 945 |
-
|
| 946 |
-
@app.get("/subtitle")
|
| 947 |
-
async def generate_subtitle(
|
| 948 |
-
file: str,
|
| 949 |
-
context: Optional[str] = None,
|
| 950 |
-
start: Optional[float] = None,
|
| 951 |
-
end: Optional[float] = None,
|
| 952 |
-
model: Optional[str] = "thinking",
|
| 953 |
-
language: Optional[str] = None,
|
| 954 |
-
temperature: Optional[float] = 0.4
|
| 955 |
-
):
|
| 956 |
-
"""
|
| 957 |
-
Endpoint PRINCIPAL para gerar legendas traduzidas (PT-BR).
|
| 958 |
-
Fluxo:
|
| 959 |
-
1. Gera SRT base com Groq + Filtro Netflix (via helper /subtitle/groq logic).
|
| 960 |
-
2. Envia SRT para Gemini traduzir para PT-BR mantendo formatação.
|
| 961 |
-
"""
|
| 962 |
-
logs = []
|
| 963 |
-
def log(msg):
|
| 964 |
-
print(msg)
|
| 965 |
-
logs.append(msg)
|
| 966 |
-
|
| 967 |
-
if not chatbots:
|
| 968 |
-
raise HTTPException(status_code=500, detail="Chatbot não inicializado")
|
| 969 |
-
|
| 970 |
-
# Selecionar chatbot
|
| 971 |
-
requested_model = model.lower() if model else "flash"
|
| 972 |
-
if "thinking" in requested_model:
|
| 973 |
-
selected_chatbot = chatbots.get('thinking', chatbots['default'])
|
| 974 |
-
else:
|
| 975 |
-
selected_chatbot = chatbots.get('flash', chatbots['default'])
|
| 976 |
-
|
| 977 |
-
if not file:
|
| 978 |
-
raise HTTPException(status_code=400, detail="Parâmetro 'file' é obrigatório")
|
| 979 |
-
|
| 980 |
-
try:
|
| 981 |
-
# 1. Gerar SRT Base (Groq)
|
| 982 |
-
log(f"🎤 [Fase 1] Gerando SRT base com Groq...")
|
| 983 |
-
srt_base, _, processed_audio_url = await get_groq_srt_base(file, language, temperature)
|
| 984 |
-
log(f"✅ SRT Base gerado ({len(srt_base)} chars)")
|
| 985 |
-
|
| 986 |
-
# 2. Traduzir com Gemini
|
| 987 |
-
log(f"🧠 [Fase 2] Enviando para Gemini ({requested_model}) para tradução PT-BR (com contexto de vídeo)...")
|
| 988 |
-
|
| 989 |
-
# Baixar arquivo para enviar ao Gemini
|
| 990 |
-
log(f"⬇️ Baixando mídia para contexto...")
|
| 991 |
-
response = download_file_with_retry(file)
|
| 992 |
-
content_type = response.headers.get('content-type', '').lower()
|
| 993 |
-
|
| 994 |
-
file_extension = '.mp4'
|
| 995 |
-
media_type = 'video'
|
| 996 |
-
if 'audio' in content_type:
|
| 997 |
-
file_extension = '.mp3'
|
| 998 |
-
media_type = 'audio'
|
| 999 |
-
|
| 1000 |
-
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=file_extension)
|
| 1001 |
-
for chunk in response.iter_content(chunk_size=8192):
|
| 1002 |
-
if chunk:
|
| 1003 |
-
temp_file.write(chunk)
|
| 1004 |
-
temp_file.close()
|
| 1005 |
-
log(f"✅ Mídia baixada: {temp_file.name}")
|
| 1006 |
-
|
| 1007 |
-
context_instruction = ""
|
| 1008 |
-
if context:
|
| 1009 |
-
context_instruction = f"\n6. **USER INSTRUCTIONS**: {context}\n"
|
| 1010 |
-
|
| 1011 |
-
prompt = f"""Translate and Correct the SRT subtitles to Brazilian Portuguese (PT-BR).
|
| 1012 |
-
|
| 1013 |
-
SOURCE MATERIAL: I have attached the video/audio file. Use it as the PRIMARY source of truth for context, speaker identification, and meaning.
|
| 1014 |
-
INPUT SRT: I have provided a draft SRT generated by an automated transcription service.
|
| 1015 |
-
|
| 1016 |
-
CRITICAL INSTRUCTIONS:
|
| 1017 |
-
1. **CORRECTION**: The input SRT is a draft. It may have wrong words or misinterpretations. **You MUST correct the text based on the actual audio.**
|
| 1018 |
-
2. **TIMESTAMPS**: The timestamps in the input SRT are generally correct. **Keep them as much as possible**, only adjust if they are clearly wrong / desynchronized.
|
| 1019 |
-
3. **FORMATTING**:
|
| 1020 |
-
- Keep the visual structure (2 lines max).
|
| 1021 |
-
- **DIALOGUES**: If two different people speak in the same block, separate them with a hyphen "- " at the start of each line.
|
| 1022 |
-
* Example Input: "I did it. So did I."
|
| 1023 |
-
* Example Output:
|
| 1024 |
-
- Eu fiz isso.
|
| 1025 |
-
- Eu também.
|
| 1026 |
-
4. **CONTEXT**: Translate naturally for a Brazilian audience. Prioritize clarity and meaning over literal translation. Make it readable and elegant.
|
| 1027 |
-
5. **PUNCTUATION**: Adjust punctuation to fit the context best.
|
| 1028 |
-
6. **OUTPUT**: Return ONLY the corrected and translated SRT content. No extra text.{context_instruction}
|
| 1029 |
-
|
| 1030 |
-
DRAFT SRT:
|
| 1031 |
-
{srt_base}
|
| 1032 |
-
"""
|
| 1033 |
-
|
| 1034 |
-
# Helper function for retry logic
|
| 1035 |
-
async def ask_with_retry(chatbot, prompt, media_file, media_type):
|
| 1036 |
-
max_retries = 3
|
| 1037 |
-
for attempt in range(max_retries):
|
| 1038 |
-
# Passar video ou audio
|
| 1039 |
-
kwargs = {}
|
| 1040 |
-
if media_type == 'video':
|
| 1041 |
-
kwargs['video'] = media_file
|
| 1042 |
-
else:
|
| 1043 |
-
kwargs['audio'] = media_file
|
| 1044 |
-
|
| 1045 |
-
response = await chatbot.ask(prompt, **kwargs)
|
| 1046 |
-
if response.get("error"):
|
| 1047 |
-
content = response.get("content", "")
|
| 1048 |
-
if "Failed to parse response body" in content and attempt < max_retries - 1:
|
| 1049 |
-
log(f"⚠️ Erro de parsing (tentativa {attempt+1}/{max_retries}). Retentando...")
|
| 1050 |
-
import asyncio
|
| 1051 |
-
await asyncio.sleep(2)
|
| 1052 |
-
continue
|
| 1053 |
-
return response
|
| 1054 |
-
return response
|
| 1055 |
-
|
| 1056 |
-
response_gemini = await ask_with_retry(selected_chatbot, prompt, temp_file.name, media_type)
|
| 1057 |
-
|
| 1058 |
-
# Limpar arquivo temporário
|
| 1059 |
-
if os.path.exists(temp_file.name):
|
| 1060 |
-
try:
|
| 1061 |
-
os.unlink(temp_file.name)
|
| 1062 |
-
except:
|
| 1063 |
-
pass
|
| 1064 |
-
|
| 1065 |
-
if response_gemini.get("error"):
|
| 1066 |
-
raise HTTPException(
|
| 1067 |
-
status_code=500,
|
| 1068 |
-
detail=f"Erro no Gemini: {response_gemini.get('content', 'Erro desconhecido')}"
|
| 1069 |
-
)
|
| 1070 |
-
|
| 1071 |
-
srt_translated = response_gemini.get("content", "").strip()
|
| 1072 |
-
|
| 1073 |
-
# Limpezas básicas no output do Gemini (caso ele devolva markdowns)
|
| 1074 |
-
if "```srt" in srt_translated:
|
| 1075 |
-
srt_translated = srt_translated.split("```srt")[1].split("```")[0].strip()
|
| 1076 |
-
elif "```" in srt_translated:
|
| 1077 |
-
srt_translated = srt_translated.split("```")[1].split("```")[0].strip()
|
| 1078 |
-
|
| 1079 |
-
log(f"✅ Tradução concluída")
|
| 1080 |
-
|
| 1081 |
-
# Retornar
|
| 1082 |
-
return JSONResponse(
|
| 1083 |
-
content={
|
| 1084 |
-
"srt": srt_translated,
|
| 1085 |
-
"success": True,
|
| 1086 |
-
"processed_audio_url": processed_audio_url,
|
| 1087 |
-
"logs": logs # Mantendo logs para debug se necessário
|
| 1088 |
-
}
|
| 1089 |
-
)
|
| 1090 |
-
|
| 1091 |
-
except HTTPException:
|
| 1092 |
-
raise
|
| 1093 |
-
except Exception as e:
|
| 1094 |
-
import traceback
|
| 1095 |
-
traceback.print_exc()
|
| 1096 |
-
raise HTTPException(status_code=500, detail=f"Erro ao gerar legendas: {str(e)}")
|
|
|
|
| 866 |
context: Optional[str] = "N/A"
|
| 867 |
model: Optional[str] = "flash" # 'flash' or 'thinking'
|
| 868 |
|
| 869 |
+
@app.post("/subtitle")
|
| 870 |
+
async def generate_subtitle(request: GeminiSubtitleRequest):
|
| 871 |
"""
|
| 872 |
Endpoint PRINCIPAL:
|
| 873 |
1. Baixa e Processa áudio (Demucs opcional + Filtros FFmpeg)
|
|
|
|
| 907 |
# 3. Montar Prompt
|
| 908 |
processed_context = request.context if request.context else "N/A"
|
| 909 |
|
| 910 |
+
prompt = f"""
|
| 911 |
Traduza essa legenda pro português do Brasil, corrija qualquer erro de formatação, pontuação e mantenha timestamps e os textos nos seus respectivos blocos de legenda.
|
| 912 |
|
| 913 |
Deve traduzir exatamente o texto da legenda observando o contexto, não é pra migrar, por exemplo, textos de um bloco de legenda pra outro. Deve traduzir exatamente o texto de cada bloco de legenda, manter sempre as palavras, nunca retirar.
|
|
|
|
| 942 |
import traceback
|
| 943 |
traceback.print_exc()
|
| 944 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|