diff --git a/constant/__init__.py b/constant/__init__.py index 32f4e1e..160606b 100644 --- a/constant/__init__.py +++ b/constant/__init__.py @@ -10,10 +10,9 @@ SOFTWARE_VERSION = '2.0.0' # 支持的任务类型 TASK_TYPES = ( - 'RENDER_SEGMENT_VIDEO', + 'RENDER_SEGMENT_TS', 'COMPOSE_TRANSITION', 'PREPARE_JOB_AUDIO', - 'PACKAGE_SEGMENT_TS', 'FINALIZE_MP4', ) diff --git a/domain/config.py b/domain/config.py index 9ed95ab..225d5b0 100644 --- a/domain/config.py +++ b/domain/config.py @@ -17,9 +17,8 @@ logger = logging.getLogger(__name__) # 默认支持的任务类型 DEFAULT_CAPABILITIES = [ - "RENDER_SEGMENT_VIDEO", + "RENDER_SEGMENT_TS", "PREPARE_JOB_AUDIO", - "PACKAGE_SEGMENT_TS", "FINALIZE_MP4" ] diff --git a/domain/task.py b/domain/task.py index eb55aa0..699e69e 100644 --- a/domain/task.py +++ b/domain/task.py @@ -20,12 +20,15 @@ IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'} class TaskType(Enum): """任务类型枚举""" - RENDER_SEGMENT_VIDEO = "RENDER_SEGMENT_VIDEO" # 渲染视频片段 + RENDER_SEGMENT_TS = "RENDER_SEGMENT_TS" # 渲染+封装 TS(合并原 RENDER_SEGMENT_VIDEO + PACKAGE_SEGMENT_TS) COMPOSE_TRANSITION = "COMPOSE_TRANSITION" # 合成转场效果 PREPARE_JOB_AUDIO = "PREPARE_JOB_AUDIO" # 生成全局音频 - PACKAGE_SEGMENT_TS = "PACKAGE_SEGMENT_TS" # 封装 TS 分片 FINALIZE_MP4 = "FINALIZE_MP4" # 产出最终 MP4 + # Deprecated: 历史任务类型,保留枚举值供兼容 + RENDER_SEGMENT_VIDEO = "RENDER_SEGMENT_VIDEO" + PACKAGE_SEGMENT_TS = "PACKAGE_SEGMENT_TS" + # 支持的转场类型(对应 FFmpeg xfade 参数) TRANSITION_TYPES = { diff --git a/handlers/__init__.py b/handlers/__init__.py index 13dab17..78d8488 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -6,17 +6,15 @@ """ from handlers.base import BaseHandler -from handlers.render_video import RenderSegmentVideoHandler +from handlers.render_video import RenderSegmentTsHandler from handlers.compose_transition import ComposeTransitionHandler from handlers.prepare_audio import PrepareJobAudioHandler -from handlers.package_ts import PackageSegmentTsHandler from handlers.finalize_mp4 import FinalizeMp4Handler __all__ = [ 'BaseHandler', - 'RenderSegmentVideoHandler', + 'RenderSegmentTsHandler', 'ComposeTransitionHandler', 'PrepareJobAudioHandler', - 'PackageSegmentTsHandler', 'FinalizeMp4Handler', ] diff --git a/handlers/package_ts.py b/handlers/package_ts.py deleted file mode 100644 index 51772c9..0000000 --- a/handlers/package_ts.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8 -*- -""" -TS 分片封装处理器 - -处理 PACKAGE_SEGMENT_TS 任务,将视频片段和对应时间区间的音频封装为 TS 分片。 -支持转场相关的 overlap 裁剪和转场分片封装。 -""" - -import os -import logging -from typing import List, Optional - -from handlers.base import BaseHandler, VIDEO_ENCODE_ARGS -from domain.task import Task, TaskType -from domain.result import TaskResult, ErrorCode - -logger = logging.getLogger(__name__) - - -class PackageSegmentTsHandler(BaseHandler): - """ - TS 分片封装处理器 - - 职责: - - 下载视频片段 - - 下载全局音频 - - 截取对应时间区间的音频 - - 封装为 TS 分片 - - 上传 TS 产物 - - 关键约束: - - TS 必须包含音视频同轨 - - 使用 output_ts_offset 保证时间戳连续 - - 输出 extinfDurationSec 供 m3u8 使用 - - 转场相关: - - 普通片段 TS:需要裁剪掉 overlap 区域(已被转场分片使用) - - 转场分片 TS:直接封装转场视频产物,无需裁剪 - - 无转场时:走原有逻辑,不做裁剪 - - 精确裁剪: - - 当需要裁剪 overlap 区域时,必须使用重编码方式(-vf trim)才能精确切割 - - 使用 -c copy 只能从关键帧切割,会导致不精确 - """ - - def get_supported_type(self) -> TaskType: - return TaskType.PACKAGE_SEGMENT_TS - - def handle(self, task: Task) -> TaskResult: - """处理 TS 封装任务""" - work_dir = self.create_work_dir(task.task_id) - - try: - # 解析参数 - video_url = task.get_video_url() - audio_url = task.get_audio_url() - start_time_ms = task.get_start_time_ms() - duration_ms = task.get_duration_ms() - output_spec = task.get_output_spec() - - # 转场相关参数 - is_transition_segment = task.is_transition_segment() - trim_head = task.should_trim_head() - trim_tail = task.should_trim_tail() - trim_head_ms = task.get_trim_head_ms() - trim_tail_ms = task.get_trim_tail_ms() - - if not video_url: - return TaskResult.fail( - ErrorCode.E_SPEC_INVALID, - "Missing videoUrl" - ) - - if not audio_url: - return TaskResult.fail( - ErrorCode.E_SPEC_INVALID, - "Missing audioUrl" - ) - - # 计算时间参数 - start_sec = start_time_ms / 1000.0 - duration_sec = duration_ms / 1000.0 - - # 1. 并行下载视频片段与全局音频 - video_file = os.path.join(work_dir, 'video.mp4') - audio_file = os.path.join(work_dir, 'audio.aac') - download_results = self.download_files_parallel([ - { - 'key': 'video', - 'url': video_url, - 'dest': video_file, - 'required': True - }, - { - 'key': 'audio', - 'url': audio_url, - 'dest': audio_file, - 'required': True - } - ]) - video_result = download_results.get('video') - if not video_result or not video_result['success']: - return TaskResult.fail( - ErrorCode.E_INPUT_UNAVAILABLE, - f"Failed to download video: {video_url}" - ) - audio_result = download_results.get('audio') - if not audio_result or not audio_result['success']: - return TaskResult.fail( - ErrorCode.E_INPUT_UNAVAILABLE, - f"Failed to download audio: {audio_url}" - ) - - # 2. 判断是否需要精确裁剪视频 - needs_video_trim = not is_transition_segment and ( - (trim_head and trim_head_ms > 0) or - (trim_tail and trim_tail_ms > 0) - ) - - # 3. 如果需要裁剪,先重编码裁剪视频 - processed_video_file = video_file - if needs_video_trim: - processed_video_file = os.path.join(work_dir, 'trimmed_video.mp4') - trim_cmd = self._build_trim_command( - video_file=video_file, - output_file=processed_video_file, - trim_head_ms=trim_head_ms if trim_head else 0, - trim_tail_ms=trim_tail_ms if trim_tail else 0, - output_spec=output_spec - ) - - logger.info(f"[task:{task.task_id}] Trimming video: head={trim_head_ms}ms, tail={trim_tail_ms}ms") - - if not self.run_ffmpeg(trim_cmd, task.task_id): - return TaskResult.fail( - ErrorCode.E_FFMPEG_FAILED, - "Video trim failed" - ) - - if not self.ensure_file_exists(processed_video_file, min_size=1024): - return TaskResult.fail( - ErrorCode.E_FFMPEG_FAILED, - "Trimmed video file is missing or too small" - ) - - # 4. 构建 TS 封装命令 - output_file = os.path.join(work_dir, 'segment.ts') - cmd = self._build_package_command( - video_file=processed_video_file, - audio_file=audio_file, - output_file=output_file, - start_sec=start_sec, - duration_sec=duration_sec - ) - - # 5. 执行 FFmpeg - if not self.run_ffmpeg(cmd, task.task_id): - return TaskResult.fail( - ErrorCode.E_FFMPEG_FAILED, - "TS packaging failed" - ) - - # 6. 验证输出文件 - if not self.ensure_file_exists(output_file, min_size=1024): - return TaskResult.fail( - ErrorCode.E_FFMPEG_FAILED, - "TS output file is missing or too small" - ) - - # 8. 获取实际时长(用于 EXTINF) - actual_duration = self.probe_duration(output_file) - extinf_duration = actual_duration if actual_duration else duration_sec - - # 9. 上传产物 - ts_url = self.upload_file(task.task_id, 'ts', output_file) - if not ts_url: - return TaskResult.fail( - ErrorCode.E_UPLOAD_FAILED, - "Failed to upload TS" - ) - - return TaskResult.ok({ - 'tsUrl': ts_url, - 'extinfDurationSec': extinf_duration - }) - - except Exception as e: - logger.error(f"[task:{task.task_id}] Unexpected error: {e}", exc_info=True) - return TaskResult.fail(ErrorCode.E_UNKNOWN, str(e)) - - finally: - self.cleanup_work_dir(work_dir) - - def _build_trim_command( - self, - video_file: str, - output_file: str, - trim_head_ms: int, - trim_tail_ms: int, - output_spec - ) -> List[str]: - """ - 构建视频精确裁剪命令(重编码方式) - - 使用 trim 滤镜进行精确帧级裁剪,而非 -ss/-t 参数的关键帧裁剪。 - - Args: - video_file: 输入视频路径 - output_file: 输出视频路径 - trim_head_ms: 头部裁剪时长(毫秒) - trim_tail_ms: 尾部裁剪时长(毫秒) - output_spec: 输出规格 - - Returns: - FFmpeg 命令参数列表 - """ - # 获取原视频时长 - original_duration = self.probe_duration(video_file) - if not original_duration: - original_duration = 10.0 # 默认值,避免除零 - - trim_head_sec = trim_head_ms / 1000.0 - trim_tail_sec = trim_tail_ms / 1000.0 - - # 计算裁剪后的起止时间 - start_time = trim_head_sec - end_time = original_duration - trim_tail_sec - - # 构建 trim 滤镜 - vf_filter = f"trim=start={start_time}:end={end_time},setpts=PTS-STARTPTS" - - cmd = [ - 'ffmpeg', '-y', '-hide_banner', - '-i', video_file, - '-vf', vf_filter, - ] - - # 编码参数 - cmd.extend(VIDEO_ENCODE_ARGS) - - # 帧率 - fps = output_spec.fps - cmd.extend(['-r', str(fps)]) - - # 计算输出视频帧数,动态调整 GOP - output_duration_sec = end_time - start_time - total_frames = int(output_duration_sec * fps) - - # 动态 GOP:短视频使用较小的 GOP - if total_frames <= 1: - gop_size = 1 - elif total_frames < fps: - gop_size = total_frames - else: - gop_size = fps # 每秒一个关键帧 - - cmd.extend(['-g', str(gop_size)]) - cmd.extend(['-keyint_min', str(min(gop_size, fps // 2 or 1))]) - - # 强制第一帧为关键帧 - cmd.extend(['-force_key_frames', 'expr:eq(n,0)']) - - # 无音频(音频单独处理) - cmd.append('-an') - - cmd.append(output_file) - - return cmd - - def _build_package_command( - self, - video_file: str, - audio_file: str, - output_file: str, - start_sec: float, - duration_sec: float - ) -> List[str]: - """ - 构建 TS 封装命令 - - 将视频和对应时间区间的音频封装为 TS 分片。 - 视频使用 copy 模式(已经过精确裁剪或无需裁剪)。 - - Args: - video_file: 视频文件路径(已处理) - audio_file: 音频文件路径 - output_file: 输出文件路径 - start_sec: 音频开始时间(秒) - duration_sec: 音频时长(秒) - - Returns: - FFmpeg 命令参数列表 - """ - cmd = [ - 'ffmpeg', '-y', '-hide_banner', - # 视频输入 - '-i', video_file, - # 音频输入(从 start_sec 开始截取 duration_sec) - '-ss', str(start_sec), - '-t', str(duration_sec), - '-i', audio_file, - # 映射流 - '-map', '0:v:0', # 使用第一个输入的视频流 - '-map', '1:a:0', # 使用第二个输入的音频流 - # 复制编码(视频已处理,无需重编码) - '-c:v', 'copy', - '-c:a', 'copy', - # 关键:时间戳偏移,保证整体连续 - '-output_ts_offset', str(start_sec), - # 复用参数 - '-muxdelay', '0', - '-muxpreload', '0', - # 输出格式 - '-f', 'mpegts', - output_file - ] - - return cmd diff --git a/handlers/render_video.py b/handlers/render_video.py index 0d629d8..43245e7 100644 --- a/handlers/render_video.py +++ b/handlers/render_video.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ -视频片段渲染处理器 +渲染+TS封装处理器 -处理 RENDER_SEGMENT_VIDEO 任务,将原素材渲染为符合输出规格的视频片段。 -支持转场 overlap 区域的帧冻结生成。 +处理 RENDER_SEGMENT_TS 任务,将原素材渲染为视频并封装为 TS 分片。 +支持转场 overlap 区域的帧冻结生成和精确裁剪。 """ import os @@ -11,7 +11,7 @@ import logging from typing import List, Optional, Tuple from urllib.parse import urlparse, unquote -from handlers.base import BaseHandler +from handlers.base import BaseHandler, VIDEO_ENCODE_ARGS from domain.task import Task, TaskType, RenderSpec, OutputSpec, Effect, IMAGE_EXTENSIONS from domain.result import TaskResult, ErrorCode @@ -26,21 +26,24 @@ def _get_extension_from_url(url: str) -> str: return ext.lower() if ext else '' -class RenderSegmentVideoHandler(BaseHandler): +class RenderSegmentTsHandler(BaseHandler): """ - 视频片段渲染处理器 + 渲染+TS封装处理器 职责: - 下载素材文件 - 下载 LUT 文件(如有) - 下载叠加层(如有) + - 下载音频(如有) - 构建 FFmpeg 渲染命令 - 执行渲染(支持帧冻结生成 overlap 区域) + - 裁剪 overlap 区域(如需要) + - 封装为 TS 分片 - 上传产物 """ def get_supported_type(self) -> TaskType: - return TaskType.RENDER_SEGMENT_VIDEO + return TaskType.RENDER_SEGMENT_TS def handle(self, task: Task) -> TaskResult: """处理视频渲染任务""" @@ -85,7 +88,10 @@ class RenderSegmentVideoHandler(BaseHandler): else: input_file = os.path.join(work_dir, 'input.mp4') - # 2. 构建并行下载任务(主素材 + 可选 LUT + 可选叠加层) + # 2. 构建并行下载任务(主素材 + 可选 LUT + 可选叠加层 + 可选音频) + audio_url = task.get_audio_url() + audio_file = None + lut_file = os.path.join(work_dir, 'lut.cube') if render_spec.lut_url else None overlay_file = None if render_spec.overlay_url: @@ -121,6 +127,14 @@ class RenderSegmentVideoHandler(BaseHandler): 'dest': overlay_file, 'required': False }) + if audio_url: + audio_file = os.path.join(work_dir, 'audio.aac') + download_jobs.append({ + 'key': 'audio', + 'url': audio_url, + 'dest': audio_file, + 'required': True + }) download_results = self.download_files_parallel(download_jobs) material_result = download_results.get('material') @@ -142,6 +156,14 @@ class RenderSegmentVideoHandler(BaseHandler): logger.warning(f"[task:{task.task_id}] Failed to download overlay, continuing without it") overlay_file = None + if audio_url: + audio_dl = download_results.get('audio') + if not audio_dl or not audio_dl['success']: + return TaskResult.fail( + ErrorCode.E_INPUT_UNAVAILABLE, + f"Failed to download audio: {audio_url}" + ) + # 3. 图片素材转换为视频 if is_image: video_input_file = os.path.join(work_dir, 'input_video.mp4') @@ -219,27 +241,82 @@ class RenderSegmentVideoHandler(BaseHandler): "Output file is missing or too small" ) - # 9. 获取实际时长 - actual_duration = self.probe_duration(output_file) - actual_duration_ms = int(actual_duration * 1000) if actual_duration else duration_ms + # 9. Overlap 裁剪(仅非转场分片、且有需要裁剪的 overlap 时) + is_transition_seg = task.is_transition_segment() + trim_head = task.should_trim_head() + trim_tail = task.should_trim_tail() + trim_head_ms = task.get_trim_head_ms() + trim_tail_ms = task.get_trim_tail_ms() + needs_video_trim = not is_transition_seg and ( + (trim_head and trim_head_ms > 0) or + (trim_tail and trim_tail_ms > 0) + ) - # 10. 上传产物 - video_url = self.upload_file(task.task_id, 'video', output_file) - if not video_url: - return TaskResult.fail( - ErrorCode.E_UPLOAD_FAILED, - "Failed to upload video" + processed_video = output_file + if needs_video_trim: + processed_video = os.path.join(work_dir, 'trimmed_video.mp4') + trim_cmd = self._build_trim_command( + video_file=output_file, + output_file=processed_video, + trim_head_ms=trim_head_ms if trim_head else 0, + trim_tail_ms=trim_tail_ms if trim_tail else 0, + output_spec=output_spec ) - # 11. 构建结果(包含 overlap 信息) - result_data = { - 'videoUrl': video_url, - 'actualDurationMs': actual_duration_ms, - 'overlapHeadMs': overlap_head_ms, - 'overlapTailMs': overlap_tail_ms - } + logger.info(f"[task:{task.task_id}] Trimming video: head={trim_head_ms}ms, tail={trim_tail_ms}ms") - return TaskResult.ok(result_data) + if not self.run_ffmpeg(trim_cmd, task.task_id): + return TaskResult.fail( + ErrorCode.E_FFMPEG_FAILED, + "Video trim failed" + ) + + if not self.ensure_file_exists(processed_video, min_size=1024): + return TaskResult.fail( + ErrorCode.E_FFMPEG_FAILED, + "Trimmed video file is missing or too small" + ) + + # 10. 封装 TS + start_time_ms = task.get_start_time_ms() + start_sec = start_time_ms / 1000.0 + duration_sec = duration_ms / 1000.0 + ts_output = os.path.join(work_dir, 'segment.ts') + ts_cmd = self._build_ts_package_command( + video_file=processed_video, + audio_file=audio_file, + output_file=ts_output, + start_sec=start_sec, + duration_sec=duration_sec + ) + + if not self.run_ffmpeg(ts_cmd, task.task_id): + return TaskResult.fail( + ErrorCode.E_FFMPEG_FAILED, + "TS packaging failed" + ) + + if not self.ensure_file_exists(ts_output, min_size=1024): + return TaskResult.fail( + ErrorCode.E_FFMPEG_FAILED, + "TS output file is missing or too small" + ) + + # 11. 获取 EXTINF 时长 + 上传 TS + actual_duration = self.probe_duration(ts_output) + extinf_duration = actual_duration if actual_duration else duration_sec + + ts_url = self.upload_file(task.task_id, 'ts', ts_output) + if not ts_url: + return TaskResult.fail( + ErrorCode.E_UPLOAD_FAILED, + "Failed to upload TS" + ) + + return TaskResult.ok({ + 'tsUrl': ts_url, + 'extinfDurationSec': extinf_duration + }) except Exception as e: logger.error(f"[task:{task.task_id}] Unexpected error: {e}", exc_info=True) @@ -350,6 +427,116 @@ class RenderSegmentVideoHandler(BaseHandler): logger.info(f"[task:{task_id}] Converting image to video: {actual_duration_sec:.2f}s at {fps}fps") return self.run_ffmpeg(cmd, task_id) + def _build_trim_command( + self, + video_file: str, + output_file: str, + trim_head_ms: int, + trim_tail_ms: int, + output_spec + ) -> List[str]: + """ + 构建视频精确裁剪命令(重编码方式) + + 使用 trim 滤镜进行精确帧级裁剪,而非 -ss/-t 参数的关键帧裁剪。 + + Args: + video_file: 输入视频路径 + output_file: 输出视频路径 + trim_head_ms: 头部裁剪时长(毫秒) + trim_tail_ms: 尾部裁剪时长(毫秒) + output_spec: 输出规格 + + Returns: + FFmpeg 命令参数列表 + """ + original_duration = self.probe_duration(video_file) + if not original_duration: + original_duration = 10.0 + + trim_head_sec = trim_head_ms / 1000.0 + trim_tail_sec = trim_tail_ms / 1000.0 + + start_time = trim_head_sec + end_time = original_duration - trim_tail_sec + + vf_filter = f"trim=start={start_time}:end={end_time},setpts=PTS-STARTPTS" + + cmd = [ + 'ffmpeg', '-y', '-hide_banner', + '-i', video_file, + '-vf', vf_filter, + ] + + cmd.extend(VIDEO_ENCODE_ARGS) + + fps = output_spec.fps + cmd.extend(['-r', str(fps)]) + + output_duration_sec = end_time - start_time + total_frames = int(output_duration_sec * fps) + + if total_frames <= 1: + gop_size = 1 + elif total_frames < fps: + gop_size = total_frames + else: + gop_size = fps + + cmd.extend(['-g', str(gop_size)]) + cmd.extend(['-keyint_min', str(min(gop_size, fps // 2 or 1))]) + cmd.extend(['-force_key_frames', 'expr:eq(n,0)']) + cmd.append('-an') + cmd.append(output_file) + + return cmd + + def _build_ts_package_command( + self, + video_file: str, + audio_file: Optional[str], + output_file: str, + start_sec: float, + duration_sec: float + ) -> List[str]: + """ + 构建 TS 封装命令 + + 将视频和对应时间区间的音频封装为 TS 分片。 + 视频使用 copy 模式(已经过渲染/裁剪)。 + 支持无音频模式(video-only TS)。 + + Args: + video_file: 视频文件路径(已处理) + audio_file: 音频文件路径(可选,None 时生成 video-only TS) + output_file: 输出文件路径 + start_sec: 音频开始时间(秒) + duration_sec: 音频时长(秒) + + Returns: + FFmpeg 命令参数列表 + """ + cmd = [ + 'ffmpeg', '-y', '-hide_banner', + '-i', video_file, + ] + + if audio_file: + cmd.extend(['-ss', str(start_sec), '-t', str(duration_sec), '-i', audio_file]) + cmd.extend(['-map', '0:v:0', '-map', '1:a:0', '-c:v', 'copy', '-c:a', 'copy']) + else: + cmd.extend(['-c:v', 'copy']) + + cmd.extend([ + '-output_ts_offset', str(start_sec), + '-muxdelay', '0', + '-muxpreload', '0', + '-f', 'mpegts', + output_file + ]) + + return cmd + def _build_command( self, input_file: str, diff --git a/index.py b/index.py index f8c46a5..7bcaaf6 100644 --- a/index.py +++ b/index.py @@ -4,9 +4,8 @@ RenderWorker v2 入口 支持 v2 API 协议的渲染 Worker,处理以下任务类型: -- RENDER_SEGMENT_VIDEO: 渲染视频片段 +- RENDER_SEGMENT_TS: 渲染视频片段并封装为 TS - PREPARE_JOB_AUDIO: 生成全局音频 -- PACKAGE_SEGMENT_TS: 封装 TS 分片 - FINALIZE_MP4: 产出最终 MP4 使用方法: diff --git a/services/task_executor.py b/services/task_executor.py index d4255c9..0b16406 100644 --- a/services/task_executor.py +++ b/services/task_executor.py @@ -14,7 +14,7 @@ from domain.task import Task, TaskType # 需要 GPU 加速的任务类型 GPU_REQUIRED_TASK_TYPES = { - TaskType.RENDER_SEGMENT_VIDEO, + TaskType.RENDER_SEGMENT_TS, TaskType.COMPOSE_TRANSITION, } from domain.config import WorkerConfig @@ -85,17 +85,15 @@ class TaskExecutor: def _register_handlers(self): """注册所有任务处理器""" # 延迟导入以避免循环依赖 - from handlers.render_video import RenderSegmentVideoHandler + from handlers.render_video import RenderSegmentTsHandler from handlers.compose_transition import ComposeTransitionHandler from handlers.prepare_audio import PrepareJobAudioHandler - from handlers.package_ts import PackageSegmentTsHandler from handlers.finalize_mp4 import FinalizeMp4Handler handlers = [ - RenderSegmentVideoHandler(self.config, self.api_client), + RenderSegmentTsHandler(self.config, self.api_client), ComposeTransitionHandler(self.config, self.api_client), PrepareJobAudioHandler(self.config, self.api_client), - PackageSegmentTsHandler(self.config, self.api_client), FinalizeMp4Handler(self.config, self.api_client), ] diff --git a/tests/unit/test_base_handler_parallel_transfer.py b/tests/unit/test_base_handler_parallel_transfer.py index 3031bf0..15440aa 100644 --- a/tests/unit/test_base_handler_parallel_transfer.py +++ b/tests/unit/test_base_handler_parallel_transfer.py @@ -21,7 +21,7 @@ class _DummyHandler(BaseHandler): return TaskResult.ok({}) def get_supported_type(self): - return TaskType.RENDER_SEGMENT_VIDEO + return TaskType.RENDER_SEGMENT_TS def _create_handler(tmp_path): diff --git a/tests/unit/test_render_video_effects.py b/tests/unit/test_render_video_effects.py index 747becf..0a2aa1d 100644 --- a/tests/unit/test_render_video_effects.py +++ b/tests/unit/test_render_video_effects.py @@ -4,7 +4,7 @@ import pytest from domain.config import WorkerConfig from domain.task import Effect, OutputSpec, RenderSpec -from handlers.render_video import RenderSegmentVideoHandler +from handlers.render_video import RenderSegmentTsHandler class _DummyApiClient: @@ -20,7 +20,7 @@ def _create_handler(tmp_path): cache_enabled=False, cache_dir=str(tmp_path / 'cache') ) - return RenderSegmentVideoHandler(config, _DummyApiClient()) + return RenderSegmentTsHandler(config, _DummyApiClient()) def test_get_zoom_params_with_valid_values(): diff --git a/tests/unit/test_tracing.py b/tests/unit/test_tracing.py index b981447..7ea2946 100644 --- a/tests/unit/test_tracing.py +++ b/tests/unit/test_tracing.py @@ -7,7 +7,7 @@ import util.tracing as tracing_module def _create_task_stub(): - task_type = SimpleNamespace(value="RENDER_SEGMENT_VIDEO") + task_type = SimpleNamespace(value="RENDER_SEGMENT_TS") return SimpleNamespace( task_id="task-1001", task_type=task_type, @@ -28,7 +28,7 @@ def test_task_trace_scope_sets_and_resets_context(monkeypatch): context = tracing.get_current_task_context() assert context is not None assert context.task_id == "task-1001" - assert context.task_type == "RENDER_SEGMENT_VIDEO" + assert context.task_type == "RENDER_SEGMENT_TS" assert context.job_id == "job-2002" assert context.segment_id == "seg-3003"