mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
Explicitly throw for odd video dimensions for AVC and HEVC (closes #142)
This commit is contained in:
+2
-2
@@ -33,8 +33,8 @@
|
||||
"watch": "tsx scripts/bundle.ts --watch",
|
||||
"lint": "eslint .",
|
||||
"test-node": "cd test && vitest node --run",
|
||||
"test-web": "cd test && vitest browser --run --browser",
|
||||
"test": "npm run test-node && npm run test-web",
|
||||
"test-browser": "cd test && vitest browser --run --browser",
|
||||
"test": "npm run test-node && npm run test-browser",
|
||||
"check": "tsc -p src --noEmit && tsc -p packages/mp3-encoder/src --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit && rm tsconfig.vite.tsbuildinfo",
|
||||
"check-docblocks": "tsx scripts/check-docblocks.ts dist/mediabunny.d.ts",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
|
||||
@@ -490,6 +490,15 @@ export const canEncodeVideo = async (
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasOddDimension = width % 2 === 1 || height % 2 === 1;
|
||||
if (
|
||||
hasOddDimension
|
||||
&& (codec === 'avc' || codec === 'hevc')
|
||||
) {
|
||||
// Disallow odd dimensions for certain codecs
|
||||
return false;
|
||||
}
|
||||
|
||||
encoderConfig ??= buildVideoEncoderConfig({
|
||||
codec,
|
||||
width,
|
||||
|
||||
+18
-5
@@ -292,7 +292,7 @@ class VideoEncoderWrapper {
|
||||
|
||||
if (!this.encoderInitialized) {
|
||||
if (!this.ensureEncoderPromise) {
|
||||
void this.ensureEncoder(videoSample);
|
||||
this.ensureEncoder(videoSample);
|
||||
}
|
||||
|
||||
// No, this "if" statement is not useless. Sometimes, the above call to `ensureEncoder` might have
|
||||
@@ -363,13 +363,13 @@ class VideoEncoderWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureEncoder(videoSample: VideoSample) {
|
||||
private ensureEncoder(videoSample: VideoSample) {
|
||||
if (this.encoder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const encoderError = new Error();
|
||||
return this.ensureEncoderPromise = (async () => {
|
||||
this.ensureEncoderPromise = (async () => {
|
||||
const encoderConfig = buildVideoEncoderConfig({
|
||||
width: videoSample.codedWidth,
|
||||
height: videoSample.codedHeight,
|
||||
@@ -409,6 +409,19 @@ class VideoEncoderWrapper {
|
||||
throw new Error('VideoEncoder is not supported by this browser.');
|
||||
}
|
||||
|
||||
const hasOddDimension = encoderConfig.width % 2 === 1 || encoderConfig.height % 2 === 1;
|
||||
if (
|
||||
hasOddDimension
|
||||
&& (this.encodingConfig.codec === 'avc' || this.encodingConfig.codec === 'hevc')
|
||||
) {
|
||||
// Throw a special error for this case as it gets hit often
|
||||
throw new Error(
|
||||
`The dimensions ${encoderConfig.width}x${encoderConfig.height} are not supported for codec`
|
||||
+ ` '${this.encodingConfig.codec}'; both width and height must be even numbers. Make sure to`
|
||||
+ ` round your dimensions to the nearest even number.`,
|
||||
);
|
||||
}
|
||||
|
||||
const support = await VideoEncoder.isConfigSupported(encoderConfig);
|
||||
if (!support.supported) {
|
||||
throw new Error(
|
||||
@@ -873,7 +886,7 @@ class AudioEncoderWrapper {
|
||||
|
||||
if (!this.encoderInitialized) {
|
||||
if (!this.ensureEncoderPromise) {
|
||||
void this.ensureEncoder(audioSample);
|
||||
this.ensureEncoder(audioSample);
|
||||
}
|
||||
|
||||
// No, this "if" statement is not useless. Sometimes, the above call to `ensureEncoder` might have
|
||||
@@ -1010,7 +1023,7 @@ class AudioEncoderWrapper {
|
||||
}
|
||||
|
||||
const encoderError = new Error();
|
||||
return this.ensureEncoderPromise = (async () => {
|
||||
this.ensureEncoderPromise = (async () => {
|
||||
const { numberOfChannels, sampleRate } = audioSample;
|
||||
|
||||
const encoderConfig = buildAudioEncoderConfig({
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { Output } from '../../src/output.js';
|
||||
import { Mp4OutputFormat } from '../../src/output-format.js';
|
||||
import { NullTarget } from '../../src/target.js';
|
||||
import { VideoSampleSource } from '../../src/media-source.js';
|
||||
import { canEncodeVideo, QUALITY_HIGH } from '../../src/encode.js';
|
||||
import { VideoSample } from '../../src/sample.js';
|
||||
|
||||
test('Odd video dimensions fail for AVC', async () => {
|
||||
const output = new Output({
|
||||
format: new Mp4OutputFormat(),
|
||||
target: new NullTarget(),
|
||||
});
|
||||
|
||||
const source = new VideoSampleSource({
|
||||
codec: 'avc',
|
||||
bitrate: QUALITY_HIGH,
|
||||
});
|
||||
output.addVideoTrack(source);
|
||||
|
||||
await output.start();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1281;
|
||||
canvas.height = 720;
|
||||
const sample = new VideoSample(canvas, { timestamp: 0 });
|
||||
|
||||
await expect(source.add(sample)).rejects.toThrow('even number'); // The error message is explicit
|
||||
});
|
||||
|
||||
test('Odd video dimensions fail for HEVC', async () => {
|
||||
const output = new Output({
|
||||
format: new Mp4OutputFormat(),
|
||||
target: new NullTarget(),
|
||||
});
|
||||
|
||||
const source = new VideoSampleSource({
|
||||
codec: 'hevc',
|
||||
bitrate: QUALITY_HIGH,
|
||||
});
|
||||
output.addVideoTrack(source);
|
||||
|
||||
await output.start();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1281;
|
||||
canvas.height = 720;
|
||||
const sample = new VideoSample(canvas, { timestamp: 0 });
|
||||
|
||||
await expect(source.add(sample)).rejects.toThrow('even number');
|
||||
});
|
||||
|
||||
test('Odd video dimensions pass for VP9', async () => {
|
||||
const output = new Output({
|
||||
format: new Mp4OutputFormat(),
|
||||
target: new NullTarget(),
|
||||
});
|
||||
|
||||
const source = new VideoSampleSource({
|
||||
codec: 'vp9',
|
||||
bitrate: QUALITY_HIGH,
|
||||
});
|
||||
output.addVideoTrack(source);
|
||||
|
||||
await output.start();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1281;
|
||||
canvas.height = 720;
|
||||
const sample = new VideoSample(canvas, { timestamp: 0 });
|
||||
|
||||
await source.add(sample);
|
||||
});
|
||||
|
||||
test('Odd video dimensions encodability checks', async () => {
|
||||
expect(await canEncodeVideo('avc', { width: 1920, height: 1081 })).toBe(false);
|
||||
expect(await canEncodeVideo('hevc', { width: 1920, height: 1081 })).toBe(false);
|
||||
expect(await canEncodeVideo('vp8', { width: 1920, height: 1081 })).toBe(true);
|
||||
expect(await canEncodeVideo('vp9', { width: 1920, height: 1081 })).toBe(true);
|
||||
expect(await canEncodeVideo('av1', { width: 1920, height: 1081 })).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user