Dimensions

This commit is contained in:
Manav Rathi
2025-04-25 08:07:42 +05:30
parent d904aab804
commit 64afcc0c70
4 changed files with 51 additions and 26 deletions

View File

@@ -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 },
);
};

View File

@@ -266,8 +266,8 @@ export const convertToMP4 = async (blob: Blob): Promise<Blob | Uint8Array> => {
};
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(),
);

View File

@@ -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,
});

View File

@@ -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<string[]> => {
): Promise<Response> => {
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;
};
/**