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
|
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
|
格式: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 # 效果类型
|
effect_type: str # 效果类型
|
||||||
params: str = "" # 参数字符串
|
params: str = "" # 参数字符串
|
||||||
@@ -122,7 +125,7 @@ class Effect:
|
|||||||
解析效果字符串
|
解析效果字符串
|
||||||
|
|
||||||
格式:effect1|effect2|effect3
|
格式:effect1|effect2|effect3
|
||||||
例如:cameraShot:3,1|blur:5
|
例如:cameraShot:3,1|zoom:1.5,1.2,2
|
||||||
"""
|
"""
|
||||||
if not effects_str:
|
if not effects_str:
|
||||||
return []
|
return []
|
||||||
@@ -152,6 +155,40 @@ class Effect:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return (3, 1)
|
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
|
@dataclass
|
||||||
class RenderSpec:
|
class RenderSpec:
|
||||||
|
|||||||
@@ -491,7 +491,10 @@ class RenderSegmentVideoHandler(BaseHandler):
|
|||||||
|
|
||||||
# 解析 effects
|
# 解析 effects
|
||||||
effects = render_spec.get_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 表面下载到系统内存)
|
# 硬件加速时需要先 hwdownload(将 GPU 表面下载到系统内存)
|
||||||
hwaccel_prefix = self.get_hwaccel_filter_prefix()
|
hwaccel_prefix = self.get_hwaccel_filter_prefix()
|
||||||
@@ -541,9 +544,8 @@ class RenderSegmentVideoHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
filters.append(scale_filter)
|
filters.append(scale_filter)
|
||||||
|
|
||||||
# 5. 特效处理(cameraShot 需要特殊处理)
|
# 5. 特效处理(cameraShot / zoom 需要使用 filter_complex)
|
||||||
if has_camera_shot:
|
if has_complex_effect:
|
||||||
# cameraShot 需要使用 filter_complex 格式
|
|
||||||
return self._build_filter_complex_with_effects(
|
return self._build_filter_complex_with_effects(
|
||||||
base_filters=filters,
|
base_filters=filters,
|
||||||
effects=effects,
|
effects=effects,
|
||||||
@@ -624,7 +626,7 @@ class RenderSegmentVideoHandler(BaseHandler):
|
|||||||
"""
|
"""
|
||||||
构建包含特效的 filter_complex 滤镜图
|
构建包含特效的 filter_complex 滤镜图
|
||||||
|
|
||||||
cameraShot 效果需要使用 split/freezeframes/concat 滤镜组合。
|
cameraShot / zoom 效果都在此处统一处理并按 effects 顺序叠加。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
base_filters: 基础滤镜列表
|
base_filters: 基础滤镜列表
|
||||||
@@ -695,6 +697,31 @@ class RenderSegmentVideoHandler(BaseHandler):
|
|||||||
f"{frozen_out}{rest_out}concat=n=2:v=1:a=0{effect_output}"
|
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
|
current_output = effect_output
|
||||||
effect_idx += 1
|
effect_idx += 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user