一、介绍
FFmpeg 是一套可以用来记录、转换 数字音频、视频,并能将其转化为流的开源计算机程序。采用 LGPL 或 GPL 许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库 libavcodec,为了保证高可移植性和编解码质量,libavcodec 里很多 code 都是从头开发的。
二、部署过程
基础环境最低要求说明:
环境名称 | 版本信息 1 |
---|---|
Ubuntu | 22.04.4 LTS |
Cuda | V12.8 |
Python | 3.12 |
NVIDIA Corporation | RTX 4090 |
1. 更新基础软件包
查看系统版本信息
bash
# 查看系统版本信息,包括ID(如ubuntu、centos等)、版本号、名称、版本号ID等
cat /etc/os-release

配置 apt 国内源
csharp
# 更新软件包列表
apt-get update
这个命令用于更新本地软件包索引。它会从所有配置的源中检索最新的软件包列表信息,但不会安装或升级任何软件包。这是安装新软件包或进行软件包升级之前的推荐步骤,因为它确保了您获取的是最新版本的软件包。
csharp
# 安装 Vim 编辑器
apt-get install -y vim
这个命令用于安装 Vim 文本编辑器。-y
选项表示自动回答所有的提示为"是",这样在安装过程中就不需要手动确认。Vim 是一个非常强大的文本编辑器,广泛用于编程和配置文件的编辑。
为了安全起见,先备份当前的 sources.list
文件之后,再进行修改:
bash
# 备份现有的软件源列表
cp /etc/apt/sources.list /etc/apt/sources.list.bak
这个命令将当前的 sources.list
文件复制为一个名为 sources.list.bak
的备份文件。这是一个好习惯,因为编辑 sources.list
文件时可能会出错,导致无法安装或更新软件包。有了备份,如果出现问题,您可以轻松地恢复原始的文件。
bash
# 编辑软件源列表文件
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 国内阿里源。
arduino
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse

安装常用软件和工具
csharp
# 更新源列表,输入以下命令:
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 的安装过程。
- 使用
bash
# 下载 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 配置清华源加速
bash
# 编辑 /etc/pip.conf 文件
vim /etc/pip.conf
加入以下代码
ini
[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
- 安装命令:
bash
# 安装ffmpeg
sudo apt update
sudo apt install ffmpeg -y
4. 创建虚拟环境
ini
# 创建一个名为 gradio的新虚拟环境(名字可自定义),并指定 Python 版本为 3.12
conda create -n gradio python=3.12 -y
5. 安装gradio依赖库
- 切换到项目目录、激活 gradio虚拟环境、安装gradio依赖
bash
# 激活 gradio 虚拟环境
conda activate gradio
# 在 gradio 环境中安装依赖
pip install gradio numpy
6. 编写并运行 ffmpeg_processor.py 文件
ini
#编写ffmpeg_processor.py
vim ffmpeg_processor.py
#以下代码片段是一个gradio网页的示例
import gradio as gr
import subprocess
import os
import uuid
import mimetypes
import json
import shlex
import time
from datetime import timedelta
from 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 e
def 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 files
def 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.py
python ffmpeg_processor.py
三、网页演示
出现以下 gradio 页面,代表已搭建完成。
