code wiki / _hdl_build / nx_roku_grid_emit.nx

nx_roku_grid_emit.nx source

↩ module page · 224 lines · 15069 B

1// nx_roku_grid_emit.nx -- SOVEREIGN Roku grid channel emitter (R1 embedded + R1c remote-fetch). Emits a channel 2// whose home screen is a SceneGraph MarkupGrid of media items. Two modes: 3// (default) embedded catalog -- GridScene builds 6 ContentNodes from a baked-in list (offline; the 4// device first-light build). 5// remote <baseurl> server-backed client -- GridScene fetches <baseurl>/catalog.json via roUrlTransfer + 6// ParseJson and builds the grid from the live feed (the Jellyfin model; the real app). 7// Parts: manifest, source/main.brs, components/GridScene.xml, components/PosterItem.xml, images/icon.png + 8// poster1..6.png (sovereign solid-colour generated stills, our own PNG encoder). BrightScript/SceneGraph EMITTED, 9// never hand-written. Packaged by opc_write (STORED zip, real CRC-32). PNG/zip helpers duplicated from nx_roku_emit 10// (2nd use; extract to nx_roku_lib at a 3rd PNG consumer). license_tier: ORIGINAL 11// nx_roku_grid_emit [out.zip] (embedded) 12// nx_roku_grid_emit remote <baseurl> [out.zip] (remote-fetch) 13import "nx_syscalls.nx" 14import "nx_crc32.nx" 15import "nx_opc.nx" 16const K_MAGIC_65521: i64 = 65521 17const K_MAGIC_262144: i64 = 262144 18const K_MAGIC_65535: i64 = 65535 19const K_MAGIC_4096: i64 = 4096 20const K_MAGIC_8192: i64 = 8192 21 22func rk_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 23func rk_num(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; var k: i64=0; if m==0{bb[0]=48 as u8;sys_write(1,bb,1);return 0}; let t: *u8=sys_mmap(28); 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(1,bb,k); return 0 } 24func rk_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 0} return 1 } 25func rk_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 } 26func rk_u32be(buf: *u8, off: i64, v: i64) -> i64 { buf[off]=((v>>24)&0xff) as u8; buf[off+1]=((v>>16)&0xff) as u8; buf[off+2]=((v>>8)&0xff) as u8; buf[off+3]=(v&0xff) as u8; return off+4 } 27func rk_u16le(buf: *u8, off: i64, v: i64) -> i64 { buf[off]=(v&0xff) as u8; buf[off+1]=((v>>8)&0xff) as u8; return off+2 } 28func rk_adler32(data: *u8, n: i64) -> i64 { var a: i64=1; var b: i64=0; var i: i64=0; while i<n { a=(a+(data[i] as i64))%K_MAGIC_65521; b=(b+a)%K_MAGIC_65521; i=i+1 } return (b<<16)|a } 29func rk_png_chunk(out: *u8, off: i64, typ: *u8, data: *u8, dlen: i64) -> i64 { 30 var o: i64=rk_u32be(out, off, dlen); let crcstart: i64=o 31 out[o]=typ[0]; out[o+1]=typ[1]; out[o+2]=typ[2]; out[o+3]=typ[3]; o=o+4 32 var i: i64=0; while i<dlen { out[o]=data[i]; o=o+1; i=i+1 } 33 let crc: i64=nx_crc32(((out as i64)+crcstart) as *u8, o-crcstart) 34 o=rk_u32be(out, o, crc); return o 35} 36func rk_png_solid(out: *u8, w: i64, h: i64, r: i64, g: i64, bl: i64) -> i64 { 37 out[0]=137 as u8; out[1]=80 as u8; out[2]=78 as u8; out[3]=71 as u8 38 out[4]=13 as u8; out[5]=10 as u8; out[6]=26 as u8; out[7]=10 as u8 39 var o: i64=8 40 let ihdr: *u8=sys_mmap(32); var p: i64=0 41 p=rk_u32be(ihdr, p, w); p=rk_u32be(ihdr, p, h) 42 ihdr[p]=8 as u8; p=p+1; ihdr[p]=2 as u8; p=p+1; ihdr[p]=0 as u8; p=p+1; ihdr[p]=0 as u8; p=p+1; ihdr[p]=0 as u8; p=p+1 43 o=rk_png_chunk(out, o, "IHDR" as *u8, ihdr, p) 44 let raw: *u8=sys_mmap(K_MAGIC_262144); var ro: i64=0; var y: i64=0 45 while y<h { raw[ro]=0 as u8; ro=ro+1; var x: i64=0; while x<w { raw[ro]=r as u8; raw[ro+1]=g as u8; raw[ro+2]=bl as u8; ro=ro+3; x=x+1 } y=y+1 } 46 let rawlen: i64=ro 47 let z: *u8=sys_mmap(K_MAGIC_262144); var zo: i64=0 48 z[0]=0x78 as u8; z[1]=0x01 as u8; zo=2 49 z[zo]=0x01 as u8; zo=zo+1 50 zo=rk_u16le(z, zo, rawlen); zo=rk_u16le(z, zo, K_MAGIC_65535-rawlen) 51 var i: i64=0; while i<rawlen { z[zo]=raw[i]; zo=zo+1; i=i+1 } 52 zo=rk_u32be(z, zo, rk_adler32(raw, rawlen)) 53 o=rk_png_chunk(out, o, "IDAT" as *u8, z, zo) 54 o=rk_png_chunk(out, o, "IEND" as *u8, 0 as *u8, 0) 55 return o 56} 57 58// GridScene.xml prefix through </children> (common to both modes) 59func emit_gridscene_head(scn: *u8, off: i64) -> i64 { 60 var so: i64=off 61 so=rk_cat(scn, so, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" as *u8) 62 so=rk_cat(scn, so, "<component name=\"GridScene\" extends=\"Scene\">\n" as *u8) 63 so=rk_cat(scn, so, " <interface>\n" as *u8) 64 so=rk_cat(scn, so, " <field id=\"deepLinkContentId\" type=\"string\" />\n" as *u8) 65 so=rk_cat(scn, so, " <field id=\"deepLinkMediaType\" type=\"string\" />\n" as *u8) 66 so=rk_cat(scn, so, " </interface>\n" as *u8) 67 so=rk_cat(scn, so, " <children>\n" as *u8) 68 so=rk_cat(scn, so, " <Rectangle width=\"1280\" height=\"720\" color=\"0x10243Cff\" />\n" as *u8) 69 so=rk_cat(scn, so, " <Label text=\"Nishi - Gallery\" translation=\"[80,40]\" width=\"700\" color=\"0xFFFFFFff\" />\n" as *u8) 70 so=rk_cat(scn, so, " <MarkupGrid id=\"grid\" translation=\"[80,120]\" itemComponentName=\"PosterItem\" numColumns=\"5\" numRows=\"2\" itemSize=\"[200,200]\" itemSpacing=\"[24,24]\" />\n" as *u8) 71 so=rk_cat(scn, so, " </children>\n" as *u8) 72 return so 73} 74// embedded mode: build the grid from a baked-in list (offline first-light build) 75func emit_gridscene_embedded(scn: *u8) -> i64 { 76 var so: i64=emit_gridscene_head(scn, 0) 77 so=rk_cat(scn, so, " <script type=\"text/brightscript\"><![CDATA[\n" as *u8) 78 so=rk_cat(scn, so, " sub init()\n" as *u8) 79 so=rk_cat(scn, so, " grid = m.top.findNode(\"grid\")\n" as *u8) 80 so=rk_cat(scn, so, " root = CreateObject(\"roSGNode\",\"ContentNode\")\n" as *u8) 81 so=rk_cat(scn, so, " titles = [\"Sunrise\",\"Forest\",\"Ocean\",\"Desert\",\"Aurora\",\"City\"]\n" as *u8) 82 so=rk_cat(scn, so, " uris = [\"pkg:/images/poster1.png\",\"pkg:/images/poster2.png\",\"pkg:/images/poster3.png\",\"pkg:/images/poster4.png\",\"pkg:/images/poster5.png\",\"pkg:/images/poster6.png\"]\n" as *u8) 83 so=rk_cat(scn, so, " for i = 0 to 5\n" as *u8) 84 so=rk_cat(scn, so, " item = root.createChild(\"ContentNode\")\n" as *u8) 85 so=rk_cat(scn, so, " item.title = titles[i]\n" as *u8) 86 so=rk_cat(scn, so, " item.HDPOSTERURL = uris[i]\n" as *u8) 87 so=rk_cat(scn, so, " end for\n" as *u8) 88 so=rk_cat(scn, so, " grid.content = root\n" as *u8) 89 so=rk_cat(scn, so, " grid.setFocus(true)\n" as *u8) 90 so=rk_cat(scn, so, " end sub\n" as *u8) 91 so=rk_cat(scn, so, " ]]></script>\n" as *u8) 92 so=rk_cat(scn, so, "</component>\n" as *u8) 93 return so 94} 95// remote mode: fetch <baseurl>/catalog.json and build the grid from the live feed 96func emit_gridscene_remote(scn: *u8, baseurl: *u8) -> i64 { 97 var so: i64=emit_gridscene_head(scn, 0) 98 so=rk_cat(scn, so, " <script type=\"text/brightscript\"><![CDATA[\n" as *u8) 99 so=rk_cat(scn, so, " sub init()\n" as *u8) 100 so=rk_cat(scn, so, " m.grid = m.top.findNode(\"grid\")\n" as *u8) 101 so=rk_cat(scn, so, " m.grid.setFocus(true)\n" as *u8) 102 so=rk_cat(scn, so, " loadCatalog()\n" as *u8) 103 so=rk_cat(scn, so, " end sub\n" as *u8) 104 so=rk_cat(scn, so, " sub loadCatalog()\n" as *u8) 105 so=rk_cat(scn, so, " base = \"" as *u8); so=rk_cat(scn, so, baseurl); so=rk_cat(scn, so, "\"\n" as *u8) 106 so=rk_cat(scn, so, " x = CreateObject(\"roUrlTransfer\")\n" as *u8) 107 so=rk_cat(scn, so, " x.setUrl(base + \"/catalog.json\")\n" as *u8) 108 so=rk_cat(scn, so, " x.setCertificatesFile(\"common:/certs/ca-bundle.crt\")\n" as *u8) 109 so=rk_cat(scn, so, " x.initClientCertificates()\n" as *u8) 110 so=rk_cat(scn, so, " body = x.getToString()\n" as *u8) 111 so=rk_cat(scn, so, " data = ParseJson(body)\n" as *u8) 112 so=rk_cat(scn, so, " root = CreateObject(\"roSGNode\",\"ContentNode\")\n" as *u8) 113 so=rk_cat(scn, so, " if data <> invalid\n" as *u8) 114 so=rk_cat(scn, so, " if data.items <> invalid\n" as *u8) 115 so=rk_cat(scn, so, " for each it in data.items\n" as *u8) 116 so=rk_cat(scn, so, " item = root.createChild(\"ContentNode\")\n" as *u8) 117 so=rk_cat(scn, so, " item.title = it.title\n" as *u8) 118 so=rk_cat(scn, so, " item.HDPOSTERURL = base + \"/\" + it.poster\n" as *u8) 119 so=rk_cat(scn, so, " end for\n" as *u8) 120 so=rk_cat(scn, so, " end if\n" as *u8) 121 so=rk_cat(scn, so, " end if\n" as *u8) 122 so=rk_cat(scn, so, " m.grid.content = root\n" as *u8) 123 so=rk_cat(scn, so, " end sub\n" as *u8) 124 so=rk_cat(scn, so, " ]]></script>\n" as *u8) 125 so=rk_cat(scn, so, "</component>\n" as *u8) 126 return so 127} 128 129func main(argc: i64, argv: *i64) -> i64 { 130 var outpath: *u8="/mnt/c/Users/elder/nishi_roku_grid.zip" as *u8 131 var remote: i64=0 132 var baseurl: *u8="https://nishifamily.com/roku" as *u8 133 if argc>=2 { 134 if rk_streq(argv[1] as *u8, "remote" as *u8)==1 { 135 remote=1 136 if argc>=3 { baseurl=argv[2] as *u8 } 137 if argc>=4 { outpath=argv[3] as *u8 } 138 } else { outpath=argv[1] as *u8 } 139 } 140 141 let manifest: *u8=sys_mmap(K_MAGIC_4096); var mo: i64=0 142 mo=rk_cat(manifest, mo, "title=Nishi\n" as *u8) 143 mo=rk_cat(manifest, mo, "major_version=1\n" as *u8) 144 mo=rk_cat(manifest, mo, "minor_version=1\n" as *u8) 145 mo=rk_cat(manifest, mo, "build_version=00002\n" as *u8) 146 mo=rk_cat(manifest, mo, "mm_icon_focus_hd=pkg:/images/icon.png\n" as *u8) 147 mo=rk_cat(manifest, mo, "mm_icon_focus_sd=pkg:/images/icon.png\n" as *u8) 148 mo=rk_cat(manifest, mo, "splash_screen_hd=pkg:/images/icon.png\n" as *u8) 149 mo=rk_cat(manifest, mo, "ui_resolutions=hd\n" as *u8) 150 151 let mbrs: *u8=sys_mmap(K_MAGIC_4096); var bo: i64=0 152 bo=rk_cat(mbrs, bo, "sub Main(args as dynamic)\n" as *u8) 153 bo=rk_cat(mbrs, bo, " screen = CreateObject(\"roSGScreen\")\n" as *u8) 154 bo=rk_cat(mbrs, bo, " m.port = CreateObject(\"roMessagePort\")\n" as *u8) 155 bo=rk_cat(mbrs, bo, " screen.setMessagePort(m.port)\n" as *u8) 156 bo=rk_cat(mbrs, bo, " scene = screen.CreateScene(\"GridScene\")\n" as *u8) 157 bo=rk_cat(mbrs, bo, " screen.show()\n" as *u8) 158 bo=rk_cat(mbrs, bo, " ' deep linking (Roku cert): jump straight to the launched catalog item\n" as *u8) 159 bo=rk_cat(mbrs, bo, " if args <> invalid\n" as *u8) 160 bo=rk_cat(mbrs, bo, " if args.contentId <> invalid\n" as *u8) 161 bo=rk_cat(mbrs, bo, " scene.deepLinkContentId = args.contentId\n" as *u8) 162 bo=rk_cat(mbrs, bo, " end if\n" as *u8) 163 bo=rk_cat(mbrs, bo, " if args.mediaType <> invalid\n" as *u8) 164 bo=rk_cat(mbrs, bo, " scene.deepLinkMediaType = args.mediaType\n" as *u8) 165 bo=rk_cat(mbrs, bo, " end if\n" as *u8) 166 bo=rk_cat(mbrs, bo, " end if\n" as *u8) 167 bo=rk_cat(mbrs, bo, " while true\n" as *u8) 168 bo=rk_cat(mbrs, bo, " msg = wait(0, m.port)\n" as *u8) 169 bo=rk_cat(mbrs, bo, " if type(msg) = \"roSGScreenEvent\"\n" as *u8) 170 bo=rk_cat(mbrs, bo, " if msg.isScreenClosed() then return\n" as *u8) 171 bo=rk_cat(mbrs, bo, " end if\n" as *u8) 172 bo=rk_cat(mbrs, bo, " end while\n" as *u8) 173 bo=rk_cat(mbrs, bo, "end sub\n" as *u8) 174 175 let scn: *u8=sys_mmap(K_MAGIC_8192); var so: i64=0 176 if remote==1 { so=emit_gridscene_remote(scn, baseurl) } else { so=emit_gridscene_embedded(scn) } 177 178 let pit: *u8=sys_mmap(K_MAGIC_4096); var po: i64=0 179 po=rk_cat(pit, po, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" as *u8) 180 po=rk_cat(pit, po, "<component name=\"PosterItem\" extends=\"Group\">\n" as *u8) 181 po=rk_cat(pit, po, " <interface>\n" as *u8) 182 po=rk_cat(pit, po, " <field id=\"itemContent\" type=\"node\" onChange=\"onContentChanged\" />\n" as *u8) 183 po=rk_cat(pit, po, " </interface>\n" as *u8) 184 po=rk_cat(pit, po, " <children>\n" as *u8) 185 po=rk_cat(pit, po, " <Poster id=\"poster\" width=\"200\" height=\"150\" />\n" as *u8) 186 po=rk_cat(pit, po, " <Label id=\"lbl\" translation=\"[0,158]\" width=\"200\" color=\"0xFFFFFFff\" />\n" as *u8) 187 po=rk_cat(pit, po, " </children>\n" as *u8) 188 po=rk_cat(pit, po, " <script type=\"text/brightscript\"><![CDATA[\n" as *u8) 189 po=rk_cat(pit, po, " sub onContentChanged()\n" as *u8) 190 po=rk_cat(pit, po, " c = m.top.itemContent\n" as *u8) 191 po=rk_cat(pit, po, " m.top.findNode(\"poster\").uri = c.HDPOSTERURL\n" as *u8) 192 po=rk_cat(pit, po, " m.top.findNode(\"lbl\").text = c.title\n" as *u8) 193 po=rk_cat(pit, po, " end sub\n" as *u8) 194 po=rk_cat(pit, po, " ]]></script>\n" as *u8) 195 po=rk_cat(pit, po, "</component>\n" as *u8) 196 197 let icon: *u8=sys_mmap(K_MAGIC_262144); let iconlen: i64=rk_png_solid(icon, 128, 128, 20, 110, 180) 198 let p1: *u8=sys_mmap(K_MAGIC_262144); let l1: i64=rk_png_solid(p1, 128, 128, 200, 80, 60) 199 let p2: *u8=sys_mmap(K_MAGIC_262144); let l2: i64=rk_png_solid(p2, 128, 128, 40, 150, 90) 200 let p3: *u8=sys_mmap(K_MAGIC_262144); let l3: i64=rk_png_solid(p3, 128, 128, 30, 90, 180) 201 let p4: *u8=sys_mmap(K_MAGIC_262144); let l4: i64=rk_png_solid(p4, 128, 128, 210, 160, 50) 202 let p5: *u8=sys_mmap(K_MAGIC_262144); let l5: i64=rk_png_solid(p5, 128, 128, 130, 60, 170) 203 let p6: *u8=sys_mmap(K_MAGIC_262144); let l6: i64=rk_png_solid(p6, 128, 128, 200, 90, 150) 204 205 let names: *i64=sys_mmap(8*16) as *i64 206 let datap: *i64=sys_mmap(8*16) as *i64 207 let lens: *i64=sys_mmap(8*16) as *i64 208 names[0]="manifest" as *u8 as i64; datap[0]=manifest as i64; lens[0]=mo 209 names[1]="source/main.brs" as *u8 as i64; datap[1]=mbrs as i64; lens[1]=bo 210 names[2]="components/GridScene.xml" as *u8 as i64; datap[2]=scn as i64; lens[2]=so 211 names[3]="components/PosterItem.xml" as *u8 as i64; datap[3]=pit as i64; lens[3]=po 212 names[4]="images/icon.png" as *u8 as i64; datap[4]=icon as i64; lens[4]=iconlen 213 names[5]="images/poster1.png" as *u8 as i64; datap[5]=p1 as i64; lens[5]=l1 214 names[6]="images/poster2.png" as *u8 as i64; datap[6]=p2 as i64; lens[6]=l2 215 names[7]="images/poster3.png" as *u8 as i64; datap[7]=p3 as i64; lens[7]=l3 216 names[8]="images/poster4.png" as *u8 as i64; datap[8]=p4 as i64; lens[8]=l4 217 names[9]="images/poster5.png" as *u8 as i64; datap[9]=p5 as i64; lens[9]=l5 218 names[10]="images/poster6.png" as *u8 as i64; datap[10]=p6 as i64; lens[10]=l6 219 let nbytes: i64=opc_write(outpath, names, datap, lens, 11) 220 if nbytes<0 { rk_puts("ROKU-GRID-EMIT-FAIL\n" as *u8); sys_exit(1); return 1 } 221 rk_puts("ROKU-GRID-EMIT-OK mode=" as *u8); if remote==1 { rk_puts("remote" as *u8) } else { rk_puts("embedded" as *u8) } 222 rk_puts(" path=" as *u8); rk_puts(outpath); rk_puts(" zipbytes=" as *u8); rk_num(nbytes); rk_puts("\n" as *u8) 223 sys_exit(0); return 0 224}