You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
4.1 KiB
117 lines
4.1 KiB
/**
|
|
* Popi video generation executor.
|
|
* Used by workflow node execution paths.
|
|
*/
|
|
|
|
import type { GenerateVideoNodeData } from "@/types";
|
|
import { createPopiserverDefaultVideoModel } from "@/store/utils/defaultImageModel";
|
|
import {
|
|
buildRuntimeExtraTaskParams,
|
|
buildRuntimeModelParameters,
|
|
readVideoGenerationConfig,
|
|
} from "@/utils/generationConfig";
|
|
import { isHttpMediaUrl } from "@/utils/mediaResolver";
|
|
import type { NodeExecutionContext } from "./types";
|
|
import { buildPromptFromConnectedTextItems } from "./textInputPrompt";
|
|
import {
|
|
executePopiMediaGeneration,
|
|
mediaGenerationErrorMessage,
|
|
} from "./popiMediaGenerationExecutor";
|
|
|
|
export interface GenerateVideoOptions {
|
|
useStoredFallback?: boolean;
|
|
}
|
|
|
|
function getGeneratedImageUrl(value: unknown): string | undefined {
|
|
if (typeof value === "string" && value.length > 0) return value;
|
|
if (!value || typeof value !== "object") return undefined;
|
|
|
|
const record = value as { url?: unknown };
|
|
return typeof record.url === "string" && record.url.length > 0 ? record.url : undefined;
|
|
}
|
|
|
|
export async function executeGenerateVideo(
|
|
ctx: NodeExecutionContext,
|
|
options: GenerateVideoOptions = {}
|
|
): Promise<void> {
|
|
const { node, getConnectedInputs, updateNodeData, getFreshNode } = ctx;
|
|
const { useStoredFallback = false } = options;
|
|
const {
|
|
images: connectedImages,
|
|
videos: connectedVideos,
|
|
text: connectedText,
|
|
textItems: connectedTextItems,
|
|
audio: connectedAudio,
|
|
} = getConnectedInputs(node.id);
|
|
|
|
const freshNode = getFreshNode(node.id);
|
|
const nodeData = (freshNode?.data || node.data) as GenerateVideoNodeData;
|
|
const nodeConfig = readVideoGenerationConfig(nodeData, createPopiserverDefaultVideoModel());
|
|
const images = useStoredFallback && connectedImages.length === 0 ? nodeConfig.inputImages : connectedImages;
|
|
const prompt = buildPromptFromConnectedTextItems(connectedTextItems, connectedText, nodeConfig.prompt);
|
|
|
|
try {
|
|
updateNodeData(node.id, {
|
|
inputImages: images,
|
|
inputPrompt: nodeConfig.prompt,
|
|
status: "loading",
|
|
error: null,
|
|
});
|
|
|
|
const result = await executePopiMediaGeneration({
|
|
ctx,
|
|
mediaType: "video",
|
|
model: nodeConfig.selectedModel,
|
|
prompt: prompt || "",
|
|
images,
|
|
videos: connectedVideos,
|
|
voices: connectedAudio,
|
|
parameters: buildRuntimeModelParameters({ ...nodeConfig, prompt: prompt || "" }),
|
|
extraTaskParams: buildRuntimeExtraTaskParams({ ...nodeConfig, prompt: prompt || "" }),
|
|
subType: nodeConfig.subType,
|
|
referenceSubjectList: nodeConfig.referenceSubjectList,
|
|
});
|
|
|
|
const videoData = result.video || result.videoUrl;
|
|
const imageData = getGeneratedImageUrl(result.image);
|
|
const outputContent = videoData || imageData;
|
|
if (!result.success || !outputContent) {
|
|
throw new Error(result.error || "Video generation failed");
|
|
}
|
|
|
|
const timestamp = Date.now();
|
|
const videoId = `${timestamp}`;
|
|
const newHistoryItem = {
|
|
id: videoId,
|
|
video: outputContent,
|
|
timestamp,
|
|
prompt: prompt || "",
|
|
model: nodeConfig.selectedModel.modelId,
|
|
modelDisplayName: nodeConfig.selectedModel.displayName || nodeConfig.selectedModel.modelId,
|
|
modelProvider: nodeConfig.selectedModel.provider,
|
|
};
|
|
const isRemoteVideo = Boolean(videoData && outputContent && isHttpMediaUrl(outputContent));
|
|
|
|
updateNodeData(node.id, {
|
|
outputVideo: outputContent,
|
|
outputVideoRef: undefined,
|
|
outputVideoRemoteUrl: isRemoteVideo ? outputContent : undefined,
|
|
outputVideoStorageStatus: isRemoteVideo ? "remote-only" : undefined,
|
|
status: "complete",
|
|
error: null,
|
|
videoHistory: [newHistoryItem, ...(nodeData.videoHistory || [])].slice(0, 50),
|
|
selectedVideoHistoryIndex: 0,
|
|
lastUsedModel: nodeConfig.selectedModel,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
throw error;
|
|
}
|
|
const errorMessage = mediaGenerationErrorMessage(error, "Video generation failed");
|
|
updateNodeData(node.id, {
|
|
status: "error",
|
|
error: errorMessage,
|
|
});
|
|
throw new Error(errorMessage);
|
|
}
|
|
}
|
|
|