Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

derive codec from file extension for remotion lambda #1357

Merged
merged 21 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/compositions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const listCompositionsCommand = async (remotionRoot: string) => {
} = await getCliOptions({
isLambda: false,
type: 'get-compositions',
codec: 'h264',
});

const {urlOrBundle: bundled, cleanup: cleanupBundle} =
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/src/config/image-format.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {ImageFormat} from '@remotion/renderer';
import {RenderInternals} from '@remotion/renderer';
import {truthy} from '../truthy';

let currentImageFormat: ImageFormat | undefined;

Expand All @@ -10,7 +11,15 @@ export const setImageFormat = (format: ImageFormat) => {
}

if (!RenderInternals.validImageFormats.includes(format)) {
throw new TypeError(`Value ${format} is not valid as an image format.`);
throw new TypeError(
[
`Value ${format} is not valid as an image format.`,
// @ts-expect-error
format === 'jpg' ? 'Did you mean "jpeg"?' : null,
]
.filter(truthy)
.join(' ')
);
}

currentImageFormat = format;
Expand Down
84 changes: 84 additions & 0 deletions packages/cli/src/determine-image-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type {ImageFormat, StillImageFormat} from '@remotion/renderer';

const deriveExtensionFromFilename = (
filename: string | null
): StillImageFormat | null => {
if (filename?.endsWith('.png')) {
return 'png';
}

if (filename?.endsWith('.jpg')) {
return 'jpeg';
}

if (filename?.endsWith('.jpeg')) {
return 'jpeg';
}

return null;
};

export const determineFinalImageFormat = ({
downloadName,
outName,
configImageFormat,
cliFlag,
isLambda,
}: {
downloadName: string | null;
outName: string | null;
configImageFormat: ImageFormat | null;
cliFlag: ImageFormat | null;
isLambda: boolean;
}): {format: StillImageFormat; source: string} => {
const outNameExtension = deriveExtensionFromFilename(outName);
const downloadNameExtension = deriveExtensionFromFilename(downloadName);

const outNameDescription = isLambda ? 'S3 output key' : 'out name';

if (
outNameExtension &&
downloadNameExtension &&
outNameExtension !== downloadNameExtension
) {
throw new TypeError(
`Image format mismatch: ${outName} was given as the ${outNameDescription} and ${downloadName} was given as the download name, but the extensions don't match.`
);
}

if (downloadNameExtension) {
if (cliFlag && downloadNameExtension !== cliFlag) {
throw new TypeError(
`Image format mismatch: ${downloadName} was given as the download name, but --image-format=${cliFlag} was passed. The image formats must match.`
);
}

return {format: downloadNameExtension, source: 'Download name extension'};
}

if (outNameExtension) {
if (cliFlag && outNameExtension !== cliFlag) {
throw new TypeError(
`Image format mismatch: ${outName} was given as the ${outNameDescription}, but --image-format=${cliFlag} was passed. The image formats must match.`
);
}

return {format: outNameExtension, source: 'Out name extension'};
}

if (cliFlag === 'none') {
throw new TypeError(
'The --image-format flag must not be "none" for stills.'
);
}

if (cliFlag !== null) {
return {format: cliFlag, source: '--image-format flag'};
}

if (configImageFormat !== null && configImageFormat !== 'none') {
return {format: configImageFormat, source: 'Config file'};
}

return {format: 'png', source: 'Default'};
};
55 changes: 26 additions & 29 deletions packages/cli/src/get-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {getFinalOutputCodec} from './get-final-output-codec';
import {getInputProps} from './get-input-props';
import {getImageFormat} from './image-formats';
import {Log} from './log';
import {getUserPassedOutputLocation} from './user-passed-output-location';
import {parsedCli} from './parse-command-line';

