@hugpy/video-intelligence-ui 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +43 -0
  2. package/index.html +17 -0
  3. package/package.json +1 -2
  4. package/src/config.ts +8 -0
  5. package/src/demo/demoFetch.ts +5 -2
  6. package/src/demo/fixtures.ts +24 -22
  7. package/src/demo/identityFixture.ts +96 -0
  8. package/src/demo/mediaBase.ts +62 -0
  9. package/src/demo/sampleData.ts +32 -31
  10. package/src/demo/sampleSets.ts +25 -24
  11. package/assets/sampels/images/bycycle/bd8603bad35d4f179f4d1134d8faf2ee.mp4 +0 -0
  12. package/assets/sampels/images/bycycle/frame_00000.png +0 -0
  13. package/assets/sampels/images/bycycle/frame_00001.png +0 -0
  14. package/assets/sampels/images/bycycle/frame_00002.png +0 -0
  15. package/assets/sampels/images/bycycle/frame_00003.png +0 -0
  16. package/assets/sampels/images/bycycle/frame_00004.png +0 -0
  17. package/assets/sampels/images/bycycle/frame_00005.png +0 -0
  18. package/assets/sampels/images/forrest/ff23090611b844a6b85c224f388bd542.mp4 +0 -0
  19. package/assets/sampels/images/forrest/frame_00000.png +0 -0
  20. package/assets/sampels/images/forrest/frame_00001.png +0 -0
  21. package/assets/sampels/images/forrest/frame_00002.png +0 -0
  22. package/assets/sampels/images/forrest/frame_00003.png +0 -0
  23. package/assets/sampels/images/forrest/frame_00004.png +0 -0
  24. package/assets/sampels/images/forrest/frame_00005.png +0 -0
  25. package/assets/sampels/images/shenzo/e1a4fcd623084ab4a986d11c8cd00db8.mp4 +0 -0
  26. package/assets/sampels/images/shenzo/frame_00000.png +0 -0
  27. package/assets/sampels/images/shenzo/frame_00001.png +0 -0
  28. package/assets/sampels/images/shenzo/frame_00002.png +0 -0
  29. package/assets/sampels/images/shenzo/frame_00003.png +0 -0
  30. package/assets/sampels/images/shenzo/frame_00004.png +0 -0
  31. package/assets/sampels/images/shenzo/frame_00005.png +0 -0
  32. package/assets/sampels/scenes/man.mp4 +0 -0
  33. package/assets/sampels/scenes/woman.mp4 +0 -0
  34. package/assets/sampels/studio_movies/caverns/_assembly/contrib_00.mp4 +0 -0
  35. package/assets/sampels/studio_movies/caverns/movie.json +0 -67
  36. package/assets/sampels/studio_movies/caverns/movie.mp4 +0 -0
  37. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/clip.mp4 +0 -0
  38. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/manifest.json +0 -87
  39. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/provenance.json +0 -7
  40. package/assets/sampels/studio_movies/caverns/segment_01/branch.png +0 -0
  41. package/assets/sampels/studio_movies/keeper-matrix/_assembly/concat_list.txt +0 -2
  42. package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_00.mp4 +0 -0
  43. package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_01.mp4 +0 -0
  44. package/assets/sampels/studio_movies/keeper-matrix/movie.json +0 -81
  45. package/assets/sampels/studio_movies/keeper-matrix/movie.mp4 +0 -0
  46. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/clip.mp4 +0 -0
  47. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/manifest.json +0 -87
  48. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/provenance.json +0 -7
  49. package/assets/sampels/studio_movies/keeper-matrix/segment_01/branch.png +0 -0
  50. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/clip.mp4 +0 -0
  51. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/manifest.json +0 -87
  52. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/provenance.json +0 -7
  53. package/assets/sampels/videos/a_red_paper_boat_drifting_on_a_calm_pond__gentle_ripples__mo_a62d6c28.mp4 +0 -0
  54. package/assets/sampels/videos/aurora.mp4 +0 -0
  55. package/assets/sampels/videos/dark_night.mp4 +0 -0
  56. package/assets/sampels/videos/fire_and_soul.mp4 +0 -0
  57. package/assets/sampels/videos/harsh_seas.mp4 +0 -0
  58. package/src/demo/assets/demo-audio.mp3 +0 -0
  59. package/src/demo/assets/demo-clip.mp4 +0 -0
  60. package/src/demo/assets/demo-crop.mp4 +0 -0
  61. package/src/demo/assets/demo-poster.jpg +0 -0
  62. package/src/demo/assets/frame-1.jpg +0 -0
  63. package/src/demo/assets/frame-2.jpg +0 -0
  64. package/src/demo/assets/frame-3.jpg +0 -0
  65. package/src/demo/assets/frame-4.jpg +0 -0
  66. package/src/demo/assets/frame-5.jpg +0 -0
  67. package/src/demo/assets/frame-6.jpg +0 -0
  68. package/src/demo/assets/gen-1.jpg +0 -0
  69. package/src/demo/assets/gen-2.jpg +0 -0
  70. package/src/demo/assets/generate/cinema-cavern/movie.mp4 +0 -0
  71. package/src/demo/assets/generate/cinema-cavern/seg_00.mp4 +0 -0
  72. package/src/demo/assets/generate/clips/cat-lounge.mp4 +0 -0
  73. package/src/demo/assets/generate/clips/drone-city.mp4 +0 -0
  74. package/src/demo/assets/generate/clips/paper-sails.mp4 +0 -0
  75. package/src/demo/assets/generate/movie-galaxy/movie.mp4 +0 -0
  76. package/src/demo/assets/generate/movie-galaxy/seg_00.mp4 +0 -0
  77. package/src/demo/assets/generate/movie-galaxy/seg_01.mp4 +0 -0
  78. package/src/demo/assets/generate/movie-galaxy/seg_02.mp4 +0 -0
  79. package/src/demo/assets/generate/movie-galaxy/seg_03.mp4 +0 -0
  80. package/src/demo/assets/generate/movie-medow/movie.mp4 +0 -0
  81. package/src/demo/assets/generate/movie-medow/seg_00.mp4 +0 -0
  82. package/src/demo/assets/generate/movie-medow/seg_01.mp4 +0 -0
  83. package/src/demo/assets/generate/scene-desert/frame_00000.png +0 -0
  84. package/src/demo/assets/generate/scene-desert/frame_00001.png +0 -0
  85. package/src/demo/assets/generate/scene-desert/frame_00002.png +0 -0
  86. package/src/demo/assets/generate/scene-desert/frame_00003.png +0 -0
  87. package/src/demo/assets/generate/scene-desert/frame_00004.png +0 -0
  88. package/src/demo/assets/generate/scene-desert/frame_00005.png +0 -0
  89. package/src/demo/assets/generate/scene-desert/scene.mp4 +0 -0
  90. package/src/demo/assets/generate/scene-vintage/frame_00000.png +0 -0
  91. package/src/demo/assets/generate/scene-vintage/frame_00001.png +0 -0
  92. package/src/demo/assets/generate/scene-vintage/frame_00002.png +0 -0
  93. package/src/demo/assets/generate/scene-vintage/frame_00003.png +0 -0
  94. package/src/demo/assets/generate/scene-vintage/frame_00004.png +0 -0
  95. package/src/demo/assets/generate/scene-vintage/frame_00005.png +0 -0
  96. package/src/demo/assets/generate/scene-vintage/scene.mp4 +0 -0
  97. package/src/demo/assets/generate2/s0.png +0 -0
  98. package/src/demo/assets/generate2/s1.png +0 -0
  99. package/src/demo/assets/generate2/s10.png +0 -0
  100. package/src/demo/assets/generate2/s11.png +0 -0
  101. package/src/demo/assets/generate2/s12.png +0 -0
  102. package/src/demo/assets/generate2/s13.png +0 -0
  103. package/src/demo/assets/generate2/s14.png +0 -0
  104. package/src/demo/assets/generate2/s15.png +0 -0
  105. package/src/demo/assets/generate2/s16.png +0 -0
  106. package/src/demo/assets/generate2/s17.png +0 -0
  107. package/src/demo/assets/generate2/s18.png +0 -0
  108. package/src/demo/assets/generate2/s19.png +0 -0
  109. package/src/demo/assets/generate2/s2.png +0 -0
  110. package/src/demo/assets/generate2/s20.png +0 -0
  111. package/src/demo/assets/generate2/s21.mp4 +0 -0
  112. package/src/demo/assets/generate2/s22.mp4 +0 -0
  113. package/src/demo/assets/generate2/s23.mp4 +0 -0
  114. package/src/demo/assets/generate2/s24.mp4 +0 -0
  115. package/src/demo/assets/generate2/s25.mp4 +0 -0
  116. package/src/demo/assets/generate2/s26.mp4 +0 -0
  117. package/src/demo/assets/generate2/s27.mp4 +0 -0
  118. package/src/demo/assets/generate2/s28.mp4 +0 -0
  119. package/src/demo/assets/generate2/s29.mp4 +0 -0
  120. package/src/demo/assets/generate2/s3.png +0 -0
  121. package/src/demo/assets/generate2/s30.mp4 +0 -0
  122. package/src/demo/assets/generate2/s4.png +0 -0
  123. package/src/demo/assets/generate2/s5.png +0 -0
  124. package/src/demo/assets/generate2/s6.png +0 -0
  125. package/src/demo/assets/generate2/s7.png +0 -0
  126. package/src/demo/assets/generate2/s8.png +0 -0
  127. package/src/demo/assets/generate2/s9.png +0 -0
  128. package/src/demo/assets.d.ts +0 -14
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @hugpy/video-intelligence-ui
2
+
3
+ Video-intelligence console — sibling arm of `media_intelligence_ui` (Vite +
4
+ Tailwind v4), served at `/video`. Four stations (image crop, audio crop,
5
+ frames/models, generate) over the hugpy job bus.
6
+
7
+ ## Demo media is hosted, not bundled
8
+
9
+ The canned demo (`?demo=1`) replays recorded job envelopes whose media
10
+ (~36 MB of sample mp4/png) is **not** shipped in this package or its build.
11
+ It loads from a hosted base, default `https://hugpy.ai/demo-media`.
12
+
13
+ Resolution order (see `src/demo/mediaBase.ts`, resolved once at load):
14
+
15
+ 1. `?mediaBase=` query param (http(s) or path-absolute values only)
16
+ 2. `window.__HUGPY_MEDIA_BASE__` — a non-empty string; `index.html` ships a
17
+ `window.__HUGPY_MEDIA_BASE__ = window.__HUGPY_MEDIA_BASE__ ?? null;`
18
+ marker that servers/self-hosters may rewrite (replace `null` with a
19
+ JSON string)
20
+ 3. `VITE_HUGPY_DEMO_MEDIA_BASE` (build-time, read in `src/config.ts` — the
21
+ one module permitted to hold URL literals)
22
+ 4. the default `https://hugpy.ai/demo-media`
23
+
24
+ ## Self-hosting the demo media (`hugpy serve`)
25
+
26
+ The Python server (`abstract_hugpy_dev` / `hugpy`) reads two env vars
27
+ (env or `.env`) and rewrites the marker in the served `index.html`:
28
+
29
+ - `HUGPY_DEMO_MEDIA_BASE=<url>` — point the demo at any hosted copy of the
30
+ media tree.
31
+ - `HUGPY_DEMO_MEDIA_DIR=<path>` — serve a local copy of the tree yourself at
32
+ `/demo-media/` (wins over `HUGPY_DEMO_MEDIA_BASE`). Unset → the route does
33
+ not exist.
34
+
35
+ ## Canonical media tree
36
+
37
+ The canonical copy lives at `/var/www/hugpy-media/` on the hugpy.ai VM,
38
+ served by an `location /demo-media/` block in the brochure nginx vhost —
39
+ deliberately OUTSIDE the brochure docroot (`/var/www/hugpy.ai`) so brochure
40
+ rebuilds (`stage.sh` rsync `--delete`) can never wipe it. Its layout mirrors
41
+ the `demoMedia("...")` specifiers: `generate/`, `generate2/`, top-level
42
+ `demo-*` files, and `sampels/` (that dir is intentionally spelled "sampels"
43
+ — do NOT rename it).
package/index.html CHANGED
@@ -11,6 +11,21 @@
11
11
  <meta name="theme-color" content="#050505" />
