基于 ffmpeg 的媒体体积压缩服务。扫描媒体库 → 人工确认 → 批量转码,支持 Intel / NVIDIA / AMD 硬件编码。
网页操作,无需命令行。
English — MediaShrink is a self-hosted web app that scans your media library, shows each file's codec, bitrate and estimated output size, and batch-transcodes only the files you tick. It ships AV1 / HEVC / H.264 presets (plus a fully manual mode), probes hardware encoders by actually encoding a test clip at startup (Intel QSV / VAAPI, NVIDIA NVENC, AMD VAAPI, or CPU), injects sidecar subtitles as soft tracks without burning them in, lets you pick audio tracks per file, and outputs MKV by default. Source files are never touched unless you explicitly rename (
.old) or delete them afterwards. Documentation is in Chinese. MIT licensed.
| 需求 | 实现 |
|---|---|
| 用 ffmpeg 压缩媒体体积 | 完整转码流水线,保留章节与元数据 |
| 扫描出来让人确认再压 | 扫描后列表展示原体积 / 分辨率 / 码率 / 预估体积 / 建议,逐项勾选,开始前二次确认 |
| 识别是否已是 AV1 / HEVC | 扫描时按 codec_name 分组:非 AV1/HEVC 的进「需要转码」表;已是 AV1/HEVC 的单独进另一张表,默认不勾选 |
| 支持 Intel 核显硬编码 | QSV(av1_qsv / hevc_qsv / h264_qsv)+ VAAPI(*_vaapi)双通道 |
| 也支持 N 卡 / A 卡 | NVENC(av1_nvenc / hevc_nvenc / h264_nvenc)、AMD VAAPI(*_vaapi) |
| 同文件夹字幕自动加上 | 自动匹配 影片名.*.srt/ass/vtt/sup,默认全部勾选,可逐条取消 |
| 字幕不烧录 | 只做软字幕轨(mkv copy / mp4 mov_text),画面始终原样重编码,不做任何字幕叠加 |
| 默认输出 MKV | 无论源文件是 mp4 还是 avi,统一封装成 .mkv:不限视频/音频编码,PGS/ASS 图形与特效字幕都能装,不会为了塞进 mp4 去重编码音轨 |
| 音轨可选 | 逐条勾选要保留的音轨(按语言 / 声道 / 码率 / 默认标记),可选「保留全部 / 仅默认轨 / 仅最高规格 / 全部去掉」 |
| 压缩完可处理源文件 | 每个完成的任务都能选「保留 / 改名加 .old 后缀 / 删除」,删除需二次勾选确认;只有输出校验通过才允许操作 |
| 较多预设可选 | 16 个预设:AV1 / HEVC / H.264 各有极致压缩→视觉无损多档,另有动画优化、HDR 保留、仅重封装 |
| 保留手动配置 | 预设之外还有一个「手动配置」:编码格式、CRF 或目标码率、编码速度、分辨率上限、位深、音频编码都能自己定,可全局生效也可只改某一行 |
| 优先 AV1 / HEVC | 全部预设基于 AV1 与 HEVC,H.264 仅作兼容兜底 |
| 优先原分辨率 | 默认完全不缩放;只有明确选「最高 1080p / 720p」预设才会缩小 |
| 画质优先 | 默认预设是 HEVC · 高画质(CRF 21);字幕不烧录;音轨默认直接复制;默认保留 10bit / HDR |
硬件编码采用「实测」而非「看 ffmpeg 支持列表」:启动时对每个候选编码器真跑一次 0.3 秒编码,跑通才算可用。所以不会出现"界面显示支持、一压就报错"。
镜像已发布在 Docker Hub:xdxy/media-shrink:latest(linux/amd64,约 1.0 GB)。
docker run -d --name media-shrink --restart unless-stopped \
-p 8420:8000 \
-v /你的/影视目录:/media \
-v /你的/appdata/media-shrink:/data \
--device=/dev/dri:/dev/dri \
xdxy/media-shrink:latest
打开 http://localhost:8420 ;也可以直接 http://localhost:8420/?scan=/media/movies 一步进到某个目录的扫描结果(适合收藏成书签)。
| 参数 | 说明 |
|---|---|
-p 8420:8000 | WebUI 端口,左边随便改,容器内固定 8000 |
-v ...:/media | 媒体目录,容器内路径自己定,但必须出现在 MEDIA_ROOTS 里 |
-v ...:/data | 任务记录,删掉只会丢历史,不影响压缩 |
--device=/dev/dri | Intel 核显 / AMD 的硬编入口。没有核显就整行删掉,Docker 找不到设备会拒绝启动 |
默认输出写回源文件同目录,不用单独挂输出目录;想统一输出再加 -v /你的/输出:/output 并设 OUTPUT_DIR=/output。
git clone https://github.com/apadn/media-shrink.git
cd media-shrink
docker compose up -d --build # compose 里已经配好路径和参数
首次构建要下载 ffmpeg 静态构建(约 160 MB),成品镜像约 1.0 GB(静态 ffmpeg 二进制占 340 MB,换来零依赖、编码器齐全)。构建默认用中科大 apt 源(实测比官方源快一个数量级),海外网络改 APT_MIRROR=deb.debian.org。


