feat(video_processor): 优化视频帧提取功能,增强Windows系统兼容性

在video_processor.py中,添加了对Windows N卡硬件加速的支持,优化了帧提取过程,改进了提取成功率的统计和错误处理。同时,在generate_script_docu.py中,增强了对硬件加速失败的处理逻辑,提供了详细的错误信息和解决建议,提升了用户体验。
This commit is contained in:
linyq 2025-07-07 20:48:36 +08:00
parent a65e8e4a95
commit 6715c29057
5 changed files with 1005 additions and 42 deletions

284
app/config/ffmpeg_config.py Normal file
View File

@ -0,0 +1,284 @@
"""
FFmpeg 配置管理模块
专门用于管理 FFmpeg 兼容性设置和优化参数
"""
import os
import platform
from typing import Dict, List, Optional
from dataclasses import dataclass
from loguru import logger
@dataclass
class FFmpegProfile:
"""FFmpeg 配置文件"""
name: str
description: str
hwaccel_enabled: bool
hwaccel_type: Optional[str]
encoder: str
quality_preset: str
pixel_format: str
additional_args: List[str]
compatibility_level: int # 1-5, 5为最高兼容性
class FFmpegConfigManager:
"""FFmpeg 配置管理器"""
# 预定义的配置文件
PROFILES = {
# 高性能配置(适用于现代硬件)
"high_performance": FFmpegProfile(
name="high_performance",
description="高性能配置NVIDIA/AMD 独立显卡)",
hwaccel_enabled=True,
hwaccel_type="auto",
encoder="auto",
quality_preset="fast",
pixel_format="yuv420p",
additional_args=["-preset", "fast"],
compatibility_level=2
),
# 兼容性配置(适用于有问题的硬件)
"compatibility": FFmpegProfile(
name="compatibility",
description="兼容性配置(解决滤镜链问题)",
hwaccel_enabled=False,
hwaccel_type=None,
encoder="libx264",
quality_preset="medium",
pixel_format="yuv420p",
additional_args=["-preset", "medium", "-crf", "23"],
compatibility_level=5
),
# Windows N 卡优化配置
"windows_nvidia": FFmpegProfile(
name="windows_nvidia",
description="Windows NVIDIA 显卡优化配置",
hwaccel_enabled=True,
hwaccel_type="nvenc_pure", # 纯编码器,避免解码问题
encoder="h264_nvenc",
quality_preset="medium",
pixel_format="yuv420p",
additional_args=["-preset", "medium", "-cq", "23"],
compatibility_level=3
),
# macOS 优化配置
"macos_videotoolbox": FFmpegProfile(
name="macos_videotoolbox",
description="macOS VideoToolbox 优化配置",
hwaccel_enabled=True,
hwaccel_type="videotoolbox",
encoder="h264_videotoolbox",
quality_preset="medium",
pixel_format="yuv420p",
additional_args=["-q:v", "65"],
compatibility_level=3
),
# 通用软件配置
"universal_software": FFmpegProfile(
name="universal_software",
description="通用软件编码配置(最高兼容性)",
hwaccel_enabled=False,
hwaccel_type=None,
encoder="libx264",
quality_preset="medium",
pixel_format="yuv420p",
additional_args=["-preset", "medium", "-crf", "23"],
compatibility_level=5
)
}
@classmethod
def get_recommended_profile(cls) -> str:
"""
根据系统环境推荐最佳配置文件
Returns:
str: 推荐的配置文件名称
"""
system = platform.system().lower()
# 检测硬件加速可用性
try:
from app.utils import ffmpeg_utils
hwaccel_info = ffmpeg_utils.get_ffmpeg_hwaccel_info()
hwaccel_available = hwaccel_info.get("available", False)
hwaccel_type = hwaccel_info.get("type", "software")
gpu_vendor = hwaccel_info.get("gpu_vendor", "unknown")
except Exception as e:
logger.warning(f"无法检测硬件加速信息: {e}")
hwaccel_available = False
hwaccel_type = "software"
gpu_vendor = "unknown"
# 根据平台和硬件推荐配置
if system == "windows":
if hwaccel_available and gpu_vendor == "nvidia":
return "windows_nvidia"
elif hwaccel_available:
return "high_performance"
else:
return "compatibility"
elif system == "darwin":
if hwaccel_available and hwaccel_type == "videotoolbox":
return "macos_videotoolbox"
else:
return "universal_software"
elif system == "linux":
if hwaccel_available:
return "high_performance"
else:
return "universal_software"
else:
return "universal_software"
@classmethod
def get_profile(cls, profile_name: str) -> FFmpegProfile:
"""
获取指定的配置文件
Args:
profile_name: 配置文件名称
Returns:
FFmpegProfile: 配置文件对象
"""
if profile_name not in cls.PROFILES:
logger.warning(f"未知的配置文件: {profile_name},使用默认配置")
profile_name = "universal_software"
return cls.PROFILES[profile_name]
@classmethod
def get_extraction_command(cls,
input_path: str,
output_path: str,
timestamp: float,
profile_name: Optional[str] = None) -> List[str]:
"""
根据配置文件生成关键帧提取命令
Args:
input_path: 输入视频路径
output_path: 输出图片路径
timestamp: 时间戳
profile_name: 配置文件名称None 表示自动选择
Returns:
List[str]: FFmpeg 命令列表
"""
if profile_name is None:
profile_name = cls.get_recommended_profile()
profile = cls.get_profile(profile_name)
# 构建基础命令
cmd = [
"ffmpeg",
"-hide_banner",
"-loglevel", "error",
]
# 添加硬件加速参数
if profile.hwaccel_enabled and profile.hwaccel_type:
if profile.hwaccel_type == "auto":
# 自动检测硬件加速
try:
from app.utils import ffmpeg_utils
hw_args = ffmpeg_utils.get_ffmpeg_hwaccel_args()
cmd.extend(hw_args)
except Exception:
pass
elif profile.hwaccel_type == "nvenc_pure":
# 纯 NVENC 编码器,不使用硬件解码
pass
else:
# 指定的硬件加速类型
cmd.extend(["-hwaccel", profile.hwaccel_type])
# 添加输入参数
cmd.extend([
"-ss", str(timestamp),
"-i", input_path,
"-vframes", "1",
])
# 添加质量和格式参数
if profile.encoder == "libx264":
cmd.extend(["-q:v", "2"])
elif profile.encoder == "h264_nvenc":
cmd.extend(["-cq", "23"])
elif profile.encoder == "h264_videotoolbox":
cmd.extend(["-q:v", "65"])
else:
cmd.extend(["-q:v", "2"])
# 添加像素格式
cmd.extend(["-pix_fmt", profile.pixel_format])
# 添加额外参数
cmd.extend(profile.additional_args)
# 添加输出参数
cmd.extend(["-y", output_path])
return cmd
@classmethod
def list_profiles(cls) -> Dict[str, str]:
"""
列出所有可用的配置文件
Returns:
Dict[str, str]: 配置文件名称到描述的映射
"""
return {name: profile.description for name, profile in cls.PROFILES.items()}
@classmethod
def get_compatibility_report(cls) -> Dict[str, any]:
"""
生成兼容性报告
Returns:
Dict: 兼容性报告
"""
recommended_profile = cls.get_recommended_profile()
profile = cls.get_profile(recommended_profile)
try:
from app.utils import ffmpeg_utils
hwaccel_info = ffmpeg_utils.get_ffmpeg_hwaccel_info()
except Exception:
hwaccel_info = {"available": False, "message": "检测失败"}
return {
"system": platform.system(),
"recommended_profile": recommended_profile,
"profile_description": profile.description,
"compatibility_level": profile.compatibility_level,
"hardware_acceleration": hwaccel_info,
"suggestions": cls._get_suggestions(profile, hwaccel_info)
}
@classmethod
def _get_suggestions(cls, profile: FFmpegProfile, hwaccel_info: Dict) -> List[str]:
"""生成优化建议"""
suggestions = []
if not hwaccel_info.get("available", False):
suggestions.append("建议更新显卡驱动以启用硬件加速")
if profile.compatibility_level >= 4:
suggestions.append("当前使用高兼容性配置,性能可能较低")
if platform.system().lower() == "windows" and "nvidia" in hwaccel_info.get("gpu_vendor", "").lower():
suggestions.append("Windows NVIDIA 用户建议使用纯编码器模式避免滤镜链问题")
return suggestions

