开发视频编辑器时,播放、裁剪、字幕和导出往往最先得到关注。真正开始剪长视频后,却会频繁遇到另一类需求:

“这里开始第二章。”

“这个镜头要对齐音乐重拍。”

“这一段保留,先记下修改意见。”

最近,我在开源项目 Timeline Studio 中补齐了时间线标记功能,并进一步接入 CLI、MCP 和 Agent Skill。用户可以在浏览器里操作标记,AI Agent 也能通过命令读取、添加和修改它们。

本文结合这次实现,介绍从 React 交互到项目数据,再到 Agent 命令接口的完整设计,以及区间吸附、重复 ID、浮点精度等容易踩坑的细节。

项目入口:

Timeline Studio 使用 React 和 Vite 构建,是一个本地优先的浏览器视频编辑器。本文涉及的 Agent 标记能力已随 Skill v1.0.7 发布。

先定义标记需要表达什么

这次实现支持四种标记:

类型用途时间字段
marker节拍、动作点、切镜头提示time
chapter章节或主题起点time
range待处理或待复查区间time、endTime
note与时间关联的修改意见time

它们共享同一个数据模型:

{
  "id": "product-review",
  "type": "range",
  "time": 5,
  "endTime": 8,
  "title": "产品展示",
  "notes": "检查字幕出现时机",
  "color": "violet"
}

时间统一使用项目时间轴上的秒数,标题和备注支持中文等 Unicode 文本。

这些数据保存在项目的 timelineMarkers 中,随可移植的 .timeline 项目一起保存。

数据层有一个需要提前确定的约束:标记不参与媒体时长计算。

例如,一个 60 秒的视频,在 90 秒处添加规划标记,不能让导出视频延长到 90 秒。章节标记也不会自动生成画面标题或 MP4 容器章节。

这让标记成为独立的项目注释。Agent 只需要整理章节或补充备注时,直接生成新的项目文件即可,无需重新渲染视频。

React 交互:默认紧凑,需要时展开

时间线的垂直空间很宝贵。如果标记始终占据独立轨道,长标题和区间会持续挤压素材轨道的可见区域。

最终采用的交互是:

  • 默认将标记合并到刻度轴,显示为紧凑旗标。
  • 点击工具栏旁的展开按钮,显示标题和区间跨度。
  • 按 M 在播放头位置快速添加标记。
  • 按 Shift + M 打开标记管理面板。

拖动过程中,组件使用临时预览状态展示位置变化,松开指针后再提交项目修改。用户按下 Escape 或发生取消事件时,可以丢弃这次拖动。

这样的状态划分,让连续的鼠标移动与最终的项目修改拥有清楚的边界。

吸附阈值应该跟随屏幕距离

如果使用固定的时间阈值,例如“距离目标小于 0.2 秒就吸附”,不同缩放级别下的体验会差很多。

时间线放大后,0.2 秒可能占据很宽的屏幕距离;缩小后,它又可能几乎不可见。

因此,实现中先定义像素阈值,再换算为秒数:

const thresholdSeconds =
  10 / railWidth * timelineDuration;

其中:

  • 10 是吸附距离,单位为像素。
  • railWidth 是时间轨道宽度。
  • timelineDuration 是该轨道对应的时间跨度。

标记复用时间线已有的吸附点集合,可以对齐播放头、素材边界和其他标记,并显示共同的对齐辅助线。按住 Alt 可以临时绕过吸附。

这个处理能让用户在缩放时间线后,仍然获得接近一致的拖动手感。

区间移动,需要同时考虑两个端点

区间吸附比单点标记多一个约束:整体移动时必须保留长度。

假设一个区间原本是 5–8 秒,长度为 3 秒。如果结束端吸附到 12 秒,新的范围应该是 9–12 秒。

核心计算可以简化为:

const nextStart =
  movingEdge === "end"
    ? targetTime - duration
    : targetTime;

const nextEnd = nextStart + duration;

实际实现会分别计算起点、终点的吸附候选,选择距离更近的结果。

如果只修正一个端点,就容易将“整体移动”意外变成“缩短或拉长区间”。因此,整体平移和调整区间终点需要分别处理。

让 Agent 使用同一套命令逻辑

浏览器交互完成后,还需要解决 Agent 如何操作的问题。

这次没有为 MCP 再实现一套标记增删改,而是采用下面的结构:

Agent Skill
    ↓
CLI / MCP
    ↓
共享项目命令引擎
    ↓
新的 .timeline 项目

各层职责分别是:

层级职责
Skill描述任务流程、时间依据与验证要求
CLI提供本地命令入口
MCP将已有命令能力暴露给 Agent
命令引擎执行校验、修改、版本检查和差异计算

MCP 适配层调用现有 CLI 运行器,实际编辑逻辑集中在共享命令引擎中。后续修复校验规则时,两种调用方式可以共同受益。

读取项目和标记的命令如下:

npm run agent -- project.inspect /projects/input.timeline

npm run agent -- marker.inspect /projects/input.timeline

也可以查询单个标记:

npm run agent -- marker.inspect \
  /projects/input.timeline product-review

MCP 对应提供只读工具:

timeline_marker_inspect

写入操作包括:

marker.add
marker.update
marker.delete

一个完整的 Agent 修改示例

