You've already forked FrameTour-RenderWorker
Compare commits
12 Commits
4b080771f6
...
master
Author | SHA1 | Date | |
---|---|---|---|
bd0c44b17f | |||
432472fd19 | |||
8f0250df43 | |||
0209c5de3f | |||
51e7d21f84 | |||
0770cb361d | |||
2f694da5fd | |||
bf912037d1 | |||
1119a7b030 | |||
5282e58a10 | |||
f7141e5d4e | |||
f23bcfdd25 |
@@ -7,5 +7,7 @@ TEMP_DIR=tmp/
|
|||||||
ENCODER_ARGS="-c:v h264_qsv -global_quality 28 -look_ahead 1"
|
ENCODER_ARGS="-c:v h264_qsv -global_quality 28 -look_ahead 1"
|
||||||
# NVENC
|
# NVENC
|
||||||
#ENCODER_ARGS="-c:v h264_nvenc -cq:v 24 -preset:v p7 -tune:v hq -profile:v high"
|
#ENCODER_ARGS="-c:v h264_nvenc -cq:v 24 -preset:v p7 -tune:v hq -profile:v high"
|
||||||
|
# HEVC
|
||||||
|
#VIDEO_ARGS="-profile:v main
|
||||||
UPLOAD_METHOD="rclone"
|
UPLOAD_METHOD="rclone"
|
||||||
RCLONE_REPLACE_MAP="https://oss.zhentuai.com|alioss://frametour-assets,https://frametour-assets.oss-cn-shanghai.aliyuncs.com|alioss://frametour-assets"
|
RCLONE_REPLACE_MAP="https://oss.zhentuai.com|alioss://frametour-assets,https://frametour-assets.oss-cn-shanghai.aliyuncs.com|alioss://frametour-assets"
|
@@ -24,6 +24,9 @@ def parse_ffmpeg_task(task_info, template_info):
|
|||||||
span.set_attribute("task_params", task_params_str)
|
span.set_attribute("task_params", task_params_str)
|
||||||
task_params: dict = json.loads(task_params_str)
|
task_params: dict = json.loads(task_params_str)
|
||||||
task_params_orig = json.loads(task_params_str)
|
task_params_orig = json.loads(task_params_str)
|
||||||
|
|
||||||
|
# 统计only_if占位符的使用次数
|
||||||
|
only_if_usage_count = {}
|
||||||
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]
|
||||||
@@ -41,8 +44,10 @@ 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:
|
||||||
if not check_placeholder_exist(only_if, task_params_orig):
|
only_if_usage_count[only_if] = only_if_usage_count.get(only_if, 0) + 1
|
||||||
logger.info("because only_if exist, placeholder: %s not exist, skip part: %s", only_if, part)
|
required_count = only_if_usage_count.get(only_if)
|
||||||
|
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)
|
||||||
continue
|
continue
|
||||||
sub_ffmpeg_task = FfmpegTask(source)
|
sub_ffmpeg_task = FfmpegTask(source)
|
||||||
sub_ffmpeg_task.resolution = template_info.get("video_size", "")
|
sub_ffmpeg_task.resolution = template_info.get("video_size", "")
|
||||||
@@ -50,10 +55,11 @@ def parse_ffmpeg_task(task_info, template_info):
|
|||||||
sub_ffmpeg_task.ext_data = ext_data or {}
|
sub_ffmpeg_task.ext_data = ext_data or {}
|
||||||
sub_ffmpeg_task.frame_rate = template_info.get("frame_rate", 25)
|
sub_ffmpeg_task.frame_rate = template_info.get("frame_rate", 25)
|
||||||
sub_ffmpeg_task.center_cut = part.get("crop_mode", None)
|
sub_ffmpeg_task.center_cut = part.get("crop_mode", None)
|
||||||
|
sub_ffmpeg_task.zoom_cut = part.get("zoom_cut", None)
|
||||||
for effect in part.get('effects', []):
|
for effect in part.get('effects', []):
|
||||||
sub_ffmpeg_task.add_effect(effect)
|
sub_ffmpeg_task.add_effect(effect)
|
||||||
for lut in part.get('filters', []):
|
for lut in part.get('luts', []):
|
||||||
sub_ffmpeg_task.add_lut(os.path.join(template_info.get("local_path"), lut))
|
sub_ffmpeg_task.add_lut(os.path.join(template_info.get("local_path"), lut).replace("\\", "/"))
|
||||||
for audio in part.get('audios', []):
|
for audio in part.get('audios', []):
|
||||||
sub_ffmpeg_task.add_audios(os.path.join(template_info.get("local_path"), audio))
|
sub_ffmpeg_task.add_audios(os.path.join(template_info.get("local_path"), audio))
|
||||||
for overlay in part.get('overlays', []):
|
for overlay in part.get('overlays', []):
|
||||||
@@ -64,6 +70,7 @@ def parse_ffmpeg_task(task_info, template_info):
|
|||||||
task.resolution = template_info.get("video_size", "")
|
task.resolution = template_info.get("video_size", "")
|
||||||
overall = template_info.get("overall_template")
|
overall = template_info.get("overall_template")
|
||||||
task.center_cut = template_info.get("crop_mode", None)
|
task.center_cut = template_info.get("crop_mode", None)
|
||||||
|
task.zoom_cut = template_info.get("zoom_cut", None)
|
||||||
task.frame_rate = template_info.get("frame_rate", 25)
|
task.frame_rate = template_info.get("frame_rate", 25)
|
||||||
# if overall.get('source', ''):
|
# if overall.get('source', ''):
|
||||||
# source, ext_data = parse_video(overall.get('source'), task_params, template_info)
|
# source, ext_data = parse_video(overall.get('source'), task_params, template_info)
|
||||||
@@ -71,8 +78,8 @@ def parse_ffmpeg_task(task_info, template_info):
|
|||||||
# task.ext_data = ext_data or {}
|
# task.ext_data = ext_data or {}
|
||||||
for effect in overall.get('effects', []):
|
for effect in overall.get('effects', []):
|
||||||
task.add_effect(effect)
|
task.add_effect(effect)
|
||||||
for lut in overall.get('filters', []):
|
for lut in overall.get('luts', []):
|
||||||
task.add_lut(os.path.join(template_info.get("local_path"), lut))
|
task.add_lut(os.path.join(template_info.get("local_path"), lut).replace("\\", "/"))
|
||||||
for audio in overall.get('audios', []):
|
for audio in overall.get('audios', []):
|
||||||
task.add_audios(os.path.join(template_info.get("local_path"), audio))
|
task.add_audios(os.path.join(template_info.get("local_path"), audio))
|
||||||
for overlay in overall.get('overlays', []):
|
for overlay in overall.get('overlays', []):
|
||||||
@@ -112,6 +119,16 @@ def check_placeholder_exist(placeholder_id, task_params):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_placeholder_exist_with_count(placeholder_id, task_params, required_count=1):
|
||||||
|
"""检查占位符是否存在足够数量的片段"""
|
||||||
|
if placeholder_id in task_params:
|
||||||
|
new_sources = task_params.get(placeholder_id, [])
|
||||||
|
if type(new_sources) is list:
|
||||||
|
return len(new_sources) >= required_count
|
||||||
|
return required_count <= 1
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def start_ffmpeg_task(ffmpeg_task):
|
def start_ffmpeg_task(ffmpeg_task):
|
||||||
tracer = get_tracer(__name__)
|
tracer = get_tracer(__name__)
|
||||||
with tracer.start_as_current_span("start_ffmpeg_task") as span:
|
with tracer.start_as_current_span("start_ffmpeg_task") as span:
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
SUPPORT_FEATURE = (
|
SUPPORT_FEATURE = (
|
||||||
'simple_render_algo',
|
'simple_render_algo',
|
||||||
'gpu_accelerate',
|
'gpu_accelerate',
|
||||||
'gpu_accelerate',
|
'hevc_encode',
|
||||||
'rapid_download',
|
'rapid_download',
|
||||||
'rclone_upload',
|
'rclone_upload',
|
||||||
|
'custom_re_encode',
|
||||||
)
|
)
|
||||||
SOFTWARE_VERSION = '0.0.2'
|
SOFTWARE_VERSION = '0.0.4'
|
||||||
|
106
entity/ffmpeg.py
106
entity/ffmpeg.py
@@ -6,11 +6,22 @@ from typing import Any
|
|||||||
|
|
||||||
DEFAULT_ARGS = ("-shortest",)
|
DEFAULT_ARGS = ("-shortest",)
|
||||||
ENCODER_ARGS = ("-c:v", "h264", ) if not os.getenv("ENCODER_ARGS", False) else os.getenv("ENCODER_ARGS", "").split(" ")
|
ENCODER_ARGS = ("-c:v", "h264", ) if not os.getenv("ENCODER_ARGS", False) else os.getenv("ENCODER_ARGS", "").split(" ")
|
||||||
VIDEO_ARGS = ("-profile:v", "high", "-level:v", "4", )
|
VIDEO_ARGS = ("-profile:v", "high", "-level:v", "4", ) if not os.getenv("VIDEO_ARGS", False) else os.getenv("VIDEO_ARGS", "").split(" ")
|
||||||
AUDIO_ARGS = ("-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "2", )
|
AUDIO_ARGS = ("-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "2", )
|
||||||
MUTE_AUDIO_INPUT = ("-f", "lavfi", "-i", "anullsrc=cl=stereo:r=48000", )
|
MUTE_AUDIO_INPUT = ("-f", "lavfi", "-i", "anullsrc=cl=stereo:r=48000", )
|
||||||
|
|
||||||
|
|
||||||
|
def get_mp4toannexb_filter():
|
||||||
|
"""
|
||||||
|
Determine which mp4toannexb filter to use based on ENCODER_ARGS.
|
||||||
|
Returns 'hevc_mp4toannexb' if ENCODER_ARGS contains 'hevc', otherwise 'h264_mp4toannexb'.
|
||||||
|
"""
|
||||||
|
encoder_args_str = os.getenv("ENCODER_ARGS", "").lower()
|
||||||
|
if "hevc" in encoder_args_str:
|
||||||
|
return "hevc_mp4toannexb"
|
||||||
|
return "h264_mp4toannexb"
|
||||||
|
|
||||||
|
|
||||||
class FfmpegTask(object):
|
class FfmpegTask(object):
|
||||||
|
|
||||||
effects: list[str]
|
effects: list[str]
|
||||||
@@ -164,7 +175,7 @@ class FfmpegTask(object):
|
|||||||
output_args = [*VIDEO_ARGS, *AUDIO_ARGS, *ENCODER_ARGS, *DEFAULT_ARGS]
|
output_args = [*VIDEO_ARGS, *AUDIO_ARGS, *ENCODER_ARGS, *DEFAULT_ARGS]
|
||||||
if self.annexb:
|
if self.annexb:
|
||||||
output_args.append("-bsf:v")
|
output_args.append("-bsf:v")
|
||||||
output_args.append("h264_mp4toannexb")
|
output_args.append(get_mp4toannexb_filter())
|
||||||
output_args.append("-reset_timestamps")
|
output_args.append("-reset_timestamps")
|
||||||
output_args.append("1")
|
output_args.append("1")
|
||||||
video_output_str = "[0:v]"
|
video_output_str = "[0:v]"
|
||||||
@@ -187,6 +198,32 @@ class FfmpegTask(object):
|
|||||||
filter_args.append(f"{video_output_str}crop=x={_x}:y=0:w=ih*ih/iw:h=ih[v_cut{effect_index}]")
|
filter_args.append(f"{video_output_str}crop=x={_x}:y=0:w=ih*ih/iw:h=ih[v_cut{effect_index}]")
|
||||||
video_output_str = f"[v_cut{effect_index}]"
|
video_output_str = f"[v_cut{effect_index}]"
|
||||||
effect_index += 1
|
effect_index += 1
|
||||||
|
if self.zoom_cut == 1 and self.resolution:
|
||||||
|
_input = None
|
||||||
|
for input_file in self.input_file:
|
||||||
|
if type(input_file) is str:
|
||||||
|
_input = input_file
|
||||||
|
break
|
||||||
|
elif isinstance(input_file, FfmpegTask):
|
||||||
|
_input = input_file.get_output_file()
|
||||||
|
break
|
||||||
|
if _input:
|
||||||
|
from util.ffmpeg import probe_video_info
|
||||||
|
_iw, _ih, _ = probe_video_info(_input)
|
||||||
|
_w, _h = self.resolution.split('x', 1)
|
||||||
|
pos_json_str = self.ext_data.get('posJson', '{}')
|
||||||
|
pos_json = json.loads(pos_json_str)
|
||||||
|
_v_w = pos_json.get('imgWidth', 1)
|
||||||
|
_v_h = pos_json.get('imgHeight', 1)
|
||||||
|
_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)
|
||||||
|
_x = min(max(0, (_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))
|
||||||
|
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}]"
|
||||||
|
effect_index += 1
|
||||||
for effect in self.effects:
|
for effect in self.effects:
|
||||||
if effect.startswith("cameraShot:"):
|
if effect.startswith("cameraShot:"):
|
||||||
param = effect.split(":", 2)[1]
|
param = effect.split(":", 2)[1]
|
||||||
@@ -212,9 +249,10 @@ class FfmpegTask(object):
|
|||||||
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}")
|
||||||
@@ -232,13 +270,63 @@ 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}]"
|
||||||
...
|
...
|
||||||
...
|
|
||||||
for lut in self.luts:
|
|
||||||
filter_args.append(f"{video_output_str}lut3d=file={lut}{video_output_str}")
|
|
||||||
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]")
|
||||||
video_output_str = "[v]"
|
video_output_str = "[v]"
|
||||||
|
for lut in self.luts:
|
||||||
|
filter_args.append(f"{video_output_str}lut3d=file={lut}{video_output_str}")
|
||||||
for overlay in self.overlays:
|
for overlay in self.overlays:
|
||||||
input_index = input_args.count("-i")
|
input_index = input_args.count("-i")
|
||||||
input_args.append("-i")
|
input_args.append("-i")
|
||||||
@@ -256,6 +344,8 @@ class FfmpegTask(object):
|
|||||||
output_args.append(video_output_str)
|
output_args.append(video_output_str)
|
||||||
output_args.append("-r")
|
output_args.append("-r")
|
||||||
output_args.append(f"{self.frame_rate}")
|
output_args.append(f"{self.frame_rate}")
|
||||||
|
output_args.append("-fps_mode")
|
||||||
|
output_args.append("cfr")
|
||||||
if self.mute:
|
if self.mute:
|
||||||
input_index = input_args.count("-i")
|
input_index = input_args.count("-i")
|
||||||
input_args += MUTE_AUDIO_INPUT
|
input_args += MUTE_AUDIO_INPUT
|
||||||
@@ -333,7 +423,7 @@ class FfmpegTask(object):
|
|||||||
output_args += AUDIO_ARGS
|
output_args += AUDIO_ARGS
|
||||||
if self.annexb:
|
if self.annexb:
|
||||||
output_args.append("-bsf:v")
|
output_args.append("-bsf:v")
|
||||||
output_args.append("h264_mp4toannexb")
|
output_args.append(get_mp4toannexb_filter())
|
||||||
output_args.append("-bsf:a")
|
output_args.append("-bsf:a")
|
||||||
output_args.append("setts=pts=DTS")
|
output_args.append("setts=pts=DTS")
|
||||||
output_args.append("-f")
|
output_args.append("-f")
|
||||||
|
12
index.py
12
index.py
@@ -1,15 +1,25 @@
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
import sys
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import biz.task
|
import biz.task
|
||||||
from telemetry import init_opentelemetry
|
from telemetry import init_opentelemetry
|
||||||
from template import load_local_template
|
from template import load_local_template, download_template, TEMPLATES
|
||||||
from util import api
|
from util import api
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
load_local_template()
|
load_local_template()
|
||||||
|
|
||||||
|
# Check for redownload parameter
|
||||||
|
if 'redownload' in sys.argv:
|
||||||
|
print("Redownloading all templates...")
|
||||||
|
for template_name in TEMPLATES.keys():
|
||||||
|
print(f"Redownloading template: {template_name}")
|
||||||
|
download_template(template_name)
|
||||||
|
print("All templates redownloaded successfully!")
|
||||||
|
sys.exit(0)
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
requests~=2.32.3
|
requests~=2.32.3
|
||||||
psutil~=6.1.0
|
psutil~=6.1.0
|
||||||
python-dotenv~=1.0.1
|
python-dotenv~=1.0.1
|
||||||
opentelemetry-api~=1.30.0
|
opentelemetry-api~=1.35.0
|
||||||
opentelemetry-sdk~=1.30.0
|
opentelemetry-sdk~=1.35.0
|
||||||
opentelemetry-exporter-otlp~=1.30.0
|
opentelemetry-exporter-otlp~=1.35.0
|
||||||
|
opentelemetry-instrumentation-threading~=0.56b0
|
||||||
flask~=3.1.0
|
flask~=3.1.0
|
@@ -6,7 +6,9 @@ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExport
|
|||||||
from opentelemetry.sdk.resources import DEPLOYMENT_ENVIRONMENT, HOST_NAME, Resource, SERVICE_NAME, SERVICE_VERSION
|
from opentelemetry.sdk.resources import DEPLOYMENT_ENVIRONMENT, HOST_NAME, Resource, SERVICE_NAME, SERVICE_VERSION
|
||||||
from opentelemetry.sdk.trace import TracerProvider
|
from opentelemetry.sdk.trace import TracerProvider
|
||||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
|
||||||
|
from opentelemetry.instrumentation.threading import ThreadingInstrumentor
|
||||||
|
|
||||||
|
ThreadingInstrumentor().instrument()
|
||||||
|
|
||||||
def get_tracer(name):
|
def get_tracer(name):
|
||||||
return trace.get_tracer(name)
|
return trace.get_tracer(name)
|
||||||
|
@@ -79,6 +79,8 @@ def download_template(template_id):
|
|||||||
tracer = get_tracer(__name__)
|
tracer = get_tracer(__name__)
|
||||||
with tracer.start_as_current_span("download_template"):
|
with tracer.start_as_current_span("download_template"):
|
||||||
template_info = api.get_template_info(template_id)
|
template_info = api.get_template_info(template_id)
|
||||||
|
if template_info is None:
|
||||||
|
return
|
||||||
if not os.path.isdir(template_info['local_path']):
|
if not os.path.isdir(template_info['local_path']):
|
||||||
os.makedirs(template_info['local_path'])
|
os.makedirs(template_info['local_path'])
|
||||||
# download template assets
|
# download template assets
|
||||||
|
@@ -87,6 +87,9 @@ def get_template_info(template_id):
|
|||||||
data = response.json()
|
data = response.json()
|
||||||
logger.debug("获取模板信息结果:【%s】", data)
|
logger.debug("获取模板信息结果:【%s】", data)
|
||||||
remote_template_info = data.get('data', {})
|
remote_template_info = data.get('data', {})
|
||||||
|
if not remote_template_info:
|
||||||
|
logger.warning("获取模板信息结果为空", data)
|
||||||
|
return None
|
||||||
template = {
|
template = {
|
||||||
'id': template_id,
|
'id': template_id,
|
||||||
'updateTime': remote_template_info.get('updateTime', template_id),
|
'updateTime': remote_template_info.get('updateTime', template_id),
|
||||||
|
@@ -7,7 +7,7 @@ from typing import Optional, IO
|
|||||||
|
|
||||||
from opentelemetry.trace import Status, StatusCode
|
from opentelemetry.trace import Status, StatusCode
|
||||||
|
|
||||||
from entity.ffmpeg import FfmpegTask, ENCODER_ARGS, VIDEO_ARGS, AUDIO_ARGS, MUTE_AUDIO_INPUT
|
from entity.ffmpeg import FfmpegTask, ENCODER_ARGS, VIDEO_ARGS, AUDIO_ARGS, MUTE_AUDIO_INPUT, get_mp4toannexb_filter
|
||||||
from telemetry import get_tracer
|
from telemetry import get_tracer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -20,12 +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)
|
||||||
ffmpeg_process = subprocess.run(["ffmpeg", "-y", "-hide_banner", "-vsync", "cfr", "-i", 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,
|
||||||
*(set() if has_audio else MUTE_AUDIO_INPUT),
|
*(set() if has_audio else MUTE_AUDIO_INPUT),
|
||||||
|
"-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", "h264_mp4toannexb",
|
*_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))
|
||||||
|
10
util/oss.py
10
util/oss.py
@@ -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
|
||||||
@@ -35,7 +36,7 @@ def upload_to_oss(url, file_path):
|
|||||||
new_url = new_url.split("?", 1)[0]
|
new_url = new_url.split("?", 1)[0]
|
||||||
r_span.set_attribute("rclone.target_dir", new_url)
|
r_span.set_attribute("rclone.target_dir", new_url)
|
||||||
if new_url != url:
|
if new_url != url:
|
||||||
result = os.system(f"rclone copyto --no-check-dest --ignore-existing --multi-thread-chunk-size 32M --multi-thread-streams 8 {file_path} {new_url}")
|
result = os.system(f"rclone copyto --no-check-dest --ignore-existing --multi-thread-chunk-size 8M --multi-thread-streams 8 {file_path} {new_url}")
|
||||||
r_span.set_attribute("rclone.result", result)
|
r_span.set_attribute("rclone.result", result)
|
||||||
if result == 0:
|
if result == 0:
|
||||||
span.set_status(Status(StatusCode.OK))
|
span.set_status(Status(StatusCode.OK))
|
||||||
@@ -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))
|
||||||
|
Reference in New Issue
Block a user