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:
2026-01-12 17:00:48 +08:00
parent 24de32e6bb
commit 2911a4eff8
21 changed files with 570 additions and 1958 deletions

View File

@@ -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: