基于 Flask + FFmpeg 的在线音视频格式转换工具,支持 12 种格式(6 音频 + 6 视频)互转,提供拖拽上传、实时进度条、一键下载等功能。
| 依赖 | 版本要求 | 说明 |
|---|---|---|
| Python | >= 3.9 | 运行时环境 |
| Flask | >= 3.0, < 4.0 | Web 框架(见 requirements.txt) |
| FFmpeg | >= 5.0(建议) | 音视频编解码引擎,需包含 ffmpeg 和 ffprobe |
FFmpeg 是系统级依赖,不在
pip install范围内,需单独安装。
# Ubuntu / Debian
sudo apt update && sudo apt install -y ffmpeg
# CentOS / RHEL
sudo yum install -y epel-release && sudo yum install -y ffmpeg
# macOS (Homebrew)
brew install ffmpeg
# 验证安装
ffmpeg -version
ffprobe -version# 建议使用虚拟环境
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt# 开发模式(带热重载)
python app.py
# 服务启动在 http://localhost:5000
# 生产模式(使用 Gunicorn)
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app浏览器打开 http://localhost:5000,拖拽文件即可开始转换。
配置文件为 config.py,可按需修改:
| 配置项 | 默认值 | 说明 |
|---|---|---|
UPLOAD_FOLDER |
./uploads |
上传文件存储目录 |
OUTPUT_FOLDER |
./outputs |
转换结果存储目录 |
MAX_CONTENT_LENGTH |
500 MB | 单文件上传大小限制 |
ALLOWED_EXTENSIONS |
6 音频 + 6 视频 | 支持的文件格式 |
FILE_RETENTION_SECONDS |
3600(1小时) | 文件保留时间 |
| 类型 | 格式 |
|---|---|
| 音频 | MP3、WAV、AAC、FLAC、OGG、M4A |
| 视频 | MP4、AVI、MKV、MOV、WebM、FLV |
同类型内可互转(音频↔音频,视频↔视频)。
convertor/
├── app.py # Flask 主应用(路由层)
├── config.py # 全局配置
├── requirements.txt # Python 依赖
├── converter/ # 核心转换模块
│ ├── __init__.py
│ ├── formats.py # 格式定义与校验
│ ├── ffmpeg_wrapper.py # FFmpeg 进程封装
│ └── task_manager.py # 任务状态管理
├── templates/
│ └── index.html # 页面模板
├── static/
│ ├── css/style.css # 暗色主题样式
│ └── js/main.js # 前端交互逻辑
├── uploads/ # 上传文件临时存储
└── outputs/ # 转换结果存储
graph TB
subgraph Browser["🌐 浏览器 (前端)"]
direction TB
HTML["index.html<br/>页面结构 · 4个步骤面板"]
CSS["style.css<br/>暗色主题 · 响应式布局"]
JS["main.js<br/>交互逻辑 · 状态机"]
HTML --- CSS
HTML --- JS
end
subgraph Flask["🐍 Flask 服务端 (app.py)"]
direction TB
R1["GET /<br/>渲染首页"]
R2["POST /api/upload<br/>接收文件 · 返回格式信息"]
R3["POST /api/convert<br/>创建转换任务 · 启动后台线程"]
R4["GET /api/task/:id<br/>查询任务进度"]
R5["GET /api/download/:id<br/>下载转换结果"]
end
subgraph Core["⚙️ converter 核心模块"]
direction TB
FMT["formats.py<br/>12种格式定义<br/>6音频 + 6视频<br/>格式校验·目标推荐"]
FFW["ffmpeg_wrapper.py<br/>FFmpeg 进程管理<br/>ffprobe 获取媒体信息<br/>-progress pipe:1 进度解析"]
TM["task_manager.py<br/>Task 数据类<br/>内存字典存储<br/>线程锁保护读写"]
end
subgraph Storage["💾 磁盘存储"]
UP["uploads/<br/>上传的原始文件"]
OUT["outputs/<br/>转换后的文件"]
end
subgraph External["🔧 外部依赖"]
FF["FFmpeg / FFprobe<br/>音视频编解码引擎"]
end
CFG["config.py<br/>路径 · 大小限制<br/>支持格式 · 保留时间"]
JS -- "XHR POST multipart/form-data" --> R2
JS -- "fetch POST JSON" --> R3
JS -- "fetch GET (500ms轮询)" --> R4
JS -- "a href 下载" --> R5
R1 -- "render_template" --> HTML
R2 -- "校验格式" --> FMT
R2 -- "ffprobe 获取时长" --> FFW
R2 -- "file.save()" --> UP
R3 -- "create_task()" --> TM
R3 -- "threading.Thread" --> FFW
R4 -- "get_task()" --> TM
R5 -- "send_file()" --> OUT
FFW -- "subprocess.Popen" --> FF
FFW -- "progress_callback" --> TM
FFW -- "输出文件" --> OUT
FFW -- "读取源文件" --> UP
FFW -- "get_format_info()" --> FMT
CFG -. "UPLOAD_FOLDER" .-> R2
CFG -. "OUTPUT_FOLDER" .-> R3
CFG -. "MAX_CONTENT_LENGTH" .-> Flask
style Browser fill:#1a1a2e,stroke:#e94560,color:#fff
style Flask fill:#16213e,stroke:#0f3460,color:#fff
style Core fill:#0f3460,stroke:#533483,color:#fff
style Storage fill:#1a1a2e,stroke:#e94560,color:#fff
style External fill:#533483,stroke:#e94560,color:#fff
sequenceDiagram
actor User as 👤 用户
participant B as 🌐 浏览器<br/>main.js
participant F as 🐍 Flask<br/>app.py
participant FMT as 📋 formats.py
participant TM as 📊 task_manager.py
participant T as 🧵 后台线程
participant FFW as ⚙️ ffmpeg_wrapper.py
participant FF as 🔧 FFmpeg 进程
Note over User,FF: ① 上传阶段
User->>B: 拖拽/选择文件
B->>B: new FormData()<br/>append('file', File)
B->>F: XHR POST /api/upload<br/>multipart/form-data
Note over B: upload.onprogress 更新上传进度条
F->>F: 校验文件格式/大小
F->>F: file.save() → uploads/
F->>FFW: get_media_info(path)
FFW->>FF: ffprobe -print_format json
FF-->>FFW: {duration, streams, format}
F->>FMT: get_convertible_targets('ogg')
FMT-->>F: [{ext:'mp3'}, {ext:'wav'}, ...]
F-->>B: 200 JSON {filename, targets, duration, size}
Note over User,FF: ② 选择格式
B->>User: 展示格式按钮 (MP3/WAV/AAC/FLAC/M4A)
User->>B: 点击 MP3 → 点击"开始转换"
Note over User,FF: ③ 转换阶段
B->>F: fetch POST /api/convert<br/>{stored_name, target_ext:'mp3'}
F->>FMT: 校验目标格式
F->>TM: create_task() → Task{id, status:pending}
F->>T: threading.Thread(target=_run_conversion)
F-->>B: 200 JSON {task_id}
T->>TM: mark_processing(task_id)
T->>FFW: convert(input, output, 'mp3', callback)
FFW->>FF: ffmpeg -y -i input.ogg -progress pipe:1 output.mp3
loop FFmpeg 持续输出进度
FF-->>FFW: stdout: out_time_ms=3500000
FFW->>FFW: progress = 3.5s / 10s × 100 = 35%
FFW->>TM: progress_callback(35.0)
TM->>TM: task.progress = 35.0 (加锁)
end
loop 前端每500ms轮询
B->>F: fetch GET /api/task/{id}
F->>TM: get_task(id)
TM-->>F: {status:'processing', progress:35.0}
F-->>B: 200 JSON
B->>B: progressBar.width = '35%'
end
FF-->>FFW: 进程退出 returncode=0
FFW->>TM: progress_callback(100.0)
T->>TM: mark_completed(task_id)
B->>F: fetch GET /api/task/{id}
F-->>B: {status:'completed', progress:100}
B->>B: clearInterval(轮询) → 展示完成界面
Note over User,FF: ④ 下载阶段
User->>B: 点击"下载文件"
B->>F: GET /api/download/{task_id}
F->>F: send_file(outputs/xxx.mp3)
F-->>B: 二进制文件流
B->>User: 浏览器触发文件下载
| 模块 | 文件 | 核心职责 |
|---|---|---|
| 配置 | config.py |
路径、大小限制(500MB)、保留时间(1h) |
| 路由层 | app.py |
5 个 API 端点,请求分发,线程调度 |
| 格式定义 | formats.py |
12 种格式(6音频+6视频)的 MIME、编解码器、互转关系 |
| FFmpeg 封装 | ffmpeg_wrapper.py |
subprocess.Popen 调用 FFmpeg,-progress pipe:1 解析进度 |
| 任务管理 | task_manager.py |
内存字典 + 线程锁,任务生命周期(pending→processing→completed/failed) |
| 前端页面 | index.html |
4 步骤面板:上传 → 选格式 → 转换中 → 下载 |
| 前端样式 | style.css |
暗色主题、拖拽区域、进度条动画 |
| 前端逻辑 | main.js |
XHR 上传(带进度)、格式选择交互、500ms 轮询进度、状态机切换 |
| 决策 | 选择 | 原因 |
|---|---|---|
| 上传方式 | XHR(非 fetch) | 需要 xhr.upload.onprogress 监听上传进度 |
| 转换执行 | 后台线程 | 避免阻塞 Flask 请求处理 |
| 进度通信 | 轮询(非 WebSocket) | MVP 阶段,实现简单,500ms 间隔体验够好 |
| 任务存储 | 内存字典 + 线程锁 | 单进程足够,无需引入 Redis/数据库 |
| 进度来源 | FFmpeg -progress pipe:1 |
原生支持,结构化输出,比解析 stderr 可靠 |
| 文件命名 | 时间戳前缀 | 防重名,同时保留原始文件名可读性 |