#!/usr/bin/env python3 """ YouTube 视频下载器 使用 Savenow API 下载 YouTube 视频/音频 使用方法: python youtube_dl.py [format] 示例: python youtube_dl.py "https://www.youtube.com/watch?v=xxx" mp3 python youtube_dl.py "https://www.youtube.com/watch?v=xxx" 1080 """ import requests import time import sys import os import re import json API_KEY = "dfcb6d76f2f6a9894gjkege8a4ab232222" API_BASES = ["https://p.savenow.to", "https://p.lbserver.xyz"] VIDEO_FORMATS = { "144": "144p Mp4", "240": "240p Mp4", "360": "360p Mp4", "480": "480p Mp4", "720": "720p HD Mp4", "1080": "1080p FULL HD Mp4", "4k": "2160p 4K WEBM", "8k": "4320p 8K WEBM", } AUDIO_FORMATS = { "mp3": "Audio MP3", "m4a": "Audio M4A", "flac": "Audio FLAC", "wav": "Audio WAV", "aac": "Audio AAC", "opus": "Audio OPUS", "ogg": "Audio OGG", } def get_title_from_response(response_text): """从 API 响应中提取视频标题""" try: match = re.search(rb'"title":"([^"]+)"', response_text.encode("utf-8")) if match: escaped = match.group(1).decode("ascii") title = bytes(escaped, "ascii").decode("unicode_escape") return title except: pass return None def request_download(url, format="mp3"): """请求下载,返回 progress_id""" params = { "copyright": "0", "allow_extended_duration": "1", "format": format, "url": url, "api": API_KEY, } for base in API_BASES: try: resp = requests.get(f"{base}/ajax/download.php", params=params, timeout=30) data = resp.json() if data.get("success"): return ( data.get("id"), data.get("progress_url"), data.get("info", {}).get("title", ""), ) except Exception as e: print(f"尝试 {base} 失败: {e}") continue raise Exception("所有 API 都失败了") def poll_progress(progress_url, title): """轮询直到下载完成,返回下载链接""" print("等待处理中...", end="", flush=True) while True: time.sleep(3) resp = requests.get(progress_url, timeout=30) data = resp.json() progress = data.get("progress", 0) / 10 status = data.get("success", 0) if status == 1 and progress == 100: return data.get("download_url"), data.get("alternative_download_urls", []) print(".", end="", flush=True) return None, [] def download_file(download_url, output_path): """下载文件""" print(f"\n正在下载: {output_path}") resp = requests.get(download_url, stream=True) total = int(resp.headers.get("content-length", 0)) downloaded = 0 with open(output_path, "wb") as f: for chunk in resp.iter_content(chunk_size=8192): if chunk: f.write(chunk) downloaded += len(chunk) if total: percent = downloaded * 100 / total print(f"\r进度: {percent:.1f}%", end="", flush=True) print("\n下载完成!") return output_path def main(): if len(sys.argv) < 2: print(__doc__) print("\n可用格式:") print("视频:", ", ".join(VIDEO_FORMATS.keys())) print("音频:", ", ".join(AUDIO_FORMATS.keys())) sys.exit(1) url = sys.argv[1] format = sys.argv[2] if len(sys.argv) > 2 else "mp3" # 获取标题 print(f"请求下载: {url}") print(f"格式: {format}") progress_id, progress_url, title = request_download(url, format) print(f"视频标题: {title}") # 轮询下载链接 download_url, alternatives = poll_progress(progress_url, title) if not download_url and alternatives: download_url = alternatives[0].get("url") if not download_url: print("获取下载链接失败!") sys.exit(1) # 确定文件扩展名 ext = ( format if format in ["mp3", "m4a", "flac", "wav", "aac", "opus", "ogg"] else "mp4" ) # 生成文件名 safe_title = re.sub(r'[\\/:*?"<>|]', "_", title or "video") output_path = f"{safe_title}({format}p).{ext}" # 下载 download_file(download_url, output_path) print(f"已保存: {output_path}") if __name__ == "__main__": main()