From 34e7d84d52570152e25d4c956080aba0a0d5c741 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 27 Feb 2026 13:37:42 +0800 Subject: [PATCH] =?UTF-8?q?refactor(video):=20=E9=87=8D=E6=9E=84=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E8=A3=81=E5=88=87=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 crop_size 字段替换为 crop_scale 浮点数字段,支持缩放倍率控制 - 将 face_pos 字段重命名为 crop_pos,统一裁切位置控制 - 移除 zoom_cut 和 crop_size 字段,简化裁切参数 - 新增 _build_crop_filter 静态方法,统一构建裁切滤镜逻辑 - 优化裁切算法,支持按目标比例和倍率进行精确裁切 - 统一处理图像和视频的裁切逻辑,消除代码重复 - 添加 cropScale 参数的安全解析,防止非法数值导致错误 - 改进裁切位置解析,支持浮点数坐标并添加异常处理 --- domain/task.py | 19 +++++++--- handlers/render_video.py | 81 ++++++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/domain/task.py b/domain/task.py index 699e69e..1bdc72a 100644 --- a/domain/task.py +++ b/domain/task.py @@ -216,14 +216,13 @@ class RenderSpec: 用于 RENDER_SEGMENT_VIDEO 任务,定义视频渲染参数。 """ crop_enable: bool = False - crop_size: Optional[str] = None + crop_scale: float = 1.0 speed: str = "1.0" lut_url: Optional[str] = None overlay_url: Optional[str] = None effects: Optional[str] = None - zoom_cut: bool = False video_crop: Optional[str] = None - face_pos: Optional[str] = None + crop_pos: Optional[str] = None transitions: Optional[str] = None # 转场配置(PRD v2 新增) transition_in: Optional[TransitionConfig] = None # 入场转场 @@ -234,16 +233,24 @@ class RenderSpec: """从字典创建 RenderSpec""" if not data: return cls() + + # 安全解析 cropScale:接受浮点数或字符串浮点数,非法值回退到 1.0 + try: + crop_scale = float(data.get('cropScale', 1.0)) + if crop_scale <= 0 or not isfinite(crop_scale): + crop_scale = 1.0 + except (ValueError, TypeError): + crop_scale = 1.0 + return cls( crop_enable=data.get('cropEnable', False), - crop_size=data.get('cropSize'), + crop_scale=crop_scale, speed=str(data.get('speed', '1.0')), lut_url=data.get('lutUrl'), overlay_url=data.get('overlayUrl'), effects=data.get('effects'), - zoom_cut=data.get('zoomCut', False), video_crop=data.get('videoCrop'), - face_pos=data.get('facePos'), + crop_pos=data.get('cropPos'), transitions=data.get('transitions'), transition_in=TransitionConfig.from_dict(data.get('transitionIn')), transition_out=TransitionConfig.from_dict(data.get('transitionOut')) diff --git a/handlers/render_video.py b/handlers/render_video.py index 43245e7..c7e8087 100644 --- a/handlers/render_video.py +++ b/handlers/render_video.py @@ -325,6 +325,43 @@ class RenderSegmentTsHandler(BaseHandler): finally: self.cleanup_work_dir(work_dir) + @staticmethod + def _build_crop_filter( + render_spec: 'RenderSpec', + width: int, + height: int, + task_id: str = '' + ) -> Optional[str]: + """ + 构建裁切滤镜 + + crop_enable 时:以目标比例为基准,按 crop_scale 倍率裁切,crop_pos 控制位置(默认居中)。 + + Returns: + crop 滤镜字符串,无需裁切时返回 None + """ + if render_spec.crop_enable: + scale = render_spec.crop_scale + target_ratio = width / height + + # 解析裁切位置,默认居中 + fx, fy = 0.5, 0.5 + if render_spec.crop_pos: + try: + fx, fy = map(float, render_spec.crop_pos.split(',')) + except ValueError: + logger.warning(f"[task:{task_id}] Invalid crop position: {render_spec.crop_pos}, using center") + fx, fy = 0.5, 0.5 + + # 基准:源中最大的目标比例矩形,再除以倍率 + return ( + f"crop='min(iw,ih*{target_ratio})/{scale}':'min(ih,iw/{target_ratio})/{scale}':" + f"'(iw-min(iw,ih*{target_ratio})/{scale})*{fx}':" + f"'(ih-min(ih,iw/{target_ratio})/{scale})*{fy}'" + ) + + return None + def _convert_image_to_video( self, image_file: str, @@ -373,23 +410,10 @@ class RenderSegmentTsHandler(BaseHandler): # 构建滤镜:缩放填充到目标尺寸 filters = [] - # 裁切处理(与视频相同逻辑) - if render_spec.crop_enable and render_spec.face_pos: - try: - fx, fy = map(float, render_spec.face_pos.split(',')) - target_ratio = width / height - filters.append( - f"crop='min(iw,ih*{target_ratio})':'min(ih,iw/{target_ratio})':" - f"'(iw-min(iw,ih*{target_ratio}))*{fx}':" - f"'(ih-min(ih,iw/{target_ratio}))*{fy}'" - ) - except (ValueError, ZeroDivisionError): - logger.warning(f"[task:{task_id}] Invalid face position: {render_spec.face_pos}") - elif render_spec.zoom_cut: - target_ratio = width / height - filters.append( - f"crop='min(iw,ih*{target_ratio})':'min(ih,iw/{target_ratio})'" - ) + # 裁切处理 + crop_filter = self._build_crop_filter(render_spec, width, height, task_id) + if crop_filter: + filters.append(crop_filter) # 缩放填充 filters.append( @@ -711,26 +735,9 @@ class RenderSegmentTsHandler(BaseHandler): filters.append(f"lut3d='{lut_path}'") # 3. 裁切处理 - if render_spec.crop_enable and render_spec.face_pos: - # 根据人脸位置进行智能裁切 - try: - fx, fy = map(float, render_spec.face_pos.split(',')) - # 计算裁切区域(保持输出比例) - target_ratio = width / height - # 假设裁切到目标比例 - filters.append( - f"crop='min(iw,ih*{target_ratio})':'min(ih,iw/{target_ratio})':" - f"'(iw-min(iw,ih*{target_ratio}))*{fx}':" - f"'(ih-min(ih,iw/{target_ratio}))*{fy}'" - ) - except (ValueError, ZeroDivisionError): - logger.warning(f"Invalid face position: {render_spec.face_pos}") - elif render_spec.zoom_cut: - # 中心缩放裁切 - target_ratio = width / height - filters.append( - f"crop='min(iw,ih*{target_ratio})':'min(ih,iw/{target_ratio})'" - ) + crop_filter = self._build_crop_filter(render_spec, width, height) + if crop_filter: + filters.append(crop_filter) # 4. 缩放和填充 scale_filter = (