mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 05:13:50 +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",
|
"watch": "tsx scripts/bundle.ts --watch",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"test-node": "cd test && vitest node --run",
|
"test-node": "cd test && vitest node --run",
|
||||||
"test-web": "cd test && vitest browser --run --browser",
|
"test-browser": "cd test && vitest browser --run --browser",
|
||||||
"test": "npm run test-node && npm run test-web",
|
"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": "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",
|
"check-docblocks": "tsx scripts/check-docblocks.ts dist/mediabunny.d.ts",
|
||||||
"docs:dev": "vitepress dev docs",
|
"docs:dev": "vitepress dev docs",
|
||||||
|
|||||||
@@ -490,6 +490,15 @@ export const canEncodeVideo = async (
|
|||||||
return false;
|
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({
|
encoderConfig ??= buildVideoEncoderConfig({
|
||||||
codec,
|
codec,
|
||||||
width,
|
width,
|
||||||
|
|||||||
+18
-5
@@ -292,7 +292,7 @@ class VideoEncoderWrapper {
|
|||||||
|
|
||||||
if (!this.encoderInitialized) {
|
if (!this.encoderInitialized) {
|
||||||
if (!this.ensureEncoderPromise) {
|
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
|
// 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) {
|
if (this.encoder) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const encoderError = new Error();
|
const encoderError = new Error();
|
||||||
return this.ensureEncoderPromise = (async () => {
|
this.ensureEncoderPromise = (async () => {
|
||||||
const encoderConfig = buildVideoEncoderConfig({
|
const encoderConfig = buildVideoEncoderConfig({
|
||||||
width: videoSample.codedWidth,
|
width: videoSample.codedWidth,
|
||||||
height: videoSample.codedHeight,
|
height: videoSample.codedHeight,
|
||||||
@@ -409,6 +409,19 @@ class VideoEncoderWrapper {
|
|||||||
throw new Error('VideoEncoder is not supported by this browser.');
|
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);
|
const support = await VideoEncoder.isConfigSupported(encoderConfig);
|
||||||
if (!support.supported) {
|
if (!support.supported) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -873,7 +886,7 @@ class AudioEncoderWrapper {
|
|||||||
|
|
||||||
if (!this.encoderInitialized) {
|
if (!this.encoderInitialized) {
|
||||||
if (!this.ensureEncoderPromise) {
|
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
|
// 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();
|
const encoderError = new Error();
|
||||||
return this.ensureEncoderPromise = (async () => {
|
this.ensureEncoderPromise = (async () => {
|
||||||
const { numberOfChannels, sampleRate } = audioSample;
|
const { numberOfChannels, sampleRate } = audioSample;
|
||||||
|
|
||||||
const encoderConfig = buildAudioEncoderConfig({
|
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