Files
2026-06-16 03:30:57 +08:00

185 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
down-video 视频下载蓝图
"""
import os
import json
import queue
from flask import Blueprint, render_template, request, jsonify, send_file, Response
from config import DEFAULT_OUTPUT_DIR, PROXY_URL
from utils.down_video_utils import (
check_ffmpeg,
check_deno,
is_twitter_url,
is_bilibili_url,
is_instagram_url,
is_youtube_url,
download_video,
)
from utils.stats_db import add_recent_path, get_recent_paths, delete_recent_path, clear_recent_paths
bp = Blueprint('dv_cookies', __name__, url_prefix='/down-video')
# 用于存储下载进度的队列
progress_queues = {}
@bp.route('/')
def page():
ffmpeg_ok = check_ffmpeg()
deno_ok = check_deno()
return render_template('down_video.html', ffmpeg_ok=ffmpeg_ok, deno_ok=deno_ok)
@bp.route('/file')
def serve_file():
filepath = request.args.get("path", "")
as_download = request.args.get("download", "0") == "1"
if not filepath:
return "Missing path", 400
filepath = os.path.abspath(filepath)
if not os.path.exists(filepath) or not os.path.isfile(filepath):
return "File not found", 404
ext = os.path.splitext(filepath)[1].lower()
if ext not in ('.mp4', '.m4a', '.webm', '.mkv', '.mov', '.avi'):
return "File type not allowed", 403
mime_types = {
'.mp4': 'video/mp4',
'.m4a': 'audio/mp4',
'.webm': 'video/webm',
'.mkv': 'video/x-matroska',
'.mov': 'video/quicktime',
'.avi': 'video/x-msvideo',
}
mimetype = mime_types.get(ext, 'application/octet-stream')
response = send_file(
filepath,
mimetype=mimetype,
conditional=True,
)
response.headers['Accept-Ranges'] = 'bytes'
if not as_download:
response.headers['Content-Disposition'] = 'inline'
return response
@bp.route('/download', methods=["POST"])
def download():
data = request.get_json()
url = data.get("url", "").strip() if data else ""
output_dir = data.get("output_dir", DEFAULT_OUTPUT_DIR).strip() if data else DEFAULT_OUTPUT_DIR
download_id = data.get("download_id", "") if data else ""
use_proxy = data.get("use_proxy", False) if data else False
proxy_url_input = (data.get("proxy_url") or "").strip() if data else ""
if not url:
return jsonify({"success": False, "message": "请输入视频链接"}), 400
if is_twitter_url(url):
platform = 'twitter'
elif is_bilibili_url(url):
platform = 'bilibili'
elif is_instagram_url(url):
platform = 'instagram'
elif is_youtube_url(url):
platform = 'youtube'
else:
return jsonify({"success": False, "message": "仅支持 Twitter/X、Bilibili 和 Instagram 视频链接"}), 400
if not output_dir:
output_dir = DEFAULT_OUTPUT_DIR
# 确定使用的代理地址
proxy_url = proxy_url_input if use_proxy and proxy_url_input else (PROXY_URL if use_proxy else None)
# 创建进度队列
progress_queue = queue.Queue()
if download_id:
progress_queues[download_id] = progress_queue
def progress_callback(data):
progress_queue.put(data)
try:
# 发送开始合并的消息
def send_merge_status():
progress_queue.put({
'type': 'merging',
'message': '正在合并视频和音频...'
})
success, message, title, filepath = download_video(url, output_dir, platform, progress_callback, proxy_url=proxy_url)
# 如果是bilibili或youtube可能有合并过程发送合并状态
if platform in ('bilibili', 'youtube') and filepath and '_merged' in filepath:
send_merge_status()
return jsonify({"success": success, "message": message, "title": title, "filepath": filepath})
finally:
# 清理队列
if download_id and download_id in progress_queues:
del progress_queues[download_id]
@bp.route('/progress/<download_id>')
def progress_stream(download_id):
"""SSE端点 - 实时推送下载进度"""
def generate():
progress_queue = progress_queues.get(download_id)
if not progress_queue:
yield f"data: {json.dumps({'type': 'error', 'message': '下载任务不存在'})}\n\n"
return
while True:
try:
data = progress_queue.get(timeout=30)
yield f"data: {json.dumps(data)}\n\n"
if data.get('type') == 'progress' and data.get('percent', 0) >= 100:
break
except queue.Empty:
# 发送心跳保持连接
yield f":\n\n"
continue
return Response(
generate(),
mimetype='text/event-stream',
headers={
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no'
}
)
# ===== 保存路径历史 =====
@bp.route('/save-paths', methods=['GET'])
def get_save_paths():
paths = get_recent_paths(limit=10)
return jsonify({'paths': paths})
@bp.route('/save-paths', methods=['POST'])
def add_save_path():
data = request.get_json()
path = data.get('path', '').strip() if data else ''
if not path:
return jsonify({'success': False, 'message': '路径不能为空'}), 400
add_recent_path(path)
return jsonify({'success': True})
@bp.route('/save-paths/delete', methods=['POST'])
def delete_save_path():
data = request.get_json()
path = data.get('path', '').strip() if data else ''
if not path:
return jsonify({'success': False, 'message': '路径不能为空'}), 400
delete_recent_path(path)
return jsonify({'success': True})