04db423416
- 70 skills with code and documentation - Add .gitignore (ignore __pycache__, output/, temp/, venv/) - Clean up test intermediates and caches
173 lines
4.4 KiB
Python
173 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
YouTube 视频下载器
|
|
使用 Savenow API 下载 YouTube 视频/音频
|
|
|
|
使用方法:
|
|
python youtube_dl.py <YouTube_URL> [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()
|