mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 19:03:46 +02:00
* Add custom quality factors and quantizer bitrate mode (closes #327) * Fix quantizer mode fallback and per-frame quantizer edge cases * Clean up quantizer mode tests * Quantizer implementation * Add quality tests * Improve conversion codec error message, make test more lenient * Add quantizer support blog post --------- Co-authored-by: Vanilagy <[email protected]>
93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
import { expect, test } from 'vitest';
|
|
import { Input } from '../../src/input.js';
|
|
import { UrlSource } from '../../src/source.js';
|
|
import { ALL_FORMATS } from '../../src/input-format.js';
|
|
import { Output } from '../../src/output.js';
|
|
import { MkvOutputFormat, Mp4OutputFormat } from '../../src/output-format.js';
|
|
import { BufferTarget } from '../../src/target.js';
|
|
import { Conversion } from '../../src/conversion.js';
|
|
import { canEncodeVideo, Quality, QualityLevel } from '../../src/encode.js';
|
|
import { VideoCodec } from '../../src/codec.js';
|
|
|
|
const QUALITY_LEVELS: QualityLevel[] = ['very-low', 'low', 'medium', 'high', 'very-high'];
|
|
|
|
for (const level of QUALITY_LEVELS) {
|
|
test(`AVC, qualitative quality '${level}'`, { timeout: 10_000 }, async () => {
|
|
await convertVideo(new Quality(level), 'avc');
|
|
});
|
|
}
|
|
|
|
test('AVC, custom qualitative quality', { timeout: 10_000 }, async () => {
|
|
await convertVideo(new Quality(0.85), 'avc');
|
|
});
|
|
|
|
test('AVC, qualitative quality with preferBitrate', { timeout: 10_000 }, async () => {
|
|
await convertVideo(new Quality({ quality: 0.5, preferBitrate: true }), 'avc');
|
|
});
|
|
|
|
test('AVC, explicit bitrate', { timeout: 10_000 }, async () => {
|
|
await convertVideo(new Quality({ bitrate: 1_000_000 }), 'avc');
|
|
});
|
|
|
|
test('AVC, explicit bitrate with constant bitrate mode', { timeout: 10_000 }, async () => {
|
|
await convertVideo(new Quality({ bitrate: 1_000_000, bitrateMode: 'constant' }), 'avc');
|
|
});
|
|
|
|
test('AVC, explicit quantizer', { timeout: 10_000 }, async () => {
|
|
// No bitrate fallback here, so environments without quantizer support are expected to reject the track
|
|
await convertVideo(new Quality({ quantizer: 30 }), 'avc', true);
|
|
});
|
|
|
|
test('AVC, explicit quantizer with bitrate fallback', { timeout: 10_000 }, async () => {
|
|
await convertVideo(new Quality({ quantizer: 30, bitrate: 1_000_000 }), 'avc');
|
|
});
|
|
|
|
// Medium quality prefers quantizer-based encoding, so this hits the quantizer path for every codec that supports it
|
|
const TESTED_VIDEO_CODECS: VideoCodec[] = ['avc', 'hevc', 'vp9', 'av1', 'vp8'];
|
|
|
|
for (const codec of TESTED_VIDEO_CODECS) {
|
|
test(`Medium quality with codec '${codec}'`, { timeout: 10_000 }, async () => {
|
|
const quality = new Quality('medium');
|
|
if (!await canEncodeVideo(codec, { quality })) {
|
|
// The environment can't encode this codec at all; nothing to test
|
|
return;
|
|
}
|
|
|
|
await convertVideo(quality, codec);
|
|
});
|
|
}
|
|
|
|
/** Converts the first two seconds of the test video using the given quality and codec. */
|
|
const convertVideo = async (quality: Quality, codec: VideoCodec, allowMissingQuantizerSupport = false) => {
|
|
using input = new Input({
|
|
source: new UrlSource('/video.mp4'),
|
|
formats: ALL_FORMATS,
|
|
});
|
|
|
|
const output = new Output({
|
|
// Matroska supports all video codecs we test here
|
|
format: codec === 'avc' ? new Mp4OutputFormat() : new MkvOutputFormat(),
|
|
target: new BufferTarget(),
|
|
});
|
|
|
|
const conversion = await Conversion.init({
|
|
input,
|
|
output,
|
|
trim: { start: 0, end: 2 },
|
|
video: { codec, quality },
|
|
audio: { discard: true },
|
|
});
|
|
|
|
if (allowMissingQuantizerSupport && !conversion.isValid) {
|
|
// The environment can't do quantizer-based encoding; a correctly-explained rejection also satisfies the test
|
|
await expect(conversion.execute()).rejects.toThrow(
|
|
`not able to encode '${codec}' with the provided parameters`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
await conversion.execute();
|
|
|
|
expect(conversion.utilizedTracks.some(x => x.isVideoTrack())).toBe(true);
|
|
};
|