Create standalone browser port of LTX Athena with authenticated native API transport
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
import { z } from 'zod'
|
||||
import { clampKeyframeStrength } from '../lib/keyframe-strength.ts'
|
||||
|
||||
export const generationModeValues = [
|
||||
'text-to-video',
|
||||
'image-to-video',
|
||||
'audio-to-video',
|
||||
'multi-keyframe',
|
||||
'text-to-image',
|
||||
'image-edit',
|
||||
'retake',
|
||||
'extend',
|
||||
'ic-lora',
|
||||
] as const
|
||||
|
||||
export const assetTypeValues = ['image', 'video', 'audio', 'adjustment'] as const
|
||||
export const timelineClipTypeValues = [...assetTypeValues, 'text'] as const
|
||||
export const transitionTypeValues = [
|
||||
'none',
|
||||
'dissolve',
|
||||
'fade-to-black',
|
||||
'fade-to-white',
|
||||
'wipe-left',
|
||||
'wipe-right',
|
||||
'wipe-up',
|
||||
'wipe-down',
|
||||
] as const
|
||||
export const trackTypeValues = ['default', 'subtitle'] as const
|
||||
export const trackKindValues = ['video', 'audio'] as const
|
||||
export const subtitlePositionValues = ['bottom', 'top', 'center'] as const
|
||||
export const fontWeightValues = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'] as const
|
||||
export const fontStyleValues = ['normal', 'italic'] as const
|
||||
export const textAlignValues = ['left', 'center', 'right'] as const
|
||||
export const effectTypeValues = [
|
||||
'blur',
|
||||
'sharpen',
|
||||
'glow',
|
||||
'vignette',
|
||||
'grain',
|
||||
'lut-cinematic',
|
||||
'lut-vintage',
|
||||
'lut-bw',
|
||||
'lut-cool',
|
||||
'lut-warm',
|
||||
'lut-muted',
|
||||
'lut-vivid',
|
||||
] as const
|
||||
export const effectMaskShapeValues = ['rectangle', 'ellipse'] as const
|
||||
export const letterboxAspectRatioValues = ['2.35:1', '2.39:1', '2.76:1', '1.85:1', '4:3', 'custom'] as const
|
||||
export const viewTypeValues = ['home', 'project'] as const
|
||||
export const projectTabValues = ['gen-space', 'video-editor'] as const
|
||||
|
||||
export const transitionTypeSchema = z.enum(transitionTypeValues)
|
||||
export const viewTypeSchema = z.enum(viewTypeValues)
|
||||
export const projectTabSchema = z.enum(projectTabValues)
|
||||
|
||||
export const persistedKeyframeSchema = z.object({
|
||||
path: z.string(),
|
||||
frameIndex: z.number(),
|
||||
// Missing on #154 assets. Out of range is clamped so a bad float cannot fail the project.
|
||||
strength: z.unknown().optional().transform(clampKeyframeStrength),
|
||||
})
|
||||
|
||||
export const generationParamsSchema = z.object({
|
||||
mode: z.enum(generationModeValues),
|
||||
prompt: z.string(),
|
||||
model: z.string(),
|
||||
// Local pipeline ids ("fast") are shared across LTX versions, so `model` alone can't say
|
||||
// which version produced the asset. Captured at generation time from the backend spec's
|
||||
// display name; absent on assets written before this existed.
|
||||
modelLabel: z.string().optional(),
|
||||
duration: z.number().nullable(),
|
||||
resolution: z.string(),
|
||||
fps: z.number(),
|
||||
audio: z.boolean(),
|
||||
cameraMotion: z.string(),
|
||||
imageAspectRatio: z.string().optional(),
|
||||
imageSteps: z.number().optional(),
|
||||
imageEditStrength: z.number().optional(),
|
||||
inputImageUrl: z.string().optional(),
|
||||
inputLastImageUrl: z.string().optional(),
|
||||
inputAudioUrl: z.string().optional(),
|
||||
keyframes: z.array(persistedKeyframeSchema).optional(),
|
||||
retakeVideoPath: z.string().optional(),
|
||||
retakeStartTime: z.number().optional(),
|
||||
retakeDuration: z.number().optional(),
|
||||
retakeMode: z.string().optional(),
|
||||
extendVideoPath: z.string().optional(),
|
||||
extendDuration: z.number().optional(),
|
||||
extendDirection: z.string().optional(),
|
||||
icLoraVideoPath: z.string().optional(),
|
||||
icLoraConditioningType: z.string().optional(),
|
||||
icLoraConditioningStrength: z.number().optional(),
|
||||
loras: z.array(z.object({
|
||||
ref: z.string(),
|
||||
name: z.string(),
|
||||
scale: z.number(),
|
||||
})).optional(),
|
||||
})
|
||||
|
||||
export const assetTakeSchema = z.object({
|
||||
path: z.string(),
|
||||
bigThumbnailPath: z.string().optional(),
|
||||
smallThumbnailPath: z.string().optional(),
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
createdAt: z.number(),
|
||||
})
|
||||
|
||||
export const subtitleStyleSchema = z.object({
|
||||
fontSize: z.number(),
|
||||
fontFamily: z.string(),
|
||||
fontWeight: z.enum(['normal', 'bold']),
|
||||
color: z.string(),
|
||||
backgroundColor: z.string(),
|
||||
position: z.enum(subtitlePositionValues),
|
||||
italic: z.boolean(),
|
||||
})
|
||||
|
||||
export const DEFAULT_SUBTITLE_STYLE = subtitleStyleSchema.parse({
|
||||
fontSize: 32,
|
||||
fontFamily: 'sans-serif',
|
||||
fontWeight: 'normal',
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: 'transparent',
|
||||
position: 'bottom',
|
||||
italic: false,
|
||||
})
|
||||
|
||||
export const trackSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
muted: z.boolean(),
|
||||
locked: z.boolean(),
|
||||
solo: z.boolean().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
sourcePatched: z.boolean().optional(),
|
||||
type: z.enum(trackTypeValues).optional(),
|
||||
kind: z.enum(trackKindValues).optional(),
|
||||
subtitleStyle: subtitleStyleSchema.partial().optional(),
|
||||
})
|
||||
|
||||
export const DEFAULT_TRACKS = trackSchema.array().parse([
|
||||
{ id: 'track-v1', name: 'V1', muted: false, locked: false, sourcePatched: true, kind: 'video' },
|
||||
{ id: 'track-v2', name: 'V2', muted: false, locked: false, sourcePatched: false, kind: 'video' },
|
||||
{ id: 'track-v3', name: 'V3', muted: false, locked: false, sourcePatched: false, kind: 'video' },
|
||||
{ id: 'track-a1', name: 'A1', muted: false, locked: false, sourcePatched: true, kind: 'audio' },
|
||||
{ id: 'track-a2', name: 'A2', muted: false, locked: false, sourcePatched: false, kind: 'audio' },
|
||||
])
|
||||
|
||||
export const subtitleClipSchema = z.object({
|
||||
id: z.string(),
|
||||
text: z.string(),
|
||||
startTime: z.number(),
|
||||
endTime: z.number(),
|
||||
trackIndex: z.number(),
|
||||
style: subtitleStyleSchema.partial().optional(),
|
||||
})
|
||||
|
||||
export const clipTransitionSchema = z.object({
|
||||
type: transitionTypeSchema,
|
||||
duration: z.number(),
|
||||
})
|
||||
|
||||
export const DEFAULT_CLIP_TRANSITION = clipTransitionSchema.parse({
|
||||
type: 'none',
|
||||
duration: 0.5,
|
||||
})
|
||||
|
||||
export const colorCorrectionSchema = z.object({
|
||||
brightness: z.number(),
|
||||
contrast: z.number(),
|
||||
saturation: z.number(),
|
||||
temperature: z.number(),
|
||||
tint: z.number(),
|
||||
exposure: z.number(),
|
||||
highlights: z.number(),
|
||||
shadows: z.number(),
|
||||
})
|
||||
|
||||
export const DEFAULT_COLOR_CORRECTION = colorCorrectionSchema.parse({
|
||||
brightness: 0,
|
||||
contrast: 0,
|
||||
saturation: 0,
|
||||
temperature: 0,
|
||||
tint: 0,
|
||||
exposure: 0,
|
||||
highlights: 0,
|
||||
shadows: 0,
|
||||
})
|
||||
|
||||
export const letterboxSettingsSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
aspectRatio: z.enum(letterboxAspectRatioValues),
|
||||
customRatio: z.number().optional(),
|
||||
color: z.string(),
|
||||
opacity: z.number(),
|
||||
})
|
||||
|
||||
export const DEFAULT_LETTERBOX = letterboxSettingsSchema.parse({
|
||||
enabled: false,
|
||||
aspectRatio: '2.35:1',
|
||||
color: '#000000',
|
||||
opacity: 100,
|
||||
})
|
||||
|
||||
export const effectMaskSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
shape: z.enum(effectMaskShapeValues),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
feather: z.number(),
|
||||
invert: z.boolean(),
|
||||
rotation: z.number(),
|
||||
})
|
||||
|
||||
export const DEFAULT_EFFECT_MASK = effectMaskSchema.parse({
|
||||
enabled: false,
|
||||
shape: 'ellipse',
|
||||
x: 50,
|
||||
y: 50,
|
||||
width: 40,
|
||||
height: 40,
|
||||
feather: 20,
|
||||
invert: false,
|
||||
rotation: 0,
|
||||
})
|
||||
|
||||
export const clipEffectSchema = z.object({
|
||||
id: z.string(),
|
||||
type: z.enum(effectTypeValues),
|
||||
enabled: z.boolean(),
|
||||
params: z.record(z.string(), z.number()),
|
||||
mask: effectMaskSchema.optional(),
|
||||
})
|
||||
|
||||
export const textOverlayStyleSchema = z.object({
|
||||
text: z.string(),
|
||||
fontFamily: z.string(),
|
||||
fontSize: z.number(),
|
||||
fontWeight: z.enum(fontWeightValues),
|
||||
fontStyle: z.enum(fontStyleValues),
|
||||
color: z.string(),
|
||||
backgroundColor: z.string(),
|
||||
textAlign: z.enum(textAlignValues),
|
||||
positionX: z.number(),
|
||||
positionY: z.number(),
|
||||
strokeColor: z.string(),
|
||||
strokeWidth: z.number(),
|
||||
shadowColor: z.string(),
|
||||
shadowBlur: z.number(),
|
||||
shadowOffsetX: z.number(),
|
||||
shadowOffsetY: z.number(),
|
||||
letterSpacing: z.number(),
|
||||
lineHeight: z.number(),
|
||||
maxWidth: z.number(),
|
||||
padding: z.number(),
|
||||
borderRadius: z.number(),
|
||||
opacity: z.number(),
|
||||
})
|
||||
|
||||
export const DEFAULT_TEXT_STYLE = textOverlayStyleSchema.parse({
|
||||
text: 'Title Text',
|
||||
fontFamily: 'Inter, Arial, sans-serif',
|
||||
fontSize: 64,
|
||||
fontWeight: 'bold',
|
||||
fontStyle: 'normal',
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: 'center',
|
||||
positionX: 50,
|
||||
positionY: 50,
|
||||
strokeColor: 'transparent',
|
||||
strokeWidth: 0,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 2,
|
||||
letterSpacing: 0,
|
||||
lineHeight: 1.2,
|
||||
maxWidth: 80,
|
||||
padding: 0,
|
||||
borderRadius: 0,
|
||||
opacity: 100,
|
||||
})
|
||||
|
||||
export const assetSchema = z.object({
|
||||
id: z.string(),
|
||||
type: z.enum(assetTypeValues),
|
||||
path: z.string(),
|
||||
bigThumbnailPath: z.string().optional(),
|
||||
smallThumbnailPath: z.string().optional(),
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
prompt: z.string(),
|
||||
resolution: z.string(),
|
||||
duration: z.number().optional(),
|
||||
createdAt: z.number(),
|
||||
favorite: z.boolean().optional(),
|
||||
binId: z.string().optional(),
|
||||
generationParams: generationParamsSchema.optional(),
|
||||
takes: z.array(assetTakeSchema).optional(),
|
||||
activeTakeIndex: z.number().optional(),
|
||||
colorLabel: z.string().optional(),
|
||||
})
|
||||
|
||||
export const timelineClipSchema = z.object({
|
||||
id: z.string(),
|
||||
assetId: z.string().nullable(),
|
||||
type: z.enum(timelineClipTypeValues),
|
||||
startTime: z.number(),
|
||||
duration: z.number(),
|
||||
trimStart: z.number(),
|
||||
trimEnd: z.number(),
|
||||
speed: z.number().default(1),
|
||||
reversed: z.boolean().default(false),
|
||||
muted: z.boolean().default(false),
|
||||
volume: z.number().default(100),
|
||||
trackIndex: z.number(),
|
||||
asset: assetSchema.nullable(),
|
||||
importedName: z.string().optional(),
|
||||
flipH: z.boolean().default(false),
|
||||
flipV: z.boolean().default(false),
|
||||
transitionIn: clipTransitionSchema.default(DEFAULT_CLIP_TRANSITION),
|
||||
transitionOut: clipTransitionSchema.default(DEFAULT_CLIP_TRANSITION),
|
||||
colorCorrection: colorCorrectionSchema.default(DEFAULT_COLOR_CORRECTION),
|
||||
opacity: z.number().default(100),
|
||||
takeIndex: z.number().optional(),
|
||||
isRegenerating: z.boolean().optional(),
|
||||
linkedClipIds: z.array(z.string()).optional(),
|
||||
colorLabel: z.string().optional(),
|
||||
effects: z.array(clipEffectSchema).optional(),
|
||||
letterbox: letterboxSettingsSchema.optional(),
|
||||
textStyle: textOverlayStyleSchema.optional(),
|
||||
})
|
||||
|
||||
export const timelineSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
createdAt: z.number(),
|
||||
tracks: z.array(trackSchema),
|
||||
clips: z.array(timelineClipSchema),
|
||||
subtitles: z.array(subtitleClipSchema).default([]),
|
||||
})
|
||||
|
||||
export const assetBinsSchema = z.record(z.string(), z.string())
|
||||
|
||||
export const projectV2Schema = z.object({
|
||||
version: z.literal(2),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
bins: assetBinsSchema,
|
||||
assets: z.array(assetSchema),
|
||||
timelines: z.array(timelineSchema),
|
||||
activeTimelineId: z.string().optional(),
|
||||
})
|
||||
|
||||
const assetV1Schema = assetSchema
|
||||
.omit({ binId: true })
|
||||
.extend({
|
||||
bin: z.string().optional(),
|
||||
})
|
||||
|
||||
export const projectV1Schema = projectV2Schema
|
||||
.omit({ version: true, bins: true, assets: true, timelines: true })
|
||||
.extend({
|
||||
version: z.undefined().optional(),
|
||||
bins: z.undefined().optional(),
|
||||
assets: z.array(assetV1Schema),
|
||||
timelines: z.array(timelineSchema).optional(),
|
||||
})
|
||||
|
||||
export const projectSchema = projectV2Schema
|
||||
|
||||
export const projectReferenceSchema = z.object({
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
const projectVersionSchema = z.object({
|
||||
version: z.unknown().optional(),
|
||||
})
|
||||
|
||||
export type GenerationParams = z.infer<typeof generationParamsSchema>
|
||||
export type AssetTake = z.infer<typeof assetTakeSchema>
|
||||
export type Asset = z.infer<typeof assetSchema>
|
||||
export type Track = z.infer<typeof trackSchema>
|
||||
export type SubtitleStyle = z.infer<typeof subtitleStyleSchema>
|
||||
export type SubtitleClip = z.infer<typeof subtitleClipSchema>
|
||||
export type TransitionType = z.infer<typeof transitionTypeSchema>
|
||||
export type ClipTransition = z.infer<typeof clipTransitionSchema>
|
||||
export type ColorCorrection = z.infer<typeof colorCorrectionSchema>
|
||||
export type LetterboxSettings = z.infer<typeof letterboxSettingsSchema>
|
||||
export type EffectMask = z.infer<typeof effectMaskSchema>
|
||||
export type EffectType = z.infer<typeof clipEffectSchema.shape.type>
|
||||
export type ClipEffect = z.infer<typeof clipEffectSchema>
|
||||
export type TextOverlayStyle = z.infer<typeof textOverlayStyleSchema>
|
||||
export type TimelineClip = z.infer<typeof timelineClipSchema>
|
||||
export type Timeline = z.infer<typeof timelineSchema>
|
||||
export type AssetBins = z.infer<typeof assetBinsSchema>
|
||||
export type ProjectV1 = z.infer<typeof projectV1Schema>
|
||||
export type ProjectV2 = z.infer<typeof projectV2Schema>
|
||||
export type Project = ProjectV2
|
||||
export type ViewType = z.infer<typeof viewTypeSchema>
|
||||
export type ProjectTab = z.infer<typeof projectTabSchema>
|
||||
|
||||
export function createAssetBinId(): string {
|
||||
return `bin-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`
|
||||
}
|
||||
|
||||
export function createDefaultTimeline(name: string = 'Timeline 1'): Timeline {
|
||||
return {
|
||||
id: `timeline-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
||||
name,
|
||||
createdAt: Date.now(),
|
||||
tracks: DEFAULT_TRACKS.map(track => ({ ...track })),
|
||||
clips: [],
|
||||
subtitles: [],
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLegacyBinName(bin: string | undefined): string | null {
|
||||
const normalized = bin?.trim()
|
||||
return normalized ? normalized : null
|
||||
}
|
||||
|
||||
function migrateProjectV1ToV2(project: ProjectV1): ProjectV2 {
|
||||
const bins = Object.fromEntries(
|
||||
Array.from(new Set(project.assets.flatMap(asset => {
|
||||
const binName = normalizeLegacyBinName(asset.bin)
|
||||
return binName ? [binName] : []
|
||||
}))).map(binName => [createAssetBinId(), binName]),
|
||||
)
|
||||
const binNameToId = new Map<string, string>(
|
||||
Object.entries(bins).map(([binId, binName]) => [binName, binId]),
|
||||
)
|
||||
|
||||
return projectV2Schema.parse({
|
||||
...project,
|
||||
version: 2,
|
||||
bins,
|
||||
assets: project.assets.map(({ bin, ...asset }) => {
|
||||
const binName = normalizeLegacyBinName(bin)
|
||||
const binId = binName ? binNameToId.get(binName) : undefined
|
||||
return binId ? { ...asset, binId } : asset
|
||||
}),
|
||||
timelines: project.timelines ?? [createDefaultTimeline('Timeline 1')],
|
||||
activeTimelineId: project.timelines ? project.activeTimelineId : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
export function migrateProjectData(projectData: unknown): { project: Project; migrated: boolean } {
|
||||
const { version } = projectVersionSchema.parse(projectData)
|
||||
|
||||
if (version === undefined) {
|
||||
return {
|
||||
project: migrateProjectV1ToV2(projectV1Schema.parse(projectData)),
|
||||
migrated: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (version === 2) {
|
||||
return {
|
||||
project: projectV2Schema.parse(projectData),
|
||||
migrated: false,
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported project version: ${String(version)}`)
|
||||
}
|
||||
|
||||
export function normalizeProject(projectData: unknown): Project {
|
||||
return migrateProjectData(projectData).project
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { EffectType, TextOverlayStyle } from './project-model'
|
||||
|
||||
export interface EffectParamDef {
|
||||
min: number
|
||||
max: number
|
||||
step: number
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface EffectDefinition {
|
||||
name: string
|
||||
category: 'filter' | 'stylize' | 'color-preset'
|
||||
icon: string
|
||||
defaultParams: Record<string, number>
|
||||
paramRanges: Record<string, EffectParamDef>
|
||||
}
|
||||
|
||||
export const EFFECT_DEFINITIONS: Record<EffectType, EffectDefinition> = {
|
||||
'blur': {
|
||||
name: 'Gaussian Blur',
|
||||
category: 'filter',
|
||||
icon: 'Droplets',
|
||||
defaultParams: { amount: 5 },
|
||||
paramRanges: { amount: { min: 0, max: 50, step: 0.5, label: 'Radius' } },
|
||||
},
|
||||
'sharpen': {
|
||||
name: 'Sharpen',
|
||||
category: 'filter',
|
||||
icon: 'Diamond',
|
||||
defaultParams: { amount: 50 },
|
||||
paramRanges: { amount: { min: 0, max: 100, step: 1, label: 'Amount' } },
|
||||
},
|
||||
'glow': {
|
||||
name: 'Glow',
|
||||
category: 'stylize',
|
||||
icon: 'Sun',
|
||||
defaultParams: { amount: 30, radius: 10 },
|
||||
paramRanges: {
|
||||
amount: { min: 0, max: 100, step: 1, label: 'Intensity' },
|
||||
radius: { min: 0, max: 50, step: 1, label: 'Radius' },
|
||||
},
|
||||
},
|
||||
'vignette': {
|
||||
name: 'Vignette',
|
||||
category: 'stylize',
|
||||
icon: 'Circle',
|
||||
defaultParams: { amount: 50 },
|
||||
paramRanges: { amount: { min: 0, max: 100, step: 1, label: 'Amount' } },
|
||||
},
|
||||
'grain': {
|
||||
name: 'Film Grain',
|
||||
category: 'stylize',
|
||||
icon: 'Scan',
|
||||
defaultParams: { amount: 30 },
|
||||
paramRanges: { amount: { min: 0, max: 100, step: 1, label: 'Amount' } },
|
||||
},
|
||||
'lut-cinematic': {
|
||||
name: 'Cinematic',
|
||||
category: 'color-preset',
|
||||
icon: 'Film',
|
||||
defaultParams: { intensity: 100 },
|
||||
paramRanges: { intensity: { min: 0, max: 100, step: 1, label: 'Intensity' } },
|
||||
},
|
||||
'lut-vintage': {
|
||||
name: 'Vintage',
|
||||
category: 'color-preset',
|
||||
icon: 'Clock',
|
||||
defaultParams: { intensity: 100 },
|
||||
paramRanges: { intensity: { min: 0, max: 100, step: 1, label: 'Intensity' } },
|
||||
},
|
||||
'lut-bw': {
|
||||
name: 'Black & White',
|
||||
category: 'color-preset',
|
||||
icon: 'Contrast',
|
||||
defaultParams: { intensity: 100 },
|
||||
paramRanges: { intensity: { min: 0, max: 100, step: 1, label: 'Intensity' } },
|
||||
},
|
||||
'lut-cool': {
|
||||
name: 'Cool Tone',
|
||||
category: 'color-preset',
|
||||
icon: 'Snowflake',
|
||||
defaultParams: { intensity: 100 },
|
||||
paramRanges: { intensity: { min: 0, max: 100, step: 1, label: 'Intensity' } },
|
||||
},
|
||||
'lut-warm': {
|
||||
name: 'Warm Tone',
|
||||
category: 'color-preset',
|
||||
icon: 'Flame',
|
||||
defaultParams: { intensity: 100 },
|
||||
paramRanges: { intensity: { min: 0, max: 100, step: 1, label: 'Intensity' } },
|
||||
},
|
||||
'lut-muted': {
|
||||
name: 'Muted',
|
||||
category: 'color-preset',
|
||||
icon: 'CloudFog',
|
||||
defaultParams: { intensity: 100 },
|
||||
paramRanges: { intensity: { min: 0, max: 100, step: 1, label: 'Intensity' } },
|
||||
},
|
||||
'lut-vivid': {
|
||||
name: 'Vivid',
|
||||
category: 'color-preset',
|
||||
icon: 'Palette',
|
||||
defaultParams: { intensity: 100 },
|
||||
paramRanges: { intensity: { min: 0, max: 100, step: 1, label: 'Intensity' } },
|
||||
},
|
||||
}
|
||||
|
||||
export interface TextPreset {
|
||||
id: string
|
||||
name: string
|
||||
category: 'titles' | 'lower-thirds' | 'captions' | 'end-cards'
|
||||
style: Partial<TextOverlayStyle>
|
||||
}
|
||||
|
||||
export const TEXT_PRESETS: TextPreset[] = [
|
||||
{ id: 'centered-title', name: 'Centered Title', category: 'titles', style: { text: 'Title', fontSize: 72, fontWeight: 'bold', positionX: 50, positionY: 50, textAlign: 'center' } },
|
||||
{ id: 'big-bold', name: 'Big & Bold', category: 'titles', style: { text: 'HEADLINE', fontSize: 96, fontWeight: '900', positionX: 50, positionY: 45, textAlign: 'center', letterSpacing: 4 } },
|
||||
{ id: 'subtitle-style', name: 'Subtitle', category: 'captions', style: { text: 'Subtitle text', fontSize: 36, fontWeight: 'normal', positionX: 50, positionY: 88, textAlign: 'center', backgroundColor: 'rgba(0,0,0,0.6)', padding: 8, borderRadius: 4 } },
|
||||
{ id: 'lower-third-basic', name: 'Lower Third', category: 'lower-thirds', style: { text: 'Name Here', fontSize: 32, fontWeight: '600', positionX: 10, positionY: 82, textAlign: 'left', backgroundColor: 'rgba(0,0,0,0.7)', padding: 12, borderRadius: 6, maxWidth: 40 } },
|
||||
{ id: 'lower-third-accent', name: 'Accent Lower Third', category: 'lower-thirds', style: { text: 'Speaker Name', fontSize: 28, fontWeight: '500', positionX: 8, positionY: 85, textAlign: 'left', color: '#FFFFFF', backgroundColor: 'rgba(124,58,237,0.85)', padding: 10, borderRadius: 4, maxWidth: 35 } },
|
||||
{ id: 'end-card', name: 'End Card', category: 'end-cards', style: { text: 'Thank You', fontSize: 80, fontWeight: '300', positionX: 50, positionY: 45, textAlign: 'center', letterSpacing: 8, color: '#E4E4E7' } },
|
||||
{ id: 'corner-tag', name: 'Corner Tag', category: 'captions', style: { text: 'LIVE', fontSize: 20, fontWeight: '700', positionX: 92, positionY: 8, textAlign: 'right', color: '#FFFFFF', backgroundColor: 'rgba(239,68,68,0.9)', padding: 6, borderRadius: 4 } },
|
||||
]
|
||||
Reference in New Issue
Block a user