You've already forked FrameTour-RenderWorker
- 添加v2支持的任务类型常量定义 - 更新软件版本至0.0.9 - 定义v2统一音视频编码参数 - 实现系统信息工具get_sys_info_v2方法 - 新增get_capabilities和_get_gpu_info功能 - 创建core模块及TaskHandler抽象基类 - 添加渲染系统设计文档包括集群架构、v2 PRD和Worker PRD - 实现任务处理器抽象基类及接口规范
176 lines
5.4 KiB
Python
176 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
TS 分片封装处理器
|
|
|
|
处理 PACKAGE_SEGMENT_TS 任务,将视频片段和对应时间区间的音频封装为 TS 分片。
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
from typing import List
|
|
|
|
from handlers.base import BaseHandler
|
|
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 使用
|
|
"""
|
|
|
|
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()
|
|
|
|
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')
|
|
if not self.download_file(video_url, video_file):
|
|
return TaskResult.fail(
|
|
ErrorCode.E_INPUT_UNAVAILABLE,
|
|
f"Failed to download video: {video_url}"
|
|
)
|
|
|
|
# 2. 下载全局音频
|
|
audio_file = os.path.join(work_dir, 'audio.aac')
|
|
if not self.download_file(audio_url, audio_file):
|
|
return TaskResult.fail(
|
|
ErrorCode.E_INPUT_UNAVAILABLE,
|
|
f"Failed to download audio: {audio_url}"
|
|
)
|
|
|
|
# 3. 构建 TS 封装命令
|
|
output_file = os.path.join(work_dir, 'segment.ts')
|
|
cmd = self._build_command(
|
|
video_file=video_file,
|
|
audio_file=audio_file,
|
|
output_file=output_file,
|
|
start_sec=start_sec,
|
|
duration_sec=duration_sec
|
|
)
|
|
|
|
# 4. 执行 FFmpeg
|
|
if not self.run_ffmpeg(cmd, task.task_id):
|
|
return TaskResult.fail(
|
|
ErrorCode.E_FFMPEG_FAILED,
|
|
"TS packaging failed"
|
|
)
|
|
|
|
# 5. 验证输出文件
|
|
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"
|
|
)
|
|
|
|
# 6. 获取实际时长(用于 EXTINF)
|
|
actual_duration = self.probe_duration(output_file)
|
|
extinf_duration = actual_duration if actual_duration else duration_sec
|
|
|
|
# 7. 上传产物
|
|
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_command(
|
|
self,
|
|
video_file: str,
|
|
audio_file: str,
|
|
output_file: str,
|
|
start_sec: float,
|
|
duration_sec: float
|
|
) -> List[str]:
|
|
"""
|
|
构建 TS 封装命令
|
|
|
|
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
|