travahacker
feat: timestamps [MM:SS] na transcrição para localizar no vídeo
46b613a
"""
Transcrição de áudio — ZeroGPU Space
Envie um arquivo de áudio (mp3, wav, m4a, etc.) e transcreva com Whisper.
"""
import tempfile
from pathlib import Path
import gradio as gr
# ZeroGPU: decorator é no-op fora do HF
try:
import spaces
except ImportError:
class _Spaces:
def GPU(self, fn=None, **kwargs):
def decorator(f):
return f
return decorator(fn) if fn else decorator
spaces = _Spaces()
def _fmt_tempo(segundos: float) -> str:
"""Formata segundos como MM:SS ou HH:MM:SS."""
h = int(segundos // 3600)
m = int((segundos % 3600) // 60)
s = int(segundos % 60)
if h > 0:
return f"{h:01d}:{m:02d}:{s:02d}"
return f"{m:01d}:{s:02d}"
@spaces.GPU(duration=180)
def transcrever(audio, modelo: str, idioma: str) -> str:
"""Transcreve áudio enviado com Whisper."""
try:
from faster_whisper import WhisperModel
path = None
if isinstance(audio, str) and audio and Path(audio).exists():
path = str(audio)
elif hasattr(audio, "name"):
p = getattr(audio, "name", None)
if p and Path(str(p)).exists():
path = str(p)
elif isinstance(audio, bytes) and audio:
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
f.write(audio)
path = f.name
if not path:
return "❌ Envie um arquivo de áudio."
model = WhisperModel(modelo, device="cuda", compute_type="float16")
lang = None if idioma == "Auto" else idioma.lower()
segments, info = model.transcribe(
path,
language=lang,
beam_size=5,
vad_filter=True,
)
resultado = []
for seg in segments:
resultado.append({
"start": seg.start,
"end": seg.end,
"text": seg.text.strip(),
})
# Timestamp + parágrafos: [MM:SS] texto, linha em branco quando pausa > 1.5s
PAUSA_PARAGRAFO = 1.5
linhas = []
prev_end = 0
for s in resultado:
if not s["text"]:
continue
if prev_end > 0 and (s["start"] - prev_end) > PAUSA_PARAGRAFO:
linhas.append("") # linha em branco = novo parágrafo
ts = _fmt_tempo(s["start"])
linhas.append(f"[{ts}] {s['text']}")
prev_end = s["end"]
texto = "\n".join(linhas)
if not texto:
return "⚠️ Nenhum texto transcrito (áudio sem fala?)."
return f"Idioma detectado: {info.language}\n\n{texto}"
except Exception as e:
return f"❌ Erro: {type(e).__name__}: {e}"
MODELOS = ["tiny", "base", "small", "medium", "large-v3"]
IDIOMAS = ["Auto", "pt", "en", "es", "fr"]
with gr.Blocks(
title="Transcrição de áudio",
theme=gr.themes.Soft(),
) as demo:
gr.Markdown("# 🎙️ Transcrição de áudio")
gr.Markdown(
"Envie um arquivo de áudio (mp3, wav, m4a, webm, etc.) e transcreva com Whisper. "
"**ZeroGPU** — processamento gratuito na nuvem."
)
with gr.Row():
audio = gr.File(
label="Áudio",
file_types=[".mp3", ".wav", ".m4a", ".webm", ".opus", ".ogg", ".flac"],
)
with gr.Row():
modelo = gr.Dropdown(
label="Modelo Whisper",
choices=MODELOS,
value="small",
info="small = bom equilíbrio; large-v3 = mais preciso (mais lento)",
)
idioma = gr.Dropdown(
label="Idioma",
choices=IDIOMAS,
value="Auto",
)
btn = gr.Button("Transcrever", variant="primary")
saida = gr.Textbox(
label="Transcrição",
lines=15,
max_lines=30,
)
btn.click(
fn=transcrever,
inputs=[audio, modelo, idioma],
outputs=saida,
)
gr.Markdown("---")
gr.Markdown(
"A transcrição pode levar 1–2 min para iniciar (fila da GPU)."
)
demo.launch(show_error=True, share=True)