code wiki / (root) / nx_media_harvest.nx

nx_media_harvest.nx source

↩ module page · 368 lines · 22625 B

1// nx_media_harvest.nx -- SUPERSET media-URL harvester (operator 2026-07-08: "how do i get my nishi browser to 2// see the source of gifs and vids and images on a page like this" -- an ad/popunder lander hides media in lazy 3// attrs, <source>/<video>, HLS manifests, JSON blobs and CSS url()). This is nx_img_harvest's proven multi- 4// strategy raw-byte scan EXTENDED past images: it also finds VIDEO (.mp4/.webm/.m4v/.mov/.mkv/.ogv), STREAM 5// manifests (.m3u8/.mpd) and AUDIO (.mp3/.m4a/.ogg/.opus/.wav/.aac), and it adds a CSS url(...) pass so 6// UNQUOTED backgrounds (background:url(/bg/hero.gif)) surface too -- the quoted-region scan alone misses those. 7// Every hit is CLASSIFIED into a kind (0 image / 1 gif / 2 video / 3 stream / 4 audio) so the caller can label 8// and preview it. Same offs/lens/url_buf interface as nx_img_harvest, plus a parallel kinds[] array. 9// nx_media_harvest(html,hlen, base_url,base_len, url_buf,cap, offs,lens,kinds, max_n) -> count 10// Mechanism (per token): unescape JSON "\/"->"/", classify by path extension (path = up to ?/#), resolve 11// rel->abs (nx_url_resolve), dedup. Junk (data: URIs, .svg, favicon/sprite/logo) rejected for image kinds. 12// license_tier: ORIGINAL 13import "nx_syscalls.nx" 14import "nx_url_resolve.nx" // nx_url_resolve + NX_URL_RESOLVE_OK (proven relative->absolute resolver) 15 16const MH_KIND_IMAGE: i64 = 0 17const MH_KIND_GIF: i64 = 1 18const MH_KIND_VIDEO: i64 = 2 19const MH_KIND_STREAM: i64 = 3 20const MH_KIND_AUDIO: i64 = 4 21 22// lowercase a byte 23func mh_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 24func mh_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]==(0 as u8){return 1} return 0 } 25 26// case-insensitive substring within hay[0..hn) 27func mh_has_ci(hay: *u8, hn: i64, needle: *u8) -> i64 { 28 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 } 29 if nl == 0 { return 0 } 30 var i: i64 = 0 31 while i + nl <= hn { 32 var m: i64 = 1; var jx: i64 = 0 33 while jx < nl { if mh_lc(hay[i+jx] as i64) != mh_lc(needle[jx] as i64) { m = 0; jx = nl } else { jx = jx + 1 } } 34 if m == 1 { return 1 } 35 i = i + 1 36 } 37 return 0 38} 39 40// Classify tok[0..tn) by its path extension. Returns MH_KIND_* or -1 (not media). Rejects data: URIs, .svg, 41// and (for image kinds only) favicon/sprite/logo UI junk. path = bytes up to the first ? or #. 42func mh_classify(tok: *u8, tn: i64) -> i64 { 43 if tn < 5 { return 0 - 1 } 44 // reject data: URIs 45 if mh_lc(tok[0] as i64)==100 { if mh_lc(tok[1] as i64)==97 { if mh_lc(tok[2] as i64)==116 { if mh_lc(tok[3] as i64)==97 { if tok[4]==(58 as u8) { return 0 - 1 } } } } } // "data:" 46 // clean-URL charset + must contain a path '/'. Measured on real Wikipedia HTML 2026-07-08: this removes prose 47 // words that merely end in .gif (com.compuserve.gif, (large).gif -- no slash) and nested-quote garbage a 48 // single-quoted attribute region can smuggle into a token (srcset="//... -- contains a quote), while keeping 49 // every real asset (real URLs never contain a raw quote/space/angle-bracket and always carry a path slash). 50 var slash: i64 = 0 51 var g: i64 = 0 52 while g < tn { 53 let cc: i64 = tok[g] as i64 54 if cc == 47 { slash = 1 } // '/' 55 if cc == 34 { return 0 - 1 } // " 56 if cc == 39 { return 0 - 1 } // ' 57 if cc == 60 { return 0 - 1 } // < 58 if cc == 62 { return 0 - 1 } // > 59 if cc == 32 { return 0 - 1 } // space 60 if cc == 9 { return 0 - 1 } // tab 61 if cc == 10 { return 0 - 1 } // nl 62 if cc == 13 { return 0 - 1 } // cr 63 if cc == 92 { return 0 - 1 } // backslash 64 if cc == 96 { return 0 - 1 } // backtick 65 if cc == 123 { return 0 - 1 } // { 66 if cc == 125 { return 0 - 1 } // } 67 if cc == 124 { return 0 - 1 } // | 68 g = g + 1 69 } 70 if slash == 0 { return 0 - 1 } 71 // path end = first ? or # 72 var end: i64 = tn 73 var i: i64 = 0 74 while i < tn { let c: i64 = tok[i] as i64; if c == 63 { if end == tn { end = i } } if c == 35 { if end == tn { end = i } } i = i + 1 } 75 if end < 5 { return 0 - 1 } 76 // last '.' 77 var dot: i64 = 0 - 1 78 var j: i64 = 0 79 while j < end { if tok[j] == (46 as u8) { dot = j } j = j + 1 } 80 if dot < 0 { return 0 - 1 } 81 if dot == 0 { return 0 - 1 } // leading-dot dotfile, no name 82 if tok[dot-1] == (47 as u8) { return 0 - 1 } // empty filename like ".../.gif" 83 let el: i64 = end - dot 84 if el < 4 { return 0 - 1 } 85 if el > 5 { return 0 - 1 } 86 // build lowercased ext (".xxx" / ".xxxx"), NUL-terminated 87 let e: *u8 = sys_mmap(8) 88 var k: i64 = 0 89 while dot + k < end { if k < 7 { e[k] = mh_lc(tok[dot + k] as i64) as u8 } k = k + 1 } 90 e[k] = 0 as u8 91 var kind: i64 = 0 - 1 92 // images 93 if mh_streq(e, ".jpg" as *u8)==1 { kind = MH_KIND_IMAGE } 94 if mh_streq(e, ".jpeg" as *u8)==1 { kind = MH_KIND_IMAGE } 95 if mh_streq(e, ".png" as *u8)==1 { kind = MH_KIND_IMAGE } 96 if mh_streq(e, ".webp" as *u8)==1 { kind = MH_KIND_IMAGE } 97 if mh_streq(e, ".avif" as *u8)==1 { kind = MH_KIND_IMAGE } 98 if mh_streq(e, ".bmp" as *u8)==1 { kind = MH_KIND_IMAGE } 99 // gif (called out separately -- the operator asked for gifs by name) 100 if mh_streq(e, ".gif" as *u8)==1 { kind = MH_KIND_GIF } 101 // video 102 if mh_streq(e, ".mp4" as *u8)==1 { kind = MH_KIND_VIDEO } 103 if mh_streq(e, ".webm" as *u8)==1 { kind = MH_KIND_VIDEO } 104 if mh_streq(e, ".m4v" as *u8)==1 { kind = MH_KIND_VIDEO } 105 if mh_streq(e, ".mov" as *u8)==1 { kind = MH_KIND_VIDEO } 106 if mh_streq(e, ".mkv" as *u8)==1 { kind = MH_KIND_VIDEO } 107 if mh_streq(e, ".ogv" as *u8)==1 { kind = MH_KIND_VIDEO } 108 // streaming manifests (HLS/DASH) -- how modern "vids" actually ship 109 if mh_streq(e, ".m3u8" as *u8)==1 { kind = MH_KIND_STREAM } 110 if mh_streq(e, ".mpd" as *u8)==1 { kind = MH_KIND_STREAM } 111 // audio 112 if mh_streq(e, ".mp3" as *u8)==1 { kind = MH_KIND_AUDIO } 113 if mh_streq(e, ".m4a" as *u8)==1 { kind = MH_KIND_AUDIO } 114 if mh_streq(e, ".ogg" as *u8)==1 { kind = MH_KIND_AUDIO } 115 if mh_streq(e, ".opus" as *u8)==1 { kind = MH_KIND_AUDIO } 116 if mh_streq(e, ".wav" as *u8)==1 { kind = MH_KIND_AUDIO } 117 if mh_streq(e, ".aac" as *u8)==1 { kind = MH_KIND_AUDIO } 118 if kind < 0 { return 0 - 1 } 119 // image-kind UI-junk denylist. ONLY favicon/sprite -- those are never content. NOT "logo" (an og:image or 120 // hero logo IS a picture the operator asked to see; measured: /logo denylist wrongly dropped a real og:image). 121 if kind == MH_KIND_IMAGE { 122 if mh_has_ci(tok, end, "favicon" as *u8)==1 { return 0 - 1 } 123 if mh_has_ci(tok, end, "sprite" as *u8)==1 { return 0 - 1 } 124 } 125 return kind 126} 127 128// copy tok[0..tn) into out, converting JSON escape "\/"->"/"; NUL-term. returns length. 129func mh_unescape(tok: *u8, tn: i64, out: *u8, cap: i64) -> i64 { 130 var o: i64 = 0 131 var i: i64 = 0 132 while i < tn { 133 if o >= cap - 1 { i = tn } else { 134 let c: i64 = tok[i] as i64 135 if c == 92 { if i + 1 < tn { if tok[i+1] == (47 as u8) { out[o] = 47 as u8; o = o + 1; i = i + 2 } else { out[o] = tok[i] as u8; o = o + 1; i = i + 1 } } else { i = tn } } 136 else { out[o] = tok[i]; o = o + 1; i = i + 1 } 137 } 138 } 139 out[o] = 0 as u8 140 return o 141} 142 143// already-collected? linear scan of url_buf via offs/lens 144func mh_seen(url_buf: *u8, offs: *i64, lens: *i64, count: i64, cand: *u8, cn: i64) -> i64 { 145 var i: i64 = 0 146 while i < count { 147 if lens[i] == cn { 148 var eq: i64 = 1; var k: i64 = 0 149 let bp: *u8 = ((url_buf as i64) + offs[i]) as *u8 150 while k < cn { if bp[k] != cand[k] { eq = 0; k = cn } else { k = k + 1 } } 151 if eq == 1 { return 1 } 152 } 153 i = i + 1 154 } 155 return 0 156} 157 158// resolve a candidate -> out; returns length or 0. absolute http(s) passes through; //h/p gets https:; else 159// nx_url_resolve against base. 160func mh_resolve(base: *u8, blen: i64, cand: *u8, cn: i64, out: *u8, cap: i64) -> i64 { 161 if cn >= 7 { if mh_lc(cand[0] as i64)==104 { if mh_lc(cand[1] as i64)==116 { if mh_lc(cand[2] as i64)==116 { if mh_lc(cand[3] as i64)==112 { 162 var s: i64 = 4 163 if mh_lc(cand[4] as i64)==115 { s = 5 } 164 if cand[s]==(58 as u8) { if cand[s+1]==(47 as u8) { if cand[s+2]==(47 as u8) { 165 var o: i64 = 0; while o < cn { if o < cap - 1 { out[o] = cand[o] } o = o + 1 } out[cn] = 0 as u8; return cn 166 } } } 167 } } } } } 168 // protocol-relative //host/path -> https: 169 if cn >= 2 { if cand[0]==(47 as u8) { if cand[1]==(47 as u8) { 170 let pfx: *u8 = "https:" as *u8 171 var o: i64 = 0; while o < 6 { out[o] = pfx[o]; o = o + 1 } 172 var q: i64 = 0; while q < cn { if o < cap - 1 { out[o] = cand[q]; o = o + 1 } q = q + 1 } 173 out[o] = 0 as u8; return o 174 } } } 175 // relative -> proven resolver 176 let olp: *i64 = sys_mmap(8) as *i64 177 let rc: i64 = nx_url_resolve(base, blen, cand, cn, out, cap, olp) 178 if rc == NX_URL_RESOLVE_OK { return olp[0] } 179 return 0 180} 181 182// consider one raw token: if it classifies as media, unescape+resolve+dedup+append (with its kind). Returns new count. 183func mh_consider(tok: *u8, tn: i64, base_url: *u8, base_url_len: i64, 184 url_buf: *u8, url_buf_cap: i64, offs: *i64, lens: *i64, kinds: *i64, max_n: i64, 185 count: i64, buf_pos_p: *i64, unesc: *u8, resolved: *u8) -> i64 { 186 if count >= max_n { return count } 187 if tn < 5 { return count } 188 let un: i64 = mh_unescape(tok, tn, unesc, 8192) 189 let kind: i64 = mh_classify(unesc, un) 190 if kind < 0 { return count } 191 let rn: i64 = mh_resolve(base_url, base_url_len, unesc, un, resolved, 8192) 192 if rn <= 0 { return count } 193 if mh_seen(url_buf, offs, lens, count, resolved, rn) == 1 { return count } 194 if buf_pos_p[0] + rn >= url_buf_cap { return count } 195 var k: i64 = 0 196 while k < rn { url_buf[buf_pos_p[0] + k] = resolved[k]; k = k + 1 } 197 offs[count] = buf_pos_p[0] 198 lens[count] = rn 199 kinds[count] = kind 200 buf_pos_p[0] = buf_pos_p[0] + rn 201 return count + 1 202} 203 204// THE HARVEST. Two passes over the raw bytes: (1) every quoted region, tokenized on space/comma (catches 205// <img src>, lazy data-*, srcset candidates, JSON arrays, quoted CSS url('..')); (2) every url(...) region 206// (catches UNQUOTED CSS backgrounds). Returns count; fills offs/lens/kinds into url_buf up to max_n. 207func nx_media_harvest(html: *u8, hlen: i64, base_url: *u8, base_url_len: i64, 208 url_buf: *u8, url_buf_cap: i64, offs: *i64, lens: *i64, kinds: *i64, max_n: i64) -> i64 { 209 var count: i64 = 0 210 let bpp: *i64 = sys_mmap(8) as *i64; bpp[0] = 0 211 let tokbuf: *u8 = sys_mmap(8192) 212 let unesc: *u8 = sys_mmap(8192) 213 let resolved: *u8 = sys_mmap(8192) 214 215 // ---- pass 1: quoted regions ---- 216 var i: i64 = 0 217 while i < hlen { 218 if count >= max_n { i = hlen } else { 219 let ch: i64 = html[i] as i64 220 var q: i64 = 0 221 if ch == 34 { q = 1 } 222 if ch == 39 { q = 1 } 223 if q == 1 { 224 var s: i64 = i + 1 225 var e: i64 = s 226 var go: i64 = 1 227 while go == 1 { if e >= hlen { go = 0 } else { if (html[e] as i64) == ch { go = 0 } else { e = e + 1 } } } 228 var ts: i64 = s 229 var p: i64 = s 230 while p <= e { 231 var brk: i64 = 0 232 if p == e { brk = 1 } else { let c2: i64 = html[p] as i64; if c2 == 32 { brk = 1 } if c2 == 9 { brk = 1 } if c2 == 10 { brk = 1 } if c2 == 13 { brk = 1 } if c2 == 44 { brk = 1 } } 233 if brk == 1 { 234 let tl: i64 = p - ts 235 if tl > 0 { if tl < 8192 { 236 var w: i64 = 0 237 while w < tl { tokbuf[w] = html[ts + w]; w = w + 1 } 238 tokbuf[tl] = 0 as u8 239 count = mh_consider(tokbuf, tl, base_url, base_url_len, url_buf, url_buf_cap, offs, lens, kinds, max_n, count, bpp, unesc, resolved) 240 } } 241 ts = p + 1 242 } 243 p = p + 1 244 } 245 if e < hlen { i = e + 1 } else { i = hlen } 246 } else { i = i + 1 } 247 } 248 } 249 250 // ---- pass 2: url(...) regions (catches UNQUOTED CSS backgrounds the quote scan misses) ---- 251 var u: i64 = 0 252 while u + 4 < hlen { 253 if count >= max_n { u = hlen } else { 254 // match "url(" case-insensitively 255 var hit: i64 = 0 256 if mh_lc(html[u] as i64)==117 { if mh_lc(html[u+1] as i64)==114 { if mh_lc(html[u+2] as i64)==108 { if html[u+3]==(40 as u8) { hit = 1 } } } } 257 if hit == 1 { 258 var s2: i64 = u + 4 259 // skip leading spaces and a single opening quote 260 var f: i64 = 1 261 while f == 1 { if s2 >= hlen { f = 0 } else { let cc: i64 = html[s2] as i64; if cc == 32 { s2 = s2 + 1 } else { if cc == 34 { s2 = s2 + 1; f = 0 } else { if cc == 39 { s2 = s2 + 1; f = 0 } else { f = 0 } } } } } 262 var e2: i64 = s2 263 var g2: i64 = 1 264 while g2 == 1 { if e2 >= hlen { g2 = 0 } else { let cc2: i64 = html[e2] as i64; if cc2 == 41 { g2 = 0 } else { if cc2 == 34 { g2 = 0 } else { if cc2 == 39 { g2 = 0 } else { e2 = e2 + 1 } } } } } 265 let tl2: i64 = e2 - s2 266 if tl2 > 0 { if tl2 < 8192 { 267 var w2: i64 = 0 268 while w2 < tl2 { tokbuf[w2] = html[s2 + w2]; w2 = w2 + 1 } 269 tokbuf[tl2] = 0 as u8 270 count = mh_consider(tokbuf, tl2, base_url, base_url_len, url_buf, url_buf_cap, offs, lens, kinds, max_n, count, bpp, unesc, resolved) 271 } } 272 if e2 > u { u = e2 } else { u = u + 1 } 273 } else { u = u + 1 } 274 } 275 } 276 277 return count 278} 279 280// human label for a kind (for callers that print/emit) 281func nx_media_kind_label(kind: i64) -> *u8 { 282 if kind == MH_KIND_GIF { return "gif" as *u8 } 283 if kind == MH_KIND_VIDEO { return "video" as *u8 } 284 if kind == MH_KIND_STREAM { return "stream" as *u8 } 285 if kind == MH_KIND_AUDIO { return "audio" as *u8 } 286 return "image" as *u8 287} 288 289// ---- shared HTML MEDIA PANEL emitter (used by nx_media_inspect AND the in-browser 'm' key) ---- 290// buffer-append helpers 291func mh_ap(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; var w: i64=off; while s[i]!=(0 as u8){ buf[w]=s[i]; w=w+1; i=i+1 } return w } 292func mh_apr(buf: *u8, off: i64, ptr: *u8, len: i64) -> i64 { var i: i64=0; var w: i64=off; while i<len { buf[w]=ptr[i]; w=w+1; i=i+1 } return w } 293func mh_apn(buf: *u8, off: i64, v0: i64) -> i64 { var v: i64=v0; var w: i64=off; if v<0 { buf[w]=45 as u8; w=w+1; v=0-v } let b: *u8=sys_mmap(24); var k: i64=0; if v==0{b[0]=48 as u8;k=1} while v>0{b[k]=(48+(v%10)) as u8;v=v/10;k=k+1} var j: i64=k-1; while j>=0{ buf[w]=b[j]; w=w+1; j=j-1 } return w } 294 295func mh_card_img(H: *u8, off: i64, p: *u8, ulen: i64) -> i64 { 296 var o: i64 = off 297 o = mh_ap(H, o, "<div class='card'><a class='prev' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' target='_blank' rel='noreferrer'><img loading='lazy' src='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' alt=''></a><div class='body'><div class='u mono'>" as *u8); o = mh_apr(H, o, p, ulen) 298 o = mh_ap(H, o, "</div><div class='row'><a class='btn' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' target='_blank' rel='noreferrer'>open</a><a class='btn' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' download>download</a></div></div></div>" as *u8) 299 return o 300} 301func mh_card_video(H: *u8, off: i64, p: *u8, ulen: i64) -> i64 { 302 var o: i64 = off 303 o = mh_ap(H, o, "<div class='card'><div class='prev'><video controls preload='metadata' src='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "'></video></div><div class='body'><div class='u mono'>" as *u8); o = mh_apr(H, o, p, ulen) 304 o = mh_ap(H, o, "</div><div class='row'><a class='btn' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' target='_blank' rel='noreferrer'>open</a><a class='btn' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' download>download</a></div></div></div>" as *u8) 305 return o 306} 307func mh_card_audio(H: *u8, off: i64, p: *u8, ulen: i64) -> i64 { 308 var o: i64 = off 309 o = mh_ap(H, o, "<div class='card'><div class='prev'><audio controls preload='metadata' src='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "'></audio></div><div class='body'><div class='u mono'>" as *u8); o = mh_apr(H, o, p, ulen) 310 o = mh_ap(H, o, "</div><div class='row'><a class='btn' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' target='_blank' rel='noreferrer'>open</a><a class='btn' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' download>download</a></div></div></div>" as *u8) 311 return o 312} 313func mh_card_link(H: *u8, off: i64, p: *u8, ulen: i64, note: *u8) -> i64 { 314 var o: i64 = off 315 o = mh_ap(H, o, "<div class='card'><div class='prev'><span class='ph'>" as *u8); o = mh_ap(H, o, note); o = mh_ap(H, o, "</span></div><div class='body'><div class='u mono'>" as *u8); o = mh_apr(H, o, p, ulen) 316 o = mh_ap(H, o, "</div><div class='row'><a class='btn' href='" as *u8); o = mh_apr(H, o, p, ulen); o = mh_ap(H, o, "' target='_blank' rel='noreferrer'>open</a></div></div></div>" as *u8) 317 return o 318} 319 320// Build the full media-panel HTML into H (caller sizes H for ~600B/item + 3KB chrome). Returns byte length. 321func nx_media_emit_panel(H: *u8, srcurl: *u8, srclen: i64, ub: *u8, offs: *i64, lens: *i64, kinds: *i64, cnt: i64) -> i64 { 322 var ci: i64=0; var cg: i64=0; var cv: i64=0; var cs: i64=0; var ca: i64=0 323 var t: i64 = 0 324 while t < cnt { 325 let kk: i64 = kinds[t] 326 if kk == MH_KIND_IMAGE { ci = ci + 1 } 327 if kk == MH_KIND_GIF { cg = cg + 1 } 328 if kk == MH_KIND_VIDEO { cv = cv + 1 } 329 if kk == MH_KIND_STREAM { cs = cs + 1 } 330 if kk == MH_KIND_AUDIO { ca = ca + 1 } 331 t = t + 1 332 } 333 var o: i64 = 0 334 o = mh_ap(H, o, "<!DOCTYPE html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><meta name='color-scheme' content='dark light'><title>Media sources</title><style>" as *u8) 335 o = mh_ap(H, o, "body{margin:0;background:#12131a;color:#e8e8ef;font:15px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,Arial,sans-serif}" as *u8) 336 o = mh_ap(H, o, ".wrap{max-width:1180px;margin:0 auto;padding:22px 18px 60px}h1{font-size:1.15rem;margin:0 0 4px}.src{color:#8a93a6;font-size:.85rem;word-break:break-all;margin:0 0 14px}" as *u8) 337 o = mh_ap(H, o, ".counts{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 20px}.pill{background:#1e2130;border:1px solid #2c3040;border-radius:999px;padding:4px 12px;font-size:.82rem;color:#c8cfe0}" as *u8) 338 o = mh_ap(H, o, "h2{font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;color:#7d879b;margin:26px 0 10px;border-bottom:1px solid #242838;padding-bottom:6px}" as *u8) 339 o = mh_ap(H, o, ".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:14px}.card{background:#191b26;border:1px solid #272b3b;border-radius:12px;overflow:hidden;display:flex;flex-direction:column}" as *u8) 340 o = mh_ap(H, o, ".card .prev{background:#0d0e14;aspect-ratio:16/10;display:flex;align-items:center;justify-content:center;overflow:hidden}.card .prev img,.card .prev video{max-width:100%;max-height:100%;object-fit:contain}" as *u8) 341 o = mh_ap(H, o, ".card .body{padding:9px 11px;display:flex;flex-direction:column;gap:7px}.u{font-size:.74rem;color:#aeb6c9;word-break:break-all;line-height:1.35}" as *u8) 342 o = mh_ap(H, o, ".row{display:flex;gap:7px}.btn{flex:1;text-align:center;font-size:.76rem;text-decoration:none;padding:5px 8px;border-radius:7px;border:1px solid #313648;color:#dfe4f0;background:#232838;cursor:pointer}.btn:hover{background:#2b3145}" as *u8) 343 o = mh_ap(H, o, ".ph{color:#5f677d;font-size:.8rem;text-align:center;padding:0 10px}.mono{font-family:ui-monospace,Menlo,Consolas,monospace}</style></head><body><div class='wrap'>" as *u8) 344 o = mh_ap(H, o, "<h1>Media sources on this page</h1><p class='src mono'>" as *u8) 345 o = mh_apr(H, o, srcurl, srclen) 346 o = mh_ap(H, o, "</p><div class='counts'>" as *u8) 347 o = mh_ap(H, o, "<span class='pill'>" as *u8); o = mh_apn(H, o, cnt); o = mh_ap(H, o, " total</span>" as *u8) 348 o = mh_ap(H, o, "<span class='pill'>" as *u8); o = mh_apn(H, o, ci); o = mh_ap(H, o, " image</span>" as *u8) 349 o = mh_ap(H, o, "<span class='pill'>" as *u8); o = mh_apn(H, o, cg); o = mh_ap(H, o, " gif</span>" as *u8) 350 o = mh_ap(H, o, "<span class='pill'>" as *u8); o = mh_apn(H, o, cv); o = mh_ap(H, o, " video</span>" as *u8) 351 o = mh_ap(H, o, "<span class='pill'>" as *u8); o = mh_apn(H, o, cs); o = mh_ap(H, o, " stream</span>" as *u8) 352 o = mh_ap(H, o, "<span class='pill'>" as *u8); o = mh_apn(H, o, ca); o = mh_ap(H, o, " audio</span></div>" as *u8) 353 // images + gifs 354 o = mh_ap(H, o, "<h2>Images &amp; GIFs</h2><div class='grid'>" as *u8) 355 var q: i64 = 0 356 while q < cnt { let kk: i64 = kinds[q]; let p: *u8 = ((ub as i64) + offs[q]) as *u8; if kk == MH_KIND_IMAGE { o = mh_card_img(H, o, p, lens[q]) } if kk == MH_KIND_GIF { o = mh_card_img(H, o, p, lens[q]) } q = q + 1 } 357 o = mh_ap(H, o, "</div><h2>Video files</h2><div class='grid'>" as *u8) 358 var q2: i64 = 0 359 while q2 < cnt { if kinds[q2] == MH_KIND_VIDEO { let p2: *u8 = ((ub as i64) + offs[q2]) as *u8; o = mh_card_video(H, o, p2, lens[q2]) } q2 = q2 + 1 } 360 o = mh_ap(H, o, "</div><h2>Stream manifests (HLS / DASH)</h2><div class='grid'>" as *u8) 361 var q3: i64 = 0 362 while q3 < cnt { if kinds[q3] == MH_KIND_STREAM { let p3: *u8 = ((ub as i64) + offs[q3]) as *u8; o = mh_card_link(H, o, p3, lens[q3], "manifest \xe2\x80\x94 open in a player" as *u8) } q3 = q3 + 1 } 363 o = mh_ap(H, o, "</div><h2>Audio files</h2><div class='grid'>" as *u8) 364 var q4: i64 = 0 365 while q4 < cnt { if kinds[q4] == MH_KIND_AUDIO { let p4: *u8 = ((ub as i64) + offs[q4]) as *u8; o = mh_card_audio(H, o, p4, lens[q4]) } q4 = q4 + 1 } 366 o = mh_ap(H, o, "</div></div></body></html>" as *u8) 367 return o 368}