@effectnode/media 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 (133) hide show
  1. package/bin/effectnode-media +2 -0
  2. package/dist/backend/index.d.ts +8 -0
  3. package/dist/backend/index.js +13 -0
  4. package/dist/backend/movie-backend/agent/agent-backend.d.ts +16 -0
  5. package/dist/backend/movie-backend/agent/agent-backend.js +703 -0
  6. package/dist/backend/movie-backend/agent/storyprompt.d.ts +1 -0
  7. package/dist/backend/movie-backend/agent/storyprompt.js +90 -0
  8. package/dist/backend/movie-backend/agent/tools/edit-image.d.ts +3 -0
  9. package/dist/backend/movie-backend/agent/tools/edit-image.js +192 -0
  10. package/dist/backend/movie-backend/agent/tools/get-time.d.ts +3 -0
  11. package/dist/backend/movie-backend/agent/tools/get-time.js +7 -0
  12. package/dist/backend/movie-backend/agent/tools/grep-files.d.ts +3 -0
  13. package/dist/backend/movie-backend/agent/tools/grep-files.js +59 -0
  14. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.d.ts +3 -0
  15. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.js +160 -0
  16. package/dist/backend/movie-backend/agent/tools/index.d.ts +7 -0
  17. package/dist/backend/movie-backend/agent/tools/index.js +59 -0
  18. package/dist/backend/movie-backend/agent/tools/list-files.d.ts +3 -0
  19. package/dist/backend/movie-backend/agent/tools/list-files.js +17 -0
  20. package/dist/backend/movie-backend/agent/tools/read-file.d.ts +3 -0
  21. package/dist/backend/movie-backend/agent/tools/read-file.js +28 -0
  22. package/dist/backend/movie-backend/agent/tools/remove-file.d.ts +3 -0
  23. package/dist/backend/movie-backend/agent/tools/remove-file.js +22 -0
  24. package/dist/backend/movie-backend/agent/tools/rename-file.d.ts +3 -0
  25. package/dist/backend/movie-backend/agent/tools/rename-file.js +37 -0
  26. package/dist/backend/movie-backend/agent/tools/show-image.d.ts +3 -0
  27. package/dist/backend/movie-backend/agent/tools/show-image.js +28 -0
  28. package/dist/backend/movie-backend/agent/tools/stitch-videos.d.ts +3 -0
  29. package/dist/backend/movie-backend/agent/tools/stitch-videos.js +96 -0
  30. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.d.ts +3 -0
  31. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.js +125 -0
  32. package/dist/backend/movie-backend/agent/tools/types.d.ts +15 -0
  33. package/dist/backend/movie-backend/agent/tools/types.js +1 -0
  34. package/dist/backend/movie-backend/agent/tools/update-file.d.ts +3 -0
  35. package/dist/backend/movie-backend/agent/tools/update-file.js +27 -0
  36. package/dist/backend/movie-backend/agent/tools/write-file.d.ts +3 -0
  37. package/dist/backend/movie-backend/agent/tools/write-file.js +26 -0
  38. package/dist/backend/movie-backend/agent/workspace.d.ts +25 -0
  39. package/dist/backend/movie-backend/agent/workspace.js +124 -0
  40. package/dist/backend/movie-backend/core.d.ts +4 -0
  41. package/dist/backend/movie-backend/core.js +1167 -0
  42. package/dist/backend/movie-backend/generation-queue.d.ts +24 -0
  43. package/dist/backend/movie-backend/generation-queue.js +766 -0
  44. package/dist/backend/movie-backend/process.d.ts +21 -0
  45. package/dist/backend/movie-backend/process.js +98 -0
  46. package/dist/backend/movie-backend/render-media.d.ts +48 -0
  47. package/dist/backend/movie-backend/render-media.js +3311 -0
  48. package/dist/backend/routes/health.d.ts +1 -0
  49. package/dist/backend/routes/health.js +9 -0
  50. package/dist/backend/routes/index.d.ts +1 -0
  51. package/dist/backend/routes/index.js +11 -0
  52. package/dist/backend/routes/media.d.ts +1 -0
  53. package/dist/backend/routes/media.js +13 -0
  54. package/dist/backend/routes/projects.d.ts +8 -0
  55. package/dist/backend/routes/projects.js +68 -0
  56. package/dist/backend/routes/voice.d.ts +1 -0
  57. package/dist/backend/routes/voice.js +39 -0
  58. package/dist/backend/routes.d.ts +1 -0
  59. package/dist/backend/routes.js +20 -0
  60. package/dist/backend/server.d.ts +1 -0
  61. package/dist/backend/server.js +23 -0
  62. package/dist/backend/workspace.d.ts +5 -0
  63. package/dist/backend/workspace.js +27 -0
  64. package/dist/backend/ws/index.d.ts +3 -0
  65. package/dist/backend/ws/index.js +27 -0
  66. package/dist/backend/ws/messages.d.ts +8 -0
  67. package/dist/backend/ws/messages.js +21 -0
  68. package/dist/backend/ws.d.ts +3 -0
  69. package/dist/backend/ws.js +29 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +80 -0
  72. package/frontend/index.html +22 -0
  73. package/frontend/public/ffmpeg/ffmpeg-core.js +16 -0
  74. package/frontend/public/ffmpeg/ffmpeg-core.wasm +0 -0
  75. package/frontend/src/index.css +46 -0
  76. package/frontend/src/main.tsx +13 -0
  77. package/frontend/src/movie-app/AppRouter.tsx +14 -0
  78. package/frontend/src/movie-app/MediaStudio.tsx +9 -0
  79. package/frontend/src/movie-app/SetupPage.tsx +293 -0
  80. package/frontend/src/movie-app/components/AgentWorkspace.tsx +574 -0
  81. package/frontend/src/movie-app/components/ChatUI/ChatUI.tsx +655 -0
  82. package/frontend/src/movie-app/components/ChatUI/MarkdownMessage.tsx +267 -0
  83. package/frontend/src/movie-app/components/EditorTabs/AgentTab.tsx +61 -0
  84. package/frontend/src/movie-app/components/EditorTabs/BatchImageToVideoTab.tsx +927 -0
  85. package/frontend/src/movie-app/components/EditorTabs/BatchVideoTab.tsx +777 -0
  86. package/frontend/src/movie-app/components/EditorTabs/BatchVoiceVideoTab.tsx +913 -0
  87. package/frontend/src/movie-app/components/EditorTabs/CharacterSheet.tsx +234 -0
  88. package/frontend/src/movie-app/components/EditorTabs/CharactersTab.tsx +664 -0
  89. package/frontend/src/movie-app/components/EditorTabs/CropTool.tsx +251 -0
  90. package/frontend/src/movie-app/components/EditorTabs/ExtendVideoTab.tsx +305 -0
  91. package/frontend/src/movie-app/components/EditorTabs/ExtractImageTab.tsx +249 -0
  92. package/frontend/src/movie-app/components/EditorTabs/FastImageEditTab.tsx +555 -0
  93. package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +722 -0
  94. package/frontend/src/movie-app/components/EditorTabs/LlmServerTab.tsx +40 -0
  95. package/frontend/src/movie-app/components/EditorTabs/MlxVlmServerPanel.tsx +490 -0
  96. package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +938 -0
  97. package/frontend/src/movie-app/components/EditorTabs/ReferencesToVideoTab.tsx +881 -0
  98. package/frontend/src/movie-app/components/EditorTabs/SceneVisualTab.tsx +267 -0
  99. package/frontend/src/movie-app/components/EditorTabs/StoryWriterTab.tsx +51 -0
  100. package/frontend/src/movie-app/components/EditorTabs/TaskQueuePanel.tsx +246 -0
  101. package/frontend/src/movie-app/components/EditorTabs/TerminalLogPanel.tsx +49 -0
  102. package/frontend/src/movie-app/components/EditorTabs/TextToImageTab.tsx +574 -0
  103. package/frontend/src/movie-app/components/EditorTabs/VoiceChatPanel.tsx +320 -0
  104. package/frontend/src/movie-app/components/ProjectEditorPage.tsx +636 -0
  105. package/frontend/src/movie-app/components/ProjectManager.tsx +381 -0
  106. package/frontend/src/movie-app/index.css +46 -0
  107. package/frontend/src/movie-app/index.html +16 -0
  108. package/frontend/src/movie-app/lambobo.png +0 -0
  109. package/frontend/src/movie-app/lib/batchImageToVideoStorage.ts +69 -0
  110. package/frontend/src/movie-app/lib/batchVideoStorage.ts +68 -0
  111. package/frontend/src/movie-app/lib/batchVoiceStorage.ts +75 -0
  112. package/frontend/src/movie-app/lib/ffmpeg.ts +42 -0
  113. package/frontend/src/movie-app/lib/referencesToVideoStorage.ts +64 -0
  114. package/frontend/src/movie-app/lib/textToImageStorage.ts +61 -0
  115. package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.js +16 -0
  116. package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.wasm +0 -0
  117. package/frontend/src/movie-app/stores/batchImageToVideoStore.ts +736 -0
  118. package/frontend/src/movie-app/stores/batchVideoStore.ts +670 -0
  119. package/frontend/src/movie-app/stores/batchVoiceStore.ts +990 -0
  120. package/frontend/src/movie-app/stores/characterStore.ts +109 -0
  121. package/frontend/src/movie-app/stores/chatStore.ts +459 -0
  122. package/frontend/src/movie-app/stores/generationStore.ts +2140 -0
  123. package/frontend/src/movie-app/stores/logStore.ts +25 -0
  124. package/frontend/src/movie-app/stores/movieStudioStore.ts +709 -0
  125. package/frontend/src/movie-app/stores/projectStore.ts +124 -0
  126. package/frontend/src/movie-app/stores/queueStore.ts +214 -0
  127. package/frontend/src/movie-app/stores/referencesToVideoStore.ts +467 -0
  128. package/frontend/src/movie-app/stores/sceneVisualStore.ts +251 -0
  129. package/frontend/src/movie-app/stores/voiceChatStore.ts +170 -0
  130. package/frontend/src/movie-app/stores/workspaceStore.ts +158 -0
  131. package/frontend/src/vite-env.d.ts +1 -0
  132. package/frontend/tsconfig.json +19 -0
  133. package/package.json +65 -0
