build out adhoc video saving graph

This commit is contained in:
Mary Hipp
2025-08-11 16:58:57 -04:00
committed by psychedelicious
parent af8ad1de3d
commit d6651ba9a7
9 changed files with 405 additions and 36 deletions

View File

@@ -1,21 +1,21 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { selectVideoFirstFrameImage, videoFirstFrameImageChanged } from 'features/parameters/store/videoSlice';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { setCurrentVideo } from 'features/ui/layouts/video-store';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiVideoBold } from 'react-icons/pi';
import { useDispatch } from 'react-redux';
export const ImageMenuItemSendToVideo = memo(() => {
const { t } = useTranslation();
const imageDTO = useImageDTOContext();
const dispatch = useDispatch();
const onClick = useCallback(() => {
// For now, we'll use the image URL as a video source
// In a real implementation, you might want to convert the image to video or use a different approach
setCurrentVideo(imageDTO.image_url);
dispatch(videoFirstFrameImageChanged(imageDTO));
navigationApi.switchToTab('video');
}, [imageDTO.image_url]);
}, [imageDTO]);
return (
<MenuItem

View File

@@ -0,0 +1,118 @@
import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectVideoFirstFrameImage, selectVideoLastFrameImage } from 'features/parameters/store/videoSlice';
import { zImageField } from 'features/nodes/types/common';
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import { selectPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
import { t } from 'i18next';
import { assert } from 'tsafe';
const log = logger('system');
// Default video parameters - these could be moved to a video params slice in the future
const DEFAULT_VIDEO_DURATION = 5;
const DEFAULT_VIDEO_ASPECT_RATIO = "1280:768"; // Default landscape
const DEFAULT_ENHANCE_PROMPT = true;
// Video parameter extraction helper
const getVideoParameters = (state: RootState) => {
// In the future, these could come from a dedicated video parameters slice
// For now, we use defaults but allow them to be overridden by any video-specific state
return {
duration: DEFAULT_VIDEO_DURATION,
aspectRatio: DEFAULT_VIDEO_ASPECT_RATIO,
enhancePrompt: DEFAULT_ENHANCE_PROMPT,
};
};
export const buildRunwayVideoGraph = (arg: GraphBuilderArg): GraphBuilderReturn => {
const { generationMode, state, manager } = arg;
log.debug({ generationMode, manager: manager?.id }, 'Building Runway video graph');
// Runway video generation supports text-to-video and image-to-video
// We can support multiple generation modes depending on whether frame images are provided
const supportedModes = ['txt2img'] as const;
if (!supportedModes.includes(generationMode as any)) {
throw new UnsupportedGenerationModeError(t('toast.runwayIncompatibleGenerationMode'));
}
const params = selectParamsSlice(state);
const prompts = selectPresetModifiedPrompts(state);
const videoFirstFrameImage = selectVideoFirstFrameImage(state);
const videoLastFrameImage = selectVideoLastFrameImage(state);
const videoParams = getVideoParameters(state);
// Get seed from params
const { seed, shouldRandomizeSeed } = params;
const finalSeed = shouldRandomizeSeed ? undefined : seed;
// Determine if this is image-to-video or text-to-video
const hasFrameImages = videoFirstFrameImage || videoLastFrameImage;
const g = new Graph(getPrefixedId('runway_video_graph'));
const positivePrompt = g.addNode({
id: getPrefixedId('positive_prompt'),
type: 'string',
value: prompts.positive,
});
// Create the runway video generation node
const runwayVideoNode = g.addNode({
id: getPrefixedId('runway_generate_video'),
// @ts-expect-error: This node is not available in the OSS application
type: 'runway_generate_video',
duration: videoParams.duration,
aspect_ratio: videoParams.aspectRatio,
seed: finalSeed,
});
// @ts-expect-error: This node is not available in the OSS application
g.addEdge(positivePrompt, 'value', runwayVideoNode, 'prompt');
// Add first frame image if provided
if (videoFirstFrameImage) {
const firstFrameImageField = zImageField.parse(videoFirstFrameImage);
// @ts-expect-error: This connection is specific to runway node
runwayVideoNode.first_frame_image = firstFrameImageField;
}
// Add last frame image if provided
if (videoLastFrameImage) {
const lastFrameImageField = zImageField.parse(videoLastFrameImage);
// @ts-expect-error: This connection is specific to runway node
runwayVideoNode.last_frame_image = lastFrameImageField;
}
// Set up metadata
g.upsertMetadata({
positive_prompt: prompts.positive,
negative_prompt: prompts.negative || '',
video_duration: videoParams.duration,
video_aspect_ratio: videoParams.aspectRatio,
seed: finalSeed,
enhance_prompt: videoParams.enhancePrompt,
generation_type: hasFrameImages ? 'image-to-video' : 'text-to-video',
});
// Add video frame images to metadata if they exist
if (hasFrameImages) {
g.upsertMetadata({
first_frame_image: videoFirstFrameImage,
last_frame_image: videoLastFrameImage,
}, 'merge');
}
g.setMetadataReceivingNode(runwayVideoNode);
return {
g,
positivePrompt,
};
};

View File

@@ -12,7 +12,10 @@ const zVideoState = z.object({
_version: z.literal(1),
videoFirstFrameImage: zImageWithDims.nullable(),
videoLastFrameImage: zImageWithDims.nullable(),
generatedVideoUrl: z.string().nullable(),
generatedVideo: z.object({
url: z.string(),
taskId: z.number(),
}).nullable(),
});
export type VideoState = z.infer<typeof zVideoState>;
@@ -21,7 +24,7 @@ const getInitialState = (): VideoState => ({
_version: 1,
videoFirstFrameImage: null,
videoLastFrameImage: null,
generatedVideoUrl: null,
generatedVideo: null,
});
const slice = createSlice({
@@ -36,8 +39,8 @@ const slice = createSlice({
state.videoLastFrameImage = action.payload;
},
generatedVideoUrlChanged: (state, action: PayloadAction<string | null>) => {
state.generatedVideoUrl = action.payload;
generatedVideoChanged: (state, action: PayloadAction<{ url: string, taskId: number } | null>) => {
state.generatedVideo = action.payload;
},
},
@@ -46,7 +49,7 @@ const slice = createSlice({
export const {
videoFirstFrameImageChanged,
videoLastFrameImageChanged,
generatedVideoUrlChanged,
generatedVideoChanged,
} = slice.actions;
export const videoSliceConfig: SliceConfig<typeof slice> = {
@@ -69,4 +72,4 @@ const createVideoSelector = <T>(selector: Selector<VideoState, T>) => createSele
export const selectVideoFirstFrameImage = createVideoSelector((video) => video.videoFirstFrameImage);
export const selectVideoLastFrameImage = createVideoSelector((video) => video.videoLastFrameImage);
export const selectGeneratedVideoUrl = createVideoSelector((video) => video.generatedVideoUrl);
export const selectGeneratedVideo = createVideoSelector((video) => video.generatedVideo);

View File

@@ -1,33 +1,55 @@
import { Box, Flex, Text } from '@invoke-ai/ui-library';
import { Box, Button, Flex, Text } from '@invoke-ai/ui-library';
import { useFocusRegion } from 'common/hooks/focus';
import { memo, useRef } from 'react';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import ReactPlayer from 'react-player';
import { useAppSelector } from 'app/store/storeHooks';
import { selectGeneratedVideoUrl } from 'features/parameters/store/videoSlice';
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { selectGeneratedVideo } from 'features/parameters/store/videoSlice';
import { PiCheckBold } from 'react-icons/pi';
import { useDispatch } from 'react-redux';
import { saveVideo } from 'features/video/saveVideo';
export const VideoPlayerPanel = memo(() => {
const { t } = useTranslation();
const ref = useRef<HTMLDivElement>(null);
const generatedVideoUrl = useAppSelector(selectGeneratedVideoUrl);
const generatedVideo = useAppSelector(selectGeneratedVideo);
useFocusRegion('video', ref);
const { dispatch, getState } = useAppStore();
const handleSaveVideo = useCallback(() => {
console.log('generatedVideo', generatedVideo);
if (!generatedVideo?.taskId) {
return
}
console.log('saving video', generatedVideo.taskId);
saveVideo({ dispatch, getState, taskId: `${generatedVideo.taskId}` });
}, [dispatch, getState, generatedVideo]);
return (
<Flex ref={ref} w="full" h="full" flexDirection="column" gap={4}>
<Box flex={1} position="relative">
{generatedVideoUrl && <ReactPlayer
src={generatedVideoUrl}
width="75%"
height="75%"
controls={true}
style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', maxWidth: '900px'}}
/>}
{!generatedVideoUrl && <Text>No video generated</Text>}
</Box>
{generatedVideo &&
<>
<Box flex={0.75} position="relative" >
<ReactPlayer
src={generatedVideo.url}
width="75%"
height="75%"
controls={true}
style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', maxWidth: '900px' }}
/>
</Box>
<Button leftIcon={<PiCheckBold />} colorScheme="invokeBlue" onClick={handleSaveVideo}>Keep</Button>
</>}
{!generatedVideo && <Text>No video generated</Text>}
</Flex>
);
});

View File

@@ -0,0 +1,25 @@
import type { RootState } from 'app/store/store';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { Graph } from 'features/nodes/util/graph/generation/Graph';
import { assert } from 'tsafe';
export const buildSaveVideoGraph = ({
state,
}: {
state: RootState;
}): { graph: Graph; outputNodeId: string } => {
const taskId = state.video.generatedVideo?.taskId;
assert(taskId, 'No task ID found in state');
const graph = new Graph(getPrefixedId('save-video-graph'));
const outputNode = graph.addNode({
// @ts-expect-error: These nodes are not available in the OSS application
type: 'save_runway_video',
id: getPrefixedId('save_runway_video'),
runway_task_id: taskId,
});
return { graph, outputNodeId: outputNode.id };
};

View File

@@ -0,0 +1,41 @@
import type { AppDispatch, AppGetState } from 'app/store/store';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { buildRunGraphDependencies, runGraph } from 'services/api/run-graph';
import { $socket } from 'services/events/stores';
import { assert } from 'tsafe';
import { buildSaveVideoGraph } from './graph';
import { saveVideoApi } from './state';
export const saveVideo = async (arg: { dispatch: AppDispatch; getState: AppGetState; taskId?: string }) => {
const { dispatch, getState, taskId } = arg;
const socket = $socket.get();
if (!socket) {
return;
}
const { graph, outputNodeId } = buildSaveVideoGraph({
state: getState(),
});
const dependencies = buildRunGraphDependencies(dispatch, socket);
try {
const { output } = await runGraph({
graph,
outputNodeId,
dependencies,
options: {
prepend: true,
},
});
assert(output.type === 'string_output');
saveVideoApi.setSuccess(output.value);
} catch {
saveVideoApi.reset();
toast({
id: 'SAVE_VIDEO_FAILED',
title: t('toast.saveVideoFailed'),
status: 'error',
});
}
};

View File

@@ -0,0 +1,98 @@
import { deepClone } from 'common/util/deepClone';
import { atom } from 'nanostores';
import type { ImageDTO } from 'services/api/types';
type SuccessState = {
isSuccess: true;
isError: false;
isPending: false;
result: string;
error: null;
imageDTO?: ImageDTO;
};
type ErrorState = {
isSuccess: false;
isError: true;
isPending: false;
result: null;
error: Error;
imageDTO?: ImageDTO;
};
type PendingState = {
isSuccess: false;
isError: false;
isPending: true;
result: null;
error: null;
imageDTO?: ImageDTO;
};
type IdleState = {
isSuccess: false;
isError: false;
isPending: false;
result: null;
error: null;
imageDTO?: ImageDTO;
};
export type PromptExpansionRequestState = IdleState | PendingState | SuccessState | ErrorState;
const IDLE_STATE: IdleState = {
isSuccess: false,
isError: false,
isPending: false,
result: null,
error: null,
imageDTO: undefined,
};
const $state = atom<PromptExpansionRequestState>(deepClone(IDLE_STATE));
const reset = () => {
$state.set(deepClone(IDLE_STATE));
};
const setPending = (imageDTO?: ImageDTO) => {
$state.set({
...$state.get(),
isSuccess: false,
isError: false,
isPending: true,
result: null,
error: null,
imageDTO,
});
};
const setSuccess = (result: string) => {
$state.set({
...$state.get(),
isSuccess: true,
isError: false,
isPending: false,
result,
error: null,
});
};
const setError = (error: Error) => {
$state.set({
...$state.get(),
isSuccess: false,
isError: true,
isPending: false,
result: null,
error,
});
};
export const saveVideoApi = {
$state,
reset,
setPending,
setSuccess,
setError,
};

View File

@@ -9094,7 +9094,7 @@ export type components = {
* @description The results of node executions
*/
results: {
[key: string]: components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["BoundingBoxCollectionOutput"] | components["schemas"]["BoundingBoxOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["CogView4ConditioningOutput"] | components["schemas"]["CogView4ModelLoaderOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["FloatGeneratorOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FluxConditioningCollectionOutput"] | components["schemas"]["FluxConditioningOutput"] | components["schemas"]["FluxControlLoRALoaderOutput"] | components["schemas"]["FluxControlNetOutput"] | components["schemas"]["FluxFillOutput"] | components["schemas"]["FluxKontextOutput"] | components["schemas"]["FluxLoRALoaderOutput"] | components["schemas"]["FluxModelLoaderOutput"] | components["schemas"]["FluxReduxOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ImageGeneratorOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ImagePanelCoordinateOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["IntegerGeneratorOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["LatentsMetaOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["LoRASelectorOutput"] | components["schemas"]["MDControlListOutput"] | components["schemas"]["MDIPAdapterListOutput"] | components["schemas"]["MDT2IAdapterListOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["MetadataToLorasCollectionOutput"] | components["schemas"]["MetadataToModelOutput"] | components["schemas"]["MetadataToSDXLModelOutput"] | components["schemas"]["ModelIdentifierOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["SD3ConditioningOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["Sd3ModelLoaderOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["StringGeneratorOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["VAEOutput"];
[key: string]: components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["BoundingBoxCollectionOutput"] | components["schemas"]["BoundingBoxOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["CogView4ConditioningOutput"] | components["schemas"]["CogView4ModelLoaderOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["FloatGeneratorOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FluxConditioningCollectionOutput"] | components["schemas"]["FluxConditioningOutput"] | components["schemas"]["FluxControlLoRALoaderOutput"] | components["schemas"]["FluxControlNetOutput"] | components["schemas"]["FluxFillOutput"] | components["schemas"]["FluxKontextOutput"] | components["schemas"]["FluxLoRALoaderOutput"] | components["schemas"]["FluxModelLoaderOutput"] | components["schemas"]["FluxReduxOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ImageGeneratorOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ImagePanelCoordinateOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["IntegerGeneratorOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["LatentsMetaOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["LoRASelectorOutput"] | components["schemas"]["MDControlListOutput"] | components["schemas"]["MDIPAdapterListOutput"] | components["schemas"]["MDT2IAdapterListOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["MetadataToLorasCollectionOutput"] | components["schemas"]["MetadataToModelOutput"] | components["schemas"]["MetadataToSDXLModelOutput"] | components["schemas"]["ModelIdentifierOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["RunwayVideoOutput"] | components["schemas"]["SD3ConditioningOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["Sd3ModelLoaderOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["StringGeneratorOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["VideoOutput"];
};
/**
* Errors
@@ -11880,7 +11880,7 @@ export type components = {
* Result
* @description The result of the invocation
*/
result: components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["BoundingBoxCollectionOutput"] | components["schemas"]["BoundingBoxOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["CogView4ConditioningOutput"] | components["schemas"]["CogView4ModelLoaderOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["FloatGeneratorOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FluxConditioningCollectionOutput"] | components["schemas"]["FluxConditioningOutput"] | components["schemas"]["FluxControlLoRALoaderOutput"] | components["schemas"]["FluxControlNetOutput"] | components["schemas"]["FluxFillOutput"] | components["schemas"]["FluxKontextOutput"] | components["schemas"]["FluxLoRALoaderOutput"] | components["schemas"]["FluxModelLoaderOutput"] | components["schemas"]["FluxReduxOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ImageGeneratorOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ImagePanelCoordinateOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["IntegerGeneratorOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["LatentsMetaOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["LoRASelectorOutput"] | components["schemas"]["MDControlListOutput"] | components["schemas"]["MDIPAdapterListOutput"] | components["schemas"]["MDT2IAdapterListOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["MetadataToLorasCollectionOutput"] | components["schemas"]["MetadataToModelOutput"] | components["schemas"]["MetadataToSDXLModelOutput"] | components["schemas"]["ModelIdentifierOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["SD3ConditioningOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["Sd3ModelLoaderOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["StringGeneratorOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["VAEOutput"];
result: components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["BoundingBoxCollectionOutput"] | components["schemas"]["BoundingBoxOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["CogView4ConditioningOutput"] | components["schemas"]["CogView4ModelLoaderOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["FloatGeneratorOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FluxConditioningCollectionOutput"] | components["schemas"]["FluxConditioningOutput"] | components["schemas"]["FluxControlLoRALoaderOutput"] | components["schemas"]["FluxControlNetOutput"] | components["schemas"]["FluxFillOutput"] | components["schemas"]["FluxKontextOutput"] | components["schemas"]["FluxLoRALoaderOutput"] | components["schemas"]["FluxModelLoaderOutput"] | components["schemas"]["FluxReduxOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ImageGeneratorOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ImagePanelCoordinateOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["IntegerGeneratorOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["LatentsMetaOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["LoRASelectorOutput"] | components["schemas"]["MDControlListOutput"] | components["schemas"]["MDIPAdapterListOutput"] | components["schemas"]["MDT2IAdapterListOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["MetadataToLorasCollectionOutput"] | components["schemas"]["MetadataToModelOutput"] | components["schemas"]["MetadataToSDXLModelOutput"] | components["schemas"]["ModelIdentifierOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["RunwayVideoOutput"] | components["schemas"]["SD3ConditioningOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["Sd3ModelLoaderOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["StringGeneratorOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["VideoOutput"];
};
/**
* InvocationErrorEvent
@@ -18285,6 +18285,28 @@ export type components = {
*/
type: "round_float";
};
/**
* RunwayVideoOutput
* @description Base class for nodes that output a runway result
*/
RunwayVideoOutput: {
/**
* Video Url
* @description The output video url
*/
video_url: string;
/**
* Runway Task Id
* @description The runway task id
*/
runway_task_id: string;
/**
* type
* @default runway_video_output
* @constant
*/
type: "runway_video_output";
};
/** SAMPoint */
SAMPoint: {
/**
@@ -21459,7 +21481,7 @@ export type components = {
* used, and the type will be ignored. They are included here for backwards compatibility.
* @enum {string}
*/
UIType: "MainModelField" | "CogView4MainModelField" | "FluxMainModelField" | "SD3MainModelField" | "SDXLMainModelField" | "SDXLRefinerModelField" | "ONNXModelField" | "VAEModelField" | "FluxVAEModelField" | "LoRAModelField" | "ControlNetModelField" | "IPAdapterModelField" | "T2IAdapterModelField" | "T5EncoderModelField" | "CLIPEmbedModelField" | "CLIPLEmbedModelField" | "CLIPGEmbedModelField" | "SpandrelImageToImageModelField" | "ControlLoRAModelField" | "SigLipModelField" | "FluxReduxModelField" | "LLaVAModelField" | "Imagen3ModelField" | "Imagen4ModelField" | "ChatGPT4oModelField" | "Gemini2_5ModelField" | "FluxKontextModelField" | "SchedulerField" | "AnyField" | "CollectionField" | "CollectionItemField" | "DEPRECATED_Boolean" | "DEPRECATED_Color" | "DEPRECATED_Conditioning" | "DEPRECATED_Control" | "DEPRECATED_Float" | "DEPRECATED_Image" | "DEPRECATED_Integer" | "DEPRECATED_Latents" | "DEPRECATED_String" | "DEPRECATED_BooleanCollection" | "DEPRECATED_ColorCollection" | "DEPRECATED_ConditioningCollection" | "DEPRECATED_ControlCollection" | "DEPRECATED_FloatCollection" | "DEPRECATED_ImageCollection" | "DEPRECATED_IntegerCollection" | "DEPRECATED_LatentsCollection" | "DEPRECATED_StringCollection" | "DEPRECATED_BooleanPolymorphic" | "DEPRECATED_ColorPolymorphic" | "DEPRECATED_ConditioningPolymorphic" | "DEPRECATED_ControlPolymorphic" | "DEPRECATED_FloatPolymorphic" | "DEPRECATED_ImagePolymorphic" | "DEPRECATED_IntegerPolymorphic" | "DEPRECATED_LatentsPolymorphic" | "DEPRECATED_StringPolymorphic" | "DEPRECATED_UNet" | "DEPRECATED_Vae" | "DEPRECATED_CLIP" | "DEPRECATED_Collection" | "DEPRECATED_CollectionItem" | "DEPRECATED_Enum" | "DEPRECATED_WorkflowField" | "DEPRECATED_IsIntermediate" | "DEPRECATED_BoardField" | "DEPRECATED_MetadataItem" | "DEPRECATED_MetadataItemCollection" | "DEPRECATED_MetadataItemPolymorphic" | "DEPRECATED_MetadataDict";
UIType: "MainModelField" | "CogView4MainModelField" | "FluxMainModelField" | "SD3MainModelField" | "SDXLMainModelField" | "SDXLRefinerModelField" | "ONNXModelField" | "VAEModelField" | "FluxVAEModelField" | "LoRAModelField" | "ControlNetModelField" | "IPAdapterModelField" | "T2IAdapterModelField" | "T5EncoderModelField" | "CLIPEmbedModelField" | "CLIPLEmbedModelField" | "CLIPGEmbedModelField" | "SpandrelImageToImageModelField" | "ControlLoRAModelField" | "SigLipModelField" | "FluxReduxModelField" | "LLaVAModelField" | "Imagen3ModelField" | "Imagen4ModelField" | "ChatGPT4oModelField" | "Gemini2_5ModelField" | "FluxKontextModelField" | "SchedulerField" | "AnyField" | "VideoField" | "CollectionField" | "CollectionItemField" | "DEPRECATED_Boolean" | "DEPRECATED_Color" | "DEPRECATED_Conditioning" | "DEPRECATED_Control" | "DEPRECATED_Float" | "DEPRECATED_Image" | "DEPRECATED_Integer" | "DEPRECATED_Latents" | "DEPRECATED_String" | "DEPRECATED_BooleanCollection" | "DEPRECATED_ColorCollection" | "DEPRECATED_ConditioningCollection" | "DEPRECATED_ControlCollection" | "DEPRECATED_FloatCollection" | "DEPRECATED_ImageCollection" | "DEPRECATED_IntegerCollection" | "DEPRECATED_LatentsCollection" | "DEPRECATED_StringCollection" | "DEPRECATED_BooleanPolymorphic" | "DEPRECATED_ColorPolymorphic" | "DEPRECATED_ConditioningPolymorphic" | "DEPRECATED_ControlPolymorphic" | "DEPRECATED_FloatPolymorphic" | "DEPRECATED_ImagePolymorphic" | "DEPRECATED_IntegerPolymorphic" | "DEPRECATED_LatentsPolymorphic" | "DEPRECATED_StringPolymorphic" | "DEPRECATED_UNet" | "DEPRECATED_Vae" | "DEPRECATED_CLIP" | "DEPRECATED_Collection" | "DEPRECATED_CollectionItem" | "DEPRECATED_Enum" | "DEPRECATED_WorkflowField" | "DEPRECATED_IsIntermediate" | "DEPRECATED_BoardField" | "DEPRECATED_MetadataItem" | "DEPRECATED_MetadataItemCollection" | "DEPRECATED_MetadataItemPolymorphic" | "DEPRECATED_MetadataDict";
/** UNetField */
UNetField: {
/** @description Info to load unet submodel */
@@ -21870,6 +21892,46 @@ export type components = {
*/
output_fields: components["schemas"]["FieldIdentifier"][];
};
/**
* VideoField
* @description A video primitive field
*/
VideoField: {
/**
* Video Id
* @description The id of the video
*/
video_id: string;
/**
* Width
* @description The width of the video in pixels
*/
width: number;
/**
* Height
* @description The height of the video in pixels
*/
height: number;
/**
* Duration Seconds
* @description The duration of the video in seconds
*/
duration_seconds: number;
};
/**
* VideoOutput
* @description Base class for nodes that output a video
*/
VideoOutput: {
/** @description The output video */
video: components["schemas"]["VideoField"];
/**
* type
* @default video_output
* @constant
*/
type: "video_output";
};
/** Workflow */
Workflow: {
/**

View File

@@ -12,7 +12,7 @@ import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gal
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { isImageField, isImageFieldCollection } from 'features/nodes/types/common';
import { zNodeStatus } from 'features/nodes/types/invocation';
import { generatedVideoUrlChanged } from 'features/parameters/store/videoSlice';
import { generatedVideoChanged } from 'features/parameters/store/videoSlice';
import type { LRUCache } from 'lru-cache';
import { boardsApi } from 'services/api/endpoints/boards';
import { getImageDTOSafe, imagesApi } from 'services/api/endpoints/images';
@@ -205,11 +205,11 @@ export const buildOnInvocationComplete = (
return imageDTOs;
};
const getResultVideoDTOs = async (data: S['InvocationCompleteEvent']): Promise<string | null> => {
const getResultVideoDTOs = async (data: S['InvocationCompleteEvent']): Promise<{url: string , taskId: number} | null> => {
// @ts-expect-error: This is a workaround to get the video name from the result
if (data.invocation.type === 'runway_generate_video') {
// @ts-expect-error: This is a workaround to get the video name from the result
return data.result.video.video_name;
return {url: data.result.video_url, taskId: data.result.runway_task_id};
}
return null;
};
@@ -235,9 +235,9 @@ export const buildOnInvocationComplete = (
await addImagesToGallery(data);
const videoUrl = await getResultVideoDTOs(data);
if (videoUrl) {
dispatch(generatedVideoUrlChanged(videoUrl));
const videoResult = await getResultVideoDTOs(data);
if (videoResult) {
dispatch(generatedVideoChanged(videoResult));
}
$lastProgressEvent.set(null);