travahacker commited on
Commit
8985e44
·
1 Parent(s): 31cb808

feat: usa legendas do YouTube como método principal (funciona para qualquer pessoa)

Browse files

- youtube-transcript-api para vídeos com legendas (sem 403)
- fallback: yt-dlp + Whisper para vídeos sem legendas

Files changed (3) hide show
  1. README.md +8 -6
  2. app.py +75 -18
  3. requirements.txt +2 -1
README.md CHANGED
@@ -6,21 +6,23 @@ 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
 
 
6
  sdk: gradio
7
  sdk_version: 4.44.0
8
  app_file: app.py
 
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
+ # Transcrição YouTube
14
 
15
+ Cola o link do YouTube e transcreve. **Funciona para qualquer pessoa**, de qualquer computador.
16
+
17
+ - **Legendas do YouTube** (prioridade): usa legendas quando disponíveis — rápido, sem download
18
+ - **Whisper** (fallback): quando o vídeo não tem legendas, usa yt-dlp + Whisper na GPU
19
 
20
  ## Como usar
21
 
22
+ 1. Cole o link do vídeo (ex: https://youtu.be/HidI2dvdTMw)
23
+ 2. Escolha o idioma preferido (ou Auto)
24
  3. Clique em Transcrever
25
+ 4. Se houver legendas: resultado em segundos. Sem legendas: aguarde a fila da GPU (1–2 min)
26
 
27
  ## Requisitos
28
 
app.py CHANGED
@@ -1,8 +1,10 @@
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
@@ -21,16 +23,55 @@ except ImportError:
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
@@ -41,22 +82,35 @@ def baixar_audio(url: str, pasta: Path) -> Path:
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()
@@ -80,7 +134,7 @@ def transcrever_gpu(url: str, modelo: str, idioma: str) -> str:
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"]
@@ -91,12 +145,15 @@ with gr.Blocks(
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():
@@ -121,7 +178,7 @@ with gr.Blocks(
121
  )
122
 
123
  btn.click(
124
- fn=transcrever_gpu,
125
  inputs=[url, modelo, idioma],
126
  outputs=saida,
127
  )
 
1
  """
2
+ Transcrição YouTube — ZeroGPU Space
3
 
4
+ Cola o link, transcreve. Usa legendas do YouTube quando disponíveis (rápido, sem GPU).
5
+ Fallback: Whisper quando o vídeo não tem legendas.
6
  """
7
+ import re
8
  import subprocess
9
  import tempfile
10
  from pathlib import Path
 
23
  spaces = _Spaces()
24
 
25
 
26
+ def extrair_video_id(url: str) -> str | None:
27
+ """Extrai o ID do vídeo de uma URL do YouTube."""
28
+ if not url or ("youtube.com" not in url and "youtu.be" not in url):
29
+ return None
30
+ m = re.search(r"(?:youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})", url)
31
+ return m.group(1) if m else None
32
+
33
+
34
+ def transcrever_via_legendas(video_id: str, idioma: str) -> tuple[str | None, str | None]:
35
+ """
36
+ Busca legendas diretamente do YouTube (sem baixar vídeo).
37
+ Retorna (texto, None) em sucesso ou (None, mensagem_erro) em falha.
38
+ """
39
+ from youtube_transcript_api import YouTubeTranscriptApi
40
+ from youtube_transcript_api._errors import NoTranscriptFound, RequestBlocked, IpBlocked
41
+
42
+ lang_codes = []
43
+ if idioma != "Auto":
44
+ lang_codes = [idioma.lower()]
45
+ lang_codes.extend(["pt", "en", "es", "fr"]) # fallbacks
46
+
47
+ try:
48
+ api = YouTubeTranscriptApi()
49
+ transcript = api.fetch(video_id, languages=lang_codes)
50
+ linhas = [s.text for s in transcript if s.text.strip()]
51
+ return "\n".join(linhas), None
52
+ except NoTranscriptFound:
53
+ return None, "Este vídeo não tem legendas disponíveis."
54
+ except (RequestBlocked, IpBlocked):
55
+ return None, "YouTube bloqueou o acesso (IP de datacenter). Tente mais tarde."
56
+ except Exception as e:
57
+ return None, str(e)
58
+
59
+
60
  def baixar_audio(url: str, pasta: Path) -> Path:
61
+ """Baixa áudio do YouTube com yt-dlp (fallback quando não há legendas)."""
62
  pasta.mkdir(parents=True, exist_ok=True)
63
  out = pasta / "audio.%(ext)s"
64
  cmd = [
65
+ "yt-dlp", "-x", "--audio-format", "m4a",
66
+ "-o", str(out), "--no-playlist", "--no-warnings",
67
+ "--no-check-certificate",
68
+ url,
69
  ]
70
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
71
+ if result.returncode != 0:
72
+ err = (result.stderr or result.stdout or "").strip()
73
+ raise RuntimeError(f"yt-dlp falhou: {err or 'erro desconhecido'}")
74
+ for ext in [".m4a", ".opus", ".webm", ".wav", ".mp3"]:
75
  p = pasta / f"audio{ext}"
76
  if p.exists():
77
  return p
 
82
 
83
 
84
  @spaces.GPU(duration=180)
85
+ def transcrever(url: str, modelo: str, idioma: str) -> str:
86
  """
87
+ Transcreve vídeo do YouTube.
88
+ 1. Tenta legendas do YouTube (rápido, sem GPU, funciona para qualquer pessoa)
89
+ 2. Se não houver legendas, usa yt-dlp + Whisper (GPU)
90
  """
91
+ video_id = extrair_video_id(url)
92
+ if not video_id:
 
93
  return "❌ Cole um link válido do YouTube."
94
 
95
+ # 1. Tentar legendas primeiro (funciona para ~85% dos vídeos, qualquer usuário)
96
+ texto, erro = transcrever_via_legendas(video_id, idioma)
97
+ if texto:
98
+ return f"📋 Legendas do YouTube\n\n{texto}"
99
+
100
+ # 2. Fallback: yt-dlp + Whisper (pode falhar com 403 em alguns ambientes)
101
+ return transcrever_com_whisper(url, modelo, idioma, msg_sem_legendas=erro)
102
+
103
+
104
+ def transcrever_com_whisper(url: str, modelo: str, idioma: str, msg_sem_legendas: str = "") -> str:
105
+ """Transcreve com Whisper (baixa áudio via yt-dlp). Usa GPU."""
106
+ from faster_whisper import WhisperModel
107
+
108
  with tempfile.TemporaryDirectory() as tmpdir:
109
  pasta = Path(tmpdir)
110
  try:
111
  audio_path = baixar_audio(url, pasta)
112
  except Exception as e:
113
+ return f"❌ Erro ao baixar: {e}\n\n💡 {msg_sem_legendas or 'Vídeo sem legendas — o download pode falhar (403) em servidores.'}"
114
 
115
  model = WhisperModel(modelo, device="cuda", compute_type="float16")
116
  lang = None if idioma == "Auto" else idioma.lower()
 
134
  if not texto:
135
  return "⚠️ Nenhum texto transcrito (vídeo sem fala?)."
136
 
137
+ return f"🎙️ Whisper — Idioma detectado: {info.language}\n\n{texto}"
138
 
139
 
140
  MODELOS = ["tiny", "base", "small", "medium", "large-v3"]
 
145
  theme=gr.themes.Soft(),
146
  ) as demo:
147
  gr.Markdown("# 🎙️ Transcrição YouTube")
148
+ gr.Markdown(
149
+ "Cola o link e transcreve. **Legendas do YouTube** quando disponíveis (rápido, qualquer vídeo). "
150
+ "Sem legendas: usa Whisper (ZeroGPU)."
151
+ )
152
 
153
  with gr.Row():
154
  url = gr.Textbox(
155
  label="Link do YouTube",
156
+ placeholder="https://youtu.be/HidI2dvdTMw",
157
  scale=3,
158
  )
159
  with gr.Row():
 
178
  )
179
 
180
  btn.click(
181
+ fn=transcrever,
182
  inputs=[url, modelo, idioma],
183
  outputs=saida,
184
  )
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  gradio>=4.0.0
2
  huggingface_hub>=0.20.0,<0.23.0
3
  faster-whisper>=1.0.0
4
- yt-dlp>=2024.1.0
 
 
1
  gradio>=4.0.0
2
  huggingface_hub>=0.20.0,<0.23.0
3
  faster-whisper>=1.0.0
4
+ yt-dlp>=2024.12.0
5
+ youtube-transcript-api>=1.2.0