From dcbd0cb1d2dc8a73ebe1760777133ae0adeacf8a Mon Sep 17 00:00:00 2001 From: LeoMortari Date: Sun, 28 Sep 2025 19:03:39 -0300 Subject: [PATCH] Ajustes de parametros e formatos --- main.py | 169 +++++++++++++++++++++++++------------------------------- 1 file changed, 76 insertions(+), 93 deletions(-) diff --git a/main.py b/main.py index d3e211f..e74d7af 100644 --- a/main.py +++ b/main.py @@ -146,6 +146,7 @@ def download_video( ): if not url and not videoId: raise HTTPException(status_code=400, detail="Informe 'url' ou 'videoId'") + if url: target = url try: @@ -155,125 +156,107 @@ def download_video( else: target = f"https://www.youtube.com/watch?v={videoId}" video_id = videoId - - quality_targets = { - "low": 480, - "medium": 720, - "high": 1080 - } - + + quality_targets = {"low": 480, "medium": 720, "high": 1080} qualidade = qualidade.lower() + if qualidade not in quality_targets: raise HTTPException(status_code=400, detail="Qualidade deve ser: low, medium ou high") - + + target_height = quality_targets[qualidade] + videos_dir = "/app/videos" os.makedirs(videos_dir, exist_ok=True) 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 = { - 'outtmpl': output_template, - 'quiet': True, - 'no_warnings': True, - 'ignoreerrors': False, - 'no_color': True, - 'extract_flat': 'in_playlist', - 'force_generic_extractor': True, - 'allow_unplayable_formats': True, + "outtmpl": outtmpl, + "quiet": True, + "no_warnings": True, + "ignoreerrors": False, + "no_color": True, + "noplaylist": True, # não tratar como playlist + "format": fmt_expr, # <<< chave da correção + "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: with YoutubeDL(ydl_opts) as ydl: - base = ydl.extract_info(target, download=False) - - if not base: - 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.") - - if '_type' in base and base['_type'] == 'playlist': - if 'entries' in base and len(base['entries']) > 0: - base = base['entries'][0] - else: + info = ydl.extract_info(target, download=False) + if not info: + raise HTTPException(status_code=404, detail="Não foi possível obter informações do vídeo.") + + # Se por acaso vier um wrapper de playlist, pegue a primeira entrada + if info.get("_type") == "playlist": + entries = info.get("entries") or [] + if not entries: raise HTTPException(status_code=404, detail="Nenhum vídeo encontrado na playlist.") - - title = base.get("title", unique_id) - if not title: - title = f"video_{unique_id[:8]}" - + info = entries[0] + + title = info.get("title") or unique_id clean_title = unidecode(title) - clean_title = re.sub(r"[^\w\s-]", "", clean_title) - clean_title = clean_title.replace(" ", "_") - clean_title = re.sub(r"_+", "_", clean_title) - clean_title = clean_title.strip("_") + clean_title = re.sub(r"[^\w\s-]", "", clean_title).strip() + clean_title = re.sub(r"\s+", "_", clean_title) filename = f"{clean_title}_{qualidade}.mp4" final_path = os.path.join(videos_dir, filename) - print('Informações do vídeo obtidas com sucesso') - if os.path.exists(final_path): - return { - "videoId": video_id, - "filename": filename - } - - 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 + return {"videoId": video_id, "filename": filename} + + # Baixa de fato result = ydl.extract_info(target, download=True) - if "requested_downloads" in result and len(result["requested_downloads"]) > 0: - real_file_path = result["requested_downloads"][0]["filepath"] - elif "filepath" in result: - real_file_path = result["filepath"] - else: - real_file_path = output_template.replace("%(ext)s", "mp4") + # Descobre o arquivo gerado + real_file_path = None + if isinstance(result, dict): + # yt-dlp costuma preencher requested_downloads + reqs = result.get("requested_downloads") or [] + 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) + except HTTPException: + raise 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 { - "videoId": video_id, - "filename": filename - } + return {"videoId": video_id, "filename": filename} @app.get("/search") def search_youtube_yt_dlp(