diff --git a/electron/ipcManager.ts b/electron/ipcManager.ts index 1aca36f..f59779f 100644 --- a/electron/ipcManager.ts +++ b/electron/ipcManager.ts @@ -6,7 +6,7 @@ import { getAvailableProfiles } from "./lib/cookie"; import { encodeJson } from "./lib/json"; import { getMetadata } from "./lib/niconico"; import { - appendBuffers, + appendFrame, appendQueue, markAsCompleted, processOnLoad, @@ -18,10 +18,10 @@ const registerListener = (): void => { ipcMain.handle("request", async (IpcMainEvent, args) => { try { const value = (args as { data: unknown[] }).data[0]; - if (typeGuard.renderer.buffer(value)) { - appendBuffers(value.data); + if (typeGuard.renderer.blob(value)) { + appendFrame(value.frameId, value.data); } else if (typeGuard.renderer.end(value)) { - markAsCompleted(); + markAsCompleted(value.frameId); } else if (typeGuard.controller.selectComment(value)) { return await selectComment(); } else if (typeGuard.controller.selectMovie(value)) { diff --git a/electron/queue.ts b/electron/queue.ts index 90fce21..25794e9 100644 --- a/electron/queue.ts +++ b/electron/queue.ts @@ -9,7 +9,6 @@ import { inputStream, startConverter } from "./converter"; import { encodeJson } from "./lib/json"; import { download, downloadComment } from "./lib/niconico"; import { createRendererWindow, sendMessageToRenderer } from "./rendererWindow"; -import { base64ToUint8Array } from "./utils"; const queueList: Queue[] = []; const queueLists: QueueLists = { @@ -19,6 +18,9 @@ const queueLists: QueueLists = { }; let convertQueue = Promise.resolve(); let processingQueue: ConvertQueue; +let lastFrame = 0; +let endFrame = -1; +const frameQueue: { [key: number]: Uint8Array } = {}; const appendQueue = (queue: Queue): void => { queueList.push(queue); if (queue.type === "convert") { @@ -126,6 +128,7 @@ const startConvert = async (): Promise => { processingQueue = queued[0]; processingQueue.status = "processing"; processingQueue.progress = 0; + lastFrame = 0; createRendererWindow(); sendProgress(); await startConverter(queued[0]); @@ -136,32 +139,45 @@ const startConvert = async (): Promise => { sendProgress(); void startConvert(); }; -const appendBuffers = (blobs: string[]): void => { - for (const item of blobs) { - const base64Image = item.split(";base64,").pop(); - if (!base64Image) continue; - convertQueue = convertQueue.then(() => - new Promise((fulfill, reject) => { - const myStream = new Stream.Readable(); - myStream._read = () => { - const u8 = base64ToUint8Array(base64Image); - myStream.push(u8); - myStream.push(null); - }; - processingQueue.progress++; - sendProgress(); - return myStream - .on("end", () => fulfill()) - .on("error", () => reject()) - .pipe(inputStream, { end: false }); - }).catch((e) => { - console.warn(e); - }), - ); + +const appendFrame = (frameId: number, data: Uint8Array): void => { + frameQueue[frameId] = data; + console.log("receive: ", frameId, lastFrame, endFrame); + if (frameId !== lastFrame + 1) { + return; + } + while (frameQueue[lastFrame + 1]) { + lastFrame++; + console.log("process: ", frameId, lastFrame, endFrame); + processFrame(frameQueue[lastFrame]); + delete frameQueue[lastFrame]; } + if (lastFrame === endFrame) { + void convertQueue.then(() => inputStream.end()); + } +}; + +const processFrame = (data: Uint8Array): void => { + convertQueue = convertQueue.then(() => + new Promise((fulfill, reject) => { + const myStream = new Stream.Readable(); + myStream._read = () => { + myStream.push(data); + myStream.push(null); + }; + processingQueue.progress++; + sendProgress(); + return myStream + .on("end", () => fulfill()) + .on("error", () => reject()) + .pipe(inputStream, { end: false }); + }).catch((e) => { + console.warn(e); + }), + ); }; -const markAsCompleted = (): void => { - void convertQueue.then(() => inputStream.end()); +const markAsCompleted = (frameId: number): void => { + endFrame = frameId; }; const sendProgress = (): void => { sendMessageToController({ @@ -186,7 +202,7 @@ const processOnLoad = (): ApiResponseLoad => { }; export { - appendBuffers, + appendFrame, appendQueue, markAsCompleted, processingQueue, diff --git a/electron/typeGuard.ts b/electron/typeGuard.ts index 3d32899..4a2a923 100644 --- a/electron/typeGuard.ts +++ b/electron/typeGuard.ts @@ -29,6 +29,7 @@ import type { ApiRequestLoad, } from "@/@types/request.renderer"; import type { ApiRequestMessage } from "@/@types/request.renderer"; +import type { ApiRequestBlob } from "@/@types/request.renderer"; const typeGuard = { controller: { @@ -85,6 +86,10 @@ const typeGuard = { typeof i === "object" && (i as ApiRequestFromRenderer).host === "renderer" && (i as ApiRequestBuffer).type === "buffer", + blob: (i: unknown): i is ApiRequestBlob => + typeof i === "object" && + (i as ApiRequestFromRenderer).host === "renderer" && + (i as ApiRequestBlob).type === "blob", end: (i: unknown): i is ApiRequestEnd => typeof i === "object" && (i as ApiRequestFromRenderer).host === "renderer" && diff --git a/src/@types/request.renderer.d.ts b/src/@types/request.renderer.d.ts index b4e2248..d213dc1 100644 --- a/src/@types/request.renderer.d.ts +++ b/src/@types/request.renderer.d.ts @@ -6,8 +6,14 @@ export type ApiRequestBuffer = { type: "buffer"; data: string[]; }; +export type ApiRequestBlob = { + type: "blob"; + frameId: number; + data: Uint8Array; +}; export type ApiRequestEnd = { type: "end"; + frameId: number; }; export type ApiRequestLoad = { type: "load"; @@ -22,4 +28,5 @@ export type ApiRequestsFromRenderer = | ApiRequestBuffer | ApiRequestEnd | ApiRequestLoad - | ApiRequestMessage; + | ApiRequestMessage + | ApiRequestBlob; diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 464039a..cd5e8ab 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -55,8 +55,16 @@ const startRenderer = async (): Promise => { let inProgress = false; let convertedFrames = 0; - const sendBuffer = (buffer: string[]): void => { - void window.api.request({ type: "buffer", host: "renderer", data: buffer }); + const sendBlob = (frameId: number, blob: Blob): void => { + console.log("sendblob", frameId); + void blob.arrayBuffer().then((buffer) => { + void window.api.request({ + type: "blob", + host: "renderer", + frameId: frameId + 1, + data: new Uint8Array(buffer), + }); + }); }; const { commentData, queue } = (await window.api.request({ @@ -71,7 +79,9 @@ const startRenderer = async (): Promise => { ...queue.option.options, format, }); - const emptyBuffer = canvas.toDataURL("image/png"); + const emptyBuffer: Blob | null = await new Promise((resolve) => + canvas.toBlob((blob) => resolve(blob)), + ); message.innerText = ""; let generatedFrames = 0, offset = Math.ceil((queue.option.ss || 0) * 100); @@ -83,16 +93,24 @@ const startRenderer = async (): Promise => { const process = async (): Promise => { for (let i = 0; i < targetFrameRate; i++) { const vpos = Math.ceil(i * (100 / targetFrameRate)) + offset; + const frame = generatedFrames; // eslint-disable-next-line - if ((nico["timeline"][vpos]?.length || 0) === 0) { - sendBuffer([emptyBuffer]); + if ((nico["timeline"][vpos]?.length || 0) === 0 && emptyBuffer) { + sendBlob(frame, emptyBuffer); } else { nico.drawCanvas(vpos); - sendBuffer([canvas.toDataURL("image/png")]); + canvas.toBlob((blob) => { + if (!blob) return; + sendBlob(frame, blob); + }); } generatedFrames++; if (generatedFrames >= totalFrames) { - await window.api.request({ type: "end", host: "renderer" }); + await window.api.request({ + type: "end", + host: "renderer", + frameId: generatedFrames, + }); inProgress = false; message.innerText = "変換の終了を待っています..."; return;