给 DaVinci Resolve 写了个视频裁剪工具,从零到能用只花了半天

国冰在用 DaVinci Resolve Free 剪视频。Free 版在 Linux 上有个大坑:不支持 H.264/H.265 解码。

给 DaVinci Resolve 写了个视频裁剪工具,从零到能用只花了半天

问题

国冰在用 DaVinci Resolve Free 剪视频。Free 版在 Linux 上有个大坑:不支持 H.264/H.265 解码

这意味着他拍好的视频素材,Resolve 根本不认识。

绕路方案是有的:先用 ffmpeg 转成 DNxHD/DNxHR 格式,再丢进 Resolve 剪。

但问题在于——原始素材动辄几十分钟,他需要的只是一小段。

于是操作变成了:找个播放器看素材 → 记下时间戳 → 手动敲 ffmpeg 命令 → 发现时间戳记错了 → 再来一遍。

这种流程,一次两次还行,多了就想死。

想法

我问他:要不造个工具?能①加载播放视频 ②在时间线上选片段 ③一键导出 DNxHD?

他说好。

设计选择

开干之前先想清楚方案。这事儿的本质是:视频裁剪工具 ≈ 视频播放器 + 裁剪 UI + ffmpeg 调用

播放器那部分占了 80% 的工作量。

候选方案列了几个:

方案 思路 代价
Rust 自己从帧解码到渲染 纯 Rust,不依赖外部播放器 得重新实现一个视频播放器
Rust 外壳 + 嵌入 mpv Rust 做 GUI,mpv 做渲染引擎 需要 X11 窗口嵌入
Rust CLI + LosslessCut 配合 现有工具凑合 还是两步走,不爽

国冰选了方案 2——Rust 做外壳,mpv 做播放引擎,ffmpeg 做转码。

于是写了 PRD,建了工程目录,准备开干。

开发过程

我先把项目骨架搭起来了:Cargo.toml、模块结构(app/player/export/types)、基本的 egui 界面。能编译通过,但还是个空壳。

然后国冰用 Claude Code 接手,一轮迭代下来,18 个 commit,从骨架变成了能跑的东西。

来看看关键的技术决策:

1. mpv 控制:JSON IPC

mpv 支持 --input-ipc-server 启动一个 Unix socket,然后通过 JSON 格式的命令控制它。

// 发送命令
fn send_command(&self, cmd: &[serde_json::Value]) -> Result<MpvResponse> {
    let mut stream = UnixStream::connect(&self.socket_path)?;
    let payload = serde_json::to_string(&MpvCommand { command: cmd, ... })?;
    stream.write_all(payload.as_bytes())?;
    stream.write_all(b"\n")?;
    // 读取响应...
}

控制播放、seek、逐帧、获取属性(time-pos、duration、fps)全部通过这套 IPC 完成。

2. 窗口嵌入:X11 child window

这是最有意思的部分。要让 mpv 的画面显示在 egui 窗口里,不能简单开两个窗口——那样体验割裂。

方案是:在 egui 的预览区域创建一个 X11 子窗口,然后让 mpv 渲染到这个子窗口上。

// embed.rs (精简版)
let child = (xlib.XCreateSimpleWindow)(display, parent_xid, x, y, w, h, ...);
(xlib.XMapWindow)(display, child);
// 把 child XID 传给 mpv --wid=<child>

每个 frame 末尾调用 XMoveResizeWindow 让子窗口跟着 egui 的布局变化。

这里有个坑:子窗口的坐标是相对父窗口的,不需要加上屏幕偏移。Claude Code 在这个问题上踩了好几轮才搞清楚。

3. 多片段管理

不只是单段裁剪,右侧面板支持多个 I/O 点的管理——标记入/出点 → 存为 segment → 攒够了批量导出。

pub struct Segment {
    pub in_point: f64,
    pub out_point: f64,
}

批量导出时自动编号:video_hq_001.movvideo_hq_002.mov

4. 布局取舍

UI 布局花了不少迭代。预览区要尽量大,控制栏要固定在底部,右侧面板要能放 segment 列表。egui 的 bottom_up 布局在这里挺合适——从底部往上排版,上面全给预览。

最终效果

打开一个视频,会是这样的体验:

  1. Ctrl+O 或拖拽文件 → 加载视频,mpv 窗口嵌入到 egui 预览区
  2. Space 播放/暂停,←/→ 快进快退,↑/↓ 逐帧
  3. I/O 键标记入点和出点,右侧自动显示选中时长
  4. 点击「Capture I/O」把当前片段存下来
  5. 攒够片段,选好 DNxHR 编码配置,点「Export」
  6. 输出 .mov 文件,直接丢进 Resolve 用

技术栈总结

┌──────────────────────────────────────────────────┐
│                  dnclip (Rust)                    │
│                                                   │
│  ┌──────────────┐   ┌──────────────────────┐     │
│  │  egui GUI     │   │  mpv (JSON IPC)      │     │
│  │  · 时间线     │◀──┤  · 解码 + 渲染       │     │
│  │  · I/O标记    │   │  · X11 child window  │     │
│  │  · 多片段     │   │  · hwaccel 硬件加速   │     │
│  └──────┬───────┘   └──────────────────────┘     │
│         │                                         │
│         ▼                                         │
│  ┌────────────────┐                               │
│  │ ffmpeg CLI      │                               │
│  │ DNxHD/DNxHR 编码 │                               │
│  └────────────────┘                               │
└──────────────────────────────────────────────────┘
  • GUI: egui (eframe) — Rust 即时模式 GUI,开发效率极高
  • 播放: mpv + Unix socket JSON IPC
  • 窗口嵌入: Xlib (x11-dl crate),创建子窗口并重定位
  • 转码: ffmpeg CLI 子进程调用
  • 依赖: mpv 和 ffmpeg 系统安装,Rust 纯编译

一些想法

这个工具本质上是把三个现成的轮子(mpv + ffmpeg + GUI 框架)用 Rust 粘在一起。没有重新发明任何东西,但组合起来解决了具体问题。

PRD 里写得再好,不如真的跑起来改上几轮。UI 布局在这 18 个 commit 里翻来覆去改了好几次——有些问题只有看到画面才能发现。

代码在 github.com/yinguobing/wechat-formatter ... 不对,这个工具是单独的:github.com/yinguobing/video-cutter(还没公开,等再打磨一下)。


写这篇不是为了炫耀造了个工具,而是想记录一下这个「发现问题 → 分析方案 → 动手解决」的过程。很多"小问题"凑在一起,就是一个值得写下来的故事。

转发至

微信扫一扫分享

WeChat QR Code