const getAndValidateFrameRange = () => {
const frameRange = ConfigInternals.getRange();
Expand All @@ -29,22 +29,12 @@ const getAndValidateFrameRange = () => {
return frameRange;
};

const getFinalCodec = async (options: {isLambda: boolean}) => {
const userCodec = ConfigInternals.getOutputCodecOrUndefined();

const codec = getFinalOutputCodec({
codec: userCodec,
fileExtension: options.isLambda
? null
: RenderInternals.getExtensionOfFilename(getUserPassedOutputLocation()),
emitWarning: true,
});
export const validateFfmepgCanUseCodec = async (codec: Codec) => {
const ffmpegExecutable = ConfigInternals.getCustomFfmpegExecutable();
if (
codec === 'vp8' &&
!(await RenderInternals.ffmpegHasFeature({
feature: 'enable-libvpx',
isLambda: options.isLambda,
ffmpegExecutable,
}))
) {
Expand All @@ -60,7 +50,6 @@ const getFinalCodec = async (options: {isLambda: boolean}) => {
codec === 'h265' &&
!(await RenderInternals.ffmpegHasFeature({
feature: 'enable-gpl',
isLambda: options.isLambda,
ffmpegExecutable,
}))
) {
Expand All @@ -76,7 +65,6 @@ const getFinalCodec = async (options: {isLambda: boolean}) => {
codec === 'h265' &&
!(await RenderInternals.ffmpegHasFeature({
feature: 'enable-libx265',
isLambda: options.isLambda,
ffmpegExecutable,
}))
) {
Expand All @@ -87,8 +75,20 @@ const getFinalCodec = async (options: {isLambda: boolean}) => {
'This does not work, please recompile your FFMPEG binary with --enable-gpl --enable-libx265 or choose a different codec.'
);
}
};

export const getFinalCodec = (options: {
downloadName: string | null;
outName: string | null;
}): {codec: Codec; reason: string} => {
const {codec, reason} = getFinalOutputCodec({
cliFlag: parsedCli.codec,
configFile: ConfigInternals.getOutputCodecOrUndefined() ?? null,
downloadName: options.downloadName,
outName: options.outName,
});

return codec;
return {codec, reason};
};

const getBrowser = () =>
Expand Down Expand Up @@ -200,15 +200,10 @@ const getAndValidateBrowser = async (browserExecutable: BrowserExecutable) => {
export const getCliOptions = async (options: {
isLambda: boolean;
type: 'still' | 'series' | 'get-compositions';
codec: Codec;
}) => {
const frameRange = getAndValidateFrameRange();

const codec: Codec =
options.type === 'get-compositions'
? 'h264'
: await getFinalCodec({
isLambda: options.isLambda,
});
const shouldOutputImageSequence =
options.type === 'still'
? true
Expand All @@ -220,14 +215,14 @@ export const getCliOptions = async (options: {
const overwrite = ConfigInternals.getShouldOverwrite({
defaultValue: !options.isLambda,
});
const crf = getAndValidateCrf(shouldOutputImageSequence, codec);
const pixelFormat = getAndValidatePixelFormat(codec);
const crf = getAndValidateCrf(shouldOutputImageSequence, options.codec);
const pixelFormat = getAndValidatePixelFormat(options.codec);
const imageFormat = getAndValidateImageFormat({
shouldOutputImageSequence,
codec,
codec: options.codec,
pixelFormat,
});
const proResProfile = getAndValidateProResProfile(codec);
const proResProfile = getAndValidateProResProfile(options.codec);
const browserExecutable = ConfigInternals.getBrowserExecutable();
const ffmpegExecutable = ConfigInternals.getCustomFfmpegExecutable();
const ffprobeExecutable = ConfigInternals.getCustomFfprobeExecutable();
Expand All @@ -242,9 +237,12 @@ export const getCliOptions = async (options: {
ConfigInternals.getChromiumOpenGlRenderer() ??
RenderInternals.DEFAULT_OPENGL_RENDERER,
};
const everyNthFrame = ConfigInternals.getAndValidateEveryNthFrame(codec);
const numberOfGifLoops =
ConfigInternals.getAndValidateNumberOfGifLoops(codec);
const everyNthFrame = ConfigInternals.getAndValidateEveryNthFrame(
options.codec
);
const numberOfGifLoops = ConfigInternals.getAndValidateNumberOfGifLoops(
options.codec
);

const concurrency = ConfigInternals.getConcurrency();

Expand All @@ -255,7 +253,6 @@ export const getCliOptions = async (options: {
concurrency,
frameRange,
shouldOutputImageSequence,
codec,
inputProps: getInputProps(() => undefined),
envVariables: await getEnvironmentVariables(),
quality: ConfigInternals.getQuality(),
Expand Down