habulaj commited on
Commit
365e50c
·
verified ·
1 Parent(s): bf07e20

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +140 -59
main.py CHANGED
@@ -39,7 +39,7 @@ class ProcessLogger:
39
 
40
  sys.path.append(str(Path(__file__).parent.parent))
41
  from detect_crop_image import detect_and_crop_image
42
- from detect_crop_video import detect_and_crop_video
43
 
44
  from gemini_webapi import GeminiClient
45
  from gemini_webapi.constants import Model
@@ -399,48 +399,84 @@ async def run_process_url(request: ProcessUrlRequest, record_id: int):
399
  else:
400
  logger.log("✂️ Processando vídeo: detectando movimento e bordas...")
401
  try:
402
- cropped_video_path = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
403
- crop_status = detect_and_crop_video(video_path_to_analyze, cropped_video_path, text_cut=request.text_cut)
 
 
 
404
 
405
- if crop_status == "aborted_area_too_small":
406
- logger.log("🚫 Crop abortado: área de texto no centro (segurança).")
407
-
408
- # Notificação do Agente no Discord (Octavio/Diana)
409
- try:
410
- import urllib.parse as _up
411
- reject_msg = f"Analisando aqui, notei que o texto desse vídeo está bem no centro. Se eu cortasse agora, ia acabar destruindo o conteúdo principal. Como a gente preza pela qualidade da página, preferi reprovar esse aqui automaticamente. ❌"
412
- _reject_url = "https://discordmsg.arthurmribeiro51.workers.dev/?" + _up.urlencode({"mensagem": reject_msg, "id": discord_id})
413
- requests.get("https://proxy.onrecurve.com/", params={"quest": _reject_url}, timeout=5)
414
- except Exception as _e_dc:
415
- logger.log(f"⚠️ Erro ao enviar Discord de rejeição automática: {_e_dc}")
416
-
417
- requests.patch(f"{supabase_url}/rest/v1/posts?id=eq.{record_id}", headers=headers, json={
418
- "approved_filter": False,
419
- "filter_message": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado).",
420
- "result": {"error": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado). Tente outro vídeo."},
421
- "contains_image": False
422
- })
423
- return # Para o processamento aqui
424
-
425
- if crop_status == "success" and os.path.exists(cropped_video_path):
426
- files_to_send.append(cropped_video_path)
427
- logger.log(f"✅ Vídeo cortado com sucesso: {cropped_video_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
- # Upload antecipado para recurve-save (Performance)
430
- logger.log("☁️ Enviando vídeo cortado antecipadamente para recurve-save...")
431
- with open(cropped_video_path, "rb") as vf:
432
- vid_upload_resp = requests.post(
433
- "https://habulaj-recurve-save.hf.space/upload",
434
- files={"files": ("cropped_video.mp4", vf, "video/mp4")},
435
- data={"long_duration": "yes"},
436
- timeout=120,
437
- )
438
- vid_upload_resp.raise_for_status()
439
- exported_cropped_video_url = vid_upload_resp.json().get("url", "")
440
- logger.log(f"✅ URL do vídeo cortado: {exported_cropped_video_url}")
441
  else:
442
  cropped_video_path = None
443
  logger.log("⚠️ Crop de vídeo ignorado ou falhou.")
 
444
  except Exception as e:
445
  cropped_video_path = None
446
  logger.log(f"⚠️ Erro no processo de crop: {e}")
@@ -1693,33 +1729,78 @@ async def run_filter_account(account: str, background_tasks: BackgroundTasks = N
1693
  else:
1694
  logger.log("✂️ Processando vídeo: detectando movimento e bordas...")
1695
  try:
1696
- # Criar arquivo temporário para o vídeo cortado
1697
  cropped_video_tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name
1698
- crop_status = detect_and_crop_video(media_path_to_analyze, cropped_video_tmp)
 
 
1699
 
1700
- if crop_status == "aborted_area_too_small":
1701
- logger.log("🚫 Crop abortado: área de texto no centro (segurança). Reprovando filtro.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1702
 
1703
- # Notificação do Agente no Discord
1704
  try:
1705
- import urllib.parse as _up
1706
- reject_msg = f"Analisei esse vídeo aqui e notei que o texto dele está bem no centro. Se eu fizesse o corte agora, ia acabar cortando partes importantes da mensagem. Por isso, decidi reprovar ele automaticamente para manter a qualidade! ❌"
1707
- _reject_url = "https://discordmsg.arthurmribeiro51.workers.dev/?" + _up.urlencode({"mensagem": reject_msg, "id": discord_id})
1708
- requests.get("https://proxy.onrecurve.com/", params={"quest": _reject_url}, timeout=5)
1709
- except Exception as _e_dc:
1710
- logger.log(f"⚠️ Erro ao enviar Discord de rejeição automática (crop): {_e_dc}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1711
 
1712
- requests.patch(f"{supabase_url}/rest/v1/posts?id=eq.{record_id}", headers=headers, json={
1713
- "approved_filter": False,
1714
- "filter_message": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado no vídeo).",
1715
- "result": {"error": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado). Tente outro vídeo."},
1716
- "contains_image": False
1717
- })
1718
- return # Para o processamento aqui
1719
-
1720
- if crop_status == "success" and os.path.exists(cropped_video_tmp):
1721
  cropped_file_path = cropped_video_tmp
1722
- logger.log(f"✅ Vídeo cortado com sucesso em: {cropped_file_path}")
1723
  else:
1724
  logger.log("⚠️ Crop de vídeo não gerou novo arquivo ou foi ignorado.")
1725
  if os.path.exists(cropped_video_tmp): os.unlink(cropped_video_tmp)
 
39
 
40
  sys.path.append(str(Path(__file__).parent.parent))
41
  from detect_crop_image import detect_and_crop_image
42
+ from detect_crop_video import detect_and_crop_video, detect_and_crop_text
43
 
44
  from gemini_webapi import GeminiClient
45
  from gemini_webapi.constants import Model
 
399
  else:
400
  logger.log("✂️ Processando vídeo: detectando movimento e bordas...")
401
  try:
402
+ # Criar arquivo temporário para o vídeo cortado (Passo 1: Motion Crop)
403
+ cropped_video_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
404
+ cropped_video_path = cropped_video_tmp # Track for finally cleanup
405
+ # Sempre fazemos o motion crop primeiro sem o text_cut interno
406
+ crop_status = detect_and_crop_video(video_path_to_analyze, cropped_video_tmp, text_cut=False)
407
 
408
+ if crop_status == "success" and os.path.exists(cropped_video_tmp):
409
+ # Passo 2: Verificação via Gemini (Apenas se o usuário solicitou text_cut)
410
+ if request.text_cut:
411
+ logger.log("🧠 Verificando presença de textos externos via Gemini (process-url)...")
412
+ text_check_model = get_gemini_model("flash")
413
+ text_check_prompt = """Analise o vídeo anexado e determine se há textos de edição externa (overlays de terceiros) adicionados após a produção original ou profissional do conteúdo.
414
+
415
+ Retorne um JSON no formato: {"has_texts": true/false}
416
+
417
+ Regras de Classificação:
418
+ - Considere como FALSE (Não marcar como texto de edição):
419
+ - Gráficos de Transmissão: Nomes de indicados, categorias de premiação, placares esportivos ou "lower thirds" que fazem parte da transmissão oficial da TV/Evento.
420
+ - Créditos Artísticos/Branding Profissional: Logotipos de marcas (ex: "Dior") ou créditos integrados que fazem parte da peça audiovisual original ou de um "lookbook" profissional.
421
+ - Textos de Ambiente: Placas, telões no fundo do palco ou escritos em roupas.
422
+ - Estética Profissional: Qualquer texto que tenha sido claramente renderizado como parte da master original do vídeo.
423
+ - Considere como TRUE (Marcar como texto de edição):
424
+ - Legendas de Redes Sociais: Captions explicativas ou legendas automáticas adicionadas para visualização em redes sociais.
425
+ - Textos de Engajamento: Frases como "Assista até o final", "O que você achou?", emojis flutuantes ou textos genéricos de meme.
426
+ - Marcas d'água de editores externos: Textos que identificam perfis de usuários ou editores que não são os produtores originais do evento.
427
+
428
+ Objetivo: O foco é detectar se o vídeo foi "re-editado" com textos informativos/estáticos típicos de redes sociais. Se o texto parece vir da produção original (Oscar, marcas de luxo, TV), retorne false. Responda apenas com o JSON, sem explicações."""
429
+
430
+ try:
431
+ text_resp = await client.generate_content(text_check_prompt, files=[cropped_video_tmp], model=text_check_model)
432
+ text_data = extract_json_from_text(text_resp.text)
433
+ has_texts = text_data.get("has_texts", False) if isinstance(text_data, dict) else False
434
+
435
+ if has_texts:
436
+ logger.log("🔍 Gemini detectou textos de edição externa. Iniciando OCR...")
437
+ ocr_video_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
438
+ text_crop_status = detect_and_crop_text(cropped_video_tmp, ocr_video_tmp)
439
+
440
+ if text_crop_status == "aborted_area_too_small":
441
+ logger.log("🚫 Crop abortado: área de texto no centro (segurança).")
442
+ # Notificação do Agente no Discord (Octavio/Diana)
443
+ try:
444
+ import urllib.parse as _up
445
+ reject_msg = f"Analisando aqui, notei que o texto desse vídeo está bem no centro. Se eu cortasse agora, ia acabar destruindo o conteúdo principal. Como a gente preza pela qualidade da página, preferi reprovar esse aqui automaticamente. ❌"
446
+ _reject_url = "https://discordmsg.arthurmribeiro51.workers.dev/?" + _up.urlencode({"mensagem": reject_msg, "id": discord_id})
447
+ requests.get("https://proxy.onrecurve.com/", params={"quest": _reject_url}, timeout=5)
448
+ except Exception as _e_dc:
449
+ logger.log(f"⚠️ Erro ao enviar Discord de rejeição automática: {_e_dc}")
450
+
451
+ requests.patch(f"{supabase_url}/rest/v1/posts?id=eq.{record_id}", headers=headers, json={
452
+ "approved_filter": False,
453
+ "filter_message": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado).",
454
+ "result": {"error": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado). Tente outro vídeo."},
455
+ "contains_image": False
456
+ })
457
+ if os.path.exists(cropped_video_tmp): os.unlink(cropped_video_tmp)
458
+ if os.path.exists(ocr_video_tmp): os.unlink(ocr_video_tmp)
459
+ return
460
+ elif text_crop_status == "success" and os.path.exists(ocr_video_tmp):
461
+ if os.path.exists(cropped_video_tmp): os.unlink(cropped_video_tmp)
462
+ cropped_video_tmp = ocr_video_tmp
463
+ cropped_video_path = ocr_video_tmp # Update tracking
464
+ logger.log(f"✅ Vídeo cortado via OCR com sucesso em: {cropped_video_tmp}")
465
+ else:
466
+ if os.path.exists(ocr_video_tmp): os.unlink(ocr_video_tmp)
467
+ logger.log("⚠️ Crop de OCR ignorado ou falhou, seguindo com motion crop.")
468
+ else:
469
+ logger.log("✅ Gemini NÃO detectou textos de social media. Pulando OCR.")
470
+ except Exception as ge:
471
+ logger.log(f"⚠️ Erro na análise Gemini de textos: {ge}. Seguindo apenas com motion crop.")
472
 
473
+ # Final path assignment
474
+ cropped_video_path = cropped_video_tmp
475
+ logger.log(f"✅ Vídeo (motion/ocr) consolidado em: {cropped_video_path}")
 
 
 
 
 
 
 
 
 
476
  else:
477
  cropped_video_path = None
478
  logger.log("⚠️ Crop de vídeo ignorado ou falhou.")
479
+ if os.path.exists(cropped_video_tmp): os.unlink(cropped_video_tmp)
480
  except Exception as e:
481
  cropped_video_path = None
482
  logger.log(f"⚠️ Erro no processo de crop: {e}")
 
1729
  else:
1730
  logger.log("✂️ Processando vídeo: detectando movimento e bordas...")
1731
  try:
1732
+ # Criar arquivo temporário para o vídeo cortado (Passo 1: Motion Crop)
1733
  cropped_video_tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name
1734
+ cropped_file_path = cropped_video_tmp # Track for finally cleanup
1735
+ # Desativamos o text_cut inicial para fazer a análise do Gemini antes
1736
+ crop_status = detect_and_crop_video(media_path_to_analyze, cropped_video_tmp, text_cut=False)
1737
 
1738
+ if crop_status == "success" and os.path.exists(cropped_video_tmp):
1739
+ # Passo 2: Verificação via Gemini para saber se deve rodar OCR
1740
+ logger.log("🧠 Verificando presença de textos externos via Gemini...")
1741
+ text_check_model = get_gemini_model("flash")
1742
+ text_check_prompt = """Analise o vídeo anexado e determine se há textos de edição externa (overlays de terceiros) adicionados após a produção original ou profissional do conteúdo.
1743
+
1744
+ Retorne um JSON no formato: {"has_texts": true/false}
1745
+
1746
+ Regras de Classificação:
1747
+ - Considere como FALSE (Não marcar como texto de edição):
1748
+ - Gráficos de Transmissão: Nomes de indicados, categorias de premiação, placares esportivos ou "lower thirds" que fazem parte da transmissão oficial da TV/Evento.
1749
+ - Créditos Artísticos/Branding Profissional: Logotipos de marcas (ex: "Dior") ou créditos integrados que fazem parte da peça audiovisual original ou de um "lookbook" profissional.
1750
+ - Textos de Ambiente: Placas, telões no fundo do palco ou escritos em roupas.
1751
+ - Estética Profissional: Qualquer texto que tenha sido claramente renderizado como parte da master original do vídeo.
1752
+ - Considere como TRUE (Marcar como texto de edição):
1753
+ - Legendas de Redes Sociais: Captions explicativas ou legendas automáticas adicionadas para visualização em redes sociais.
1754
+ - Textos de Engajamento: Frases como "Assista até o final", "O que você achou?", emojis flutuantes ou textos genéricos de meme.
1755
+ - Marcas d'água de editores externos: Textos que identificam perfis de usuários ou editores que não são os produtores originais do evento.
1756
+
1757
+ Objetivo: O foco é detectar se o vídeo foi "re-editado" com textos informativos/estáticos típicos de redes sociais. Se o texto parece vir da produção original (Oscar, marcas de luxo, TV), retorne false. Responda apenas com o JSON, sem explicações."""
1758
 
 
1759
  try:
1760
+ text_resp = await client.generate_content(text_check_prompt, files=[cropped_video_tmp], model=text_check_model)
1761
+ text_data = extract_json_from_text(text_resp.text)
1762
+ has_texts = text_data.get("has_texts", False) if isinstance(text_data, dict) else False
1763
+
1764
+ if has_texts:
1765
+ logger.log("🔍 Gemini detectou textos de edição externa. Iniciando OCR...")
1766
+ # Passo 3: OCR Crop (Apenas se o Gemini confirmou)
1767
+ ocr_video_tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name
1768
+ text_crop_status = detect_and_crop_text(cropped_video_tmp, ocr_video_tmp)
1769
+
1770
+ if text_crop_status == "aborted_area_too_small":
1771
+ logger.log("🚫 Crop abortado: área de texto no centro (segurança). Reprovando filtro.")
1772
+ # Notificação do Agente no Discord
1773
+ try:
1774
+ import urllib.parse as _up
1775
+ reject_msg = f"Analisei esse vídeo aqui e notei que o texto dele está bem no centro. Se eu fizesse o corte agora, ia acabar cortando partes importantes da mensagem. Por isso, decidi reprovar ele automaticamente para manter a qualidade! ❌"
1776
+ _reject_url = "https://discordmsg.arthurmribeiro51.workers.dev/?" + _up.urlencode({"mensagem": reject_msg, "id": discord_id})
1777
+ requests.get("https://proxy.onrecurve.com/", params={"quest": _reject_url}, timeout=5)
1778
+ except Exception as _e_dc:
1779
+ logger.log(f"⚠️ Erro ao enviar Discord de rejeição automática (crop): {_e_dc}")
1780
+
1781
+ requests.patch(f"{supabase_url}/rest/v1/posts?id=eq.{record_id}", headers=headers, json={
1782
+ "approved_filter": False,
1783
+ "filter_message": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado no vídeo).",
1784
+ "result": {"error": "Rejeitado automaticamente: Região útil de texto insuficiente para crop seguro (texto centralizado). Tente outro vídeo."},
1785
+ "contains_image": False
1786
+ })
1787
+ return {"status": "ok", "message": "Postagem rejeitada por crop insuficiente.", "next_steps": {}}
1788
+ elif text_crop_status == "success" and os.path.exists(ocr_video_tmp):
1789
+ # Reemplazamos el motion crop original por el OCR crop
1790
+ if os.path.exists(cropped_video_tmp): os.unlink(cropped_video_tmp)
1791
+ cropped_video_tmp = ocr_video_tmp
1792
+ cropped_file_path = ocr_video_tmp # Update tracking
1793
+ logger.log(f"✅ Vídeo cortado via OCR com sucesso em: {cropped_video_tmp}")
1794
+ else:
1795
+ if os.path.exists(ocr_video_tmp): os.unlink(ocr_video_tmp)
1796
+ logger.log("⚠️ Crop de OCR ignorado ou falhou, mantendo apenas motion crop.")
1797
+ else:
1798
+ logger.log("✅ Gemini NÃO detectou textos de social media. Pulando OCR.")
1799
+ except Exception as ge:
1800
+ logger.log(f"⚠️ Erro na análise Gemini de textos: {ge}. Seguindo apenas com motion crop.")
1801
 
 
 
 
 
 
 
 
 
 
1802
  cropped_file_path = cropped_video_tmp
1803
+ logger.log(f"✅ Vídeo (motion) consolidado em: {cropped_file_path}")
1804
  else:
1805
  logger.log("⚠️ Crop de vídeo não gerou novo arquivo ou foi ignorado.")
1806
  if os.path.exists(cropped_video_tmp): os.unlink(cropped_video_tmp)