nx_galx_thumb.nx source
↩ module page · 202 lines · 9745 B
1// nx_galx_thumb.nx -- SOVEREIGN gallery THUMBNAIL generator. Composes the existing sovereign codecs
2// (nx_png_decoder = RFC2083 decode, nx_png = RGB8 encode) with an integer box-average downscale -- NO
3// ffmpeg / PIL / libpng. Produces a small thumbnail PNG (max dimension TH_MAX px) so the gallery grid
4// loads ~KB thumbs instead of ~MB full-res images. Output is CID-addressed under knowledge/store/galx-thumb/
5// so the daemon serves it at GET /thumb/<CID>.
6//
7// MEMORY DISCIPLINE: nx_png_decode mmaps a 64MiB IDAT buffer per call and the toolchain has no sys_munmap,
8// so a single process MUST NOT decode many images. The batch mode therefore FORKS one child per image --
9// the kernel reclaims every mmap on child exit, bounding RSS regardless of corpus size (same sovereign
10// pattern as nx_galx_fill / nx_torrent_get).
11//
12// modes:
13// nx_galx_thumb one <src.png> <out.png> -- thumbnail a single image (the gate path)
14// nx_galx_thumb batch <start> <count> -- walk knowledge/status/galx_cid_paths.tsv [start,start+count),
15// thumbnail each to galx-thumb/<CID>.png, idempotent (skip if present)
16// license_tier: ORIGINAL
17import "nx_syscalls.nx"
18import "nx_png_decoder.nx"
19import "nx_png.nx"
20const TH_MAGIC_65536: i64 = 65536
21const TH_MAGIC_1000000000: i64 = 1000000000
22const TH_MAGIC_1024: i64 = 1024
23
24const TH_MAX: i64 = 256 // max thumbnail dimension in px (data-driven cap, rule 11)
25const TH_DIR: *u8 = "knowledge/store/galx-thumb" as *u8
26
27func th_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
28func th_n(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;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{b[i]=t[k-1-i];i=i+1}; sys_write(1,b,k); return 0 }
29func th_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ if s[i]>=(48 as u8){ if s[i]<=(57 as u8){ v=v*10+((s[i]-(48 as u8)) as i64) } } i=i+1 } return v }
30func th_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 }
31// mkdir via x86_64 mkdirat=258 directly (the portable path used across the gallery organs).
32func th_mkdir(path: *u8) -> i64 { return __syscall(258, 0-100, path as i64, 0x1ed, 0, 0, 0) }
33
34// Thumbnail ONE image: read -> decode -> box-average downscale to <=TH_MAX -> write RGB8 PNG.
35// Returns 0 on success, negative on any failure (caller treats negatives as a skip, never a crash).
36func th_one(src: *u8, out: *u8) -> i64 {
37 let szp: *i64 = sys_mmap(16) as *i64
38 let buf: *u8 = sys_read_file(src, szp)
39 let n: i64 = szp[0]
40 if (buf as i64) == 0 { return 0 - 1 }
41 if n < 8 { return 0 - 2 }
42 let r: *NxPngResult = nx_png_decode(buf, n)
43 if r.error_code != 0 { return 0 - 3 }
44 let hdr: *NxPngHeader = r.header
45 let w: i64 = hdr.width
46 let h: i64 = hdr.height
47 if w <= 0 { return 0 - 4 }
48 if h <= 0 { return 0 - 4 }
49 let nch: i64 = r.n_channels
50 let bpp: i64 = r.bytes_per_pix
51 if nch <= 0 { return 0 - 5 }
52 let bps: i64 = bpp / nch // bytes per sample: 1 (8-bit) or 2 (16-bit, big-endian high byte first)
53 let px: *u8 = r.pixels
54 let stride: i64 = w * bpp
55 // target dims: preserve aspect, longest side = TH_MAX; never upscale a small source.
56 var dw: i64 = TH_MAX
57 var dh: i64 = TH_MAX
58 if w >= h { dw = TH_MAX; dh = h * TH_MAX / w } else { dh = TH_MAX; dw = w * TH_MAX / h }
59 if dw < 1 { dw = 1 }
60 if dh < 1 { dh = 1 }
61 if w <= TH_MAX { if h <= TH_MAX { dw = w; dh = h } }
62 let fb: *i64 = sys_mmap(8 * dw * dh) as *i64
63 var dy: i64 = 0
64 while dy < dh {
65 let sy0: i64 = dy * h / dh
66 var sy1: i64 = (dy + 1) * h / dh
67 if sy1 <= sy0 { sy1 = sy0 + 1 }
68 if sy1 > h { sy1 = h }
69 var dx: i64 = 0
70 while dx < dw {
71 let sx0: i64 = dx * w / dw
72 var sx1: i64 = (dx + 1) * w / dw
73 if sx1 <= sx0 { sx1 = sx0 + 1 }
74 if sx1 > w { sx1 = w }
75 var sr: i64 = 0
76 var sg: i64 = 0
77 var sb: i64 = 0
78 var cnt: i64 = 0
79 var yy: i64 = sy0
80 while yy < sy1 {
81 var xx: i64 = sx0
82 while xx < sx1 {
83 let off: i64 = yy * stride + xx * bpp
84 let rr: i64 = px[off] as i64
85 var gg: i64 = rr
86 var bb: i64 = rr
87 if nch >= 3 {
88 gg = px[off + bps] as i64
89 bb = px[off + 2 * bps] as i64
90 }
91 sr = sr + rr
92 sg = sg + gg
93 sb = sb + bb
94 cnt = cnt + 1
95 xx = xx + 1
96 }
97 yy = yy + 1
98 }
99 if cnt < 1 { cnt = 1 }
100 let cr: i64 = sr / cnt
101 let cg: i64 = sg / cnt
102 let cb: i64 = sb / cnt
103 fb[dy * dw + dx] = cr + cg * 256 + cb * TH_MAGIC_65536
104 dx = dx + 1
105 }
106 dy = dy + 1
107 }
108 return write_png(fb, dw, dh, out)
109}
110
111// does a file already exist (and is openable)? -> 1 yes, 0 no.
112func th_exists(path: *u8) -> i64 {
113 let fd: i64 = sys_openat_rd(path)
114 if fd < 0 { return 0 }
115 sys_close(fd)
116 return 1
117}
118
119func main(argc: i64, argv: *i64) -> i64 {
120 if argc < 2 {
121 th_p("usage: nx_galx_thumb one <src> <out> | batch <start> <count>\n" as *u8)
122 sys_exit(2); return 2
123 }
124 let mode: *u8 = argv[1] as *u8
125 th_mkdir(TH_DIR)
126
127 // ---- single-image mode (gate path) ----
128 if mode[0] == (111 as u8) { // 'o'
129 if argc < 4 { th_p("usage: nx_galx_thumb one <src> <out>\n" as *u8); sys_exit(2); return 2 }
130 let rc: i64 = th_one(argv[2] as *u8, argv[3] as *u8)
131 if rc == 0 { th_p("THUMB ok -> " as *u8); th_p(argv[3] as *u8); th_p("\n" as *u8); sys_exit(0); return 0 }
132 th_p("THUMB FAIL rc=" as *u8); th_n(rc); th_p("\n" as *u8); sys_exit(1); return 1
133 }
134
135 // ---- batch mode: fork one child per image so each decode's mmaps are reclaimed on exit ----
136 if mode[0] == (98 as u8) { // 'b'
137 var start: i64 = 0
138 var count: i64 = TH_MAGIC_1000000000
139 if argc >= 4 { start = th_atoi(argv[2] as *u8); count = th_atoi(argv[3] as *u8) }
140 let endl: i64 = start + count
141 let szp: *i64 = sys_mmap(16) as *i64
142 let lst: *u8 = sys_read_file("knowledge/status/galx_cid_paths.tsv" as *u8, szp)
143 let lsz: i64 = szp[0]
144 if (lst as i64) == 0 { th_p("THUMB-BATCH FAIL: no sidecar\n" as *u8); sys_exit(1); return 1 }
145 let st: *i64 = sys_mmap(16) as *i64
146 let cid: *u8 = sys_mmap(96)
147 let pathbuf: *u8 = sys_mmap(TH_MAGIC_1024)
148 let outbuf: *u8 = sys_mmap(512)
149 var ls: i64 = 0
150 var i: i64 = 0
151 var lineno: i64 = 0
152 var done: i64 = 0
153 var skip: i64 = 0
154 var fail: i64 = 0
155 while i <= lsz {
156 var isnl: i64 = 0
157 if i == lsz { isnl = 1 } else { if lst[i] == (10 as u8) { isnl = 1 } }
158 if isnl == 1 {
159 let llen: i64 = i - ls
160 if llen > 70 { // need at least 69-char CID + tab + 1-char path
161 if lineno >= start { if lineno < endl {
162 // CID = first 69 bytes; column tab is at ls+69; path = ls+70 .. i
163 var c: i64 = 0
164 while c < 69 { cid[c] = lst[ls + c]; c = c + 1 } cid[69] = 0 as u8
165 if lst[ls + 69] == (9 as u8) {
166 var po: i64 = 0
167 var p: i64 = ls + 70
168 while p < i { pathbuf[po] = lst[p]; po = po + 1; p = p + 1 } pathbuf[po] = 0 as u8
169 var oo: i64 = th_cat(outbuf, 0, TH_DIR)
170 oo = th_cat(outbuf, oo, "/" as *u8)
171 oo = th_cat(outbuf, oo, cid)
172 oo = th_cat(outbuf, oo, ".png" as *u8)
173 outbuf[oo] = 0 as u8
174 if th_exists(outbuf) == 1 { skip = skip + 1 } else {
175 if th_exists(pathbuf) == 0 { skip = skip + 1 } else { // source absent (e.g. NAS unmounted) -> skip, don't fork
176 let pid: i64 = sys_fork()
177 if pid == 0 {
178 let rc: i64 = th_one(pathbuf, outbuf)
179 if rc == 0 { sys_exit(0) }
180 sys_exit(1)
181 }
182 sys_wait4(pid, st, 0)
183 let ec: i64 = (st[0] >> 8) & 0xff
184 if ec == 0 { done = done + 1 } else { fail = fail + 1 }
185 if ((done + fail) % 200) == 0 { th_p(" thumbs done=" as *u8); th_n(done); th_p(" fail=" as *u8); th_n(fail); th_p(" skip=" as *u8); th_n(skip); th_p("\n" as *u8) }
186 }
187 }
188 }
189 }}
190 lineno = lineno + 1
191 }
192 ls = i + 1
193 }
194 i = i + 1
195 }
196 th_p("THUMB-BATCH done=" as *u8); th_n(done); th_p(" fail=" as *u8); th_n(fail); th_p(" skip=" as *u8); th_n(skip); th_p(" (lines " as *u8); th_n(start); th_p(".." as *u8); th_n(endl); th_p(")\n" as *u8)
197 sys_exit(0); return 0
198 }
199
200 th_p("unknown mode\n" as *u8)
201 sys_exit(2); return 2
202}