Communitygithub.com

businessbaum/vedit

生成 ASS 字幕文件并将其烧录到视频中,支持德语特定规则,如像素宽度分组和字体选择。

vedit 是什么?

这个技能专为需要生成和烧录字幕(尤其是德语字幕)的场景设计。它从词级别的转录生成 ASS 字幕文件,并通过 libass 渲染到最终视频中。该技能的核心是针对德语的特定优化:它不采用常见的 `max_chars` 字符数分组规则(因为德语复合词如 'Geschwindigkeitsbegrenzung' 很容易超限),而是使用基于像素宽度预算的分组方法。同时,它严格限制了可用的字体文件,要求使用静态单字重字体,避免可变字体导致 libass 静默回退到 Arial 的问题。支持 Cursor、Claude Code 等 AI 编码工具,流程包括:选择预设、生成 ASS 文件、通过覆盖文本修正(如数字、专有名词)、最终烧录,以及强制性的 QC 检查(确认字体实际被正确加载,而非静默回退)。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/businessbaum/vedit/tree/HEAD/.agents/skills/captions

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

captions

从词级别的转录生成 ASS 字幕文件并将其烧录到渲染视频中。 参数:$project 项目 slug,$preset 来自 presets/captions/*.json 的名称。完整请求在 $ARGUMENTS 中。空的 $ 占位符表示该技能是自动触发的,而不是通过斜杠调用:从会话上下文中的 PIPELINE_STATE 获取项目并说明选择了哪个项目。动词名称遵循构建契约;python engine/ve.py --help 为准。

决定此技能是否生效的两个德语特定规则

1. 永不使用 max_chars 分组

德语复合名词是单个不可分割的 token,会突破任何字符限制。 Geschwindigkeitsbegrenzung 有 26 个字符,在 90 像素字号下大约占据 1920 帧的 60%。 max_chars: 22 规则无法将其拆分,因此它要么溢出帧边界,要么分组器静默地发出一行它承诺不会发出的内容。

改为使用 像素宽度预算 进行分组,并对单个超出预算的词应用自动缩小规则。estimate_text_width_px() 负责测量; group_words() 负责预算。详细信息位于 docs/fonts.md 和 engine/vedit/captions.py 的模块文档字符串中。

这是最常见的德语字幕失败原因,并且完全是可避免的。

2. 字体必须是静态单字重文件,以其精确的 nameID 1 命名

可变字体无法通过 ASS 进行字重选择。InterVariable.ttf 通过 libass 渲染时与 Arial 字节相同 - 相同的 md5 - 同时日志显示它加载了该文件。 返回码 0,无警告。Style: D,Inter,...,Bold=-1 会得到 DirectWrite Arial,直到导出到 YouTube 时才会有人注意到。

仅使用以下字体,并使用精确的 Fontname 字符串和 Bold 值:

文件ASS FontnameBold
Inter-Regular.ttfInter0
Inter-Bold.ttfInter-1
Inter-SemiBold.ttfInter SemiBold0
Inter-ExtraBold.ttfInter ExtraBold0
Montserrat-Bold.ttfMontserrat-1
Montserrat-ExtraBold.ttfMontserrat ExtraBold0

assets/fonts/ 目录只包含字体文件。 libass 会加载它找到的每个文件,并且对任何其他文件都会报错 - 一个多余的 README.md 会产生 Error opening memory font 'README.md'。 该目录在设置中因此被列为 ask 权限;如果某个文档与字体相关,它应该放在 docs/fonts.md 中。

步骤 1 - 选择预设

presets/captions/*.json 包含样式、分组预算、位置和高亮行为。 根据 output.platform 选择,而不是凭喜好:

  • 垂直平台需要大号、居中、1-3 个词、位于安全区域高处 - 底部三分之一被平台自身的 UI 占据。
  • YouTube 长视频需要两行、底部锚定、大小适合手机观看者阅读。

在生成之前与用户确认选择,并说明更改的内容。

步骤 2 - 生成 ASS

python engine/ve.py captions $project --preset $preset

两个结构要求,违反时都会静默失败:

  • 23 字段的 V4+ Style: Format: 行(Name .. Encoding,libass 自身的字段列表 - 第 24 个字段 AlphaLevel 是 SSA v4 而不是 ASS V4+)和 10 字段的 Events Format: 行必须是规范的,并且其下的每一行必须有精确相等的列数。数量不匹配会导致 libass 返回码 0,在渲染文本前添加一个多余的逗号,并静默丢失轮廓和粗体。 使用 5 字段的 Format 对 6 字段的 Dialogue 可以复现。captions.validate_ass() 在任何烧录之前检查这两个计数;它不是可选的,并且 burn_in() 在失败时会拒绝执行。
  • 文件是 UTF-8 编码。当文件是 UTF-8 时,德语变音符号可以通过 libass 正确往返 - Größe Straße Übermäßig äöüÄÖÜß 已通过端到端验证。

辅助文件写入到 projects/<slug>/out/captions.ass,并从 captions.sidecar_path 引用。

时间码来自与剪切相同的词列表,并通过 _computed 中的时间线位置进行映射。因此,任何剪切更改都会使字幕失效 - 在重新剪切后重新生成,而不是修补。

步骤 3 - 修复文本,而不是转录

数字是可预见的麻烦。--suppress_numerals 在转录时是强制性的,因此当说话者说“20”时,转录会显示 zwanzig。在 captions.overrides[] 中修复为 {word_index, text},永远不要通过编辑转录来修复 - 转录是项目中每个词范围的锚点,重写它会取消剪切同步。

专有名词、产品名称和 GmbH 风格的缩写也是如此。通过扫描低 score 的词一次性收集它们:对齐分数作为填充信号是无用的,但它是“对齐器不确定这个词是什么”的良好信号,而这正是专有名词的样子。

步骤 4 - 烧录

python engine/ve.py render $project

烧录在最终的混流中一次性完成,在拼接后的视频上 - 永远不要在分段上进行。分段烧录会在每个边界处重新渲染相同的字幕,并在切口的每一侧产生部分行。

subtitles= 和 fontsdir= 的转义是精确且不明显的。全程使用正斜杠,并且值用引号括起来,驱动器冒号前加单个转义:

subtitles='C\:/Users/me/.../out/captions.ass':fontsdir='C\:/Users/me/.../assets/fonts'

vedit/ffmpeg.py 中的 escape_filter_path() 负责此操作,并返回包含其引号的字符串 - 不要添加自己的引号。决定因素在于引号,而不是嵌套深度:裸的单个转义形式会因 No option name near '/Users/...' 而失败,只有带引号的形式才能在路径包含逗号、分号或括号时幸存,而这些在 Windows 上都是合法的。 escape_filter_path_nested() 为任何需要的人返回加倍的未加引号变体 C\\:/...,并附带该限制的文档说明。

所有这一切都通过 Python 的 argv 列表进行。在 Git Bash 中构建的相同路径会变成 C\;C:\Program Files\Git\Users\... - 已现场复现,非纯理论。

设置 captions.enabled 和 captions.burn_in,并将 stage 设置为 "captions"。

步骤 5 - 证明字体实际已加载

QC 检查 (e) 是强制性的,这是捕获静默回退的唯一方法。 使用项目样式渲染一个字幕帧,并使用 Arial 控制样式渲染另一个,并断言哈希值不同。相同的哈希值意味着 libass 回退了,并且导出将使用错误的字体。

然后还要确认:Format: 行上的字段计数、没有行超出像素预算、并且没有字幕以留下片段的方式跨越分段边界。

失败模式

症状原因修复
字幕看起来像 Arial可变字体,或错误的 Fontname静态文件 + 精确的 nameID 1 字符串;运行 QC (e)
文本中出现多余逗号,无轮廓格式错误的 Format: 行发出规范的 24/10 字段行
Error opening memory font 'README.md'assets/fonts/ 中存在非字体文件该目录只放字体
一个长词超出帧边界使用 max_chars 分组像素宽度预算加单字自动缩小
No option name near '/Users/...'fontsdir 单个转义在冒号前加双反斜杠
字幕拼出数字--suppress_numerals,按设计captions.overrides 条目
重新剪切后字幕不同步过时的 ASS重新生成;永远不要手动修补时间码

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

businessbaum/vedit

This skill should be used when the user asks for mechanical tightening of material that is already selected - "mach den Rohschnitt", "bau den Rohschnitt", "schneid die Ähms raus", "nimm die Füllwörter raus", "entferne die Pausen", "kürz die Pausen", "schneid die Versprecher raus", "mach den Radioschnitt", "straff das", "build the rough cut", "cut the fillers", "remove the ums", "tighten the pauses", "trim the dead air", "cut the retakes", "radio edit". Turns kept story blocks into enabled segments in project.json. Does not decide what the video is about - that is cut-story.

businessbaum/vedit

This skill should be used when the user asks to decide what the video is actually about before any mechanical cutting - "bau die Story", "was soll ins Video", "was kann raus", "welche Themen bleiben", "kürz das auf 6 Minuten", "mach daraus ein kurzes Video", "wo ist der Hook", "sortier die Themen", "worum geht es hier eigentlich", "build the story", "decide what stays", "structure this", "cut this down to N minutes", "find the hook", "reorder the topics". This is the CONTENT-selection pass that writes the "story" block in project.json; it runs after transcribe and before cut-rough.

businessbaum/vedit

This skill should be used when the user asks for the finished video file - "exportier das", "render das fertig", "mach das final", "renderte das Video", "mach den Endexport", "bau das Video zusammen", "mach es fertig für YouTube", "gib mir die MP4", "export the final", "render the final cut", "make the master", "export for TikTok", "give me the mp4". Covers segment rendering with the cache, guarded concat, the one-pass audio, loudness normalisation and the single mux.

相关技能