You've already forked FrameTour-RenderWorker
mypy
This commit is contained in:
@@ -9,67 +9,76 @@ from opentelemetry.trace import Status, StatusCode
|
||||
from entity.render_task import RenderTask
|
||||
from entity.ffmpeg_command_builder import FFmpegCommandBuilder
|
||||
from util.exceptions import RenderError, FFmpegError
|
||||
from util.ffmpeg import probe_video_info, fade_out_audio, handle_ffmpeg_output, subprocess_args
|
||||
from util.ffmpeg import (
|
||||
probe_video_info,
|
||||
fade_out_audio,
|
||||
handle_ffmpeg_output,
|
||||
subprocess_args,
|
||||
)
|
||||
from telemetry import get_tracer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 向后兼容层 - 处理旧的FfmpegTask对象
|
||||
|
||||
|
||||
class RenderService(ABC):
|
||||
"""渲染服务抽象接口"""
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def render(self, task: Union[RenderTask, 'FfmpegTask']) -> bool:
|
||||
def render(self, task: Union[RenderTask, "FfmpegTask"]) -> bool:
|
||||
"""
|
||||
执行渲染任务
|
||||
|
||||
|
||||
Args:
|
||||
task: 渲染任务
|
||||
|
||||
|
||||
Returns:
|
||||
bool: 渲染是否成功
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def get_video_info(self, file_path: str) -> tuple[int, int, float]:
|
||||
"""
|
||||
获取视频信息
|
||||
|
||||
|
||||
Args:
|
||||
file_path: 视频文件路径
|
||||
|
||||
|
||||
Returns:
|
||||
tuple: (width, height, duration)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def fade_out_audio(self, file_path: str, duration: float, fade_seconds: float = 2.0) -> str:
|
||||
def fade_out_audio(
|
||||
self, file_path: str, duration: float, fade_seconds: float = 2.0
|
||||
) -> str:
|
||||
"""
|
||||
音频淡出处理
|
||||
|
||||
|
||||
Args:
|
||||
file_path: 音频文件路径
|
||||
duration: 音频总时长
|
||||
fade_seconds: 淡出时长
|
||||
|
||||
|
||||
Returns:
|
||||
str: 处理后的文件路径
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DefaultRenderService(RenderService):
|
||||
"""默认渲染服务实现"""
|
||||
|
||||
def render(self, task: Union[RenderTask, 'FfmpegTask']) -> bool:
|
||||
|
||||
def render(self, task: Union[RenderTask, "FfmpegTask"]) -> bool:
|
||||
"""执行渲染任务"""
|
||||
# 兼容旧的FfmpegTask
|
||||
if hasattr(task, 'get_ffmpeg_args'): # 这是FfmpegTask
|
||||
if hasattr(task, "get_ffmpeg_args"): # 这是FfmpegTask
|
||||
# 使用旧的方式执行
|
||||
return self._render_legacy_ffmpeg_task(task)
|
||||
|
||||
|
||||
tracer = get_tracer(__name__)
|
||||
with tracer.start_as_current_span("render_task") as span:
|
||||
try:
|
||||
@@ -78,98 +87,104 @@ class DefaultRenderService(RenderService):
|
||||
span.set_attribute("task.type", task.task_type.value)
|
||||
span.set_attribute("task.input_files", len(task.input_files))
|
||||
span.set_attribute("task.output_file", task.output_file)
|
||||
|
||||
|
||||
# 检查是否需要处理
|
||||
if not task.need_processing():
|
||||
if len(task.input_files) == 1:
|
||||
task.output_file = task.input_files[0]
|
||||
span.set_status(Status(StatusCode.OK))
|
||||
return True
|
||||
|
||||
|
||||
# 构建FFmpeg命令
|
||||
builder = FFmpegCommandBuilder(task)
|
||||
ffmpeg_args = builder.build_command()
|
||||
|
||||
|
||||
if not ffmpeg_args:
|
||||
# 不需要处理,直接返回
|
||||
if len(task.input_files) == 1:
|
||||
task.output_file = task.input_files[0]
|
||||
span.set_status(Status(StatusCode.OK))
|
||||
return True
|
||||
|
||||
|
||||
# 执行FFmpeg命令
|
||||
return self._execute_ffmpeg(ffmpeg_args, span)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
logger.error(f"Render failed: {e}", exc_info=True)
|
||||
raise RenderError(f"Render failed: {e}") from e
|
||||
|
||||
|
||||
def _execute_ffmpeg(self, args: list[str], span) -> bool:
|
||||
"""执行FFmpeg命令"""
|
||||
span.set_attribute("ffmpeg.args", " ".join(args))
|
||||
logger.info("Executing FFmpeg: %s", " ".join(args))
|
||||
|
||||
|
||||
try:
|
||||
# 执行FFmpeg进程 (使用构建器已经包含的参数)
|
||||
process = subprocess.run(
|
||||
args,
|
||||
stderr=subprocess.PIPE,
|
||||
**subprocess_args(True)
|
||||
args, stderr=subprocess.PIPE, **subprocess_args(True)
|
||||
)
|
||||
|
||||
|
||||
span.set_attribute("ffmpeg.return_code", process.returncode)
|
||||
|
||||
|
||||
# 处理输出
|
||||
if process.stdout:
|
||||
output = handle_ffmpeg_output(process.stdout)
|
||||
span.set_attribute("ffmpeg.output", output)
|
||||
logger.info("FFmpeg output: %s", output)
|
||||
|
||||
|
||||
# 检查返回码
|
||||
if process.returncode != 0:
|
||||
error_msg = process.stderr.decode() if process.stderr else "Unknown error"
|
||||
error_msg = (
|
||||
process.stderr.decode() if process.stderr else "Unknown error"
|
||||
)
|
||||
span.set_attribute("ffmpeg.error", error_msg)
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
logger.error("FFmpeg failed with return code %d: %s", process.returncode, error_msg)
|
||||
logger.error(
|
||||
"FFmpeg failed with return code %d: %s",
|
||||
process.returncode,
|
||||
error_msg,
|
||||
)
|
||||
raise FFmpegError(
|
||||
f"FFmpeg execution failed",
|
||||
command=args,
|
||||
return_code=process.returncode,
|
||||
stderr=error_msg
|
||||
stderr=error_msg,
|
||||
)
|
||||
|
||||
|
||||
# 检查输出文件
|
||||
output_file = args[-1] # 输出文件总是最后一个参数
|
||||
if not os.path.exists(output_file):
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
raise RenderError(f"Output file not created: {output_file}")
|
||||
|
||||
|
||||
# 检查文件大小
|
||||
file_size = os.path.getsize(output_file)
|
||||
span.set_attribute("output.file_size", file_size)
|
||||
|
||||
|
||||
if file_size < 4096: # 文件过小
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
raise RenderError(f"Output file too small: {file_size} bytes")
|
||||
|
||||
|
||||
span.set_status(Status(StatusCode.OK))
|
||||
logger.info("FFmpeg execution completed successfully")
|
||||
return True
|
||||
|
||||
|
||||
except subprocess.SubprocessError as e:
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
logger.error("Subprocess error: %s", e)
|
||||
raise FFmpegError(f"Subprocess error: {e}") from e
|
||||
|
||||
|
||||
def get_video_info(self, file_path: str) -> tuple[int, int, float]:
|
||||
"""获取视频信息"""
|
||||
return probe_video_info(file_path)
|
||||
|
||||
def fade_out_audio(self, file_path: str, duration: float, fade_seconds: float = 2.0) -> str:
|
||||
|
||||
def fade_out_audio(
|
||||
self, file_path: str, duration: float, fade_seconds: float = 2.0
|
||||
) -> str:
|
||||
"""音频淡出处理"""
|
||||
return fade_out_audio(file_path, duration, fade_seconds)
|
||||
|
||||
|
||||
def _render_legacy_ffmpeg_task(self, ffmpeg_task) -> bool:
|
||||
"""兼容处理旧的FfmpegTask"""
|
||||
tracer = get_tracer(__name__)
|
||||
@@ -180,19 +195,19 @@ class DefaultRenderService(RenderService):
|
||||
if not self.render(sub_task):
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
return False
|
||||
|
||||
|
||||
# 获取FFmpeg参数
|
||||
ffmpeg_args = ffmpeg_task.get_ffmpeg_args()
|
||||
|
||||
|
||||
if not ffmpeg_args:
|
||||
# 不需要处理,直接返回
|
||||
span.set_status(Status(StatusCode.OK))
|
||||
return True
|
||||
|
||||
|
||||
# 执行FFmpeg命令
|
||||
return self._execute_ffmpeg(ffmpeg_args, span)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
span.set_status(Status(StatusCode.ERROR))
|
||||
logger.error(f"Legacy FFmpeg task render failed: {e}", exc_info=True)
|
||||
raise RenderError(f"Legacy render failed: {e}") from e
|
||||
raise RenderError(f"Legacy render failed: {e}") from e
|
||||
|
||||
Reference in New Issue
Block a user