Ajustes de parametros e formatos

This commit is contained in:
LeoMortari
2025-09-28 19:03:39 -03:00
parent a0de3536b0
commit dcbd0cb1d2

169
main.py
View File

@@ -146,6 +146,7 @@ def download_video(
): ):
if not url and not videoId: if not url and not videoId:
raise HTTPException(status_code=400, detail="Informe 'url' ou 'videoId'") raise HTTPException(status_code=400, detail="Informe 'url' ou 'videoId'")
if url: if url:
target = url target = url
try: try:
@@ -155,125 +156,107 @@ def download_video(
else: else:
target = f"https://www.youtube.com/watch?v={videoId}" target = f"https://www.youtube.com/watch?v={videoId}"
video_id = videoId video_id = videoId
quality_targets = { quality_targets = {"low": 480, "medium": 720, "high": 1080}
"low": 480,
"medium": 720,
"high": 1080
}
qualidade = qualidade.lower() qualidade = qualidade.lower()
if qualidade not in quality_targets: if qualidade not in quality_targets:
raise HTTPException(status_code=400, detail="Qualidade deve ser: low, medium ou high") raise HTTPException(status_code=400, detail="Qualidade deve ser: low, medium ou high")
target_height = quality_targets[qualidade]
videos_dir = "/app/videos" videos_dir = "/app/videos"
os.makedirs(videos_dir, exist_ok=True) os.makedirs(videos_dir, exist_ok=True)
unique_id = str(uuid.uuid4()) unique_id = str(uuid.uuid4())
output_template = os.path.join(videos_dir, f"{unique_id}.%(ext)s") outtmpl = os.path.join(videos_dir, f"{unique_id}.%(ext)s")
# Expressão de formato robusta:
# 1) Tenta bestvideo até a altura desejada + bestaudio (mp4/m4a quando der)
# 2) Cai para best [altura<=] em um único arquivo
# 3) Finalmente qualquer best disponível
fmt_expr = (
f"bv*[height<={target_height}][ext=mp4]+ba[ext=m4a]/"
f"bv*[height<={target_height}]+ba/"
f"b[height<={target_height}][ext=mp4]/"
f"b[height<={target_height}]/"
f"b"
)
ydl_opts = { ydl_opts = {
'outtmpl': output_template, "outtmpl": outtmpl,
'quiet': True, "quiet": True,
'no_warnings': True, "no_warnings": True,
'ignoreerrors': False, "ignoreerrors": False,
'no_color': True, "no_color": True,
'extract_flat': 'in_playlist', "noplaylist": True, # não tratar como playlist
'force_generic_extractor': True, "format": fmt_expr, # <<< chave da correção
'allow_unplayable_formats': True, "merge_output_format": "mp4", # força saída mp4 quando há merge
# NUNCA usar force_generic_extractor p/ YouTube
# NUNCA usar extract_flat aqui
# NÃO usar allow_unplayable_formats
} }
def get_best_format(ydl, target_height):
info = ydl.extract_info(target, download=False)
if not info or 'formats' not in info:
return 'best'
formats = info['formats']
best_format = None
best_height = 0
for f in formats:
if f.get('height') and f.get('acodec') != 'none':
if f['height'] <= target_height and f['height'] > best_height:
best_format = f
best_height = f['height']
if not best_format:
video_format = None
audio_format = None
for f in formats:
if f.get('vcodec') != 'none' and f.get('acodec') == 'none':
if f.get('height') and f['height'] <= target_height and (not video_format or f['height'] > video_format.get('height', 0)):
video_format = f
for f in formats:
if f.get('acodec') != 'none' and f.get('vcodec') == 'none':
if not audio_format or f.get('tbr', 0) > audio_format.get('tbr', 0):
audio_format = f
if video_format and audio_format:
return f"{video_format['format_id']}+{audio_format['format_id']}"
return best_format['format_id'] if best_format else 'best'
try: try:
with YoutubeDL(ydl_opts) as ydl: with YoutubeDL(ydl_opts) as ydl:
base = ydl.extract_info(target, download=False) info = ydl.extract_info(target, download=False)
if not info:
if not base: raise HTTPException(status_code=404, detail="Não foi possível obter informações do vídeo.")
raise HTTPException(status_code=404, detail="Não foi possível obter informações do vídeo. Verifique a URL ou o ID do vídeo.")
# Se por acaso vier um wrapper de playlist, pegue a primeira entrada
if '_type' in base and base['_type'] == 'playlist': if info.get("_type") == "playlist":
if 'entries' in base and len(base['entries']) > 0: entries = info.get("entries") or []
base = base['entries'][0] if not entries:
else:
raise HTTPException(status_code=404, detail="Nenhum vídeo encontrado na playlist.") raise HTTPException(status_code=404, detail="Nenhum vídeo encontrado na playlist.")
info = entries[0]
title = base.get("title", unique_id)
if not title: title = info.get("title") or unique_id
title = f"video_{unique_id[:8]}"
clean_title = unidecode(title) clean_title = unidecode(title)
clean_title = re.sub(r"[^\w\s-]", "", clean_title) clean_title = re.sub(r"[^\w\s-]", "", clean_title).strip()
clean_title = clean_title.replace(" ", "_") clean_title = re.sub(r"\s+", "_", clean_title)
clean_title = re.sub(r"_+", "_", clean_title)
clean_title = clean_title.strip("_")
filename = f"{clean_title}_{qualidade}.mp4" filename = f"{clean_title}_{qualidade}.mp4"
final_path = os.path.join(videos_dir, filename) final_path = os.path.join(videos_dir, filename)
print('Informações do vídeo obtidas com sucesso')
if os.path.exists(final_path): if os.path.exists(final_path):
return { return {"videoId": video_id, "filename": filename}
"videoId": video_id,
"filename": filename # Baixa de fato
}
print(f'Buscando melhor formato disponível para qualidade: {qualidade}')
best_format = get_best_format(ydl, target_height)
print(f'Melhor formato encontrado: {best_format}')
ydl.params['format'] = best_format
result = ydl.extract_info(target, download=True) result = ydl.extract_info(target, download=True)
if "requested_downloads" in result and len(result["requested_downloads"]) > 0: # Descobre o arquivo gerado
real_file_path = result["requested_downloads"][0]["filepath"] real_file_path = None
elif "filepath" in result: if isinstance(result, dict):
real_file_path = result["filepath"] # yt-dlp costuma preencher requested_downloads
else: reqs = result.get("requested_downloads") or []
real_file_path = output_template.replace("%(ext)s", "mp4") if reqs:
real_file_path = reqs[0].get("filepath")
if not real_file_path:
real_file_path = result.get("filepath")
if not real_file_path:
# fallback bruto para o template com mp4
real_file_path = outtmpl.replace("%(ext)s", "mp4")
if not os.path.exists(real_file_path):
# Ajuda a diagnosticar quando o formato pedido não existe
# (por segurança não expomos toda a lista ao cliente)
raise HTTPException(
status_code=500,
detail="Falha ao localizar o arquivo baixado. O formato selecionado pode não estar disponível para este vídeo."
)
os.rename(real_file_path, final_path) os.rename(real_file_path, final_path)
except HTTPException:
raise
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao baixar vídeo: {e}") # Erros comuns: falta do ffmpeg no container
msg = str(e)
if "ffmpeg" in msg.lower():
msg += " (verifique se o ffmpeg está instalado no container)"
raise HTTPException(status_code=500, detail=f"Erro ao baixar vídeo: {msg}")
return { return {"videoId": video_id, "filename": filename}
"videoId": video_id,
"filename": filename
}
@app.get("/search") @app.get("/search")
def search_youtube_yt_dlp( def search_youtube_yt_dlp(