Compare commits

...

5 Commits

Author SHA1 Message Date
bd0c44b17f tail效果 2025-08-12 14:22:26 +08:00
432472fd19 逻辑问题 2025-08-09 10:57:45 +08:00
8f0250df43 通过argv传skip_if_exist默认值 2025-08-08 13:58:01 +08:00
0209c5de3f 单独渲染模板 2025-08-08 13:58:01 +08:00
51e7d21f84 帧跳过、zoom 2025-08-08 13:58:01 +08:00
5 changed files with 80 additions and 15 deletions

View File

@@ -27,10 +27,6 @@ def parse_ffmpeg_task(task_info, template_info):
# 统计only_if占位符的使用次数 # 统计only_if占位符的使用次数
only_if_usage_count = {} only_if_usage_count = {}
for part in template_info.get("video_parts", []):
only_if = part.get('only_if', '')
if only_if:
only_if_usage_count[only_if] = only_if_usage_count.get(only_if, 0) + 1
with tracer.start_as_current_span("parse_ffmpeg_task.download_all") as sub_span: with tracer.start_as_current_span("parse_ffmpeg_task.download_all") as sub_span:
with ThreadPoolExecutor(max_workers=8) as executor: with ThreadPoolExecutor(max_workers=8) as executor:
param_list: list[dict] param_list: list[dict]
@@ -48,7 +44,8 @@ def parse_ffmpeg_task(task_info, template_info):
continue continue
only_if = part.get('only_if', '') only_if = part.get('only_if', '')
if only_if: if only_if:
required_count = only_if_usage_count.get(only_if, 1) only_if_usage_count[only_if] = only_if_usage_count.get(only_if, 0) + 1
required_count = only_if_usage_count.get(only_if)
if not check_placeholder_exist_with_count(only_if, task_params_orig, required_count): if not check_placeholder_exist_with_count(only_if, task_params_orig, required_count):
logger.info("because only_if exist, placeholder: %s insufficient (need %d), skip part: %s", only_if, required_count, part) logger.info("because only_if exist, placeholder: %s insufficient (need %d), skip part: %s", only_if, required_count, part)
continue continue

View File

@@ -4,5 +4,6 @@ SUPPORT_FEATURE = (
'hevc_encode', 'hevc_encode',
'rapid_download', 'rapid_download',
'rclone_upload', 'rclone_upload',
'custom_re_encode',
) )
SOFTWARE_VERSION = '0.0.3' SOFTWARE_VERSION = '0.0.4'

View File

@@ -246,12 +246,13 @@ class FfmpegTask(object):
_mid_out_str = "[eff_m]" _mid_out_str = "[eff_m]"
_end_out_str = "[eff_e]" _end_out_str = "[eff_e]"
filter_args.append(f"{video_output_str}split=3{_start_out_str}{_mid_out_str}{_end_out_str}") filter_args.append(f"{video_output_str}split=3{_start_out_str}{_mid_out_str}{_end_out_str}")
filter_args.append(f"{_start_out_str}select=lt(n\\,{int(start*self.frame_rate)}){_start_out_str}") filter_args.append(f"{_start_out_str}select=lt(n\\,{int(start * self.frame_rate)}){_start_out_str}")
filter_args.append(f"{_end_out_str}select=gt(n\\,{int(start*self.frame_rate)}){_end_out_str}") filter_args.append(f"{_end_out_str}select=gt(n\\,{int(start * self.frame_rate)}){_end_out_str}")
filter_args.append(f"{_mid_out_str}select=eq(n\\,{int(start*self.frame_rate)}){_mid_out_str}") filter_args.append(f"{_mid_out_str}select=eq(n\\,{int(start * self.frame_rate)}){_mid_out_str}")
filter_args.append(f"{_mid_out_str}tpad=start_mode=clone:start_duration={duration:.4f}{_mid_out_str}") filter_args.append(
f"{_mid_out_str}tpad=start_mode=clone:start_duration={duration:.4f}{_mid_out_str}")
if rotate_deg != 0: if rotate_deg != 0:
filter_args.append(f"{_mid_out_str}rotate=PI*{rotate_deg}/360{_mid_out_str}") filter_args.append(f"{_mid_out_str}rotate=PI*{rotate_deg}/180{_mid_out_str}")
# filter_args.append(f"{video_output_str}trim=start=0:end={start+duration},tpad=stop_mode=clone:stop_duration={duration},setpts=PTS-STARTPTS{_start_out_str}") # filter_args.append(f"{video_output_str}trim=start=0:end={start+duration},tpad=stop_mode=clone:stop_duration={duration},setpts=PTS-STARTPTS{_start_out_str}")
# filter_args.append(f"tpad=start_mode=clone:start_duration={duration},setpts=PTS-STARTPTS{_start_out_str}") # filter_args.append(f"tpad=start_mode=clone:start_duration={duration},setpts=PTS-STARTPTS{_start_out_str}")
# filter_args.append(f"{_end_out_str}trim=start={start}{_end_out_str}") # filter_args.append(f"{_end_out_str}trim=start={start}{_end_out_str}")
@@ -269,7 +270,57 @@ class FfmpegTask(object):
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}]"
elif effect.startswith("zoom:"): elif effect.startswith("zoom:"):
... param = effect.split(":", 2)[1]
if param == '':
continue
_split = param.split(",")
if len(_split) < 3:
continue
try:
start_time = float(_split[0])
zoom_factor = float(_split[1])
duration = float(_split[2])
if start_time < 0:
start_time = 0
if duration < 0:
duration = 0
if zoom_factor <= 0:
zoom_factor = 1
except (ValueError, IndexError):
start_time = 0
duration = 0
zoom_factor = 1
if zoom_factor == 1:
continue
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}]")
else:
zoom_expr = f"if(between(t,{start_time},{start_time + duration}),{zoom_factor},1)"
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}]")
video_output_str = f"[v_eff{effect_index}]"
elif effect.startswith("skip:"):
param = effect.split(":", 2)[1]
if param == '':
param = "0"
skip_seconds = float(param)
if skip_seconds > 0:
effect_index += 1
filter_args.append(f"{video_output_str}trim=start={skip_seconds},setpts=PTS-STARTPTS[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]"
elif effect.startswith("tail:"):
param = effect.split(":", 2)[1]
if param == '':
param = "0"
tail_seconds = float(param)
if tail_seconds > 0:
effect_index += 1
# 首先获取视频总时长,然后计算开始时间
# 使用reverse+trim+reverse的方法来精确获取最后N秒
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_trim{effect_index}]reverse[v_eff{effect_index}]")
video_output_str = f"[v_eff{effect_index}]"
... ...
if self.resolution: if self.resolution:
filter_args.append(f"{video_output_str}scale={self.resolution.replace('x', ':')}[v]") filter_args.append(f"{video_output_str}scale={self.resolution.replace('x', ':')}[v]")

