@hugpy/video-intelligence-ui 0.1.0

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 (177) hide show
  1. package/LICENSE +41 -0
  2. package/assets/sampels/images/bycycle/bd8603bad35d4f179f4d1134d8faf2ee.mp4 +0 -0
  3. package/assets/sampels/images/bycycle/frame_00000.png +0 -0
  4. package/assets/sampels/images/bycycle/frame_00001.png +0 -0
  5. package/assets/sampels/images/bycycle/frame_00002.png +0 -0
  6. package/assets/sampels/images/bycycle/frame_00003.png +0 -0
  7. package/assets/sampels/images/bycycle/frame_00004.png +0 -0
  8. package/assets/sampels/images/bycycle/frame_00005.png +0 -0
  9. package/assets/sampels/images/forrest/ff23090611b844a6b85c224f388bd542.mp4 +0 -0
  10. package/assets/sampels/images/forrest/frame_00000.png +0 -0
  11. package/assets/sampels/images/forrest/frame_00001.png +0 -0
  12. package/assets/sampels/images/forrest/frame_00002.png +0 -0
  13. package/assets/sampels/images/forrest/frame_00003.png +0 -0
  14. package/assets/sampels/images/forrest/frame_00004.png +0 -0
  15. package/assets/sampels/images/forrest/frame_00005.png +0 -0
  16. package/assets/sampels/images/shenzo/e1a4fcd623084ab4a986d11c8cd00db8.mp4 +0 -0
  17. package/assets/sampels/images/shenzo/frame_00000.png +0 -0
  18. package/assets/sampels/images/shenzo/frame_00001.png +0 -0
  19. package/assets/sampels/images/shenzo/frame_00002.png +0 -0
  20. package/assets/sampels/images/shenzo/frame_00003.png +0 -0
  21. package/assets/sampels/images/shenzo/frame_00004.png +0 -0
  22. package/assets/sampels/images/shenzo/frame_00005.png +0 -0
  23. package/assets/sampels/scenes/man.mp4 +0 -0
  24. package/assets/sampels/scenes/woman.mp4 +0 -0
  25. package/assets/sampels/studio_movies/caverns/_assembly/contrib_00.mp4 +0 -0
  26. package/assets/sampels/studio_movies/caverns/movie.json +67 -0
  27. package/assets/sampels/studio_movies/caverns/movie.mp4 +0 -0
  28. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/clip.mp4 +0 -0
  29. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/manifest.json +87 -0
  30. package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/provenance.json +7 -0
  31. package/assets/sampels/studio_movies/caverns/segment_01/branch.png +0 -0
  32. package/assets/sampels/studio_movies/keeper-matrix/_assembly/concat_list.txt +2 -0
  33. package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_00.mp4 +0 -0
  34. package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_01.mp4 +0 -0
  35. package/assets/sampels/studio_movies/keeper-matrix/movie.json +81 -0
  36. package/assets/sampels/studio_movies/keeper-matrix/movie.mp4 +0 -0
  37. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/clip.mp4 +0 -0
  38. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/manifest.json +87 -0
  39. package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/provenance.json +7 -0
  40. package/assets/sampels/studio_movies/keeper-matrix/segment_01/branch.png +0 -0
  41. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/clip.mp4 +0 -0
  42. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/manifest.json +87 -0
  43. package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/provenance.json +7 -0
  44. package/assets/sampels/videos/a_red_paper_boat_drifting_on_a_calm_pond__gentle_ripples__mo_a62d6c28.mp4 +0 -0
  45. package/assets/sampels/videos/aurora.mp4 +0 -0
  46. package/assets/sampels/videos/dark_night.mp4 +0 -0
  47. package/assets/sampels/videos/fire_and_soul.mp4 +0 -0
  48. package/assets/sampels/videos/harsh_seas.mp4 +0 -0
  49. package/entry.tsx +74 -0
  50. package/index.html +53 -0
  51. package/package.json +42 -0
  52. package/src/assets/hugpy-mark.png +0 -0
  53. package/src/config.ts +676 -0
  54. package/src/demo/DemoBanner.tsx +54 -0
  55. package/src/demo/assets/demo-audio.mp3 +0 -0
  56. package/src/demo/assets/demo-clip.mp4 +0 -0
  57. package/src/demo/assets/demo-crop.mp4 +0 -0
  58. package/src/demo/assets/demo-poster.jpg +0 -0
  59. package/src/demo/assets/frame-1.jpg +0 -0
  60. package/src/demo/assets/frame-2.jpg +0 -0
  61. package/src/demo/assets/frame-3.jpg +0 -0
  62. package/src/demo/assets/frame-4.jpg +0 -0
  63. package/src/demo/assets/frame-5.jpg +0 -0
  64. package/src/demo/assets/frame-6.jpg +0 -0
  65. package/src/demo/assets/gen-1.jpg +0 -0
  66. package/src/demo/assets/gen-2.jpg +0 -0
  67. package/src/demo/assets.d.ts +14 -0
  68. package/src/demo/demo.css +64 -0
  69. package/src/demo/demoFetch.ts +305 -0
  70. package/src/demo/fixtures.ts +814 -0
  71. package/src/demo/index.ts +43 -0
  72. package/src/demo/mode.ts +81 -0
  73. package/src/demo/promptAssist.ts +181 -0
  74. package/src/demo/seed.ts +97 -0
  75. package/src/nav/BrandMark.css +66 -0
  76. package/src/nav/BrandMark.tsx +40 -0
  77. package/src/nav/Navbar.css +13 -0
  78. package/src/nav/Navbar.tsx +68 -0
  79. package/src/regions/SpatialRegionEditor.tsx +375 -0
  80. package/src/regions/TemporalRegionEditor.tsx +485 -0
  81. package/src/regions/types.ts +85 -0
  82. package/src/session.ts +85 -0
  83. package/src/share.ts +77 -0
  84. package/src/stations/ActivePanel.tsx +55 -0
  85. package/src/stations/ActiveProcessesStation.tsx +272 -0
  86. package/src/stations/ActiveProcessesStation.tsx.bak-sessions-20260812 +228 -0
  87. package/src/stations/AddFileBar.tsx +59 -0
  88. package/src/stations/AudioCropStation.tsx +157 -0
  89. package/src/stations/ComfyPanel.tsx +210 -0
  90. package/src/stations/ComfyStation.tsx +196 -0
  91. package/src/stations/CondensedKnobStrip.tsx +245 -0
  92. package/src/stations/ConsolePanel.tsx +82 -0
  93. package/src/stations/FrameExtractStation.tsx +148 -0
  94. package/src/stations/GenIdentityBar.tsx +412 -0
  95. package/src/stations/GenerateStation.tsx +5603 -0
  96. package/src/stations/GenerateStation.tsx.bak-tester-removal +5170 -0
  97. package/src/stations/GoalComposer.tsx +139 -0
  98. package/src/stations/ImageCropStation.tsx +153 -0
  99. package/src/stations/KnobInfo.tsx +64 -0
  100. package/src/stations/PlaceholderStation.tsx +26 -0
  101. package/src/stations/SectionTabs.tsx +276 -0
  102. package/src/stations/SharePanel.tsx +298 -0
  103. package/src/stations/SidebarRegistry.tsx +105 -0
  104. package/src/stations/StationShell.tsx +96 -0
  105. package/src/stations/StudioClipsStation.tsx +28 -0
  106. package/src/stations/WorkbenchDrawer.tsx +66 -0
  107. package/src/stations/WorkbenchSidebar.tsx +1224 -0
  108. package/src/stations/WorkbenchStation.tsx +91 -0
  109. package/src/stations/comfyEmbed.ts +79 -0
  110. package/src/stations/drawerShell.tsx +419 -0
  111. package/src/stations/ops/AudioCropCore.tsx +218 -0
  112. package/src/stations/ops/CharacterGroupsPanel.tsx +716 -0
  113. package/src/stations/ops/FrameExtractCore.tsx +695 -0
  114. package/src/stations/ops/ImageCropCore.tsx +164 -0
  115. package/src/stations/ops/VideoCropCore.tsx +193 -0
  116. package/src/stations/ops/useVideoCropJobs.ts +224 -0
  117. package/src/stations/pollCaps.ts +87 -0
  118. package/src/stations/registry.ts +191 -0
  119. package/src/stations/studio/AngleRingEditor.tsx +159 -0
  120. package/src/stations/studio/IdentitiesStation.tsx +2455 -0
  121. package/src/stations/studio/IdentityProfileControls.tsx +262 -0
  122. package/src/stations/studio/StudioActiveProcesses.tsx +224 -0
  123. package/src/stations/studio/StudioAssistLogPanel.tsx +220 -0
  124. package/src/stations/studio/StudioGenerateMode.tsx +29 -0
  125. package/src/stations/studio/StudioGenerateTab.tsx +2688 -0
  126. package/src/stations/studio/StudioItemCard.tsx +334 -0
  127. package/src/stations/studio/StudioLibraryTab.tsx +506 -0
  128. package/src/stations/studio/StudioMovieComposer.tsx +2875 -0
  129. package/src/stations/studio/StudioMovieComposer.tsx.bak-sessions-20260812 +2729 -0
  130. package/src/stations/studio/StudioMovieSessions.tsx +553 -0
  131. package/src/stations/studio/StudioMovieSessions.tsx.bak-sessions-20260812 +417 -0
  132. package/src/stations/studio/StudioPlane.tsx +302 -0
  133. package/src/stations/studio/StudioSplicedRow.tsx +476 -0
  134. package/src/stations/studio/StudioViewer.tsx +339 -0
  135. package/src/stations/studio/TurntableViewer.tsx +220 -0
  136. package/src/stations/studio/movieTimeline.ts +198 -0
  137. package/src/stations/studio/promptCard.tsx +498 -0
  138. package/src/stations/studio/studioItem.ts +87 -0
  139. package/src/stations/studio/studioShared.tsx +821 -0
  140. package/src/stations/studio/useIdentityProfiles.ts +575 -0
  141. package/src/stations/studio/useReconstruction.ts +283 -0
  142. package/src/stations/types.ts +43 -0
  143. package/src/stations/useAudioCropJobs.ts +202 -0
  144. package/src/stations/useAudioExtractJob.ts +144 -0
  145. package/src/stations/useCropJobs.ts +206 -0
  146. package/src/stations/useFrameJobs.ts +140 -0
  147. package/src/stations/useGenerateJob.ts +151 -0
  148. package/src/stations/useGenerateMovieJob.ts +177 -0
  149. package/src/stations/useGenerateRowJobs.ts +184 -0
  150. package/src/stations/useGenerateSceneJob.ts +156 -0
  151. package/src/stations/useGenerateSceneRowJobs.ts +282 -0
  152. package/src/style/app.css +4974 -0
  153. package/src/transport/client.ts +262 -0
  154. package/src/transport/telemetry.ts +97 -0
  155. package/src/video/DropReceptacle.tsx +122 -0
  156. package/src/video/PromptAssistButtons.tsx +118 -0
  157. package/src/video/composerBridge.ts +66 -0
  158. package/src/video/contract.ts +599 -0
  159. package/src/video/jobTracker.ts +702 -0
  160. package/src/video/mediaLibrary.ts +328 -0
  161. package/src/video/renderCompat.ts +512 -0
  162. package/src/video/sessionForm.ts +189 -0
  163. package/src/video/spreadAssist.ts +392 -0
  164. package/src/video/studioBridge.ts +82 -0
  165. package/src/video/useMediaJobs.ts +279 -0
  166. package/src/video/useModels.ts +413 -0
  167. package/src/video/useMoviePresets.ts +97 -0
  168. package/src/video/useMovieSessions.ts +292 -0
  169. package/src/video/usePresets.ts +167 -0
  170. package/src/video/useProjects.ts +87 -0
  171. package/src/video/usePromptAssist.ts +394 -0
  172. package/src/video/useRenderPresets.ts +221 -0
  173. package/src/video/useStudioAssistLog.ts +266 -0
  174. package/src/video/useStudioPresets.ts +128 -0
  175. package/src/video/useWorkerCalls.ts +165 -0
  176. package/tsconfig.json +17 -0
  177. package/vite.config.ts +33 -0
