A MoonBit library for validating complete file packages against JSON manifests, with optional image metadata checks and machine-readable reports.
| 十月新增与完善 | 具体变化 | 代码与复现依据 |
|---|---|---|
| 通用文件包校验 | 图片必填尺寸改为可选;普通文件可按大小和摘要验收,未登记的非图像文件也会报告 | f7a78a1;examples/library_consumer 的文本附件消费示例 |
| 通用目录清单生成及转义修复 | init 纳入普通文件;生成含控制字符路径时使用合法 JSON 转义,保留图片尺寸的可选生成 | 4daa2f7 至 25f16e4;manifest.mbt 单测与 CLI 草稿回读、拒绝覆盖场景 |
| 直接库调用的约束校验 | 修复直接构造规格时非法约束可能被忽略的问题;与 JSON 入口共用规则并拒绝矛盾大小区间 | 163a9d3;独立消费包的 3 项测试及 CLI 无效清单回归 |
| AVIF 主图元数据关联解析 | 不再把 ipco 中第一个 ispe 当作主图尺寸;解析 pitm 主 item 与 ipma 属性索引,再按索引解析 ipco,区分主图和缩略图并拒绝缺失、越界或含糊关联 | avif.mbt 的多 item 回归:缩略图先出现时仍选主图;未关联的 ispe 被拒绝;扩展属性结构边界检查 |
| AVIF 有界结构一致性核心 | 串联 pitm → iinf/infe → ipma/ipco → iloc → mdat/idat;检验 item 引用、主图属性绑定、受支持的尺寸/配置/位深关系及显式数据区间归属;未实现的主图 essential 属性明确返回 unsupported | avif_structure.mbt、avif_structure_wbtest.mbt、examples/avif_semantics 的 9 项单约束证据和 scripts/cli_smoke.mbtx 的包级端到端回归 |
| 预期场景与调用者 | 输入与规则 | 调用路径和可核对结果 |
|---|---|---|
| MoonBit 应用验收发布附件或文档包 | examples/library_consumer 的 release-notes.txt;仅约束相对路径及 20–2048 bytes 的大小范围,无图像字段 | 独立包调用公开的清单解析、扫描和报告 API;应得到 checked=1、passed=1、failed=0,并生成清单 SHA-256 和 HTML/JSON 报告 |
| 交付者或 CI 检查媒体资源规格 | examples/clean 清单与素材目录;检查图片尺寸、文件上限、预期 SHA-256 和整包 100000 bytes 预算 | CLI 应返回 0,报告 passed=7、failed=0(6 个文件及 1 个 <package> 汇总项);受控错误包 examples/demo 应返回 1,保留报告并指出规格不符、缺件和未登记项 |
| 维护者为已有目录建立清单草稿 | 现有普通文件目录;自动填入相对路径和大小上限,可读取尺寸的受支持图片另填宽高 | init 为非图像目录生成不含宽高的清单;生成清单可回读验收。相同输出路径再次执行应拒绝覆盖;图片目录的尺寸生成另由 examples/formats 及 CLI 回归验证 |
# 1. 独立包消费公开 API;报告位于 _build/library-consumer/
moon run --target native examples/library_consumer
# 2. 通过包与受控错误包;两次运行均应生成 report.html 和 report.json
moon run --target native cmd/main -- examples/clean/media-pack.json examples/clean/assets --output _build/scenarios/clean
moon run --target native cmd/main -- examples/demo/media-pack.json examples/demo/assets --output _build/scenarios/issues
# 3. 从非图像目录起草清单,再用草稿检查同一目录;预期均返回 0
moon run --target native cmd/main -- init examples/library_consumer/assets --output _build/scenarios/draft.json
moon run --target native cmd/main -- _build/scenarios/draft.json examples/library_consumer/assets --output _build/scenarios/draft-check| 关系或边界 | 本实现检查 |
|---|---|
| 主图与声明 | pitm 指向唯一、非零的 iinf/infe 声明,主图类型须为直接 av01;计数与实际条目一致,拒绝重复 ID |
| 属性引用 | 所有 ipma 的 item 必须已声明,每个表内 ID 递增且跨表唯一;同版本/flags 表不得重复;所有非零属性索引须落在 ipco 中,包括次要 item 的索引 |
| 主图属性 | 主图须关联唯一 ispe 和 av1C;检查正尺寸、配置头和位深标志;如果关联了 pixi,其通道数与各通道位深须与 av1C 一致。保留主图属性的索引、fourcc、属性盒偏移及 essential 标志;未知或未实现且标记为 essential 的主图属性返回 UNSUPPORTED_ESSENTIAL_PROPERTY,不再给出子集成功结果。属性索引为零且 essential 为真返回 PROPERTY_ESSENTIAL_ZERO。尺寸是存储画布尺寸,不应用旋转或裁剪属性 |
| 数据来源 | iloc 条目须引用已声明 item 且不得重复,主图须有位置;解析 base + extent offset,显式正长度区间须完整落在单个 mdat 载荷中,或相对当前 meta/idat 载荷。允许多段和跨 item 共享区间,不把共享内容误判为重叠错误 |
| 有界读取 | 先按父盒边界检查盒长和字段宽度,再做偏移运算;扩展盒长与字段值超过有符号 Int64 范围明确返回不支持。大文件的末尾 mdat 载荷无需读入即可判断声明区间是否越界 |
// prefix 是完整文件的前缀,file_size 必须来自实际文件大小。
inspect_avif_structure(prefix, file_size) // Result[AvifStructure, AvifDiagnostic]
inspect_avif_file(path) // 异步,读取并调用同一核心| 项目/组件 | 已有能力 | 与片盒点检的关系与取舍 |
|---|---|---|
| Pillow | 图像打开、解码、像素处理、转换与保存;支持格式和插件依安装构建环境而异 | 产品整包预检与 Pillow 的单图处理职责不同,但图像格式/尺寸读取有重叠。本次自有 AVIF 关系核心的范围和差异见逐项对照;不移植 Pillow,也不把它的解码、编辑和编码能力算作本项目能力 |
| mizchi/image | PNG/BMP/JPEG 解码与编码,以及部分格式编码、缩放;解码结果为 RGBA 像素数据 | 面向编解码和图像处理。使用它获取尺寸会进入解码路径;本项目读取交付所需的容器元数据,并为格式差异做受限解析 |
| gmlewis/moonbit-image | 基于 Go image 的简单 MoonBit 图像表示 | 面向图像数据表示;没有本项目需要的素材清单、目录验收与报告流程 |
| hanbings/fluorescence | 仓库说明了 MoonBit BMP/PNG 读取及图像模糊、取色等能力 | 覆盖部分格式与图像处理,不包含本项目的素材包验收流程;不将其描述为没有图像读取能力 |
| MoonBit Notary | 公开的 v0.1.0 说明包含电子证据清单、目录扫描与分类、路径/摘要/重复项规则、批处理、审计记录和报告 | 与清单驱动目录校验、摘要及报告存在直接重叠;其公开说明偏电子证据交接与审计。本项目的差异证据及限制见核心范围对照,不据其公开说明推断未列出的能力一定不存在 |
| gmlewis/crc32 / moonbitlang/x/crypto | CRC-32 与 SHA-256 通用算法 | 项目直接依赖前者验证 PNG CRC,依赖后者计算内容指纹,不重复实现哈希算法 |
moon check --target native
moon test
moon run --target native cmd/main -- examples/clean/media-pack.json examples/clean/assets --output reportmoon run --target native cmd/main -- init examples/formats/assets --output media-pack.json{ "file": "说明\u001f.txt", "max_bytes": 1024 }moon run --target native cmd/main -- examples/duplicates/media-pack.json examples/duplicates/assets --output report-duplicatesmoon run --target native cmd/main -- examples/formats/media-pack.json examples/formats/assets --output report-formatsmoon run --target native cmd/main -- examples/demo/media-pack.json examples/demo/assets --output report-issuesmoon run --target native cmd/main -- examples/invalid/fractional-width.json examples/clean/assets --output report-invalid-manifestmoon run --target native scripts/cli_smoke.mbtxStart-Process .\report\report.htmlmoon add hxiuzheng/media-pack-audit
# 固定到已验证版本时:moon add hxiuzheng/media-pack-audit@0.1.0moon run --target native examples/library_consumerimport {
"hxiuzheng/media-pack-audit" @audit,
}fn inspect(bytes : Bytes) -> Unit {
match @audit.detect_content_format(bytes) {
Some(format) => {
let dims : (Int, Int)? = match format {
@audit.ImageFormat::Png => @audit.png_dimensions(bytes)
@audit.ImageFormat::Jpeg => @audit.jpeg_dimensions(bytes)
_ => None
}
println("格式 \{@audit.format_name(format)},尺寸 \{dims}")
}
None => println("未识别的文件头")
}
}{
"max_total_bytes": 100000,
"assets": [
{ "file": "poster.png", "label": "课程主视觉", "width": 1920, "height": 1080, "max_bytes": 5000000 },
{ "file": "social/cover.jpeg", "width": 1200, "height": 630, "max_bytes": 2000000, "expected_sha256": "FF1EE418EE804BB1BEB25D59D3E5059FDFB9B2F6B7E97B81943D3ADEDAFD7458" },
{ "file": "social/cover.webp", "width": 1200, "height": 630, "max_bytes": 2000000 },
{ "file": "motion/preview.gif", "width": 320, "height": 180, "max_bytes": 500000 },
{ "file": "social-card.png", "width": 1200, "height": 630, "max_bytes": 2000000 }
]
}{ "assets": [{ "file": "release-notes.txt", "label": "发布说明", "max_bytes": 2048, "min_bytes": 20 }] }moon version --all
moon update
moon fmt --check
moon fmt --check scripts/cli_smoke.mbtx
moon check --target native --deny-warn
moon test --target native
moon run --target native examples/library_consumer
moon run --target native scripts/cli_smoke.mbtxpub suberror ManifestParseError {
InvalidJson(String)
DuplicateJsonKey(String)
InvalidField(String)
}pub(all) struct AuditReport {
asset_dir : String
manifest_sha256 : String
checked : Int
passed : Int
failed : Int
results : Array[CheckResult]
} derive(ToJson)pub(all) struct AvifDiagnostic {
code : String
box_type : String
offset : Int64
item_id : Int64?
detail : String
} derive(Debug)pub(all) struct AvifExtent {
item_id : Int64
offset : Int64
length : Int64
storage : String
} derive(Debug)pub(all) struct AvifPropertyAssociation {
index : Int
box_type : String
offset : Int64
essential : Bool
interpreted : Bool
} derive(Debug)pub(all) struct AvifStructure {
primary_item_id : Int64
width : Int
height : Int
item_count : Int
primary_properties : Array[AvifPropertyAssociation]
extents : Array[AvifExtent]
} derive(Debug)pub(all) struct CheckResult {
file : String
label : String?
status : String
observed : String
sha256 : String
details : String
} derive(ToJson)fn bmp_dimensions(bytes : Bytes) -> (Int, Int)?fn canonical_format(value : String) -> String?fn gif_dimensions(bytes : Bytes) -> (Int, Int)?fn inspect_avif_structure(prefix : Bytes, file_size : Int64) -> Result[AvifStructure, AvifDiagnostic]fn matches_aspect(width : Int, height : Int, aw : Int, ah : Int) -> Boolfn parse_aspect(value : String) -> (Int, Int)?async fn scan_assets(asset_dir : String, manifest : Manifest, manifest_sha256 : String) -> AuditReportfn sha256_bytes(bytes : Bytes) -> Stringfn tiff_dimensions(bytes : Bytes) -> (Int, Int)?fn webp_dimensions(bytes : Bytes, file_size : Int64) -> (Int, Int)?Install
Download zipA MoonBit library for validating complete file packages against JSON manifests, with optional image metadata checks and machine-readable reports.