code wiki / _hdl_build / nx_mobi_cover.nx
nx_mobi_cover.nx source
↩ module page · 52 lines · 3821 B
1// nx_mobi_cover.nx -- SHARED embedded-cover extraction for the Kindle decoders (DRY: nx_mobi_book + nx_kf8_book both
2// call it). The cover is read out of the file ITSELF, offline -- never generated, never downloaded (how Calibre does
3// it). Grounded on the registered MOBI spec (mobileread-mobi-spec.ref): the MOBI header's "First Image index" lives
4// at rec0off+108 (record-0-relative); EXTH 201 coveroffset / 202 thumboffset are ADDED to it to find the cover PDB
5// record. Image records are absolute in the PDB, so the SAME record-0 First Image index serves MOBI6 and KF8 (a dual
6// file shares its image records). Detects JPEG/PNG/GIF magic and writes <dir>/cover.<ext>; sets covername (or "").
7// Returns 1 if a cover was written, else 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 mc_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
13func mc_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 }
14func mc_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) }
15func mc_p(s: *u8) -> i64 { sys_write(1, s, mc_slen(s)); return 0 }
16// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
17// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
18// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
19// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
20func mc_pn(v: i64) -> i64 { nxi_out(v); return 0 }
21
22func mobi_extract_cover(z: *u8, zl: i64, numRec: i64, rec0off: i64, coveroff: i64, coverfound: i64, thumboff: i64, thumbfound: i64, dir: *u8, covername: *u8) -> i64 {
23 covername[0] = 0 as u8
24 let mobihdrlen: i64 = mc_be32(z, rec0off + 20)
25 if mobihdrlen < 96 { return 0 } // no First Image index field in a minimal header
26 let firstimg: i64 = mc_be32(z, rec0off + 108)
27 if firstimg < 1 { return 0 }
28 if firstimg >= numRec { return 0 }
29 var coverrec: i64 = firstimg
30 if coverfound == 1 { coverrec = firstimg + coveroff } else { if thumbfound == 1 { coverrec = firstimg + thumboff } }
31 if coverrec < 1 { return 0 }
32 if coverrec >= numRec { return 0 }
33 let cs2: i64 = mc_be32(z, 78 + coverrec*8)
34 var ce2: i64 = zl
35 if coverrec + 1 < numRec { ce2 = mc_be32(z, 78 + (coverrec+1)*8) }
36 let clen: i64 = ce2 - cs2
37 if clen < 4 { return 0 }
38 if cs2 + clen > zl { return 0 }
39 var ext: *u8 = 0 as *u8
40 let b0: i64 = z[cs2] as i64; let b1: i64 = z[cs2+1] as i64; let b2: i64 = z[cs2+2] as i64; let b3: i64 = z[cs2+3] as i64
41 if b0==0xff { if b1==0xd8 { if b2==0xff { ext = "jpg" as *u8 } } } // JPEG FF D8 FF
42 if b0==0x89 { if b1==0x50 { if b2==0x4e { if b3==0x47 { ext = "png" as *u8 } } } } // PNG 89 'PNG'
43 if b0==0x47 { if b1==0x49 { if b2==0x46 { ext = "gif" as *u8 } } } // GIF 'GIF'
44 if (ext as i64) == 0 { mc_p("MOBI-COVER-SKIP first-image record is not a known image (jpg/png/gif)\n" as *u8); return 0 }
45 let cvp: *u8 = sys_mmap(K_MAGIC_1024); var cvl: i64 = mc_cat(cvp, 0, dir); cvl = mc_cat(cvp, cvl, "/cover." as *u8); cvl = mc_cat(cvp, cvl, ext); cvp[cvl] = 0 as u8
46 let cvfd: i64 = sys_openat_wr(cvp, 0x1a4)
47 if cvfd < 0 { return 0 }
48 sys_write(cvfd, (z as i64 + cs2) as *u8, clen); sys_close(cvfd)
49 var nn: i64 = mc_cat(covername, 0, "cover." as *u8); nn = mc_cat(covername, nn, ext); covername[nn] = 0 as u8
50 mc_p("MOBI-COVER rec=" as *u8); mc_pn(coverrec); mc_p(" bytes=" as *u8); mc_pn(clen); mc_p(" fmt=" as *u8); mc_p(ext); mc_p(" -> " as *u8); mc_p(cvp); mc_p("\n" as *u8)
51 return 1
52}