12
12
  <link rel="canonical" href="https://hugpy.ai/video/" />
13
13
 
14
+ <meta property="og:type" content="website" />
15
+ <meta property="og:site_name" content="Hugpy" />
16
+ <meta property="og:title" content="Hugpy Video Intelligence Studio" />
17
+ <meta
18
+ property="og:description"
19
+ content="Self-hosted video AI on hardware you own: scene and clip workflows, frame extraction, character identities, and image generation. Try the studio in demo mode — nothing to install."
20
+ />
21
+ <meta property="og:url" content="https://hugpy.ai/video/" />
22
+ <meta property="og:image" content="https://hugpy.ai/demo-media/og/video-thumb.png" />
23
+ <meta property="og:image:width" content="2400" />
24
+ <meta property="og:image:height" content="1260" />
25
+ <meta name="twitter:card" content="summary_large_image" />
26
+ <meta name="twitter:title" content="Hugpy Video Intelligence Studio" />
27
+ <meta name="twitter:image" content="https://hugpy.ai/demo-media/og/video-thumb.png" />
28
+
14
29
  <!-- Same type system as the media arm: Newsreader serif + IBM Plex Mono. -->
15
30
  <link rel="preconnect" href="https://fonts.googleapis.com" />
16
31
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@@ -48,6 +63,8 @@
48
63
  <p><a style="color:#8ab4ff" href="/">Hugpy — Inference You Own</a></p>
