Create standalone browser port of LTX Athena with authenticated native API transport
This commit is contained in:
commit
1333b0c36b
202 files changed
+63054
No files matched your search
@@ -0,0 +1,129 @@
|
||||
import { addVisualAssetToProject } from './asset-copy'
|
||||
import { toModelsDirRelativeRef } from './lora-library'
|
||||
import { logger } from './logger'
|
||||
import type { RecoveryGenType, RecoveryImporter } from './generation-recovery'
|
||||
import { videoGenerationModeFromInputs } from './multi-keyframe'
|
||||
|
||||
// Mirrors GenSpace's own live-generation completion effect (see the `videoPath` effect in
|
||||
// GenSpace.tsx) but operates on the recovery marker's captured context instead of live component
|
||||
// state, so it can run in the background while that project's GenSpace isn't mounted. ic-lora/
|
||||
// retake/extend write a marker with no full `settings` (see GenerationRecoveryContext) and
|
||||
// recover as a standalone video asset here too; retake/extend additionally set `ctx.model` so
|
||||
// the pipeline label isn't forced to 'fast'.
|
||||
const importVideo: RecoveryImporter = async (ctx, result, { addAsset, modelsDir }) => {
|
||||
const videoPath = typeof result === 'string' ? result : result[0]
|
||||
if (!videoPath) return
|
||||
|
||||
const copied = await addVisualAssetToProject(videoPath, ctx.projectId, 'video')
|
||||
if (!copied) throw new Error('Could not persist generated video to project storage')
|
||||
|
||||
const s = ctx.settings
|
||||
const genMode = videoGenerationModeFromInputs({
|
||||
keyframes: ctx.keyframes,
|
||||
audioUrl: ctx.inputAudioUrl,
|
||||
imageUrl: ctx.inputImageUrl,
|
||||
})
|
||||
|
||||
addAsset(ctx.projectId, {
|
||||
type: 'video',
|
||||
path: copied.path,
|
||||
bigThumbnailPath: copied.bigThumbnailPath,
|
||||
smallThumbnailPath: copied.smallThumbnailPath,
|
||||
width: copied.width,
|
||||
height: copied.height,
|
||||
prompt: ctx.prompt,
|
||||
resolution: s?.videoResolution ?? '',
|
||||
duration: s?.duration ?? undefined,
|
||||
generationParams: {
|
||||
mode: genMode,
|
||||
prompt: ctx.prompt,
|
||||
model: ctx.model ?? s?.model ?? 'fast',
|
||||
modelLabel: ctx.modelLabel,
|
||||
duration: s?.duration ?? null,
|
||||
resolution: s?.videoResolution ?? '',
|
||||
fps: s?.fps ?? 24,
|
||||
audio: s?.audio ?? false,
|
||||
cameraMotion: 'none',
|
||||
imageAspectRatio: s?.aspectRatio,
|
||||
imageSteps: 4,
|
||||
inputImageUrl: ctx.inputImageUrl,
|
||||
inputLastImageUrl: ctx.inputLastImageUrl,
|
||||
inputAudioUrl: ctx.inputAudioUrl,
|
||||
keyframes: ctx.keyframes && ctx.keyframes.length > 0 ? ctx.keyframes : undefined,
|
||||
loras: s?.loras && s.loras.length > 0
|
||||
? s.loras.map(l => ({ ref: toModelsDirRelativeRef(l.ref, modelsDir), name: l.name, scale: l.scale }))
|
||||
: undefined,
|
||||
},
|
||||
takes: [{
|
||||
path: copied.path,
|
||||
bigThumbnailPath: copied.bigThumbnailPath,
|
||||
smallThumbnailPath: copied.smallThumbnailPath,
|
||||
width: copied.width,
|
||||
height: copied.height,
|
||||
createdAt: Date.now(),
|
||||
}],
|
||||
activeTakeIndex: 0,
|
||||
})
|
||||
}
|
||||
|
||||
// Mirrors GenSpace's `imagePaths` completion effect. A failed copy for one image in a multi-image
|
||||
// batch is logged and skipped rather than thrown, same as the live effect — one bad file must not
|
||||
// drop the rest of the batch. But if *every* copy in the batch fails, this must throw rather than
|
||||
// return normally: checkAndConsumeRecovery treats a clean return as "imported, delete the
|
||||
// marker" — silently succeeding on a total failure would drop the result for good.
|
||||
const importImage: RecoveryImporter = async (ctx, result, { addAsset }) => {
|
||||
const paths = Array.isArray(result) ? result : [result]
|
||||
const s = ctx.settings
|
||||
const genMode = ctx.inputImageUrl ? 'image-edit' : 'text-to-image'
|
||||
let importedAny = false
|
||||
|
||||
for (const imgPath of paths) {
|
||||
const copied = await addVisualAssetToProject(imgPath, ctx.projectId, 'image')
|
||||
if (!copied) {
|
||||
logger.error(`Could not persist generated image to project storage: ${imgPath}`)
|
||||
continue
|
||||
}
|
||||
importedAny = true
|
||||
addAsset(ctx.projectId, {
|
||||
type: 'image',
|
||||
path: copied.path,
|
||||
bigThumbnailPath: copied.bigThumbnailPath,
|
||||
smallThumbnailPath: copied.smallThumbnailPath,
|
||||
width: copied.width,
|
||||
height: copied.height,
|
||||
prompt: ctx.prompt,
|
||||
resolution: s?.imageResolution ?? '',
|
||||
generationParams: {
|
||||
mode: genMode,
|
||||
prompt: ctx.prompt,
|
||||
model: 'fast',
|
||||
duration: 5,
|
||||
resolution: s?.imageResolution ?? '',
|
||||
fps: 24,
|
||||
audio: false,
|
||||
cameraMotion: 'none',
|
||||
imageAspectRatio: s?.aspectRatio,
|
||||
imageSteps: s?.imageSteps ?? 4,
|
||||
...(ctx.inputImageUrl ? { inputImageUrl: ctx.inputImageUrl, imageEditStrength: s?.imageEditStrength } : {}),
|
||||
},
|
||||
takes: [{
|
||||
path: copied.path,
|
||||
bigThumbnailPath: copied.bigThumbnailPath,
|
||||
smallThumbnailPath: copied.smallThumbnailPath,
|
||||
width: copied.width,
|
||||
height: copied.height,
|
||||
createdAt: Date.now(),
|
||||
}],
|
||||
activeTakeIndex: 0,
|
||||
})
|
||||
}
|
||||
|
||||
if (!importedAny && paths.length > 0) {
|
||||
throw new Error('Could not persist any generated image to project storage')
|
||||
}
|
||||
}
|
||||
|
||||
export const builtinRecoveryImporters: Partial<Record<RecoveryGenType, RecoveryImporter>> = {
|
||||
video: importVideo,
|
||||
image: importImage,
|
||||
}
|
||||
Reference in new issue
Block a user