@@ -0,0 +1,218 @@
1
+ // AudioCropCore — the reusable body of the Audio Crop station (Studio slice 2a).
2
+ // The station's "Components view" MINUS the upload bar: the video→audio extract
3
+ // step (useAudioExtractJob) + the TemporalRegionEditor + the useAudioCropJobs
4
+ // enqueue hook + the result grid, parameterised by a `source` ref (audio OR video).
5
+ //
6
+ // The video→audio extract PATH IS PRESERVED verbatim: when the `source` is a video
7
+ // the Core pulls its audio track out first and the editor works over the extracted
8
+ // audio; an audio source is edited directly. Behaviour is IDENTICAL to the
9
+ // pre-slice-2a AudioCropStation body: same `vi-station-components` shell, same
10
+ // extract progress affordance, same TemporalRegionEditor props, same run footer,
11
+ // same `vi-result-grid` + AudioJobCard. `onProduced` is an OPTIONAL slice-2b sink;
12
+ // when OMITTED (the station case) finished clips still land in the library via the
13
+ // tracker exactly as before.
14
+ //
15
+ // Source lifecycle: the station owned `extract.reset()` + `resetCrops()` + clearing
16
+ // the queued windows inside onPick/clearSource, and kicked `extract.run()` for a
17
+ // video. The Core now owns those, so it runs them whenever the `source` REFERENCE
18
+ // changes (a new pick or a clear). The initial mount is skipped (prevSource seed) so
19
+ // a remount after a sub-tab switch does NOT re-run the extract or wipe the durable
20
+ // tracker's finished clips.
21
+ import { useEffect, useRef, useState } from "react";
22
+ import { TemporalRegionEditor } from "../../regions/TemporalRegionEditor";
23
+ import type { TemporalRegion } from "../../regions/types";
24
+ import { useAudioExtractJob } from "../useAudioExtractJob";
25
+ import { useAudioCropJobs, type TrackedAudioJob } from "../useAudioCropJobs";
26
+ import { STILL_RUNNING_MESSAGE } from "../pollCaps";
27
+ import { mediaBytesUrl } from "../../config";
28
+ import type { MediaRef } from "../../video/contract";
29
+
30
+ export interface AudioCropCoreProps {
31
+ /** Source audio or video, or null before one is loaded. */
32
+ source: MediaRef | null;
33
+ /** Multi-clip queue (station) vs single clip (inline-on-item, slice 2b). */
34
+ multiRegion?: boolean;
35
+ /** Additive (slice 2b): notified with each finished clip's ref + its window.
36
+ Omitted by the station — the clip still lands in the library via the tracker. */
37
+ onProduced?: (
38
+ ref: MediaRef,
39
+ meta: { spatial?: null; temporal?: TemporalRegion | null },
40
+ ) => void;
41
+ }
42
+
43
+ export function AudioCropCore({
44
+ source,
45
+ multiRegion = true,
46
+ onProduced,
47
+ }: AudioCropCoreProps) {
48
+ const [queued, setQueued] = useState<TemporalRegion[]>([]);
49
+
50
+ // Video sources are routed through the audio_extract chain first.
51
+ const extract = useAudioExtractJob();
52
+
53
+ // The audio ref the editor works over: an audio source directly, or the audio
54
+ // track extracted from a video source once that job finishes.
55
+ const audioSource: MediaRef | null =
56
+ source?.kind === "audio"
57
+ ? source
58
+ : source?.kind === "video"
59
+ ? extract.output
60
+ : null;
61
+
62
+ const { jobs, runCrops, resume, reset: resetCrops } = useAudioCropJobs(audioSource);
63
+
64
+ const duration = audioSource?.duration_s ?? 0;
65
+
66
+ // Reset the queue + extract + this station's tracker rows when a NEW source is
67
+ // picked (or cleared), and kick the audio_extract chain for a video — keyed on the
68
+ // source reference so it fires exactly on a pick/clear. The mount is skipped
69
+ // (prevSource seed) so a remount after a tab switch neither re-runs the extract
70
+ // (no duplicate job) nor wipes the durable tracker's finished clips.
71
+ const prevSource = useRef<MediaRef | null>(source);
72
+ useEffect(() => {
73
+ if (prevSource.current === source) return;
74
+ prevSource.current = source;
75
+ setQueued([]);
76
+ extract.reset();
77
+ resetCrops();
78
+ // A video needs its audio track pulled out before the editor can open.
79
+ if (source?.kind === "video") extract.run(source);
80
+ // eslint-disable-next-line react-hooks/exhaustive-deps
81
+ }, [source]);
82
+
83
+ // Additive slice-2b sink: fire once per finished clip. Inert when onProduced is
84
+ // omitted (the station case) — the library push is owned by the tracker regardless.
85
+ const notified = useRef<Set<string>>(new Set());
86
+ useEffect(() => {
87
+ if (!onProduced) return;
88
+ for (const j of jobs) {
89
+ if (j.status === "done" && j.output && !notified.current.has(j.key)) {
90
+ notified.current.add(j.key);
91
+ onProduced(j.output, { spatial: null, temporal: j.region });
92
+ }
93
+ }
94
+ }, [jobs, onProduced]);
95
+
96
+ const isVideo = source?.kind === "video";
97
+ const extracting = isVideo && !extract.output && extract.status !== "failed";
98
+
99
+ return (
100
+ <div className="vi-station-components">
101
+ {/* Video → audio extract progress + still-running affordance. */}
102
+ {isVideo && extracting && (
103
+ <p className="vi-crop-hint">
104
+ {extract.capped
105
+ ? STILL_RUNNING_MESSAGE
106
+ : `Extracting the audio track… ${extract.status ?? "queued"}`}
107
+ {extract.capped && (
108
+ <button
109
+ type="button"
110
+ className="vi-btn vi-btn-sm"
111
+ onClick={extract.resume}
112
+ style={{ marginLeft: "0.6rem" }}
113
+ >
114
+ Check again
115
+ </button>
116
+ )}
117
+ </p>
118
+ )}
119
+ {isVideo && extract.error && (
120
+ <p className="vi-error" role="alert">{extract.error}</p>
121
+ )}
122
+
123
+ {/* Audio view + crop/manipulation options (waveform + numeric
124
+ windows) — ALWAYS rendered. With no audio loaded the editor shows
125
+ its full chrome over a blank (flat) waveform; loading audio only
126
+ fills the waveform. */}
127
+ <TemporalRegionEditor
128
+ source={audioSource}
129
+ duration={duration}
130
+ values={queued}
131
+ onChange={(next) => setQueued(Array.isArray(next) ? next : [next])}
132
+ multiRegion={multiRegion}
133
+ numericInputs
134
+ />
135
+
136
+ {/* Run */}
137
+ <div className="vi-station-footer">
138
+ <div className="vi-station-footer-main">
139
+ <span className="vi-crop-hint">
140
+ Each queued window enqueues one temporal crop; clips land in the
141
+ media library.
142
+ </span>
143
+ </div>
144
+ <div className="vi-station-footer-run">
145
+ <button
146
+ type="button"
147
+ className="vi-btn vi-btn-accent"
148
+ disabled={!audioSource || queued.length === 0}
149
+ onClick={() => runCrops(queued)}
150
+ >
151
+ Run crop{queued.length === 1 ? "" : "s"}
152
+ {queued.length > 0 ? ` (${queued.length})` : ""}
153
+ </button>
154
+ </div>
155
+ </div>
156
+
157
+ {jobs.length > 0 && (
158
+ <div className="vi-result-grid">
159
+ {jobs.map((j) => (
160
+ <AudioJobCard key={j.key} job={j} onResume={() => resume(j.key)} />
161
+ ))}
162
+ </div>
163
+ )}
164
+ </div>
165
+ );
166
+ }
167
+
168
+ const STATUS_TEXT: Record<string, string> = {
169
+ queued: "queued",
170
+ claimed: "claimed",
171
+ running: "running",
172
+ done: "done",
173
+ failed: "failed",
174
+ };
175
+
176
+ function AudioJobCard({
177
+ job,
178
+ onResume,
179
+ }: {
180
+ job: TrackedAudioJob;
181
+ onResume: () => void;
182
+ }) {
183
+ const statusKey = job.status ?? "queued";
184
+ const done = job.status === "done";
185
+ const failed = job.status === "failed";
186
+ return (
187
+ <figure className="vi-result-card">
188
+ <div className="vi-result-head">
189
+ <span className="vi-region-idx">#{job.index}</span>
190
+ <span className={`vi-status vi-status-${statusKey}`}>{STATUS_TEXT[statusKey]}</span>
191
+ <code className="vi-region-dims">
192
+ {job.region.start_s.toFixed(2)}–{job.region.end_s.toFixed(2)}s
193
+ </code>
194
+ </div>
195
+ <div className="vi-result-body">
196
+ {done && job.output ? (
197
+ <div className="vi-audio-clip">
198
+ <audio controls className="vi-audio-player" src={mediaBytesUrl(job.output.uri)} />
199
+ <span className="vi-gen-note">in library</span>
200
+ </div>
201
+ ) : failed ? (
202
+ <p className="vi-error">{job.error ?? "Audio-crop job failed."}</p>
203
+ ) : job.capped ? (
204
+ <div className="vi-result-pending">
205
+ <p className="vi-crop-hint">{STILL_RUNNING_MESSAGE}</p>
206
+ <button type="button" className="vi-btn vi-btn-sm" onClick={onResume}>
207
+ Check again
208
+ </button>
209
+ </div>
210
+ ) : (
211
+ <div className="vi-result-pending" aria-hidden>
212
+ {STATUS_TEXT[statusKey]}…
213
+ </div>
214
+ )}
215
+ </div>
216
+ </figure>
217
+ );
218
+ }