Compare commits

7 Commits

Author SHA1 Message Date
d7704005b6 feat(entity/ffmpeg.py): 添加grid4效果支持在ffmpeg.py中增加了对grid4效果的支持。该功能允许用户通过指定参数来创建一个四宫格视频布局,每个格子显示不同的视频片段,并且可以设置延迟时间以实现更丰富的视觉效果。具体改动包括:
- 解析`grid4`效果的参数,如果未提供则默认为1。
- 根据提供的或默认的分辨率分割视频流为四个部分。
-为每个分割后的视频流应用缩放和时间延迟处理。
- 创建黑色背景并使用overlay滤镜将处理后的视频流放置于正确的位置上,形成最终的四宫格布局。
2025-09-18 17:01:03 +08:00
f85ccea933 feat(constant): 更新软件版本号至 0.0.6- 在 constant/__init__.py 文件中将 SOFTWARE_VERSION 从 '0.0.5' 修改为 '0.0.6'
- 在 entity/ffmpeg.py 文件中添加了新的视频效果处理逻辑,支持显示特定时长的视频片段
2025-09-18 09:42:57 +08:00
0c7181911e refactor(entity): 优化视频变速和缩放效果处理
-将视频变速实现从 minterpolate 改为使用 setpts,避免 PTS 冲突问题
-简化缩放效果处理逻辑
2025-09-12 18:01:41 +08:00
cf43f6379e feat(ffmpeg): 使用 minterpolate 替代 fps 调整视频速度
- 将视频变速功能从直接调整帧率改为使用 minterpolate 滤镜- 通过设置 fps 和 mi_mode 参数实现平滑的视频慢放效果
- 解决了直接调整帧率可能导致的 PTS 冲突问题
2025-09-12 16:50:53 +08:00
ce8854404b fix(entity): 修复视频慢放时 PTS 冲突问题
- 修改视频变速功能,通过改变帧率实现慢放效果
-避免使用 setpts滤镜导致的 PTS 冲突
- 优化代码结构,提高可读性和可维护性
2025-09-12 14:54:01 +08:00
c36e838d4f fix(entity): 修复缩放效果
fix(entity): 移除 ffmpeg缩放和裁剪滤镜中的 setpts 指令

移除了 ffmpeg 缩放、裁剪和尾部处理滤镜中的 setpts=PTS-STARTPTS指令。这个指令在某些情况下可能导致视频处理出现问题,例如在使用 zoompan 滤镜时。此修改旨在提高视频处理的稳定性和正确性。

fix(entity): 修复缩放效果中中心点计算错误

-针对静态缩放和动态缩放分别修正了中心点计算公式
- 确保在不同缩放因子下,图像中心点位置保持正确

fix(entity): 修复 ffmpeg zoompan 滤镜参数

-将 zoompan 滤镜的参数从 'z=' 改为 'z=',统一参数格式- 此修改解决了 ffmpeg 在处理某些视频时可能遇到的参数解析问题

feat(zoom): 实现视频缩放特效的自定义中心点功能

- 添加代码以解析 posJson 数据,计算并设置缩放中心点
- 使用 zoompan滤镜替代原有的 scale 和 crop滤镜,支持动态缩放
- 优化静态缩放的实现,确保整个视频时长的应用

fix(entity): 修复视频缩放效果的 FFmpeg 命令

- 在 zoom_expr 中添加转义字符,以解决 FFmpeg 解析问题
- 修改缩放和裁剪滤镜的参数,提高视频处理的准确性
2025-09-12 13:20:10 +08:00
1571934943 fix(entity): 修复中心裁剪计算逻辑并优化 JSON 解析
- 在解析 posJson 时添加异常处理,避免无效 JSON 导致程序崩溃
- 修复中心裁剪计算逻辑中的取整问题,确保裁剪位置准确
2025-09-07 01:45:56 +08:00
3 changed files with 88 additions and 19 deletions

1
.gitignore vendored
View File

@@ -32,3 +32,4 @@ target/
venv/ venv/
cython_debug/ cython_debug/
.env .env
.serena

View File

@@ -6,4 +6,4 @@ SUPPORT_FEATURE = (
'rclone_upload', 'rclone_upload',
'custom_re_encode', 'custom_re_encode',
) )
SOFTWARE_VERSION = '0.0.5' SOFTWARE_VERSION = '0.0.6'

View File