@@ -0,0 +1,249 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useGenerationStore, type ProjectVideo } from "../../stores/generationStore";
3
+
4
+ const API_BASE = `http://localhost:${(window as any).PORT}`;
5
+
6
+ interface Props {
7
+ projectId: string;
8
+ }
9
+
10
+ /** Seek the video to `time` and draw its current frame into a PNG data URL. */
11
+ async function captureFrameAt(
12
+ video: HTMLVideoElement,
13
+ time: number,
14
+ ): Promise<string> {
15
+ await new Promise<void>((resolve) => {
16
+ if (Math.abs(video.currentTime - time) < 0.001 && !video.seeking) {
17
+ resolve();
18
+ return;
19
+ }
20
+ const cleanup = () => {
21
+ clearTimeout(timer);
22
+ video.removeEventListener("seeked", onSeeked);
23
+ };
24
+ const onSeeked = () => {
25
+ cleanup();
26
+ resolve();
27
+ };
28
+ const timer = setTimeout(onSeeked, 1500);
29
+ video.addEventListener("seeked", onSeeked);
30
+ video.currentTime = time;
31
+ });
32
+
33
+ const canvas = document.createElement("canvas");
34
+ canvas.width = video.videoWidth;
35
+ canvas.height = video.videoHeight;
36
+ const ctx = canvas.getContext("2d");
37
+ if (!ctx) throw new Error("Canvas not supported");
38
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
39
+ return canvas.toDataURL("image/png");
40
+ }
41
+
42
+ export default function ExtractImageTab({ projectId }: Props) {
43
+ const gen = useGenerationStore();
44
+
45
+ const videoRef = useRef<HTMLVideoElement>(null);
46
+
47
+ const [selected, setSelected] = useState<ProjectVideo | null>(null);
48
+ const [duration, setDuration] = useState(0);
49
+ const [time, setTime] = useState(0);
50
+ const [extracting, setExtracting] = useState(false);
51
+ const [preview, setPreview] = useState<string | null>(null);
52
+ const [savedName, setSavedName] = useState<string | null>(null);
53
+ const [error, setError] = useState<string | null>(null);
54
+
55
+ useEffect(() => {
56
+ gen.fetchProjectVideos(projectId);
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [projectId]);
59
+
60
+ const selectVideo = (v: ProjectVideo) => {
61
+ setSelected(v);
62
+ setPreview(null);
63
+ setSavedName(null);
64
+ setDuration(0);
65
+ setTime(0);
66
+ setError(null);
67
+ };
68
+
69
+ const onLoadedMetadata = (e: React.SyntheticEvent<HTMLVideoElement>) => {
70
+ const d = e.currentTarget.duration;
71
+ setDuration(Number.isFinite(d) ? d : 0);
72
+ setTime(0);
73
+ };
74
+
75
+ const handleExtract = async () => {
76
+ const video = videoRef.current;
77
+ if (!video) return;
78
+ setExtracting(true);
79
+ setError(null);
80
+ setPreview(null);
81
+ setSavedName(null);
82
+ try {
83
+ const dataUrl = await captureFrameAt(video, time);
84
+ setPreview(dataUrl);
85
+
86
+ const res = await fetch(`${API_BASE}/api/extracted-frames`, {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/json" },
89
+ body: JSON.stringify({
90
+ image: dataUrl,
91
+ filename: `frame-${Date.now()}.png`,
92
+ projectId,
93
+ }),
94
+ });
95
+ if (!res.ok) throw new Error(await res.text());
96
+ const data = await res.json();
97
+ setSavedName(data.filename as string);
98
+ } catch (e) {
99
+ setError(String(e));
100
+ } finally {
101
+ setExtracting(false);
102
+ }
103
+ };
104
+
105
+ // ========== SVG Icons ==========
106
+
107
+ const VideoIcon = (
108
+ <svg
109
+ width="18"
110
+ height="18"
111
+ viewBox="0 0 24 24"
112
+ fill="none"
113
+ stroke="currentColor"
114
+ strokeWidth="2"
115
+ >
116
+ <polygon points="23 7 16 12 23 17 23 7" />
117
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
118
+ </svg>
119
+ );
120
+
121
+ const CameraIcon = (
122
+ <svg
123
+ width="14"
124
+ height="14"
125
+ viewBox="0 0 24 24"
126
+ fill="none"
127
+ stroke="currentColor"
128
+ strokeWidth="2"
129
+ strokeLinecap="round"
130
+ strokeLinejoin="round"
131
+ >
132
+ <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
133
+ <circle cx="12" cy="13" r="4" />
134
+ </svg>
135
+ );
136
+
137
+ return (
138
+ <div className="flex flex-col gap-7">
139
+ <div className="flex items-center gap-2">
140
+ <span className="text-tiffany-600">{CameraIcon}</span>
141
+ <h2 className="text-base font-semibold text-ink-900">
142
+ Image Extract from Video
143
+ </h2>
144
+ </div>
145
+
146
+ {/* Video picker */}
147
+ <div>
148
+ <label className="block text-xs font-semibold text-ink-700 uppercase tracking-wider mb-2">
149
+ Videos
150
+ </label>
151
+ {gen.projectVideosLoading ? (
152
+ <p className="text-xs text-ink-500 italic py-4 text-center">
153
+ Loading videos...
154
+ </p>
155
+ ) : gen.projectVideos.length === 0 ? (
156
+ <p className="text-xs text-ink-500 italic py-4 text-center border border-dashed border-ink-200 rounded-2xl">
157
+ No generated videos yet.
158
+ </p>
159
+ ) : (
160
+ <ul className="divide-y divide-ink-200 border border-ink-200 rounded-2xl overflow-hidden max-h-48 overflow-y-auto">
161
+ {gen.projectVideos.map((v) => (
162
+ <li key={v.filename}>
163
+ <button
164
+ onClick={() => selectVideo(v)}
165
+ className={`w-full flex items-center gap-2 px-3 py-2 text-left text-xs transition-colors ${
166
+ selected?.filename === v.filename
167
+ ? "bg-ink-100 text-ink-800"
168
+ : "text-ink-600 hover:bg-ink-100"
169
+ }`}
170
+ >
171
+ <span className="text-ink-500">{VideoIcon}</span>
172
+ <span className="truncate">{v.filename}</span>
173
+ </button>
174
+ </li>
175
+ ))}
176
+ </ul>
177
+ )}
178
+ </div>
179
+
180
+ {selected && (
181
+ <div className="flex flex-col gap-6">
182
+ <video
183
+ ref={videoRef}
184
+ src={selected.url}
185
+ crossOrigin="anonymous"
186
+ controls
187
+ onLoadedMetadata={onLoadedMetadata}
188
+ className="w-full max-h-64 rounded-2xl border border-ink-200 bg-black"
189
+ />
190
+
191
+ <div>
192
+ <label className="block text-xs font-semibold text-ink-700 uppercase tracking-wider mb-2">
193
+ Moment ({time.toFixed(2)}s / {duration.toFixed(2)}s)
194
+ </label>
195
+ <input
196
+ type="range"
197
+ min={0}
198
+ max={duration || 0}
199
+ step={0.05}
200
+ value={time}
201
+ onChange={(e) => {
202
+ const t = Number(e.target.value);
203
+ setTime(t);
204
+ if (videoRef.current) videoRef.current.currentTime = t;
205
+ }}
206
+ disabled={!duration}
207
+ className="w-full accent-tiffany-500"
208
+ />
209
+ </div>
210
+
211
+ <div className="flex items-center gap-2">
212
+ <button
213
+ onClick={handleExtract}
214
+ disabled={extracting || !duration}
215
+ className="flex items-center gap-1.5 px-4 py-2 text-xs font-medium rounded-xl bg-tiffany-500 hover:bg-tiffany-600 disabled:bg-ink-200 disabled:text-ink-500 text-ink-950 transition-colors"
216
+ >
217
+ {CameraIcon}
218
+ Extract Frame
219
+ </button>
220
+ {extracting && (
221
+ <span className="text-xs text-ink-600 italic">
222
+ Extracting...
223
+ </span>
224
+ )}
225
+ </div>
226
+
227
+ {error && <p className="text-xs text-red-600">{error}</p>}
228
+
229
+ {preview && (
230
+ <div className="flex flex-col gap-3">
231
+ <div className="rounded-2xl overflow-hidden border border-ink-200 inline-block bg-ink-100">
232
+ <img
233
+ src={preview}
234
+ alt="extracted"
235
+ className="max-h-72 object-contain"
236
+ />
237
+ </div>
238
+ {savedName && (
239
+ <p className="text-xs text-emerald-600">
240
+ Saved as {savedName} in extracted-frames/{projectId}
241
+ </p>
242
+ )}
243
+ </div>
244
+ )}
245
+ </div>
246
+ )}
247
+ </div>
248
+ );
249
+ }