nx_asset_meta.nx source
↩ module page · 99 lines · 5946 B
1// nx_asset_meta.nx -- METADATA ENRICHMENT (R3 of the universal organization-tooling arc).
2//
3// Makes the catalog know WHAT each asset IS: it populates the type-specific metadata block plus the
4// Dublin Core core spine ON an asset record, so downstream search/dedup/dashboard can answer "show me
5// 1920x1080 PNGs" or "docs about taxes" without re-opening the bytes (org_research.tsv CONFIRMED:
6// schema-mediaobject = width/height/duration/encodingFormat; dublin-core-core = title/creator/date/
7// subject; iptc-image-embedded = read intrinsic image facts from the bytes).
8//
9// ZERO NEW STORAGE/IDENTITY -- pure COMPOSITION over what already exists:
10// * record field model + canonical encode/decode + CID -> nx_asset_record (ar_fields/ar_addf/
11// ar_encode/ar_decode/ar_get/ar_cid/ar_cpy)
12// * REAL image header extraction (width/height/format) -> nx_png_header (png_check_sig /
13// png_parse_ihdr / PngIhdr) [iptc-image-embedded]
14//
15// HONEST EXTRACTION BOUNDARY:
16// * IMAGE (PNG): width / height / encoding_format are REAL-EXTRACTED from the actual bytes via the
17// in-tree PNG header decoder (signature + IHDR). No PNG signature -> not a PNG; we do NOT fabricate
18// dimensions (am_enrich_image returns AM_NOT_PNG and writes nothing -- deny-by-default honesty).
19// * DOC / DATASET: there is no universal binary header carrying a title; the title/subject are
20// PROVIDED (Dublin Core descriptive metadata is authored, not intrinsic). The model + API is the
21// deliverable; real per-format extraction (e.g. JPEG/EXIF, MP4 boxes) wires in exactly here as new
22// am_enrich_* paths, each reusing the same ar_addf record-extension idiom (#19 / #25).
23//
24// Pure/deterministic; no hardware/persistent-firmware writes (Rule 26).
25// license_tier: ORIGINAL
26import "nx_syscalls.nx"
27import "nx_asset_record.nx"
28import "nx_png_header.nx"
29
30const AM_NOT_PNG: i64 = 0 - 1 // bytes carry no PNG signature -> we refuse to invent dimensions
31
32// integer -> null-terminated decimal string in caller buffer `out` (>= 24 bytes). Returns digit count.
33// Record field VALUES are strings (canon_encode is a string KV store), so an extracted width 1920 must
34// become the bytes "1920\0" before ar_addf. Deterministic; handles 0; non-negative dimensions only.
35func am_itoa(v: i64, out: *u8) -> i64 {
36 if v < 0 { v = 0 }
37 let t: *u8 = sys_mmap(24)
38 var k: i64 = 0
39 if v == 0 { t[0] = 48 as u8; k = 1 }
40 while v > 0 { t[k] = (48 + (v % 10)) as u8; v = v / 10; k = k + 1 }
41 var i: i64 = 0
42 while i < k { out[i] = t[k - 1 - i]; i = i + 1 }
43 out[k] = 0 as u8
44 return k
45}
46
47// ---- IMAGE enrichment: REAL extraction of width/height/encoding_format from PNG bytes ----
48// Reads the actual signature + IHDR (nx_png_header), formats width/height as decimal strings into the
49// caller-provided w_out/h_out buffers (>=24 each) and stamps encoding_format = "image/png" into
50// fmt_out. Returns 0 on a real PNG (buffers populated, ready for ar_addf), AM_NOT_PNG otherwise
51// (nothing written -- we never fabricate dimensions for non-PNG bytes).
52func am_extract_image(bytes: *u8, len: i64, w_out: *u8, h_out: *u8, fmt_out: *u8) -> i64 {
53 if png_check_sig(bytes, len) != 0 { return AM_NOT_PNG }
54 let ih_raw: *u8 = sys_mmap(64)
55 let ih: *PngIhdr = ih_raw as *PngIhdr
56 if png_parse_ihdr(bytes, len, ih) != 0 { return AM_NOT_PNG }
57 am_itoa(ih.width, w_out)
58 am_itoa(ih.height, h_out)
59 ar_cpy(fmt_out, "image/png\x00" as *u8)
60 return 0
61}
62
63// am_enrich_image: REAL-extract image facts from `bytes`, then EXTEND an existing field set
64// (keys[]/vals[], with kv[0] = current field count) by adding width/height/encoding_format -- exactly
65// the ar_addf idiom R0 uses, so the enriched fields key-sort + round-trip through ar_encode/ar_decode
66// identically. The w_buf/h_buf/fmt_buf are caller-mmap'd (>=24/24/16) and OUTLIVE this call because
67// ar_addf stores pointers into the field set (no copy). Returns the new field count, or AM_NOT_PNG
68// (field set untouched) if the bytes are not a PNG.
69func am_enrich_image(bytes: *u8, len: i64, keys: *i64, vals: *i64, kv: *i64,
70 w_buf: *u8, h_buf: *u8, fmt_buf: *u8) -> i64 {
71 if am_extract_image(bytes, len, w_buf, h_buf, fmt_buf) != 0 { return AM_NOT_PNG }
72 ar_addf(keys, vals, kv, "width\x00" as *u8, w_buf)
73 ar_addf(keys, vals, kv, "height\x00" as *u8, h_buf)
74 ar_addf(keys, vals, kv, "encoding_format\x00" as *u8, fmt_buf)
75 return kv[0]
76}
77
78// ---- DOC / DATASET enrichment: Dublin Core descriptive spine (PROVIDED, not intrinsic) ----
79// am_enrich_doc: extend a field set with dc:title + dc:subject (faceted-findability). These are
80// AUTHORED descriptive metadata (a doc's bytes carry no universal title header), so they are provided
81// by the caller -- the same ar_addf record-extension contract. Empty values are skipped by ar_addf
82// (ar_present), so a title-only doc simply omits subject. Returns the new field count.
83func am_enrich_doc(title: *u8, subject: *u8, keys: *i64, vals: *i64, kv: *i64) -> i64 {
84 ar_addf(keys, vals, kv, "title\x00" as *u8, title)
85 ar_addf(keys, vals, kv, "subject\x00" as *u8, subject)
86 return kv[0]
87}
88
89// am_enrich_dc: the full Dublin Core core spine in one call (dublin-core-core: title/creator/date/
90// subject). Any empty field is skipped (ar_present). Reusable across every asset type -- the
91// descriptive layer is type-agnostic, unlike the media block. Returns the new field count.
92func am_enrich_dc(title: *u8, creator: *u8, date: *u8, subject: *u8,
93 keys: *i64, vals: *i64, kv: *i64) -> i64 {
94 ar_addf(keys, vals, kv, "title\x00" as *u8, title)
95 ar_addf(keys, vals, kv, "creator\x00" as *u8, creator)
96 ar_addf(keys, vals, kv, "date\x00" as *u8, date)
97 ar_addf(keys, vals, kv, "subject\x00" as *u8, subject)
98 return kv[0]
99}