travahacker commited on
Commit ·
18fd051
1
Parent(s): 8ceb1f3
Add transcrição YouTube com Whisper (ZeroGPU)
Browse files- README.md +27 -7
- app.py +135 -0
- packages.txt +1 -0
- requirements.txt +3 -0
README.md
CHANGED
|
@@ -1,13 +1,33 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
|
|
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Transcrição YouTube
|
| 3 |
+
emoji: 🎙️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
+
hardware: zerogpu
|
| 10 |
pinned: false
|
| 11 |
+
license: mit
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# Transcrição YouTube com Whisper
|
| 15 |
+
|
| 16 |
+
Cola o link do YouTube, escolhe o modelo Whisper e transcreve. **100% local na GPU** (ZeroGPU).
|
| 17 |
+
|
| 18 |
+
## Como usar
|
| 19 |
+
|
| 20 |
+
1. Cole o link do vídeo
|
| 21 |
+
2. Escolha o modelo (small = bom equilíbrio)
|
| 22 |
+
3. Clique em Transcrever
|
| 23 |
+
4. Aguarde (pode levar 1–2 min na fila da GPU)
|
| 24 |
+
|
| 25 |
+
## Requisitos
|
| 26 |
+
|
| 27 |
+
- **ZeroGPU**: Este Space usa ZeroGPU. Ao criar, selecione **ZeroGPU** no hardware.
|
| 28 |
+
- **Conta PRO** necessária para *criar* Spaces ZeroGPU. Qualquer um pode *usar* o Space.
|
| 29 |
+
|
| 30 |
+
## Quota
|
| 31 |
+
|
| 32 |
+
- Conta grátis: ~3.5 min de GPU/dia
|
| 33 |
+
- PRO: ~25 min/dia
|
app.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Transcrição YouTube com Whisper — ZeroGPU Space
|
| 3 |
+
|
| 4 |
+
Cola o link, escolhe o modelo, transcreve. Usa ZeroGPU para processamento.
|
| 5 |
+
"""
|
| 6 |
+
import subprocess
|
| 7 |
+
import tempfile
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
import gradio as gr
|
| 11 |
+
|
| 12 |
+
# ZeroGPU: decorator é no-op fora do HF
|
| 13 |
+
try:
|
| 14 |
+
import spaces
|
| 15 |
+
except ImportError:
|
| 16 |
+
class _Spaces:
|
| 17 |
+
def GPU(self, fn=None, **kwargs):
|
| 18 |
+
def decorator(f):
|
| 19 |
+
return f
|
| 20 |
+
return decorator(fn) if fn else decorator
|
| 21 |
+
spaces = _Spaces()
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def baixar_audio(url: str, pasta: Path) -> Path:
|
| 25 |
+
"""Baixa áudio do YouTube com yt-dlp."""
|
| 26 |
+
pasta.mkdir(parents=True, exist_ok=True)
|
| 27 |
+
out = pasta / "audio.%(ext)s"
|
| 28 |
+
cmd = [
|
| 29 |
+
"yt-dlp", "-x", "--audio-format", "wav", "--audio-quality", "0",
|
| 30 |
+
"-o", str(out), "--no-playlist", url,
|
| 31 |
+
]
|
| 32 |
+
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
| 33 |
+
for ext in [".wav", ".m4a", ".webm", ".opus"]:
|
| 34 |
+
p = pasta / f"audio{ext}"
|
| 35 |
+
if p.exists():
|
| 36 |
+
return p
|
| 37 |
+
for f in pasta.iterdir():
|
| 38 |
+
if f.suffix.lower() in (".wav", ".m4a", ".webm", ".opus", ".mp3"):
|
| 39 |
+
return f
|
| 40 |
+
raise FileNotFoundError("Áudio não encontrado após download")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@spaces.GPU(duration=180)
|
| 44 |
+
def transcrever_gpu(url: str, modelo: str, idioma: str) -> str:
|
| 45 |
+
"""
|
| 46 |
+
Transcreve vídeo do YouTube. Roda na GPU (ZeroGPU).
|
| 47 |
+
duration=180: vídeos até ~3min; aumente para vídeos mais longos.
|
| 48 |
+
"""
|
| 49 |
+
from faster_whisper import WhisperModel
|
| 50 |
+
|
| 51 |
+
if not url or ("youtube.com" not in url and "youtu.be" not in url):
|
| 52 |
+
return "❌ Cole um link válido do YouTube."
|
| 53 |
+
|
| 54 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 55 |
+
pasta = Path(tmpdir)
|
| 56 |
+
try:
|
| 57 |
+
audio_path = baixar_audio(url, pasta)
|
| 58 |
+
except Exception as e:
|
| 59 |
+
return f"❌ Erro ao baixar: {e}"
|
| 60 |
+
|
| 61 |
+
model = WhisperModel(modelo, device="cuda", compute_type="float16")
|
| 62 |
+
lang = None if idioma == "Auto" else idioma.lower()
|
| 63 |
+
|
| 64 |
+
segments, info = model.transcribe(
|
| 65 |
+
str(audio_path),
|
| 66 |
+
language=lang,
|
| 67 |
+
beam_size=5,
|
| 68 |
+
vad_filter=True,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
resultado = []
|
| 72 |
+
for seg in segments:
|
| 73 |
+
resultado.append({
|
| 74 |
+
"start": seg.start,
|
| 75 |
+
"end": seg.end,
|
| 76 |
+
"text": seg.text.strip(),
|
| 77 |
+
})
|
| 78 |
+
|
| 79 |
+
texto = "\n".join(s["text"] for s in resultado if s["text"])
|
| 80 |
+
if not texto:
|
| 81 |
+
return "⚠️ Nenhum texto transcrito (vídeo sem fala?)."
|
| 82 |
+
|
| 83 |
+
return f"Idioma detectado: {info.language}\n\n{texto}"
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
MODELOS = ["tiny", "base", "small", "medium", "large-v3"]
|
| 87 |
+
IDIOMAS = ["Auto", "pt", "en", "es", "fr"]
|
| 88 |
+
|
| 89 |
+
with gr.Blocks(
|
| 90 |
+
title="Transcrição YouTube",
|
| 91 |
+
theme=gr.themes.Soft(),
|
| 92 |
+
) as demo:
|
| 93 |
+
gr.Markdown("# 🎙️ Transcrição YouTube")
|
| 94 |
+
gr.Markdown("Cola o link, escolhe o modelo Whisper. **ZeroGPU** — processamento gratuito na nuvem.")
|
| 95 |
+
|
| 96 |
+
with gr.Row():
|
| 97 |
+
url = gr.Textbox(
|
| 98 |
+
label="Link do YouTube",
|
| 99 |
+
placeholder="https://www.youtube.com/watch?v=...",
|
| 100 |
+
scale=3,
|
| 101 |
+
)
|
| 102 |
+
with gr.Row():
|
| 103 |
+
modelo = gr.Dropdown(
|
| 104 |
+
label="Modelo Whisper",
|
| 105 |
+
choices=MODELOS,
|
| 106 |
+
value="small",
|
| 107 |
+
info="small = bom equilíbrio; large-v3 = mais preciso (mais lento)",
|
| 108 |
+
)
|
| 109 |
+
idioma = gr.Dropdown(
|
| 110 |
+
label="Idioma",
|
| 111 |
+
choices=IDIOMAS,
|
| 112 |
+
value="Auto",
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
btn = gr.Button("Transcrever", variant="primary")
|
| 116 |
+
|
| 117 |
+
saida = gr.Textbox(
|
| 118 |
+
label="Transcrição",
|
| 119 |
+
lines=15,
|
| 120 |
+
max_lines=30,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
btn.click(
|
| 124 |
+
fn=transcrever_gpu,
|
| 125 |
+
inputs=[url, modelo, idioma],
|
| 126 |
+
outputs=saida,
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
gr.Markdown("---")
|
| 130 |
+
gr.Markdown(
|
| 131 |
+
"**Uso de quota ZeroGPU:** ~3.5 min/dia (conta grátis). "
|
| 132 |
+
"A transcrição pode levar 1–2 min para iniciar (fila da GPU)."
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
demo.launch()
|
packages.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ffmpeg
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
| 2 |
+
faster-whisper>=1.0.0
|
| 3 |
+
yt-dlp>=2024.1.0
|