Browse Source

fix: save video/audio to generations/ and strip imageHistory bloat

Videos and audio were saving to the project root instead of the
generations/ subfolder. Also strips legacy inline base64 image data
from imageHistory items (~4.5MB each) that was inflating workflow
JSON files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
3d204bdf8d
  1. 73
      src/utils/mediaStorage.ts

73
src/utils/mediaStorage.ts

@ -240,12 +240,42 @@ async function externalizeNodeMedia(
}
}
// Strip inline image data from imageHistory items (legacy bloat)
// Each history item may carry a full base64 `image` field (~4.5MB each)
// The `image` field isn't in the CarouselImageItem type but exists at runtime
let cleanedHistory = d.imageHistory;
if (d.imageHistory?.length) {
cleanedHistory = [];
for (const item of d.imageHistory) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const legacyImage = (item as any).image as string | undefined;
if (legacyImage && isDataUrl(legacyImage)) {
// Save history image to generations if not already saved
await saveImageAndGetId(
legacyImage,
workflowPath, savedImageIds, "generations", item.id
);
// Push clean item with only typed fields (strips legacy `image`)
cleanedHistory.push({
id: item.id,
timestamp: item.timestamp,
prompt: item.prompt,
aspectRatio: item.aspectRatio,
model: item.model,
});
} else {
cleanedHistory.push(item);
}
}
}
newData = {
...d,
inputImages: inputImages.length > 0 && inputImages.every(i => i === "") ? [] : inputImages,
inputImageRefs: inputImageRefs.length > 0 ? inputImageRefs : undefined,
outputImage,
outputImageRef,
imageHistory: cleanedHistory,
};
break;
}
@ -613,7 +643,8 @@ async function saveVideoAndGetRef(
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
directoryPath: workflowPath,
directoryPath: `${workflowPath}/generations`,
createDirectory: true,
imageId: videoId,
video: videoData,
}),
@ -680,7 +711,8 @@ async function saveAudioAndGetRef(
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
directoryPath: workflowPath,
directoryPath: `${workflowPath}/generations`,
createDirectory: true,
imageId: audioId,
audio: audioData,
}),
@ -1038,27 +1070,48 @@ async function loadMediaById(
response = await fetch(`/api/workflow-images?${params.toString()}`);
} else {
// Use load-generation API for videos and audio
// Try generations/ subfolder first, fall back to root for legacy files
response = await fetch("/api/load-generation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
directoryPath: workflowPath,
directoryPath: `${workflowPath}/generations`,
imageId: mediaId,
}),
});
let genResult = await response.json();
// Fallback to root directory for legacy files saved before generations/ fix
if (!genResult.success && genResult.notFound) {
response = await fetch("/api/load-generation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
directoryPath: workflowPath,
imageId: mediaId,
}),
});
genResult = await response.json();
}
const mediaData = mediaType === "video" ? genResult.video : genResult.audio;
if (!genResult.success) {
console.log(`${mediaType} not found: ${mediaId}`);
return "";
}
loadedMedia.set(mediaId, mediaData);
return mediaData;
}
// Only images reach here (video/audio return early above)
const result = await response.json();
if (!result.success) {
// Missing media is expected when refs point to deleted/moved files
console.log(`${mediaType} not found: ${mediaId}`);
return ""; // Return empty string to avoid breaking the workflow
return "";
}
const mediaData = mediaType === "image" ? result.image
: mediaType === "video" ? result.video
: result.audio;
loadedMedia.set(mediaId, mediaData);
return mediaData;
loadedMedia.set(mediaId, result.image);
return result.image;
}

Loading…
Cancel
Save