添加边转换边输出frame_urls

This commit is contained in:
nnwang
2026-02-07 02:21:57 +08:00
parent b318144230
commit 736d7dd8f9
5 changed files with 540 additions and 70 deletions

View File

@@ -29,6 +29,27 @@ else:
FFMPEG_PATH = os.path.join(BASE_DIR, 'lib', 'ffmpeg', 'bin', 'ffmpeg')
FFPROBE_PATH = os.path.join(BASE_DIR, 'lib', 'ffmpeg', 'bin', 'ffprobe')
def parse_ffmpeg_frame_progress(line, task_id, task_registry, task_lock):
"""解析FFmpeg进度输出并更新任务状态"""
import re
# 匹配帧数frame= 180 fps= 90
frame_match = re.search(r'frame=\s*(\d+)', line)
if frame_match:
current_frame = int(frame_match.group(1))
# 更新任务状态的当前帧数
with task_lock:
if task_id in task_registry:
task_registry[task_id]['current_frames'] = current_frame
# 如果有总帧数估计,可以计算进度
if 'estimated_total_frames' in task_registry[task_id]:
total_frames = task_registry[task_id]['estimated_total_frames']
if total_frames > 0:
progress = min(80, int((current_frame / total_frames) * 60) + 20) # 20-80%为转换进度
task_registry[task_id]['progress'] = progress
def log_subprocess_output(pipe, task_id, task_registry, task_lock, prefix=""):
"""从管道实时读取并记录日志"""
if not pipe:
@@ -38,12 +59,30 @@ def log_subprocess_output(pipe, task_id, task_registry, task_lock, prefix=""):
if line:
clean_line = line.strip()
if clean_line and task_id and task_registry and task_lock:
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
# 过滤掉一些不必要的进度字符
if '\r' in clean_line:
clean_line = clean_line.replace('\r', '')
# 过滤掉过于频繁的进度更新(只记录有意义的内容)
if prefix.startswith("[yt-dlp") and clean_line.startswith('['):
# 只记录重要的进度信息,过滤掉过于频繁的百分比更新
if '%' not in clean_line or '100%' in clean_line or 'Downloading' in clean_line:
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
elif prefix == "[FFmpeg] " and "frame=" in clean_line:
# FFmpeg进度信息
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
parse_ffmpeg_frame_progress(clean_line, task_id, task_registry, task_lock)
else:
# 其他日志正常记录
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
except Exception as e:
if task_id and task_registry and task_lock:
add_task_log(task_id, f"[日志读取错误] {e}", task_registry, task_lock)
finally:
pipe.close()
try:
pipe.close()
except:
pass
# ======================
# 任务处理函数
@@ -104,27 +143,46 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
# === 替换 yt-dlp 下载部分 ===
if task_id and task_registry and task_lock:
add_task_log(task_id, f"开始下载视频: {video_url}", task_registry, task_lock)
with task_lock:
if task_id in task_registry:
task_registry[task_id]['progress'] = 20 # 开始下载进度20%
yt_dlp_cmd = [
sys.executable, '-m', 'yt_dlp',
video_url,
'-o', temp_base,
'-f', 'bv*[height<=720]+ba/b',
'--no-warnings',
'--progress', # 启用进度显示
'--newline', # 确保换行符正常
'--console-title', # 确保进度信息正确输出
'--no-colors', # 禁用颜色,避免控制字符干扰
]
# 使用 Popen 实时捕获 stderr
# 使用 Popen 实时捕获 stdout 和 stderr
proc = subprocess.Popen(
yt_dlp_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
universal_newlines=True,
encoding='utf-8',
errors='replace'
)
# 启动日志线程(只读 stderr因为 yt-dlp 进度在 stderr
# 启动日志线程yt-dlp 进度主要在 stdout
stdout_thread = threading.Thread(
target=log_subprocess_output,
args=(proc.stdout, task_id, task_registry, task_lock, "[yt-dlp] "),
daemon=True
)
stdout_thread.start()
# stderr 也可能有重要信息
stderr_thread = threading.Thread(
target=log_subprocess_output,
args=(proc.stderr, task_id, task_registry, task_lock, "[yt-dlp] "),
args=(proc.stderr, task_id, task_registry, task_lock, "[yt-dlp-error] "),
daemon=True
)
stderr_thread.start()
@@ -132,12 +190,34 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
try:
proc.wait(timeout=600)
if proc.returncode != 0:
raise subprocess.CalledProcessError(proc.returncode, yt_dlp_cmd)
# 获取更详细的错误信息
error_details = []
try:
if proc.stderr:
stderr_content = proc.stderr.read()
if stderr_content:
error_details.append(f"stderr: {stderr_content}")
if proc.stdout:
stdout_content = proc.stdout.read()
if stdout_content:
error_details.append(f"stdout: {stdout_content}")
except:
pass
error_msg = f"yt-dlp 下载失败 (返回码: {proc.returncode})"
if error_details:
error_msg += f" - {' '.join(error_details[:500])}" # 限制错误信息长度
raise subprocess.CalledProcessError(proc.returncode, yt_dlp_cmd, output=error_msg)
except subprocess.TimeoutExpired:
proc.kill()
if task_id and task_registry and task_lock:
add_task_log(task_id, "yt-dlp 下载超时超过10分钟", task_registry, task_lock)
raise Exception("yt-dlp 下载超时超过10分钟")
finally:
stderr_thread.join(timeout=5) # 等待日志线程结束
# 等待日志线程结束
stdout_thread.join(timeout=5)
stderr_thread.join(timeout=5)
# 查找实际生成的文件
candidates = glob.glob(temp_base + ".*")
@@ -153,9 +233,79 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
if task_id in task_registry:
task_registry[task_id]['progress'] = 40
# 首先处理音频(在视频处理之前)
audio_exists = has_audio(temp_video)
audio_urls = {}
if audio_exists:
if task_id and task_registry and task_lock:
add_task_log(task_id, "提取音频(左/右/混合声道)", task_registry, task_lock)
with task_lock:
if task_id in task_registry:
task_registry[task_id]['progress'] = 40
# 公共参数
dfpwm_args = [
FFMPEG_PATH, '-y',
'-i',temp_video,
'-vn',
'-ar', '48000',
'-ac', '1',
'-f', 'dfpwm'
]
# 混合声道
dfpwm_path_mix = os.path.join(job_dir, "audio.dfpwm")
subprocess.run(
dfpwm_args + [dfpwm_path_mix],
check=True, capture_output=True
)
# 左声道
dfpwm_path_left = os.path.join(job_dir, "audio_left.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c0'] + [dfpwm_path_left],
check=True, capture_output=True
)
# 右声道
dfpwm_path_right = os.path.join(job_dir, "audio_right.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c1'] + [dfpwm_path_right],
check=True, capture_output=True
)
audio_urls = {
"audio_dfpwm_url": f"/frames/{task_id}/audio.dfpwm",
"audio_dfpwm_left_url": f"/frames/{task_id}/audio_left.dfpwm",
"audio_dfpwm_right_url": f"/frames/{task_id}/audio_right.dfpwm"
}
# 更新音频URL到任务状态
if task_id and task_registry and task_lock:
with task_lock:
if task_id in task_registry:
task_registry[task_id]['audio_urls'] = audio_urls
# 获取视频信息
duration = get_video_duration(temp_video)
# 估计总帧数
estimated_total_frames = int(duration * fps)
# 初始化任务状态的帧相关字段
if task_id and task_registry and task_lock:
with task_lock:
if task_id in task_registry:
task_registry[task_id]['estimated_total_frames'] = estimated_total_frames
task_registry[task_id]['current_frames'] = 0
task_registry[task_id]['generated_frame_urls'] = []
task_registry[task_id]['frame_job_dir'] = job_dir
task_registry[task_id]['frame_params'] = {
'w': w, 'h': h, 'fps': fps,
'force_resolution': force_resolution, 'pad_to_target': pad_to_target
}
# 构建滤镜
vf = build_video_filter(w, h, fps, force_resolution, pad_to_target)
@@ -212,46 +362,12 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
out_w, out_h = get_output_resolution(job_dir)
total_frames = len([f for f in os.listdir(job_dir) if f.endswith('.png')])
# 检查音频
audio_exists = has_audio(temp_video)
if audio_exists:
if task_id and task_registry and task_lock:
add_task_log(task_id, "提取音频(左/右/混合声道)", task_registry, task_lock)
with task_lock:
if task_id in task_registry:
task_registry[task_id]['progress'] = 90
# 公共参数
dfpwm_args = [
FFMPEG_PATH, '-y',
'-i', temp_video,
'-vn',
'-ar', '48000',
'-ac', '1',
'-f', 'dfpwm'
]
# 混合声道
dfpwm_path_mix = os.path.join(job_dir, "audio.dfpwm")
subprocess.run(
dfpwm_args + [dfpwm_path_mix],
check=True, capture_output=True
)
# 左声道
dfpwm_path_left = os.path.join(job_dir, "audio_left.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c0'] + [dfpwm_path_left],
check=True, capture_output=True
)
# 右声道
dfpwm_path_right = os.path.join(job_dir, "audio_right.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c1'] + [dfpwm_path_right],
check=True, capture_output=True
)
# 保存输出分辨率和实际总帧数到任务状态
if task_id and task_registry and task_lock:
with task_lock:
if task_id in task_registry:
task_registry[task_id]['output_resolution'] = {"w": out_w, "h": out_h}
task_registry[task_id]['estimated_total_frames'] = total_frames
# 生成结果
result_data = {