This commit is contained in:
Manav Rathi
2025-04-11 15:07:50 +05:30
parent 7bfc5cb08d
commit 2d8d137029
3 changed files with 89 additions and 53 deletions

View File

@@ -280,7 +280,7 @@ const convertToMP4Native = async (electron: Electron, blob: Blob) => {
*
* @param blob The input video blob.
*
* @returns The data of the generated preview variant.
* @returns The output video blob containing the generated preview variant.
*/
export const generateVideoPreviewVariantWeb = async (blob: Blob) =>
ffmpegExecWeb(generateVideoPreviewMetadataCommand, blob, "mp4");
@@ -290,38 +290,29 @@ export const generateVideoPreviewVariantWeb = async (blob: Blob) =>
*
* Current parameters
*
* - H264
* - 720p width
* - 2000kbps bitrate
* - 30fps frame rate
*
* See: [Note: Preview variant of videos]
*
* Options:
*
* - `-vf` creates a filter graph for the video stream
*
* - `-vf scale=720:-1` scales the video to 720p width, keeping aspect ratio.
*
* - `-r` sets the frame rate.
*
* - `-c:v libx264` sets the codec for the video stream to H264.
*
* - `-b:v 2000k` sets the bitrate for the video stream.
*
* - `-c:a aac -b:a 128k` converts the audio stream to 128k bit AAC.
*/
const generateVideoPreviewMetadataCommand = [
ffmpegPathPlaceholder,
"-i",
inputPathPlaceholder,
// `-vf` creates a filter graph for the video stream.
"-vf",
// `-vf scale=720:-1` scales the video to 720p width, keeping aspect ratio.
"scale=720:-1",
// `-r 30` sets the frame rate to 30 fps.
"-r",
"30",
// `-c:v libx264` sets the codec for the video stream to H264.
"-c:v",
"libx264",
// `-b:v 2000k` sets the bitrate for the video stream.
"-b:v",
"2000k",
// `-c:a aac -b:a 128k` converts the audio stream to 128k bit AAC.
"-c:a",
"aac",
"-b:a",

View File

@@ -1,34 +0,0 @@
import type { EnteFile } from "ente-media/file";
import { FileType } from "ente-media/file-type";
import { downloadManager } from "./download";
import { generateVideoPreviewVariantWeb } from "./ffmpeg";
// TODO(HLS): Move me to video.ts
/**
* Create a preview variant of the given video {@link file}.
*
* [Note: Preview variant of videos]
*
* A preview variant of a video is created by transcoding it into a smaller,
* streamable and more standard format.
*
* The video is transcoded into a format that is both smaller but is also using
* a much more widely supported codec etc so that it can be played back readily
* across browsers and OSes independent of the codec used by the source video.
*
* We also use a format that can be streamed back by the client instead of
* needing to download it all at once.
*
* @param file The {@link EnteFile} of type video for which we want to create a
* preview variant.
*/
export const createVideoPreviewVariant = async (file: EnteFile) => {
if (file.metadata.fileType != FileType.video)
throw new Error("Preview variant can only be created for video files");
const fileBlob = await downloadManager.fileBlob(file);
const previewFileData = await generateVideoPreviewVariantWeb(fileBlob);
// Unrevoked currently.
const previewFileURL = URL.createObjectURL(new Blob([previewFileData]));
return previewFileURL;
};

View File

@@ -9,6 +9,8 @@ import { settingsSnapshot } from "ente-new/photos/services/settings";
import { gunzip } from "ente-new/photos/utils/gzip";
import { ensurePrecondition } from "ente-utils/ensure";
import { z } from "zod";
import { downloadManager } from "./download";
import { generateVideoPreviewVariantWeb } from "./ffmpeg";
import { fetchFileData, fetchFilePreviewData } from "./file-data";
import type { UploadItem } from "./upload";
@@ -277,10 +279,10 @@ export const processVideoNewUpload = (
return;
}
// Enqueue.
// Enqueue the item.
_state.videoProcessingQueue.push({ file, uploadItem });
// Tickle.
// Tickle the processor if it isn't already running.
_state.queueProcessor ??= processQueue();
};
@@ -305,11 +307,88 @@ const processQueue = async () => {
_state.queueProcessor = undefined;
};
/**
* Generate and upload a streamable variant of the given {@link EnteFile}.
*
* [Note: Preview variant of videos]
*
* A preview variant of a video is created by transcoding it into a smaller,
* streamable, and (more) widely supported format.
*
* 1. The video is transcoded into a format that is both smaller but is also
* using a much more widely supported codec so that it can be played back
* readily across browsers and OSes independent of the codec used by the
* source video.
*
* 2. We use a format that can be streamed back by the client instead of needing
* to download it all at once, and also generate an HLS playlist that refers
* to the offsets in the generated video file.
*
* 3. Both the generated video and the HLS playlist are then uploaded, E2EE.
*/
const processQueueItem = async ({
file,
uploadItem,
}: VideoProcessingQueueItem) => {
log.debug(() => ["gen-hls", { file, uploadItem }]);
const fileBlob = await fetchOriginalVideoBlob(file, uploadItem);
const previewFileData = await generateVideoPreviewVariantWeb(fileBlob);
console.log(previewFileData);
await Promise.resolve(0);
};
/**
* Return a blob containing the contents of the given video file.
*
* The blob is either constructed using the given {@link uploadItem} if present,
* otherwise it is downloaded from remote.
*
* @param file An {@link EnteFile} of type {@link FileType.video}.
*
* @param uploadItem If we're called during the upload process, then this will
* be set to the {@link UploadItem} that was uploaded. This way, we can directly
* use the on-disk file instead of needing to download the original from remote.
*/
const fetchOriginalVideoBlob = async (
file: EnteFile,
uploadItem: UploadItem | undefined,
): Promise<Blob> =>
uploadItem
? fetchOriginalVideoUploadItemBlob(file, uploadItem)
: await downloadManager.fileBlob(file);
const fetchOriginalVideoUploadItemBlob = (
_: EnteFile,
uploadItem: UploadItem,
) => {
// TODO(HLS): Commented below is the implementation that the eventual
// desktop only conversion would need to handle - the conversion logic would
// need to move to the desktop side to allow it to handle large videos.
//
// Meanwhile during development, we assume we're on the happy web-only cases
// (dragging and dropping a file). All this code is behind a development
// feature flag, so it is not going to impact end users.
if (typeof uploadItem == "string" || Array.isArray(uploadItem)) {
throw new Error("Not implemented");
// const { response, lastModifiedMs } = await readStream(
// ensureElectron(),
// uploadItem,
// );
// const path = typeof uploadItem == "string" ? uploadItem : uploadItem[1];
// // This function will not be called for videos, and for images
// // it is reasonable to read the entire stream into memory here.
// return new File([await response.arrayBuffer()], basename(path), {
// lastModified: lastModifiedMs,
// });
} else {
if (uploadItem instanceof File) {
return uploadItem;
} else {
return uploadItem.file;
}
}
};