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}