预设之外还有「手动配置」,全参数自己定(编码 / CRF 或码率 / 速度 / 分辨率 / 位深 / 音频):

也可以只在某一行展开、单独改这一个文件,不影响其他行:

压缩完成后可以在任务卡片上直接处理源文件(保留 / 改名加 .old / 删除):

删除需要勾选确认,弹窗会列出完整路径:

扫描后是两个独立的表:
① 需要转码 —— 编码不是 AV1 / HEVC 的文件(H.264、MPEG-2、VC-1、WMV…)。 默认勾选其中「建议压缩」的项。这一组是压缩收益的主要来源。
② 已是 AV1 / HEVC —— 已经是目标编码的文件。 默认全部不勾选。仍然列出来是因为三种情况值得重新处理:
每行前面的 ▸ 展开可以单独改字幕、音轨、CRF、预设。

预估列直接显示「预计省 x%」,因为它只是经验估算,不是精确值——真实结果只有转码完才知道。
模型由两部分合成:
bpp × 宽 × 高 × 帧率 算,bpp 由编码器和质量档决定(例如 HEVC 高画质 0.056、AV1 均衡 0.032)。它回答的是"这个编码器在这个质量档下大概需要多少码率"。源码率 × 编码器效率比(H.264→HEVC 取 0.72,→AV1 取 0.58),与理论值取几何平均。只做第 1 步会两头失真:
另外源码率是硬上限(留 2% 封装余量):重编码不会凭空造出码率,所以估算值不会超过源文件太多。±3% 以内显示「体积基本持平」——这个区间属于估算误差,说成"增加"只会让人以为估算坏了。
选「目标码率」模式时不受以上影响,体积是直接算出来的(码率 × 时长),此时
confidence为 high。
devices:
- /dev/dri:/dev/dri
宿主机确认:
ls -l /dev/dri # 应该有 renderD128
sudo apt install vainfo # 然后
vainfo # 能看到 VAProfileXXX / VAEntrypointEncSlice 即可
容器起来后,界面顶部会显示 Intel 核显 · Intel iHD driver ...,且 AV1/HEVC: hevc_qsv 之类的徽章为绿色。
av1_qsv)宿主机装 nvidia-container-toolkit,然后把 compose 里 deploy: 段取消注释:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu, video, compute, utility]
capabilities 里的 video 必须有,否则 NVENC 不可用。
同样是 /dev/dri:
devices:
- /dev/dri:/dev/dri
Linux 下 ffmpeg 走 VAAPI(hevc_vaapi)。AV1 编码需要 RDNA3 及以上(RX 7000 系)。
注意:
*_amf编码器只在 Windows 版 ffmpeg 里存在,Linux 容器内不会出现,这是驱动层限制,不是本项目的问题。
界面上「编码器」选 仅 CPU,或对单个文件在展开行里指定。CPU 编码(libsvtav1 / libx265)同码率画质更好,但慢 5~20 倍。
/media/movies)HEVC · 高画质(画质优先);扫描时按这个预设估算体积▸ 展开:
默认输出到源文件同目录,文件名加 _compressed 后缀,绝不会覆盖源文件;删不删源文件由你在压缩完成后逐个决定。
默认一律封装成 MKV,源文件是 mp4 / avi / ts 都一样。原因是 MKV 没有编码和字幕类型的限制:
-c:v copy 时不需要换容器就重编码copy;mp4 遇到 DTS 就只能转成 AAC,白损失一次想改回跟随源文件或输出 mp4,改环境变量即可(装不下时会自动退回 mkv,不会让任务失败):
OUTPUT_CONTAINER: "auto" # 跟随源文件:mp4→mp4、webm→webm、其余→mkv
OUTPUT_CONTAINER: "mp4" # 强制 mp4(遇 PGS/DVD 图形字幕的文件自动用 mkv)
不会自动删任何东西。每个压缩成功的任务下方会出现三个选项:
| 选项 | 结果 | 什么时候用 |
|---|---|---|
| 保留 | 什么都不做,只标记成已确认 | 想留着原片 |
改名加 .old | movie.mkv → movie.old.mkv | 推荐。文件只是多个后缀,随时能改回来;播放一圈确认没问题再删 |
| 删除… | 永久删除,不进回收站 | 已经确认过压缩结果,要立刻腾空间 |
任务队列右上角还有一批:源文件批量改名 .old / 批量删除源文件…,适合压完几十个文件后一次性收拾。
删除的按钮是「删除…」(带省略号),点了会弹出确认框列出完整路径,还要勾一个「我确认这些源文件可以永久删除」才会真正执行。
服务端还有一层兜底,下面任何一条不满足都拒绝操作源文件(宁可不动手):
done想彻底禁用删除,设 ALLOW_SOURCE_DELETE: "0",界面上的删除按钮直接变灰,API 也会返回 403。
改名只动视频文件本身,不会碰同目录的外挂字幕。
movie.old.mkv下次扫描会被识别成.old归档并标为「跳过」,不会被当成新片子再压一遍。
三个默认行为都是为了不损画质:
| 做法 | 原因 |
|---|---|
| 字幕只做软轨 | 烧录(hardsub)要把字幕合成到画面再重新编码,等于在压缩损失之上再叠一次损失。软字幕轨由播放器实时叠加,画面保持纯净,还能开关 |
音轨默认 copy | 音轨重新编码会损失细节,而音频体积通常只占 5~10%,省不了多少 |
| 默认保留 10bit / HDR | 源是 10bit 就输出 10bit,HDR10 / HLG 元数据与色彩标记原样传递 |
需要更极致的画质可以在展开行里把 CRF 调低(HEVC 18~20 已经很接近视觉无损)。
| 预设 | 适用 |
|---|---|
| HEVC · 高画质(推荐) | 默认。画质优先,兼容性最好,硬编码支持最广 |
| AV1 · 高画质 / 视觉无损 | 收藏级片源,同画质比 HEVC 再小 20~30% |
| AV1 · 均衡 | 通用归档 |
| AV1 · 极致压缩 | 空间极度紧张,画质有可见损失 |
| AV1 · 动画/番剧 | 关胶片颗粒合成,再省 15~25% |
| AV1 · 4K/HDR 保留 | 10bit + HDR 元数据原样保留 |
| HEVC · 均衡 | 兼容性与压缩率平衡 |
| HEVC · 极致压缩 | 激进档 |
| HEVC 10bit · HDR 原样保留 | 强制 10bit,HDR10/HLG 完整保留 |
| HEVC · 动画/番剧 | 平涂色块优化 |
| H.264 · 兼容优先 / 小体积 | 老设备、网页播放 |
| HEVC · 最高 1080p / AV1 · 最高 720p | 主动降分辨率(唯一会缩放的预设) |
| 仅重封装 | 只换容器 + 加字幕 + 裁音轨,画质零损失,秒级完成 |
| 手动配置 | 上面都不合适时的出口:编码、CRF/码率、速度、分辨率、位深、音频全自己定 |
CRF 参考:HEVC 1824,AV1 2330(画质优先取小值),数值越小画质越好、体积越大。
预设覆盖的是常见组合,剩下的特殊情况走「手动配置」——它在预设下拉框里,位置和普通预设一样,选中后下方会展开参数区。
| 参数 | 可选值 | 说明 |
|---|---|---|
| 编码格式 | AV1 / HEVC / H.264 | 选完自动按你的硬件匹配编码器(QSV / NVENC / VAAPI / CPU) |
| 码率控制 | 恒定质量(CRF) / 目标码率 | CRF 画质稳定、体积浮动;目标码率体积可控 |
| CRF 质量 | 滑杆 + 数字输入,0~63 | 切换编码格式时上限自动跟着变 |
| 视频码率 | 200~200000 kbps | 仅「目标码率」模式显示,实际编码会按 1.5× 峰值 / 2× 缓冲做 VBR |
| 编码速度 | 极速 / 快 / 均衡 / 慢 / 最慢 | 映射到各编码器自己的参数:x265 ultrafast…veryslow、SVT-AV1 12…2、NVENC p2…p7、QSV veryfast…veryslow |
| 分辨率上限 | 保持原分辨率 / 2160p / 1440p / 1080p / 720p | 只在源分辨率更高时缩小,不会放大 |
| 位深 | 跟随源文件 / 强制 8bit / 强制 10bit | 强制 10bit 会走 yuv420p10le / p010le |
| 音频 | 直接复制 / 转 AAC / 转 Opus | 和展开行里的「音频编码」是同一套,那里选过就以那里为准 |
作用范围有两种:
配置会存在浏览器里(localStorage),下次打开还是你上次调的值;「恢复默认」按钮回到出厂参数(HEVC / CRF 21 / 均衡 / 原分辨率 / 音频复制)。
体积预估对两种模式都准:CRF 模式走经验公式(±20% 量级),目标码率模式直接按 码率 × 时长 算,基本就是实际大小。
注意:手动配置里选了什么不改变扫描分组——分组看的是源文件编码,不是目标编码。
| 变量 | 默认 | 说明 |
|---|---|---|
MEDIA_ROOTS | /media,/data,/output | 允许浏览/扫描的根目录(安全边界) |
OUTPUT_DIR | 空 | 留空=输出到源文件旁;填 /output=统一输出目录 |
OUTPUT_SUFFIX | _compressed | 输出文件后缀 |
OUTPUT_CONTAINER | mkv | 输出容器:mkv / mp4 / webm / auto(跟随源文件) |
SOURCE_ACTION | keep | 源文件默认处理:keep / rename / delete(界面仍逐个确认) |
SOURCE_OLD_SUFFIX | .old | 归档后缀,movie.mkv → movie.old.mkv |
ALLOW_SOURCE_DELETE | 1 | 设 0 禁用删除源文件,只留「保留 / 改名」 |
CONCURRENT_JOBS | 1 | 并行任务数。硬件编码建议 1~2 |
VAAPI_DEVICE | /dev/dri/renderD128 | VAAPI 渲染节点 |
MIN_SIZE_MB | 1 | 扫描时忽略小于此体积的文件 |
APT_MIRROR | deb.debian.org | 构建期 apt 源 |
FFMPEG_TAG | latest | 构建期 ffmpeg 版本(BtbN 构建的 tag) |
界面显示「未挂载 /dev/dri」
compose 里的 devices 没生效,或宿主机没有 /dev/dri(虚拟机 / 群晖需开启核显直通)。
编码器测试全部失败,但 /dev/dri 存在
容器内缺 VAAPI 驱动。进容器跑 vainfo 看报错;群晖/Unraid 常需要额外映射 /dev/dri 的权限。
AV1 硬编不可用
只有 Arc 独显、部分 14 代+ 酷睿、RDNA3 的 A 卡支持 AV1 硬件编码。其余平台会回落到 libsvtav1(CPU),速度慢属正常。
已经是 HEVC 的片源,勾了压缩反而变大了 原片码率已经很低(第二张表里会标「不建议」)。这种情况选「仅重封装」或直接跳过。
保留的音轨在播放器里显示「未知语言」
转码时会显式把源语言标签(chi / eng / jpn…)写回输出流。如果源文件本身就没有语言标签,界面上也只能显示编码格式,需要先用 mkvtoolnix 打标签。
能不能自动删源文件
不会自动删。压缩完成后每个任务会出现「保留 / 改名加 .old / 删除…」三个选项,必须你手动点;删除还要勾确认框,并且输出文件得先通过完整性检查。觉得太危险就设 ALLOW_SOURCE_DELETE=0 直接关掉删除,只留改名归档。
media-shrink/
├── Dockerfile # 多阶段:BtbN ffmpeg 静态构建 + Debian slim 运行时
├── docker-compose.yml
├── requirements.txt
├── LICENSE
├── tools/ # make_favicon.py 生成站点图标、github-actions-ci.yml 可选的 CI 配置
├── docs/ # 界面截图
├── test/e2e.sh # 端到端自检
└── app/
├── main.py # FastAPI 路由
├── config.py # 环境变量配置
├── probe.py # ffprobe 解析(含音轨/字幕流元数据)
├── presets.py # 预设矩阵 + 编码参数映射 + 体积估算
├── hwdetect.py # 硬件实测探测
├── scanner.py # 目录扫描 / 编码分组 / 字幕匹配 / 建议
├── transcoder.py # 命令构建(音轨精确映射)+ 队列 + 进度
└── static/ # 前端(原生 JS,无构建)
USER=xdxy # 你的 Docker Hub 账号
VERSION=1.0.2 # 跟 app/config.py 里的 APP_VERSION 保持一致
docker build -t $USER/media-shrink:$VERSION -t $USER/media-shrink:latest .
docker login
docker push $USER/media-shrink:$VERSION
docker push $USER/media-shrink:latest
latest 和版本号指向同一个镜像 ID,别人 docker pull $USER/media-shrink:latest 就能用;想回滚旧版也有具名标签可拉。
本机是 x86_64,docker build 出来的就是 linux/amd64,普通 NAS / 服务器都能直接用。要同时出 arm64(树莓派、ARM 版 NAS)就换成 buildx:
docker buildx build --platform linux/amd64,linux/arm64 \
-t $USER/media-shrink:latest -t $USER/media-shrink:$VERSION --push .
1 GB 的镜像推一次几分钟,取决于上行带宽。改完代码只重建最后几层,
docker push也只会推变化的分层。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/hw | 硬件与编码器探测结果 |
| POST | /api/scan | 扫描目录,返回带 codec_family 分组信息的 items |
| POST | /api/estimate | 重算体积。可传 items: [{path, preset_id, manual, tracks}] 逐条指定;也兼容旧的 paths + preset_id + tracks |
| POST | /api/jobs | 创建转码任务(audio_tracks 为流 index 数组,null 表示全部) |
| GET | /api/jobs | 任务列表与进度 |
| POST | /api/jobs/{id}/cancel | 取消任务 |
| POST | /api/jobs/source | 处理源文件:{job_ids, action: keep/rename/delete, confirm};删除必须 confirm=true |
bash test/e2e.sh
脚本会自动走完一整条链路:造测试素材(H.264 单音轨 / H.264 无音轨 / H.264 双音轨 / 已是 HEVC,含中英文外挂字幕)→ 起容器 → 扫描并断言编码分组 → 重封装注入字幕(断言视频流未被重编码)→ 只保留一条音轨(断言输出音轨数)→ HEVC 压缩(断言编码与分辨率)→ 手动配置(断言编码器选择、码率预估、输出 10bit / 720p / 900kbps)→ 源文件保留 / 改名 / 删除的安全校验。需要 Docker 与 python3(解析 JSON),改动代码后跑一遍即可回归。
提 Issue 或 PR 都欢迎。改动前建议先跑一遍 bash test/e2e.sh;涉及前端改动的,说明一下改的是哪个交互。
本项目只做「体积压缩 + 元数据整理」,不追求成为完整的转码工作台,功能建议请围绕这条主线。
MIT © 2026 apadn
镜像里打包的 ffmpeg 是 GPL 构建(含 libx265 / libsvtav1 / x264 等), 分发镜像时需同时遵守其 GPL 条款;本项目自身的代码仍是 MIT。
Content type
Image
Digest
sha256:7ff2db6cf…
Size
277.3 MB
Last updated
about 10 hours ago
docker pull xdxy/media-shrink