# Video Sync

把拉片数据和原片合成一条**能直接看的视频**：一边是画面，一边是这一镜的分镜信息 （镜号、起止、时长、景别、类别、运镜、画面描述、台词），**镜头切了信息跟着切**，...

- Repository: https://github.com/eternityspring/reelbench-skills (1194 stars)
- Category: Design
- License: Apache-2.0
- Last updated: 2026-10-24
- Runs scripts: yes, review before use

## Install

Claude Code: `npx skills add eternityspring/reelbench-skills --skill video-sync -g -a claude-code -y`
Codex: `npx skills add eternityspring/reelbench-skills --skill video-sync -g -a codex -y`
Cursor: `npx skills add eternityspring/reelbench-skills --skill video-sync -g -a cursor -y`
Gemini CLI: `npx skills add eternityspring/reelbench-skills --skill video-sync -g -a gemini-cli -y`

## Description

把拉片数据和原片合成一条**能直接看的视频**：一边是画面，一边是这一镜的分镜信息 （镜号、起止、时长、景别、类别、运镜、画面描述、台词），**镜头切了信息跟着切**， 镜头表自动滚动并高亮当前这一镜。 版式只看原片的宽高比：**横版 / 方版 → 画面在上、信息在下；竖版 → 画面在左、信息在右**， 画面永远原样缩放，不裁不拉。 镜头表**随播放滚动**：切点处滚一小段把当前镜头带到锚点、随后停住，高亮条同步滑过去。 实现上只截三张图（底板 + 铺开的长图 ×2），滚动与高亮由 ffmpeg 按时间裁窗， **布局全在 scripts/panel.css 里，改它就能改版式**，不用碰脚本。 吃 video-shots 产出的 shots.json（有 frames/ 就把关键帧当缩略图用）。 零 npm 依赖，用 ffmpeg 合成、无头浏览器渲面板。 Use when asked to 导出视频、合成视频、分镜视频、带分镜信息的视频、解说版视频、 video with shot info、annotated shot video。

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

## video-sync

把 **shots.json + 原片** 合成一条视频：画面一边，**当前镜头的分镜信息**另一边，
镜头切了信息跟着切，镜头表自动滚动并高亮。

**版式只看宽高比，不看别的：**

| 原片 | 版式 | 合成方式 |
| --- | --- | --- |
| 横版（宽 > 高）、方版 | **画面在上，信息在下** | `vstack` |
| 竖版（高 > 宽） | **画面在左，信息在右** | `hstack` |

两个方向都保证**画面原样缩放、不裁不拉**，面板补足剩下的地方。

**只截三张图，动的部分交给 ffmpeg。** 面板上会变的只有两样：滚到哪、哪一行亮。
所以量一次行位置、截一张底板、截两张把镜头表铺开的长图（一张压暗、一张全亮），
滚动＝从暗底长图裁一个视窗，高亮＝从亮条长图裁一行盖上去，位置由 `sendcmd`
在每个切点下发一条小表达式驱动。53 镜的片子截图从 53 次降到 3 次（12 秒）。

**当前镜头钉在第二行**：第 1、2 镜不滚（高亮往下挪一行），第 3 镜起每切一次往上滚一行。
**动在切点，静在镜头里**, , 切点处滚 `--ease`（默认 0.45）秒到位就停住，
16 秒的长镜头里让列表一直爬，观众要盯着它晃 16 秒。

**行高由内容决定**，描述写得长行就高；高亮条按行高分层（`crop` 的高度运行时改不动）。

`{baseDir}` = 本文件所在目录。脚本 `{baseDir}/scripts/video-sync.mjs`，零依赖，`node` 直接跑。

**边界（不做的事）**：不做拉片（那是 `video-shots` 的活，本 skill 吃它的 shots.json）、
不剪辑不转场不配乐、不烧字幕到画面上、不做逐帧渲染（动效由 ffmpeg 按时间裁窗做出来）。

---

### Step 0, 先有拉片数据

要一份 `shots.json`（`video-shots` 的产出）和**对应的原片**。有 `frames/`（每镜关键帧）
更好, , 镜头表里会用起手帧当缩略图，没有就留空格。

**shots.json 必须和这条片子对得上**：`meta.durationSeconds` 与原片不符就别合，
先回去把拉片做对。

### Step 1, 先看几何，再动视频

```bash
node {baseDir}/scripts/video-sync.mjs plan shots.json --video <片>
```

只算不合成，几秒出结果：版式、画面区、面板区、输出尺寸。**先确认这三个数字合理再往下走**, , 合成一条 3 分钟的片子要一两分钟，算错了再重来不值。

不满意就调：`--panel <比例>`（横版是面板高÷画面高，默认 0.8；竖版是面板宽÷画面宽，默认 1.6）、
`--width` / `--height`（画面区的上限，默认 1920 / 1080）。

**原片太小就放大画面区**：面板是跟着画面区算的，640×360 的片子面板只有 640×288，
四列挤在一起读不了。`--scale 2` 把画面区放到 1280×720、面板放到 1280×576，宽高比不动。
默认是 1（只缩不放）。`plan` / `panels` / `compose` / `export` 都要带上同一个 `--scale`。

### Step 2, 渲面板

```bash
node {baseDir}/scripts/video-sync.mjs panels shots.json --video <片> \
  --frames frames --lang zh|en [--out panels] [--chrome <路径>]
```

产出 `static.png`（底板）、`list-dim.png` / `list-lit.png`（整张镜头表铺开的长图，
一暗一亮）、`layout.json`（每行的位置，合成时按它算滚动），外加一张 `panels/panel.html`, , **用浏览器打开它就能预览面板**，`#S07` 换镜头看效果（预览里的滚动动效和导出一致）。

**改布局就改 `{baseDir}/scripts/panel.css`**，改完重跑 `panels` 就是新版面，
不用碰脚本、不用重新想截图逻辑。样式约定见 `{baseDir}/references/panel-style.md`。

### Step 3, 合成

The rest: https://github.com/eternityspring/reelbench-skills/blob/HEAD/skills/video-sync/SKILL.md
