code wiki / _hdl_build / nx_audio_book.nx

nx_audio_book.nx source

↩ module page · 105 lines · 6510 B

1// nx_audio_book.nx -- SOVEREIGN audiobook/audio (MP3 + ID3v2) -> Nishi format. Operator goal: liberate purchased 2// audiobooks (the operator's ~153 audiobook files) into the owned .nmz, as the @kind=audio media kind. Reads the 3// ID3v2.3/2.4 tag (TIT2 title, TPE1 artist/narrator, TALB album) and CARRIES the audio bytes through unchanged 4// (the browser/player plays MP3 last-mile -- no codec to build, container unifies + codec specializes). Emits the 5// audio.json manifest (with "file":"X" track entries, the same convention book.json/cbx.json use) + track0.mp3, so 6// nx_nmz_pack packs it into .nmz @kind=audio. Rung 1: single MP3 -> one track; multi-file audiobook zips + M4B 7// (MP4 atoms) + per-chapter splitting = next rungs. LEGAL LINE: liberates DRM-FREE audio; no DRM circumvention. 8// Usage: nx_audio_book <mp3-path> <slug>. expect_exit: 0 license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 11const K_MAGIC_1024: i64 = 1024 12 13func ab_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 14func ab_w(fd: i64, s: *u8) -> i64 { sys_write(fd, s, ab_slen(s)); return 0 } 15// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 16// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 17// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 18// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 19func ab_n(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 20func ab_p(s: *u8) -> i64 { ab_w(1, s); return 0 } 21func ab_pn(v: i64) -> i64 { ab_n(1, v); return 0 } 22func ensure_dir(path: *u8) -> i64 { __syscall(258, 0-100, path, 0x1ed, 0, 0, 0); return 0 } 23func 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 } 24func 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) } 25func er_wjson(fd: i64, s: *u8) -> i64 { 26 var i: i64 = 0 27 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 } 28 return 0 29} 30func id4eq(z: *u8, p: i64, id: *u8) -> i64 { var k: i64=0; while k<4 { if z[p+k]!=id[k] { return 0 } k=k+1 } return 1 } 31// copy an ID3 text-frame value: data[off+1 .. off+flen) (skip the 1-byte encoding), NUL bytes stripped (handles 32// the interspersed NULs of UTF-16 text for ASCII content), into out (NUL-terminated). 33func frame_text(z: *u8, off: i64, flen: i64, out: *u8, cap: i64) -> i64 { 34 var o: i64 = 0; var i: i64 = off + 1; let e: i64 = off + flen 35 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 } 36 out[o]=0 as u8; return o 37} 38 39func do_audio(path: *u8, slug: *u8) -> i64 { 40 let lb: *i64 = sys_mmap(16) as *i64 41 let z: *u8 = sys_read_file(path, lb) 42 if (z as i64)==0 { ab_p("AUDIO-FAIL read " as *u8); ab_p(path); ab_p("\n" as *u8); return 0-1 } 43 let zl: i64 = lb[0] 44 45 let title: *u8 = sys_mmap(K_MAGIC_1024); title[0]=0 as u8 46 let narrator: *u8 = sys_mmap(K_MAGIC_1024); narrator[0]=0 as u8 47 let album: *u8 = sys_mmap(K_MAGIC_1024); album[0]=0 as u8 48 49 // ---- ID3v2 tag (if present) ---- 50 if zl > 10 { if z[0]==(0x49 as u8) { if z[1]==(0x44 as u8) { if z[2]==(0x33 as u8) { 51 // header size is syncsafe (7 bits/byte) 52 let tagsize: i64 = ((z[6] as i64)<<21)|((z[7] as i64)<<14)|((z[8] as i64)<<7)|(z[9] as i64) 53 let endf: i64 = 10 + tagsize 54 var p: i64 = 10 55 while p + 10 <= endf { 56 if z[p]==(0 as u8) { p = endf } else { // padding -> stop 57 let flen: i64 = be32(z, p+4) // v2.3 plain-BE frame size 58 let dat: i64 = p + 10 59 if flen <= 0 { p = endf } else { 60 if id4eq(z, p, "TIT2" as *u8)==1 { frame_text(z, dat, flen, title, K_MAGIC_1024) } 61 if id4eq(z, p, "TPE1" as *u8)==1 { frame_text(z, dat, flen, narrator, K_MAGIC_1024) } 62 if id4eq(z, p, "TALB" as *u8)==1 { frame_text(z, dat, flen, album, K_MAGIC_1024) } 63 p = dat + flen 64 } 65 } 66 } 67 } } } } 68 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 } 69 70 // ---- emit reader/<slug>/{track0.mp3, audio.json} ---- 71 ensure_dir("knowledge/staging/media/reader" as *u8) 72 let dir: *u8 = sys_mmap(512) 73 var dl: i64 = er_cat(dir, 0, "knowledge/staging/media/reader/" as *u8); dl = er_cat(dir, dl, slug); dir[dl]=0 as u8 74 ensure_dir(dir) 75 76 let tp: *u8 = sys_mmap(K_MAGIC_1024) 77 var tpl: i64 = er_cat(tp, 0, dir as *u8); tpl = er_cat(tp, tpl, "/track0.mp3" as *u8); tp[tpl]=0 as u8 78 let tfd: i64 = sys_openat_wr(tp, 0x1a4) 79 if tfd >= 0 { sys_write(tfd, z, zl); sys_close(tfd) } // carry the audio bytes through unchanged 80 81 let jp: *u8 = sys_mmap(K_MAGIC_1024) 82 var jl: i64 = er_cat(jp, 0, dir as *u8); jl = er_cat(jp, jl, "/audio.json" as *u8); jp[jl]=0 as u8 83 let jfd: i64 = sys_openat_wr(jp, 0x1a4) 84 if jfd < 0 { ab_p("AUDIO-FAIL open-json\n" as *u8); return 0-1 } 85 ab_w(jfd, "{\"title\":\"" as *u8); er_wjson(jfd, title) 86 ab_w(jfd, "\",\"narrator\":\"" as *u8); er_wjson(jfd, narrator) 87 ab_w(jfd, "\",\"album\":\"" as *u8); er_wjson(jfd, album) 88 ab_w(jfd, "\",\"format\":\"audio\",\"slug\":\"" as *u8); er_wjson(jfd, slug) 89 ab_w(jfd, "\",\"tracks\":[{\"idx\":0,\"file\":\"track0.mp3\",\"bytes\":" as *u8); ab_n(jfd, zl) 90 ab_w(jfd, "}],\"ntracks\":1}" as *u8) 91 sys_close(jfd) 92 93 ab_p("AUDIO-BOOK title=\"" as *u8); ab_w(1, title); ab_p("\" narrator=\"" as *u8); ab_w(1, narrator) 94 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) 95 return 0 96} 97 98func main(argc: i64, argv: *i64) -> i64 { 99 var path: *u8 = "knowledge/fixtures/nishi_audio.mp3" as *u8 100 var slug: *u8 = "nishi_audio_fixture" as *u8 101 if argc >= 3 { path = argv[1] as *u8; slug = argv[2] as *u8 } 102 let r: i64 = do_audio(path, slug) 103 if r == 0 { ab_p("AUDIO-BOOK-OK\n" as *u8); sys_exit(0); return 0 } 104 ab_p("AUDIO-BOOK-FAIL\n" as *u8); sys_exit(1); return 1 105}