Pure MoonBit toolkit for subtitles, timecode, creator cleanup, delivery checks, and post-production QC.
Dependencies
| 项目 | 状态 |
|---|---|
| GitHub 仓库 | MaoDingA/moonbitpostqc |
| GitLink 仓库 | 已与 GitHub 同步 |
| 项目申报书 | 已提交(2026 MoonBit 国产基础软件开源大赛) |
| mooncakes.io | moon add MaoDingA/moonpost |
| 本地 Demo | ./wasm-demo/build.sh && python3 -m http.server 8765 -d wasm-demo/public |
| 用户体验指南 | user-experience/README.md |
| 方向 | 目标 | 默认策略 | 典型入口 |
|---|---|---|---|
| Creator | 自媒体、短视频、口播、AI 字幕发布前清洗 | 温和提示,安全清理,不默认失败 | creator check、creator clean |
| Delivery | 影视、OTT、广播、本地化字幕交付验收 | 严格 QC,Error 可触发非零退出码 | delivery check、delivery subtitle-check |
| 用户 | MoonPost 能提供什么 |
|---|---|
| 创作者与发布者 | 上传前检查 AI 字幕、口播字幕、标点、行长、阅读速度和平台化字幕习惯。 |
| 剪辑师与字幕编辑 | 在交付或发布前发现重叠、空字幕、超长行、过短时长和阅读速度问题。 |
| 本地化团队 | 在翻译、审校和回传过程中做可重复的 SRT/WebVTT/ASS 检查与双语字幕处理。 |
| 后期与媒体 QA | 检查时间码、帧网格、cue 间隔,并生成可读且可自动化消费的 QC 报告。 |
| 交付工程与平台运营 | 使用 JSON report 和失败退出码,把字幕 QC 接入批处理或 CI 流程。 |
moon 0.1.20260512
moonc v0.9.2moon add MaoDingA/moonpost// 字幕解析与写出
let track = parse_srt(input)
// 时间码与帧率
let tc = parse_timecode("01:00:00:00", FrameRate::fps_25())
// QC 校验
let issues = check_cues(track.cues, default_profile())moon test --target native
moon test --target wasm-gcmoon run cmd/main --target native -- --helpmoon run cmd/main --target native --moon build cmd/main --target native
./scripts/e2e-acceptance.sh
_build/native/debug/build/cmd/main/main.exe subtitle convert examples/good.srt --to ass
_build/native/debug/build/cmd/main/main.exe creator clean examples/creator/ai-clean-bad.srt --verbose
_build/native/debug/build/cmd/main/main.exe delivery subtitle-check examples/bad.srt --profile nrta --fail-on-warningmoon run cmd/main --target native -- creator check examples/bilingual.srt --profile bilingual
moon run cmd/main --target native -- creator clean examples/good.srt --profile douyin -o fixed.srt
moon run cmd/main --target native -- delivery check examples/delivery-package/good --json
moon run cmd/main --target native -- delivery subtitle-check examples/bad.srt --profile ott-zh --fps 25
moon run cmd/main --target native -- delivery subtitle-check examples/delivery/dcp-source-srt-bad.srt --profile dcp-source-srt --fail-on-warning
moon run cmd/main --target native -- delivery subtitle-check examples/delivery/dcp-source-srt-bad.srt --profile dcp-frame-strict --fail-on-warningmoon run cmd/main --target native -- creator check examples/creator/ai-clean-bad.srt --profile douyinexamples/creator/ai-clean-bad.srt
summary: 0 errors, 15 warnings
WARN C601 cue#1 | repeated word: '然后'
WARN C601 cue#2 | repeated word: '就是'
WARN C602 cue#3 | ASR marker: ASR music marker '[Music]'
WARN C401 cue#3 | possible confusion: '认真的做' should be '认真地做'
WARN C402 | inconsistent term: 'moonpost' vs canonical 'MoonPost'
WARN C402 | inconsistent term: 'webvtt' vs canonical 'WebVTT'moon run cmd/main --target native -- explain C601
moon run cmd/main --target native -- explain E101moon run cmd/main --target native -- profile list
moon run cmd/main --target native -- profile show creator --name douyin
moon run cmd/main --target native -- profile show delivery --name ott-zhmoon run cmd/main --target native -- delivery check examples/delivery-package/good --json
moon run cmd/main --target native -- delivery check examples/delivery-package/checksum-bad --fail-on-error
moon run cmd/main --target native -- delivery check examples/delivery-package/bad --profile distribution --subtitle-profile ott-zh --fps 25
moon run cmd/main --target native -- delivery check examples/delivery-package/srt-source-bad --profile distribution --subtitle-profile dcp-source-srt --fail-on-warning
moon run cmd/main --target native -- delivery check examples/delivery-package/srt-source-bad --profile distribution --subtitle-profile dcp-frame-strict --fail-on-warningmoon run cmd/main --target native -- delivery subtitle-check examples/delivery/ott-good.srt --profile ott-zh --json
moon run cmd/main --target native -- delivery subtitle-check examples/delivery/srt-basic-good.srt --profile srt-basic --fail-on-error
moon run cmd/main --target native -- delivery subtitle-check examples/delivery/dcp-source-srt-bad.srt --profile dcp-source-srt --fail-on-warning
moon run cmd/main --target native -- delivery subtitle-check examples/delivery/dcp-source-srt-bad.srt --profile dcp-frame-strict --fail-on-warning
moon build cmd/main --target native
_build/native/debug/build/cmd/main/main.exe delivery subtitle-check examples/delivery/ott-bad.srt --profile ott-zh --fps 25 --fail-on-error| Profile | 适用场景 | 时长/阅读速度 | 帧网格 |
|---|---|---|---|
| srt-basic | 普通 SRT 源文件预检,检查格式、序号、WebVTT settings 混入和样式标记风险。 | 500ms 到 7000ms,42 CPL / 20 CPS。 | 默认不开启。 |
| dcp-source-srt | DCP 制作前的 SRT 源文件风险预检,额外提示首条字幕过早出现等转制风险。 | 500ms 到 6000ms,32 CPL / 17 CPS。 | 默认不开启,不报 W401/W402。 |
| dcp-frame-strict | 最终制版前需要主动打开的 DCP 帧网格和最小间隔严格检查。 | 1000ms 到 6000ms,32 CPL / 17 CPS。 | 24fps,最小 2 帧间隔。 |
MoonPost - subtitle, timecode and post-production QC toolkit
Usage:
moonpost <command> [options]
Commands:
creator Check and clean creator-platform subtitle files with gentle hints
delivery Run strict delivery-profile subtitle checks
subtitle Convert subtitle formats
timecode Convert SMPTE-style timecode and frame counts
retime Shift, speed-convert, or snap subtitle timing
merge Merge two subtitle tracks into bilingual cues
split-bilingual
Split bilingual cues into two subtitle tracks
qc Compatibility alias for subtitle delivery QC
explain Explain a QC diagnostic code
profile List or show profile configurationsmoon run cmd/main --target native -- qc examples/bad.srt --fps 25 --profile streamingmoon run cmd/main --target native -- qc examples/good.srt --jsonmoon run cmd/main --target native -- qc examples/good.srt --text-style bilingualmoon build cmd/main --target native
_build/native/debug/build/cmd/main/main.exe qc examples/bad.srt --fail-on-errorexamples/bad.srt
summary: 3 errors, 8 warnings
WARN W201 cue#1 | duration 377ms below minimum 800ms
WARN W310 cue#1 | 225 cps exceeds max 20
WARN W311 cue#1 | 34 wps exceeds max 3
WARN W203 cue#1 | line 1: 85 chars, max 42
WARN W401 cue#1 | cue timing is not aligned to 40ms frame grid
WARN W202 cue#2 | duration 7600ms above maximum 7000ms
WARN W203 cue#2 | line 1: 49 chars, max 42
ERROR E102 cue#3 | end time must be after start time
ERROR E201 cue#3 | empty cue text
WARN W401 cue#3 | cue timing is not aligned to 40ms frame grid
ERROR E101 cue#1 | overlaps cue#2 by 100ms| Profile | 最大 CPL | 最大 CPS | 最大行数 | 最小时长 | 最大时长 | 最小间隔 | 帧网格 |
|---|---|---|---|---|---|---|---|
| default | 42 | 20 | 2 | 800ms | 7000ms | 2 frames | 无 |
| streaming | 42 | 20 | 2 | 800ms | 7000ms | 2 frames | 25fps |
| cinema | 32 | 17 | 2 | 1000ms | 6000ms | 2 frames | 24fps |
| social-video | 30 | 24 | 2 | 500ms | 5000ms | 1 frame | 30fps |
| Code | 级别 | 含义 |
|---|---|---|
| E101 | Error | 当前 cue 与下一个 cue 时间重叠。 |
| E102 | Error | cue 结束时间不晚于开始时间。 |
| E201 | Error | cue 文本为空。 |
| W100 | Warning | SRT 源文件 profile 收到 WebVTT 输入。 |
| W101 | Warning | SRT cue 缺少数字序号。 |
| W102 | Warning | SRT cue 数字序号不连续。 |
| W201 | Warning | cue 时长短于当前 profile 允许值。 |
| W202 | Warning | cue 时长长于当前 profile 允许值。 |
| W203 | Warning | 某一行文本超过当前 CPL 限制。 |
| W204 | Warning | cue 行数超过当前 profile 允许值。 |
| W310 | Warning | 阅读速度超过当前 CPS 限制。 |
| W401 | Warning | cue 时间未对齐当前帧网格。 |
| W402 | Warning | 与下一个 cue 的间隔低于当前帧间隔限制。 |
| W501 | Warning | 显式开启文本规范检查后,发现混用标点风格。 |
| W502 | Warning | 显式开启文本规范检查后,发现重复标点。 |
| W503 | Warning | 显式开启文本规范检查后,发现可疑括号或引号配对。 |
| W510 | Warning | 显式开启文本规范检查后,发现超长连续字符块。 |
| W511 | Warning | 显式开启文本规范检查后,发现孤立单字行。 |
| W520 | Warning | 显式开启文本规范检查后,发现词库中的疑似错别字或术语问题。 |
| W601 | Warning | SRT 源文件包含字体或颜色标记,转制时可能不被保留。 |
| W602 | Warning | SRT 源文件包含 ASS/SSA 样式覆盖,转制时可能不被保留。 |
| W603 | Warning | SRT 时间行包含 WebVTT-style settings。 |
| W701 | Warning | DCP-source SRT profile 下首条字幕离节目开头太近。 |
moon run cmd/main --target native -- timecode to-frames 01:00:00:00 --fps 2501:00:00:00 @25fps = 90000 framesmoon run cmd/main --target native -- timecode from-frames 90000 --fps 2590000 frames @25fps = 01:00:00:00moon run cmd/main --target native -- timecode convert 01:00:00:00 --from 23.976 --to 2501:00:00:00 @23.976fps = 01:00:03:15 @25fpsmoon run cmd/main --target native -- timecode imf-from '01:00:00;00' --fps 29.97df01:00:00;00 @29.97df = editRate 30000/1001 timecodeRate 30 dropFrame true startAddress 01:00:00:00moon run cmd/main --target native -- timecode edl-event '001 AX V C 01:00:00:00 01:00:10:00 00:00:00:00 00:00:10:00' --fps 25001 AX V C 01:00:00:00 01:00:10:00 00:00:00:00 00:00:10:00| Value | 含义 |
|---|---|
| 23.976, 23.98, 23976, 2398 | 23.976fps |
| 24 | 24fps |
| 25 | 25fps |
| 47.952, 47.95, 47952, 4795 | 47.952fps |
| 48 | 48fps |
| 29.97, 29.97ndf, 2997, 2997ndf | 29.97 non-drop |
| 29.97df, 29.97DF, 29.97-drop, 29.97 drop, 2997df, 2997drop | 29.97 drop-frame |
| 30 | 30fps |
| 50 | 50fps |
| 59.94, 59.94ndf, 5994, 5994ndf | 59.94 non-drop |
| 59.94df, 59.94DF, 59.94-drop, 59.94 drop, 5994df, 5994drop | 59.94 drop-frame |
| 60 | 60fps |
| 72 | 72fps |
| 96 | 96fps |
| 100 | 100fps |
| 119.88, 11988 | 119.88fps |
| 120 | 120fps |
moon run cmd/main --target native -- subtitle convert examples/good.srt --to webvttmoon run cmd/main --target native -- subtitle convert examples/good.srt --to ass -o output.assmoon run cmd/main --target native -- subtitle convert examples/anime.ass --to srt -o output.srtmoon run cmd/main --target native -- subtitle convert examples/good.srt --to webvtt -o output.vttmoon run cmd/main --target native -- subtitle convert examples/good.vtt --to srt -o output.srtmoon run cmd/main --target native -- subtitle normalize examples/good.srt --punctuation bilingual -o fixed.srtmoon run cmd/main --target native -- retime examples/good.srt --offset +1200ms1
00:00:02,200 --> 00:00:04,400
Hello, welcome to MoonBit.
2
00:00:05,200 --> 00:00:06,700
This subtitle is ready for QC.moon run cmd/main --target native -- retime examples/good.srt --offset -500ms -o shifted.srtmoon run cmd/main --target native -- retime examples/good.srt --from-fps 23.976 --to-fps 25 -o converted.srtmoon run cmd/main --target native -- retime examples/good.srt --snap-fps 25 -o snapped.srt需要支持 WebAssembly GC 的浏览器(Chrome 119+ / Firefox 120+ / Edge 119+)
./wasm-demo/build.shpython3 -m http.server 8765 --directory wasm-demo/publichttp://localhost:8765| Package | 用途 |
|---|---|
| MaoDingA/moonpost/timecode | 帧率、时间码解析、帧数/时长/区间转换、同帧率算术和 drop-frame 计算。 |
| MaoDingA/moonpost/subtitle | SRT/WebVTT/ASS 解析、时间戳格式化、字幕写出。 |
| MaoDingA/moonpost/qc | QC profiles、issue 模型、cue 检查、报告格式化。 |
| MaoDingA/moonpost/creator | 创作者字幕 profile、文本检查和清洗流程。 |
| MaoDingA/moonpost/delivery | 交付目录资产、manifest、checksum 和字幕 profile。 |
| MaoDingA/moonpost/dcp | DCP AssetMap、PKL、CPL 的轻量模型和一致性检查。 |
| MaoDingA/moonpost/retime | cue 的整体偏移、帧率转换和帧吸附。 |
| MaoDingA/moonpost/align | 双语字幕合并和拆分 helpers。 |
| MaoDingA/moonpost/cli | CLI 参数解析。 |
| MaoDingA/moonpost/wasm_demo/core | demo 使用的 Wasm 导出 qc_subtitle。 |
parse_timecode(String, FrameRate) -> Timecode?
parse_timecode_result(String, FrameRate) -> Result[Timecode, TimecodeParseError]
parse_timecode_with_policy(String, FrameRate, TimecodeParsePolicy) -> Result[Timecode, TimecodeParseError]
Timecode::to_frames() -> Int
Timecode::format() -> String
Timecode::add_frames(Int) -> Timecode
Timecode::sub_frames(Int) -> Timecode
Timecode::add_duration(Duration) -> Timecode?
Timecode::sub_duration(Duration) -> Timecode?
Timecode::frame_distance(Timecode) -> Int?
Timecode::is_before(Timecode) -> Bool?
Timecode::is_after(Timecode) -> Bool?
FrameRate::frames_to_timecode(Int) -> Timecode
FrameRate::frames_to_timecode_with_wrap(Int, TimecodeWrapMode) -> Timecode
FrameRate::parse(String) -> FrameRate?
FrameRate::label() -> String
FrameRate::nominal_fps() -> Int
FrameRate::fps_numerator() -> Int
FrameRate::fps_denominator() -> Int
convert_timecode(Timecode, FrameRate) -> Timecode
rescale_ms_between_rates(Int, FrameRate, FrameRate) -> Int
Duration::from_frames(Int, FrameRate) -> Duration
Duration::from_ms(Int, FrameRate) -> Duration
Duration::to_frames() -> Int
Duration::to_ms() -> Int
Duration::format() -> String
Duration::add(Duration) -> Duration?
Duration::sub(Duration) -> Duration?
Duration::scale(Int) -> Duration
TimecodeRange::new(Timecode, Timecode) -> TimecodeRange?
TimecodeRange::duration() -> Duration?
TimecodeRange::contains(Timecode) -> Bool?
TimecodeRange::overlaps(TimecodeRange) -> Bool?
TimecodeRange::shift(Duration) -> TimecodeRange?
RationalSeconds::parse(String) -> RationalSeconds?
RationalSeconds::format() -> String
RationalSeconds::to_frames(FrameRate) -> Int
RationalSeconds::from_frames(Int, FrameRate) -> RationalSeconds
FcpXmlTimecodeAttrs::to_timecode() -> Result[Timecode, TimecodeInteropError]
FcpXmlTimecodeAttrs::from_timecode(Timecode) -> FcpXmlTimecodeAttrs
ImfTimecode::to_timecode() -> Result[Timecode, TimecodeInteropError]
ImfTimecode::from_timecode(Timecode) -> ImfTimecode
ImfEditRate::from_frame_rate(FrameRate) -> ImfEditRate
ImfEditRate::to_frame_rate(Bool) -> Result[FrameRate, TimecodeInteropError]
ImfCompositionTimecode::to_timecode() -> Result[Timecode, TimecodeInteropError]
ImfCompositionTimecode::from_timecode(Timecode) -> ImfCompositionTimecode
SmpteUserBits::from_hex(String) -> Result[SmpteUserBits, TimecodeInteropError]
SmpteUserBits::format_hex() -> String
SmpteTimecodeWord::from_timecode(Timecode) -> SmpteTimecodeWord
SmpteTimecodeWord::to_timecode() -> Result[Timecode, TimecodeInteropError]
SmpteTimecodeWord::pack_ltc_bytes() -> Result[Bytes, TimecodeInteropError]
SmpteTimecodeWord::unpack_ltc_bytes(Bytes, FrameRate) -> Result[SmpteTimecodeWord, TimecodeInteropError]
EdlEvent::parse(String, FrameRate) -> Result[EdlEvent, TimecodeInteropError]
EdlEvent::format() -> String
AppleDeliveryTimecodeFormat::parse(String) -> FrameRate?
AppleDeliveryTimecodeFormat::format(FrameRate) -> String?
AppleDeliveryTimecodeFormat::supported_rates() -> Array[FrameRate]
AppleDeliveryTimecodeFormat::is_supported(FrameRate) -> Boolparse_srt(String) -> SubtitleTrack?
parse_srt_detailed(String) -> Result[SubtitleTrack, ParseError]
parse_webvtt(String) -> SubtitleTrack?
parse_webvtt_detailed(String) -> Result[SubtitleTrack, ParseError]
write_srt(SubtitleTrack) -> String
write_webvtt(SubtitleTrack) -> String
parse_timestamp_ms(String) -> Int?
format_srt_timestamp(Int) -> String
format_vtt_timestamp(Int) -> Stringdefault_profile() -> QcProfile
streaming_profile() -> QcProfile
cinema_profile() -> QcProfile
social_video_profile() -> QcProfile
profile_by_name(String) -> QcProfile?
check_cues(Array[Cue], QcProfile) -> Array[QcIssue]
check_srt_source_track(SubtitleTrack, SrtSourcePolicy) -> Array[QcIssue]
srt_basic_source_policy() -> SrtSourcePolicy
dcp_source_srt_policy() -> SrtSourcePolicy
format_report(String, Array[QcIssue]) -> Array[String]
format_json_report(String, Array[QcIssue]) -> String
has_errors(Array[QcIssue]) -> Booloffset_cues(Array[Cue], Int) -> Array[Cue]
convert_fps_cues(Array[Cue], FrameRate, FrameRate) -> Array[Cue]
snap_cues_to_frames(Array[Cue], FrameRate) -> Array[Cue]merge_bilingual(Array[Cue], Array[Cue], tolerance_ms~ : Int) -> Array[Cue]
split_bilingual(Array[Cue]) -> BilingualSplitparse_asset_map(String) -> Result[Array[DcpAsset], DcpParseError]
parse_packing_list(String) -> Result[Array[DcpAsset], DcpParseError]
parse_composition_playlist(String) -> Result[DcpComposition, DcpParseError]
check_dcp_package(DcpPackage) -> Array[DcpIssue]
has_dcp_errors(Array[DcpIssue]) -> Bool
classify_dcp_path(String) -> DcpAssetKindqc_subtitle(String, String) -> Stringmoonpost/
├── timecode/ SMPTE 风格时间码和帧率 helpers
├── subtitle/ SRT/WebVTT/ASS parser、timestamp 和 writer
├── qc/ 字幕 QC、报告、双语文本规范和标点规范化
├── retime/ 整体偏移、帧率转换和帧吸附
├── align/ 双语字幕 merge/split helpers
├── delivery/ 单集交付预检模型和 manifest 解析
├── dcp/ DCP AssetMap、PKL、CPL 轻量解析和一致性检查
├── cli/ CLI 参数模型和命令解析
├── cmd/main/ Native CLI 入口和文件系统 I/O
├── wasm_demo/core/ MoonBit Wasm 导出包
├── wasm-demo/ 浏览器 demo shell、构建脚本和静态资源
├── examples/ 示例字幕文件
├── docs/ 项目结构、设计文档和实施计划
└── .github/ CI workflow./scripts/competition-check.shmoon test --target nativemoon test --target wasm-gcmoon check --target native
moon check --target wasm-gcmoon fmt --check
moon check --fmt --deny-warnmoon info
git diff --exit-code -- .moon build wasm_demo/core --target wasm-gc --releasePure MoonBit toolkit for subtitles, timecode, creator cleanup, delivery checks, and post-production QC.
Dependencies