一、介绍
FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用 LGPL 或 GPL 许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库 libavcodec,为了保证高可移植性和编解码质量,libavcodec 里很多 code 都是从头开发的。
二、部署过程
基础环境最低要求说明:
| 环境名称 | 版本信息 1 |
|---|---|
| Ubuntu | 22.04.4 LTS |
| Cuda | V12.8 |
| Python | 3.12 |
| NVIDIA Corporation | RTX 4090 |
1. 更新基础软件包
查看系统版本信息
# 查看系统版本信息,包括ID(如ubuntu、centos等)、版本号、名称、版本号ID等cat /etc/os-release配置 apt 国内源
# 更新软件包列表apt-get update这个命令用于更新本地软件包索引。它会从所有配置的源中检索最新的软件包列表信息,但不会安装或升级任何软件包。这是安装新软件包或进行软件包升级之前的推荐步骤,因为它确保了您获取的是最新版本的软件包。
# 安装 Vim 编辑器apt-get install -y vim这个命令用于安装 Vim 文本编辑器。-y 选项表示自动回答所有的提示为“是”,这样在安装过程中就不需要手动确认。Vim 是一个非常强大的文本编辑器,广泛用于编程和配置文件的编辑。
为了安全起见,先备份当前的 sources.list 文件之后,再进行修改:
# 备份现有的软件源列表cp /etc/apt/sources.list /etc/apt/sources.list.bak这个命令将当前的 sources.list 文件复制为一个名为 sources.list.bak 的备份文件。这是一个好习惯,因为编辑 sources.list 文件时可能会出错,导致无法安装或更新软件包。有了备份,如果出现问题,您可以轻松地恢复原始的文件。
# 编辑软件源列表文件vim /etc/apt/sources.list这个命令使用 Vim 编辑器打开 sources.list 文件,以便您可以编辑它。这个文件包含了 APT(Advanced Package Tool)用于安装和更新软件包的软件源列表。通过编辑这个文件,您可以添加新的软件源、更改现有软件源的优先级或禁用某些软件源。
在 Vim 中,您可以使用方向键来移动光标,i 键进入插入模式(可以开始编辑文本),Esc 键退出插入模式,:wq 命令保存更改并退出 Vim,或 :q! 命令不保存更改并退出 Vim。
编辑 sources.list 文件时,请确保您了解自己在做什么,特别是如果您正在添加新的软件源。错误的源可能会导致软件包安装失败或系统安全问题。如果您不确定,最好先搜索并找到可靠的源信息,或者咨询有经验的 Linux 用户。
使用 Vim 编辑器打开 sources.list 文件,复制以下代码替换 sources.list 里面的全部代码,配置 apt 国内阿里源。
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse安装常用软件和工具
# 更新源列表,输入以下命令:apt-get update# 更新系统软件包,输入以下命令:apt-get upgrade# 安装常用软件和工具,输入以下命令:apt-get -y install vim wget git git-lfs unzip lsof net-tools gcc cmake build-essential出现以下页面,说明国内 apt 源已替换成功,且能正常安装 apt 软件和工具
2. 安装 Miniconda
下载 Miniconda 安装脚本 :
- 使用
wget 命令从 Anaconda 的官方仓库下载 Miniconda 的安装脚本。Miniconda 是一个更小的 Anaconda 发行版,包含了 Anaconda 的核心组件,用于安装和管理 Python 包。运行 Miniconda 安装脚本 :
- 使用
bash 命令运行下载的 Miniconda 安装脚本。这将启动 Miniconda 的安装过程。# 下载 Miniconda 安装脚本wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh# 运行 Miniconda 安装脚本bash Miniconda3-latest-Linux-x86_64.sh# 初次安装需要激活 base 环境source ~/.bashrc按下回车键(enter)
输入 yes
输入 yes
安装成功如下图所示
pip 配置清华源加速
# 编辑 /etc/pip.conf 文件vim /etc/pip.conf加入以下代码
[global]index-url = https://pypi.tuna.tsinghua.edu.cn/simple注意事项:
- 请确保您的系统是 Linux x86_64 架构,因为下载的 Miniconda 版本是为该架构设计的。在运行安装脚本之前,您可能需要使用
chmod +x Miniconda3-latest-Linux-x86_64.sh 命令给予脚本执行权限。安装过程中,您将被提示是否同意许可协议,以及是否将 Miniconda 初始化。通常选择 “yes” 以完成安装和初始化。安装完成后,您可以使用 conda 命令来管理 Python 环境和包。如果链接无法访问或解析失败,可能是因为网络问题或链接本身的问题。请检查网络连接,并确保链接是最新的和有效的。如果问题依旧,请访问 Anaconda 的官方网站获取最新的下载链接。3. 安装FFmpeg
- 安装命令:
# 安装ffmpegsudo apt updatesudo apt install ffmpeg -y4. 创建虚拟环境
# 创建一个名为 gradio的新虚拟环境(名字可自定义),并指定 Python 版本为 3.12conda create -n gradio python=3.12 -y5. 安装gradio依赖库
- 切换到项目目录、激活 gradio虚拟环境、安装gradio依赖
# 激活 gradio 虚拟环境conda activate gradio# 在 gradio 环境中安装依赖pip install gradio numpy6. 编写并运行 ffmpeg_processor.py 文件
#编写ffmpeg_processor.pyvim ffmpeg_processor.py#以下代码片段是一个gradio网页的示例import gradio as grimport subprocessimport osimport uuidimport mimetypesimport jsonimport shleximport timefrom datetime import timedeltafrom pathlib import Path# 临时文件目录配置TEMP_DIR = "temp_files"os.makedirs(TEMP_DIR, exist_ok=True)CLEANUP_THRESHOLD = timedelta(hours=1) # 清理1小时前的临时文件# 本地文件目录配置LOCAL_FILES_DIR = "local_files"os.makedirs(LOCAL_FILES_DIR, exist_ok=True)# GPU支持检测def check_gpu_support(): """检测GPU支持情况""" try: # 检查NVIDIA GPU result = subprocess.run(['nvidia-smi'], capture_output=True, text=True, timeout=5) if result.returncode == 0: # 检查FFmpeg是否支持CUDA ffmpeg_result = subprocess.run(['ffmpeg', '-hwaccels'], capture_output=True, text=True, timeout=5) if 'cuda' in ffmpeg_result.stdout.lower(): # 检查编码器支持 encoders_result = subprocess.run(['ffmpeg', '-encoders'], capture_output=True, text=True, timeout=5) if 'h264_nvenc' in encoders_result.stdout.lower(): return 'nvidia' # 检查Intel GPU lspci_result = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5) if 'intel' in lspci_result.stdout.lower() or 'display' in lspci_result.stdout.lower(): ffmpeg_result = subprocess.run(['ffmpeg', '-hwaccels'], capture_output=True, text=True, timeout=5) if 'vaapi' in ffmpeg_result.stdout.lower(): return 'intel' except: pass return 'cpu'# 检测GPU类型GPU_TYPE = check_gpu_support()def clean_temp_files(): """清理过期的临时文件""" now = time.time() for filename in os.listdir(TEMP_DIR): file_path = os.path.join(TEMP_DIR, filename) file_time = os.path.getmtime(file_path) # 删除超过阈值的文件 if now - file_time > CLEANUP_THRESHOLD.total_seconds(): try: os.remove(file_path) except Exception as e: print(f"清理临时文件失败: {file_path}, 错误: {e}")def get_file_type(file_path): """获取文件类型(视频/音频/图片)""" if not file_path: return None mime_type, _ = mimetypes.guess_type(file_path) if mime_type: if mime_type.startswith('video'): return 'video' elif mime_type.startswith('audio'): return 'audio' elif mime_type.startswith('image'): return 'image' return 'unknown'def get_available_formats(file_type): """根据文件类型获取可用转换格式""" if file_type == 'video': return ["mp4", "avi", "mov", "mkv", "flv", "webm", "gif"] elif file_type == 'audio': return ["mp3", "wav", "flac", "aac", "ogg", "m4a"] elif file_type == 'image': return ["jpg", "jpeg", "png", "bmp", "webp", "gif"] return ["mp4", "avi", "mov", "mp3", "wav", "jpg", "png"] # 默认格式def get_gpu_encoding_params(codec='h264'): """获取GPU编码参数""" if GPU_TYPE == 'nvidia': if codec == 'h264': return ['-c:v', 'h264_nvenc'] elif codec == 'hevc': return ['-c:v', 'hevc_nvenc'] elif GPU_TYPE == 'intel': if codec == 'h264': return ['-c:v', 'h264_vaapi', '-vaapi_device', '/dev/dri/renderD128'] elif codec == 'hevc': return ['-c:v', 'hevc_vaapi', '-vaapi_device', '/dev/dri/renderD128'] return [] # CPU编码def run_ffmpeg_command(cmd_parts, input_file_path, output_ext, use_gpu=False, fallback_to_cpu=True): """执行FFmpeg命令并返回输出文件路径""" output_filename = f"{uuid.uuid4()}.{output_ext}" output_path = os.path.join(TEMP_DIR, output_filename) # 尝试GPU加速 if use_gpu and GPU_TYPE != 'cpu': # 构建GPU命令 gpu_command = ['ffmpeg', '-y'] # GPU解码支持 if GPU_TYPE == 'nvidia': gpu_command.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda']) elif GPU_TYPE == 'intel': gpu_command.extend(['-hwaccel', 'vaapi', '-hwaccel_device', '/dev/dri/renderD128']) gpu_command.extend(['-i', input_file_path]) gpu_command.extend(cmd_parts) gpu_command.append(output_path) try: print(f"尝试GPU加速命令: {' '.join(gpu_command)}") result = subprocess.run( gpu_command, check=True, capture_output=True, text=True, timeout=21600 # 360分钟超时 ) if os.path.exists(output_path): print("GPU加速处理成功") return output_path except subprocess.CalledProcessError as e: error_msg = e.stderr if e.stderr else e.stdout print(f"GPU加速失败: {error_msg}") if not fallback_to_cpu: raise gr.Error(f"GPU处理失败: {error_msg[:200]}...") except Exception as e: print(f"GPU加速异常: {str(e)}") if not fallback_to_cpu: raise gr.Error(f"GPU处理异常: {str(e)}") # 如果GPU失败且允许回退,则继续使用CPU # CPU处理(或GPU回退) print("使用CPU处理") cpu_command = ['ffmpeg', '-y', '-i', input_file_path] cpu_command.extend(cmd_parts) cpu_command.append(output_path) try: print(f"执行CPU命令: {' '.join(cpu_command)}") result = subprocess.run( cpu_command, check=True, capture_output=True, text=True, timeout=21600 # 360分钟超时 ) # 验证输出文件 if not os.path.exists(output_path): raise gr.Error(f"输出文件未生成: {output_path}") return output_path except subprocess.CalledProcessError as e: error_msg = f"FFmpeg执行失败: {e.stderr if e.stderr else e.stdout}" print(f"错误命令: {' '.join(shlex.quote(arg) for arg in cpu_command)}") print(error_msg) raise gr.Error(f"处理失败: {error_msg[:200]}...") # 截断长错误信息 except Exception as e: raise gr.Error(f"发生未知错误: {str(e)}")def process_media(file_info_dict, operation, format_convert_val, clip_start_val, clip_end_val, clip_format_val, resize_width_val, resize_height_val, extract_audio_format_val, bitrate_input_val, extract_frame_time_val, extract_frame_format_val, use_gpu_accel): """处理多媒体文件的主函数""" if file_info_dict is None or not file_info_dict.get('path'): raise gr.Error("请先选择文件") use_gpu = use_gpu_accel and GPU_TYPE != 'cpu' # 获取文件路径和类型 file_path = file_info_dict['path'] file_type = file_info_dict.get('type') or get_file_type(file_path) # 获取原始文件扩展名(不含点) orig_ext = os.path.splitext(file_path)[1][1:].lower() if file_path else "mp4" if not orig_ext: # 处理无扩展名的情况 orig_ext = "mp4" # 根据操作类型选择处理方式 try: if operation == "格式转换": output_ext = format_convert_val if output_ext == "original": output_ext = orig_ext # GPU加速编码 if use_gpu and file_type == 'video' and output_ext in ['mp4', 'mov', 'avi']: gpu_params = get_gpu_encoding_params('h264') if gpu_params: return run_ffmpeg_command(gpu_params, file_path, output_ext, use_gpu) return run_ffmpeg_command([], file_path, output_ext, use_gpu) elif operation == "片段剪辑": start = float(clip_start_val) end = float(clip_end_val) output_ext = clip_format_val if output_ext == "original": output_ext = orig_ext # GPU加速编码 if use_gpu and file_type == 'video' and output_ext in ['mp4', 'mov', 'avi']: # 对于片段剪辑,先尝试流复制(最快),如果失败再用GPU编码 try: return run_ffmpeg_command([ "-ss", str(start), "-to", str(end), "-c", "copy" # 使用流复制提高速度 ], file_path, output_ext, False, False) # 不使用GPU,不回退 except: # 如果流复制失败,使用GPU编码 gpu_params = get_gpu_encoding_params('h264') if gpu_params: return run_ffmpeg_command([ "-ss", str(start), "-to", str(end) ] + gpu_params, file_path, output_ext, use_gpu) return run_ffmpeg_command([ "-ss", str(start), "-to", str(end), "-c", "copy" # 使用流复制提高速度 ], file_path, output_ext, use_gpu) elif operation == "调整尺寸": width = int(resize_width_val) height = int(resize_height_val) # GPU加速缩放 if use_gpu and file_type == 'video': if GPU_TYPE == 'nvidia': # 使用scale_npp而不是scale_cuda,更稳定 return run_ffmpeg_command([ "-vf", f"scale_npp={width}:{height}:force_original_aspect_ratio=decrease", "-c:v", "h264_nvenc" ], file_path, orig_ext, use_gpu) elif GPU_TYPE == 'intel': return run_ffmpeg_command([ "-vf", f"scale_vaapi={width}:{height}:force_original_aspect_ratio=decrease", "-c:v", "h264_vaapi", "-vaapi_device", "/dev/dri/renderD128" ], file_path, orig_ext, use_gpu) return run_ffmpeg_command([ "-vf", f"scale={width}:{height}:force_original_aspect_ratio=decrease", "-c:a", "copy" # 保留原始音频 ], file_path, orig_ext, use_gpu) elif operation == "提取音频": output_ext = extract_audio_format_val if output_ext == "original": output_ext = "mp3" audio_codec = "copy" if output_ext == orig_ext and file_type == 'audio' else "libmp3lame" return run_ffmpeg_command([ "-vn", # 禁用视频流 "-acodec", audio_codec ], file_path, output_ext, use_gpu) elif operation == "调整比特率": bitrate = bitrate_input_val # 根据文件类型选择比特率参数 bitrate_param = "-b:a" if file_type == 'audio' else "-b:v" # GPU加速编码 if use_gpu and file_type == 'video': gpu_params = get_gpu_encoding_params('h264') if gpu_params: return run_ffmpeg_command([bitrate_param, bitrate] + gpu_params, file_path, orig_ext, use_gpu) return run_ffmpeg_command([bitrate_param, bitrate], file_path, orig_ext, use_gpu) elif operation == "提取帧": timestamp = float(extract_frame_time_val) output_ext = extract_frame_format_val if output_ext == "original": output_ext = "jpg" return run_ffmpeg_command([ "-ss", str(timestamp), "-vframes", "1", "-q:v", "2" # 控制图片质量 ], file_path, output_ext, use_gpu) else: raise gr.Error("未选择有效操作") except Exception as e: # 如果GPU处理失败,自动回退到CPU处理 if use_gpu: print(f"GPU处理失败,回退到CPU处理: {str(e)}") # 重新调用函数,但禁用GPU return process_media(file_info_dict, operation, format_convert_val, clip_start_val, clip_end_val, clip_format_val, resize_width_val, resize_height_val, extract_audio_format_val, bitrate_input_val, extract_frame_time_val, extract_frame_format_val, False) else: raise edef get_local_files(): """获取本地文件列表""" files = [] if os.path.exists(LOCAL_FILES_DIR): for file in os.listdir(LOCAL_FILES_DIR): file_path = os.path.join(LOCAL_FILES_DIR, file) if os.path.isfile(file_path): files.append(file) return filesdef refresh_local_files(): """刷新本地文件列表""" return gr.update(choices=get_local_files())def use_local_file(selected_file): """使用本地文件""" if selected_file: file_path = os.path.join(LOCAL_FILES_DIR, selected_file) if os.path.exists(file_path): file_type = get_file_type(file_path) file_ext = os.path.splitext(file_path)[1][1:].lower() or "未知" file_size = f"{os.path.getsize(file_path)/1024/1024:.2f} MB" info_text = f""" **文件信息:** - 类型: {file_type or '未知'} - 格式: {file_ext} - 大小: {file_size} """ return {"path": file_path, "type": file_type, "ext": file_ext}, info_text return {"path": None, "type": None, "ext": None}, "**文件信息:** 未选择文件"# 定义操作参数OPERATIONS = { "格式转换": { "description": "将文件转换为其他格式", "params": [ { "name": "format", "label": "目标格式", "type": "dropdown", "default": "mp4", } ] }, "片段剪辑": { "description": "截取文件中的一段", "params": [ {"name": "start_time", "label": "开始时间(秒)", "type": "number", "default": 0, "minimum": 0}, {"name": "end_time", "label": "结束时间(秒)", "type": "number", "default": 10, "minimum": 0}, {"name": "format", "label": "输出格式", "type": "dropdown", "default": "original"} ] }, "调整尺寸": { "description": "调整视频/图片尺寸", "params": [ {"name": "width", "label": "宽度", "type": "number", "default": 640, "minimum": 10}, {"name": "height", "label": "高度", "type": "number", "default": 480, "minimum": 10} ] }, "提取音频": { "description": "从视频中提取音频", "params": [ {"name": "format", "label": "音频格式", "type": "dropdown", "default": "mp3"} ] }, "调整比特率": { "description": "调整视频/音频质量", "params": [ {"name": "bitrate", "label": "比特率", "type": "text", "default": "128k", "placeholder": "例如: 1M, 500k"} ] }, "提取帧": { "description": "从视频中提取单帧图像", "params": [ {"name": "timestamp", "label": "截取时间点(秒)", "type": "number", "default": 5, "minimum": 0}, {"name": "format", "label": "图片格式", "type": "dropdown", "default": "jpg"} ] }}# 创建Gradio界面with gr.Blocks(title="FFmpeg多媒体处理器") as demo: gr.Markdown(f""" # 🎥 FFmpeg多媒体处理工具 **上传文件或选择本地文件进行处理** **系统信息**: GPU加速: {'✅ 已启用 (' + GPU_TYPE.upper() + ')' if GPU_TYPE != 'cpu' else '❌ 未检测到GPU'} **注意事项**:①不是所有格式都支持GPU加速,不支持时默认调用CPU;②程序超时设置为6小时,小型文件处理过久请尝试重启;③本地文件请上传至/local_files/;④ """) # 状态变量 file_info = gr.State({"path": None, "type": None, "ext": None}) current_operation = gr.State("格式转换") with gr.Tabs(): with gr.TabItem("上传文件"): with gr.Row(): with gr.Column(scale=3): file_input = gr.File( label="上传文件", type="filepath", file_types=["video", "audio", "image"] ) with gr.Column(scale=2): operation_select = gr.Dropdown( label="选择操作", choices=list(OPERATIONS.keys()), value="格式转换" ) with gr.TabItem("本地文件"): with gr.Row(): with gr.Column(scale=3): local_file_dropdown = gr.Dropdown( label="选择本地文件", choices=get_local_files(), interactive=True ) with gr.Row(): refresh_btn = gr.Button("刷新文件列表") use_local_btn = gr.Button("使用选中文件", variant="primary") with gr.Column(scale=2): operation_select_local = gr.Dropdown( label="选择操作", choices=list(OPERATIONS.keys()), value="格式转换" ) # 文件信息展示 file_info_display = gr.Markdown("**文件信息:** 未选择文件") # 操作描述区域 operation_info = gr.Markdown() # GPU加速选项 with gr.Row(): use_gpu_accel = gr.Checkbox( label=f"使用GPU加速 ({'CUDA' if GPU_TYPE == 'nvidia' else 'VAAPI' if GPU_TYPE == 'intel' else '不支持'})", value=False, interactive=GPU_TYPE != 'cpu' ) gpu_info_text = gr.Markdown(f"*GPU类型: {GPU_TYPE.upper() if GPU_TYPE != 'cpu' else '无'}*") # 动态参数区域 - 预先创建所有可能的参数组件 with gr.Column() as params_container: # 格式转换参数 with gr.Group(visible=True) as format_convert_group: format_convert_dropdown = gr.Dropdown( label="目标格式", choices=["mp4", "avi", "mov", "mkv", "flv", "webm", "gif", "mp3", "wav", "flac", "aac", "ogg", "m4a", "jpg", "jpeg", "png", "bmp", "webp", "original"], value="mp4", interactive=True ) # 片段剪辑参数 with gr.Group(visible=False) as clip_group: clip_start = gr.Number( label="开始时间(秒)", value=0, minimum=0, interactive=True ) clip_end = gr.Number( label="结束时间(秒)", value=10, minimum=0, interactive=True ) clip_format = gr.Dropdown( label="输出格式", choices=["mp4", "avi", "mov", "mkv", "flv", "webm", "gif", "mp3", "wav", "flac", "aac", "ogg", "m4a", "jpg", "jpeg", "png", "bmp", "webp", "original"], value="original", interactive=True ) # 调整尺寸参数 with gr.Group(visible=False) as resize_group: resize_width = gr.Number( label="宽度", value=640, minimum=10, interactive=True ) resize_height = gr.Number( label="高度", value=480, minimum=10, interactive=True ) # 提取音频参数 with gr.Group(visible=False) as extract_audio_group: extract_audio_format = gr.Dropdown( label="音频格式", choices=["mp3", "wav", "flac", "aac", "ogg", "m4a", "original"], value="mp3", interactive=True ) # 调整比特率参数 with gr.Group(visible=False) as bitrate_group: bitrate_input = gr.Textbox( label="比特率", value="128k", placeholder="例如: 1M, 500k", interactive=True ) # 提取帧参数 with gr.Group(visible=False) as extract_frame_group: extract_frame_time = gr.Number( label="截取时间点(秒)", value=5, minimum=0, interactive=True ) extract_frame_format = gr.Dropdown( label="图片格式", choices=["jpg", "jpeg", "png", "bmp", "webp", "gif", "original"], value="jpg", interactive=True ) # 所有参数组的引用 all_param_groups = { "格式转换": format_convert_group, "片段剪辑": clip_group, "调整尺寸": resize_group, "提取音频": extract_audio_group, "调整比特率": bitrate_group, "提取帧": extract_frame_group } # 输出区域 with gr.Row(): output_file = gr.File(label="处理结果", interactive=False) with gr.Column(): submit_btn = gr.Button("开始处理", variant="primary") status_text = gr.Markdown("**处理状态:** 等待操作", elem_id="status-text") # 更新文件信息 def update_file_info(file_path): """更新文件信息并返回显示内容""" if not file_path: return {"path": None, "type": None, "ext": None}, "**文件信息:** 未选择文件" file_type = get_file_type(file_path) file_ext = os.path.splitext(file_path)[1][1:].lower() or "未知" file_size = f"{os.path.getsize(file_path)/1024/1024:.2f} MB" info_text = f""" **文件信息:** - 类型: {file_type or '未知'} - 格式: {file_ext} - 大小: {file_size} """ return {"path": file_path, "type": file_type, "ext": file_ext}, info_text # 更新参数UI和格式选项 def update_params_and_formats(operation, file_info_dict): """更新参数UI可见性和格式选项""" file_type = file_info_dict.get("type") if file_info_dict else None # 隐藏所有参数组 group_updates = [] for op_name, group in all_param_groups.items(): if op_name == operation: group_updates.append(gr.update(visible=True)) else: group_updates.append(gr.update(visible=False)) # 根据文件类型更新格式选项 video_formats = ["mp4", "avi", "mov", "mkv", "flv", "webm", "gif", "original"] audio_formats = ["mp3", "wav", "flac", "aac", "ogg", "m4a", "original"] image_formats = ["jpg", "jpeg", "png", "bmp", "webp", "gif", "original"] if file_type == 'video': format_choices = video_formats elif file_type == 'audio': format_choices = audio_formats elif file_type == 'image': format_choices = image_formats else: format_choices = video_formats + audio_formats + image_formats + ["original"] # 更新各个格式下拉框的选项 format_updates = [] if operation == "格式转换": format_updates.append(gr.update(choices=format_choices)) format_updates.append(gr.update()) # clip_format format_updates.append(gr.update()) # extract_audio_format format_updates.append(gr.update()) # extract_frame_format elif operation == "片段剪辑": format_updates.append(gr.update()) # format_convert_dropdown format_updates.append(gr.update(choices=format_choices)) format_updates.append(gr.update()) # extract_audio_format format_updates.append(gr.update()) # extract_frame_format elif operation == "提取音频": audio_only_formats = ["mp3", "wav", "flac", "aac", "ogg", "m4a", "original"] format_updates.append(gr.update()) # format_convert_dropdown format_updates.append(gr.update()) # clip_format format_updates.append(gr.update(choices=audio_only_formats)) format_updates.append(gr.update()) # extract_frame_format elif operation == "提取帧": image_only_formats = ["jpg", "jpeg", "png", "bmp", "webp", "gif", "original"] format_updates.append(gr.update()) # format_convert_dropdown format_updates.append(gr.update()) # clip_format format_updates.append(gr.update()) # extract_audio_format format_updates.append(gr.update(choices=image_only_formats)) else: format_updates = [gr.update(), gr.update(), gr.update(), gr.update()] return group_updates + format_updates # 更新操作描述 def update_operation_info(operation): return f"**{operation}**: {OPERATIONS[operation]['description']}" # 清理临时文件并启动 clean_temp_files() # 界面事件处理 # 文件上传时更新信息和格式选项 file_input.upload( update_file_info, inputs=file_input, outputs=[file_info, file_info_display] ).then( lambda op: update_operation_info(op), inputs=operation_select, outputs=operation_info ).then( update_params_and_formats, inputs=[operation_select, file_info], outputs=[ format_convert_group, clip_group, resize_group, extract_audio_group, bitrate_group, extract_frame_group, format_convert_dropdown, clip_format, extract_audio_format, extract_frame_format ] ) # 本地文件操作 refresh_btn.click( refresh_local_files, outputs=local_file_dropdown ) use_local_btn.click( use_local_file, inputs=local_file_dropdown, outputs=[file_info, file_info_display] ).then( lambda op: update_operation_info(op), inputs=operation_select_local, outputs=operation_info ).then( update_params_and_formats, inputs=[operation_select_local, file_info], outputs=[ format_convert_group, clip_group, resize_group, extract_audio_group, bitrate_group, extract_frame_group, format_convert_dropdown, clip_format, extract_audio_format, extract_frame_format ] ) # 操作变更时更新参数可见性和格式选项 operation_select.change( update_operation_info, inputs=operation_select, outputs=operation_info ).then( update_params_and_formats, inputs=[operation_select, file_info], outputs=[ format_convert_group, clip_group, resize_group, extract_audio_group, bitrate_group, extract_frame_group, format_convert_dropdown, clip_format, extract_audio_format, extract_frame_format ] ) operation_select_local.change( update_operation_info, inputs=operation_select_local, outputs=operation_info ).then( update_params_and_formats, inputs=[operation_select_local, file_info], outputs=[ format_convert_group, clip_group, resize_group, extract_audio_group, bitrate_group, extract_frame_group, format_convert_dropdown, clip_format, extract_audio_format, extract_frame_format ] ) # 文件信息变更时更新格式选项 file_info.change( update_params_and_formats, inputs=[operation_select, file_info], outputs=[ format_convert_group, clip_group, resize_group, extract_audio_group, bitrate_group, extract_frame_group, format_convert_dropdown, clip_format, extract_audio_format, extract_frame_format ] ) # 提交处理 submit_btn.click( lambda: gr.update(value="**处理状态:** 处理中..."), outputs=status_text ).then( process_media, inputs=[ file_info, operation_select, format_convert_dropdown, clip_start, clip_end, clip_format, resize_width, resize_height, extract_audio_format, bitrate_input, extract_frame_time, extract_frame_format, use_gpu_accel ], outputs=output_file ).then( lambda: gr.update(value="**处理状态:** 处理完成!"), outputs=status_text ) # 初始加载 demo.load( lambda: f"**格式转换**: {OPERATIONS['格式转换']['description']}", outputs=operation_info )if __name__ == "__main__": # 清理旧文件并启动 clean_temp_files() print(f"请将需要处理的本地文件放入 {LOCAL_FILES_DIR} 目录中") print(f"本地文件目录: {os.path.abspath(LOCAL_FILES_DIR)}") demo.launch( server_name="0.0.0.0", server_port=8080, show_error=True, share=True )#激活虚拟环境conda activate gradio#运行ffmpeg_processor.pypython ffmpeg_processor.py三、网页演示
出现以下 gradio 页面,代表已搭建完成。
