一行命令,玩转所有主流音视频格式!一站式音视频处理工具——FFmpeg本地部署教程

一、介绍

FFmpeg 是一套可以用来记录、转换 数字音频、视频,并能将其转化为流的开源计算机程序。采用 LGPLGPL 许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库 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 页面,代表已搭建完成。

相关推荐
骥龙20 分钟前
XX汽集团数字化转型:全生命周期网络安全、数据合规与AI工业物联网融合实践
人工智能·物联网·web安全
zskj_qcxjqr26 分钟前
告别传统繁琐!七彩喜艾灸机器人:一键开启智能养生新时代
大数据·人工智能·科技·机器人
Ven%29 分钟前
第一章 神经网络的复习
人工智能·深度学习·神经网络
研梦非凡1 小时前
CVPR 2025|基于视觉语言模型的零样本3D视觉定位
人工智能·深度学习·计算机视觉·3d·ai·语言模型·自然语言处理
Monkey的自我迭代1 小时前
多目标轮廓匹配
人工智能·opencv·计算机视觉
每日新鲜事1 小时前
Saucony索康尼推出全新 WOOOLLY 运动生活羊毛系列 生动无理由,从专业跑步延展运动生活的每一刻
大数据·人工智能
空白到白1 小时前
机器学习-聚类
人工智能·算法·机器学习·聚类
中新赛克1 小时前
双引擎驱动!中新赛克AI安全方案入选网安创新大赛优胜榜单
人工智能·安全
飞哥数智坊1 小时前
解决AI幻觉,只能死磕模型?OpenAI给出不一样的思路
人工智能·openai
聚客AI1 小时前
🌈多感官AI革命:解密多模态对齐与融合的底层逻辑
人工智能·llm·掘金·日新计划