Update app.py
Browse files
app.py
CHANGED
|
@@ -699,76 +699,110 @@ def groq_json_to_srt(data):
|
|
| 699 |
|
| 700 |
return srt_output
|
| 701 |
|
| 702 |
-
from srt_utils import apply_netflix_style_filter
|
| 703 |
|
| 704 |
async def get_groq_srt_base(url: str, language: Optional[str] = None, temperature: Optional[float] = 0.4):
|
| 705 |
"""
|
| 706 |
Helper para gerar SRT base usando Groq (dando suporte a filtro Netflix).
|
| 707 |
Retorna (srt_filtered, srt_word_level)
|
|
|
|
| 708 |
"""
|
| 709 |
if not url:
|
| 710 |
raise HTTPException(status_code=400, detail="URL é obrigatória para processamento Groq")
|
| 711 |
|
| 712 |
-
#
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
"model": (None, "whisper-large-v3"),
|
| 720 |
-
"url": (None, url),
|
| 721 |
-
"temperature": (None, str(temperature)),
|
| 722 |
-
"timestamp_granularities[]": (None, "word"),
|
| 723 |
-
"response_format": (None, "verbose_json")
|
| 724 |
-
}
|
| 725 |
-
|
| 726 |
-
if language and language in GROQ_SUPPORTED_LANGUAGES:
|
| 727 |
-
files["language"] = (None, language)
|
| 728 |
-
else:
|
| 729 |
-
# Se language não for suportado ou None, não envia (auto-detect)
|
| 730 |
-
if language:
|
| 731 |
-
print(f"⚠️ Linguagem '{language}' não suportada ou código inválido. Enviando sem language.")
|
| 732 |
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
|
| 740 |
-
if
|
| 741 |
-
|
| 742 |
-
break
|
| 743 |
|
| 744 |
-
|
| 745 |
-
error_msg = response_groq.text.lower()
|
| 746 |
-
is_deadline_error = "context deadline exceeded" in error_msg
|
| 747 |
-
is_server_error = response_groq.status_code >= 500
|
| 748 |
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
print(f"⚠️ Erro transiente Groq ({response_groq.status_code}): {error_msg[:100]}... Tentando novamente em {wait_time}s...")
|
| 752 |
-
await asyncio.sleep(wait_time)
|
| 753 |
-
|
| 754 |
-
# Reset files pointer if needed (though for URL it's fine, but if we sent file-like obj we'd need seek(0))
|
| 755 |
-
# Since we send tuples with strings/None, we don't need to reset anything for 'files' dict here.
|
| 756 |
-
continue
|
| 757 |
-
|
| 758 |
-
raise HTTPException(status_code=response_groq.status_code, detail=f"Erro Groq: {response_groq.text}")
|
| 759 |
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
|
| 767 |
-
|
| 768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
srt_word = groq_json_to_srt(result)
|
| 770 |
-
|
| 771 |
-
# Aplicar filtro Netflix (Merge e Fix Overlaps)
|
| 772 |
srt_filtered = apply_netflix_style_filter(srt_word)
|
| 773 |
|
| 774 |
return srt_filtered, srt_word
|
|
|
|
| 699 |
|
| 700 |
return srt_output
|
| 701 |
|
| 702 |
+
from srt_utils import apply_netflix_style_filter, process_audio_for_transcription
|
| 703 |
|
| 704 |
async def get_groq_srt_base(url: str, language: Optional[str] = None, temperature: Optional[float] = 0.4):
|
| 705 |
"""
|
| 706 |
Helper para gerar SRT base usando Groq (dando suporte a filtro Netflix).
|
| 707 |
Retorna (srt_filtered, srt_word_level)
|
| 708 |
+
Agora faz download e pré-processamento do áudio localmente para melhorar qualidade.
|
| 709 |
"""
|
| 710 |
if not url:
|
| 711 |
raise HTTPException(status_code=400, detail="URL é obrigatória para processamento Groq")
|
| 712 |
|
| 713 |
+
# 1. Baixar arquivo
|
| 714 |
+
print(f"⬇️ [Groq] Baixando arquivo para pré-processamento...")
|
| 715 |
+
try:
|
| 716 |
+
response = download_file_with_retry(url)
|
| 717 |
+
except Exception as e:
|
| 718 |
+
print(f"⚠️ Falha ao baixar arquivo para Groq: {e}")
|
| 719 |
+
raise HTTPException(status_code=400, detail=f"Falha ao baixar arquivo: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 720 |
|
| 721 |
+
# Salvar temp
|
| 722 |
+
content_type = response.headers.get('content-type', '').lower()
|
| 723 |
+
ext = '.mp3' # Default fallback
|
| 724 |
+
if 'video' in content_type: ext = '.mp4'
|
| 725 |
+
elif 'audio' in content_type: ext = '.mp3'
|
| 726 |
|
| 727 |
+
temp_input = tempfile.NamedTemporaryFile(delete=False, suffix=ext)
|
| 728 |
+
try:
|
| 729 |
+
for chunk in response.iter_content(chunk_size=8192):
|
| 730 |
+
if chunk:
|
| 731 |
+
temp_input.write(chunk)
|
| 732 |
+
temp_input.close()
|
| 733 |
+
|
| 734 |
+
# 2. Pré-processar (Remover ruído, filtrar voz, etc)
|
| 735 |
+
print(f"🔊 [Groq] Pré-processando áudio (Isolamento de voz + Highpass/Lowpass)...")
|
| 736 |
+
processed_file_path = process_audio_for_transcription(temp_input.name)
|
| 737 |
+
|
| 738 |
+
# 3. Enviar para Groq
|
| 739 |
+
groq_url = "https://api.groq.com/openai/v1/audio/transcriptions"
|
| 740 |
+
headers = {
|
| 741 |
+
"Authorization": f"Bearer {GROQ_API_KEY}"
|
| 742 |
+
}
|
| 743 |
+
|
| 744 |
+
# Abrir arquivo processado
|
| 745 |
+
with open(processed_file_path, "rb") as f:
|
| 746 |
+
files = {
|
| 747 |
+
"model": (None, "whisper-large-v3"),
|
| 748 |
+
"file": ("audio.mp3", f, "audio/mpeg"), # Mandar como file
|
| 749 |
+
"temperature": (None, str(temperature)),
|
| 750 |
+
"timestamp_granularities[]": (None, "word"),
|
| 751 |
+
"response_format": (None, "verbose_json")
|
| 752 |
+
}
|
| 753 |
|
| 754 |
+
if language and language in GROQ_SUPPORTED_LANGUAGES:
|
| 755 |
+
files["language"] = (None, language)
|
|
|
|
| 756 |
|
| 757 |
+
print(f"🧠 [Groq] Enviando ÁUDIO PROCESSADO para API...")
|
|
|
|
|
|
|
|
|
|
| 758 |
|
| 759 |
+
max_retries = 3
|
| 760 |
+
result = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
|
| 762 |
+
for attempt in range(max_retries):
|
| 763 |
+
try:
|
| 764 |
+
# Precisamos resetar o ponteiro do arquivo se for retry?
|
| 765 |
+
# O requests deve ler tudo. Se falhar, na proxima tentativa, o 'f' ja foi lido.
|
| 766 |
+
# Mover seek(0) é importante.
|
| 767 |
+
f.seek(0)
|
| 768 |
+
|
| 769 |
+
response_groq = requests.post(groq_url, headers=headers, files=files, timeout=300)
|
| 770 |
+
|
| 771 |
+
if response_groq.status_code == 200:
|
| 772 |
+
result = response_groq.json()
|
| 773 |
+
break
|
| 774 |
+
|
| 775 |
+
error_msg = response_groq.text.lower()
|
| 776 |
+
is_deadline = "context deadline exceeded" in error_msg
|
| 777 |
+
is_server = response_groq.status_code >= 500
|
| 778 |
+
|
| 779 |
+
if (is_deadline or is_server) and attempt < max_retries - 1:
|
| 780 |
+
wait_time = 2 * (attempt + 1)
|
| 781 |
+
print(f"⚠️ Erro transiente Groq ({response_groq.status_code}). Retentando em {wait_time}s...")
|
| 782 |
+
await asyncio.sleep(wait_time)
|
| 783 |
+
continue
|
| 784 |
+
|
| 785 |
+
raise HTTPException(status_code=response_groq.status_code, detail=f"Erro Groq: {response_groq.text}")
|
| 786 |
+
|
| 787 |
+
except requests.RequestException as e:
|
| 788 |
+
if attempt < max_retries - 1:
|
| 789 |
+
print(f"⚠️ Erro conexão Groq. Retentando...")
|
| 790 |
+
await asyncio.sleep(2)
|
| 791 |
+
continue
|
| 792 |
+
raise HTTPException(status_code=500, detail=f"Erro conexão Groq: {e}")
|
| 793 |
|
| 794 |
+
finally:
|
| 795 |
+
# Cleanup
|
| 796 |
+
if os.path.exists(temp_input.name):
|
| 797 |
+
try: os.unlink(temp_input.name)
|
| 798 |
+
except: pass
|
| 799 |
+
# Se process_audio criou um arquivo novo (com sufixo .processed.mp3)
|
| 800 |
+
if 'processed_file_path' in locals() and processed_file_path != temp_input.name and os.path.exists(processed_file_path):
|
| 801 |
+
try: os.unlink(processed_file_path)
|
| 802 |
+
except: pass
|
| 803 |
+
|
| 804 |
+
# Converter para SRT
|
| 805 |
srt_word = groq_json_to_srt(result)
|
|
|
|
|
|
|
| 806 |
srt_filtered = apply_netflix_style_filter(srt_word)
|
| 807 |
|
| 808 |
return srt_filtered, srt_word
|