code wiki / _hdl_build / nx_imgbook_promote.nx

nx_imgbook_promote.nx source

↩ module page · 276 lines · 12086 B

1// nx_imgbook_promote.nx -- promote an ALREADY-EXTRACTED image-dominant book to the CBX/zoom viewer. 2// 3// GROUND TRUTH driving this organ (operator slug ba4e3a12c8d2091): a Kindle/EPUB COMIC extracts as 4// ~190 full-res JPEGs + ~190 text "chapters" of 33-215 BYTES each (image-anchor stubs). The text 5// reader then shows 190 empty chapters = "the book doesn't load". The images are ALREADY on disk -- 6// no re-extraction needed. This organ inspects reader/<slug>/, and IF the book is image-dominant 7// (>= IB_MIN_IMAGES real image files AND avg chapter text < IB_MAX_AVG_CHAP bytes), writes cbx.json 8// (natural-sorted pages, "cfix":2) so /api/open routes it to the ZOOM viewer. A text book is left 9// untouched (exit 2). Idempotent: re-running overwrites the same cbx.json. 10// 11// argv: nx_imgbook_promote <bookpath-ignored> <slug> (matches ms_run_extractor's <path> <slug> shape) 12// exit: 0 promoted (cbx.json written) | 2 not an image-book (honest skip) | 1 error 13// license_tier: ORIGINAL 14import "nx_syscalls.nx" 15const IB_MAGIC_1048576: i64 = 1048576 16const IB_MAGIC_65536: i64 = 65536 17const IB_MAGIC_1024: i64 = 1024 18const IB_MAGIC_999999: i64 = 999999 19 20const IB_MIN_IMAGES: i64 = 8 // fewer real images than this = a text book with plates, not a comic 21const IB_MAX_AVG_CHAP: i64 = 300 // avg chapter bytes at/under this = image-anchor stubs, not prose 22 23func ib_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 24func ib_w(fd: i64, s: *u8) -> i64 { sys_write(fd, s, ib_slen(s)); return 0 } 25func ib_n(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(fd,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 } 26func ib_p(s: *u8) -> i64 { ib_w(1, s); return 0 } 27func ib_pn(v: i64) -> i64 { ib_n(1, v); return 0 } 28func ib_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 } 29 30func ib_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 31 32// name ends with an image extension (case-insensitive) 33func ib_is_image(name: *u8) -> i64 { 34 let n: i64 = ib_slen(name) 35 if n < 5 { return 0 } 36 var dot: i64 = 0-1 37 var i: i64 = 0 38 while i < n { if name[i] == (46 as u8) { dot = i } i = i + 1 } 39 if dot < 0 { return 0 } 40 let e1: i64 = ib_lc(name[dot+1] as i64) 41 var e2: i64 = 0; if dot+2 < n { e2 = ib_lc(name[dot+2] as i64) } 42 var e3: i64 = 0; if dot+3 < n { e3 = ib_lc(name[dot+3] as i64) } 43 if e1 == 106 { if e2 == 112 { return 1 } } // jpg/jpeg 44 if e1 == 112 { if e2 == 110 { if e3 == 103 { return 1 } } } // png 45 if e1 == 119 { if e2 == 101 { if e3 == 98 { return 1 } } } // webp 46 if e1 == 103 { if e2 == 105 { if e3 == 102 { return 1 } } } // gif 47 if e1 == 98 { if e2 == 109 { if e3 == 112 { return 1 } } } // bmp 48 return 0 49} 50 51// derived artifacts are NOT pages: cover.* (grid cover), *.t.jpg (thumb cache), *.new (atomic temp), page* (cbx output) 52func ib_is_derived(name: *u8) -> i64 { 53 let n: i64 = ib_slen(name) 54 if n >= 6 { if name[0]==(99 as u8) { if name[1]==(111 as u8) { if name[2]==(118 as u8) { if name[3]==(101 as u8) { if name[4]==(114 as u8) { if name[5]==(46 as u8) { return 1 } } } } } } } // cover. 55 if n >= 5 { if name[0]==(112 as u8) { if name[1]==(97 as u8) { if name[2]==(103 as u8) { if name[3]==(101 as u8) { return 1 } } } } } // page* 56 if n >= 6 { if name[n-6]==(46 as u8) { if name[n-5]==(116 as u8) { if name[n-4]==(46 as u8) { if name[n-3]==(106 as u8) { if name[n-2]==(112 as u8) { if name[n-1]==(103 as u8) { return 1 } } } } } } } // .t.jpg 57 if n >= 4 { if name[n-4]==(46 as u8) { if name[n-3]==(110 as u8) { if name[n-2]==(101 as u8) { if name[n-1]==(119 as u8) { return 1 } } } } } // .new 58 return 0 59} 60 61// name starts "chap" and ends ".txt" 62func ib_is_chap(name: *u8) -> i64 { 63 let n: i64 = ib_slen(name) 64 if n < 9 { return 0 } 65 if name[0] != (99 as u8) { return 0 } 66 if name[1] != (104 as u8) { return 0 } 67 if name[2] != (97 as u8) { return 0 } 68 if name[3] != (112 as u8) { return 0 } 69 if name[n-4] != (46 as u8) { return 0 } 70 if name[n-3] != (116 as u8) { return 0 } 71 if name[n-2] != (120 as u8) { return 0 } 72 if name[n-1] != (116 as u8) { return 0 } 73 return 1 74} 75 76// file size via sys_fstatat(path, buf) (AT_FDCWD internal); st_size at +48 in x86_64 struct stat. -1 on error. 77func ib_fsize(path: *u8) -> i64 { 78 let sb: *u8 = sys_mmap(160) 79 if sys_fstatat(path, sb) != 0 { return 0-1 } 80 let szp: *i64 = ((sb as i64) + 48) as *i64 81 return szp[0] 82} 83 84func nat_digits(s: *u8, o: i64) -> i64 { 85 var k: i64 = 0 86 var go: i64 = 1 87 while go == 1 { 88 let c: i64 = s[o+k] as i64 89 var d: i64 = 0 90 if c >= 48 { if c <= 57 { d = 1 } } 91 if d == 1 { k = k + 1 } else { go = 0 } 92 } 93 return k 94} 95 96// NATURAL name compare (same algorithm as nx_cbx_pages -- page2 < page10, case-insensitive) 97func name_cmp_nat(a: *u8, b: *u8) -> i64 { 98 var i: i64 = 0 99 var j: i64 = 0 100 var res: i64 = 2 101 while res == 2 { 102 let ca: i64 = a[i] as i64 103 let cb: i64 = b[j] as i64 104 if ca == 0 { 105 if cb == 0 { res = 0 } else { res = 0-1 } 106 } else { 107 if cb == 0 { res = 1 } else { 108 var da: i64 = 0 109 if ca >= 48 { if ca <= 57 { da = 1 } } 110 var db: i64 = 0 111 if cb >= 48 { if cb <= 57 { db = 1 } } 112 var both: i64 = 0 113 if da == 1 { if db == 1 { both = 1 } } 114 if both == 1 { 115 var zi: i64 = i 116 var zj: i64 = j 117 while a[zi] == (48 as u8) { zi = zi + 1 } 118 while b[zj] == (48 as u8) { zj = zj + 1 } 119 let li: i64 = nat_digits(a, zi) 120 let lj: i64 = nat_digits(b, zj) 121 if li < lj { res = 0-1 } else { 122 if li > lj { res = 1 } else { 123 var k: i64 = 0 124 while k < li { 125 if res == 2 { 126 let xa: i64 = a[zi+k] as i64 127 let xb: i64 = b[zj+k] as i64 128 if xa < xb { res = 0-1 } 129 if xa > xb { res = 1 } 130 } 131 k = k + 1 132 } 133 if res == 2 { i = zi + li; j = zj + lj } 134 } 135 } 136 } else { 137 let la: i64 = ib_lc(ca) 138 let lb2: i64 = ib_lc(cb) 139 if la < lb2 { res = 0-1 } 140 if la > lb2 { res = 1 } 141 if res == 2 { i = i + 1; j = j + 1 } 142 } 143 } 144 } 145 } 146 return res 147} 148 149const IB_ECAP: i64 = 4096 150 151func main(argc: i64, argv: *i64) -> i64 { 152 if argc < 3 { ib_p("usage: nx_imgbook_promote <bookpath-ignored> <slug>\n" as *u8); sys_exit(1); return 1 } 153 let slug: *u8 = argv[2] as *u8 154 let dir: *u8 = sys_mmap(512) 155 var dl: i64 = ib_cat(dir, 0, "knowledge/staging/media/reader/" as *u8) 156 dl = ib_cat(dir, dl, slug); dir[dl] = 0 as u8 157 158 let dfd: i64 = sys_openat_rd(dir) 159 if dfd < 0 { ib_p("IMGBOOK-SKIP no-dir slug=" as *u8); ib_w(1, slug); ib_p("\n" as *u8); sys_exit(2); return 2 } 160 161 let arena: *u8 = sys_mmap(IB_MAGIC_1048576) 162 let noffs: *i64 = sys_mmap(8 * IB_ECAP) as *i64 163 var nimg: i64 = 0 164 var aoff: i64 = 0 165 var nchap: i64 = 0 166 var chap_bytes: i64 = 0 167 168 let dbuf: *u8 = sys_mmap(IB_MAGIC_65536) 169 let fpath: *u8 = sys_mmap(IB_MAGIC_1024) 170 var going: i64 = 1 171 while going == 1 { 172 let nr: i64 = sys_getdents64(dfd, dbuf, IB_MAGIC_65536) 173 if nr <= 0 { going = 0 } else { 174 var off: i64 = 0 175 while off < nr { 176 let rec: *u8 = ((dbuf as i64) + off) as *u8 177 let rl: i64 = dirent_reclen(rec) 178 let nm: *u8 = dirent_name(rec) 179 if nm[0] != (46 as u8) { 180 // full path for size stat 181 var fo: i64 = ib_cat(fpath, 0, dir as *u8) 182 fpath[fo] = 47 as u8; fo = fo + 1 183 fo = ib_cat(fpath, fo, nm); fpath[fo] = 0 as u8 184 if ib_is_chap(nm) == 1 { 185 nchap = nchap + 1 186 let cs: i64 = ib_fsize(fpath) 187 if cs > 0 { chap_bytes = chap_bytes + cs } 188 } else { 189 if ib_is_image(nm) == 1 { if ib_is_derived(nm) == 0 { 190 let nl: i64 = ib_slen(nm) 191 if nimg < IB_ECAP { if aoff + nl + 1 < IB_MAGIC_1048576 { 192 noffs[nimg] = aoff 193 aoff = ib_cat(arena, aoff, nm) + 1 194 arena[aoff-1] = 0 as u8 195 nimg = nimg + 1 196 } } 197 } } 198 } 199 } 200 if rl <= 0 { off = nr } else { off = off + rl } 201 } 202 } 203 } 204 sys_close(dfd) 205 206 var avg: i64 = IB_MAGIC_999999 207 if nchap > 0 { avg = chap_bytes / nchap } 208 ib_p("IMGBOOK slug=" as *u8); ib_w(1, slug) 209 ib_p(" images=" as *u8); ib_pn(nimg) 210 ib_p(" chaps=" as *u8); ib_pn(nchap) 211 ib_p(" avg_chap_bytes=" as *u8); ib_pn(avg) 212 ib_p("\n" as *u8) 213 214 var promote: i64 = 0 215 if nimg >= IB_MIN_IMAGES { 216 if nchap == 0 { promote = 1 } 217 if nchap > 0 { if avg <= IB_MAX_AVG_CHAP { promote = 1 } } 218 } 219 if promote == 0 { ib_p("IMGBOOK-SKIP text-book (left on the text reader)\n" as *u8); sys_exit(2); return 2 } 220 221 // natural sort the image names (insertion sort over noffs order) 222 let order: *i64 = sys_mmap(8 * IB_ECAP) as *i64 223 var i: i64 = 0 224 while i < nimg { order[i] = i; i = i + 1 } 225 var a: i64 = 1 226 while a < nimg { 227 let cur: i64 = order[a] 228 let curnm: *u8 = (arena as i64 + noffs[cur]) as *u8 229 var b: i64 = a - 1 230 var stop: i64 = 0 231 while stop == 0 { 232 if b < 0 { stop = 1 } 233 else { 234 let bnm: *u8 = (arena as i64 + noffs[order[b]]) as *u8 235 if name_cmp_nat(bnm, curnm) > 0 { order[b+1] = order[b]; b = b - 1 } 236 else { stop = 1 } 237 } 238 } 239 order[b+1] = cur 240 a = a + 1 241 } 242 243 // write cbx.json over the images IN PLACE (files stay where the book extractor put them) 244 let jp: *u8 = sys_mmap(600) 245 var jo: i64 = ib_cat(jp, 0, dir as *u8); jo = ib_cat(jp, jo, "/cbx.json" as *u8); jp[jo] = 0 as u8 246 let jfd: i64 = sys_openat_wr(jp, 0x1a4) 247 if jfd < 0 { ib_p("IMGBOOK-FAIL open-json\n" as *u8); sys_exit(1); return 1 } 248 ib_w(jfd, "{\"title\":\"" as *u8); ib_w(jfd, slug) 249 ib_w(jfd, "\",\"format\":\"imgbook\",\"cfix\":2,\"pages\":[" as *u8) 250 var w: i64 = 0 251 var p: i64 = 0 252 while p < nimg { 253 let nm: *u8 = (arena as i64 + noffs[order[p]]) as *u8 254 var fo2: i64 = ib_cat(fpath, 0, dir as *u8) 255 fpath[fo2] = 47 as u8; fo2 = fo2 + 1 256 fo2 = ib_cat(fpath, fo2, nm); fpath[fo2] = 0 as u8 257 let fsz: i64 = ib_fsize(fpath) 258 if fsz > 0 { 259 if w > 0 { ib_w(jfd, "," as *u8) } 260 ib_w(jfd, "{\"idx\":" as *u8); ib_n(jfd, w) 261 ib_w(jfd, ",\"file\":\"" as *u8); ib_w(jfd, nm) 262 ib_w(jfd, "\",\"bytes\":" as *u8); ib_n(jfd, fsz) 263 ib_w(jfd, "}" as *u8) 264 w = w + 1 265 } 266 p = p + 1 267 } 268 ib_w(jfd, "],\"npages\":" as *u8); ib_n(jfd, w) 269 ib_w(jfd, "}" as *u8) 270 sys_close(jfd) 271 272 ib_p("IMGBOOK-PROMOTED npages=" as *u8); ib_pn(w); ib_p(" -> zoom viewer\n" as *u8) 273 if w > 0 { sys_exit(0); return 0 } 274 sys_exit(1) 275 return 1 276}