You've already forked FrameTour-RenderWorker
refactor(core): 移除旧版 FFmpeg 业务逻辑并重构常量配置
- 删除 biz/ffmpeg.py 和 biz/task.py 旧版业务模块 - 删除 entity/ffmpeg.py FFmpeg 任务实体类 - 删除 config/__init__.py 旧版配置初始化 - 更新 constant/__init__.py 常量定义,从 v1/v2 版本改为统一版本 - 修改 handlers/base.py 基础处理器,替换 OSS 相关导入为存储服务 - 添加 subprocess_args 工具函数支持跨平台进程参数配置 - 新增 probe_video_info 函数用于视频信息探测 - 新增 probe_duration_json 函数用于媒体时长探测
This commit is contained in:
128
handlers/base.py
128
handlers/base.py
@@ -6,19 +6,19 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import shutil
|
||||
import tempfile
|
||||
import subprocess
|
||||
from abc import ABC
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
from typing import Optional, List, Dict, Any, Tuple, TYPE_CHECKING
|
||||
|
||||
from core.handler import TaskHandler
|
||||
from domain.task import Task
|
||||
from domain.result import TaskResult, ErrorCode
|
||||
from domain.config import WorkerConfig
|
||||
from util import oss
|
||||
from util.ffmpeg import subprocess_args
|
||||
from services import storage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from services.api_client import APIClientV2
|
||||
@@ -45,6 +45,117 @@ AUDIO_ENCODE_ARGS = [
|
||||
]
|
||||
|
||||
|
||||
def subprocess_args(include_stdout: bool = True) -> Dict[str, Any]:
|
||||
"""
|
||||
创建跨平台的 subprocess 参数
|
||||
|
||||
在 Windows 上使用 Pyinstaller --noconsole 打包时,需要特殊处理以避免弹出命令行窗口。
|
||||
|
||||
Args:
|
||||
include_stdout: 是否包含 stdout 捕获
|
||||
|
||||
Returns:
|
||||
subprocess.run 使用的参数字典
|
||||
"""
|
||||
ret: Dict[str, Any] = {}
|
||||
|
||||
# Windows 特殊处理
|
||||
if hasattr(subprocess, 'STARTUPINFO'):
|
||||
si = subprocess.STARTUPINFO()
|
||||
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
ret['startupinfo'] = si
|
||||
ret['env'] = os.environ
|
||||
|
||||
# 重定向 stdin 避免 "handle is invalid" 错误
|
||||
ret['stdin'] = subprocess.PIPE
|
||||
|
||||
if include_stdout:
|
||||
ret['stdout'] = subprocess.PIPE
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def probe_video_info(video_file: str) -> Tuple[int, int, float]:
|
||||
"""
|
||||
探测视频信息(宽度、高度、时长)
|
||||
|
||||
Args:
|
||||
video_file: 视频文件路径
|
||||
|
||||
Returns:
|
||||
(width, height, duration) 元组,失败返回 (0, 0, 0)
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
'ffprobe', '-v', 'error',
|
||||
'-select_streams', 'v:0',
|
||||
'-show_entries', 'stream=width,height:format=duration',
|
||||
'-of', 'csv=s=x:p=0',
|
||||
video_file
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=30,
|
||||
**subprocess_args(False)
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"ffprobe failed for {video_file}")
|
||||
return 0, 0, 0
|
||||
|
||||
output = result.stdout.decode('utf-8').strip()
|
||||
if not output:
|
||||
return 0, 0, 0
|
||||
|
||||
lines = output.split('\n')
|
||||
if len(lines) >= 2:
|
||||
wh = lines[0].strip()
|
||||
duration_str = lines[1].strip()
|
||||
width, height = wh.split('x')
|
||||
return int(width), int(height), float(duration_str)
|
||||
|
||||
return 0, 0, 0
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"probe_video_info error: {e}")
|
||||
return 0, 0, 0
|
||||
|
||||
|
||||
def probe_duration_json(file_path: str) -> Optional[float]:
|
||||
"""
|
||||
使用 ffprobe JSON 输出探测媒体时长
|
||||
|
||||
Args:
|
||||
file_path: 媒体文件路径
|
||||
|
||||
Returns:
|
||||
时长(秒),失败返回 None
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
'ffprobe', '-v', 'error',
|
||||
'-show_entries', 'format=duration',
|
||||
'-of', 'json',
|
||||
file_path
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=30,
|
||||
**subprocess_args(False)
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
|
||||
data = json.loads(result.stdout.decode('utf-8'))
|
||||
duration = data.get('format', {}).get('duration')
|
||||
return float(duration) if duration else None
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"probe_duration_json error: {e}")
|
||||
return None
|
||||
|
||||
|
||||
class BaseHandler(TaskHandler, ABC):
|
||||
"""
|
||||
任务处理器基类
|
||||
@@ -128,7 +239,7 @@ class BaseHandler(TaskHandler, ABC):
|
||||
timeout = self.config.download_timeout
|
||||
|
||||
try:
|
||||
result = oss.download_from_oss(url, dest)
|
||||
result = storage.download_file(url, dest, timeout=timeout)
|
||||
if result:
|
||||
file_size = os.path.getsize(dest) if os.path.exists(dest) else 0
|
||||
logger.debug(f"Downloaded: {url} -> {dest} ({file_size} bytes)")
|
||||
@@ -171,7 +282,7 @@ class BaseHandler(TaskHandler, ABC):
|
||||
|
||||
# 上传文件
|
||||
try:
|
||||
result = oss.upload_to_oss(upload_url, file_path)
|
||||
result = storage.upload_file(upload_url, file_path, timeout=self.config.upload_timeout)
|
||||
if result:
|
||||
file_size = os.path.getsize(file_path)
|
||||
logger.info(f"[task:{task_id}] Uploaded: {file_path} ({file_size} bytes)")
|
||||
@@ -241,8 +352,13 @@ class BaseHandler(TaskHandler, ABC):
|
||||
Returns:
|
||||
时长(秒),失败返回 None
|
||||
"""
|
||||
# 首先尝试 JSON 输出方式
|
||||
duration = probe_duration_json(file_path)
|
||||
if duration is not None:
|
||||
return duration
|
||||
|
||||
# 回退到旧方式
|
||||
try:
|
||||
from util.ffmpeg import probe_video_info
|
||||
_, _, duration = probe_video_info(file_path)
|
||||
return float(duration) if duration else None
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user