View File

@@ -20,13 +20,23 @@ def re_encode_and_annexb(file):
return file return file
logger.info("ReEncodeAndAnnexb: %s", file) logger.info("ReEncodeAndAnnexb: %s", file)
has_audio = not not probe_video_audio(file) has_audio = not not probe_video_audio(file)
# 优先使用RE_ENCODE_VIDEO_ARGS环境变量,其次使用默认的VIDEO_ARGS
if os.getenv("RE_ENCODE_VIDEO_ARGS", False):
_video_args = tuple(os.getenv("RE_ENCODE_VIDEO_ARGS", "").split(" "))
else:
_video_args = VIDEO_ARGS
# 优先使用RE_ENCODE_ENCODER_ARGS环境变量,其次使用默认的ENCODER_ARGS
if os.getenv("RE_ENCODE_ENCODER_ARGS", False):
_encoder_args = tuple(os.getenv("RE_ENCODE_ENCODER_ARGS", "").split(" "))
else:
_encoder_args = ENCODER_ARGS
ffmpeg_process = subprocess.run(["ffmpeg", "-y", "-hide_banner", "-i", file, ffmpeg_process = subprocess.run(["ffmpeg", "-y", "-hide_banner", "-i", file,
*(set() if has_audio else MUTE_AUDIO_INPUT), *(set() if has_audio else MUTE_AUDIO_INPUT),
"-fps_mode", "cfr", "-fps_mode", "cfr",
"-map", "0:v", "-map", "0:a" if has_audio else "1:a", "-map", "0:v", "-map", "0:a" if has_audio else "1:a",
*VIDEO_ARGS, "-bsf:v", get_mp4toannexb_filter(), *_video_args, "-bsf:v", get_mp4toannexb_filter(),
*AUDIO_ARGS, "-bsf:a", "setts=pts=DTS", *AUDIO_ARGS, "-bsf:a", "setts=pts=DTS",
*ENCODER_ARGS, "-shortest", "-fflags", "+genpts", *_encoder_args, "-shortest", "-fflags", "+genpts",
"-f", "mpegts", file + ".ts"]) "-f", "mpegts", file + ".ts"])
logger.info(" ".join(ffmpeg_process.args)) logger.info(" ".join(ffmpeg_process.args))
span.set_attribute("ffmpeg.args", json.dumps(ffmpeg_process.args)) span.set_attribute("ffmpeg.args", json.dumps(ffmpeg_process.args))

View File

@@ -1,5 +1,6 @@
import logging import logging
import os import os
import sys
import requests import requests
from opentelemetry.trace import Status, StatusCode from opentelemetry.trace import Status, StatusCode
@@ -70,7 +71,7 @@ def upload_to_oss(url, file_path):
return False return False
def download_from_oss(url, file_path, skip_if_exist=False): def download_from_oss(url, file_path, skip_if_exist=None):
""" """
使用签名URL下载文件到OSS 使用签名URL下载文件到OSS
:param skip_if_exist: 如果存在就不下载了 :param skip_if_exist: 如果存在就不下载了
@@ -82,6 +83,11 @@ def download_from_oss(url, file_path, skip_if_exist=False):
with tracer.start_as_current_span("download_from_oss") as span: with tracer.start_as_current_span("download_from_oss") as span:
span.set_attribute("file.url", url) span.set_attribute("file.url", url)
span.set_attribute("file.path", file_path) span.set_attribute("file.path", file_path)
# 如果skip_if_exist为None,则从启动参数中读取
if skip_if_exist is None:
skip_if_exist = 'skip_if_exist' in sys.argv
if skip_if_exist and os.path.exists(file_path): if skip_if_exist and os.path.exists(file_path):
span.set_attribute("file.exist", True) span.set_attribute("file.exist", True)
span.set_attribute("file.size", os.path.getsize(file_path)) span.set_attribute("file.size", os.path.getsize(file_path))