You've already forked FrameTour-RenderWorker
feat(video): 添加视频缩放特效功能支持
- 在 EffectConfig 中新增 zoom 特效类型及参数解析 - 实现 get_zoom_params 方法用于获取缩放效果参数 - 更新文档注释说明 zoom 特效使用格式示例 - 修改渲染逻辑支持 zoom 特效的 filter_complex 处理 - 添加缩放特效的视频滤镜构建实现 - 统一处理 cameraShot 和 zoom 特效的效果叠加逻辑
This commit is contained in:
@@ -5,12 +5,13 @@
|
||||
定义任务类型、任务实体、渲染规格、输出规格等数据结构。
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlparse, unquote
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from math import isfinite
|
||||
from typing import Dict, Any, Optional, List
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
|
||||
# 支持的图片扩展名
|
||||
@@ -95,7 +96,9 @@ class Effect:
|
||||
特效配置
|
||||
|
||||
格式:type:params
|
||||
例如:cameraShot:3,1 表示在第3秒定格1秒
|
||||
例如:
|
||||
- cameraShot:3,1 表示在第3秒定格1秒
|
||||
- zoom:1.5,1.2,2 表示从第1.5秒开始放大 1.2 倍并持续 2 秒
|
||||
"""
|
||||
effect_type: str # 效果类型
|
||||
params: str = "" # 参数字符串
|
||||
@@ -122,7 +125,7 @@ class Effect:
|
||||
解析效果字符串
|
||||
|
||||
格式:effect1|effect2|effect3
|
||||
例如:cameraShot:3,1|blur:5
|
||||
例如:cameraShot:3,1|zoom:1.5,1.2,2
|
||||
"""
|
||||
if not effects_str:
|
||||
return []
|
||||
@@ -152,6 +155,40 @@ class Effect:
|
||||
except ValueError:
|
||||
return (3, 1)
|
||||
|
||||
def get_zoom_params(self) -> tuple:
|
||||
"""
|
||||
获取 zoom 效果参数
|
||||
|
||||
Returns:
|
||||
(start_sec, scale_factor, duration_sec): 起始时间、放大倍数、持续时长(秒)
|
||||
"""
|
||||
if self.effect_type != 'zoom':
|
||||
return (0.0, 1.2, 1.0)
|
||||
|
||||
default_start_sec = 0.0
|
||||
default_scale_factor = 1.2
|
||||
default_duration_sec = 1.0
|
||||
|
||||
if not self.params:
|
||||
return (default_start_sec, default_scale_factor, default_duration_sec)
|
||||
|
||||
parts = [part.strip() for part in self.params.split(',')]
|
||||
try:
|
||||
start_sec = float(parts[0]) if len(parts) >= 1 and parts[0] else default_start_sec
|
||||
scale_factor = float(parts[1]) if len(parts) >= 2 and parts[1] else default_scale_factor
|
||||
duration_sec = float(parts[2]) if len(parts) >= 3 and parts[2] else default_duration_sec
|
||||
except ValueError:
|
||||
return (default_start_sec, default_scale_factor, default_duration_sec)
|
||||
|
||||
if not isfinite(start_sec) or start_sec < 0:
|
||||
start_sec = default_start_sec
|
||||
if not isfinite(scale_factor) or scale_factor <= 1.0:
|
||||
scale_factor = default_scale_factor
|
||||
if not isfinite(duration_sec) or duration_sec <= 0:
|
||||
duration_sec = default_duration_sec
|
||||
|
||||
return (start_sec, scale_factor, duration_sec)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RenderSpec:
|
||||
|
||||
@@ -491,7 +491,10 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
|
||||
# 解析 effects
|
||||
effects = render_spec.get_effects()
|
||||
has_camera_shot = any(e.effect_type == 'cameraShot' for e in effects)
|
||||
has_complex_effect = any(
|
||||
effect.effect_type in {'cameraShot', 'zoom'}
|
||||
for effect in effects
|
||||
)
|
||||
|
||||
# 硬件加速时需要先 hwdownload(将 GPU 表面下载到系统内存)
|
||||
hwaccel_prefix = self.get_hwaccel_filter_prefix()
|
||||
@@ -541,9 +544,8 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
)
|
||||
filters.append(scale_filter)
|
||||
|
||||
# 5. 特效处理(cameraShot 需要特殊处理)
|
||||
if has_camera_shot:
|
||||
# cameraShot 需要使用 filter_complex 格式
|
||||
# 5. 特效处理(cameraShot / zoom 需要使用 filter_complex)
|
||||
if has_complex_effect:
|
||||
return self._build_filter_complex_with_effects(
|
||||
base_filters=filters,
|
||||
effects=effects,
|
||||
@@ -624,7 +626,7 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
"""
|
||||
构建包含特效的 filter_complex 滤镜图
|
||||
|
||||
cameraShot 效果需要使用 split/freezeframes/concat 滤镜组合。
|
||||
cameraShot / zoom 效果都在此处统一处理并按 effects 顺序叠加。
|
||||
|
||||
Args:
|
||||
base_filters: 基础滤镜列表
|
||||
@@ -695,6 +697,31 @@ class RenderSegmentVideoHandler(BaseHandler):
|
||||
f"{frozen_out}{rest_out}concat=n=2:v=1:a=0{effect_output}"
|
||||
)
|
||||
|
||||
current_output = effect_output
|
||||
effect_idx += 1
|
||||
elif effect.effect_type == 'zoom':
|
||||
start_sec, scale_factor, duration_sec = effect.get_zoom_params()
|
||||
if start_sec < 0 or scale_factor <= 1.0 or duration_sec <= 0:
|
||||
continue
|
||||
|
||||
zoom_end_sec = start_sec + duration_sec
|
||||
base_out = f'[eff{effect_idx}_base]'
|
||||
zoom_source_out = f'[eff{effect_idx}_zoom_src]'
|
||||
zoom_scaled_out = f'[eff{effect_idx}_zoom_scaled]'
|
||||
effect_output = f'[v_eff{effect_idx}]'
|
||||
zoom_enable = f"'between(t,{start_sec},{zoom_end_sec})'"
|
||||
|
||||
filter_parts.append(
|
||||
f"{current_output}split=2{base_out}{zoom_source_out}"
|
||||
)
|
||||
filter_parts.append(
|
||||
f"{zoom_source_out}scale=iw*{scale_factor}:ih*{scale_factor},"
|
||||
f"crop={width}:{height}:(in_w-{width})/2:(in_h-{height})/2{zoom_scaled_out}"
|
||||
)
|
||||
filter_parts.append(
|
||||
f"{base_out}{zoom_scaled_out}overlay=0:0:enable={zoom_enable}{effect_output}"
|
||||
)
|
||||
|
||||
current_output = effect_output
|
||||
effect_idx += 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user