fix: 3 bugs in Electron export pipeline

Bug 1: RENDERS_DIR mismatch (root cause)
- server.ts had hardcoded renders dir, now reads BRADLY_RENDERS_DIR env
- renderQueue.ts saves to ~/Library/.../Bradly/renders/
- server.ts now serves from the same directory

Bug 2: Upload origin pointing to wrong port
- uploadBlobContent.ts used window.location.origin (Vite 5173)
- Remotion bundler needs Express origin (3000) to access media
- Added getExpressOrigin() helper that detects Electron

Bug 3: Batch ZIP export using file-saver (doesn't work in Electron)
- Added saveBlobFile IPC method (preload + main)
- batchExporter.ts now uses native save dialog in Electron
- Web mode falls back to file-saver
This commit is contained in:
2026-06-02 04:44:25 -05:00
parent 25587ab07f
commit 7c4196475c
5 changed files with 61 additions and 4 deletions
+13 -1
View File
@@ -198,7 +198,19 @@ export async function exportBatchAsZip(
.replace(/\s+/g, '_')
.replace(/[^a-zA-Z0-9._-]/g, '');
saveAs(zipBlob, zipName);
// In Electron, use native save dialog
const electronAPI = (typeof window !== 'undefined') ? (window as any).electronAPI : null;
if (electronAPI?.saveBlobFile) {
const arrayBuffer = await zipBlob.arrayBuffer();
await electronAPI.saveBlobFile(
new Uint8Array(arrayBuffer),
zipName,
[{ name: 'ZIP Archive', extensions: ['zip'] }],
);
} else {
// Web fallback
saveAs(zipBlob, zipName);
}
onProgress?.({ current: total, total, status: 'done' });
}
+18 -2
View File
@@ -27,6 +27,23 @@ async function blobUrlToFile(blobUrl: string, fallbackName: string): Promise<Fil
return new File([blob], `${fallbackName}${ext}`, { type: blob.type });
}
/**
* Get the Express server origin for building absolute media URLs.
* In Electron dev mode, the renderer runs on Vite (5173) but media
* is served by Express (3000). Remotion's bundler needs Express URLs.
*/
function getExpressOrigin(): string {
if (typeof window !== 'undefined' && (window as any).electronAPI?.isElectron) {
// In Electron, Express is always on 127.0.0.1:3000
return 'http://127.0.0.1:3000';
}
// In web mode, Express IS the origin
if (typeof window !== 'undefined') {
return window.location.origin;
}
return 'http://localhost:3000';
}
/**
* Upload a single File to the server and return an absolute persistent URL.
* Must be absolute because Remotion's server-side bundler runs on a different
@@ -44,8 +61,7 @@ async function uploadFile(file: File): Promise<string> {
}
const data = await res.json();
// Return absolute URL so Remotion's bundler (different port) can reach it
const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000';
return `${origin}${data.url}`;
return `${getExpressOrigin()}${data.url}`;
}
/**