@@ -190,7 +190,10 @@ class FfmpegTask(object):
input_args.append(input_file.get_output_file()) input_args.append(input_file.get_output_file())
if self.center_cut == 1: if self.center_cut == 1:
pos_json_str = self.ext_data.get('posJson', '{}') pos_json_str = self.ext_data.get('posJson', '{}')
try:
pos_json = json.loads(pos_json_str) pos_json = json.loads(pos_json_str)
except Exception as e:
pos_json = {}
_v_w = pos_json.get('imgWidth', 1) _v_w = pos_json.get('imgWidth', 1)
_f_x = pos_json.get('ltX', 0) _f_x = pos_json.get('ltX', 0)
_f_x2 = pos_json.get('rbX', 0) _f_x2 = pos_json.get('rbX', 0)
@@ -212,15 +215,18 @@ class FfmpegTask(object):
_iw, _ih, _ = probe_video_info(_input) _iw, _ih, _ = probe_video_info(_input)
_w, _h = self.resolution.split('x', 1) _w, _h = self.resolution.split('x', 1)
pos_json_str = self.ext_data.get('posJson', '{}') pos_json_str = self.ext_data.get('posJson', '{}')
try:
pos_json = json.loads(pos_json_str) pos_json = json.loads(pos_json_str)
except Exception as e:
pos_json = {}
_v_w = pos_json.get('imgWidth', 1) _v_w = pos_json.get('imgWidth', 1)
_v_h = pos_json.get('imgHeight', 1) _v_h = pos_json.get('imgHeight', 1)
_f_x = pos_json.get('ltX', 0) _f_x = pos_json.get('ltX', 0)
_f_x2 = pos_json.get('rbX', 0) _f_x2 = pos_json.get('rbX', 0)
_f_y = pos_json.get('ltY', 0) _f_y = pos_json.get('ltY', 0)
_f_y2 = pos_json.get('rbY', 0) _f_y2 = pos_json.get('rbY', 0)
_x = min(max(0, (_f_x + _f_x2) / 2 - int(_w) / 2), _iw - int(_w)) _x = min(max(0, int((_f_x + _f_x2) / 2 - int(_w) / 2)), _iw - int(_w))
_y = min(max(0, (_f_y + _f_y2) / 2 - int(_h) / 2), _ih - int(_h)) _y = min(max(0, int((_f_y + _f_y2) / 2 - int(_h) / 2)), _ih - int(_h))
filter_args.append(f"{video_output_str}crop=x={_x}:y={_y}:w={_w}:h={_h}[vz_cut{effect_index}]") filter_args.append(f"{video_output_str}crop=x={_x}:y={_y}:w={_w}:h={_h}[vz_cut{effect_index}]")
video_output_str = f"[vz_cut{effect_index}]" video_output_str = f"[vz_cut{effect_index}]"
effect_index += 1 effect_index += 1
@@ -265,7 +271,7 @@ class FfmpegTask(object):
if param == '': if param == '':
param = "1" param = "1"
if param != "1": if param != "1":
# 视频变速 # 视频变速:使用fps实现,避免PTS冲突
effect_index += 1 effect_index += 1
filter_args.append(f"{video_output_str}setpts={param}*PTS[v_eff{effect_index}]") filter_args.append(f"{video_output_str}setpts={param}*PTS[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]" video_output_str = f"[v_eff{effect_index}]"
@@ -274,30 +280,47 @@ class FfmpegTask(object):
if param == '': if param == '':
continue continue
_split = param.split(",") _split = param.split(",")
if len(_split) < 3: if len(_split) < 2:
continue continue
try: try:
start_time = float(_split[0]) start_time = float(_split[0])
zoom_factor = float(_split[1]) zoom_factor = float(_split[1])
duration = float(_split[2])
if start_time < 0: if start_time < 0:
start_time = 0 start_time = 0
if duration < 0:
duration = 0
if zoom_factor <= 0: if zoom_factor <= 0:
zoom_factor = 1 zoom_factor = 1
except (ValueError, IndexError): except (ValueError, IndexError):
start_time = 0 start_time = 0
duration = 0
zoom_factor = 1 zoom_factor = 1
if zoom_factor == 1: if zoom_factor == 1:
continue continue
effect_index += 1 effect_index += 1
if duration == 0:
filter_args.append(f"{video_output_str}trim=start={start_time},scale=iw*{zoom_factor}:ih*{zoom_factor},crop=iw/{zoom_factor}:ih/{zoom_factor}:(iw-iw/{zoom_factor})/2:(ih-ih/{zoom_factor})/2,setpts=PTS-STARTPTS[v_eff{effect_index}]") left_x = f"iw/(2*{zoom_factor})"
else: top_y = f"ih/(2*{zoom_factor})"
zoom_expr = f"if(between(t,{start_time},{start_time + duration}),{zoom_factor},1)" pos_json_str = self.ext_data.get('posJson', '{}')
filter_args.append(f"{video_output_str}scale=iw*({zoom_expr}):ih*({zoom_expr}),crop=iw:ih:(iw-iw)/2:(ih-ih)/2[v_eff{effect_index}]") try:
pos_json = json.loads(pos_json_str) if pos_json_str != '{}' else {}
if pos_json:
_f_x = pos_json.get('ltX', 0)
_f_x2 = pos_json.get('rbX', 0)
_f_y = pos_json.get('ltY', 0)
_f_y2 = pos_json.get('rbY', 0)
_v_w = pos_json.get('imgWidth', 1)
_v_h = pos_json.get('imgHeight', 1)
if _v_w > 0 and _v_h > 0:
# 计算坐标系统中的中心点
center_x_ratio = (_f_x + _f_x2) / (2 * _v_w)
center_y_ratio = (_f_y + _f_y2) / (2 * _v_h)
# 转换为视频坐标系统
left_x = f"iw*({center_x_ratio:.6f}-1/(2*{zoom_factor}))"
top_y = f"ih*({center_y_ratio:.6f}-1/(2*{zoom_factor}))"
except Exception as e:
# 解析失败使用默认中心
pass
# 静态缩放(整个视频时长)
filter_args.append(f"{video_output_str}scale={zoom_factor}*iw:{zoom_factor}*ih,crop=iw/{zoom_factor}:ih/{zoom_factor}:{left_x}:{top_y}[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]" video_output_str = f"[v_eff{effect_index}]"
elif effect.startswith("skip:"): elif effect.startswith("skip:"):
param = effect.split(":", 2)[1] param = effect.split(":", 2)[1]
@@ -306,7 +329,7 @@ class FfmpegTask(object):
skip_seconds = float(param) skip_seconds = float(param)
if skip_seconds > 0: if skip_seconds > 0:
effect_index += 1 effect_index += 1
filter_args.append(f"{video_output_str}trim=start={skip_seconds},setpts=PTS-STARTPTS[v_eff{effect_index}]") filter_args.append(f"{video_output_str}trim=start={skip_seconds}[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]" video_output_str = f"[v_eff{effect_index}]"
elif effect.startswith("tail:"): elif effect.startswith("tail:"):
param = effect.split(":", 2)[1] param = effect.split(":", 2)[1]
@@ -318,8 +341,53 @@ class FfmpegTask(object):
# 首先获取视频总时长,然后计算开始时间 # 首先获取视频总时长,然后计算开始时间
# 使用reverse+trim+reverse的方法来精确获取最后N秒 # 使用reverse+trim+reverse的方法来精确获取最后N秒
filter_args.append(f"{video_output_str}reverse[v_rev{effect_index}]") filter_args.append(f"{video_output_str}reverse[v_rev{effect_index}]")
filter_args.append(f"[v_rev{effect_index}]trim=duration={tail_seconds},setpts=PTS-STARTPTS[v_trim{effect_index}]") filter_args.append(f"[v_rev{effect_index}]trim=duration={tail_seconds}[v_trim{effect_index}]")
filter_args.append(f"[v_trim{effect_index}]reverse[v_eff{effect_index}]") filter_args.append(f"[v_trim{effect_index}]reverse[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]"
elif effect.startswith("show:"):
param = effect.split(":", 2)[1]
if param == '':
param = "0"
show_seconds = float(param)
if show_seconds > 0:
effect_index += 1
filter_args.append(f"{video_output_str}trim=end={show_seconds}[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]"
elif effect.startswith("grid4:"):
param = effect.split(":", 2)[1]
if param == '':
param = "1"
delay_seconds = float(param)
effect_index += 1
# 获取分辨率,如果没有设置则使用默认值
if self.resolution:
width, height = self.resolution.split('x')
else:
width, height = "1920", "1080" # 默认分辨率
# 计算四宫格中每个象限的大小
grid_width = int(width) // 2
grid_height = int(height) // 2
# 分割视频流为4份
filter_args.append(f"{video_output_str}split=4[grid_v1_{effect_index}][grid_v2_{effect_index}][grid_v3_{effect_index}][grid_v4_{effect_index}]")
# 创建黑色背景
filter_args.append(f"color=black:size={width}x{height}:duration=1[bg_{effect_index}]")
# 缩放每个视频流到绝对尺寸
filter_args.append(f"[grid_v1_{effect_index}]scale={grid_width}:{grid_height}[v1_scaled_{effect_index}]")
filter_args.append(f"[grid_v2_{effect_index}]scale={grid_width}:{grid_height},tpad=start_duration={delay_seconds}[v2_scaled_{effect_index}]")
filter_args.append(f"[grid_v3_{effect_index}]scale={grid_width}:{grid_height},tpad=start_duration={delay_seconds*2}[v3_scaled_{effect_index}]")
filter_args.append(f"[grid_v4_{effect_index}]scale={grid_width}:{grid_height},tpad=start_duration={delay_seconds*3}[v4_scaled_{effect_index}]")
# 使用overlay将四个视频流叠加到四个位置
filter_args.append(f"[bg_{effect_index}][v1_scaled_{effect_index}]overlay=0:0:shortest=1[grid_step1_{effect_index}]")
filter_args.append(f"[grid_step1_{effect_index}][v2_scaled_{effect_index}]overlay=w/2:0:shortest=1[grid_step2_{effect_index}]")
filter_args.append(f"[grid_step2_{effect_index}][v3_scaled_{effect_index}]overlay=0:h/2:shortest=1[grid_step3_{effect_index}]")
filter_args.append(f"[grid_step3_{effect_index}][v4_scaled_{effect_index}]overlay=w/2:h/2:shortest=1[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]" video_output_str = f"[v_eff{effect_index}]"
... ...
if self.resolution: if self.resolution: