2024最新YT-DLP使用demo网页端渲染
前提摘要
1.使用python的fastapi库和jinjia2库进行前端渲染
功能如下
使用YT-DLP,缓存文件到本地,预览文件信息
2.代码实现
1)目录结构
2)代码
style.css
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f0f0;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 添加 logo 相关样式 */
.logo {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 20px;
}
.youtube-icon, .download-icon {
width: 32px; /* 调整图标大小 */
height: 32px; /* 调整图标大小 */
}
.header {
text-align: center;
margin-bottom: 30px;
}
h1 {
font-size: 24px;
margin: 10px 0;
}
.subtitle {
color: #666;
margin-bottom: 20px;
}
form {
margin: 20px 0;
}
.input-group {
display: flex;
gap: 10px;
}
input[type="text"] {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.disclaimer {
text-align: center;
color: #666;
font-size: 14px;
margin: 20px 0;
}
.info-icon {
color: #007bff;
cursor: help;
}
.footer {
text-align: center;
margin-top: 30px;
color: #666;
font-size: 14px;
}
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube 视频下载器</title>
<link rel="stylesheet" href="{{ url_for('static', path='style.css') }}">
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">
<svg class="youtube-icon" viewBox="0 0 24 24">
<path fill="#FF0000" d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
</svg>
<svg class="download-icon" viewBox="0 0 24 24">
<path fill="#4285f4" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
</svg>
</div>
<h1>YT-DLP-DEMO</h1>
<p class="subtitle">YTDownload</p>
</div>
<form action="/download" method="POST">
<div class="input-group">
<input type="text"
name="url"
placeholder="https://www.youtube.com/watch?v=..."
required>
<button type="submit">下载</button>
</div>
</form>
<div class="footer">
<p>由 FastAPI 和 yt-dlp 提供技术支持</p>
</div>
</div>
</body>
</html>
result.html
<!DOCTYPE html>
<html>
<head>
<title>Download Result</title>
<link rel="stylesheet" href="{{ url_for('static', path='style.css') }}">
</head>
<body>
<div class="container">
{% if success %}
<div class="result-info">
<div class="video-header">
<div class="video-details">
<h2>{{ video_info.title }}</h2>
<div class="meta-info">
<p><strong>作者:</strong> {{ video_info.author }}</p>
<p><strong>时长:</strong> {{ video_info.length }}</p>
<p><strong>观看次数:</strong> {{ '{:,}'.format(video_info.views) }}</p>
</div>
</div>
</div>
<div class="download-sections">
<div class="preview-section">
<h3>视频预览</h3>
<div class="preview-container">
<video controls>
<source src="{{ video_path }}" type="video/mp4">
您的浏览器不支持视频标签。
</video>
</div>
<a href="{{ video_path }}" class="download-button" download>
<span class="icon">⭳</span> 下载视频
</a>
</div>
<div class="preview-section">
<h3>音频预览</h3>
<div class="preview-container">
<audio controls>
<source src="{{ audio_path }}" type="audio/mp4">
您的浏览器不支持音频标签。
</audio>
</div>
<a href="{{ audio_path }}" class="download-button" download>
<span class="icon">⭳</span> 下载音频
</a>
</div>
</div>
</div>
{% else %}
<div class="error-container">
<h2>下载失败</h2>
<p class="error-message">{{ error }}</p>
</div>
{% endif %}
<a href="/" class="back-button">返回首页</a>
</div>
</body>
</html>
main.py
from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
import yt_dlp
from pathlib import Path
import os
app = FastAPI()
# 配置静态文件和模板
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
# 配置下载目录
VIDEO_DIR = Path("downloads/video")
AUDIO_DIR = Path("downloads/audio")
VIDEO_DIR.mkdir(parents=True, exist_ok=True)
AUDIO_DIR.mkdir(parents=True, exist_ok=True)
def download_youtube_video(url):
try:
# yt-dlp 基础配置
common_opts = {
'cookiefile': r'www.youtube.com_cookies.txt',
'quiet': False,
'no_warnings': False,
'verbose': True,
'proxy': 'http://127.0.0.1:10809',
'socket_timeout': 30,
'retries': 3,
'nocheckcertificate': True,
'prefer_insecure': True
}
# 视频下载选项
video_opts = {
**common_opts,
'format': 'best[ext=mp4][height<=720]/best[height<=720]/best',
'outtmpl': str(VIDEO_DIR / '%(title)s.%(ext)s'),
}
# 音频下载选项
audio_opts = {
**common_opts,
'format': 'bestaudio[ext=m4a]/bestaudio',
'outtmpl': str(AUDIO_DIR / '%(title)s.%(ext)s'),
}
# 获取视频信息
with yt_dlp.YoutubeDL(common_opts) as ydl:
info = ydl.extract_info(url, download=False)
title = info['title']
duration = info['duration']
thumbnail = info['thumbnail']
author = info.get('uploader', 'Unknown')
views = info.get('view_count', 0)
# 下载视频
with yt_dlp.YoutubeDL(video_opts) as ydl:
ydl.download([url])
# 下载音频
with yt_dlp.YoutubeDL(audio_opts) as ydl:
ydl.download([url])
# 获取下载后的文件路径
video_file = next(VIDEO_DIR.glob(f"{title}.*"))
audio_file = next(AUDIO_DIR.glob(f"{title}.*"))
return {
"status": "success",
"title": title,
"author": author,
"duration": f"{duration // 60}:{duration % 60:02d}",
"views": views,
"thumbnail": thumbnail,
"video_path": str(video_file.name),
"audio_path": str(audio_file.name)
}
except Exception as e:
return {"status": "error", "message": str(e)}
@app.get("/")
async def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/download")
async def download_video_route(request: Request, url: str = Form(...)):
try:
result = download_youtube_video(url)
if result["status"] == "success":
video_info = {
"title": result["title"],
"author": result["author"],
"length": result["duration"],
"views": result["views"],
"thumbnail": result["thumbnail"]
}
return templates.TemplateResponse("result.html", {
"request": request,
"video_info": video_info,
"video_path": f"/downloads/video/{result['video_path']}",
"audio_path": f"/downloads/audio/{result['audio_path']}",
"success": True
})
else:
raise Exception(result["message"])
except Exception as e:
return templates.TemplateResponse("result.html", {
"request": request,
"error": str(e),
"success": False
})
# 配置下载目录的静态文件服务
app.mount("/downloads", StaticFiles(directory="downloads"), name="downloads")
run.py
import uvicorn
import os
def check_directories():
"""确保必要的目录存在"""
directories = [
'static',
'templates',
'downloads',
'downloads/video',
'downloads/audio'
]
for directory in directories:
if not os.path.exists(directory):
os.makedirs(directory)
print(f"Created directory: {directory}")
if __name__ == "__main__":
# 检查并创建必要的目录
check_directories()
# 配置并启动服务器
uvicorn.run(
"main:app",
host="127.0.0.1",
port=8000,
reload=True,
reload_dirs=["templates", "static"],
log_level="info"
)