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}