diff --git a/electron/ipc/register/assets.ts b/electron/ipc/register/assets.ts index a0b132ea2..649c74f70 100644 --- a/electron/ipc/register/assets.ts +++ b/electron/ipc/register/assets.ts @@ -116,11 +116,28 @@ export function registerAssetHandlers() { // require a real on-disk file so this cannot be used to read directories. const resolved = await resolveReadableLocalFilePath(filePath) - const data = await fs.readFile(resolved) - return { success: true, data } + // Use fileHandle.read instead of fs.readFile to support files >2 GiB. + // fs.readFile throws ERR_FS_FILE_TOO_LARGE on large files, but + // fileHandle.read has no such restriction. + const fileHandle = await fs.open(resolved, 'r') + try { + const stat = await fileHandle.stat() + const fileSize = stat.size + const buffer = Buffer.allocUnsafe(fileSize) + let offset = 0 + while (offset < fileSize) { + const chunkSize = Math.min(64 * 1024 * 1024, fileSize - offset) // 64 MiB chunks + const { bytesRead } = await fileHandle.read(buffer, offset, chunkSize, offset) + if (bytesRead === 0) break + offset += bytesRead + } + return { success: true as const, data: offset < fileSize ? buffer.slice(0, offset) : buffer } + } finally { + await fileHandle.close() + } } catch (error) { console.error('Failed to read local file:', error) - return { success: false, error: String(error) } + return { success: false as const, error: String(error) } } }) diff --git a/src/lib/exporter/localMediaSource.ts b/src/lib/exporter/localMediaSource.ts index 4f5a1b57a..7e9cdaed2 100644 --- a/src/lib/exporter/localMediaSource.ts +++ b/src/lib/exporter/localMediaSource.ts @@ -122,16 +122,16 @@ export async function createReadableMediaResourceFile(resource: string): Promise if (localFilePath && typeof window !== "undefined" && window.electronAPI?.readLocalFile) { const result = await window.electronAPI.readLocalFile(localFilePath); - if (!result.success || !result.data) { - throw new Error(result.error || "Failed to read local media file"); + if (result.success && result.data) { + const bytes = + result.data instanceof Uint8Array ? result.data : new Uint8Array(result.data); + const arrayBuffer = bytes.buffer.slice( + bytes.byteOffset, + bytes.byteOffset + bytes.byteLength, + ) as ArrayBuffer; + return new File([arrayBuffer], filename, { type: inferMimeType(filename) }); } - - const bytes = result.data instanceof Uint8Array ? result.data : new Uint8Array(result.data); - const arrayBuffer = bytes.buffer.slice( - bytes.byteOffset, - bytes.byteOffset + bytes.byteLength, - ) as ArrayBuffer; - return new File([arrayBuffer], filename, { type: inferMimeType(filename) }); + // readLocalFile failed — fall through to URL-based fetch } const resourceUrl = await resolveMediaResourceUrl(resource);