From 9139727dc6b0526ded25273b80af5fbdff79c89c Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Nov 2024 16:47:39 +0800 Subject: [PATCH] basic cpu encode support --- biz/ffmpeg.py | 42 ++++++++++--------- entity/ffmpeg.py | 107 ++++++++++++++++++++++++++++++++++++++++++++++- util/ffmpeg.py | 10 ++++- 3 files changed, 136 insertions(+), 23 deletions(-) diff --git a/biz/ffmpeg.py b/biz/ffmpeg.py index 04c4235..ffebb4f 100644 --- a/biz/ffmpeg.py +++ b/biz/ffmpeg.py @@ -1,3 +1,5 @@ +import os.path + from entity.ffmpeg import FfmpegTask import logging @@ -10,32 +12,32 @@ def parse_ffmpeg_task(task_info, template_info): tasks = [] # 中间片段 for part in template_info.get("video_parts"): - sub_ffmpeg_task = parse_video_part(part, task_info) - if not sub_ffmpeg_task: + source = select_video_if_needed(part.get('source'), task_info, template_info) + if not source: + logger.warning("no video found for part: " + str(part)) continue + sub_ffmpeg_task = FfmpegTask(source) + sub_ffmpeg_task.frame_rate = template_info.get("frame_rate", 25) + for lut in part.get('filters', []): + sub_ffmpeg_task.add_lut(os.path.join(template_info.get("local_path"), lut)) + for audio in part.get('audios', []): + sub_ffmpeg_task.add_audios(os.path.join(template_info.get("local_path"), audio)) + for overlay in part.get('overlays', []): + sub_ffmpeg_task.add_overlay(os.path.join(template_info.get("local_path"), overlay)) tasks.append(sub_ffmpeg_task) task = FfmpegTask(tasks, output_file="test.mp4") - task.correct_task_type() overall = template_info.get("overall_template") - task.add_lut(*overall.get('luts', [])) - task.add_audios(*overall.get('audios', [])) - task.add_overlay(*overall.get('overlays', [])) + task.frame_rate = template_info.get("frame_rate", 25) + for lut in overall.get('filters', []): + task.add_lut(os.path.join(template_info.get("local_path"), lut)) + for audio in overall.get('audios', []): + task.add_audios(os.path.join(template_info.get("local_path"), audio)) + for overlay in overall.get('overlays', []): + task.add_overlay(os.path.join(template_info.get("local_path"), overlay)) return task -def parse_video_part(video_part, task_info): - source = select_video_if_needed(video_part.get('source'), task_info) - if not source: - logger.warning("no video found for part: " + str(video_part)) - return None - task = FfmpegTask(source) - task.add_lut(*video_part.get('luts', [])) - task.add_audios(*video_part.get('audios', [])) - task.add_overlay(*video_part.get('overlays', [])) - return task - - -def select_video_if_needed(source, task_info): +def select_video_if_needed(source, task_info, template_info): if source.startswith('PLACEHOLDER_'): placeholder_id = source.replace('PLACEHOLDER_', '') new_sources = task_info.get('user_videos', {}).get(placeholder_id, []) @@ -47,7 +49,7 @@ def select_video_if_needed(source, task_info): # TODO: Random Pick / Policy Pick new_sources = new_sources[0] return new_sources - return source + return os.path.join(template_info.get("local_path"), source) def start_ffmpeg_task(ffmpeg_task): diff --git a/entity/ffmpeg.py b/entity/ffmpeg.py index d5383fd..9419a3b 100644 --- a/entity/ffmpeg.py +++ b/entity/ffmpeg.py @@ -12,6 +12,7 @@ class FfmpegTask(object): self.output_file = output_file self.mute = True self.speed = 1 + self.frame_rate = 25 self.subtitles = [] self.luts = [] self.audios = [] @@ -36,7 +37,7 @@ class FfmpegTask(object): for i in self.input_file: if type(i) is str: continue - elif type(i) is FfmpegTask: + elif isinstance(i, FfmpegTask): if i.need_run(): yield i @@ -69,7 +70,9 @@ class FfmpegTask(object): def get_output_file(self): if self.task_type == 'copy': - return self.input_file + return self.input_file[0] + if self.output_file == '': + self.set_output_file() return self.output_file def correct_task_type(self): @@ -109,3 +112,103 @@ class FfmpegTask(object): def check_audio_track(self): if len(self.audios) > 0: self.mute = False + + def get_ffmpeg_args(self): + args = ['-y', '-hide_banner'] + video_output_str = "[0:v]" + if self.task_type == 'encode': + input_args = [] + filter_args = [] + output_args = [] + 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") + if type(input_file) is str: + input_args.append(input_file) + elif isinstance(input_file, FfmpegTask): + input_args.append(input_file.get_output_file()) + for lut in self.luts: + filter_args.append("[0:v]lut3d=file=" + lut + "[0:v]") + for overlay in self.overlays: + input_index = input_args.count("-i") + input_args.append("-i") + input_args.append(overlay) + filter_args.append(f"{video_output_str}[{input_index}:v]scale=rw:rh[v]") + filter_args.append(f"[v][{input_index}:v]overlay=1:eof_action=endall[v]") + video_output_str = "[v]" + output_args.append("-map") + output_args.append(video_output_str) + output_args.append("-r") + output_args.append(f"{self.frame_rate}") + if self.mute: + output_args.append("-an") + else: + input_index = 0 + for audio in self.audios: + input_index = input_args.count("-i") + input_args.append("-i") + input_args.append(audio.replace("\\", "/")) + if audio_input_count > 0: + filter_args.append(f"{audio_output_str}[{input_index}:a]amix[a]") + audio_output_str = "[a]" + else: + audio_output_str = f"[{input_index}:a]" + audio_input_count += 1 + if audio_input_count == 1: + audio_output_str = f"{input_index}" + output_args.append(f"-map") + output_args.append(audio_output_str) + return args + input_args + ["-filter_complex", ";".join(filter_args)] + output_args + [self.get_output_file()] + elif self.task_type == 'concat': + input_args = [] + output_args = [] + filter_args = [] + 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_index = input_args.count("-i") + input_args.append("-i") + if type(input_file) is str: + input_args.append(input_file.replace("\\", "/")) + elif isinstance(input_file, FfmpegTask): + input_args.append(input_file.get_output_file().replace("\\", "/")) + if video_input_count > 0: + filter_args.append(f"{video_output_str}[{input_index}:v]concat=n=2:v=1:a=0[v]") + video_output_str = "[v]" + else: + video_output_str = f"[{input_index}:v]" + video_input_count += 1 + output_args.append("-map") + output_args.append(video_output_str) + if self.mute: + output_args.append("-an") + else: + input_index = 0 + for audio in self.audios: + input_index = input_args.count("-i") + input_args.append("-i") + input_args.append(audio.replace("\\", "/")) + if audio_input_count > 0: + filter_args.append(f"{audio_output_str}[{input_index}:a]amix[a]") + audio_output_str = "[a]" + else: + audio_output_str = f"[{input_index}:a]" + audio_input_count += 1 + if audio_input_count == 1: + audio_output_str = f"{input_index}" + output_args.append(f"-map") + output_args.append(audio_output_str) + return args + input_args + ["-filter_complex", ";".join(filter_args)] + output_args + [self.get_output_file()] + + def set_output_file(self, file=None): + if file is None: + if self.output_file == '': + # TODO: Random Filename + self.output_file = "rand.mp4" + else: + self.output_file = file \ No newline at end of file diff --git a/util/ffmpeg.py b/util/ffmpeg.py index e377b7a..678c164 100644 --- a/util/ffmpeg.py +++ b/util/ffmpeg.py @@ -1,3 +1,5 @@ +import os +from datetime import datetime from typing import Optional, IO from entity.ffmpeg import FfmpegTask @@ -5,6 +7,8 @@ from entity.ffmpeg import FfmpegTask def start_render(ffmpeg_task: FfmpegTask): print(ffmpeg_task) + print(ffmpeg_task.get_ffmpeg_args()) + os.system("ffmpeg.exe "+" ".join(ffmpeg_task.get_ffmpeg_args())) def handle_ffmpeg_output(stdout: Optional[IO[bytes]]) -> str: out_time = "0:0:0.0" @@ -24,4 +28,8 @@ def handle_ffmpeg_output(stdout: Optional[IO[bytes]]) -> str: if line.startswith(b"speed="): speed = line.replace(b"speed=", b"").decode().strip() print("[ ]Speed:", out_time, "@", speed) - return out_time \ No newline at end of file + return out_time + +def duration_str_to_float(duration_str: str) -> float: + _duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1) + return _duration.total_seconds()