Change ProRes codec string, remove ProRes RAW variants for now

This commit is contained in:
Vanilagy
2025-12-10 10:18:53 +01:00
parent 0dd53ff1f7
commit 18d64ffb33
5 changed files with 33 additions and 30 deletions
+13 -16
View File
@@ -215,14 +215,12 @@ const VP9_DEFAULT_SUFFIX = '.01.01.01.01.00';
const AV1_DEFAULT_SUFFIX = '.0.110.01.01.01.0'; const AV1_DEFAULT_SUFFIX = '.0.110.01.01.01.0';
export const PRORES_FOURCCS = [ export const PRORES_FOURCCS = [
'ap4x', 'ap4x', // ProRes 4444 XQ
'ap4h', 'ap4h', // ProRes 4444
'apch', 'apch', // ProRes 422 High Quality
'apcn', 'apcn', // ProRes 422 Standard Definition
'apcs', 'apcs', // ProRes 422 LT
'apco', 'apco', // ProRes 422 Proxy
'aprh',
'aprn',
]; ];
export const buildVideoCodecString = (codec: VideoCodec, width: number, height: number, bitrate: number) => { export const buildVideoCodecString = (codec: VideoCodec, width: number, height: number, bitrate: number) => {
@@ -285,7 +283,7 @@ export const buildVideoCodecString = (codec: VideoCodec, width: number, height:
return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}`; return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}`;
} else if (codec === 'prores') { } else if (codec === 'prores') {
return 'apr1.apch'; return 'apch';
} else { } else {
assertNever(codec); assertNever(codec);
} }
@@ -528,7 +526,7 @@ export const extractVideoCodecString = (trackInfo: {
return string; return string;
} else if (codec === 'prores') { } else if (codec === 'prores') {
return `apr1.${proresFormat ?? 'apch'}`; return proresFormat ?? 'apch';
} else if (codec !== null) { } else if (codec !== null) {
assertNever(codec); assertNever(codec);
} }
@@ -816,7 +814,7 @@ export const getAudioEncoderConfigExtension = (codec: AudioCodec) => {
return {}; return {};
}; };
const VALID_VIDEO_CODEC_STRING_PREFIXES = ['avc1', 'avc3', 'hev1', 'hvc1', 'vp8', 'vp09', 'av01', 'apr1']; const VALID_VIDEO_CODEC_STRING_PREFIXES = ['avc1', 'avc3', 'hev1', 'hvc1', 'vp8', 'vp09', 'av01', ...PRORES_FOURCCS];
const AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/; const AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/;
const HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/; const HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/;
const VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/; const VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/;
@@ -952,14 +950,13 @@ export const validateVideoChunkMetadata = (metadata: EncodedVideoChunkMetadata |
+ ' specified in Section "Codecs Parameter String" of https://aomediacodec.github.io/av1-isobmff/.', + ' specified in Section "Codecs Parameter String" of https://aomediacodec.github.io/av1-isobmff/.',
); );
} }
} else if (metadata.decoderConfig.codec.startsWith('apr1')) { } else if (PRORES_FOURCCS.some(x => metadata.decoderConfig!.codec.startsWith(x))) {
// ProRes-specific validation // ProRes-specific validation
const parts = metadata.decoderConfig.codec.split('.'); if (!PRORES_FOURCCS.some(x => metadata.decoderConfig!.codec === x)) {
if (parts.length !== 2 || parts[0] !== 'apr1' || !PRORES_FOURCCS.includes(parts[1]!)) {
throw new TypeError( throw new TypeError(
'Video chunk metadata decoder configuration codec string for ProRes must be a valid ProRes codec' 'Video chunk metadata decoder configuration codec string for ProRes must be one of the valid ProRes'
+ ' string as specified in the Mediabunny Codec Registry.', + ` four-character codes: ${PRORES_FOURCCS.join(', ')}.`,
); );
} }
} }
+1 -1
View File
@@ -1584,7 +1584,7 @@ const videoCodecToBoxName = (codec: VideoCodec, fullCodecString: string) => {
case 'vp8': return 'vp08'; case 'vp8': return 'vp08';
case 'vp9': return 'vp09'; case 'vp9': return 'vp09';
case 'av1': return 'av01'; case 'av1': return 'av01';
case 'prores': return fullCodecString.split('.')[1]!; case 'prores': return fullCodecString;
} }
}; };
+9
View File
@@ -19,6 +19,7 @@ import {
extractVideoCodecString, extractVideoCodecString,
MediaCodec, MediaCodec,
OPUS_SAMPLE_RATE, OPUS_SAMPLE_RATE,
PRORES_FOURCCS,
VideoCodec, VideoCodec,
} from '../codec'; } from '../codec';
import { Demuxer } from '../demuxer'; import { Demuxer } from '../demuxer';
@@ -1044,7 +1045,15 @@ export class MatroskaDemuxer extends Demuxer {
} else if (codecIdWithoutSuffix === CODEC_STRING_MAP.av1) { } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.av1) {
this.currentTrack.info.codec = 'av1'; this.currentTrack.info.codec = 'av1';
} else if (codecIdWithoutSuffix === CODEC_STRING_MAP.prores) { } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.prores) {
const format = this.currentTrack.codecPrivate
? textDecoder.decode(this.currentTrack.codecPrivate)
: '';
if (PRORES_FOURCCS.includes(format)) {
this.currentTrack.info.codec = 'prores'; this.currentTrack.info.codec = 'prores';
} else {
// We don't support ProRes RAW yet
}
} }
const videoTrack = this.currentTrack as InternalVideoTrack; const videoTrack = this.currentTrack as InternalVideoTrack;
+1 -4
View File
@@ -747,11 +747,8 @@ export class MatroskaMuxer extends Muxer {
generateAv1CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec), generateAv1CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec),
); );
} else if (track.source._codec === 'prores') { } else if (track.source._codec === 'prores') {
const format = meta.decoderConfig.codec.split('.')[1];
assert(format);
// "The Private Data contains the FourCC as found in MP4 movies" // "The Private Data contains the FourCC as found in MP4 movies"
newTrackData.codecPrivate = textEncoder.encode(format); newTrackData.codecPrivate = textEncoder.encode(meta.decoderConfig.codec);
} }
this.trackDatas.push(newTrackData); this.trackDatas.push(newTrackData);
+8 -8
View File
@@ -9,7 +9,7 @@ import { Conversion } from '../../src/conversion.js';
test.concurrent('ProRes MOV file reading', async () => { test.concurrent('ProRes MOV file reading', async () => {
using input = new Input({ using input = new Input({
source: new UrlSource('https://pub-cf9fcfcb5c0a44e9b1bb5ff890e041ae.r2.dev/IMG_0158-prores-raw.MOV'), source: new UrlSource('https://pub-cf9fcfcb5c0a44e9b1bb5ff890e041ae.r2.dev/IMG_0158-prores-log.MOV'),
formats: ALL_FORMATS, formats: ALL_FORMATS,
}); });
@@ -19,13 +19,13 @@ test.concurrent('ProRes MOV file reading', async () => {
expect(videoTrack.codedHeight).toBe(1080); expect(videoTrack.codedHeight).toBe(1080);
const decoderConfig = (await videoTrack.getDecoderConfig())!; const decoderConfig = (await videoTrack.getDecoderConfig())!;
expect(decoderConfig.codec).toBe('apr1.apch'); expect(decoderConfig.codec).toBe('apch');
expect(decoderConfig.description).toBeUndefined(); expect(decoderConfig.description).toBeUndefined();
}); });
test.concurrent('ProRes transcoding into MOV', { timeout: 60_000 }, async () => { test.concurrent('ProRes transmuxing into MOV', { timeout: 60_000 }, async () => {
using input = new Input({ using input = new Input({
source: new UrlSource('https://pub-cf9fcfcb5c0a44e9b1bb5ff890e041ae.r2.dev/IMG_0158-prores-raw.MOV'), source: new UrlSource('https://pub-cf9fcfcb5c0a44e9b1bb5ff890e041ae.r2.dev/IMG_0158-prores-log.MOV'),
formats: ALL_FORMATS, formats: ALL_FORMATS,
}); });
@@ -56,13 +56,13 @@ test.concurrent('ProRes transcoding into MOV', { timeout: 60_000 }, async () =>
expect((await videoTrack.computePacketStats()).packetCount).toBe(15); expect((await videoTrack.computePacketStats()).packetCount).toBe(15);
const decoderConfig = (await videoTrack.getDecoderConfig())!; const decoderConfig = (await videoTrack.getDecoderConfig())!;
expect(decoderConfig.codec).toBe('apr1.apch'); expect(decoderConfig.codec).toBe('apch');
expect(decoderConfig.description).toBeUndefined(); expect(decoderConfig.description).toBeUndefined();
}); });
test.concurrent('ProRes transcoding into MKV', { timeout: 60_000 }, async () => { test.concurrent('ProRes transmuxing into MKV', { timeout: 60_000 }, async () => {
using input = new Input({ using input = new Input({
source: new UrlSource('https://pub-cf9fcfcb5c0a44e9b1bb5ff890e041ae.r2.dev/IMG_0158-prores-raw.MOV'), source: new UrlSource('https://pub-cf9fcfcb5c0a44e9b1bb5ff890e041ae.r2.dev/IMG_0158-prores-log.MOV'),
formats: ALL_FORMATS, formats: ALL_FORMATS,
}); });
@@ -96,6 +96,6 @@ test.concurrent('ProRes transcoding into MKV', { timeout: 60_000 }, async () =>
expect((await videoTrack.computePacketStats()).packetCount).toBe(15); expect((await videoTrack.computePacketStats()).packetCount).toBe(15);
const decoderConfig = (await videoTrack.getDecoderConfig())!; const decoderConfig = (await videoTrack.getDecoderConfig())!;
expect(decoderConfig.codec).toBe('apr1.apch'); expect(decoderConfig.codec).toBe('apch');
expect(decoderConfig.description).toBeUndefined(); expect(decoderConfig.description).toBeUndefined();
}); });