View File

@ -20,6 +20,7 @@ from loguru import logger
from tqdm import tqdm
from app.utils import ffmpeg_utils
from app.config.ffmpeg_config import FFmpegConfigManager
class VideoProcessor:
@ -90,6 +91,8 @@ class VideoProcessor:
"""
按指定时间间隔提取视频帧
优化了 Windows 系统兼容性特别是 N 卡硬件加速的滤镜链问题
Args:
output_dir: 输出目录
interval_seconds: 帧提取间隔
@ -115,52 +118,225 @@ class VideoProcessor:
logger.warning("未找到需要提取的帧")
return []
# 确定硬件加速器选项
hw_accel = []
# 获取硬件加速信息
hwaccel_info = ffmpeg_utils.get_ffmpeg_hwaccel_info()
hwaccel_type = hwaccel_info.get("type", "software")
# 提取帧 - 使用优化的进度条
frame_numbers = []
successful_extractions = 0
failed_extractions = 0
logger.info(f"开始提取 {len(extraction_times)} 个关键帧,使用 {hwaccel_type} 加速")
with tqdm(total=len(extraction_times), desc="提取视频帧", unit="") as pbar:
for i, timestamp in enumerate(extraction_times):
frame_number = int(timestamp * self.fps)
frame_numbers.append(frame_number)
# 格式化时间戳字符串 (HHMMSSmmm)
hours = int(timestamp // 3600)
minutes = int((timestamp % 3600) // 60)
seconds = int(timestamp % 60)
milliseconds = int((timestamp % 1) * 1000)
time_str = f"{hours:02d}{minutes:02d}{seconds:02d}{milliseconds:03d}"
output_path = os.path.join(output_dir, f"keyframe_{frame_number:06d}_{time_str}.jpg")
# 构建 FFmpeg 命令 - 针对 Windows N 卡优化
success = self._extract_single_frame_optimized(
timestamp, output_path, use_hw_accel, hwaccel_type
)
if success:
successful_extractions += 1
pbar.set_postfix({
"成功": successful_extractions,
"失败": failed_extractions,
"当前": f"{timestamp:.1f}s"
})
else:
failed_extractions += 1
pbar.set_postfix({
"成功": successful_extractions,
"失败": failed_extractions,
"当前": f"{timestamp:.1f}s (失败)"
})
pbar.update(1)
# 统计结果
total_attempts = len(extraction_times)
success_rate = (successful_extractions / total_attempts) * 100 if total_attempts > 0 else 0
logger.info(f"关键帧提取完成: 成功 {successful_extractions}/{total_attempts} 帧 ({success_rate:.1f}%)")
if failed_extractions > 0:
logger.warning(f"{failed_extractions} 帧提取失败,可能是硬件加速兼容性问题")
# 验证实际生成的文件
actual_files = [f for f in os.listdir(output_dir) if f.endswith('.jpg')]
logger.info(f"实际生成文件数量: {len(actual_files)}")
if len(actual_files) == 0:
logger.error("未生成任何关键帧文件,可能需要禁用硬件加速")
raise Exception("关键帧提取完全失败,请检查视频文件和 FFmpeg 配置")
return frame_numbers
def _extract_single_frame_optimized(self, timestamp: float, output_path: str,
use_hw_accel: bool, hwaccel_type: str) -> bool:
"""
优化的单帧提取方法解决 Windows N 卡硬件加速兼容性问题
Args:
timestamp: 时间戳
output_path: 输出文件路径
use_hw_accel: 是否使用硬件加速
hwaccel_type: 硬件加速类型
Returns:
bool: 是否成功提取
"""
# 策略1: 优先尝试纯编码器方案(避免硬件解码滤镜链问题)
if use_hw_accel and hwaccel_type in ["nvenc", "cuda"]:
# 对于 NVIDIA 显卡,优先使用纯软件解码 + NVENC 编码
if self._try_extract_with_software_decode(timestamp, output_path):
return True
logger.debug(f"纯软件解码方案失败,尝试其他方案")
# 策略2: 尝试标准硬件加速
if use_hw_accel and ffmpeg_utils.is_ffmpeg_hwaccel_available():
hw_accel = ffmpeg_utils.get_ffmpeg_hwaccel_args()
if self._try_extract_with_hwaccel(timestamp, output_path, hw_accel):
return True
logger.debug(f"硬件加速方案失败,回退到软件方案")
# 提取帧
frame_numbers = []
for i, timestamp in enumerate(tqdm(extraction_times, desc="提取视频帧")):
frame_number = int(timestamp * self.fps)
frame_numbers.append(frame_number)
# 策略3: 软件方案(最后的备用方案)
return self._try_extract_with_software(timestamp, output_path)
# 格式化时间戳字符串 (HHMMSSmmm)
hours = int(timestamp // 3600)
minutes = int((timestamp % 3600) // 60)
seconds = int(timestamp % 60)
milliseconds = int((timestamp % 1) * 1000)
time_str = f"{hours:02d}{minutes:02d}{seconds:02d}{milliseconds:03d}"
def _try_extract_with_software_decode(self, timestamp: float, output_path: str) -> bool:
"""
使用纯软件解码提取帧推荐用于 Windows N
output_path = os.path.join(output_dir, f"keyframe_{frame_number:06d}_{time_str}.jpg")
Args:
timestamp: 时间戳
output_path: 输出路径
# 使用ffmpeg提取单帧
cmd = [
"ffmpeg",
"-hide_banner",
"-loglevel", "error",
]
Returns:
bool: 是否成功
"""
# 使用 Windows NVIDIA 优化配置
cmd = FFmpegConfigManager.get_extraction_command(
input_path=self.video_path,
output_path=output_path,
timestamp=timestamp,
profile_name="windows_nvidia"
)
# 添加硬件加速参数
cmd.extend(hw_accel)
return self._execute_ffmpeg_command(cmd, f"软件解码提取帧 {timestamp:.1f}s")
cmd.extend([
"-ss", str(timestamp),
"-i", self.video_path,
"-vframes", "1",
"-q:v", "1", # 最高质量
"-y",
output_path
])
def _try_extract_with_hwaccel(self, timestamp: float, output_path: str, hw_accel: List[str]) -> bool:
"""
使用硬件加速提取帧
try:
subprocess.run(cmd, check=True, capture_output=True)
except subprocess.CalledProcessError as e:
logger.warning(f"提取帧 {frame_number} 失败: {e.stderr}")
Args:
timestamp: 时间戳
output_path: 输出路径
hw_accel: 硬件加速参数
logger.info(f"成功提取了 {len(frame_numbers)} 个视频帧")
return frame_numbers
Returns:
bool: 是否成功
"""
cmd = [
"ffmpeg",
"-hide_banner",
"-loglevel", "error",
]
# 添加硬件加速参数
cmd.extend(hw_accel)
cmd.extend([
"-ss", str(timestamp),
"-i", self.video_path,
"-vframes", "1",
"-q:v", "2",
"-pix_fmt", "yuv420p",
"-y",
output_path
])
return self._execute_ffmpeg_command(cmd, f"硬件加速提取帧 {timestamp:.1f}s")
def _try_extract_with_software(self, timestamp: float, output_path: str) -> bool:
"""
使用纯软件方案提取帧最后的备用方案
Args:
timestamp: 时间戳
output_path: 输出路径
Returns:
bool: 是否成功
"""
# 使用最高兼容性配置
cmd = FFmpegConfigManager.get_extraction_command(
input_path=self.video_path,
output_path=output_path,
timestamp=timestamp,
profile_name="compatibility"
)
# 软件方案使用更详细的日志
cmd[cmd.index("-loglevel") + 1] = "warning"
return self._execute_ffmpeg_command(cmd, f"软件方案提取帧 {timestamp:.1f}s")
def _execute_ffmpeg_command(self, cmd: List[str], description: str) -> bool:
"""
执行 FFmpeg 命令并处理结果
Args:
cmd: FFmpeg 命令列表
description: 操作描述
Returns:
bool: 是否成功
"""
try:
# 在 Windows 上使用 UTF-8 编码
is_windows = os.name == 'nt'
process_kwargs = {
"check": True,
"capture_output": True,
"timeout": 30 # 30秒超时
}
if is_windows:
process_kwargs["encoding"] = 'utf-8'
process_kwargs["text"] = True
result = subprocess.run(cmd, **process_kwargs)
# 验证输出文件
output_path = cmd[-1]
if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
return True
else:
logger.debug(f"{description} - 输出文件无效")
return False
except subprocess.CalledProcessError as e:
error_msg = e.stderr if hasattr(e, 'stderr') and e.stderr else str(e)
logger.debug(f"{description} - 命令执行失败: {error_msg}")
return False
except subprocess.TimeoutExpired:
logger.debug(f"{description} - 命令执行超时")
return False
except Exception as e:
logger.debug(f"{description} - 未知错误: {str(e)}")
return False
def _detect_hw_accelerator(self) -> List[str]:
"""

