From 29bb80f3b927315845af5d63240a63851b3b0d29 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 22 Jan 2025 14:31:59 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E6=B8=B2=E6=9F=93=E5=90=8E=E5=86=8Dto=20an?= =?UTF-8?q?nexb=EF=BC=8C=E4=BD=BF=E7=94=A8=E6=96=B0=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entity/ffmpeg.py | 1 - template/__init__.py | 4 ++-- util/ffmpeg.py | 15 ++++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/entity/ffmpeg.py b/entity/ffmpeg.py index ef9e19e..4d22331 100644 --- a/entity/ffmpeg.py +++ b/entity/ffmpeg.py @@ -202,7 +202,6 @@ class FfmpegTask(object): output_args.append("copy") output_args.append("-f") output_args.append("mp4") - output_args += ("-c:v", "h264_qsv", "-r", "25", "-global_quality", "28", "-look_ahead", "1") return args + input_args + output_args + [self.get_output_file()] output_args += ("-c:v", "h264_qsv", "-r", "25", "-global_quality", "28", "-look_ahead", "1") filter_args = [] diff --git a/template/__init__.py b/template/__init__.py index 381860a..1f9bbfa 100644 --- a/template/__init__.py +++ b/template/__init__.py @@ -88,8 +88,8 @@ def download_template(template_id): new_fp = os.path.join(template_info['local_path'], _fn) oss.download_from_oss(_template['source'], new_fp) if _fn.endswith(".mp4"): - from util.ffmpeg import to_annexb - new_fp = to_annexb(new_fp) + from util.ffmpeg import re_encode_and_annexb + new_fp = re_encode_and_annexb(new_fp) _template['source'] = os.path.relpath(new_fp, template_info['local_path']) if 'overlays' in _template: for i in range(len(_template['overlays'])): diff --git a/util/ffmpeg.py b/util/ffmpeg.py index c6765d4..48edfd7 100644 --- a/util/ffmpeg.py +++ b/util/ffmpeg.py @@ -21,6 +21,19 @@ def to_annexb(file): else: return file +def re_encode_and_annexb(file): + if not os.path.exists(file): + return file + logger.info("ReEncodeAndAnnexb: %s", file) + ffmpeg_process = subprocess.run(["ffmpeg", "-y", "-hide_banner", "-i", file, "-c:v", "h264_qsv", "-global_quality", "28", "-look_ahead", "1", "-bsf:v", "h264_mp4toannexb", + "-f", "mpegts", file+".ts"]) + logger.info("ReEncodeAndAnnexb: %s, returned: %s", file, ffmpeg_process.returncode) + if ffmpeg_process.returncode == 0: + os.remove(file) + return file+".ts" + else: + return file + def start_render(ffmpeg_task: FfmpegTask): logger.info(ffmpeg_task) if not ffmpeg_task.need_run(): @@ -64,7 +77,7 @@ def duration_str_to_float(duration_str: str) -> float: def probe_video_info(video_file): # 获取宽度和高度 result = subprocess.run( - ["ffprobe.exe", '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height:format=duration', '-of', + ["ffprobe", '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height:format=duration', '-of', 'csv=s=x:p=0', video_file], stderr=subprocess.STDOUT, **subprocess_args(True) From 549ee8320a04d7f9c7b3d0838ba9b547d5c868d2 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 22 Jan 2025 14:47:31 +0800 Subject: [PATCH 2/9] =?UTF-8?q?ffprobe=20=E6=8A=A5=E9=94=99=E5=90=8E?= =?UTF-8?q?=E4=B8=8D=E9=87=87=E7=94=A8=E5=85=B6=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- util/api.py | 2 +- util/ffmpeg.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/util/api.py b/util/api.py index 46c698e..59206dd 100644 --- a/util/api.py +++ b/util/api.py @@ -73,7 +73,7 @@ def get_template_info(template_id): 'scenic_name': remote_template_info.get('scenicName', '景区'), 'name': remote_template_info.get('name', '模版'), 'video_size': '1920x1080', - 'frame_rate': 30, + 'frame_rate': 25, 'overall_duration': 30, 'video_parts': [ diff --git a/util/ffmpeg.py b/util/ffmpeg.py index 48edfd7..0f878d0 100644 --- a/util/ffmpeg.py +++ b/util/ffmpeg.py @@ -82,6 +82,8 @@ def probe_video_info(video_file): stderr=subprocess.STDOUT, **subprocess_args(True) ) + if result.returncode != 0: + return 0, 0, 0 all_result = result.stdout.decode('utf-8').strip() wh, duration = all_result.split('\n') width, height = wh.strip().split('x') From 6d9d3730328fbdaa9efb930f680f4246fb662df4 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 23 Jan 2025 14:28:51 +0800 Subject: [PATCH 3/9] =?UTF-8?q?only=20if=20=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/ffmpeg.py | 16 ++++++++++++++++ util/api.py | 3 +++ 2 files changed, 19 insertions(+) diff --git a/biz/ffmpeg.py b/biz/ffmpeg.py index 927637c..d0868ea 100644 --- a/biz/ffmpeg.py +++ b/biz/ffmpeg.py @@ -20,6 +20,11 @@ def parse_ffmpeg_task(task_info, template_info): if not source: logger.warning("no video found for part: " + str(part)) continue + only_if = part.get('only_if', '') + if only_if: + if not check_placeholder_exist(only_if, task_params): + logger.info("because only_if exist, placeholder: %s not exist, skip part: %s", only_if, part) + continue sub_ffmpeg_task = FfmpegTask(source) sub_ffmpeg_task.annexb = True sub_ffmpeg_task.frame_rate = template_info.get("frame_rate", 25) @@ -66,6 +71,17 @@ def parse_video(source, task_params, template_info): return os.path.join(template_info.get("local_path"), source) +def check_placeholder_exist(placeholder_id, task_params): + if placeholder_id in task_params: + new_sources = task_params.get(placeholder_id, []) + if type(new_sources) is list: + if len(new_sources) == 0: + return False + else: + return True + return True + return False + def start_ffmpeg_task(ffmpeg_task): for task in ffmpeg_task.analyze_input_render_tasks(): start_ffmpeg_task(task) diff --git a/util/api.py b/util/api.py index 59206dd..80ad92d 100644 --- a/util/api.py +++ b/util/api.py @@ -101,6 +101,9 @@ def get_template_info(template_id): _luts = template_info.get('luts', '') if _luts: _template['luts'] = _luts.split(",") + _only_if = template_info.get('onlyIf', '') + if _only_if: + _template['only_if'] = _only_if return _template # outer template definition From b7d679790128349d588b759a5cf2d3884b39c327 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 5 Feb 2025 10:13:33 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E5=BF=BD=E7=95=A5=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index c2044f4..75e0eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ __pycache__/ *.so .Python build/ +dist/ +*.mp4 +*.ts +tmp_concat_*.txt *.egg-info/ *.egg *.manifest From 94a5e687df5db1282c174975c915acdcb9ff7bba Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sat, 8 Feb 2025 15:02:36 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E6=9C=AA=E7=94=9F=E6=88=90=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=97=B6=EF=BC=8C=E4=B8=8A=E6=8A=A5=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + biz/ffmpeg.py | 4 +++- util/api.py | 2 +- util/ffmpeg.py | 13 ++++++++++++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 75e0eb4..6054df3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ build/ dist/ *.mp4 *.ts +rand*.ts tmp_concat_*.txt *.egg-info/ *.egg diff --git a/biz/ffmpeg.py b/biz/ffmpeg.py index d0868ea..047db41 100644 --- a/biz/ffmpeg.py +++ b/biz/ffmpeg.py @@ -84,7 +84,9 @@ def check_placeholder_exist(placeholder_id, task_params): def start_ffmpeg_task(ffmpeg_task): for task in ffmpeg_task.analyze_input_render_tasks(): - start_ffmpeg_task(task) + result = start_ffmpeg_task(task) + if not result: + return False ffmpeg_task.correct_task_type() return ffmpeg.start_render(ffmpeg_task) diff --git a/util/api.py b/util/api.py index 80ad92d..8a55a93 100644 --- a/util/api.py +++ b/util/api.py @@ -168,7 +168,7 @@ def upload_task_file(task_info, ffmpeg_task): logger.info("开始上传文件: %s 至 %s", task_info.get("id"), url) try: with open(ffmpeg_task.get_output_file(), 'rb') as f: - requests.put(url, data=f) + requests.put(url, data=f, headers={"Content-Type": "video/mp4"}) except requests.RequestException as e: logger.error("上传失败!", e) return False diff --git a/util/ffmpeg.py b/util/ffmpeg.py index 0f878d0..ff68a8b 100644 --- a/util/ffmpeg.py +++ b/util/ffmpeg.py @@ -47,7 +47,18 @@ def start_render(ffmpeg_task: FfmpegTask): ffmpeg_process = subprocess.run(["ffmpeg", "-progress", "-", "-loglevel", "error", *ffmpeg_args], **subprocess_args(True)) logger.info("FINISH TASK, OUTPUT IS %s", handle_ffmpeg_output(ffmpeg_process.stdout)) code = ffmpeg_process.returncode - return code == 0 + if code != 0: + logger.error("FFMPEG ERROR: %s", ffmpeg_process.stderr) + return False + try: + out_file_stat = os.stat(ffmpeg_task.output_file) + if out_file_stat.st_size < 4096: + logger.error("FFMPEG ERROR: OUTPUT FILE IS TOO SMALL") + return False + except OSError: + logger.error("FFMPEG ERROR: OUTPUT FILE NOT FOUND") + return False + return True def handle_ffmpeg_output(stdout: Optional[bytes]) -> str: out_time = "0:0:0.0" From 358207efdc54afb64bd68500741064d51464cfb0 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sun, 16 Feb 2025 18:15:25 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E6=B8=85=E7=90=86?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=B8=8B=E6=97=A0=E7=94=A8=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/index.py b/index.py index 09197b7..84e4958 100644 --- a/index.py +++ b/index.py @@ -5,15 +5,36 @@ import config from template import load_local_template from util import api -load_local_template() +import os +import glob +load_local_template() +import logging + +LOGGER = logging.getLogger(__name__) while True: # print(get_sys_info()) print("waiting for task...") - task_list = api.sync_center() + try: + task_list = api.sync_center() + except Exception as e: + LOGGER.error("sync_center error", exc_info=e) + sleep(5) + continue if len(task_list) == 0: + # 删除当前文件夹下所有以.mp4、.ts结尾的文件 + for file_globs in ['*.mp4', '*.ts', 'tmp_concat*.txt']: + for file_path in glob.glob(file_globs): + try: + os.remove(file_path) + print(f"Deleted file: {file_path}") + except Exception as e: + LOGGER.error(f"Error deleting file {file_path}", exc_info=e) sleep(5) for task in task_list: print("start task:", task) - biz.task.start_task(task) + try: + biz.task.start_task(task) + except Exception as e: + LOGGER.error("task_start error", exc_info=e) From 2ea248c02e5ad0fb2ed43a3f40a66ffa89a8f431 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sun, 16 Feb 2025 18:15:35 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B9=9F=E5=BC=84=E4=B8=AA=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- util/oss.py | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/util/oss.py b/util/oss.py index 140dba1..e41f6d1 100644 --- a/util/oss.py +++ b/util/oss.py @@ -13,13 +13,22 @@ def upload_to_oss(url, file_path): :param str file_path: 文件路径 :return bool: 是否成功 """ - with open(file_path, 'rb') as f: + max_retries = 5 + retries = 0 + while retries < max_retries: try: - response = requests.put(url, data=f) - return response.status_code == 200 + with open(file_path, 'rb') as f: + response = requests.put(url, data=f, timeout=60) # 设置超时时间为1分钟 + if response.status_code == 200: + return True + except requests.exceptions.Timeout: + retries += 1 + logger.warning(f"Upload timed out. Retrying {retries}/{max_retries}...") except Exception as e: - print(e) - return False + logger.warning(f"Upload failed. Retrying {retries}/{max_retries}...") + retries += 1 + return False + def download_from_oss(url, file_path): """ @@ -33,11 +42,18 @@ def download_from_oss(url, file_path): if file_dir: if not os.path.exists(file_dir): os.makedirs(file_dir) - try: - response = requests.get(url) - with open(file_path, 'wb') as f: - f.write(response.content) - return True - except Exception as e: - print(e) - return False + max_retries = 5 + retries = 0 + while retries < max_retries: + try: + response = requests.get(url, timeout=15) # 设置超时时间 + with open(file_path, 'wb') as f: + f.write(response.content) + return True + except requests.exceptions.Timeout: + retries += 1 + logger.warning(f"Download timed out. Retrying {retries}/{max_retries}...") + except Exception as e: + logger.warning(f"Download failed. Retrying {retries}/{max_retries}...") + retries += 1 + return False From 67696739f9f172dd786bcc1f655f5f7ff2e3a607 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 27 Feb 2025 14:02:17 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E5=88=87=E5=89=B2=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/ffmpeg.py | 1 + entity/ffmpeg.py | 20 ++++++++++++++++++-- util/api.py | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/biz/ffmpeg.py b/biz/ffmpeg.py index 047db41..b58c86a 100644 --- a/biz/ffmpeg.py +++ b/biz/ffmpeg.py @@ -38,6 +38,7 @@ def parse_ffmpeg_task(task_info, template_info): output_file = "out_" + str(time.time()) + ".mp4" task = FfmpegTask(tasks, output_file=output_file) overall = template_info.get("overall_template") + task.center_cut = template_info.get("crop_mode", None) task.frame_rate = template_info.get("frame_rate", 25) if overall.get('source', ''): source = parse_video(overall.get('source'), task_params, template_info) diff --git a/entity/ffmpeg.py b/entity/ffmpeg.py index 4d22331..61a7879 100644 --- a/entity/ffmpeg.py +++ b/entity/ffmpeg.py @@ -14,6 +14,9 @@ class FfmpegTask(object): self.input_file = input_file else: self.input_file = [] + self.zoom_cut = None + self.center_cut = None + self.ext_data = {} self.task_type = task_type self.output_file = output_file self.mute = True @@ -101,6 +104,10 @@ class FfmpegTask(object): return False if self.speed != 1: return False + if self.zoom_cut is not None: + return False + if self.center_cut is not None: + return False return True def check_can_copy(self): @@ -116,6 +123,10 @@ class FfmpegTask(object): return False if len(self.input_file) > 1: return False + if self.zoom_cut is not None: + return False + if self.center_cut is not None: + return False return True def check_audio_track(self): @@ -134,7 +145,6 @@ class FfmpegTask(object): output_args.append("h264_mp4toannexb") video_output_str = "[0:v]" audio_output_str = "[0:v]" - video_input_count = 0 audio_input_count = 0 for input_file in self.input_file: input_args.append("-i") @@ -142,8 +152,14 @@ class FfmpegTask(object): input_args.append(input_file) elif isinstance(input_file, FfmpegTask): input_args.append(input_file.get_output_file()) + if self.center_cut == 1: + pos_json = self.ext_data.get('posJson', {}) + _v_w = pos_json.get('imgWidth', 1) + _f_x = pos_json.get('ltX', 0) + _x = f'{float(_f_x/_v_w) :.5f}*iw' + filter_args.append(f"[{video_output_str}]crop=x={_x}:y=0:w=ih*ih/iw:h=ih[{video_output_str}]") for lut in self.luts: - filter_args.append("[0:v]lut3d=file=" + lut + "[0:v]") + filter_args.append(f"[{video_output_str}]lut3d=file={lut}[{video_output_str}]") for overlay in self.overlays: input_index = input_args.count("-i") input_args.append("-i") diff --git a/util/api.py b/util/api.py index 8a55a93..f264490 100644 --- a/util/api.py +++ b/util/api.py @@ -90,6 +90,7 @@ def get_template_info(template_id): # 占位符 _template['source'] = "PLACEHOLDER_" + template_info.get('sourceUrl', '') _template['mute'] = template_info.get('mute', True) + _template['crop_mode'] = template_info.get('cropEnable', None) else: _template['source'] = None _overlays = template_info.get('overlays', '') From fff20610a5200342ba0bc22126541fe996ed770d Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 27 Feb 2025 15:36:53 +0800 Subject: [PATCH 9/9] =?UTF-8?q?profile=20level=E6=8C=87=E5=AE=9A=E5=8F=8A?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entity/ffmpeg.py | 10 +++++++--- util/ffmpeg.py | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/entity/ffmpeg.py b/entity/ffmpeg.py index 61a7879..1328348 100644 --- a/entity/ffmpeg.py +++ b/entity/ffmpeg.py @@ -1,6 +1,9 @@ import time import uuid +ENCODER_ARGS = ("-c:v", "h264_qsv", "-global_quality", "28", "-look_ahead", "1",) +PROFILE_LEVEL_ARGS = ("-profile:v", "high", "-level:v", "4") + class FfmpegTask(object): @@ -136,13 +139,14 @@ class FfmpegTask(object): def get_ffmpeg_args(self): args = ['-y', '-hide_banner'] if self.task_type == 'encode': - # args += ('-hwaccel', 'qsv', '-hwaccel_output_format', 'qsv') input_args = [] filter_args = [] - output_args = ["-shortest", "-c:v", "h264_qsv", "-global_quality", "28", "-look_ahead", "1"] + output_args = ["-profile", "high", "-level", "4","-shortest", *ENCODER_ARGS] if self.annexb: output_args.append("-bsf:v") output_args.append("h264_mp4toannexb") + output_args.append("-reset_timestamps") + output_args.append("1") video_output_str = "[0:v]" audio_output_str = "[0:v]" audio_input_count = 0 @@ -219,7 +223,7 @@ class FfmpegTask(object): output_args.append("-f") output_args.append("mp4") return args + input_args + output_args + [self.get_output_file()] - output_args += ("-c:v", "h264_qsv", "-r", "25", "-global_quality", "28", "-look_ahead", "1") + output_args += ["-r", f"{self.frame_rate}", *PROFILE_LEVEL_ARGS, *ENCODER_ARGS] filter_args = [] video_output_str = "[0:v]" audio_output_str = "[0:a]" diff --git a/util/ffmpeg.py b/util/ffmpeg.py index ff68a8b..f572f78 100644 --- a/util/ffmpeg.py +++ b/util/ffmpeg.py @@ -4,7 +4,7 @@ import subprocess from datetime import datetime from typing import Optional, IO -from entity.ffmpeg import FfmpegTask +from entity.ffmpeg import FfmpegTask, ENCODER_ARGS, PROFILE_LEVEL_ARGS logger = logging.getLogger(__name__) @@ -25,8 +25,8 @@ def re_encode_and_annexb(file): if not os.path.exists(file): return file logger.info("ReEncodeAndAnnexb: %s", file) - ffmpeg_process = subprocess.run(["ffmpeg", "-y", "-hide_banner", "-i", file, "-c:v", "h264_qsv", "-global_quality", "28", "-look_ahead", "1", "-bsf:v", "h264_mp4toannexb", - "-f", "mpegts", file+".ts"]) + ffmpeg_process = subprocess.run(["ffmpeg", "-y", "-hide_banner", "-i", file, *PROFILE_LEVEL_ARGS, *ENCODER_ARGS, "-bsf:v", "h264_mp4toannexb", + "-f", "mpegts", file +".ts"]) logger.info("ReEncodeAndAnnexb: %s, returned: %s", file, ffmpeg_process.returncode) if ffmpeg_process.returncode == 0: os.remove(file)