"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.benchmarkCommand = void 0; const renderer_1 = require("@remotion/renderer"); const client_1 = require("@remotion/renderer/client"); const no_react_1 = require("remotion/no-react"); const browser_download_bar_1 = require("./browser-download-bar"); const chalk_1 = require("./chalk"); const cleanup_before_quit_1 = require("./cleanup-before-quit"); const config_1 = require("./config"); const preview_server_1 = require("./config/preview-server"); const convert_entry_point_to_serve_url_1 = require("./convert-entry-point-to-serve-url"); const entry_point_1 = require("./entry-point"); const get_cli_options_1 = require("./get-cli-options"); const image_formats_1 = require("./image-formats"); const log_1 = require("./log"); const make_progress_bar_1 = require("./make-progress-bar"); const parsed_cli_1 = require("./parsed-cli"); const progress_bar_1 = require("./progress-bar"); const setup_cache_1 = require("./setup-cache"); const should_use_non_overlaying_logger_1 = require("./should-use-non-overlaying-logger"); const show_compositions_picker_1 = require("./show-compositions-picker"); const truthy_1 = require("./truthy"); const DEFAULT_RUNS = 3; const { audioBitrateOption, x264Option, offthreadVideoCacheSizeInBytesOption, scaleOption, crfOption, jpegQualityOption, videoBitrateOption, enforceAudioOption, mutedOption, videoCodecOption, colorSpaceOption, disallowParallelEncodingOption, enableMultiprocessOnLinuxOption, glOption, numberOfGifLoopsOption, encodingMaxRateOption, encodingBufferSizeOption, delayRenderTimeoutInMillisecondsOption, headlessOption, overwriteOption, binariesDirectoryOption, forSeamlessAacConcatenationOption, publicPathOption, publicDirOption, metadataOption, hardwareAccelerationOption, chromeModeOption, offthreadVideoThreadsOption, mediaCacheSizeInBytesOption, darkModeOption, askAIOption, experimentalClientSideRenderingOption, keyboardShortcutsOption, } = client_1.BrowserSafeApis.options; const getValidConcurrency = (cliConcurrency) => { const { concurrencies } = parsed_cli_1.parsedCli; if (!concurrencies) { return [renderer_1.RenderInternals.resolveConcurrency(cliConcurrency)]; } return String(concurrencies) .split(',') .map((c) => parseInt(c.trim(), 10)); }; const runBenchmark = async (runs, options, onProgress) => { const timeTaken = []; for (let run = 0; run < runs; ++run) { const startTime = performance.now(); await renderer_1.RenderInternals.internalRenderMedia({ onProgress: ({ progress }) => onProgress === null || onProgress === void 0 ? void 0 : onProgress(run, progress), ...options, }); const endTime = performance.now(); timeTaken.push(endTime - startTime); } return timeTaken; }; const formatTime = (time) => { let ret = ''; const hours = Math.floor(time / (60 * 60 * 1000)); if (hours) { ret = `${hours}h`; } time %= 60 * 60 * 1000; const minutes = Math.floor(time / (60 * 1000)); if (minutes) { ret = `${ret}${minutes}m`; } time %= 60 * 1000; const seconds = (time / 1000).toFixed(5); if (seconds) { ret = `${ret}${seconds}s`; } return ret; }; const avg = (time) => time.reduce((prev, curr) => prev + curr) / time.length; const stdDev = (time) => { const mean = avg(time); return Math.sqrt(time.map((x) => (x - mean) ** 2).reduce((a, b) => a + b) / time.length); }; const getResults = (results, runs) => { const mean = avg(results); const dev = stdDev(results); const max = Math.max(...results); const min = Math.min(...results); return ` Time (${chalk_1.chalk.green('mean')} ± ${chalk_1.chalk.green('σ')}): ${chalk_1.chalk.green(formatTime(mean))} ± ${chalk_1.chalk.green(formatTime(dev))}\n Range (${chalk_1.chalk.blue('min')} ... ${chalk_1.chalk.red('max')}): ${chalk_1.chalk.blue(formatTime(min))} ... ${chalk_1.chalk.red(formatTime(max))} \t ${chalk_1.chalk.gray(`${runs} runs`)} `; }; const makeBenchmarkProgressBar = ({ totalRuns, run, progress, doneIn, }) => { const totalProgress = (run + progress) / totalRuns; return [ `Rendering (${run + 1} out of ${totalRuns} runs)`, (0, make_progress_bar_1.makeProgressBar)(totalProgress, false), doneIn === null ? `${(totalProgress * 100).toFixed(2)}% ` : chalk_1.chalk.gray(doneIn), ].join(' '); }; const benchmarkCommand = async (remotionRoot, args, logLevel) => { var _a, _b, _c; const runs = (_a = parsed_cli_1.parsedCli.runs) !== null && _a !== void 0 ? _a : DEFAULT_RUNS; const { file, reason, remainingArgs } = (0, entry_point_1.findEntryPoint)({ args, remotionRoot, logLevel, allowDirectory: true, }); if (!file) { log_1.Log.error({ indent: false, logLevel }, 'No entry file passed.'); log_1.Log.info({ indent: false, logLevel }, 'Pass an additional argument specifying the entry file'); log_1.Log.info({ indent: false, logLevel }); log_1.Log.info({ indent: false, logLevel }, `$ remotion benchmark `); process.exit(1); } const fullEntryPoint = (0, convert_entry_point_to_serve_url_1.convertEntryPointToServeUrl)(file); const { inputProps, envVariables, browserExecutable, proResProfile, frameRange: defaultFrameRange, pixelFormat, everyNthFrame, ffmpegOverride, height, width, concurrency: unparsedConcurrency, disableWebSecurity, userAgent, ignoreCertificateErrors, } = (0, get_cli_options_1.getCliOptions)({ isStill: false, logLevel, indent: false, }); log_1.Log.verbose({ indent: false, logLevel }, 'Entry point:', fullEntryPoint, 'reason:', reason); const scale = scaleOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const enableMultiProcessOnLinux = enableMultiprocessOnLinuxOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const gl = glOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const headless = headlessOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const publicPath = publicPathOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const publicDir = publicDirOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const chromeMode = chromeModeOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const darkMode = darkModeOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const experimentalClientSideRenderingEnabled = experimentalClientSideRenderingOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const askAIEnabled = askAIOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const keyboardShortcutsEnabled = keyboardShortcutsOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; if (experimentalClientSideRenderingEnabled) { log_1.Log.warn({ indent: false, logLevel }, 'Enabling WIP client-side rendering. Please see caveats on https://www.remotion.dev/docs/client-side-rendering/.'); } const chromiumOptions = { disableWebSecurity, enableMultiProcessOnLinux, gl, headless, ignoreCertificateErrors, userAgent, darkMode, }; const onBrowserDownload = (0, browser_download_bar_1.defaultBrowserDownloadProgress)({ indent: false, logLevel, quiet: (0, parsed_cli_1.quietFlagProvided)(), onProgress: () => undefined, }); const indent = false; await renderer_1.RenderInternals.internalEnsureBrowser({ browserExecutable, indent, logLevel, onBrowserDownload, chromeMode, }); const browserInstance = renderer_1.RenderInternals.internalOpenBrowser({ browser: 'chrome', browserExecutable, chromiumOptions, forceDeviceScaleFactor: scale, indent, viewport: null, logLevel, onBrowserDownload, chromeMode, }); const { urlOrBundle: bundleLocation, cleanup: cleanupBundle } = await (0, setup_cache_1.bundleOnCliOrTakeServeUrl)({ fullPath: fullEntryPoint, publicDir, remotionRoot, onProgress: () => undefined, indentOutput: false, logLevel, onDirectoryCreated: (dir) => { (0, cleanup_before_quit_1.registerCleanupJob)(`Delete ${dir}`, () => renderer_1.RenderInternals.deleteDirectory(dir)); }, quietProgress: false, quietFlag: (0, parsed_cli_1.quietFlagProvided)(), outDir: null, // Not needed for benchmark gitSource: null, bufferStateDelayInMilliseconds: null, maxTimelineTracks: null, publicPath, audioLatencyHint: null, experimentalClientSideRenderingEnabled, askAIEnabled, keyboardShortcutsEnabled, }); (0, cleanup_before_quit_1.registerCleanupJob)(`Deleting bundle`, () => cleanupBundle()); const puppeteerInstance = await browserInstance; const serializedInputPropsWithCustomSchema = no_react_1.NoReactInternals.serializeJSONWithSpecialTypes({ data: inputProps !== null && inputProps !== void 0 ? inputProps : {}, indent: undefined, staticBase: null, }).serializedString; const comps = await renderer_1.RenderInternals.internalGetCompositions({ serveUrlOrWebpackUrl: bundleLocation, serializedInputPropsWithCustomSchema, envVariables, chromiumOptions, timeoutInMilliseconds: delayRenderTimeoutInMillisecondsOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, port: (0, preview_server_1.getRendererPortFromConfigFileAndCliFlag)(), puppeteerInstance, browserExecutable, indent: false, onBrowserLog: null, // Intentionally disabling server to not cache results server: undefined, logLevel, offthreadVideoCacheSizeInBytes: offthreadVideoCacheSizeInBytesOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, offthreadVideoThreads: offthreadVideoThreadsOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, binariesDirectory: binariesDirectoryOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, onBrowserDownload, chromeMode, mediaCacheSizeInBytes: mediaCacheSizeInBytesOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, onLog: renderer_1.RenderInternals.defaultOnLog, }); const ids = (remainingArgs[0] ? String(remainingArgs[0]) .split(',') .map((c) => c.trim()) .filter(truthy_1.truthy) : await (0, show_compositions_picker_1.showMultiCompositionsPicker)(comps, logLevel)); const compositions = ids.map((compId) => { const composition = comps.find((c) => c.id === compId); if (!composition) { throw new Error(`No composition with the ID "${compId}" found.`); } return composition; }); if (compositions.length === 0) { log_1.Log.error({ indent: false, logLevel }, 'No composition IDs passed. Add another argument to the command specifying at least 1 composition ID.'); } const benchmark = {}; let count = 1; const x264Preset = x264Option.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const audioBitrate = audioBitrateOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const configFileCrf = crfOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const jpegQuality = jpegQualityOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const videoBitrate = videoBitrateOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const enforceAudioTrack = enforceAudioOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const muted = mutedOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; const disallowParallelEncoding = disallowParallelEncodingOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const numberOfGifLoops = numberOfGifLoopsOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const encodingMaxRate = encodingMaxRateOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const encodingBufferSize = encodingBufferSizeOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const delayRenderInMilliseconds = delayRenderTimeoutInMillisecondsOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value; const overwrite = overwriteOption.getValue({ commandLine: parsed_cli_1.parsedCli, }, true).value; const metadata = metadataOption.getValue({ commandLine: parsed_cli_1.parsedCli }).value; for (const composition of compositions) { const { value: videoCodec, source: codecReason } = videoCodecOption.getValue({ commandLine: parsed_cli_1.parsedCli, }, { downloadName: null, outName: null, configFile: (_b = config_1.ConfigInternals.getOutputCodecOrUndefined()) !== null && _b !== void 0 ? _b : null, uiCodec: null, compositionCodec: (_c = composition.defaultCodec) !== null && _c !== void 0 ? _c : null, }); const concurrency = getValidConcurrency(unparsedConcurrency); benchmark[composition.id] = {}; for (const con of concurrency) { const benchmarkProgress = (0, progress_bar_1.createOverwriteableCliOutput)({ quiet: (0, parsed_cli_1.quietFlagProvided)(), cancelSignal: null, updatesDontOverwrite: (0, should_use_non_overlaying_logger_1.shouldUseNonOverlayingLogger)({ logLevel }), indent: false, }); log_1.Log.info({ indent: false, logLevel }); log_1.Log.info({ indent: false, logLevel }, `${chalk_1.chalk.bold(`Benchmark #${count++}:`)} ${chalk_1.chalk.gray(`composition=${composition.id} concurrency=${con} codec=${videoCodec} (${codecReason})`)}`); const timeTaken = await runBenchmark(runs, { outputLocation: null, composition: { ...composition, width: width !== null && width !== void 0 ? width : composition.width, height: height !== null && height !== void 0 ? height : composition.height, }, crf: configFileCrf !== null && configFileCrf !== void 0 ? configFileCrf : null, envVariables, frameRange: defaultFrameRange, imageFormat: (0, image_formats_1.getVideoImageFormat)({ codec: videoCodec, uiImageFormat: null, }), serializedInputPropsWithCustomSchema, overwrite, pixelFormat, proResProfile, x264Preset, jpegQuality, chromiumOptions, timeoutInMilliseconds: delayRenderInMilliseconds, scale, port: (0, preview_server_1.getRendererPortFromConfigFileAndCliFlag)(), numberOfGifLoops, everyNthFrame, logLevel, muted, enforceAudioTrack, browserExecutable, ffmpegOverride, serveUrl: bundleLocation, codec: videoCodec, audioBitrate, videoBitrate, encodingMaxRate, encodingBufferSize, puppeteerInstance, concurrency: con, audioCodec: null, cancelSignal: undefined, disallowParallelEncoding, indent: false, onBrowserLog: null, onCtrlCExit: () => undefined, onDownload: () => undefined, onStart: () => undefined, preferLossless: false, server: undefined, serializedResolvedPropsWithCustomSchema: no_react_1.NoReactInternals.serializeJSONWithSpecialTypes({ data: composition.props, indent: undefined, staticBase: null, }).serializedString, offthreadVideoThreads: offthreadVideoThreadsOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, offthreadVideoCacheSizeInBytes: offthreadVideoCacheSizeInBytesOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, colorSpace: colorSpaceOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, repro: false, binariesDirectory: binariesDirectoryOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, separateAudioTo: null, forSeamlessAacConcatenation: forSeamlessAacConcatenationOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, compositionStart: 0, onBrowserDownload, onArtifact: () => undefined, metadata, hardwareAcceleration: hardwareAccelerationOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, chromeMode, mediaCacheSizeInBytes: mediaCacheSizeInBytesOption.getValue({ commandLine: parsed_cli_1.parsedCli, }).value, onLog: renderer_1.RenderInternals.defaultOnLog, licenseKey: null, isProduction: null, }, (run, progress) => { benchmarkProgress.update(makeBenchmarkProgressBar({ totalRuns: runs, run, doneIn: null, progress, }), false); }); benchmarkProgress.update('', false); benchmarkProgress.update(getResults(timeTaken, runs), false); benchmark[composition.id][`${con}`] = timeTaken; } } log_1.Log.info({ indent: false, logLevel }); }; exports.benchmarkCommand = benchmarkCommand;