49
64
  </main>
50
65
  </noscript>
66
+ <!-- Self-hosters/servers: replace null with your demo-media base URL string. -->
67
+ <script>window.__HUGPY_MEDIA_BASE__ = window.__HUGPY_MEDIA_BASE__ ?? null;</script>
51
68
  <script type="module" src="./entry.tsx"></script>
52
69
  </body>
53
70
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hugpy/video-intelligence-ui",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "description": "Video-intelligence console \u2014 sibling arm of media_intelligence_ui (Vite + Tailwind v4), served at /video. Four stations (image crop, audio crop, frames/models, generate) over the hugpy job bus. Dials the same-origin current-hugpy API (/api).",
@@ -28,7 +28,6 @@
28
28
  "src",
29
29
  "entry.tsx",
30
30
  "index.html",
31
- "assets",
32
31
  "vite.config.ts",
33
32
  "tsconfig.json",
34
33
  "LICENSE"
package/src/config.ts CHANGED
@@ -39,6 +39,14 @@ export const hugpyConfig = {
39
39
  apiBase,
40
40
  /** Public hugpy site — CTA target. Override with VITE_HUGPY_SITE_URL. */
41
41
  siteUrl: stripTrailingSlash(read("VITE_HUGPY_SITE_URL") ?? "https://hugpy.ai"),
42
+ /**
43
+ * Hosted demo media tree (canned-demo samples live OUT of the bundle).
44
+ * Build-time override: VITE_HUGPY_DEMO_MEDIA_BASE. Runtime overrides
45
+ * (?mediaBase= / window.__HUGPY_MEDIA_BASE__) are layered in demo/mediaBase.ts.
46
+ */
47
+ demoMediaBase: stripTrailingSlash(
48
+ read("VITE_HUGPY_DEMO_MEDIA_BASE") ?? "https://hugpy.ai/demo-media",
49
+ ),
42
50
  /** Multipart file upload endpoint. */
43
51
  uploadUrl: read("VITE_HUGPY_UPLOAD_URL") ?? `${apiBase}/uploads`,
44
52
  /**
@@ -13,6 +13,7 @@ import { hugpyConfig } from "../config";
13
13
  import * as fx from "./fixtures";
14
14
  import type { JobKind } from "./fixtures";
15
15
  import { samplePromptFor, enhancePrompt } from "./promptAssist";
16
+ import { demoIdentityLuigi } from "./identityFixture";
16
17
 
17
18
  // ── request introspection (same helpers as the media arm) ───────────────────
18
19
  function pathOf(input: RequestInfo | URL): string {
@@ -245,9 +246,11 @@ async function route(
245
246
  }
246
247
 
247
248
  // IDENTITY PROFILES (Studio identity picker, useIdentityProfiles.ts, on mount)
248
- // — GET → { profiles: [...] }. No durable identities in the showroom; empty.
249
+ // — GET → { profiles: [...] }. One REAL canned identity ("luigi": 12 refs →
250
+ // 72-view turntable → textured GLB, assets on the hosted demo-media tree)
251
+ // showcases the full pipeline; mutations still 401/501 honestly below.
249
252
  if (path === "/video/identity-profiles" && method === "GET") {
250
- return jsonResponse({ profiles: [] });
253
+ return jsonResponse({ profiles: [demoIdentityLuigi] });
251
254
  }
252
255
 
253
256
  // k9 VIDEO-SHARE links (SharePanel probe on mount + mint/revoke). The public
@@ -1,35 +1,37 @@
1
1
  // Canned fixtures for the video-intelligence demo (?demo=1).
2
2
  //
3
- // Every MediaRef here points its `uri` at a Vite-BUNDLED asset URL rather than a
3
+ // Every MediaRef here points its `uri` at a HOSTED demo-media URL rather than a
4
4
  // server store key. That matters because <img>/<video> element loads do NOT go
5
5
  // through window.fetch (the shim can't intercept them) — so in canned mode
6
- // mediaBytesUrl() passes these absolute asset URLs straight through and the
7
- // browser loads the bundled bytes directly. See config.ts.
6
+ // mediaBytesUrl() passes these absolute URLs straight through and the browser
7
+ // loads the hosted bytes directly. See config.ts and demo/mediaBase.ts.
8
8
  import type { MediaRef } from "../video/contract";
9
9
  import { takeSet, sectionFor, type SampleSet } from "./sampleSets";
10
10
  import { SAMPLES2, type Sample2 } from "./sampleData";
11
11
 
12
- // Real sample media dropped by the operator under assets/sampels/ (note: the dir
13
- // is intentionally spelled "sampels" — do NOT rename it). Imported in place as
14
- // Vite asset URLs (relative from src/demo/), which resolve to bundled hashed URLs.
15
- import demoClipUrl from "../../assets/sampels/scenes/woman.mp4";
16
- import demoCropUrl from "../../assets/sampels/scenes/man.mp4";
17
- import demoAudioUrl from "./assets/demo-audio.mp3"; // no audio sample provided — kept
18
- import frame1 from "../../assets/sampels/images/forrest/frame_00000.png";
19
- import frame2 from "../../assets/sampels/images/forrest/frame_00001.png";
20
- import frame3 from "../../assets/sampels/images/forrest/frame_00002.png";
21
- import frame4 from "../../assets/sampels/images/forrest/frame_00003.png";
22
- import frame5 from "../../assets/sampels/images/forrest/frame_00004.png";
23
- import frame6 from "../../assets/sampels/images/forrest/frame_00005.png";
24
- import gen1 from "../../assets/sampels/images/shenzo/frame_00000.png";
25
- import gen2 from "../../assets/sampels/images/bycycle/frame_00000.png";
12
+ // Real sample media dropped by the operator — the sampels/ tree now lives at
13
+ // the HOSTED media base (default https://hugpy.ai/demo-media/sampels/; canonical
14
+ // copy /var/www/hugpy-media/ on the hugpy.ai VM). Note: the dir is intentionally
15
+ // spelled "sampels" — do NOT rename it in the hosted tree.
16
+ import { demoMedia } from "./mediaBase";
17
+ const demoClipUrl = demoMedia("sampels/scenes/woman.mp4");
18
+ const demoCropUrl = demoMedia("sampels/scenes/man.mp4");
19
+ const demoAudioUrl = demoMedia("demo-audio.mp3"); // no audio sample provided — kept
20
+ const frame1 = demoMedia("sampels/images/forrest/frame_00000.png");
21
+ const frame2 = demoMedia("sampels/images/forrest/frame_00001.png");
22
+ const frame3 = demoMedia("sampels/images/forrest/frame_00002.png");
23
+ const frame4 = demoMedia("sampels/images/forrest/frame_00003.png");
24
+ const frame5 = demoMedia("sampels/images/forrest/frame_00004.png");
25
+ const frame6 = demoMedia("sampels/images/forrest/frame_00005.png");
26
+ const gen1 = demoMedia("sampels/images/shenzo/frame_00000.png");
27
+ const gen2 = demoMedia("sampels/images/bycycle/frame_00000.png");
26
28
  // The forrest scene's assembled clip (single .mp4 in that folder — hashed name).
27
- import forrestSceneUrl from "../../assets/sampels/images/forrest/ff23090611b844a6b85c224f388bd542.mp4";
29
+ const forrestSceneUrl = demoMedia("sampels/images/forrest/ff23090611b844a6b85c224f388bd542.mp4");
28
30
  // The real 2-segment "keeper-matrix" sailboat movie (see movie.json for numbers).
29
- import movieUrl from "../../assets/sampels/studio_movies/keeper-matrix/movie.mp4";
30
- import movieSeg0Url from "../../assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/clip.mp4";
31
- import movieSeg1Url from "../../assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/clip.mp4";
32
- import movieSeg1BranchUrl from "../../assets/sampels/studio_movies/keeper-matrix/segment_01/branch.png";
31
+ const movieUrl = demoMedia("sampels/studio_movies/keeper-matrix/movie.mp4");
32
+ const movieSeg0Url = demoMedia("sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/clip.mp4");
33
+ const movieSeg1Url = demoMedia("sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/clip.mp4");
34
+ const movieSeg1BranchUrl = demoMedia("sampels/studio_movies/keeper-matrix/segment_01/branch.png");
33
35
 
34
36
  // ── the worked example: one ingested clip + derived assets ──────────────────
35
37
 
@@ -0,0 +1,96 @@
1
+ // Canned IDENTITY PROFILE for the video-intelligence demo (?demo=1): the real
2
+ // "luigi" identity (12 refs → 72-view turntable → textured GLB), served from the
3
+ // HOSTED demo-media tree (canonical copy /var/www/hugpy-media/identities/luigi/).
4
+ //
5
+ // CRITICAL: every asset path here is a demoMedia() URL. config.ts's
6
+ // mediaBytesUrl() canned-mode bypass passes absolute http(s) URLs straight
7
+ // through to the browser, but any "/mnt/"-prefixed store handle would be routed
8
+ // back to the API — where the demo shim honestly 501s. Shape mirrors the
9
+ // server's _public() projection (zod: src/stations/studio/useIdentityProfiles.ts).
10
+ import type { IdentityProfile } from "../stations/studio/useIdentityProfiles";
11
+ import { demoMedia } from "./mediaBase";
12
+
13
+ const pad2 = (n: number) => String(n).padStart(2, "0");
14
+
15
+ const luigiRefs = Array.from({ length: 12 }, (_, i) =>
16
+ demoMedia(`identities/luigi/refs/ref_${pad2(i)}.jpeg`),
17
+ );
18
+ const luigiCanonical = Array.from({ length: 4 }, (_, i) =>
19
+ demoMedia(`identities/luigi/canonical/ref_${pad2(i)}.jpg`),
20
+ );
21
+ const luigiViews = Array.from({ length: 72 }, (_, i) =>
22
+ demoMedia(`identities/luigi/views/view_${pad2(i)}.jpg`),
23
+ );
24
+
25
+ export const demoIdentityLuigi: IdentityProfile = {
26
+ slug: "luigi",
27
+ name: "Luigi",
28
+ created_at: 1784100737.0394623,
29
+ notes: "",
30
+ reference_images: luigiRefs,
31
+ canonical: luigiCanonical,
32
+ canonical_angles: [0.0, 90.0, 180.0, 270.0],
33
+ // The TEXTURED version — NOT the clay base — so the station's activeRecon
34
+ // resolution (active_version → version.recon_id → reconstructions[]) lands on
35
+ // the turntable recon with the done mesh below.
36
+ active_version: "ver_851e3e418dc3869a",
37
+ versions: [
38
+ {
39
+ version_id: "ver_d9f3bf88e5fc7acd",
40
+ name: "base",
41
+ kind: "clay",
42
+ recon_id: "identity_ace73ab07078c529",
43
+ created_at: 1784101503.040569,
44
+ canonical: [],
45
+ notes: "",
46
+ },
47
+ {
48
+ version_id: "ver_851e3e418dc3869a",
49
+ name: "textured-01",
50
+ kind: "textured",
51
+ recon_id: "identity_64853ed017380507",
52
+ created_at: 1784101750.5200672,
53
+ canonical: luigiCanonical,
54
+ notes: "",
55
+ },
56
+ ],
57
+ reconstructions: [
58
+ {
59
+ recon_id: "identity_64853ed017380507",
60
+ mode: "turntable",
61
+ created_at: 1784101750.4968696,
62
+ frame_count: 72,
63
+ degrees_per_frame: 5.0,
64
+ job_id: "b31f8a1eb4e94d4faf8552cb83012290",
65
+ views: luigiViews,
66
+ mesh: {
67
+ status: "done",
68
+ error: null,
69
+ textured: true,
70
+ frame_count: 72,
71
+ auto_promoted: true,
72
+ glb_path: demoMedia("identities/luigi/identity.glb"),
73
+ video_path: demoMedia("identities/luigi/turntable.mp4"),
74
+ version_id: "ver_851e3e418dc3869a",
75
+ version_kind: "textured",
76
+ version_name: "textured-01",
77
+ job_id: "b31f8a1eb4e94d4faf8552cb83012290",
78
+ },
79
+ },
80
+ ],
81
+ gen_settings: {
82
+ texture: true,
83
+ pose: "t-pose",
84
+ frame_count: 72,
85
+ fps: 24,
86
+ width: 768,
87
+ height: 768,
88
+ auto_promote: true,
89
+ front_ref: null,
90
+ remove_background: true,
91
+ vision_model: "Qwen2.5-VL-3B-Instruct-GGUF",
92
+ cleanup_prompt: "",
93
+ negative_prompt: "",
94
+ },
95
+ authorization: {},
96
+ };
@@ -0,0 +1,62 @@
1
+ // Demo media base — where the canned-demo sample tree is served from.
2
+ //
3
+ // The demo media (~36M of mp4/png samples) is NOT bundled; it lives at a
4
+ // hosted base (default https://hugpy.ai/demo-media, see config.ts — the ONE
5
+ // module permitted to hold URL literals). Resolved ONCE at module load, same
6
+ // as demo/mode.ts. Precedence:
7
+ // 1. ?mediaBase= query param (http(s) or path-absolute only)
8
+ // 2. window.__HUGPY_MEDIA_BASE__ (non-empty string — servers/self-hosters
9
+ // set this by rewriting the index.html marker; see index.html)
10
+ // 3. VITE_HUGPY_DEMO_MEDIA_BASE (build-time, read in config.ts)
11
+ // 4. the config.ts default.
12
+
13
+ import { hugpyConfig } from "../config";
14
+
15
+ declare global {
16
+ interface Window {
17
+ __HUGPY_MEDIA_BASE__?: string | null;
18
+ }
19
+ }
20
+
21
+ function stripTrailingSlash(url: string): string {
22
+ return url.replace(/\/+$/, "");
23
+ }
24
+
25
+ /** Accept only http(s) URLs or path-absolute ("/...") bases. */
26
+ function acceptable(raw: string): boolean {
27
+ return /^(https?:\/\/|\/)/i.test(raw);
28
+ }
29
+
30
+ function resolve(): string {
31
+ // 1. ?mediaBase= — same query-param precedent as demo/mode.ts (?demo=).
32
+ try {
33
+ const params = new URLSearchParams(
34
+ typeof window !== "undefined" ? window.location.search : "",
35
+ );
36
+ const raw = (params.get("mediaBase") || "").trim();
37
+ if (raw && acceptable(raw)) return stripTrailingSlash(raw);
38
+ } catch {
39
+ /* fall through */
40
+ }
41
+
42
+ // 2. window.__HUGPY_MEDIA_BASE__ — server-injected (index.html marker).
43
+ try {
44
+ const injected =
45
+ typeof window !== "undefined" ? window.__HUGPY_MEDIA_BASE__ : null;
46
+ if (typeof injected === "string" && injected.trim()) {
47
+ return stripTrailingSlash(injected.trim());
48
+ }
49
+ } catch {
50
+ /* fall through */
51
+ }
52
+
53
+ // 3./4. Build-time env override or the config default (both in config.ts).
54
+ return hugpyConfig.demoMediaBase;
55
+ }
56
+
57
+ const base = resolve();
58
+
59
+ /** Absolute URL for a demo media file, e.g. demoMedia("generate2/s0.png"). */
60
+ export function demoMedia(relPath: string): string {
61
+ return `${base}/${relPath}`;
62
+ }
@@ -3,37 +3,38 @@
3
3
  // assets), so the canned wire shape is exactly what production returned.
4
4
  // Regenerate with the script in the session notes; do not hand-edit results.
5
5
 
6
- import A0 from "./assets/generate2/s0.png";
7
- import A1 from "./assets/generate2/s1.png";
8
- import A2 from "./assets/generate2/s2.png";
9
- import A3 from "./assets/generate2/s3.png";
10
- import A4 from "./assets/generate2/s4.png";
11
- import A5 from "./assets/generate2/s5.png";
12
- import A6 from "./assets/generate2/s6.png";
13
- import A7 from "./assets/generate2/s7.png";
14
- import A8 from "./assets/generate2/s8.png";
15
- import A9 from "./assets/generate2/s9.png";
16
- import A10 from "./assets/generate2/s10.png";
17
- import A11 from "./assets/generate2/s11.png";
18
- import A12 from "./assets/generate2/s12.png";
19
- import A13 from "./assets/generate2/s13.png";
20
- import A14 from "./assets/generate2/s14.png";
21
- import A15 from "./assets/generate2/s15.png";
22
- import A16 from "./assets/generate2/s16.png";
23
- import A17 from "./assets/generate2/s17.png";
24
- import A18 from "./assets/generate2/s18.png";
25
- import A19 from "./assets/generate2/s19.png";
26
- import A20 from "./assets/generate2/s20.png";
27
- import A21 from "./assets/generate2/s21.mp4";
28
- import A22 from "./assets/generate2/s22.mp4";
29
- import A23 from "./assets/generate2/s23.mp4";
30
- import A24 from "./assets/generate2/s24.mp4";
31
- import A25 from "./assets/generate2/s25.mp4";
32
- import A26 from "./assets/generate2/s26.mp4";
33
- import A27 from "./assets/generate2/s27.mp4";
34
- import A28 from "./assets/generate2/s28.mp4";
35
- import A29 from "./assets/generate2/s29.mp4";
36
- import A30 from "./assets/generate2/s30.mp4";
6
+ import { demoMedia } from "./mediaBase";
7
+ const A0 = demoMedia("generate2/s0.png");
8
+ const A1 = demoMedia("generate2/s1.png");
9
+ const A2 = demoMedia("generate2/s2.png");
10
+ const A3 = demoMedia("generate2/s3.png");
11
+ const A4 = demoMedia("generate2/s4.png");
12
+ const A5 = demoMedia("generate2/s5.png");
13
+ const A6 = demoMedia("generate2/s6.png");
14
+ const A7 = demoMedia("generate2/s7.png");
15
+ const A8 = demoMedia("generate2/s8.png");
16
+ const A9 = demoMedia("generate2/s9.png");
17
+ const A10 = demoMedia("generate2/s10.png");
18
+ const A11 = demoMedia("generate2/s11.png");
19
+ const A12 = demoMedia("generate2/s12.png");
20
+ const A13 = demoMedia("generate2/s13.png");
21
+ const A14 = demoMedia("generate2/s14.png");
22
+ const A15 = demoMedia("generate2/s15.png");
23
+ const A16 = demoMedia("generate2/s16.png");
24
+ const A17 = demoMedia("generate2/s17.png");
25
+ const A18 = demoMedia("generate2/s18.png");
26
+ const A19 = demoMedia("generate2/s19.png");
27
+ const A20 = demoMedia("generate2/s20.png");
28
+ const A21 = demoMedia("generate2/s21.mp4");
29
+ const A22 = demoMedia("generate2/s22.mp4");
30
+ const A23 = demoMedia("generate2/s23.mp4");
31
+ const A24 = demoMedia("generate2/s24.mp4");
32
+ const A25 = demoMedia("generate2/s25.mp4");
33
+ const A26 = demoMedia("generate2/s26.mp4");
34
+ const A27 = demoMedia("generate2/s27.mp4");
35
+ const A28 = demoMedia("generate2/s28.mp4");
36
+ const A29 = demoMedia("generate2/s29.mp4");
37
+ const A30 = demoMedia("generate2/s30.mp4");
37
38
 
38
39
  export interface Sample2 { kind: string; prompts: string[]; result: Record<string, unknown>; }
39
40
  export const SAMPLES2: Sample2[] = [
@@ -22,35 +22,36 @@
22
22
  import type { MediaRef } from "../video/contract";
23
23
 
24
24
  // scene: desert roads (job 187ac900…, sd-turbo, 6 frames @512)
25
- import sdF0 from "./assets/generate/scene-desert/frame_00000.png";
26
- import sdF1 from "./assets/generate/scene-desert/frame_00001.png";
27
- import sdF2 from "./assets/generate/scene-desert/frame_00002.png";
28
- import sdF3 from "./assets/generate/scene-desert/frame_00003.png";
29
- import sdF4 from "./assets/generate/scene-desert/frame_00004.png";
30
- import sdF5 from "./assets/generate/scene-desert/frame_00005.png";
31
- import sdClip from "./assets/generate/scene-desert/scene.mp4";
25
+ import { demoMedia } from "./mediaBase";
26
+ const sdF0 = demoMedia("generate/scene-desert/frame_00000.png");
27
+ const sdF1 = demoMedia("generate/scene-desert/frame_00001.png");
28
+ const sdF2 = demoMedia("generate/scene-desert/frame_00002.png");
29
+ const sdF3 = demoMedia("generate/scene-desert/frame_00003.png");
30
+ const sdF4 = demoMedia("generate/scene-desert/frame_00004.png");
31
+ const sdF5 = demoMedia("generate/scene-desert/frame_00005.png");
32
+ const sdClip = demoMedia("generate/scene-desert/scene.mp4");
32
33
  // scene: vintage frames (job 69a7bbcf…) — render showcased, prompt not
33
- import svF0 from "./assets/generate/scene-vintage/frame_00000.png";
34
- import svF1 from "./assets/generate/scene-vintage/frame_00001.png";
35
- import svF2 from "./assets/generate/scene-vintage/frame_00002.png";
36
- import svClip from "./assets/generate/scene-vintage/scene.mp4";
34
+ const svF0 = demoMedia("generate/scene-vintage/frame_00000.png");
35
+ const svF1 = demoMedia("generate/scene-vintage/frame_00001.png");
36
+ const svF2 = demoMedia("generate/scene-vintage/frame_00002.png");
37
+ const svClip = demoMedia("generate/scene-vintage/scene.mp4");
37
38
  // movie: sunny meadow (job 5ca10b84…, 2 goals) + swirling galaxy (1eed0b95…, 4 goals)
38
- import mmSeg0 from "./assets/generate/movie-medow/seg_00.mp4";
39
- import mmSeg1 from "./assets/generate/movie-medow/seg_01.mp4";
40
- import mmMovie from "./assets/generate/movie-medow/movie.mp4";
41
- import mgSeg0 from "./assets/generate/movie-galaxy/seg_00.mp4";
42
- import mgSeg1 from "./assets/generate/movie-galaxy/seg_01.mp4";
43
- import mgSeg2 from "./assets/generate/movie-galaxy/seg_02.mp4";
44
- import mgSeg3 from "./assets/generate/movie-galaxy/seg_03.mp4";
45
- import mgMovie from "./assets/generate/movie-galaxy/movie.mp4";
39
+ const mmSeg0 = demoMedia("generate/movie-medow/seg_00.mp4");
40
+ const mmSeg1 = demoMedia("generate/movie-medow/seg_01.mp4");
41
+ const mmMovie = demoMedia("generate/movie-medow/movie.mp4");
42
+ const mgSeg0 = demoMedia("generate/movie-galaxy/seg_00.mp4");
43
+ const mgSeg1 = demoMedia("generate/movie-galaxy/seg_01.mp4");
44
+ const mgSeg2 = demoMedia("generate/movie-galaxy/seg_02.mp4");
45
+ const mgSeg3 = demoMedia("generate/movie-galaxy/seg_03.mp4");
46
+ const mgMovie = demoMedia("generate/movie-galaxy/movie.mp4");
46
47
  // clips (studio i2v manifests carry the prompts)
47
- import clCat from "./assets/generate/clips/cat-lounge.mp4";
48
- import clDrone from "./assets/generate/clips/drone-city.mp4";
49
- import clSails from "./assets/generate/clips/paper-sails.mp4";
48
+ const clCat = demoMedia("generate/clips/cat-lounge.mp4");
49
+ const clDrone = demoMedia("generate/clips/drone-city.mp4");
50
+ const clSails = demoMedia("generate/clips/paper-sails.mp4");
50
51
  // cinema: dank cavern (movie.json segment prompts; seg_01 was a branch-only
51
52
  // render, so the set is segment_00 + the assembled movie LAST)
52
- import ccSeg0 from "./assets/generate/cinema-cavern/seg_00.mp4";
53
- import ccMovie from "./assets/generate/cinema-cavern/movie.mp4";
53
+ const ccSeg0 = demoMedia("generate/cinema-cavern/seg_00.mp4");
54
+ const ccMovie = demoMedia("generate/cinema-cavern/movie.mp4");
54
55
 
55
56
  function img(id: string, uri: string): MediaRef {
56
57
  return { asset_id: id, kind: "image", uri, mime: "image/png",
Binary file
Binary file
@@ -1,67 +0,0 @@
1
- {
2
- "kind": "studio_movie",
3
- "drift": "still-mode splices condition each segment on ONE frame of its parent \u2014 motion is NOT carried across the splice. A joint's joint_mode='vace_extend' carries motion via VACE-extend (conditioning on the parent's trailing context_frames through the diffusers video+mask extend idiom) instead of a single still. A joint_mode='cut' is a HARD scene cut: no frame carry, the parent plays in full. With movie-level reference_images set (an identity movie) every segment renders id_lock so the subject carries across scene changes even though no pixels do \u2014 on the VACE path the references win the conditioning (an i2v branch still is accepted but unused; it governs only the parent trim).",
4
- "fps": 24,
5
- "width": 480,
6
- "height": 480,
7
- "vram_budget_gb": null,
8
- "id_lock": false,
9
- "reference_images": [],
10
- "segments": [
11
- {
12
- "index": 0,
13
- "segment_id": "seg_00",
14
- "parent_segment_id": null,
15
- "prompt": "In a subterranean cavern bathed in the harsh midday sun, a towering structure dwarfs its surroundings, casting short, sharp black shadows that dance across the cavern's walls. The cavern's natural beauty is accentuated by saturated primaries, creating an intimate and awe-inspiring mood. The shot begins with a sweeping establishing view, capturing the structure's majesty. As the camera slowly pushes in, the structure's intricate details come into focus, each element standing out against the cavern's stark, yet beautiful backdrop. The light, a hard midday sun, highlights the structure's grandeur, making it the focal point of the scene. The palette is saturated primaries, intensifying the cavern's natural colors and the structure's imposing presence. The shot starts from rest, maintaining the intimate mood as the viewer is drawn closer to the structure, experiencing its grandeur through the lens.",
16
- "capability": "t2v",
17
- "model_id": "wan2.1-t2v-1.3b",
18
- "model_source": "movie",
19
- "seed": 0,
20
- "branch_frame": null,
21
- "resolved_branch": null,
22
- "joint_mode": "still",
23
- "context_frames": 0,
24
- "context_drop": 0,
25
- "vram_budget_gb": 21.6,
26
- "budget_source": "autofit:ae",
27
- "clip_path": "/mnt/llm_storage/video_intel/studio_movies/a0a47af626bd471da3c06832f7e4f99d/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/clip.mp4",
28
- "clip_uri": "/mnt/llm_storage/video_intel/studio_movies/a0a47af626bd471da3c06832f7e4f99d/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/clip.mp4",
29
- "frames": 81,
30
- "width": 480,
31
- "height": 480,
32
- "duration_s": 3.375,
33
- "content_hash": "5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b",
34
- "resumed": false,
35
- "status": "done"
36
- },
37
- {
38
- "index": 1,
39
- "segment_id": "seg_01",
40
- "parent_segment_id": "seg_00",
41
- "prompt": "In a flooded forest, a pair of exhausted hands, their skin slick with water and sweat, complete a delicate task under the unflattering, green fluorescent strip light. The cool teal and slate palette, cool and muted, highlights the hands' strained features. As the camera pulls back, revealing the hands' movements, a towering structure, bathed in harsh midday sun, casts sharp shadows across the flooded forest. The structure's intricate details stand out against the unflattering light, creating an intimate and awe-inspiring mood. The shot begins from rest, maintaining the exhausted and awe-inspiring mood as the viewer is drawn closer to the hands and the structure, experiencing their struggle and the cavern's natural beauty through the lens.",
42
- "capability": "i2v",
43
- "model_id": null,
44
- "model_source": "capability_fallback",
45
- "pinned_model_id": "wan2.1-t2v-1.3b",
46
- "model_note": "movie-level pin 'wan2.1-t2v-1.3b' serves t2v, not 'i2v'; this segment resolved a capable model through the studio router instead (the pin still binds the segments it serves)",
47
- "joint_mode": "still",
48
- "context_frames": 0,
49
- "resolved_branch": 80,
50
- "vram_budget_gb": 21.6,
51
- "budget_source": "autofit:ae",
52
- "status": "failed",
53
- "error": {
54
- "code": "cancelled",
55
- "message": "cancelled (render child killed after grace)"
56
- }
57
- }
58
- ],
59
- "joints": [],
60
- "assembly": {
61
- "movie": "movie.mp4",
62
- "total_frames": 81
63
- },
64
- "partial": true,
65
- "segments_completed": 1,
66
- "segments_total": 2
67
- }