Scaffold
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user