From 52ad992c3c1dbaa009c5b1c52e057b205bea01e9 Mon Sep 17 00:00:00 2001 From: poususer <852281867@qq.com> Date: Thu, 27 Mar 2025 08:52:57 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=96=B0=E5=A2=9E=E5=B8=B8=E8=A7=84=E8=B4=A8?= =?UTF-8?q?=E9=87=8F=E3=80=81=E9=AB=98=E8=B4=A8=E9=87=8F=E3=80=81=E6=97=A0?= =?UTF-8?q?=E6=8D=9Fffmpeg=E9=A2=84=E8=AE=BE=E5=8F=82=E6=95=B0=E3=80=822.?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=A0=B9=E6=8D=AE-f=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E8=BE=93=E5=87=BA=E8=A7=86=E9=A2=91=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8A=9F=E8=83=BD=E3=80=823.=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=9F=B3=E9=A2=91copy=E5=A4=B1=E6=95=88bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/config.py | 53 ++++++++++++++++++++------ backend/main.py | 96 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 122 insertions(+), 27 deletions(-) diff --git a/backend/config.py b/backend/config.py index 10b55c6..1ea853b 100644 --- a/backend/config.py +++ b/backend/config.py @@ -132,19 +132,48 @@ LAMA_SUPER_FAST = False # ×××××××××× InpaintMode.LAMA算法设置 end ×××××××××× # ×××××××××× FFmpeg参数设置 start ×××××××××× +# 以下参数都是FFmpeg的参数,不懂的话直接用预设即可 # 自定义 FFmpeg 音频参数,可以根据需要自行修改 -FFMPEG_AUDIO_PARAMS = "-c:a copy " # 指定音频编码参数 +FFMPEG_AUDIO_PARAMS = [ + "-c:a", "copy" +] # 自定义 FFmpeg 视频参数,可以根据需要自行修改 -FFMPEG_VIDEO_PARAMS = ( - "-f mp4 " # 指定输出格式 - "-c:v libx264 " # 指定视频编码器 - "-crf 17 " # 视频质量 - "-profile:v high " # 编码规格 - "-preset veryslow " # 编码速度 - "-sn " # 禁用字幕流 - # "-map_metadata -1 " # 删除元数据 - # "-map_chapters -1 " # 删除章节信息 - "-pix_fmt yuv420p" # 指定像素格式,不能修改 -) +# #常规质量预设 +# FFMPEG_FORMAT_PARAMS = [ "-f" , "mp4" ] # 指定输出格式 +# FFMPEG_VIDEO_PARAMS = [ +# "-c:v", "libx264", # 指定视频编码器 +# "-crf", "22", # 视频质量 +# "-profile:v", "high", # 编码规格 +# "-preset", "medium", # 编码速度 +# "-sn", # 禁用字幕流 + # "-map_metadata", "0", # 保留元数据 + # "-map_chapters", "0", # 保留章节信息 +# "-pix_fmt", "yuv420p" # 指定像素格式 +# ] +#高质量预设 +FFMPEG_FORMAT_PARAMS = [ "-f" , "mp4" ] # 指定输出格式 +FFMPEG_VIDEO_PARAMS = [ + "-c:v", "libx264", # 指定视频编码器 + "-crf", "17", # 视频质量 + "-profile:v", "high", # 编码规格 + "-preset", "veryslow", # 编码速度 + "-sn", # 禁用字幕流 + "-map_metadata", "0", # 保留元数据 + "-map_chapters", "0", # 保留章节信息 + "-pix_fmt", "yuv420p" # 指定像素格式 +] +#无损预设 +# FFMPEG_FORMAT_PARAMS = [ "-f" , "matroska" ] # 指定输出格式 +# FFMPEG_VIDEO_PARAMS = [ +# "-c:v", "libx264", # 指定视频编码器 +# "-crf", "0", # 视频质量 +# "-qp", "0", +# "-profile:v", "high444", # 编码规格 +# "-preset", "veryslow", # 编码速度 +# "-sn", # 禁用字幕流 +# "-map_metadata", "0", # 保留元数据 +# "-map_chapters", "0", # 保留章节信息 +# "-pix_fmt", "yuv444p" # 指定像素格式 +# ] # ×××××××××× FFmpeg参数设置 end ×××××××××× # ×××××××××××××××××××× [可以改] end ×××××××××××××××××××× diff --git a/backend/main.py b/backend/main.py index f9e4b7c..2a781ef 100644 --- a/backend/main.py +++ b/backend/main.py @@ -21,6 +21,8 @@ import importlib import platform import tempfile import multiprocessing +import json +import re from shapely.geometry import Polygon import time from tqdm import tqdm @@ -35,19 +37,18 @@ class FFmpegVideoWriter: self.fps = fps self.output_file = output_file ffmpeg_cmd = [ - "ffmpeg", - "-y", # 覆盖输出文件 + "ffmpeg", "-y", # 覆盖输出文件 "-f", "rawvideo", # 输入为原始视频流 "-pixel_format", "bgr24", # OpenCV 默认格式 bgr24 "-video_size", f"{size[0]}x{size[1]}", # 视频尺寸:宽x高 "-framerate", str(fps), # 帧率 "-i", "-" # 从标准输入读取数据 ] - # 添加自定义参数,拆分成列表 - ffmpeg_cmd.extend(ffmpeg_params.split()) + # 添加自定义参数 + ffmpeg_cmd.extend(ffmpeg_params) # 最后追加输出文件名 ffmpeg_cmd.append(output_file) - print("FFmpeg参数:", " ".join(ffmpeg_cmd)) + # print("FFmpeg参数:", " ".join(ffmpeg_cmd)) self.process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE) def write(self, frame): @@ -61,6 +62,69 @@ class FFmpegVideoWriter: self.process.stdin.close() self.process.wait() self.process = None + +def get_audio_suffix(input_file, audio_codec): + """ + 根据-c:a的音频编码器动态获取后缀 + """ + default_suffix = "aac" # 默认后缀 + try: + if audio_codec == "copy": + # 使用 ffprobe 检查输入文件音频格式 + result = subprocess.run( + ["ffprobe", "-i", input_file, "-show_streams", "-select_streams", "a", "-loglevel", "quiet"], + capture_output=True, text=True, check=True + ) + match = re.search(r"codec_name=([^\s]+)", result.stdout) + if match: + codec = match.group(1) + return f".{codec}" # 返回音频流格式后缀 + else: + print("无法提取音频编码信息,使用默认后缀") + return f".{default_suffix}" + else: + # 非 copy 模式时,直接使用编码器名作为后缀 + return f".{audio_codec}" if audio_codec else f".{default_suffix}" + except subprocess.CalledProcessError as e: + print(f"命令失败,使用默认后缀 {default_suffix}: {e}") + return f".{default_suffix}" + +def get_video_suffix(input_file, format_name): + """ + 根据 -f 参数动态获取视频文件后缀。 + """ + default_suffix = "mp4" # 默认后缀 + try: + if format_name == "copy": + # 使用 ffprobe 检查输入文件的视频流编码器类型 + result = subprocess.run( + ["ffprobe", "-i", input_file, "-show_streams", "-select_streams", "v", "-loglevel", "quiet"], + capture_output=True, text=True, check=True + ) + # 提取 codec_name 信息 + match = re.search(r"codec_name=([^\s]+)", result.stdout) + if match: + codec = match.group(1) + return f".{codec}" # 返回视频流的实际编码器作为后缀 + else: + print(f"无法提取视频编码信息,使用默认后缀 {default_suffix}") + return f".{default_suffix}" + else: + # 非 copy 模式,根据容器格式动态获取后缀 + result = subprocess.run( + ["ffmpeg", "-h", f"muxer={format_name}"], + capture_output=True, text=True, check=True + ) + match = re.search(r"Common extensions: (\S+)", result.stdout) + if match: + extensions = match.group(1).split(",") + # 去掉后缀中可能的多余点(".mp4." -> "mp4") + first_extension = extensions[0].strip(".") + return f".{first_extension}" if first_extension else f".{default_suffix}" + except subprocess.CalledProcessError as e: + print(f"FFmpeg 命令失败,使用默认后缀 {default_suffix}: {e}") + return f".{default_suffix}" + class SubtitleDetect: """ 文本框检测类,用于检测视频帧中是否存在文本框 @@ -539,8 +603,6 @@ class SubtitleDetect: new_box_list.append(current_box) correct_subtitle_frame_no_box_dict[frame_no] = new_box_list return correct_subtitle_frame_no_box_dict - - class SubtitleRemover: def __init__(self, vd_path, sub_area=None, gui_mode=False): importlib.reload(config) @@ -572,12 +634,17 @@ class SubtitleRemover: # 创建字幕检测对象 self.sub_detector = SubtitleDetect(self.video_path, self.sub_area) # 创建视频临时对象,windows下delete=True会有permission denied的报错 - self.video_temp_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) + # 根据 FFMPEG 格式参数动态获取后缀 + self.video_suffix = get_video_suffix(self.video_path, config.FFMPEG_FORMAT_PARAMS[1].strip()) + self.audio_suffix = get_audio_suffix(self.video_path, config.FFMPEG_AUDIO_PARAMS[1].strip()) + print(self.video_suffix) + print(self.audio_suffix) + self.video_temp_file = tempfile.NamedTemporaryFile(suffix=self.video_suffix, delete=False) # 创建视频写对象 # self.video_writer = cv2.VideoWriter(self.video_temp_file.name, cv2.VideoWriter_fourcc(*'mp4v'), self.fps, self.size) # 使用FFmpeg生成视频 self.video_writer = FFmpegVideoWriter(self.size, self.fps, self.video_temp_file.name) - self.video_out_name = os.path.join(os.path.dirname(self.video_path), f'{self.vd_name}_no_sub.mp4') + self.video_out_name = os.path.join(os.path.dirname(self.video_path), f'{self.vd_name}_no_sub{self.video_suffix}') self.video_inpaint = None self.lama_inpaint = None self.ext = os.path.splitext(vd_path)[-1] @@ -900,7 +967,7 @@ class SubtitleRemover: def merge_audio_to_video(self): # 创建音频临时对象,windows下delete=True会有permission denied的报错 - temp = tempfile.NamedTemporaryFile(suffix='.aac', delete=False) + temp = tempfile.NamedTemporaryFile(suffix=self.audio_suffix, delete=False) audio_extract_command = [config.FFMPEG_PATH, "-y", "-i", self.video_path, "-acodec", "copy", @@ -919,11 +986,10 @@ class SubtitleRemover: "-vcodec", "copy", # "-vcodec", "libx264" if config.USE_H264 else "copy", # "-acodec", "copy", - "-loglevel", "error", self.video_out_name] - # 新增自定义音频参数 - audio_params = config.FFMPEG_AUDIO_PARAMS.replace("\n", " ").split() - # 将音频参数追加到合并命令中 - audio_merge_command.extend(audio_params) +] + # 将自定义音频参数追加到合并命令中 + audio_merge_command.extend(config.FFMPEG_AUDIO_PARAMS) + audio_merge_command.extend(["-loglevel", "error", self.video_out_name]) try: subprocess.check_output(audio_merge_command, stdin=open(os.devnull), shell=use_shell) except Exception: