habulaj commited on
Commit
f1d4b6e
·
verified ·
1 Parent(s): 7da87f4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +320 -1
app.py CHANGED
@@ -1,9 +1,12 @@
1
  from fastapi import FastAPI, HTTPException, File, UploadFile, Form
2
- from fastapi.responses import JSONResponse
3
  from typing import Optional
4
  import os
5
  import tempfile
6
  from pathlib import Path
 
 
 
7
 
8
  from gemini_client import AsyncChatbot, Model, load_cookies, save_cookies
9
  from gemini_client.enums import Endpoint, Headers
@@ -134,6 +137,322 @@ def root():
134
  """Endpoint raiz"""
135
  return {"status": "ok", "message": "Gemini Chat API está funcionando"}
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  @app.get("/get")
138
  async def get_response(
139
  message: str,
 
1
  from fastapi import FastAPI, HTTPException, File, UploadFile, Form
2
+ from fastapi.responses import Response, JSONResponse
3
  from typing import Optional
4
  import os
5
  import tempfile
6
  from pathlib import Path
7
+ import re
8
+ import requests
9
+ import mimetypes
10
 
11
  from gemini_client import AsyncChatbot, Model, load_cookies, save_cookies
12
  from gemini_client.enums import Endpoint, Headers
 
137
  """Endpoint raiz"""
138
  return {"status": "ok", "message": "Gemini Chat API está funcionando"}
139
 
140
+ def clean_and_validate_srt(srt_content):
141
+ """Limpa e valida conteúdo SRT seguindo o padrão do example.py"""
142
+ if "```" in srt_content:
143
+ # Remover markdown code blocks
144
+ parts = srt_content.split("```")
145
+ if len(parts) > 1:
146
+ # Pegar o conteúdo dentro dos blocos de código
147
+ for part in parts:
148
+ if "srt" in part.lower() or not part.strip().startswith("srt"):
149
+ srt_content = part.strip()
150
+ break
151
+
152
+ # Padrão mais flexível para capturar timestamps mal formatados
153
+ pattern = re.compile(r"(\d+)\s*\n([^-\n]+?) --> ([^-\n]+?)\s*\n((?:(?!^\d+\s*\n).+\n?)*)", re.MULTILINE)
154
+ matches = pattern.findall(srt_content)
155
+
156
+ def corrigir_timestamp(timestamp):
157
+ timestamp = timestamp.strip()
158
+
159
+ # Se já está correto, retorna
160
+ if re.match(r"\d{2}:\d{2}:\d{2},\d{3}", timestamp):
161
+ return timestamp
162
+
163
+ # Formato: MM:SS,mmm -> HH:MM:SS,mmm
164
+ if re.match(r"\d{2}:\d{2},\d{3}", timestamp):
165
+ return f"00:{timestamp}"
166
+
167
+ # Formato: M:SS,mmm -> HH:MM:SS,mmm
168
+ if re.match(r"\d{1}:\d{2},\d{3}", timestamp):
169
+ parts = timestamp.split(":")
170
+ minutes = parts[0].zfill(2)
171
+ return f"00:{minutes}:{parts[1]}"
172
+
173
+ # Formato: SS,mmm -> HH:MM:SS,mmm
174
+ if re.match(r"\d{1,2},\d{3}", timestamp):
175
+ seconds_ms = timestamp.split(",")
176
+ seconds = seconds_ms[0].zfill(2)
177
+ return f"00:00:{seconds},{seconds_ms[1]}"
178
+
179
+ # Outros formatos problemáticos
180
+ if re.match(r"\d{2}:\d{2}:\d{3}", timestamp):
181
+ parts = timestamp.split(":")
182
+ if len(parts) == 3:
183
+ h, m, s_ms = parts
184
+ if len(s_ms) == 3:
185
+ return f"{h}:{m}:00,{s_ms}"
186
+ elif len(s_ms) >= 4:
187
+ s = s_ms[:-3]
188
+ ms = s_ms[-3:]
189
+ return f"{h}:{m}:{s.zfill(2)},{ms}"
190
+
191
+ return timestamp
192
+
193
+ srt_corrigido = ""
194
+ for i, (num, start, end, text) in enumerate(matches, 1):
195
+ text = text.strip()
196
+ if not text:
197
+ continue
198
+
199
+ # Verificar se a legenda tem mais de 2 linhas
200
+ text_lines = [line.strip() for line in text.split('\n') if line.strip()]
201
+ if len(text_lines) > 2:
202
+ # Limitar a 2 linhas, juntando as extras na segunda linha
203
+ text = text_lines[0] + '\n' + ' '.join(text_lines[1:])
204
+
205
+ start_corrigido = corrigir_timestamp(start)
206
+ end_corrigido = corrigir_timestamp(end)
207
+ srt_corrigido += f"{i}\n{start_corrigido} --> {end_corrigido}\n{text}\n\n"
208
+
209
+ return srt_corrigido.strip()
210
+
211
+ @app.get("/subtitle")
212
+ async def generate_subtitle(
213
+ file: str,
214
+ context: Optional[str] = None
215
+ ):
216
+ """
217
+ Endpoint para gerar legendas SRT a partir de um arquivo (imagem, vídeo ou áudio).
218
+
219
+ Parâmetros:
220
+ - file: URL do arquivo (imagem, vídeo ou áudio)
221
+ - context: Contexto adicional opcional para a geração de legendas
222
+
223
+ Retorna:
224
+ - Arquivo SRT formatado
225
+ """
226
+ if chatbot is None:
227
+ raise HTTPException(status_code=500, detail="Chatbot não inicializado")
228
+
229
+ if not file:
230
+ raise HTTPException(status_code=400, detail="Parâmetro 'file' é obrigatório")
231
+
232
+ temp_file = None
233
+ try:
234
+ # Baixar arquivo da URL
235
+ print(f"📥 Baixando arquivo de: {file}")
236
+ response = requests.get(file, timeout=300, stream=True)
237
+ response.raise_for_status()
238
+
239
+ # Determinar tipo de mídia e extensão
240
+ content_type = response.headers.get('content-type', '').lower()
241
+ file_extension = None
242
+
243
+ if 'video' in content_type:
244
+ file_extension = '.mp4'
245
+ media_type = 'video'
246
+ elif 'audio' in content_type:
247
+ file_extension = '.mp3'
248
+ media_type = 'audio'
249
+ elif 'image' in content_type:
250
+ # Determinar extensão da imagem
251
+ if 'jpeg' in content_type or 'jpg' in content_type:
252
+ file_extension = '.jpg'
253
+ elif 'png' in content_type:
254
+ file_extension = '.png'
255
+ elif 'gif' in content_type:
256
+ file_extension = '.gif'
257
+ elif 'webp' in content_type:
258
+ file_extension = '.webp'
259
+ else:
260
+ file_extension = '.jpg'
261
+ media_type = 'image'
262
+ else:
263
+ # Tentar inferir do URL
264
+ url_lower = file.lower()
265
+ if any(ext in url_lower for ext in ['.mp4', '.avi', '.mov', '.webm', '.mkv']):
266
+ file_extension = '.mp4'
267
+ media_type = 'video'
268
+ elif any(ext in url_lower for ext in ['.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a']):
269
+ file_extension = '.mp3'
270
+ media_type = 'audio'
271
+ elif any(ext in url_lower for ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']):
272
+ file_extension = Path(file).suffix or '.jpg'
273
+ media_type = 'image'
274
+ else:
275
+ raise HTTPException(status_code=400, detail="Tipo de arquivo não suportado. Use imagem, vídeo ou áudio.")
276
+
277
+ # Salvar arquivo temporariamente
278
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=file_extension)
279
+ for chunk in response.iter_content(chunk_size=8192):
280
+ if chunk:
281
+ temp_file.write(chunk)
282
+ temp_file.close()
283
+
284
+ print(f"✅ Arquivo baixado: {temp_file.name} (tipo: {media_type})")
285
+
286
+ # Preparar prompt (mesmo do example.py)
287
+ context_text = context.strip() if context else "N/A"
288
+ media_desc = 'áudio' if media_type in ['video', 'audio'] else 'conteúdo'
289
+ media_desc_final = 'VÍDEO' if media_type in ['video', 'audio'] else 'CONTEÚDO'
290
+
291
+ prompt = f"Gere uma legenda em formato SRT para este {media_desc} seguindo RIGOROSAMENTE todas as especificações do sistema.\n\nContexto adicional: {context_text}"
292
+
293
+ # System instruction (mesmo do example.py)
294
+ system_instruction = f"""FORMATO TÉCNICO OBRIGATÓRIO
295
+
296
+ Estrutura de cada bloco:
297
+
298
+ [número sequencial]
299
+ HH:MM:SS,mmm --> HH:MM:SS,mmm
300
+ [texto da legenda]
301
+ [linha em branco]
302
+
303
+ CRÍTICO - Formato de tempo:
304
+ - SEMPRE usar: HH:MM:SS,mmm (exemplo: 00:01:23,456)
305
+ - Vírgula (,) separando segundos de milissegundos
306
+ - Duas casas para horas, minutos e segundos
307
+ - Três casas para milissegundos
308
+ - Nunca omitir as horas, mesmo que sejam 00
309
+
310
+ PADRÃO NETFLIX - REGRAS DE TEXTO
311
+
312
+ Limitações de caracteres:
313
+ - Máximo 2 linhas por legenda
314
+ - Máximo 42 caracteres por linha (incluindo espaços e pontuação)
315
+ - Quebras de linha devem respeitar unidades semânticas (não partir palavras ou expressões)
316
+
317
+ Separação de falas:
318
+ - NUNCA misture falas de pessoas diferentes na mesma legenda
319
+ - Se houver mudança de locutor, SEMPRE crie um novo bloco numerado
320
+ - Única exceção: diálogos rápidos marcados com hífen (veja abaixo)
321
+
322
+ Uso de hífen (-):
323
+ Use APENAS para:
324
+ 1. Diálogos alternados quando o timing impede separação:
325
+
326
+ - Vamos?
327
+ - Vamos!
328
+
329
+ 2. Interrupções abruptas de fala
330
+ 3. Falas sobrepostas simultâneas
331
+
332
+ NÃO use hífen para:
333
+ - Fala única de uma pessoa
334
+ - Marcação desnecessária de locutor
335
+
336
+ NATURALIDADE E EMOÇÃO
337
+
338
+ Idioma:
339
+ - Português brasileiro natural
340
+ - Adaptar gírias, expressões regionais e modo de falar brasileiro
341
+ - Evitar traduções literais ou formais demais
342
+
343
+ Expressão emocional:
344
+ - Gritos, ênfase forte: LETRAS MAIÚSCULAS
345
+ - Hesitação, pausa: reticências (...)
346
+ - Surpresa, exclamação: ponto de exclamação (!)
347
+ - Interrogação: ponto de interrogação (?)
348
+ - Nunca deixe frases importantes sem pontuação
349
+ - Exemplos:
350
+ - "João" → "João..." (hesitante)
351
+ - "João" → "João!" (chamando com urgência)
352
+ - "João" → "JOÃO!" (gritando)
353
+
354
+ SINCRONIA TEMPORAL
355
+
356
+ - Precisão de milissegundos
357
+ - Início da legenda: EXATAMENTE quando a fala começa
358
+ - Fim da legenda: quando a fala termina (mínimo 1 segundo de exibição)
359
+ - Respeitar pausas naturais entre falas
360
+
361
+ EXEMPLO DE FORMATAÇÃO PERFEITA
362
+
363
+ 1
364
+ 00:00:01,200 --> 00:00:04,000
365
+ Oi, tudo bem?
366
+
367
+ 2
368
+ 00:00:04,500 --> 00:00:06,800
369
+ Tudo ótimo, e você?
370
+
371
+ 3
372
+ 00:00:07,100 --> 00:00:09,500
373
+ - Quer almoçar comigo?
374
+ - Claro!
375
+
376
+ 4
377
+ 00:00:10,000 --> 00:00:12,300
378
+ QUE LEGAL!
379
+
380
+ 5
381
+ 00:00:12,800 --> 00:00:15,100
382
+ Não acredito que você aceitou...
383
+
384
+ INSTRUÇÕES FINAIS
385
+
386
+ - Retorne APENAS o arquivo SRT formatado
387
+ - Sem explicações, comentários ou textos adicionais
388
+ - Sem marcadores de código (```), apenas o conteúdo puro
389
+ - Numere sequencialmente a partir de 1
390
+ - Linha em branco entre cada bloco de legenda
391
+
392
+ TRADUZA TUDO DE IMPORTANTE NO {media_desc_final}, que tenha dialogo... Nunca deixe passar nada."""
393
+
394
+ # Adicionar system instruction ao prompt
395
+ full_prompt = f"{system_instruction}\n\n{prompt}"
396
+
397
+ # Enviar para o Gemini
398
+ print(f"🧠 Enviando {media_type} para o Gemini...")
399
+
400
+ # Determinar qual parâmetro usar baseado no tipo de mídia
401
+ if media_type == 'image':
402
+ response_gemini = await chatbot.ask(full_prompt, image=temp_file.name)
403
+ elif media_type == 'video':
404
+ response_gemini = await chatbot.ask(full_prompt, video=temp_file.name)
405
+ else: # audio
406
+ response_gemini = await chatbot.ask(full_prompt, audio=temp_file.name)
407
+
408
+ if response_gemini.get("error"):
409
+ raise HTTPException(
410
+ status_code=500,
411
+ detail=f"Erro ao gerar legendas: {response_gemini.get('content', 'Erro desconhecido')}"
412
+ )
413
+
414
+ # Extrair conteúdo SRT da resposta
415
+ raw_srt = response_gemini.get("content", "").strip()
416
+
417
+ if not raw_srt or len(raw_srt) < 10:
418
+ raise HTTPException(
419
+ status_code=500,
420
+ detail="Nenhuma legenda foi gerada - arquivo pode estar vazio ou inaudível"
421
+ )
422
+
423
+ # Limpar e validar SRT
424
+ print("📝 Processando formato SRT...")
425
+ srt_cleaned = clean_and_validate_srt(raw_srt)
426
+
427
+ if not srt_cleaned or len(srt_cleaned.strip()) < 10:
428
+ raise HTTPException(
429
+ status_code=500,
430
+ detail="Falha ao processar formato SRT - resposta inválida"
431
+ )
432
+
433
+ # Retornar SRT como resposta de texto
434
+ return Response(
435
+ content=srt_cleaned,
436
+ media_type="text/plain; charset=utf-8",
437
+ headers={
438
+ "Content-Disposition": "attachment; filename=subtitles.srt"
439
+ }
440
+ )
441
+
442
+ except HTTPException:
443
+ raise
444
+ except requests.RequestException as e:
445
+ raise HTTPException(status_code=400, detail=f"Erro ao baixar arquivo: {str(e)}")
446
+ except Exception as e:
447
+ raise HTTPException(status_code=500, detail=f"Erro ao gerar legendas: {str(e)}")
448
+ finally:
449
+ # Limpar arquivo temporário
450
+ if temp_file and os.path.exists(temp_file.name):
451
+ try:
452
+ os.unlink(temp_file.name)
453
+ except:
454
+ pass
455
+
456
  @app.get("/get")
457
  async def get_response(
458
  message: str,