189
test_video_extraction.py Normal file
View File

@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""
视频关键帧提取测试脚本
用于验证 Windows 系统 FFmpeg 兼容性修复效果
"""
import os
import sys
import tempfile
import traceback
from pathlib import Path
# 添加项目根目录到 Python 路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from loguru import logger
from app.utils import video_processor, ffmpeg_utils
def test_ffmpeg_compatibility():
"""测试 FFmpeg 兼容性"""
print("=" * 60)
print("🔧 FFmpeg 兼容性测试")
print("=" * 60)
# 检查 FFmpeg 安装
if not ffmpeg_utils.check_ffmpeg_installation():
print("❌ FFmpeg 未安装或不在系统 PATH 中")
return False
print("✅ FFmpeg 已安装")
# 获取硬件加速信息
hwaccel_info = ffmpeg_utils.get_ffmpeg_hwaccel_info()
print(f"🎮 硬件加速状态: {hwaccel_info.get('message', '未知')}")
print(f"🔧 加速类型: {hwaccel_info.get('type', 'software')}")
print(f"🎯 编码器: {hwaccel_info.get('encoder', 'libx264')}")
return True
def test_video_extraction(video_path: str, output_dir: str = None):
"""测试视频关键帧提取"""
print("\n" + "=" * 60)
print("🎬 视频关键帧提取测试")
print("=" * 60)
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return False
# 创建临时输出目录
if output_dir is None:
output_dir = tempfile.mkdtemp(prefix="keyframes_test_")
try:
# 初始化视频处理器
print(f"📁 输入视频: {video_path}")
print(f"📁 输出目录: {output_dir}")
processor = video_processor.VideoProcessor(video_path)
# 显示视频信息
print(f"📊 视频信息:")
print(f" - 分辨率: {processor.width}x{processor.height}")
print(f" - 帧率: {processor.fps:.1f} fps")
print(f" - 时长: {processor.duration:.1f}")
print(f" - 总帧数: {processor.total_frames}")
# 测试关键帧提取
print("\n🚀 开始提取关键帧...")
# 先测试硬件加速方案
print("\n1⃣ 测试硬件加速方案:")
try:
processor.process_video_pipeline(
output_dir=output_dir,
interval_seconds=10.0, # 10秒间隔减少测试时间
use_hw_accel=True
)
# 检查结果
extracted_files = [f for f in os.listdir(output_dir) if f.endswith('.jpg')]
print(f"✅ 硬件加速成功,提取了 {len(extracted_files)} 个关键帧")
if len(extracted_files) > 0:
return True
except Exception as e:
print(f"⚠️ 硬件加速失败: {str(e)}")
# 清理失败的文件
for f in os.listdir(output_dir):
if f.endswith('.jpg'):
os.remove(os.path.join(output_dir, f))
# 测试软件方案
print("\n2⃣ 测试软件方案:")
try:
# 强制使用软件编码
ffmpeg_utils.force_software_encoding()
processor.process_video_pipeline(
output_dir=output_dir,
interval_seconds=10.0,
use_hw_accel=False
)
# 检查结果
extracted_files = [f for f in os.listdir(output_dir) if f.endswith('.jpg')]
print(f"✅ 软件方案成功,提取了 {len(extracted_files)} 个关键帧")
if len(extracted_files) > 0:
return True
else:
print("❌ 软件方案也未能提取到关键帧")
return False
except Exception as e:
print(f"❌ 软件方案也失败: {str(e)}")
return False
except Exception as e:
print(f"❌ 测试过程中发生错误: {str(e)}")
print(f"详细错误信息:\n{traceback.format_exc()}")
return False
finally:
# 清理临时文件
try:
import shutil
if output_dir and os.path.exists(output_dir):
shutil.rmtree(output_dir)
print(f"🧹 已清理临时目录: {output_dir}")
except Exception as e:
print(f"⚠️ 清理临时目录失败: {e}")
def main():
"""主函数"""
print("🎯 视频关键帧提取兼容性测试工具")
print("专门用于测试 Windows 系统 FFmpeg 兼容性修复效果")
# 测试 FFmpeg 兼容性
if not test_ffmpeg_compatibility():
return
# 获取测试视频路径
if len(sys.argv) > 1:
video_path = sys.argv[1]
else:
# 尝试找到项目中的测试视频
possible_paths = [
"./resource/videos/test.mp4",
"./storage/videos/test.mp4",
"./test_video.mp4"
]
video_path = None
for path in possible_paths:
if os.path.exists(path):
video_path = path
break
if not video_path:
print("\n❌ 未找到测试视频文件")
print("请提供视频文件路径作为参数:")
print(f"python {sys.argv[0]} <video_path>")
return
# 执行测试
success = test_video_extraction(video_path)
print("\n" + "=" * 60)
if success:
print("🎉 测试成功!关键帧提取功能正常工作")
print("💡 建议:如果之前遇到问题,现在应该已经修复")
else:
print("❌ 测试失败!可能需要进一步调试")
print("💡 建议:")
print(" 1. 检查视频文件是否损坏")
print(" 2. 尝试更新显卡驱动")
print(" 3. 检查 FFmpeg 版本是否过旧")
print("=" * 60)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,280 @@
"""
FFmpeg 诊断和配置组件
为用户提供 FFmpeg 兼容性诊断和配置选项
"""
import streamlit as st
import platform
from typing import Dict, Any
from loguru import logger
try:
from app.utils import ffmpeg_utils
from app.config.ffmpeg_config import FFmpegConfigManager
except ImportError as e:
logger.error(f"导入模块失败: {e}")
ffmpeg_utils = None
FFmpegConfigManager = None
def show_ffmpeg_diagnostics():
"""显示 FFmpeg 诊断信息"""
st.subheader("🔧 FFmpeg 兼容性诊断")
if ffmpeg_utils is None or FFmpegConfigManager is None:
st.error("❌ 无法加载 FFmpeg 工具模块")
return
# 基础信息
col1, col2 = st.columns(2)
with col1:
st.write("**系统信息**")
st.write(f"- 操作系统: {platform.system()} {platform.release()}")
st.write(f"- 架构: {platform.machine()}")
st.write(f"- Python: {platform.python_version()}")
with col2:
st.write("**FFmpeg 状态**")
# 检查 FFmpeg 安装
if ffmpeg_utils.check_ffmpeg_installation():
st.success("✅ FFmpeg 已安装")
else:
st.error("❌ FFmpeg 未安装或不在 PATH 中")
st.info("请安装 FFmpeg 并确保其在系统 PATH 中")
return
# 硬件加速信息
st.write("**硬件加速检测**")
try:
hwaccel_info = ffmpeg_utils.get_ffmpeg_hwaccel_info()
if hwaccel_info.get("available", False):
st.success(f"{hwaccel_info.get('message', '硬件加速可用')}")
# 显示详细信息
with st.expander("硬件加速详情"):
st.write(f"- 加速类型: {hwaccel_info.get('type', '未知')}")
st.write(f"- 编码器: {hwaccel_info.get('encoder', '未知')}")
st.write(f"- GPU 厂商: {hwaccel_info.get('gpu_vendor', '未知')}")
st.write(f"- 独立显卡: {'' if hwaccel_info.get('is_dedicated_gpu', False) else ''}")
if hwaccel_info.get("tested_methods"):
st.write(f"- 测试的方法: {', '.join(hwaccel_info['tested_methods'])}")
else:
st.warning(f"⚠️ {hwaccel_info.get('message', '硬件加速不可用')}")
except Exception as e:
st.error(f"❌ 硬件加速检测失败: {str(e)}")
# 配置文件推荐
st.write("**推荐配置**")
try:
recommended_profile = FFmpegConfigManager.get_recommended_profile()
profile = FFmpegConfigManager.get_profile(recommended_profile)
st.info(f"🎯 推荐配置: **{profile.description}**")
# 显示配置详情
with st.expander("配置详情"):
st.write(f"- 配置名称: {profile.name}")
st.write(f"- 硬件加速: {'启用' if profile.hwaccel_enabled else '禁用'}")
st.write(f"- 编码器: {profile.encoder}")
st.write(f"- 质量预设: {profile.quality_preset}")
st.write(f"- 兼容性等级: {profile.compatibility_level}/5")
except Exception as e:
st.error(f"❌ 配置推荐失败: {str(e)}")
# 兼容性报告
if st.button("🔍 生成详细兼容性报告"):
try:
report = FFmpegConfigManager.get_compatibility_report()
st.write("**详细兼容性报告**")
st.json(report)
# 显示建议
if report.get("suggestions"):
st.write("**优化建议**")
for suggestion in report["suggestions"]:
st.write(f"- {suggestion}")
except Exception as e:
st.error(f"❌ 生成报告失败: {str(e)}")
def show_ffmpeg_settings():
"""显示 FFmpeg 设置选项"""
st.subheader("⚙️ FFmpeg 设置")
if FFmpegConfigManager is None:
st.error("❌ 无法加载配置管理器")
return
# 配置文件选择
profiles = FFmpegConfigManager.list_profiles()
# 获取当前推荐配置
try:
recommended_profile = FFmpegConfigManager.get_recommended_profile()
except Exception:
recommended_profile = "universal_software"
# 配置文件选择器
selected_profile = st.selectbox(
"选择 FFmpeg 配置文件",
options=list(profiles.keys()),
index=list(profiles.keys()).index(recommended_profile) if recommended_profile in profiles else 0,
format_func=lambda x: f"{profiles[x]} {'(推荐)' if x == recommended_profile else ''}",
help="不同的配置文件针对不同的硬件和兼容性需求进行了优化"
)
# 显示选中配置的详情
if selected_profile:
profile = FFmpegConfigManager.get_profile(selected_profile)
st.write("**配置详情**")
col1, col2 = st.columns(2)
with col1:
st.write(f"- 硬件加速: {'✅ 启用' if profile.hwaccel_enabled else '❌ 禁用'}")
st.write(f"- 编码器: {profile.encoder}")
st.write(f"- 质量预设: {profile.quality_preset}")
with col2:
st.write(f"- 像素格式: {profile.pixel_format}")
st.write(f"- 兼容性等级: {profile.compatibility_level}/5")
if profile.additional_args:
st.write(f"- 额外参数: {' '.join(profile.additional_args)}")
# 高级设置
with st.expander("🔧 高级设置"):
st.write("**强制设置选项**")
col1, col2 = st.columns(2)
with col1:
if st.button("🚫 强制禁用硬件加速"):
try:
ffmpeg_utils.force_software_encoding()
st.success("✅ 已强制禁用硬件加速")
st.info("这将使用纯软件编码,兼容性最高但性能较低")
except Exception as e:
st.error(f"❌ 操作失败: {str(e)}")
with col2:
if st.button("🔄 重置硬件加速检测"):
try:
ffmpeg_utils.reset_hwaccel_detection()
st.success("✅ 已重置硬件加速检测")
st.info("下次使用时将重新检测硬件加速能力")
except Exception as e:
st.error(f"❌ 操作失败: {str(e)}")
# 测试按钮
st.write("**测试功能**")
if st.button("🧪 测试 FFmpeg 兼容性"):
with st.spinner("正在测试 FFmpeg 兼容性..."):
try:
# 这里可以调用测试脚本
st.info("请在终端运行 `python test_video_extraction.py <video_path>` 进行完整测试")
# 简单的兼容性测试
if ffmpeg_utils and ffmpeg_utils.check_ffmpeg_installation():
hwaccel_info = ffmpeg_utils.get_ffmpeg_hwaccel_info()
if hwaccel_info.get("available"):
st.success("✅ 基础兼容性测试通过")
else:
st.warning("⚠️ 硬件加速不可用,但软件编码应该可以工作")
else:
st.error("❌ FFmpeg 不可用")
except Exception as e:
st.error(f"❌ 测试失败: {str(e)}")
def show_troubleshooting_guide():
"""显示故障排除指南"""
st.subheader("🆘 故障排除指南")
# 常见问题
st.write("**常见问题及解决方案**")
with st.expander("❌ 关键帧提取失败 - 滤镜链错误"):
st.write("""
**问题描述**: 出现 "Impossible to convert between the formats" 错误
**解决方案**:
1. 在设置中选择 "兼容性配置" "Windows NVIDIA 优化配置"
2. 点击 "强制禁用硬件加速" 按钮
3. 重新尝试关键帧提取
4. 如果仍然失败请更新显卡驱动程序
""")
with st.expander("⚠️ 硬件加速不可用"):
st.write("""
**可能原因**:
- 显卡驱动程序过旧
- FFmpeg 版本不支持当前硬件
- 系统缺少必要的运行库
**解决方案**:
1. 更新显卡驱动程序到最新版本
2. 对于 NVIDIA 用户安装 CUDA 工具包
3. 对于 AMD 用户安装 AMD Media SDK
4. 使用软件编码作为备用方案
""")
with st.expander("🐌 处理速度很慢"):
st.write("""
**优化建议**:
1. 启用硬件加速如果可用
2. 选择 "高性能配置"
3. 降低视频质量设置
4. 增加关键帧提取间隔
5. 关闭其他占用 GPU 的程序
""")
with st.expander("📁 文件权限问题"):
st.write("""
**解决方案**:
1. 确保对输出目录有写入权限
2. 以管理员身份运行程序Windows
3. 检查磁盘空间是否充足
4. 避免使用包含特殊字符的文件路径
""")
# 联系支持
st.write("**需要更多帮助?**")
st.info("""
如果上述解决方案都无法解决您的问题
1. 运行 `python test_video_extraction.py` 生成详细的测试报告
2. 记录具体的错误信息和系统环境
3. 联系技术支持并提供相关信息
""")
def render_ffmpeg_diagnostics_page():
"""渲染 FFmpeg 诊断页面"""
st.title("🔧 FFmpeg 诊断与配置")
# 选项卡
tab1, tab2, tab3 = st.tabs(["🔍 诊断信息", "⚙️ 配置设置", "🆘 故障排除"])
with tab1:
show_ffmpeg_diagnostics()
with tab2:
show_ffmpeg_settings()
with tab3:
show_troubleshooting_guide()
if __name__ == "__main__":
render_ffmpeg_diagnostics_page()

View File

@ -65,11 +65,32 @@ def generate_script_docu(params):
# 初始化视频处理器
processor = video_processor.VideoProcessor(params.video_origin_path)
# 显示视频信息
st.info(f"视频信息: {processor.width}x{processor.height}, {processor.fps:.1f}fps, {processor.duration:.1f}")
# 处理视频并提取关键帧
processor.process_video_pipeline(
output_dir=video_keyframes_dir,
interval_seconds=st.session_state.get('frame_interval_input'),
)
update_progress(15, "正在提取关键帧...")
try:
processor.process_video_pipeline(
output_dir=video_keyframes_dir,
interval_seconds=st.session_state.get('frame_interval_input'),
)
except Exception as extract_error:
# 如果硬件加速失败,尝试强制使用软件方案
logger.warning(f"硬件加速提取失败: {extract_error}")
st.warning("硬件加速提取失败,正在尝试软件方案...")
# 强制使用软件编码重试
from app.utils import ffmpeg_utils
ffmpeg_utils.force_software_encoding()
processor.process_video_pipeline(
output_dir=video_keyframes_dir,
interval_seconds=st.session_state.get('frame_interval_input'),
use_hw_accel=False # 明确禁用硬件加速
)
# 获取所有关键文件路径
for filename in sorted(os.listdir(video_keyframes_dir)):
@ -77,9 +98,13 @@ def generate_script_docu(params):
keyframe_files.append(os.path.join(video_keyframes_dir, filename))
if not keyframe_files:
raise Exception("未提取到任何关键帧")
# 检查目录中是否有其他文件
all_files = os.listdir(video_keyframes_dir)
logger.error(f"关键帧目录内容: {all_files}")
raise Exception("未提取到任何关键帧文件,可能是 FFmpeg 兼容性问题")
update_progress(20, f"关键帧提取完成,共 {len(keyframe_files)}")
st.success(f"✅ 成功提取 {len(keyframe_files)} 个关键帧")
except Exception as e:
# 如果提取失败,清理创建的目录
@ -90,7 +115,16 @@ def generate_script_docu(params):
except Exception as cleanup_err:
logger.error(f"清理失败的关键帧目录时出错: {cleanup_err}")
raise Exception(f"关键帧提取失败: {str(e)}")
# 提供更详细的错误信息和解决建议
error_msg = str(e)
if "滤镜链" in error_msg or "filter" in error_msg.lower():
suggestion = "建议:这可能是硬件加速兼容性问题,请尝试在设置中禁用硬件加速"
elif "cuda" in error_msg.lower() or "nvenc" in error_msg.lower():
suggestion = "建议NVIDIA 显卡驱动可能需要更新,或尝试禁用硬件加速"
else:
suggestion = "建议:检查视频文件是否损坏,或尝试转换为标准格式"
raise Exception(f"关键帧提取失败: {error_msg}\n{suggestion}")
"""
2. 视觉分析(批量分析每一帧)