diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index b5d497d053..1e2ae44013 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -246,7 +246,7 @@ const handleConvertToMP4Write = async (request: Request) => { const token = randomUUID(); pendingVideoResults.set(token, outputTempFilePath); - return new Response(JSON.stringify([token]), { status: 200 }); + return new Response(token, { status: 200 }); }; const handleVideoRead = async (token: string) => { @@ -277,17 +277,17 @@ const handleVideoDone = async (token: string) => { * The difference here is that we the conversion generates two streams - one for * the HLS playlist itself, and one for the file containing the encrypted and * transcoded video chunks. So instead of returning a single token, we return a - * JSON array containing two tokens so that the renderer can read them off - * separately. + * JSON object containing two tokens (aand other metadata) so that the renderer + * can read them off separately. */ const handleGenerateHLSWrite = async (request: Request) => { const inputTempFilePath = await makeTempFilePath(); await writeStream(inputTempFilePath, request.body!); const outputFilePathPrefix = await makeTempFilePath(); - let paths: FFmpegGenerateHLSPlaylistAndSegmentsResult; + let result: FFmpegGenerateHLSPlaylistAndSegmentsResult; try { - paths = await ffmpegGenerateHLSPlaylistAndSegments( + result = await ffmpegGenerateHLSPlaylistAndSegments( inputTempFilePath, outputFilePathPrefix, ); @@ -297,9 +297,12 @@ const handleGenerateHLSWrite = async (request: Request) => { const playlistToken = randomUUID(); const videoToken = randomUUID(); - pendingVideoResults.set(playlistToken, paths.playlistPath); - pendingVideoResults.set(videoToken, paths.videoPath); - return new Response(JSON.stringify([playlistToken, videoToken]), { - status: 200, - }); + pendingVideoResults.set(playlistToken, result.playlistPath); + pendingVideoResults.set(videoToken, result.videoPath); + + const { dimensions } = result; + return new Response( + JSON.stringify({ playlistToken, videoToken, dimensions }), + { status: 200 }, + ); }; diff --git a/web/packages/gallery/services/ffmpeg/index.ts b/web/packages/gallery/services/ffmpeg/index.ts index 3f69fdc520..7d67a37e44 100644 --- a/web/packages/gallery/services/ffmpeg/index.ts +++ b/web/packages/gallery/services/ffmpeg/index.ts @@ -266,8 +266,8 @@ export const convertToMP4 = async (blob: Blob): Promise => { }; const convertToMP4Native = async (electron: Electron, blob: Blob) => { - const tokens = await writeVideoStream(electron, "convert-to-mp4", blob); - const token = tokens[0]!; + const res = await writeVideoStream(electron, "convert-to-mp4", blob); + const token = await res.text(); const mp4Blob = await readVideoStream(electron, token).then((res) => res.blob(), ); diff --git a/web/packages/gallery/services/video.ts b/web/packages/gallery/services/video.ts index 1f6f377837..aa2c1bd119 100644 --- a/web/packages/gallery/services/video.ts +++ b/web/packages/gallery/services/video.ts @@ -13,6 +13,7 @@ import { gunzip, gzip } from "ente-new/photos/utils/gzip"; import { ensurePrecondition } from "ente-utils/ensure"; import { z } from "zod"; import { + GenerateHLSResult, readVideoStream, videoStreamDone, writeVideoStream, @@ -399,11 +400,13 @@ const processQueueItem = async ( ) => { log.debug(() => ["gen-hls", { file, uploadItem }]); + // TODO(HLS): const fileBlob = await fetchOriginalVideoBlob(file, uploadItem); - // TODO(HLS): - const tokens = await writeVideoStream(electron, "generate-hls", fileBlob!); - const [playlistToken, videoToken] = [tokens[0]!, tokens[1]!]; + const res = await writeVideoStream(electron, "generate-hls", fileBlob!); + const { playlistToken, videoToken, dimensions } = GenerateHLSResult.parse( + await res.json(), + ); try { const playlistBlob = await readVideoStream( @@ -431,10 +434,7 @@ const processQueueItem = async ( const playlistData = await encodePlaylistJSON({ type: "hls_video", playlist: await playlistBlob.text(), - // TODO(HLS): Critical, fix this before any use. - width: 1280, - // TODO(HLS): Critical, fix this before any use. - height: 720, + ...dimensions, size: objectSize, }); diff --git a/web/packages/gallery/utils/native-stream.ts b/web/packages/gallery/utils/native-stream.ts index 50934e5afa..9c67d3afc4 100644 --- a/web/packages/gallery/utils/native-stream.ts +++ b/web/packages/gallery/utils/native-stream.ts @@ -128,6 +128,27 @@ export const writeStream = async ( */ type VideoStreamOp = "convert-to-mp4" | "generate-hls"; +/** + * The contents of the {@link Response} body returned by + * {@link writeVideoStream} for op "generate-hls". + */ +export const GenerateHLSResult = z.object({ + /** + * A token that can be used to passed to {@link readVideoStream} to retrieve + * the generated HLS playlist. + */ + playlistToken: z.string(), + /** + * A token that can be used to passed to {@link readVideoStream} to retrieve + * the video segments. + */ + videoToken: z.string(), + /** + * The dimensions (width and height in pixels) of the generated video stream. + */ + dimensions: z.object({ width: z.number(), height: z.number() }), +}); + /** * Variant of {@link writeStream} tailored for video processing operations. * @@ -138,22 +159,23 @@ type VideoStreamOp = "convert-to-mp4" | "generate-hls"; * @param video The video to convert, as a {@link Blob} or a * {@link ReadableStream}. * - * @returns an array of token that can then be passed to {@link readVideoStream} - * to read back the processed video. The count (and semantics) of the tokens are + * @returns the successful (2xx) HTTP response received from the node side (An + * exception is thrown otherwise). The contents of the response body are * dependent on the operation: * - * - "convert-to-mp4" returns a single token (which can be used to retrieve the - * converted MP4 file). + * - "convert-to-mp4" returns a plain string which is a token that can then be + * passed to {@link readVideoStream} to retrieve the converted MP4 file. * * - "generate-hls" returns two tokens, first one that can be used to retrieve * the generated HLS playlist, and the second one that can be used to retrieve - * the video (segments). + * the video (segments). It also returns the dimensions of the generated + * video. See {@link GenerateHLSResult}. */ export const writeVideoStream = async ( _: Electron, op: VideoStreamOp, video: Blob | ReadableStream, -): Promise => { +): Promise => { const url = `stream://video?op=${op}`; const req = new Request(url, { @@ -169,7 +191,7 @@ export const writeVideoStream = async ( if (!res.ok) throw new Error(`Failed to write stream to ${url}: HTTP ${res.status}`); - return z.array(z.string()).parse(await res.json()); + return res; }; /**