code wiki / _hdl_build / nx_m4b_book.nx

nx_m4b_book.nx source

↩ module page · 121 lines · 7548 B

1// nx_m4b_book.nx -- SOVEREIGN M4B / M4A / MP4 audiobook -> Nishi format (@kind=audio). The other common audiobook 2// container (iTunes/Audible-non-DRM). MP4 = a tree of atoms (size:u32be + type:4 + payload); iTunes metadata is at 3// moov/udta/meta/ilst/<©nam title|©ART artist/narrator|©alb album>/data. meta is a FULLBOX (4 version/flags bytes 4// before its children). Carries the audio bytes through unchanged (no codec) -> audio.json + track0.m4b -> .nmz. 5// Emits the SAME audio.json schema as nx_audio_book (rung 2 = unify both into one dispatching audio liberator). 6// LEGAL LINE: liberates DRM-FREE M4B; Audible .aax/.aaxc DRM would be detected + declined (not built yet -- M4B 7// itself is DRM-free). Usage: nx_m4b_book <m4b-path> <slug>. expect_exit: 0 license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 10const K_MAGIC_1024: i64 = 1024 11 12func ab_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 13func ab_w(fd: i64, s: *u8) -> i64 { sys_write(fd, s, ab_slen(s)); return 0 } 14// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 15// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 16// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 17// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 18func ab_n(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 19func ab_p(s: *u8) -> i64 { ab_w(1, s); return 0 } 20func ab_pn(v: i64) -> i64 { ab_n(1, v); return 0 } 21func ensure_dir(path: *u8) -> i64 { __syscall(258, 0-100, path, 0x1ed, 0, 0, 0); return 0 } 22func er_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){dst[off+i]=s[i];i=i+1} return off+i } 23func be32(b: *u8, o: i64) -> i64 { return ((b[o] as i64)<<24)|((b[o+1] as i64)<<16)|((b[o+2] as i64)<<8)|(b[o+3] as i64) } 24func er_wjson(fd: i64, s: *u8) -> i64 { 25 var i: i64 = 0 26 while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c == 0x22 { sys_write(fd, "\\\"" as *u8, 2) } else { if c == 0x5c { sys_write(fd, "\\\\" as *u8, 2) } else { if c < 0x20 { sys_write(fd, " " as *u8, 1) } else { sys_write(fd, (s as i64 + i) as *u8, 1) } } } i = i + 1 } 27 return 0 28} 29func atom_type_eq(z: *u8, o: i64, t: *u8) -> i64 { if z[o+4]!=t[0]{return 0} if z[o+5]!=t[1]{return 0} if z[o+6]!=t[2]{return 0} if z[o+7]!=t[3]{return 0} return 1 } 30// walk atoms in [start,end) looking for type t (4 raw bytes); on hit set payload start/end. ret 1/0. 31func find_atom(z: *u8, start: i64, end: i64, t: *u8, ps: *i64, pe: *i64) -> i64 { 32 var o: i64 = start 33 while o + 8 <= end { 34 var sz: i64 = be32(z, o) 35 if sz == 0 { sz = end - o } 36 if sz < 8 { return 0 } 37 if o + sz > end { return 0 } 38 if atom_type_eq(z, o, t) == 1 { ps[0] = o + 8; pe[0] = o + sz; return 1 } 39 o = o + sz 40 } 41 return 0 42} 43// extract an ilst metadata value: find <type> atom in [ilst_s,ilst_e), then its "data" child; value is after the 44// 8-byte (type+locale) data preamble. NUL-stripped into out. 45func mp4_value(z: *u8, ilst_s: i64, ilst_e: i64, t: *u8, out: *u8, cap: i64) -> i64 { 46 let as0: *i64 = sys_mmap(8) as *i64; let ae0: *i64 = sys_mmap(8) as *i64 47 if find_atom(z, ilst_s, ilst_e, t, as0, ae0) == 0 { out[0]=0 as u8; return 0 } 48 let ds: *i64 = sys_mmap(8) as *i64; let de: *i64 = sys_mmap(8) as *i64 49 if find_atom(z, as0[0], ae0[0], "data" as *u8, ds, de) == 0 { out[0]=0 as u8; return 0 } 50 var i: i64 = ds[0] + 8; let e: i64 = de[0]; var o: i64 = 0 51 while i < e { let c: i64 = z[i] as i64; if c != 0 { if o < cap-1 { out[o]=z[i]; o=o+1 } } i=i+1 } 52 out[o]=0 as u8; return o 53} 54 55func do_m4b(path: *u8, slug: *u8) -> i64 { 56 let lb: *i64 = sys_mmap(16) as *i64 57 let z: *u8 = sys_read_file(path, lb) 58 if (z as i64)==0 { ab_p("M4B-FAIL read " as *u8); ab_p(path); ab_p("\n" as *u8); return 0-1 } 59 let zl: i64 = lb[0] 60 if zl < 16 { ab_p("M4B-FAIL too-small\n" as *u8); return 0-1 } 61 if atom_type_eq(z, 0, "ftyp" as *u8) == 0 { ab_p("M4B-FAIL not-MP4 (no ftyp)\n" as *u8); return 0-1 } 62 63 let title: *u8 = sys_mmap(K_MAGIC_1024); title[0]=0 as u8 64 let narrator: *u8 = sys_mmap(K_MAGIC_1024); narrator[0]=0 as u8 65 let album: *u8 = sys_mmap(K_MAGIC_1024); album[0]=0 as u8 66 let nam: *u8 = sys_mmap(8); nam[0]=0xa9 as u8; nam[1]=0x6e as u8; nam[2]=0x61 as u8; nam[3]=0x6d as u8 67 let art: *u8 = sys_mmap(8); art[0]=0xa9 as u8; art[1]=0x41 as u8; art[2]=0x52 as u8; art[3]=0x54 as u8 68 let alb: *u8 = sys_mmap(8); alb[0]=0xa9 as u8; alb[1]=0x61 as u8; alb[2]=0x6c as u8; alb[3]=0x62 as u8 69 70 // navigate moov/udta/meta(+4 fullbox)/ilst 71 let ms: *i64 = sys_mmap(8) as *i64; let me: *i64 = sys_mmap(8) as *i64 72 let us: *i64 = sys_mmap(8) as *i64; let ue: *i64 = sys_mmap(8) as *i64 73 let es: *i64 = sys_mmap(8) as *i64; let ee: *i64 = sys_mmap(8) as *i64 74 let is0: *i64 = sys_mmap(8) as *i64; let ie0: *i64 = sys_mmap(8) as *i64 75 if find_atom(z, 0, zl, "moov" as *u8, ms, me) == 1 { 76 if find_atom(z, ms[0], me[0], "udta" as *u8, us, ue) == 1 { 77 if find_atom(z, us[0], ue[0], "meta" as *u8, es, ee) == 1 { 78 if find_atom(z, es[0] + 4, ee[0], "ilst" as *u8, is0, ie0) == 1 { // meta is a fullbox: +4 79 mp4_value(z, is0[0], ie0[0], nam, title, K_MAGIC_1024) 80 mp4_value(z, is0[0], ie0[0], art, narrator, K_MAGIC_1024) 81 mp4_value(z, is0[0], ie0[0], alb, album, K_MAGIC_1024) 82 } 83 } 84 } 85 } 86 if title[0]==(0 as u8) { var k: i64=0; while slug[k]!=(0 as u8){ title[k]=slug[k]; k=k+1 } title[k]=0 as u8 } 87 88 ensure_dir("knowledge/staging/media/reader" as *u8) 89 let dir: *u8 = sys_mmap(512) 90 var dl: i64 = er_cat(dir, 0, "knowledge/staging/media/reader/" as *u8); dl = er_cat(dir, dl, slug); dir[dl]=0 as u8 91 ensure_dir(dir) 92 let tp: *u8 = sys_mmap(K_MAGIC_1024) 93 var tpl: i64 = er_cat(tp, 0, dir as *u8); tpl = er_cat(tp, tpl, "/track0.m4b" as *u8); tp[tpl]=0 as u8 94 let tfd: i64 = sys_openat_wr(tp, 0x1a4) 95 if tfd >= 0 { sys_write(tfd, z, zl); sys_close(tfd) } 96 97 let jp: *u8 = sys_mmap(K_MAGIC_1024) 98 var jl: i64 = er_cat(jp, 0, dir as *u8); jl = er_cat(jp, jl, "/audio.json" as *u8); jp[jl]=0 as u8 99 let jfd: i64 = sys_openat_wr(jp, 0x1a4) 100 if jfd < 0 { ab_p("M4B-FAIL open-json\n" as *u8); return 0-1 } 101 ab_w(jfd, "{\"title\":\"" as *u8); er_wjson(jfd, title) 102 ab_w(jfd, "\",\"narrator\":\"" as *u8); er_wjson(jfd, narrator) 103 ab_w(jfd, "\",\"album\":\"" as *u8); er_wjson(jfd, album) 104 ab_w(jfd, "\",\"format\":\"audio\",\"container\":\"m4b\",\"slug\":\"" as *u8); er_wjson(jfd, slug) 105 ab_w(jfd, "\",\"tracks\":[{\"idx\":0,\"file\":\"track0.m4b\",\"bytes\":" as *u8); ab_n(jfd, zl) 106 ab_w(jfd, "}],\"ntracks\":1}" as *u8) 107 sys_close(jfd) 108 109 ab_p("M4B-BOOK title=\"" as *u8); ab_w(1, title); ab_p("\" narrator=\"" as *u8); ab_w(1, narrator) 110 ab_p("\" album=\"" as *u8); ab_w(1, album); ab_p("\" audio_bytes=" as *u8); ab_pn(zl); ab_p(" -> " as *u8); ab_p(dir); ab_p("\n" as *u8) 111 return 0 112} 113 114func main(argc: i64, argv: *i64) -> i64 { 115 var path: *u8 = "knowledge/fixtures/nishi_audio.m4b" as *u8 116 var slug: *u8 = "nishi_m4b_fixture" as *u8 117 if argc >= 3 { path = argv[1] as *u8; slug = argv[2] as *u8 } 118 let r: i64 = do_m4b(path, slug) 119 if r == 0 { ab_p("M4B-BOOK-OK\n" as *u8); sys_exit(0); return 0 } 120 ab_p("M4B-BOOK-FAIL\n" as *u8); sys_exit(1); return 1 121}