@effectnode/media 0.9.0 → 0.11.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.
- package/dist/backend/movie-backend/agent/prompt/ltx.txt +20 -0
- package/dist/backend/movie-backend/agent/prompt/script.md +111 -1
- package/dist/backend/movie-backend/core.js +41 -0
- package/dist/backend/movie-backend/generation-queue.d.ts +1 -1
- package/dist/backend/movie-backend/generation-queue.js +75 -8
- package/dist/backend/movie-backend/render-media.d.ts +79 -2
- package/dist/backend/movie-backend/render-media.js +694 -418
- package/frontend/src/movie-app/components/EditorTabs/AdvancedVoiceCloneTab.tsx +366 -0
- package/frontend/src/movie-app/components/EditorTabs/AudioToVideoTab.tsx +406 -0
- package/frontend/src/movie-app/components/EditorTabs/FastImageEditTab.tsx +47 -0
- package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +155 -1
- package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +33 -13
- package/frontend/src/movie-app/components/EditorTabs/SetupAiModelTab.tsx +26 -5
- package/frontend/src/movie-app/components/EditorTabs/UpscaleTab.tsx +343 -0
- package/frontend/src/movie-app/components/EditorTabs/VoiceCloneTab.tsx +365 -0
- package/frontend/src/movie-app/components/ProjectEditorPage.tsx +130 -125
- package/frontend/src/movie-app/stores/advancedVoiceCloneStore.ts +205 -0
- package/frontend/src/movie-app/stores/aiModelStore.ts +25 -2
- package/frontend/src/movie-app/stores/audioToVideoStore.ts +274 -0
- package/frontend/src/movie-app/stores/generationStore.ts +156 -255
- package/frontend/src/movie-app/stores/movieStudioStore.ts +10 -3
- package/frontend/src/movie-app/stores/queueStore.ts +47 -1
- package/frontend/src/movie-app/stores/upscaleStore.ts +118 -0
- package/frontend/src/movie-app/stores/voiceCloneStore.ts +227 -0
- package/package.json +1 -1
- package/frontend/src/movie-app/components/EditorTabs/BatchVoiceVideoTab.tsx +0 -913
- package/frontend/src/movie-app/components/EditorTabs/ExtendVideoTab.tsx +0 -305
- package/frontend/src/movie-app/components/EditorTabs/ExtractImageTab.tsx +0 -249
- package/frontend/src/movie-app/components/EditorTabs/SceneVisualTab.tsx +0 -267
- package/frontend/src/movie-app/lib/batchVoiceStorage.ts +0 -75
- package/frontend/src/movie-app/stores/batchVoiceStore.ts +0 -990
- package/frontend/src/movie-app/stores/sceneVisualStore.ts +0 -251
|
@@ -16,17 +16,17 @@ export type GenerationTab =
|
|
|
16
16
|
| "movieStudio"
|
|
17
17
|
| "fastImageEdit"
|
|
18
18
|
| "video"
|
|
19
|
-
| "extend"
|
|
20
19
|
| "agent"
|
|
21
20
|
| "storyWriter"
|
|
22
|
-
| "extract"
|
|
23
|
-
| "sceneVisual"
|
|
24
21
|
| "textToImage"
|
|
25
22
|
| "batchVideo"
|
|
26
23
|
| "batchImageToVideo"
|
|
27
|
-
| "batchVoice"
|
|
28
24
|
| "llmServer"
|
|
29
|
-
| "aiModels"
|
|
25
|
+
| "aiModels"
|
|
26
|
+
| "upscale"
|
|
27
|
+
| "voiceClone"
|
|
28
|
+
| "audioToVideo"
|
|
29
|
+
| "advancedVoiceClone";
|
|
30
30
|
export type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4";
|
|
31
31
|
export type Resolution =
|
|
32
32
|
| "320p"
|
|
@@ -77,16 +77,9 @@ interface VideoState {
|
|
|
77
77
|
aspectRatio: AspectRatio;
|
|
78
78
|
resolution: Resolution;
|
|
79
79
|
mode: VideoMode;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
logs: string[];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface ExtendState {
|
|
87
|
-
prompt: string;
|
|
88
|
-
extendDuration: number;
|
|
89
|
-
extendFrames: number;
|
|
80
|
+
model: string;
|
|
81
|
+
stage1Steps: number;
|
|
82
|
+
stage2Steps: number;
|
|
90
83
|
generating: boolean;
|
|
91
84
|
result: string | null;
|
|
92
85
|
error: string | null;
|
|
@@ -114,6 +107,8 @@ interface TextToImageState {
|
|
|
114
107
|
|
|
115
108
|
interface FastImageEditState {
|
|
116
109
|
prompt: string;
|
|
110
|
+
steps: number;
|
|
111
|
+
upscale: string;
|
|
117
112
|
referenceImages: ProjectImage[];
|
|
118
113
|
downloading: boolean;
|
|
119
114
|
downloadingLogs: string[];
|
|
@@ -159,18 +154,14 @@ interface GenerationStore {
|
|
|
159
154
|
setVideoAspectRatio: (v: AspectRatio) => void;
|
|
160
155
|
setVideoResolution: (v: Resolution) => void;
|
|
161
156
|
setVideoMode: (v: VideoMode) => void;
|
|
157
|
+
setVideoModel: (v: string) => void;
|
|
158
|
+
setVideoStage1Steps: (v: number) => void;
|
|
159
|
+
setVideoStage2Steps: (v: number) => void;
|
|
162
160
|
clearVideoResult: () => void;
|
|
163
161
|
generateVideo: (projectId: string, imagePath?: string) => Promise<void>;
|
|
162
|
+
applyVideoQueueTask: (task: QueueTask) => void;
|
|
164
163
|
cancelGenerate: () => void;
|
|
165
164
|
|
|
166
|
-
// Video extension
|
|
167
|
-
extend: ExtendState;
|
|
168
|
-
setExtendPrompt: (v: string) => void;
|
|
169
|
-
setExtendDuration: (v: number) => void;
|
|
170
|
-
setExtendFrames: (v: number) => void;
|
|
171
|
-
clearExtendResult: () => void;
|
|
172
|
-
generateExtend: (projectId: string, videoPath: string) => Promise<void>;
|
|
173
|
-
|
|
174
165
|
// Text-to-image (mlx-gen z-image-turbo)
|
|
175
166
|
textToImage: TextToImageState;
|
|
176
167
|
setTextToImagePrompt: (v: string) => void;
|
|
@@ -193,6 +184,8 @@ interface GenerationStore {
|
|
|
193
184
|
// Fast image edit (mlx-gen FLUX.2 Klein)
|
|
194
185
|
fastImageEdit: FastImageEditState;
|
|
195
186
|
setFastImageEditPrompt: (v: string) => void;
|
|
187
|
+
setFastImageEditSteps: (v: number) => void;
|
|
188
|
+
setFastImageEditUpscale: (v: string) => void;
|
|
196
189
|
toggleFastImageEditImage: (img: ProjectImage) => void;
|
|
197
190
|
clearFastImageEditImages: () => void;
|
|
198
191
|
clearFastImageEditResult: () => void;
|
|
@@ -216,8 +209,6 @@ interface GenerationStore {
|
|
|
216
209
|
projectVideos: ProjectVideo[];
|
|
217
210
|
projectVideosLoading: boolean;
|
|
218
211
|
fetchProjectVideos: (projectId: string) => Promise<void>;
|
|
219
|
-
selectedVideo: ProjectVideo | null;
|
|
220
|
-
selectVideo: (video: ProjectVideo | null) => void;
|
|
221
212
|
|
|
222
213
|
// Project audio picker
|
|
223
214
|
projectAudios: ProjectAudio[];
|
|
@@ -393,6 +384,8 @@ async function enqueueFastImageEditTask(
|
|
|
393
384
|
projectId: string,
|
|
394
385
|
prompt: string,
|
|
395
386
|
images: string[],
|
|
387
|
+
steps: number,
|
|
388
|
+
upscale: string,
|
|
396
389
|
): Promise<{ ok: boolean; error?: string; taskId?: string }> {
|
|
397
390
|
try {
|
|
398
391
|
const res = await fetch(`${API_BASE}/api/queue/enqueue`, {
|
|
@@ -402,7 +395,44 @@ async function enqueueFastImageEditTask(
|
|
|
402
395
|
projectId,
|
|
403
396
|
type: "fast-image-edit",
|
|
404
397
|
label: "Fast image edit",
|
|
405
|
-
payload: { prompt, images, projectId },
|
|
398
|
+
payload: { prompt, images, steps, upscale, projectId },
|
|
399
|
+
}),
|
|
400
|
+
});
|
|
401
|
+
if (!res.ok) {
|
|
402
|
+
return { ok: false, error: await res.text() };
|
|
403
|
+
}
|
|
404
|
+
const task = (await res.json()) as { id?: string };
|
|
405
|
+
return { ok: true, taskId: task.id };
|
|
406
|
+
} catch (e) {
|
|
407
|
+
return { ok: false, error: String(e) };
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Enqueue a scene video generation task in the backend queue worker. */
|
|
412
|
+
async function enqueueImageToVideoTask(
|
|
413
|
+
projectId: string,
|
|
414
|
+
payload: {
|
|
415
|
+
prompt: string;
|
|
416
|
+
imagePath: string;
|
|
417
|
+
width: number;
|
|
418
|
+
height: number;
|
|
419
|
+
frames: number;
|
|
420
|
+
frameRate: number;
|
|
421
|
+
mode: string;
|
|
422
|
+
model: string;
|
|
423
|
+
stage1Steps: number;
|
|
424
|
+
stage2Steps: number;
|
|
425
|
+
},
|
|
426
|
+
): Promise<{ ok: boolean; error?: string; taskId?: string }> {
|
|
427
|
+
try {
|
|
428
|
+
const res = await fetch(`${API_BASE}/api/queue/enqueue`, {
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: { "Content-Type": "application/json" },
|
|
431
|
+
body: JSON.stringify({
|
|
432
|
+
projectId,
|
|
433
|
+
type: "image-to-video",
|
|
434
|
+
label: "Scene video",
|
|
435
|
+
payload,
|
|
406
436
|
}),
|
|
407
437
|
});
|
|
408
438
|
if (!res.ok) {
|
|
@@ -428,6 +458,14 @@ let fastImageEditProjectId: string | null = null;
|
|
|
428
458
|
/** The queue task id enqueued by the current generate action, if any. */
|
|
429
459
|
let fastImageEditActiveTaskId: string | null = null;
|
|
430
460
|
|
|
461
|
+
// ========== Scene Video Queue ==========
|
|
462
|
+
|
|
463
|
+
/** Project whose image-to-video task should refresh the video grid on completion. */
|
|
464
|
+
let videoProjectId: string | null = null;
|
|
465
|
+
|
|
466
|
+
/** The queue task id enqueued by the current generate video action, if any. */
|
|
467
|
+
let videoActiveTaskId: string | null = null;
|
|
468
|
+
|
|
431
469
|
// ========== Beep ==========
|
|
432
470
|
|
|
433
471
|
function playBeep() {
|
|
@@ -468,16 +506,9 @@ const initialVideo: VideoState = {
|
|
|
468
506
|
aspectRatio: "1:1",
|
|
469
507
|
resolution: "480p",
|
|
470
508
|
mode: "distilled",
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
logs: [],
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
const initialExtend: ExtendState = {
|
|
478
|
-
prompt: "Continue the scene: the camera holds, motion flows naturally...",
|
|
479
|
-
extendDuration: 2,
|
|
480
|
-
extendFrames: 2 * 24 + 1,
|
|
509
|
+
model: "dgrauet/ltx-2.3-mlx-q8",
|
|
510
|
+
stage1Steps: 30,
|
|
511
|
+
stage2Steps: 3,
|
|
481
512
|
generating: false,
|
|
482
513
|
result: null,
|
|
483
514
|
error: null,
|
|
@@ -530,6 +561,8 @@ const initialTextToImage: TextToImageState = {
|
|
|
530
561
|
|
|
531
562
|
const initialFastImageEdit: FastImageEditState = {
|
|
532
563
|
prompt: "",
|
|
564
|
+
steps: 4,
|
|
565
|
+
upscale: "none",
|
|
533
566
|
referenceImages: [],
|
|
534
567
|
downloading: false,
|
|
535
568
|
downloadingLogs: [],
|
|
@@ -686,6 +719,21 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
686
719
|
setVideoResolution: (resolution) =>
|
|
687
720
|
set((s) => ({ video: { ...s.video, resolution } })),
|
|
688
721
|
setVideoMode: (mode) => set((s) => ({ video: { ...s.video, mode } })),
|
|
722
|
+
setVideoModel: (model) => set((s) => ({ video: { ...s.video, model } })),
|
|
723
|
+
setVideoStage1Steps: (stage1Steps) =>
|
|
724
|
+
set((s) => ({
|
|
725
|
+
video: {
|
|
726
|
+
...s.video,
|
|
727
|
+
stage1Steps: Math.max(1, Math.round(Number(stage1Steps)) || 1),
|
|
728
|
+
},
|
|
729
|
+
})),
|
|
730
|
+
setVideoStage2Steps: (stage2Steps) =>
|
|
731
|
+
set((s) => ({
|
|
732
|
+
video: {
|
|
733
|
+
...s.video,
|
|
734
|
+
stage2Steps: Math.max(1, Math.round(Number(stage2Steps)) || 1),
|
|
735
|
+
},
|
|
736
|
+
})),
|
|
689
737
|
clearVideoResult: () =>
|
|
690
738
|
set((s) => ({
|
|
691
739
|
video: { ...s.video, result: null, error: null, logs: [] },
|
|
@@ -735,10 +783,6 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
735
783
|
// The backend now only accepts bare filenames — extract just the basename
|
|
736
784
|
resolvedImagePath = resolvedImagePath.split("/").pop() || resolvedImagePath;
|
|
737
785
|
|
|
738
|
-
// Create a fresh AbortController for this single generate run
|
|
739
|
-
generateAbortController = new AbortController();
|
|
740
|
-
const signal = generateAbortController.signal;
|
|
741
|
-
|
|
742
786
|
set((s) => ({
|
|
743
787
|
video: {
|
|
744
788
|
...s.video,
|
|
@@ -749,75 +793,63 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
749
793
|
},
|
|
750
794
|
}));
|
|
751
795
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
796
|
+
videoProjectId = projectId;
|
|
797
|
+
const r = await enqueueImageToVideoTask(projectId, {
|
|
798
|
+
prompt: video.prompt.trim(),
|
|
799
|
+
imagePath: resolvedImagePath,
|
|
800
|
+
width,
|
|
801
|
+
height,
|
|
802
|
+
frames: video.duration * 24 + 1,
|
|
803
|
+
frameRate: 24,
|
|
804
|
+
mode: video.mode,
|
|
805
|
+
model: video.model,
|
|
806
|
+
stage1Steps: video.stage1Steps,
|
|
807
|
+
stage2Steps: video.stage2Steps,
|
|
808
|
+
});
|
|
809
|
+
videoActiveTaskId = r.taskId ?? null;
|
|
810
|
+
if (!r.ok) {
|
|
811
|
+
set((s) => ({
|
|
812
|
+
video: {
|
|
813
|
+
...s.video,
|
|
814
|
+
generating: false,
|
|
815
|
+
error: r.error ?? "Failed to enqueue video",
|
|
816
|
+
},
|
|
817
|
+
}));
|
|
818
|
+
}
|
|
819
|
+
},
|
|
768
820
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
821
|
+
// Reconcile the scene video state with the latest queue task state. Only the
|
|
822
|
+
// task enqueued by this tab is reflected, so past tasks never surface stale
|
|
823
|
+
// results.
|
|
824
|
+
applyVideoQueueTask: (task) => {
|
|
825
|
+
if (task.type !== "image-to-video") return;
|
|
826
|
+
if (task.id !== videoActiveTaskId) return;
|
|
827
|
+
const v = get().video;
|
|
777
828
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
break;
|
|
788
|
-
case "complete":
|
|
789
|
-
set((s) => ({
|
|
790
|
-
video: {
|
|
791
|
-
...s.video,
|
|
792
|
-
generating: false,
|
|
793
|
-
result: `http://localhost:${(window as any).PORT}/api/files?path=${encodeURIComponent(data.path)}`,
|
|
794
|
-
},
|
|
795
|
-
}));
|
|
796
|
-
break;
|
|
797
|
-
case "error":
|
|
798
|
-
set((s) => ({
|
|
799
|
-
video: {
|
|
800
|
-
...s.video,
|
|
801
|
-
generating: false,
|
|
802
|
-
error: data.error || "Video generation failed",
|
|
803
|
-
},
|
|
804
|
-
}));
|
|
805
|
-
break;
|
|
806
|
-
}
|
|
829
|
+
if (task.status === "completed") {
|
|
830
|
+
const url = task.result?.url;
|
|
831
|
+
set({
|
|
832
|
+
video: {
|
|
833
|
+
...v,
|
|
834
|
+
generating: false,
|
|
835
|
+
result: url ? resolveImageUrl(url) : null,
|
|
836
|
+
error: null,
|
|
837
|
+
},
|
|
807
838
|
});
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
839
|
+
if (videoProjectId) get().fetchProjectVideos(videoProjectId);
|
|
840
|
+
} else if (task.status === "failed") {
|
|
841
|
+
set({
|
|
842
|
+
video: {
|
|
843
|
+
...v,
|
|
844
|
+
generating: false,
|
|
845
|
+
error: task.error ?? "Video generation failed",
|
|
846
|
+
},
|
|
847
|
+
});
|
|
848
|
+
} else if (task.status === "cancelled" || task.status === "paused") {
|
|
849
|
+
set({ video: { ...v, generating: false, error: null } });
|
|
850
|
+
} else {
|
|
851
|
+
// pending / running
|
|
852
|
+
set({ video: { ...v, generating: true, error: null } });
|
|
821
853
|
}
|
|
822
854
|
},
|
|
823
855
|
|
|
@@ -1045,6 +1077,9 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
1045
1077
|
frames: video.duration * 24 + 1,
|
|
1046
1078
|
frameRate: 24,
|
|
1047
1079
|
mode: video.mode,
|
|
1080
|
+
model: video.model,
|
|
1081
|
+
stage1Steps: video.stage1Steps,
|
|
1082
|
+
stage2Steps: video.stage2Steps,
|
|
1048
1083
|
}),
|
|
1049
1084
|
signal,
|
|
1050
1085
|
});
|
|
@@ -1112,140 +1147,6 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
1112
1147
|
}
|
|
1113
1148
|
},
|
|
1114
1149
|
|
|
1115
|
-
// ---- Video Extension ----
|
|
1116
|
-
extend: { ...initialExtend },
|
|
1117
|
-
|
|
1118
|
-
setExtendPrompt: (prompt) =>
|
|
1119
|
-
set((s) => ({ extend: { ...s.extend, prompt, error: null } })),
|
|
1120
|
-
setExtendDuration: (extendDuration) =>
|
|
1121
|
-
set((s) => ({
|
|
1122
|
-
extend: {
|
|
1123
|
-
...s.extend,
|
|
1124
|
-
extendDuration,
|
|
1125
|
-
extendFrames: extendDuration * 24 + 1,
|
|
1126
|
-
},
|
|
1127
|
-
})),
|
|
1128
|
-
setExtendFrames: (extendFrames) =>
|
|
1129
|
-
set((s) => ({
|
|
1130
|
-
extend: {
|
|
1131
|
-
...s.extend,
|
|
1132
|
-
extendFrames,
|
|
1133
|
-
extendDuration: Math.max(0.5, (extendFrames - 1) / 24),
|
|
1134
|
-
},
|
|
1135
|
-
})),
|
|
1136
|
-
clearExtendResult: () =>
|
|
1137
|
-
set((s) => ({
|
|
1138
|
-
extend: { ...s.extend, result: null, error: null, logs: [] },
|
|
1139
|
-
})),
|
|
1140
|
-
|
|
1141
|
-
generateExtend: async (projectId, videoPath) => {
|
|
1142
|
-
const { extend } = get();
|
|
1143
|
-
if (!extend.prompt.trim() || extend.generating) return;
|
|
1144
|
-
|
|
1145
|
-
// Extract raw file path from HTTP URL (e.g. http://localhost:PORT/api/files?path=...)
|
|
1146
|
-
let resolvedVideoPath = videoPath || "";
|
|
1147
|
-
if (!resolvedVideoPath) {
|
|
1148
|
-
set((s) => ({
|
|
1149
|
-
extend: { ...s.extend, error: "Select a video to extend first." },
|
|
1150
|
-
}));
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
if (resolvedVideoPath.includes("/api/files?path=")) {
|
|
1154
|
-
try {
|
|
1155
|
-
const url = new URL(resolvedVideoPath);
|
|
1156
|
-
resolvedVideoPath = url.searchParams.get("path") || resolvedVideoPath;
|
|
1157
|
-
} catch {
|
|
1158
|
-
// not a valid URL, use as-is
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
if (resolvedVideoPath.startsWith("file://")) {
|
|
1162
|
-
resolvedVideoPath = resolvedVideoPath.slice(7);
|
|
1163
|
-
}
|
|
1164
|
-
// The backend only accepts bare filenames — extract just the basename
|
|
1165
|
-
resolvedVideoPath = resolvedVideoPath.split("/").pop() || resolvedVideoPath;
|
|
1166
|
-
|
|
1167
|
-
// Create a fresh AbortController for this extend run
|
|
1168
|
-
generateAbortController = new AbortController();
|
|
1169
|
-
const signal = generateAbortController.signal;
|
|
1170
|
-
|
|
1171
|
-
set((s) => ({
|
|
1172
|
-
extend: {
|
|
1173
|
-
...s.extend,
|
|
1174
|
-
generating: true,
|
|
1175
|
-
error: null,
|
|
1176
|
-
result: null,
|
|
1177
|
-
logs: [],
|
|
1178
|
-
},
|
|
1179
|
-
}));
|
|
1180
|
-
|
|
1181
|
-
try {
|
|
1182
|
-
const res = await fetch(`${API_BASE}/api/render/extend-video`, {
|
|
1183
|
-
method: "POST",
|
|
1184
|
-
headers: { "Content-Type": "application/json" },
|
|
1185
|
-
body: JSON.stringify({
|
|
1186
|
-
prompt: extend.prompt.trim(),
|
|
1187
|
-
videoPath: resolvedVideoPath,
|
|
1188
|
-
projectId,
|
|
1189
|
-
extendFrames: extend.extendDuration * 24 + 1,
|
|
1190
|
-
}),
|
|
1191
|
-
signal,
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
if (!res.ok) {
|
|
1195
|
-
const err = await res.text();
|
|
1196
|
-
set((s) => ({
|
|
1197
|
-
extend: { ...s.extend, generating: false, error: err },
|
|
1198
|
-
}));
|
|
1199
|
-
generateAbortController = null;
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
await readSSEStream(res, (event, data) => {
|
|
1204
|
-
switch (event) {
|
|
1205
|
-
case "log":
|
|
1206
|
-
set((s) => ({
|
|
1207
|
-
extend: {
|
|
1208
|
-
...s.extend,
|
|
1209
|
-
logs: [...s.extend.logs, data.text as string],
|
|
1210
|
-
},
|
|
1211
|
-
}));
|
|
1212
|
-
break;
|
|
1213
|
-
case "complete":
|
|
1214
|
-
set((s) => ({
|
|
1215
|
-
extend: {
|
|
1216
|
-
...s.extend,
|
|
1217
|
-
generating: false,
|
|
1218
|
-
result: `http://localhost:${(window as any).PORT}/api/files?path=${encodeURIComponent(data.path)}`,
|
|
1219
|
-
},
|
|
1220
|
-
}));
|
|
1221
|
-
// Refresh the video grid so the new extended-*.mp4 appears
|
|
1222
|
-
get().fetchProjectVideos(projectId);
|
|
1223
|
-
break;
|
|
1224
|
-
case "error":
|
|
1225
|
-
set((s) => ({
|
|
1226
|
-
extend: {
|
|
1227
|
-
...s.extend,
|
|
1228
|
-
generating: false,
|
|
1229
|
-
error: data.error || "Video extension failed",
|
|
1230
|
-
},
|
|
1231
|
-
}));
|
|
1232
|
-
break;
|
|
1233
|
-
}
|
|
1234
|
-
});
|
|
1235
|
-
} catch (e: any) {
|
|
1236
|
-
// If the request was aborted, just stop silently
|
|
1237
|
-
if (e?.name !== "AbortError") {
|
|
1238
|
-
set((s) => ({
|
|
1239
|
-
extend: { ...s.extend, generating: false, error: String(e) },
|
|
1240
|
-
}));
|
|
1241
|
-
} else {
|
|
1242
|
-
set((s) => ({ extend: { ...s.extend, generating: false } }));
|
|
1243
|
-
}
|
|
1244
|
-
} finally {
|
|
1245
|
-
generateAbortController = null;
|
|
1246
|
-
}
|
|
1247
|
-
},
|
|
1248
|
-
|
|
1249
1150
|
// ---- Fast Image Edit (FLUX.2 Klein) ----
|
|
1250
1151
|
fastImageEdit: { ...initialFastImageEdit },
|
|
1251
1152
|
|
|
@@ -1254,6 +1155,17 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
1254
1155
|
fastImageEdit: { ...s.fastImageEdit, prompt, error: null },
|
|
1255
1156
|
})),
|
|
1256
1157
|
|
|
1158
|
+
setFastImageEditSteps: (steps) =>
|
|
1159
|
+
set((s) => ({
|
|
1160
|
+
fastImageEdit: {
|
|
1161
|
+
...s.fastImageEdit,
|
|
1162
|
+
steps: Math.max(1, Math.round(Number(steps)) || 1),
|
|
1163
|
+
},
|
|
1164
|
+
})),
|
|
1165
|
+
|
|
1166
|
+
setFastImageEditUpscale: (upscale) =>
|
|
1167
|
+
set((s) => ({ fastImageEdit: { ...s.fastImageEdit, upscale } })),
|
|
1168
|
+
|
|
1257
1169
|
toggleFastImageEditImage: (img) =>
|
|
1258
1170
|
set((s) => {
|
|
1259
1171
|
const current = s.fastImageEdit.referenceImages;
|
|
@@ -1420,6 +1332,8 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
1420
1332
|
projectId,
|
|
1421
1333
|
fastImageEdit.prompt.trim(),
|
|
1422
1334
|
images,
|
|
1335
|
+
fastImageEdit.steps,
|
|
1336
|
+
fastImageEdit.upscale,
|
|
1423
1337
|
);
|
|
1424
1338
|
fastImageEditActiveTaskId = r.taskId ?? null;
|
|
1425
1339
|
if (!r.ok) {
|
|
@@ -2006,7 +1920,6 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
2006
1920
|
// ---- Project Videos ----
|
|
2007
1921
|
projectVideos: [],
|
|
2008
1922
|
projectVideosLoading: false,
|
|
2009
|
-
selectedVideo: null,
|
|
2010
1923
|
|
|
2011
1924
|
fetchProjectVideos: async (projectId) => {
|
|
2012
1925
|
set({ projectVideosLoading: true });
|
|
@@ -2027,17 +1940,6 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
2027
1940
|
}
|
|
2028
1941
|
},
|
|
2029
1942
|
|
|
2030
|
-
selectVideo: (video) => {
|
|
2031
|
-
if (!video) {
|
|
2032
|
-
set({ selectedVideo: null });
|
|
2033
|
-
return;
|
|
2034
|
-
}
|
|
2035
|
-
const fullUrl = video.url.startsWith("http")
|
|
2036
|
-
? video.url
|
|
2037
|
-
: `http://localhost:${(window as any).PORT}${video.url}`;
|
|
2038
|
-
set({ selectedVideo: { ...video, url: fullUrl } });
|
|
2039
|
-
},
|
|
2040
|
-
|
|
2041
1943
|
// ---- Project Audio ----
|
|
2042
1944
|
projectAudios: [],
|
|
2043
1945
|
projectAudiosLoading: false,
|
|
@@ -2068,7 +1970,6 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
2068
1970
|
// Immediately reset generating state so UI returns to ready
|
|
2069
1971
|
set((s) => ({
|
|
2070
1972
|
video: { ...s.video, generating: false },
|
|
2071
|
-
extend: { ...s.extend, generating: false },
|
|
2072
1973
|
textToImage: { ...s.textToImage, generating: false },
|
|
2073
1974
|
}));
|
|
2074
1975
|
// Also kill the backend spawn process
|
|
@@ -2109,11 +2010,12 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
2109
2010
|
resetAll: () => {
|
|
2110
2011
|
fastImageEditProjectId = null;
|
|
2111
2012
|
fastImageEditActiveTaskId = null;
|
|
2013
|
+
videoProjectId = null;
|
|
2014
|
+
videoActiveTaskId = null;
|
|
2112
2015
|
set({
|
|
2113
2016
|
activeTab: "movieStudio",
|
|
2114
2017
|
image: { ...initialImage },
|
|
2115
2018
|
video: { ...initialVideo },
|
|
2116
|
-
extend: { ...initialExtend },
|
|
2117
2019
|
fastImageEdit: { ...initialFastImageEdit },
|
|
2118
2020
|
textToImage: { ...initialTextToImage },
|
|
2119
2021
|
textToImageProjectId: null,
|
|
@@ -2128,7 +2030,6 @@ export const useGenerationStore = create<GenerationStore>((set, get) => ({
|
|
|
2128
2030
|
selectedImage: null,
|
|
2129
2031
|
projectVideos: [],
|
|
2130
2032
|
projectVideosLoading: false,
|
|
2131
|
-
selectedVideo: null,
|
|
2132
2033
|
projectAudios: [],
|
|
2133
2034
|
projectAudiosLoading: false,
|
|
2134
2035
|
csvRows: [],
|
|
@@ -274,7 +274,9 @@ interface MovieStudioStore {
|
|
|
274
274
|
sceneImagesError: string | null;
|
|
275
275
|
sceneImageProgress: { current: number; total: number } | null;
|
|
276
276
|
regeneratingSceneImages: string[];
|
|
277
|
+
sceneImageSteps: number;
|
|
277
278
|
setIdea: (v: string) => void;
|
|
279
|
+
setSceneImageSteps: (v: number) => void;
|
|
278
280
|
hydrate: (projectId: string) => Promise<void>;
|
|
279
281
|
generate: (projectId: string, model: string) => Promise<void>;
|
|
280
282
|
render: (projectId: string) => Promise<void>;
|
|
@@ -330,12 +332,16 @@ export const useMovieStudioStore = create<MovieStudioStore>((set, get) => ({
|
|
|
330
332
|
sceneImagesError: null,
|
|
331
333
|
sceneImageProgress: null,
|
|
332
334
|
regeneratingSceneImages: [],
|
|
335
|
+
sceneImageSteps: 6,
|
|
333
336
|
|
|
334
337
|
setIdea: (idea) => {
|
|
335
338
|
set({ idea, error: null });
|
|
336
339
|
persistMovieStudioState();
|
|
337
340
|
},
|
|
338
341
|
|
|
342
|
+
setSceneImageSteps: (steps) =>
|
|
343
|
+
set({ sceneImageSteps: Math.max(1, Math.round(Number(steps)) || 1) }),
|
|
344
|
+
|
|
339
345
|
hydrate: async (projectId) => {
|
|
340
346
|
// Switching projects: reset to defaults so the previous project's idea
|
|
341
347
|
// doesn't leak through, then load the stored state (if any) below.
|
|
@@ -435,7 +441,7 @@ export const useMovieStudioStore = create<MovieStudioStore>((set, get) => ({
|
|
|
435
441
|
projectId,
|
|
436
442
|
"render-scene-image",
|
|
437
443
|
`Render scene image: ${scene.slug}`,
|
|
438
|
-
{ scene, batchId },
|
|
444
|
+
{ scene, batchId, steps: get().sceneImageSteps },
|
|
439
445
|
);
|
|
440
446
|
if (!r.ok) {
|
|
441
447
|
const b = batches.get(batchId);
|
|
@@ -611,7 +617,7 @@ export const useMovieStudioStore = create<MovieStudioStore>((set, get) => ({
|
|
|
611
617
|
projectId,
|
|
612
618
|
"render-scene-image",
|
|
613
619
|
`Render scene image: ${scene.slug}`,
|
|
614
|
-
{ scene, batchId },
|
|
620
|
+
{ scene, batchId, steps: get().sceneImageSteps },
|
|
615
621
|
);
|
|
616
622
|
if (!r.ok) {
|
|
617
623
|
const b = batches.get(batchId);
|
|
@@ -637,7 +643,7 @@ export const useMovieStudioStore = create<MovieStudioStore>((set, get) => ({
|
|
|
637
643
|
projectId,
|
|
638
644
|
"regenerate-scene-image",
|
|
639
645
|
`Regenerate scene image: ${slug}`,
|
|
640
|
-
{ slug, scene },
|
|
646
|
+
{ slug, scene, steps: get().sceneImageSteps },
|
|
641
647
|
);
|
|
642
648
|
if (!r.ok) {
|
|
643
649
|
set((s) => ({
|
|
@@ -1058,6 +1064,7 @@ export const useMovieStudioStore = create<MovieStudioStore>((set, get) => ({
|
|
|
1058
1064
|
sceneImagesError: null,
|
|
1059
1065
|
sceneImageProgress: null,
|
|
1060
1066
|
regeneratingSceneImages: [],
|
|
1067
|
+
sceneImageSteps: 6,
|
|
1061
1068
|
});
|
|
1062
1069
|
},
|
|
1063
1070
|
}));
|