mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
split out video aspect/ratio into its own components
This commit is contained in:
committed by
psychedelicious
parent
b52a885e74
commit
2b688ed855
@@ -504,7 +504,7 @@ export const RUNWAY_ASPECT_RATIOS: Record<RunwayAspectRatio, Dimensions> = {
|
||||
export const zVeo3Resolution = z.enum(['720p', '1080p']);
|
||||
export type Veo3Resolution = z.infer<typeof zVeo3Resolution>;
|
||||
export const isVeo3Resolution = (v: unknown): v is Veo3Resolution => zVeo3Resolution.safeParse(v).success;
|
||||
export const VEO3_RESOLUTIONS: Record<Veo3Resolution, Dimensions> = {
|
||||
export const RESOLUTION_MAP: Record<Veo3Resolution | RunwayResolution, Dimensions> = {
|
||||
'720p': { width: 1280, height: 720 },
|
||||
'1080p': { width: 1920, height: 1080 },
|
||||
};
|
||||
@@ -512,9 +512,6 @@ export const VEO3_RESOLUTIONS: Record<Veo3Resolution, Dimensions> = {
|
||||
export const zRunwayResolution = z.enum(['720p']);
|
||||
export type RunwayResolution = z.infer<typeof zRunwayResolution>;
|
||||
export const isRunwayResolution = (v: unknown): v is RunwayResolution => zRunwayResolution.safeParse(v).success;
|
||||
export const RUNWAY_RESOLUTIONS: Record<RunwayResolution, Dimensions> = {
|
||||
'720p': { width: 1280, height: 720 },
|
||||
};
|
||||
|
||||
const zAspectRatioConfig = z.object({
|
||||
id: zAspectRatioID,
|
||||
|
||||
@@ -37,6 +37,7 @@ export const DimensionsAspectRatioSelect = memo(() => {
|
||||
const isGemini2_5 = useAppSelector(selectIsGemini2_5);
|
||||
const isVeo3 = useAppSelector(selectIsVeo3);
|
||||
const isRunway = useAppSelector(selectIsRunway);
|
||||
|
||||
const options = useMemo(() => {
|
||||
// Imagen3 and ChatGPT4o have different aspect ratio options, and do not support freeform sizes
|
||||
if (isImagen3 || isImagen4) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FormControl, FormLabel, Select } from '@invoke-ai/ui-library';
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
isRunwayDurationID,
|
||||
@@ -7,10 +8,8 @@ import {
|
||||
VEO3_DURATIONS,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { selectVideoDuration, selectVideoModel, videoDurationChanged } from 'features/parameters/store/videoSlice';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
export const ParamDuration = () => {
|
||||
const videoDuration = useAppSelector(selectVideoDuration);
|
||||
@@ -34,9 +33,9 @@ export const ParamDuration = () => {
|
||||
}
|
||||
}, [model]);
|
||||
|
||||
const onChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
|
||||
(e) => {
|
||||
const duration = e.target.value;
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
const duration = v?.value;
|
||||
if (!isVeo3DurationID(duration) && !isRunwayDurationID(duration)) {
|
||||
return;
|
||||
}
|
||||
@@ -46,25 +45,12 @@ export const ParamDuration = () => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const value = useMemo(() => options.find((o) => o.value === videoDuration)?.value, [videoDuration, options]);
|
||||
const value = useMemo(() => options.find((o) => o.value === videoDuration), [videoDuration, options]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{t('parameters.duration')}</FormLabel>
|
||||
<Select
|
||||
size="sm"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
cursor="pointer"
|
||||
iconSize="0.75rem"
|
||||
icon={<PiCaretDownBold />}
|
||||
>
|
||||
{options.map((duration) => (
|
||||
<option key={duration.value} value={duration.value}>
|
||||
{duration.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Combobox value={value} options={options} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { FormControl, FormLabel, Select } from '@invoke-ai/ui-library';
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
isRunwayResolution,
|
||||
isVeo3Resolution,
|
||||
VEO3_RESOLUTIONS,
|
||||
zRunwayResolution,
|
||||
zVeo3Resolution,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { selectVideoModel, selectVideoResolution, videoResolutionChanged } from 'features/parameters/store/videoSlice';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
export const ParamResolution = () => {
|
||||
const videoResolution = useAppSelector(selectVideoResolution);
|
||||
@@ -21,47 +19,32 @@ export const ParamResolution = () => {
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (model?.base === 'veo3') {
|
||||
return zVeo3Resolution.options;
|
||||
return zVeo3Resolution.options.map((o) => ({ label: o, value: o }));
|
||||
} else if (model?.base === 'runway') {
|
||||
return zRunwayResolution.options;
|
||||
return zRunwayResolution.options.map((o) => ({ label: o, value: o }));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [model]);
|
||||
|
||||
const onChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
|
||||
(e) => {
|
||||
const resolution = e.target.value;
|
||||
if (!isVeo3Resolution(resolution)) {
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
const resolution = v?.value;
|
||||
if (!isVeo3Resolution(resolution) && !isRunwayResolution(resolution)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(videoResolutionChanged(resolution));
|
||||
dispatch(widthChanged({ width: VEO3_RESOLUTIONS[resolution].width, updateAspectRatio: true, clamp: true }));
|
||||
dispatch(heightChanged({ height: VEO3_RESOLUTIONS[resolution].height, updateAspectRatio: true, clamp: true }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const value = useMemo(() => options.find((o) => o === videoResolution), [videoResolution, options]);
|
||||
const value = useMemo(() => options.find((o) => o.value === videoResolution), [videoResolution, options]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{t('parameters.resolution')}</FormLabel>
|
||||
<Select
|
||||
size="sm"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
cursor="pointer"
|
||||
iconSize="0.75rem"
|
||||
icon={<PiCaretDownBold />}
|
||||
>
|
||||
{options.map((resolution) => (
|
||||
<option key={resolution} value={resolution}>
|
||||
{resolution}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Combobox value={value} options={options} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { ParamResolution } from './ParamResolution';
|
||||
import { VideoDimensionsAspectRatioSelect } from './VideoDimensionsAspectRatioSelect';
|
||||
import { VideoDimensionsPreview } from './VideoDimensionsPreview';
|
||||
|
||||
export const VideoDimensions = memo(() => {
|
||||
return (
|
||||
<Flex gap={4} alignItems="center">
|
||||
<Flex gap={4} flexDirection="column" width="full">
|
||||
<ParamResolution />
|
||||
<VideoDimensionsAspectRatioSelect />
|
||||
</Flex>
|
||||
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0} alignItems="center" justifyContent="center" py={4}>
|
||||
<VideoDimensionsPreview />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
VideoDimensions.displayName = 'VideoDimensions';
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import {
|
||||
isAspectRatioID,
|
||||
zAspectRatioID,
|
||||
zRunwayAspectRatioID,
|
||||
zVeo3AspectRatioID,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import {
|
||||
selectIsRunway,
|
||||
selectIsVeo3,
|
||||
selectVideoAspectRatio,
|
||||
videoAspectRatioChanged,
|
||||
} from 'features/parameters/store/videoSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const VideoDimensionsAspectRatioSelect = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const id = useAppSelector(selectVideoAspectRatio);
|
||||
const isVeo3 = useAppSelector(selectIsVeo3);
|
||||
const isRunway = useAppSelector(selectIsRunway);
|
||||
const options = useMemo(() => {
|
||||
if (isVeo3) {
|
||||
return zVeo3AspectRatioID.options.map((o) => ({ label: o, value: o }));
|
||||
}
|
||||
if (isRunway) {
|
||||
return zRunwayAspectRatioID.options.map((o) => ({ label: o, value: o }));
|
||||
}
|
||||
// All other models
|
||||
return zAspectRatioID.options.map((o) => ({ label: o, value: o }));
|
||||
}, [isVeo3, isRunway]);
|
||||
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!isAspectRatioID(v?.value)) {
|
||||
return;
|
||||
}
|
||||
dispatch(videoAspectRatioChanged(v.value));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const value = useMemo(() => options.find((o) => o.value === id), [id, options]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InformationalPopover feature="paramAspect">
|
||||
<FormLabel>{t('parameters.aspect')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox value={value} options={options} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
VideoDimensionsAspectRatioSelect.displayName = 'VideoDimensionsAspectRatioSelect';
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Flex, Grid, GridItem, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ASPECT_RATIO_MAP } from 'features/controlLayers/store/types';
|
||||
import { useCurrentVideoDimensions } from 'features/parameters/hooks/useCurrentVideoDimensions';
|
||||
import { selectVideoAspectRatio } from 'features/parameters/store/videoSlice';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
export const VideoDimensionsPreview = memo(() => {
|
||||
const aspectRatio = useAppSelector(selectVideoAspectRatio);
|
||||
const [ref, dims] = useMeasure<HTMLDivElement>();
|
||||
|
||||
const currentVideoDimensions = useCurrentVideoDimensions();
|
||||
|
||||
const previewBoxSize = useMemo(() => {
|
||||
if (!dims || aspectRatio === 'Free') {
|
||||
return { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
const aspectRatioValue = ASPECT_RATIO_MAP[aspectRatio]?.ratio ?? 1;
|
||||
|
||||
let width = currentVideoDimensions.width;
|
||||
let height = currentVideoDimensions.height;
|
||||
|
||||
if (currentVideoDimensions.width > currentVideoDimensions.height) {
|
||||
width = dims.width;
|
||||
height = width / aspectRatioValue;
|
||||
} else {
|
||||
height = dims.height;
|
||||
width = height * aspectRatioValue;
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
}, [dims, currentVideoDimensions, aspectRatio]);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" alignItems="center" justifyContent="center" ref={ref}>
|
||||
<Flex
|
||||
position="relative"
|
||||
borderRadius="base"
|
||||
borderColor="base.600"
|
||||
borderWidth="3px"
|
||||
width={`${previewBoxSize.width}px`}
|
||||
height={`${previewBoxSize.height}px`}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid
|
||||
borderRadius="base"
|
||||
position="absolute"
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
gridTemplateColumns="1fr 1fr 1fr"
|
||||
gridTemplateRows="1fr 1fr 1fr"
|
||||
gap="1px"
|
||||
bg="base.700"
|
||||
>
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
<GridItem bg="base.800" />
|
||||
</Grid>
|
||||
<Flex
|
||||
position="absolute"
|
||||
top="50%"
|
||||
right="50%"
|
||||
bottom="50%"
|
||||
left="50%"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Text color="base.200" fontSize="xs">
|
||||
{currentVideoDimensions.width}x{currentVideoDimensions.height}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
VideoDimensionsPreview.displayName = 'VideoDimensionsPreview';
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import type { AspectRatioID } from 'features/controlLayers/store/types';
|
||||
import { ASPECT_RATIO_MAP, RESOLUTION_MAP } from 'features/controlLayers/store/types';
|
||||
import { selectVideoAspectRatio, selectVideoResolution } from 'features/parameters/store/videoSlice';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useCurrentVideoDimensions = () => {
|
||||
const videoAspectRatio = useAppSelector(selectVideoAspectRatio);
|
||||
const videoResolution = useAppSelector(selectVideoResolution);
|
||||
|
||||
const currentVideoDimensions = useMemo(() => {
|
||||
// Default fallback dimensions
|
||||
const fallback = { width: 1280, height: 720 };
|
||||
|
||||
if (!videoAspectRatio || !videoResolution) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// Get base resolution dimensions from the resolution tables
|
||||
let baseWidth: number;
|
||||
let baseHeight: number;
|
||||
|
||||
const resolutionDims = RESOLUTION_MAP[videoResolution];
|
||||
baseWidth = resolutionDims.width;
|
||||
baseHeight = resolutionDims.height;
|
||||
|
||||
// Get the aspect ratio value from the map
|
||||
const aspectRatioData = ASPECT_RATIO_MAP[videoAspectRatio as Exclude<AspectRatioID, 'Free'>];
|
||||
if (!aspectRatioData) {
|
||||
return { width: baseWidth, height: baseHeight };
|
||||
}
|
||||
|
||||
const targetRatio = aspectRatioData.ratio;
|
||||
|
||||
// Calculate dimensions that maintain the aspect ratio while respecting the resolution
|
||||
// We use the resolution as a constraint on the total pixel count
|
||||
const totalPixels = baseWidth * baseHeight;
|
||||
|
||||
// Calculate dimensions that match the aspect ratio and approximate the target pixel count
|
||||
// width * height = totalPixels
|
||||
// width / height = targetRatio
|
||||
// Therefore: width = sqrt(totalPixels * targetRatio) and height = sqrt(totalPixels / targetRatio)
|
||||
const calculatedWidth = Math.round(Math.sqrt(totalPixels * targetRatio));
|
||||
const calculatedHeight = Math.round(Math.sqrt(totalPixels / targetRatio));
|
||||
|
||||
// Ensure dimensions are even numbers (common requirement for video encoding)
|
||||
const width = calculatedWidth % 2 === 0 ? calculatedWidth : calculatedWidth + 1;
|
||||
const height = calculatedHeight % 2 === 0 ? calculatedHeight : calculatedHeight + 1;
|
||||
|
||||
return { width, height };
|
||||
}, [videoAspectRatio, videoResolution]);
|
||||
|
||||
return currentVideoDimensions;
|
||||
};
|
||||
@@ -3,10 +3,25 @@ import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { SliceConfig } from 'app/store/types';
|
||||
import { isPlainObject } from 'es-toolkit';
|
||||
import type { ImageWithDims, RunwayDuration, Veo3Duration, Veo3Resolution } from 'features/controlLayers/store/types';
|
||||
import type {
|
||||
AspectRatioID,
|
||||
ImageWithDims,
|
||||
RunwayDuration,
|
||||
RunwayResolution,
|
||||
Veo3Duration,
|
||||
Veo3Resolution,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import {
|
||||
isRunwayAspectRatioID,
|
||||
isRunwayDurationID,
|
||||
isRunwayResolution,
|
||||
isVeo3AspectRatioID,
|
||||
isVeo3DurationID,
|
||||
isVeo3Resolution,
|
||||
zAspectRatioID,
|
||||
zImageWithDims,
|
||||
zRunwayDurationID,
|
||||
zRunwayResolution,
|
||||
zVeo3DurationID,
|
||||
zVeo3Resolution,
|
||||
} from 'features/controlLayers/store/types';
|
||||
@@ -21,20 +36,24 @@ const zVideoState = z.object({
|
||||
startingFrameImage: zImageWithDims.nullable(),
|
||||
generatedVideo: zVideoField.nullable(),
|
||||
videoModel: zModelIdentifierField.nullable(),
|
||||
videoResolution: zVeo3Resolution.nullable(),
|
||||
videoDuration: zVeo3DurationID.or(zRunwayDurationID).nullable(),
|
||||
videoResolution: zVeo3Resolution.or(zRunwayResolution),
|
||||
videoDuration: zVeo3DurationID.or(zRunwayDurationID),
|
||||
videoAspectRatio: zAspectRatioID,
|
||||
});
|
||||
|
||||
export type VideoState = z.infer<typeof zVideoState>;
|
||||
|
||||
const getInitialState = (): VideoState => ({
|
||||
_version: 1,
|
||||
startingFrameImage: null,
|
||||
generatedVideo: null,
|
||||
videoModel: null,
|
||||
videoResolution: '720p',
|
||||
videoDuration: '8',
|
||||
});
|
||||
const getInitialState = (): VideoState => {
|
||||
return {
|
||||
_version: 1,
|
||||
startingFrameImage: null,
|
||||
generatedVideo: null,
|
||||
videoModel: null,
|
||||
videoResolution: '720p',
|
||||
videoDuration: '8',
|
||||
videoAspectRatio: '16:9',
|
||||
};
|
||||
};
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'video',
|
||||
@@ -52,15 +71,41 @@ const slice = createSlice({
|
||||
videoModelChanged: (state, action: PayloadAction<Veo3ModelConfig | RunwayModelConfig | null>) => {
|
||||
const parsedModel = zModelIdentifierField.parse(action.payload);
|
||||
state.videoModel = parsedModel;
|
||||
|
||||
if (parsedModel?.base === 'veo3') {
|
||||
if (!state.videoResolution || !isVeo3Resolution(state.videoResolution)) {
|
||||
state.videoResolution = '720p';
|
||||
}
|
||||
if (!state.videoDuration || !isVeo3DurationID(state.videoDuration)) {
|
||||
state.videoDuration = '8';
|
||||
}
|
||||
if (!state.videoAspectRatio || !isVeo3AspectRatioID(state.videoAspectRatio)) {
|
||||
state.videoAspectRatio = '16:9';
|
||||
}
|
||||
} else if (parsedModel?.base === 'runway') {
|
||||
if (!state.videoResolution || !isRunwayResolution(state.videoResolution)) {
|
||||
state.videoResolution = '720p';
|
||||
}
|
||||
if (!state.videoDuration || !isRunwayDurationID(state.videoDuration)) {
|
||||
state.videoDuration = '5';
|
||||
}
|
||||
if (!state.videoAspectRatio || !isRunwayAspectRatioID(state.videoAspectRatio)) {
|
||||
state.videoAspectRatio = '16:9';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
videoResolutionChanged: (state, action: PayloadAction<Veo3Resolution | null>) => {
|
||||
videoResolutionChanged: (state, action: PayloadAction<Veo3Resolution | RunwayResolution>) => {
|
||||
state.videoResolution = action.payload;
|
||||
},
|
||||
|
||||
videoDurationChanged: (state, action: PayloadAction<Veo3Duration | RunwayDuration | null>) => {
|
||||
videoDurationChanged: (state, action: PayloadAction<Veo3Duration | RunwayDuration>) => {
|
||||
state.videoDuration = action.payload;
|
||||
},
|
||||
|
||||
videoAspectRatioChanged: (state, action: PayloadAction<AspectRatioID>) => {
|
||||
state.videoAspectRatio = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -70,6 +115,7 @@ export const {
|
||||
videoModelChanged,
|
||||
videoResolutionChanged,
|
||||
videoDurationChanged,
|
||||
videoAspectRatioChanged,
|
||||
} = slice.actions;
|
||||
|
||||
export const videoSliceConfig: SliceConfig<typeof slice> = {
|
||||
@@ -96,6 +142,6 @@ export const selectVideoModel = createVideoSelector((video) => video.videoModel)
|
||||
export const selectVideoModelKey = createVideoSelector((video) => video.videoModel?.key);
|
||||
export const selectVideoResolution = createVideoSelector((video) => video.videoResolution);
|
||||
export const selectVideoDuration = createVideoSelector((video) => video.videoDuration);
|
||||
|
||||
export const selectVideoAspectRatio = createVideoSelector((video) => video.videoAspectRatio);
|
||||
export const selectIsVeo3 = createVideoSelector((video) => video.videoModel?.base === 'veo3');
|
||||
export const selectIsRunway = createVideoSelector((video) => video.videoModel?.base === 'runway');
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import { Flex, StandaloneAccordion } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
aspectRatioIdChanged,
|
||||
aspectRatioLockToggled,
|
||||
heightChanged,
|
||||
widthChanged,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { RUNWAY_ASPECT_RATIOS, VEO3_RESOLUTIONS } from 'features/controlLayers/store/types';
|
||||
import { Dimensions } from 'features/parameters/components/Dimensions/Dimensions';
|
||||
import { ParamSeed } from 'features/parameters/components/Seed/ParamSeed';
|
||||
import { ParamDuration } from 'features/parameters/components/Video/ParamDuration';
|
||||
import { ParamResolution } from 'features/parameters/components/Video/ParamResolution';
|
||||
import { selectVideoModel, videoResolutionChanged } from 'features/parameters/store/videoSlice';
|
||||
import { VideoDimensions } from 'features/parameters/components/Video/VideoDimensions';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { StartingFrameImage } from './StartingFrameImage';
|
||||
@@ -25,29 +15,6 @@ export const VideoSettingsAccordion = memo(() => {
|
||||
id: 'video-settings',
|
||||
defaultIsOpen: true,
|
||||
});
|
||||
const videoModel = useAppSelector(selectVideoModel);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
// hack to get the default aspect ratio etc for models outside paramsSlice
|
||||
if (videoModel?.base === 'runway') {
|
||||
dispatch(aspectRatioIdChanged({ id: '16:9' }));
|
||||
const { width, height } = RUNWAY_ASPECT_RATIOS['16:9'];
|
||||
dispatch(widthChanged({ width, clamp: true }));
|
||||
dispatch(heightChanged({ height, clamp: true }));
|
||||
dispatch(aspectRatioLockToggled());
|
||||
}
|
||||
|
||||
if (videoModel?.base === 'veo3') {
|
||||
dispatch(aspectRatioIdChanged({ id: '16:9' }));
|
||||
dispatch(videoResolutionChanged('720p'));
|
||||
const { width, height } = VEO3_RESOLUTIONS['720p'];
|
||||
dispatch(widthChanged({ width, clamp: true }));
|
||||
dispatch(heightChanged({ height, clamp: true }));
|
||||
dispatch(aspectRatioLockToggled());
|
||||
}
|
||||
}, [dispatch, videoModel]);
|
||||
|
||||
return (
|
||||
<StandaloneAccordion
|
||||
@@ -57,16 +24,15 @@ export const VideoSettingsAccordion = memo(() => {
|
||||
onToggle={onToggleAccordion}
|
||||
>
|
||||
<Flex p={4} w="full" h="full" flexDir="column" data-testid="upscale-settings-accordion">
|
||||
<Flex gap={4} flexDirection="column" width="full">
|
||||
<Flex gap={1} flexDirection="column" width="full">
|
||||
<Flex gap={4}>
|
||||
<StartingFrameImage />
|
||||
<Flex gap={4} flexDirection="column" width="full">
|
||||
<VideoModelPicker />
|
||||
<ParamDuration />
|
||||
<ParamResolution />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Dimensions />
|
||||
<VideoDimensions />
|
||||
<ParamSeed />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
Reference in New Issue
Block a user