假设项目检查结果显示当前版本为 0,已有一个 ID 为 product-review、范围为 5–8 秒的标记。

用户希望将它移动到 9 秒,并在 11 秒添加一条结尾修改意见,可以生成下面的计划:

{
  "schemaVersion": 1,
  "project": "/projects/input.timeline",
  "baseRevision": 0,
  "operations": [
    {
      "id": "move-product-review-v1",
      "type": "marker.update",
      "markerId": "product-review",
      "time": 9
    },
    {
      "id": "add-ending-note-v1",
      "type": "marker.add",
      "markerId": "ending-note",
      "markerType": "note",
      "time": 11,
      "title": "结尾节奏",
      "notes": "用户意见:结尾多留一点呼吸",
      "color": "rose"
    }
  ],
  "output": {
    "project": "/projects/output-marked.timeline"
  }
}

实际使用时,路径、版本号和标记 ID 都应替换为检查得到的值,备注也应来自真实需求。

这里有两个容易混淆的地方:

  • id 标识一次操作,markerId 标识项目中的标记。
  • type 表示操作类型,markerType 表示新增标记的类型。

更新区间时只提供 time,会保持区间长度。上面的操作因此将 5–8 秒移动为 9–12 秒。

准备好计划后,先检查结构,再预览差异:

node skills/edit-timeline-studio/scripts/validate_edit_plan.mjs \
  /projects/markers-plan.json

npm run agent -- project.diff /projects/markers-plan.json

确认结果符合预期,再执行:

npm run agent -- project.run /projects/markers-plan.json

npm run agent -- marker.inspect \
  /projects/output-marked.timeline

语义差异会列出新增、删除和修改的标记,并提供修改前后的数据。对于只移动区间的任务,预期变化应该集中在标记时间,而不是素材轨道。

版本、事务和文件保护需要落实到代码

Skill 会要求 Agent 先检查项目、预览差异,再应用操作。命令引擎则负责实际保护:

  • baseRevision 不匹配时返回 REVISION_CONFLICT。
  • 已执行的操作 ID 再次提交时,不重复应用。
  • 一批操作中出现失败时,不写出部分修改结果。
  • 输出路径不能覆盖输入项目或已有文件。

这种分工能让工作流程和执行约束相互配合。

例如,Agent 收到版本冲突后,应重新读取项目并审查计划,而不是直接把版本号改成最新值后继续执行。

两个在批量操作中暴露的问题

第一个问题是旧项目中的重复 ID。

假设导入数据里有三个标记都叫 x,读取时将它们规范化为:

x
x-2
x-3

如果每执行一次操作都重新生成 ID,那么删除第一个标记后,后续标记的身份可能发生变化。接着执行“更新 x-2”,就可能修改错误的对象。

修复方式是在事务开始时,对项目副本统一规范化一次,并固定这组 ID。后续操作按固定身份查找,不再根据数组位置重新分配。

生成重复 ID 的后缀时,也需要给后缀预留长度,避免规范化后的结果超过字段长度限制。

第二个问题是浮点精度。

一个位于 1000 秒附近、长度为 1 毫秒的区间,经过减法计算后,结果可能略小于 0.001。如果直接严格比较,就会把合法移动误判为区间过短。

这里需要处理浮点误差,同时继续拒绝真正无效的区间输入。单纯放宽所有时间校验,会带来新的数据问题。

Agent 的时间点必须有依据

浏览器拖动可以使用像素吸附,CLI 和 MCP 接收的则是精确秒数。

Agent 应从项目检查结果、片段边界或经过验证的音频分析中获得时间信息。

如果使用源视频里的事件时间,还需要考虑裁剪与变速。恒定倍速下可以这样换算:

项目时间 =
片段在项目中的起点
+(事件源时间 - 源裁剪起点)/ 播放倍速

启用速度曲线后,需要使用对应的源时间映射,不能简单除以平均速度。

音乐标记也遵循相同原则:当前标记命令负责保存时间点,本身不执行自动节拍检测。生成节拍提示前,仍然需要可靠的节拍起点、速度信息或分析结果。

验证结果与使用入口

这次验证覆盖了 CLI、MCP 实际调用,以及一个完整的 Agent 工作流:

将“产品展示”从 5–8 秒移到 9–12 秒,在 11 秒新增中文备注,并保存为新项目。

验证内容包括:

  • 区间长度保持 3 秒。
  • 中文备注完整保存。
  • 其他标记保持不变。
  • 原文件哈希和归档媒体字节保持不变。
  • 媒体时长与渲染计划保持不变。

此外,还检查了重复执行、事务回滚、异常 ID 和最小区间边界,相关实现通过了类型检查和生产构建。

如果你希望实际体验,可以打开 Timeline Studio 在线编辑器,尝试添加标记、拖动区间以及播放头吸附。

如果你正在开发 React 编辑器、MCP 工具或 Agent 文件操作接口,可以到 GitHub 仓库 查看实现。欢迎 Star 关注更新,也欢迎通过 Issue 分享实际剪辑需求。

给 Codex 安装这一版 Skill,可以使用:

gh skill install MartinDelophy/ai-video-editor edit-timeline-studio \
  --pin v1.0.7 --agent codex --scope user

使用本地命令仍需要准备 Timeline Studio 仓库和相应 Node 依赖。完整说明见 v1.0.7 Release 和时间线标记工作流文档。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