mirror of
https://github.com/linyqh/NarratoAI.git
synced 2025-12-11 02:12:50 +00:00
优化 ffmpeg 硬件加速兼容性
This commit is contained in:
parent
6356a140aa
commit
47cd4f145d
@ -13,6 +13,7 @@ from app.config import config
|
||||
from app.models.exception import HttpException
|
||||
from app.router import root_api_router
|
||||
from app.utils import utils
|
||||
from app.utils import ffmpeg_utils
|
||||
|
||||
|
||||
def exception_handler(request: Request, e: HttpException):
|
||||
@ -80,3 +81,10 @@ def shutdown_event():
|
||||
@app.on_event("startup")
|
||||
def startup_event():
|
||||
logger.info("startup event")
|
||||
|
||||
# 检测FFmpeg硬件加速
|
||||
hwaccel_info = ffmpeg_utils.detect_hardware_acceleration()
|
||||
if hwaccel_info["available"]:
|
||||
logger.info(f"FFmpeg硬件加速检测结果: 可用 | 类型: {hwaccel_info['type']} | 编码器: {hwaccel_info['encoder']} | 独立显卡: {hwaccel_info['is_dedicated_gpu']} | 参数: {hwaccel_info['hwaccel_args']}")
|
||||
else:
|
||||
logger.warning(f"FFmpeg硬件加速不可用: {hwaccel_info['message']}, 将使用CPU软件编码")
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
@Project: NarratoAI
|
||||
@File : clip_video
|
||||
@Author : 小林同学
|
||||
@Date : 2025/5/6 下午6:14
|
||||
@Date : 2025/5/6 下午6:14
|
||||
'''
|
||||
|
||||
import os
|
||||
@ -16,14 +16,16 @@ from loguru import logger
|
||||
from typing import Dict, List, Optional
|
||||
from pathlib import Path
|
||||
|
||||
from app.utils import ffmpeg_utils
|
||||
|
||||
|
||||
def parse_timestamp(timestamp: str) -> tuple:
|
||||
"""
|
||||
解析时间戳字符串,返回开始和结束时间
|
||||
|
||||
|
||||
Args:
|
||||
timestamp: 格式为'HH:MM:SS-HH:MM:SS'或'HH:MM:SS,sss-HH:MM:SS,sss'的时间戳字符串
|
||||
|
||||
|
||||
Returns:
|
||||
tuple: (开始时间, 结束时间) 格式为'HH:MM:SS'或'HH:MM:SS,sss'
|
||||
"""
|
||||
@ -34,37 +36,37 @@ def parse_timestamp(timestamp: str) -> tuple:
|
||||
def calculate_end_time(start_time: str, duration: float, extra_seconds: float = 1.0) -> str:
|
||||
"""
|
||||
根据开始时间和持续时间计算结束时间
|
||||
|
||||
|
||||
Args:
|
||||
start_time: 开始时间,格式为'HH:MM:SS'或'HH:MM:SS,sss'(带毫秒)
|
||||
duration: 持续时间,单位为秒
|
||||
extra_seconds: 额外添加的秒数,默认为1秒
|
||||
|
||||
|
||||
Returns:
|
||||
str: 计算后的结束时间,格式与输入格式相同
|
||||
"""
|
||||
# 检查是否包含毫秒
|
||||
has_milliseconds = ',' in start_time
|
||||
milliseconds = 0
|
||||
|
||||
|
||||
if has_milliseconds:
|
||||
time_part, ms_part = start_time.split(',')
|
||||
h, m, s = map(int, time_part.split(':'))
|
||||
milliseconds = int(ms_part)
|
||||
else:
|
||||
h, m, s = map(int, start_time.split(':'))
|
||||
|
||||
|
||||
# 转换为总毫秒数
|
||||
total_milliseconds = ((h * 3600 + m * 60 + s) * 1000 + milliseconds +
|
||||
total_milliseconds = ((h * 3600 + m * 60 + s) * 1000 + milliseconds +
|
||||
int((duration + extra_seconds) * 1000))
|
||||
|
||||
|
||||
# 计算新的时、分、秒、毫秒
|
||||
ms_new = total_milliseconds % 1000
|
||||
total_seconds = total_milliseconds // 1000
|
||||
h_new = int(total_seconds // 3600)
|
||||
m_new = int((total_seconds % 3600) // 60)
|
||||
s_new = int(total_seconds % 60)
|
||||
|
||||
|
||||
# 返回与输入格式一致的时间字符串
|
||||
if has_milliseconds:
|
||||
return f"{h_new:02d}:{m_new:02d}:{s_new:02d},{ms_new:03d}"
|
||||
@ -75,44 +77,12 @@ def calculate_end_time(start_time: str, duration: float, extra_seconds: float =
|
||||
def check_hardware_acceleration() -> Optional[str]:
|
||||
"""
|
||||
检查系统支持的硬件加速选项
|
||||
|
||||
|
||||
Returns:
|
||||
Optional[str]: 硬件加速参数,如果不支持则返回None
|
||||
"""
|
||||
# 检查NVIDIA GPU支持
|
||||
try:
|
||||
nvidia_check = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "cuda", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if nvidia_check.returncode == 0:
|
||||
return "cuda"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 检查MacOS videotoolbox支持
|
||||
try:
|
||||
videotoolbox_check = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "videotoolbox", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if videotoolbox_check.returncode == 0:
|
||||
return "videotoolbox"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 检查Intel Quick Sync支持
|
||||
try:
|
||||
qsv_check = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "qsv", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if qsv_check.returncode == 0:
|
||||
return "qsv"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
# 使用集中式硬件加速检测
|
||||
return ffmpeg_utils.get_ffmpeg_hwaccel_type()
|
||||
|
||||
|
||||
def clip_video(
|
||||
@ -123,13 +93,13 @@ def clip_video(
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
根据时间戳裁剪视频
|
||||
|
||||
|
||||
Args:
|
||||
video_origin_path: 原始视频的路径
|
||||
tts_result: 包含时间戳和持续时间信息的列表
|
||||
output_dir: 输出目录路径,默认为None时会自动生成
|
||||
task_id: 任务ID,用于生成唯一的输出目录,默认为None时会自动生成
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: 时间戳到裁剪后视频路径的映射
|
||||
"""
|
||||
@ -152,12 +122,11 @@ def clip_video(
|
||||
# 确保输出目录存在
|
||||
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 检查硬件加速支持
|
||||
# 获取硬件加速支持
|
||||
hwaccel = check_hardware_acceleration()
|
||||
hwaccel_args = []
|
||||
if hwaccel:
|
||||
hwaccel_args = ["-hwaccel", hwaccel]
|
||||
logger.info(f"使用硬件加速: {hwaccel}")
|
||||
hwaccel_args = ffmpeg_utils.get_ffmpeg_hwaccel_args()
|
||||
|
||||
# 存储裁剪结果
|
||||
result = {}
|
||||
@ -170,7 +139,7 @@ def clip_video(
|
||||
# 根据持续时间计算真正的结束时间(加上1秒余量)
|
||||
duration = item["duration"]
|
||||
calculated_end_time = calculate_end_time(start_time, duration)
|
||||
|
||||
|
||||
# 转换为FFmpeg兼容的时间格式(逗号替换为点)
|
||||
ffmpeg_start_time = start_time.replace(',', '.')
|
||||
ffmpeg_end_time = calculated_end_time.replace(',', '.')
|
||||
|
||||
@ -14,6 +14,7 @@ from moviepy.video.io.VideoFileClip import VideoFileClip
|
||||
from app.config import config
|
||||
from app.models.schema import VideoAspect, VideoConcatMode, MaterialInfo
|
||||
from app.utils import utils
|
||||
from app.utils import ffmpeg_utils
|
||||
|
||||
requested_count = 0
|
||||
|
||||
@ -257,10 +258,10 @@ def time_to_seconds(time_str: str) -> float:
|
||||
"""
|
||||
将时间字符串转换为秒数
|
||||
支持格式: 'HH:MM:SS,mmm' (时:分:秒,毫秒)
|
||||
|
||||
|
||||
Args:
|
||||
time_str: 时间字符串,如 "00:00:20,100"
|
||||
|
||||
|
||||
Returns:
|
||||
float: 转换后的秒数(包含毫秒)
|
||||
"""
|
||||
@ -282,7 +283,7 @@ def time_to_seconds(time_str: str) -> float:
|
||||
raise ValueError("时间格式必须为 HH:MM:SS,mmm")
|
||||
|
||||
return seconds + ms
|
||||
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"时间格式错误: {time_str}")
|
||||
raise ValueError(f"时间格式错误: 必须为 HH:MM:SS,mmm 格式") from e
|
||||
@ -291,10 +292,10 @@ def time_to_seconds(time_str: str) -> float:
|
||||
def format_timestamp(seconds: float) -> str:
|
||||
"""
|
||||
将秒数转换为可读的时间格式 (HH:MM:SS,mmm)
|
||||
|
||||
|
||||
Args:
|
||||
seconds: 秒数(可包含毫秒)
|
||||
|
||||
|
||||
Returns:
|
||||
str: 格式化的时间字符串,如 "00:00:20,100"
|
||||
"""
|
||||
@ -303,57 +304,26 @@ def format_timestamp(seconds: float) -> str:
|
||||
seconds_remain = seconds % 60
|
||||
whole_seconds = int(seconds_remain)
|
||||
milliseconds = int((seconds_remain - whole_seconds) * 1000)
|
||||
|
||||
|
||||
return f"{hours:02d}:{minutes:02d}:{whole_seconds:02d},{milliseconds:03d}"
|
||||
|
||||
|
||||
def _detect_hardware_acceleration() -> Optional[str]:
|
||||
"""
|
||||
检测系统可用的硬件加速器
|
||||
|
||||
|
||||
Returns:
|
||||
Optional[str]: 硬件加速参数,如果不支持则返回None
|
||||
"""
|
||||
# 检查NVIDIA GPU支持
|
||||
try:
|
||||
nvidia_check = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "cuda", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if nvidia_check.returncode == 0:
|
||||
return "cuda"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 检查MacOS videotoolbox支持
|
||||
try:
|
||||
videotoolbox_check = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "videotoolbox", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if videotoolbox_check.returncode == 0:
|
||||
return "videotoolbox"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 检查Intel Quick Sync支持
|
||||
try:
|
||||
qsv_check = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "qsv", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if qsv_check.returncode == 0:
|
||||
return "qsv"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
# 使用集中式硬件加速检测
|
||||
hwaccel_type = ffmpeg_utils.get_ffmpeg_hwaccel_type()
|
||||
return hwaccel_type
|
||||
|
||||
|
||||
def save_clip_video(timestamp: str, origin_video: str, save_dir: str = "") -> str:
|
||||
"""
|
||||
保存剪辑后的视频
|
||||
|
||||
|
||||
Args:
|
||||
timestamp: 需要裁剪的时间戳,格式为 'HH:MM:SS,mmm-HH:MM:SS,mmm'
|
||||
例如: '00:00:00,000-00:00:20,100'
|
||||
@ -374,7 +344,7 @@ def save_clip_video(timestamp: str, origin_video: str, save_dir: str = "") -> st
|
||||
|
||||
# 解析时间戳
|
||||
start_str, end_str = timestamp.split('-')
|
||||
|
||||
|
||||
# 格式化输出文件名(使用连字符替代冒号和逗号)
|
||||
safe_start_time = start_str.replace(':', '-').replace(',', '-')
|
||||
safe_end_time = end_str.replace(':', '-').replace(',', '-')
|
||||
@ -391,48 +361,47 @@ def save_clip_video(timestamp: str, origin_video: str, save_dir: str = "") -> st
|
||||
if not os.path.exists(origin_video):
|
||||
logger.error(f"源视频文件不存在: {origin_video}")
|
||||
return ''
|
||||
|
||||
|
||||
# 获取视频总时长
|
||||
try:
|
||||
probe_cmd = ["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
||||
probe_cmd = ["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1", origin_video]
|
||||
total_duration = float(subprocess.check_output(probe_cmd).decode('utf-8').strip())
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"获取视频时长失败: {str(e)}")
|
||||
return ''
|
||||
|
||||
|
||||
# 计算时间点
|
||||
start = time_to_seconds(start_str)
|
||||
end = time_to_seconds(end_str)
|
||||
|
||||
|
||||
# 验证时间段
|
||||
if start >= total_duration:
|
||||
logger.warning(f"起始时间 {format_timestamp(start)} ({start:.3f}秒) 超出视频总时长 {format_timestamp(total_duration)} ({total_duration:.3f}秒)")
|
||||
return ''
|
||||
|
||||
|
||||
if end > total_duration:
|
||||
logger.warning(f"结束时间 {format_timestamp(end)} ({end:.3f}秒) 超出视频总时长 {format_timestamp(total_duration)} ({total_duration:.3f}秒),将自动调整为视频结尾")
|
||||
end = total_duration
|
||||
|
||||
|
||||
if end <= start:
|
||||
logger.warning(f"结束时间 {format_timestamp(end)} 必须大于起始时间 {format_timestamp(start)}")
|
||||
return ''
|
||||
|
||||
|
||||
# 计算剪辑时长
|
||||
duration = end - start
|
||||
# logger.info(f"开始剪辑视频: {format_timestamp(start)} - {format_timestamp(end)},时长 {format_timestamp(duration)}")
|
||||
|
||||
# 检测可用的硬件加速选项
|
||||
|
||||
# 获取硬件加速选项
|
||||
hwaccel = _detect_hardware_acceleration()
|
||||
hwaccel_args = []
|
||||
if hwaccel:
|
||||
hwaccel_args = ["-hwaccel", hwaccel]
|
||||
logger.info(f"使用硬件加速: {hwaccel}")
|
||||
|
||||
hwaccel_args = ffmpeg_utils.get_ffmpeg_hwaccel_args()
|
||||
|
||||
# 转换为FFmpeg兼容的时间格式(逗号替换为点)
|
||||
ffmpeg_start_time = start_str.replace(',', '.')
|
||||
ffmpeg_end_time = end_str.replace(',', '.')
|
||||
|
||||
|
||||
# 构建FFmpeg命令
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg", "-y", *hwaccel_args,
|
||||
@ -444,36 +413,36 @@ def save_clip_video(timestamp: str, origin_video: str, save_dir: str = "") -> st
|
||||
"-strict", "experimental",
|
||||
video_path
|
||||
]
|
||||
|
||||
|
||||
# 执行FFmpeg命令
|
||||
# logger.info(f"裁剪视频片段: {timestamp} -> {ffmpeg_start_time}到{ffmpeg_end_time}")
|
||||
# logger.debug(f"执行命令: {' '.join(ffmpeg_cmd)}")
|
||||
|
||||
|
||||
process = subprocess.run(
|
||||
ffmpeg_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
ffmpeg_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=False # 不抛出异常,我们会检查返回码
|
||||
)
|
||||
|
||||
|
||||
# 检查是否成功
|
||||
if process.returncode != 0:
|
||||
logger.error(f"视频剪辑失败: {process.stderr}")
|
||||
if os.path.exists(video_path):
|
||||
os.remove(video_path)
|
||||
return ''
|
||||
|
||||
|
||||
# 验证生成的视频文件
|
||||
if os.path.exists(video_path) and os.path.getsize(video_path) > 0:
|
||||
# 检查视频是否可播放
|
||||
probe_cmd = ["ffprobe", "-v", "error", video_path]
|
||||
validate_result = subprocess.run(probe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
if validate_result.returncode == 0:
|
||||
logger.info(f"视频剪辑成功: {video_path}")
|
||||
return video_path
|
||||
|
||||
|
||||
logger.error("视频文件验证失败")
|
||||
if os.path.exists(video_path):
|
||||
os.remove(video_path)
|
||||
@ -506,14 +475,14 @@ def clip_videos(task_id: str, timestamp_terms: List[str], origin_video: str, pro
|
||||
saved_video_path = save_clip_video(timestamp=item, origin_video=origin_video, save_dir=material_directory)
|
||||
if saved_video_path:
|
||||
video_paths.update({index+1:saved_video_path})
|
||||
|
||||
|
||||
# 更新进度
|
||||
if progress_callback:
|
||||
progress_callback(index + 1, total_items)
|
||||
except Exception as e:
|
||||
logger.error(f"视频裁剪失败: {utils.to_json(item)} =>\n{str(traceback.format_exc())}")
|
||||
return {}
|
||||
|
||||
|
||||
logger.success(f"裁剪 {len(video_paths)} videos")
|
||||
# logger.debug(json.dumps(video_paths, indent=4, ensure_ascii=False))
|
||||
return video_paths
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
@Project: NarratoAI
|
||||
@File : merger_video
|
||||
@Author : 小林同学
|
||||
@Date : 2025/5/6 下午7:38
|
||||
@Date : 2025/5/6 下午7:38
|
||||
'''
|
||||
|
||||
import os
|
||||
@ -15,6 +15,8 @@ from enum import Enum
|
||||
from typing import List, Optional, Tuple
|
||||
from loguru import logger
|
||||
|
||||
from app.utils import ffmpeg_utils
|
||||
|
||||
|
||||
class VideoAspect(Enum):
|
||||
"""视频宽高比枚举"""
|
||||
@ -43,7 +45,7 @@ class VideoAspect(Enum):
|
||||
def check_ffmpeg_installation() -> bool:
|
||||
"""
|
||||
检查ffmpeg是否已安装
|
||||
|
||||
|
||||
Returns:
|
||||
bool: 如果安装则返回True,否则返回False
|
||||
"""
|
||||
@ -58,88 +60,36 @@ def check_ffmpeg_installation() -> bool:
|
||||
def get_hardware_acceleration_option() -> Optional[str]:
|
||||
"""
|
||||
根据系统环境选择合适的硬件加速选项
|
||||
|
||||
|
||||
Returns:
|
||||
Optional[str]: 硬件加速参数,如果不支持则返回None
|
||||
"""
|
||||
try:
|
||||
# 检测操作系统
|
||||
is_windows = os.name == 'nt'
|
||||
|
||||
# 检查NVIDIA GPU支持
|
||||
nvidia_check = subprocess.run(
|
||||
['ffmpeg', '-hide_banner', '-hwaccels'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
output = nvidia_check.stdout.lower()
|
||||
|
||||
# 首先尝试获取系统信息,Windows系统使用更安全的检测方法
|
||||
if is_windows:
|
||||
try:
|
||||
# 尝试检测显卡信息
|
||||
gpu_info = subprocess.run(
|
||||
['wmic', 'path', 'win32_VideoController', 'get', 'name'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
gpu_info_output = gpu_info.stdout.lower()
|
||||
|
||||
# 检测是否为AMD显卡
|
||||
if 'amd' in gpu_info_output or 'radeon' in gpu_info_output:
|
||||
logger.info("检测到AMD显卡,为避免兼容性问题,将使用软件编码")
|
||||
return None
|
||||
|
||||
# 检测是否为集成显卡
|
||||
if 'intel' in gpu_info_output and ('hd graphics' in gpu_info_output or 'uhd graphics' in gpu_info_output):
|
||||
# 在Windows上,Intel集成显卡可能不稳定,建议使用软件编码
|
||||
logger.info("检测到Intel集成显卡,为避免兼容性问题,将使用软件编码")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"获取显卡信息失败: {str(e)},将谨慎处理硬件加速")
|
||||
|
||||
# 根据ffmpeg支持的硬件加速器决定使用哪种
|
||||
if 'cuda' in output and not is_windows:
|
||||
# 在非Windows系统上使用CUDA
|
||||
return 'cuda'
|
||||
elif 'nvenc' in output and not is_windows:
|
||||
# 在非Windows系统上使用NVENC
|
||||
return 'nvenc'
|
||||
elif 'qsv' in output and not (is_windows and ('amd' in gpu_info_output if 'gpu_info_output' in locals() else False)):
|
||||
# 只有在非AMD系统上使用QSV
|
||||
return 'qsv'
|
||||
elif 'videotoolbox' in output: # macOS
|
||||
return 'videotoolbox'
|
||||
elif 'vaapi' in output and not is_windows: # Linux VA-API
|
||||
return 'vaapi'
|
||||
else:
|
||||
logger.info("没有找到支持的硬件加速器或系统不兼容,将使用软件编码")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"检测硬件加速器时出错: {str(e)},将使用软件编码")
|
||||
return None
|
||||
# 使用集中式硬件加速检测
|
||||
return ffmpeg_utils.get_ffmpeg_hwaccel_type()
|
||||
|
||||
|
||||
def check_video_has_audio(video_path: str) -> bool:
|
||||
"""
|
||||
检查视频是否包含音频流
|
||||
|
||||
|
||||
Args:
|
||||
video_path: 视频文件路径
|
||||
|
||||
|
||||
Returns:
|
||||
bool: 如果视频包含音频流则返回True,否则返回False
|
||||
"""
|
||||
if not os.path.exists(video_path):
|
||||
logger.warning(f"视频文件不存在: {video_path}")
|
||||
return False
|
||||
|
||||
|
||||
probe_cmd = [
|
||||
'ffprobe', '-v', 'error',
|
||||
'-select_streams', 'a:0',
|
||||
'-show_entries', 'stream=codec_type',
|
||||
'-of', 'csv=p=0',
|
||||
'ffprobe', '-v', 'error',
|
||||
'-select_streams', 'a:0',
|
||||
'-show_entries', 'stream=codec_type',
|
||||
'-of', 'csv=p=0',
|
||||
video_path
|
||||
]
|
||||
|
||||
|
||||
try:
|
||||
result = subprocess.run(probe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
|
||||
return result.stdout.strip() == 'audio'
|
||||
@ -151,11 +101,11 @@ def check_video_has_audio(video_path: str) -> bool:
|
||||
def create_ffmpeg_concat_file(video_paths: List[str], concat_file_path: str) -> str:
|
||||
"""
|
||||
创建ffmpeg合并所需的concat文件
|
||||
|
||||
|
||||
Args:
|
||||
video_paths: 需要合并的视频文件路径列表
|
||||
concat_file_path: concat文件的输出路径
|
||||
|
||||
|
||||
Returns:
|
||||
str: concat文件的路径
|
||||
"""
|
||||
@ -169,10 +119,10 @@ def create_ffmpeg_concat_file(video_paths: List[str], concat_file_path: str) ->
|
||||
else: # Unix/Mac系统
|
||||
# 转义特殊字符
|
||||
abs_path = abs_path.replace('\\', '\\\\').replace(':', '\\:')
|
||||
|
||||
|
||||
# 处理路径中的单引号 (如果有)
|
||||
abs_path = abs_path.replace("'", "\\'")
|
||||
|
||||
|
||||
f.write(f"file '{abs_path}'\n")
|
||||
return concat_file_path
|
||||
|
||||
@ -187,7 +137,7 @@ def process_single_video(
|
||||
) -> str:
|
||||
"""
|
||||
处理单个视频:调整分辨率、帧率等
|
||||
|
||||
|
||||
Args:
|
||||
input_path: 输入视频路径
|
||||
output_path: 输出视频路径
|
||||
@ -195,7 +145,7 @@ def process_single_video(
|
||||
target_height: 目标高度
|
||||
keep_audio: 是否保留音频
|
||||
hwaccel: 硬件加速选项
|
||||
|
||||
|
||||
Returns:
|
||||
str: 处理后的视频路径
|
||||
"""
|
||||
@ -212,14 +162,14 @@ def process_single_video(
|
||||
try:
|
||||
# 对视频进行快速探测,检测其基本信息
|
||||
probe_cmd = [
|
||||
'ffprobe', '-v', 'error',
|
||||
'-select_streams', 'v:0',
|
||||
'-show_entries', 'stream=codec_name,width,height',
|
||||
'-of', 'csv=p=0',
|
||||
'ffprobe', '-v', 'error',
|
||||
'-select_streams', 'v:0',
|
||||
'-show_entries', 'stream=codec_name,width,height',
|
||||
'-of', 'csv=p=0',
|
||||
input_path
|
||||
]
|
||||
result = subprocess.run(probe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
|
||||
|
||||
|
||||
# 如果探测成功,使用硬件加速;否则降级到软件编码
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"视频探测失败,为安全起见,禁用硬件加速: {result.stderr}")
|
||||
@ -231,15 +181,9 @@ def process_single_video(
|
||||
# 添加硬件加速参数(根据前面的安全检查可能已经被禁用)
|
||||
if hwaccel:
|
||||
try:
|
||||
if hwaccel == 'cuda' or hwaccel == 'nvenc':
|
||||
command.extend(['-hwaccel', 'cuda'])
|
||||
elif hwaccel == 'qsv':
|
||||
command.extend(['-hwaccel', 'qsv'])
|
||||
elif hwaccel == 'videotoolbox':
|
||||
command.extend(['-hwaccel', 'videotoolbox'])
|
||||
elif hwaccel == 'vaapi':
|
||||
command.extend(['-hwaccel', 'vaapi', '-vaapi_device', '/dev/dri/renderD128'])
|
||||
logger.info(f"应用硬件加速: {hwaccel}")
|
||||
# 使用集中式硬件加速参数
|
||||
hwaccel_args = ffmpeg_utils.get_ffmpeg_hwaccel_args()
|
||||
command.extend(hwaccel_args)
|
||||
except Exception as e:
|
||||
logger.warning(f"应用硬件加速参数时出错: {str(e)},将使用软件编码")
|
||||
# 重置命令,移除可能添加了一半的硬件加速参数
|
||||
@ -270,7 +214,7 @@ def process_single_video(
|
||||
|
||||
# 选择编码器 - 考虑到Windows和特定硬件的兼容性
|
||||
use_software_encoder = True
|
||||
|
||||
|
||||
if hwaccel:
|
||||
if hwaccel == 'cuda' or hwaccel == 'nvenc':
|
||||
try:
|
||||
@ -289,7 +233,7 @@ def process_single_video(
|
||||
elif hwaccel == 'vaapi' and not is_windows: # Linux VA-API
|
||||
command.extend(['-c:v', 'h264_vaapi', '-profile', '100'])
|
||||
use_software_encoder = False
|
||||
|
||||
|
||||
# 如果前面的条件未能应用硬件编码器,使用软件编码
|
||||
if use_software_encoder:
|
||||
logger.info("使用软件编码器(libx264)")
|
||||
@ -315,14 +259,14 @@ def process_single_video(
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_msg = e.stderr.decode() if e.stderr else str(e)
|
||||
logger.error(f"处理视频失败: {error_msg}")
|
||||
|
||||
|
||||
# 如果使用硬件加速失败,尝试使用软件编码
|
||||
if hwaccel:
|
||||
logger.info("尝试使用软件编码作为备选方案")
|
||||
try:
|
||||
# 构建新的命令,使用软件编码
|
||||
fallback_cmd = ['ffmpeg', '-y', '-i', input_path]
|
||||
|
||||
|
||||
# 保持原有的音频设置
|
||||
if not keep_audio:
|
||||
fallback_cmd.extend(['-an'])
|
||||
@ -332,7 +276,7 @@ def process_single_video(
|
||||
fallback_cmd.extend(['-c:a', 'aac', '-b:a', '128k'])
|
||||
else:
|
||||
fallback_cmd.extend(['-an'])
|
||||
|
||||
|
||||
# 保持原有的视频过滤器
|
||||
fallback_cmd.extend([
|
||||
'-vf', f"{scale_filter},{pad_filter}",
|
||||
@ -346,7 +290,7 @@ def process_single_video(
|
||||
'-pix_fmt', 'yuv420p',
|
||||
output_path
|
||||
])
|
||||
|
||||
|
||||
logger.info(f"执行备选FFmpeg命令: {' '.join(fallback_cmd)}")
|
||||
subprocess.run(fallback_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
logger.info(f"使用软件编码成功处理视频: {output_path}")
|
||||
@ -355,7 +299,7 @@ def process_single_video(
|
||||
fallback_error_msg = fallback_error.stderr.decode() if fallback_error.stderr else str(fallback_error)
|
||||
logger.error(f"备选软件编码也失败: {fallback_error_msg}")
|
||||
raise RuntimeError(f"无法处理视频 {input_path}: 硬件加速和软件编码都失败")
|
||||
|
||||
|
||||
# 如果不是硬件加速导致的问题,或者备选方案也失败了,抛出原始错误
|
||||
raise RuntimeError(f"处理视频失败: {error_msg}")
|
||||
|
||||
@ -409,7 +353,7 @@ def combine_clip_videos(
|
||||
|
||||
# 重组视频路径和原声设置为一个字典列表结构
|
||||
video_segments = []
|
||||
|
||||
|
||||
# 检查视频路径和原声设置列表长度是否匹配
|
||||
if len(video_paths) != len(video_ost_list):
|
||||
logger.warning(f"视频路径列表({len(video_paths)})和原声设置列表({len(video_ost_list)})长度不匹配")
|
||||
@ -417,16 +361,16 @@ def combine_clip_videos(
|
||||
min_length = min(len(video_paths), len(video_ost_list))
|
||||
video_paths = video_paths[:min_length]
|
||||
video_ost_list = video_ost_list[:min_length]
|
||||
|
||||
|
||||
# 创建视频处理配置字典列表
|
||||
for i, (video_path, video_ost) in enumerate(zip(video_paths, video_ost_list)):
|
||||
if not os.path.exists(video_path):
|
||||
logger.warning(f"视频不存在,跳过: {video_path}")
|
||||
continue
|
||||
|
||||
|
||||
# 检查是否有音频流
|
||||
has_audio = check_video_has_audio(video_path)
|
||||
|
||||
|
||||
# 构建视频片段配置
|
||||
segment = {
|
||||
"index": i,
|
||||
@ -435,11 +379,11 @@ def combine_clip_videos(
|
||||
"has_audio": has_audio,
|
||||
"keep_audio": video_ost > 0 and has_audio # 只有当ost>0且实际有音频时才保留
|
||||
}
|
||||
|
||||
|
||||
# 记录日志
|
||||
if video_ost > 0 and not has_audio:
|
||||
logger.warning(f"视频 {video_path} 设置为保留原声(ost={video_ost}),但该视频没有音频流")
|
||||
|
||||
|
||||
video_segments.append(segment)
|
||||
|
||||
# 处理每个视频片段
|
||||
@ -495,20 +439,20 @@ def combine_clip_videos(
|
||||
|
||||
if not processed_videos:
|
||||
raise ValueError("没有有效的视频片段可以合并")
|
||||
|
||||
|
||||
# 按原始索引排序处理后的视频
|
||||
processed_videos.sort(key=lambda x: x["index"])
|
||||
|
||||
|
||||
# 第二阶段:分步骤合并视频 - 避免复杂的filter_complex滤镜
|
||||
try:
|
||||
# 1. 首先,将所有没有音频的视频或音频被禁用的视频合并到一个临时文件中
|
||||
video_paths_only = [video["path"] for video in processed_videos]
|
||||
video_concat_path = os.path.join(temp_dir, "video_concat.mp4")
|
||||
|
||||
|
||||
# 创建concat文件,用于合并视频流
|
||||
concat_file = os.path.join(temp_dir, "concat_list.txt")
|
||||
create_ffmpeg_concat_file(video_paths_only, concat_file)
|
||||
|
||||
|
||||
# 合并所有视频流,但不包含音频
|
||||
concat_cmd = [
|
||||
'ffmpeg', '-y',
|
||||
@ -522,19 +466,19 @@ def combine_clip_videos(
|
||||
'-threads', str(threads),
|
||||
video_concat_path
|
||||
]
|
||||
|
||||
|
||||
subprocess.run(concat_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
logger.info("视频流合并完成")
|
||||
|
||||
|
||||
# 2. 提取并合并有音频的片段
|
||||
audio_segments = [video for video in processed_videos if video["keep_audio"]]
|
||||
|
||||
|
||||
if not audio_segments:
|
||||
# 如果没有音频片段,直接使用无音频的合并视频作为最终结果
|
||||
shutil.copy(video_concat_path, output_video_path)
|
||||
logger.info("无音频视频合并完成")
|
||||
return output_video_path
|
||||
|
||||
|
||||
# 创建音频中间文件
|
||||
audio_files = []
|
||||
for i, segment in enumerate(audio_segments):
|
||||
@ -554,11 +498,11 @@ def combine_clip_videos(
|
||||
"path": audio_file
|
||||
})
|
||||
logger.info(f"提取音频 {i+1}/{len(audio_segments)} 完成")
|
||||
|
||||
|
||||
# 3. 计算每个音频片段的时间位置
|
||||
audio_timings = []
|
||||
current_time = 0.0
|
||||
|
||||
|
||||
# 获取每个视频片段的时长
|
||||
for i, video in enumerate(processed_videos):
|
||||
duration_cmd = [
|
||||
@ -569,7 +513,7 @@ def combine_clip_videos(
|
||||
]
|
||||
result = subprocess.run(duration_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
duration = float(result.stdout.strip())
|
||||
|
||||
|
||||
# 如果当前片段需要保留音频,记录时间位置
|
||||
if video["keep_audio"]:
|
||||
for audio in audio_files:
|
||||
@ -580,9 +524,9 @@ def combine_clip_videos(
|
||||
"index": video["index"]
|
||||
})
|
||||
break
|
||||
|
||||
|
||||
current_time += duration
|
||||
|
||||
|
||||
# 4. 创建静音音频轨道作为基础
|
||||
silence_audio = os.path.join(temp_dir, "silence.aac")
|
||||
create_silence_cmd = [
|
||||
@ -595,28 +539,28 @@ def combine_clip_videos(
|
||||
silence_audio
|
||||
]
|
||||
subprocess.run(create_silence_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
# 5. 创建复杂滤镜命令以混合音频
|
||||
filter_script = os.path.join(temp_dir, "filter_script.txt")
|
||||
with open(filter_script, 'w') as f:
|
||||
f.write(f"[0:a]volume=0.0[silence];\n") # 首先静音背景轨道
|
||||
|
||||
|
||||
# 添加每个音频文件
|
||||
for i, timing in enumerate(audio_timings):
|
||||
f.write(f"[{i+1}:a]adelay={int(timing['start']*1000)}|{int(timing['start']*1000)}[a{i}];\n")
|
||||
|
||||
|
||||
# 混合所有音频
|
||||
mix_str = "[silence]"
|
||||
for i in range(len(audio_timings)):
|
||||
mix_str += f"[a{i}]"
|
||||
mix_str += f"amix=inputs={len(audio_timings)+1}:duration=longest[aout]"
|
||||
f.write(mix_str)
|
||||
|
||||
|
||||
# 6. 构建音频合并命令
|
||||
audio_inputs = ['-i', silence_audio]
|
||||
for timing in audio_timings:
|
||||
audio_inputs.extend(['-i', timing["file"]])
|
||||
|
||||
|
||||
mixed_audio = os.path.join(temp_dir, "mixed_audio.aac")
|
||||
audio_mix_cmd = [
|
||||
'ffmpeg', '-y'
|
||||
@ -627,10 +571,10 @@ def combine_clip_videos(
|
||||
'-b:a', '128k',
|
||||
mixed_audio
|
||||
]
|
||||
|
||||
|
||||
subprocess.run(audio_mix_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
logger.info("音频混合完成")
|
||||
|
||||
|
||||
# 7. 将合并的视频和混合的音频组合在一起
|
||||
final_cmd = [
|
||||
'ffmpeg', '-y',
|
||||
@ -643,22 +587,22 @@ def combine_clip_videos(
|
||||
'-shortest',
|
||||
output_video_path
|
||||
]
|
||||
|
||||
|
||||
subprocess.run(final_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
logger.info("视频最终合并完成")
|
||||
|
||||
|
||||
return output_video_path
|
||||
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"合并视频过程中出错: {e.stderr.decode() if e.stderr else str(e)}")
|
||||
|
||||
|
||||
# 尝试备用合并方法 - 最简单的无音频合并
|
||||
logger.info("尝试备用合并方法 - 无音频合并")
|
||||
try:
|
||||
concat_file = os.path.join(temp_dir, "concat_list.txt")
|
||||
video_paths_only = [video["path"] for video in processed_videos]
|
||||
create_ffmpeg_concat_file(video_paths_only, concat_file)
|
||||
|
||||
|
||||
backup_cmd = [
|
||||
'ffmpeg', '-y',
|
||||
'-f', 'concat',
|
||||
@ -668,14 +612,14 @@ def combine_clip_videos(
|
||||
'-an', # 无音频
|
||||
output_video_path
|
||||
]
|
||||
|
||||
|
||||
subprocess.run(backup_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
logger.warning("使用备用方法(无音频)成功合并视频")
|
||||
return output_video_path
|
||||
except Exception as backup_error:
|
||||
logger.error(f"备用合并方法也失败: {str(backup_error)}")
|
||||
raise RuntimeError(f"无法合并视频: {str(backup_error)}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"合并视频时出错: {str(e)}")
|
||||
raise
|
||||
|
||||
419
app/utils/ffmpeg_utils.py
Normal file
419
app/utils/ffmpeg_utils.py
Normal file
@ -0,0 +1,419 @@
|
||||
"""
|
||||
FFmpeg 工具模块 - 提供 FFmpeg 相关的工具函数,特别是硬件加速检测
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from loguru import logger
|
||||
|
||||
# 全局变量,存储检测到的硬件加速信息
|
||||
_FFMPEG_HW_ACCEL_INFO = {
|
||||
"available": False,
|
||||
"type": None,
|
||||
"encoder": None,
|
||||
"hwaccel_args": [],
|
||||
"message": "",
|
||||
"is_dedicated_gpu": False
|
||||
}
|
||||
|
||||
|
||||
def check_ffmpeg_installation() -> bool:
|
||||
"""
|
||||
检查ffmpeg是否已安装
|
||||
|
||||
Returns:
|
||||
bool: 如果安装则返回True,否则返回False
|
||||
"""
|
||||
try:
|
||||
subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
||||
return True
|
||||
except (subprocess.SubprocessError, FileNotFoundError):
|
||||
logger.error("ffmpeg未安装或不在系统PATH中,请安装ffmpeg")
|
||||
return False
|
||||
|
||||
|
||||
def detect_hardware_acceleration() -> Dict[str, Union[bool, str, List[str], None]]:
|
||||
"""
|
||||
检测系统可用的硬件加速器,并存储结果到全局变量
|
||||
|
||||
Returns:
|
||||
Dict: 包含硬件加速信息的字典
|
||||
"""
|
||||
global _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
# 如果已经检测过,直接返回结果
|
||||
if _FFMPEG_HW_ACCEL_INFO["type"] is not None:
|
||||
return _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
# 检查ffmpeg是否已安装
|
||||
if not check_ffmpeg_installation():
|
||||
_FFMPEG_HW_ACCEL_INFO["message"] = "FFmpeg未安装或不在系统PATH中"
|
||||
return _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
# 检测操作系统
|
||||
system = platform.system().lower()
|
||||
logger.debug(f"检测硬件加速 - 操作系统: {system}")
|
||||
|
||||
# 获取FFmpeg支持的硬件加速器列表
|
||||
try:
|
||||
hwaccels_cmd = subprocess.run(
|
||||
['ffmpeg', '-hide_banner', '-hwaccels'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
supported_hwaccels = hwaccels_cmd.stdout.lower()
|
||||
except Exception as e:
|
||||
logger.error(f"获取FFmpeg硬件加速器列表失败: {str(e)}")
|
||||
supported_hwaccels = ""
|
||||
|
||||
# 根据操作系统检测不同的硬件加速器
|
||||
if system == 'darwin': # macOS
|
||||
_detect_macos_acceleration(supported_hwaccels)
|
||||
elif system == 'windows': # Windows
|
||||
_detect_windows_acceleration(supported_hwaccels)
|
||||
elif system == 'linux': # Linux
|
||||
_detect_linux_acceleration(supported_hwaccels)
|
||||
else:
|
||||
logger.warning(f"不支持的操作系统: {system}")
|
||||
_FFMPEG_HW_ACCEL_INFO["message"] = f"不支持的操作系统: {system}"
|
||||
|
||||
# 记录检测结果已经在启动时输出,这里不再重复输出
|
||||
|
||||
return _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
|
||||
def _detect_macos_acceleration(supported_hwaccels: str) -> None:
|
||||
"""
|
||||
检测macOS系统的硬件加速
|
||||
|
||||
Args:
|
||||
supported_hwaccels: FFmpeg支持的硬件加速器列表
|
||||
"""
|
||||
global _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
if 'videotoolbox' in supported_hwaccels:
|
||||
# 测试videotoolbox
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "videotoolbox", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "videotoolbox"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_videotoolbox"
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "videotoolbox"]
|
||||
# macOS的Metal GPU加速通常是集成GPU
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = False
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试videotoolbox失败: {str(e)}")
|
||||
|
||||
_FFMPEG_HW_ACCEL_INFO["message"] = "macOS系统未检测到可用的videotoolbox硬件加速"
|
||||
|
||||
|
||||
def _detect_windows_acceleration(supported_hwaccels: str) -> None:
|
||||
"""
|
||||
检测Windows系统的硬件加速
|
||||
|
||||
Args:
|
||||
supported_hwaccels: FFmpeg支持的硬件加速器列表
|
||||
"""
|
||||
global _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
# 在Windows上,首先检查显卡信息
|
||||
gpu_info = _get_windows_gpu_info()
|
||||
|
||||
# 检查是否为AMD显卡
|
||||
if 'amd' in gpu_info.lower() or 'radeon' in gpu_info.lower():
|
||||
logger.info("检测到AMD显卡,为避免兼容性问题,将使用软件编码")
|
||||
_FFMPEG_HW_ACCEL_INFO["message"] = "检测到AMD显卡,为避免兼容性问题,将使用软件编码"
|
||||
return
|
||||
|
||||
# 检查是否为Intel集成显卡
|
||||
is_intel_integrated = False
|
||||
if 'intel' in gpu_info.lower() and ('hd graphics' in gpu_info.lower() or 'uhd graphics' in gpu_info.lower()):
|
||||
logger.info("检测到Intel集成显卡")
|
||||
is_intel_integrated = True
|
||||
|
||||
# 检测NVIDIA CUDA支持
|
||||
if 'cuda' in supported_hwaccels and 'nvidia' in gpu_info.lower():
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "cuda", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "cuda"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_nvenc"
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "cuda"]
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = True
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试CUDA失败: {str(e)}")
|
||||
|
||||
# 检测Intel QSV支持(如果是Intel显卡)
|
||||
if 'qsv' in supported_hwaccels and 'intel' in gpu_info.lower():
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "qsv", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "qsv"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_qsv"
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "qsv"]
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = not is_intel_integrated
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试QSV失败: {str(e)}")
|
||||
|
||||
# 检测D3D11VA支持
|
||||
if 'd3d11va' in supported_hwaccels:
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "d3d11va", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "d3d11va"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264" # D3D11VA只用于解码,编码仍使用软件编码器
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "d3d11va"]
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = not is_intel_integrated
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试D3D11VA失败: {str(e)}")
|
||||
|
||||
# 检测DXVA2支持
|
||||
if 'dxva2' in supported_hwaccels:
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "dxva2", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "dxva2"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264" # DXVA2只用于解码,编码仍使用软件编码器
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "dxva2"]
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = not is_intel_integrated
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试DXVA2失败: {str(e)}")
|
||||
|
||||
_FFMPEG_HW_ACCEL_INFO["message"] = f"Windows系统未检测到可用的硬件加速,显卡信息: {gpu_info}"
|
||||
|
||||
|
||||
def _detect_linux_acceleration(supported_hwaccels: str) -> None:
|
||||
"""
|
||||
检测Linux系统的硬件加速
|
||||
|
||||
Args:
|
||||
supported_hwaccels: FFmpeg支持的硬件加速器列表
|
||||
"""
|
||||
global _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
# 获取Linux显卡信息
|
||||
gpu_info = _get_linux_gpu_info()
|
||||
is_nvidia = 'nvidia' in gpu_info.lower()
|
||||
is_intel = 'intel' in gpu_info.lower()
|
||||
is_amd = 'amd' in gpu_info.lower() or 'radeon' in gpu_info.lower()
|
||||
|
||||
# 检测NVIDIA CUDA支持
|
||||
if 'cuda' in supported_hwaccels and is_nvidia:
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "cuda", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "cuda"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_nvenc"
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "cuda"]
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = True
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试CUDA失败: {str(e)}")
|
||||
|
||||
# 检测VAAPI支持
|
||||
if 'vaapi' in supported_hwaccels:
|
||||
# 检查是否存在渲染设备
|
||||
render_devices = ['/dev/dri/renderD128', '/dev/dri/renderD129']
|
||||
render_device = None
|
||||
for device in render_devices:
|
||||
if os.path.exists(device):
|
||||
render_device = device
|
||||
break
|
||||
|
||||
if render_device:
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "vaapi", "-vaapi_device", render_device,
|
||||
"-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "vaapi"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_vaapi"
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "vaapi", "-vaapi_device", render_device]
|
||||
# 根据显卡类型判断是否为独立显卡
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = is_nvidia or (is_amd and not is_intel)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试VAAPI失败: {str(e)}")
|
||||
|
||||
# 检测Intel QSV支持
|
||||
if 'qsv' in supported_hwaccels and is_intel:
|
||||
try:
|
||||
test_cmd = subprocess.run(
|
||||
["ffmpeg", "-hwaccel", "qsv", "-i", "/dev/null", "-f", "null", "-"],
|
||||
stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
if test_cmd.returncode == 0:
|
||||
_FFMPEG_HW_ACCEL_INFO["available"] = True
|
||||
_FFMPEG_HW_ACCEL_INFO["type"] = "qsv"
|
||||
_FFMPEG_HW_ACCEL_INFO["encoder"] = "h264_qsv"
|
||||
_FFMPEG_HW_ACCEL_INFO["hwaccel_args"] = ["-hwaccel", "qsv"]
|
||||
_FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"] = False # Intel QSV通常是集成GPU
|
||||
return
|
||||
except Exception as e:
|
||||
logger.debug(f"测试QSV失败: {str(e)}")
|
||||
|
||||
_FFMPEG_HW_ACCEL_INFO["message"] = f"Linux系统未检测到可用的硬件加速,显卡信息: {gpu_info}"
|
||||
|
||||
|
||||
def _get_windows_gpu_info() -> str:
|
||||
"""
|
||||
获取Windows系统的显卡信息
|
||||
|
||||
Returns:
|
||||
str: 显卡信息字符串
|
||||
"""
|
||||
try:
|
||||
gpu_info = subprocess.run(
|
||||
['wmic', 'path', 'win32_VideoController', 'get', 'name'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False
|
||||
)
|
||||
return gpu_info.stdout
|
||||
except Exception as e:
|
||||
logger.warning(f"获取Windows显卡信息失败: {str(e)}")
|
||||
return "Unknown GPU"
|
||||
|
||||
|
||||
def _get_linux_gpu_info() -> str:
|
||||
"""
|
||||
获取Linux系统的显卡信息
|
||||
|
||||
Returns:
|
||||
str: 显卡信息字符串
|
||||
"""
|
||||
try:
|
||||
# 尝试使用lspci命令
|
||||
gpu_info = subprocess.run(
|
||||
['lspci', '-v', '-nn', '|', 'grep', '-i', 'vga\\|display'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=False
|
||||
)
|
||||
if gpu_info.stdout:
|
||||
return gpu_info.stdout
|
||||
|
||||
# 如果lspci命令失败,尝试使用glxinfo
|
||||
gpu_info = subprocess.run(
|
||||
['glxinfo', '|', 'grep', '-i', 'vendor\\|renderer'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=False
|
||||
)
|
||||
if gpu_info.stdout:
|
||||
return gpu_info.stdout
|
||||
|
||||
return "Unknown GPU"
|
||||
except Exception as e:
|
||||
logger.warning(f"获取Linux显卡信息失败: {str(e)}")
|
||||
return "Unknown GPU"
|
||||
|
||||
|
||||
def get_ffmpeg_hwaccel_args() -> List[str]:
|
||||
"""
|
||||
获取FFmpeg硬件加速参数
|
||||
|
||||
Returns:
|
||||
List[str]: FFmpeg硬件加速参数列表
|
||||
"""
|
||||
# 如果还没有检测过,先进行检测
|
||||
if _FFMPEG_HW_ACCEL_INFO["type"] is None:
|
||||
detect_hardware_acceleration()
|
||||
|
||||
return _FFMPEG_HW_ACCEL_INFO["hwaccel_args"]
|
||||
|
||||
|
||||
def get_ffmpeg_hwaccel_type() -> Optional[str]:
|
||||
"""
|
||||
获取FFmpeg硬件加速类型
|
||||
|
||||
Returns:
|
||||
Optional[str]: 硬件加速类型,如果不支持则返回None
|
||||
"""
|
||||
# 如果还没有检测过,先进行检测
|
||||
if _FFMPEG_HW_ACCEL_INFO["type"] is None:
|
||||
detect_hardware_acceleration()
|
||||
|
||||
return _FFMPEG_HW_ACCEL_INFO["type"] if _FFMPEG_HW_ACCEL_INFO["available"] else None
|
||||
|
||||
|
||||
def get_ffmpeg_hwaccel_encoder() -> Optional[str]:
|
||||
"""
|
||||
获取FFmpeg硬件加速编码器
|
||||
|
||||
Returns:
|
||||
Optional[str]: 硬件加速编码器,如果不支持则返回None
|
||||
"""
|
||||
# 如果还没有检测过,先进行检测
|
||||
if _FFMPEG_HW_ACCEL_INFO["type"] is None:
|
||||
detect_hardware_acceleration()
|
||||
|
||||
return _FFMPEG_HW_ACCEL_INFO["encoder"] if _FFMPEG_HW_ACCEL_INFO["available"] else None
|
||||
|
||||
|
||||
def get_ffmpeg_hwaccel_info() -> Dict[str, Union[bool, str, List[str], None]]:
|
||||
"""
|
||||
获取FFmpeg硬件加速信息
|
||||
|
||||
Returns:
|
||||
Dict: 包含硬件加速信息的字典
|
||||
"""
|
||||
# 如果还没有检测过,先进行检测
|
||||
if _FFMPEG_HW_ACCEL_INFO["type"] is None:
|
||||
detect_hardware_acceleration()
|
||||
|
||||
return _FFMPEG_HW_ACCEL_INFO
|
||||
|
||||
|
||||
def is_ffmpeg_hwaccel_available() -> bool:
|
||||
"""
|
||||
检查是否有可用的FFmpeg硬件加速
|
||||
|
||||
Returns:
|
||||
bool: 如果有可用的硬件加速则返回True,否则返回False
|
||||
"""
|
||||
# 如果还没有检测过,先进行检测
|
||||
if _FFMPEG_HW_ACCEL_INFO["type"] is None:
|
||||
detect_hardware_acceleration()
|
||||
|
||||
return _FFMPEG_HW_ACCEL_INFO["available"]
|
||||
|
||||
|
||||
def is_dedicated_gpu() -> bool:
|
||||
"""
|
||||
检查是否使用独立显卡进行硬件加速
|
||||
|
||||
Returns:
|
||||
bool: 如果使用独立显卡则返回True,否则返回False
|
||||
"""
|
||||
# 如果还没有检测过,先进行检测
|
||||
if _FFMPEG_HW_ACCEL_INFO["type"] is None:
|
||||
detect_hardware_acceleration()
|
||||
|
||||
return _FFMPEG_HW_ACCEL_INFO["is_dedicated_gpu"]
|
||||
@ -19,6 +19,8 @@ from typing import List, Dict
|
||||
from loguru import logger
|
||||
from tqdm import tqdm
|
||||
|
||||
from app.utils import ffmpeg_utils
|
||||
|
||||
|
||||
class VideoProcessor:
|
||||
def __init__(self, video_path: str):
|
||||
@ -63,7 +65,7 @@ class VideoProcessor:
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
info[key] = value
|
||||
|
||||
|
||||
# 处理帧率(可能是分数形式)
|
||||
if 'r_frame_rate' in info:
|
||||
try:
|
||||
@ -71,9 +73,9 @@ class VideoProcessor:
|
||||
info['fps'] = str(num / den)
|
||||
except ValueError:
|
||||
info['fps'] = info.get('r_frame_rate', '25')
|
||||
|
||||
|
||||
return info
|
||||
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"获取视频信息失败: {e.stderr}")
|
||||
return {
|
||||
@ -83,7 +85,7 @@ class VideoProcessor:
|
||||
'duration': '0'
|
||||
}
|
||||
|
||||
def extract_frames_by_interval(self, output_dir: str, interval_seconds: float = 5.0,
|
||||
def extract_frames_by_interval(self, output_dir: str, interval_seconds: float = 5.0,
|
||||
use_hw_accel: bool = True) -> List[int]:
|
||||
"""
|
||||
按指定时间间隔提取视频帧
|
||||
@ -98,57 +100,51 @@ class VideoProcessor:
|
||||
"""
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
|
||||
# 计算起始时间和帧提取点
|
||||
start_time = 0
|
||||
end_time = self.duration
|
||||
extraction_times = []
|
||||
|
||||
|
||||
current_time = start_time
|
||||
while current_time < end_time:
|
||||
extraction_times.append(current_time)
|
||||
current_time += interval_seconds
|
||||
|
||||
|
||||
if not extraction_times:
|
||||
logger.warning("未找到需要提取的帧")
|
||||
return []
|
||||
|
||||
# 确定硬件加速器选项
|
||||
hw_accel = []
|
||||
if use_hw_accel:
|
||||
# 尝试检测可用的硬件加速器
|
||||
hw_accel_options = self._detect_hw_accelerator()
|
||||
if hw_accel_options:
|
||||
hw_accel = hw_accel_options
|
||||
logger.info(f"使用硬件加速: {' '.join(hw_accel)}")
|
||||
else:
|
||||
logger.warning("未检测到可用的硬件加速器,使用软件解码")
|
||||
|
||||
if use_hw_accel and ffmpeg_utils.is_ffmpeg_hwaccel_available():
|
||||
hw_accel = ffmpeg_utils.get_ffmpeg_hwaccel_args()
|
||||
|
||||
# 提取帧
|
||||
frame_numbers = []
|
||||
for i, timestamp in enumerate(tqdm(extraction_times, desc="提取视频帧")):
|
||||
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提取单帧
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
]
|
||||
|
||||
|
||||
# 添加硬件加速参数
|
||||
cmd.extend(hw_accel)
|
||||
|
||||
|
||||
cmd.extend([
|
||||
"-ss", str(timestamp),
|
||||
"-i", self.video_path,
|
||||
@ -157,12 +153,12 @@ class VideoProcessor:
|
||||
"-y",
|
||||
output_path
|
||||
])
|
||||
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True, capture_output=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"提取帧 {frame_number} 失败: {e.stderr}")
|
||||
|
||||
|
||||
logger.info(f"成功提取了 {len(frame_numbers)} 个视频帧")
|
||||
return frame_numbers
|
||||
|
||||
@ -173,119 +169,9 @@ class VideoProcessor:
|
||||
Returns:
|
||||
List[str]: 硬件加速器ffmpeg命令参数
|
||||
"""
|
||||
# 检测操作系统
|
||||
import platform
|
||||
system = platform.system().lower()
|
||||
|
||||
# 测试不同的硬件加速器
|
||||
accelerators = []
|
||||
|
||||
if system == 'darwin': # macOS
|
||||
# 测试 videotoolbox (Apple 硬件加速)
|
||||
test_cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-hwaccel", "videotoolbox",
|
||||
"-i", self.video_path,
|
||||
"-t", "0.1",
|
||||
"-f", "null",
|
||||
"-"
|
||||
]
|
||||
try:
|
||||
subprocess.run(test_cmd, capture_output=True, check=True)
|
||||
return ["-hwaccel", "videotoolbox"]
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
elif system == 'linux':
|
||||
# 测试 VAAPI
|
||||
test_cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-hwaccel", "vaapi",
|
||||
"-i", self.video_path,
|
||||
"-t", "0.1",
|
||||
"-f", "null",
|
||||
"-"
|
||||
]
|
||||
try:
|
||||
subprocess.run(test_cmd, capture_output=True, check=True)
|
||||
return ["-hwaccel", "vaapi"]
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
# 尝试 CUDA
|
||||
test_cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-hwaccel", "cuda",
|
||||
"-i", self.video_path,
|
||||
"-t", "0.1",
|
||||
"-f", "null",
|
||||
"-"
|
||||
]
|
||||
try:
|
||||
subprocess.run(test_cmd, capture_output=True, check=True)
|
||||
return ["-hwaccel", "cuda"]
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
elif system == 'windows':
|
||||
# 测试 CUDA
|
||||
test_cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-hwaccel", "cuda",
|
||||
"-i", self.video_path,
|
||||
"-t", "0.1",
|
||||
"-f", "null",
|
||||
"-"
|
||||
]
|
||||
try:
|
||||
subprocess.run(test_cmd, capture_output=True, check=True)
|
||||
return ["-hwaccel", "cuda"]
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
# 测试 D3D11VA
|
||||
test_cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-hwaccel", "d3d11va",
|
||||
"-i", self.video_path,
|
||||
"-t", "0.1",
|
||||
"-f", "null",
|
||||
"-"
|
||||
]
|
||||
try:
|
||||
subprocess.run(test_cmd, capture_output=True, check=True)
|
||||
return ["-hwaccel", "d3d11va"]
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
# 测试 DXVA2
|
||||
test_cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-hwaccel", "dxva2",
|
||||
"-i", self.video_path,
|
||||
"-t", "0.1",
|
||||
"-f", "null",
|
||||
"-"
|
||||
]
|
||||
try:
|
||||
subprocess.run(test_cmd, capture_output=True, check=True)
|
||||
return ["-hwaccel", "dxva2"]
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
# 如果没有找到可用的硬件加速器
|
||||
# 使用集中式硬件加速检测
|
||||
if ffmpeg_utils.is_ffmpeg_hwaccel_available():
|
||||
return ffmpeg_utils.get_ffmpeg_hwaccel_args()
|
||||
return []
|
||||
|
||||
def process_video_pipeline(self,
|
||||
@ -294,7 +180,7 @@ class VideoProcessor:
|
||||
use_hw_accel: bool = True) -> None:
|
||||
"""
|
||||
执行简化的视频处理流程,直接从原视频按固定时间间隔提取帧
|
||||
|
||||
|
||||
Args:
|
||||
output_dir: 输出目录
|
||||
interval_seconds: 帧提取间隔(秒)
|
||||
@ -302,7 +188,7 @@ class VideoProcessor:
|
||||
"""
|
||||
# 创建输出目录
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
|
||||
try:
|
||||
# 直接从原视频提取关键帧
|
||||
logger.info(f"从视频间隔 {interval_seconds} 秒提取关键帧...")
|
||||
@ -311,7 +197,7 @@ class VideoProcessor:
|
||||
interval_seconds=interval_seconds,
|
||||
use_hw_accel=use_hw_accel
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"处理完成!视频帧已保存在: {output_dir}")
|
||||
|
||||
except Exception as e:
|
||||
@ -324,16 +210,16 @@ if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
|
||||
# 使用示例
|
||||
processor = VideoProcessor("./resource/videos/test.mp4")
|
||||
|
||||
|
||||
# 设置间隔为3秒提取帧
|
||||
processor.process_video_pipeline(
|
||||
output_dir="output",
|
||||
interval_seconds=3.0,
|
||||
use_hw_accel=True
|
||||
)
|
||||
|
||||
|
||||
end_time = time.time()
|
||||
print(f"处理完成!总耗时: {end_time - start_time:.2f} 秒")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
[app]
|
||||
project_version="0.6.1"
|
||||
project_version="0.6.2"
|
||||
# 支持视频理解的大模型提供商
|
||||
# gemini (谷歌, 需要 VPN)
|
||||
# siliconflow (硅基流动)
|
||||
|
||||
22
webui.py
22
webui.py
@ -7,6 +7,7 @@ from webui.components import basic_settings, video_settings, audio_settings, sub
|
||||
review_settings, merge_settings, system_settings
|
||||
# from webui.utils import cache, file_utils
|
||||
from app.utils import utils
|
||||
from app.utils import ffmpeg_utils
|
||||
from app.models.schema import VideoClipParams, VideoAspect
|
||||
|
||||
|
||||
@ -64,7 +65,7 @@ def init_log():
|
||||
try:
|
||||
for handler_id in logger._core.handlers:
|
||||
logger.remove(handler_id)
|
||||
|
||||
|
||||
# 重新添加带有高级过滤的处理器
|
||||
def advanced_filter(record):
|
||||
"""更复杂的过滤器,在应用启动后安全使用"""
|
||||
@ -74,7 +75,7 @@ def init_log():
|
||||
"CUDA initialization"
|
||||
]
|
||||
return not any(msg in record["message"] for msg in ignore_messages)
|
||||
|
||||
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
level=_lvl,
|
||||
@ -91,7 +92,7 @@ def init_log():
|
||||
colorize=True
|
||||
)
|
||||
logger.error(f"设置高级日志过滤器失败: {e}")
|
||||
|
||||
|
||||
# 将高级过滤器设置放到启动主逻辑后
|
||||
import threading
|
||||
threading.Timer(5.0, setup_advanced_filters).start()
|
||||
@ -192,7 +193,14 @@ def main():
|
||||
"""主函数"""
|
||||
init_log()
|
||||
init_global_state()
|
||||
|
||||
|
||||
# 检测FFmpeg硬件加速
|
||||
hwaccel_info = ffmpeg_utils.detect_hardware_acceleration()
|
||||
if hwaccel_info["available"]:
|
||||
logger.info(f"FFmpeg硬件加速检测结果: 可用 | 类型: {hwaccel_info['type']} | 编码器: {hwaccel_info['encoder']} | 独立显卡: {hwaccel_info['is_dedicated_gpu']} | 参数: {hwaccel_info['hwaccel_args']}")
|
||||
else:
|
||||
logger.warning(f"FFmpeg硬件加速不可用: {hwaccel_info['message']}, 将使用CPU软件编码")
|
||||
|
||||
# 仅初始化基本资源,避免过早地加载依赖PyTorch的资源
|
||||
# 检查是否能分解utils.init_resources()为基本资源和高级资源(如依赖PyTorch的资源)
|
||||
try:
|
||||
@ -218,15 +226,15 @@ def main():
|
||||
audio_settings.render_audio_panel(tr)
|
||||
with panel[2]:
|
||||
subtitle_settings.render_subtitle_panel(tr)
|
||||
|
||||
|
||||
# 渲染视频审查面板
|
||||
review_settings.render_review_panel(tr)
|
||||
|
||||
|
||||
# 放到最后渲染可能使用PyTorch的部分
|
||||
# 渲染系统设置面板
|
||||
with panel[2]:
|
||||
system_settings.render_system_panel(tr)
|
||||
|
||||
|
||||
# 放到最后渲染生成按钮和处理逻辑
|
||||
render_generate_button()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user