mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 05:43:50 +02:00
Add frame container atom header stripping & readding for ProRes in Matroska, bump patch
This commit is contained in:
Generated
+8
-8
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
".",
|
".",
|
||||||
@@ -12894,7 +12894,7 @@
|
|||||||
},
|
},
|
||||||
"packages/aac-encoder": {
|
"packages/aac-encoder": {
|
||||||
"name": "@mediabunny/aac-encoder",
|
"name": "@mediabunny/aac-encoder",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
@@ -12909,7 +12909,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ac3": {
|
"packages/ac3": {
|
||||||
"name": "@mediabunny/ac3",
|
"name": "@mediabunny/ac3",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
@@ -12924,7 +12924,7 @@
|
|||||||
},
|
},
|
||||||
"packages/flac-encoder": {
|
"packages/flac-encoder": {
|
||||||
"name": "@mediabunny/flac-encoder",
|
"name": "@mediabunny/flac-encoder",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
@@ -12939,7 +12939,7 @@
|
|||||||
},
|
},
|
||||||
"packages/mp3-encoder": {
|
"packages/mp3-encoder": {
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
@@ -12954,7 +12954,7 @@
|
|||||||
},
|
},
|
||||||
"packages/prores": {
|
"packages/prores": {
|
||||||
"name": "@mediabunny/prores",
|
"name": "@mediabunny/prores",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"turbores": "^1.1.2"
|
"turbores": "^1.1.2"
|
||||||
@@ -12969,7 +12969,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@mediabunny/server",
|
"name": "@mediabunny/server",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mediabunny/prores": "^1.50.0",
|
"@mediabunny/prores": "^1.50.0",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/aac-encoder",
|
"name": "@mediabunny/aac-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"description": "AAC encoder extension for Mediabunny, based on FFmpeg.",
|
"description": "AAC encoder extension for Mediabunny, based on FFmpeg.",
|
||||||
"main": "./dist/bundles/mediabunny-aac-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-aac-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-aac-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-aac-encoder.mjs",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/ac3",
|
"name": "@mediabunny/ac3",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.",
|
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.",
|
||||||
"main": "./dist/bundles/mediabunny-ac3.mjs",
|
"main": "./dist/bundles/mediabunny-ac3.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-ac3.mjs",
|
"module": "./dist/bundles/mediabunny-ac3.mjs",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/flac-encoder",
|
"name": "@mediabunny/flac-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
|
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
|
||||||
"main": "./dist/bundles/mediabunny-flac-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-flac-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-flac-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-flac-encoder.mjs",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/prores",
|
"name": "@mediabunny/prores",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"description": "Apple ProRes decoder extension for Mediabunny, based on TurboRes.",
|
"description": "Apple ProRes decoder extension for Mediabunny, based on TurboRes.",
|
||||||
"main": "./dist/bundles/mediabunny-prores.mjs",
|
"main": "./dist/bundles/mediabunny-prores.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-prores.mjs",
|
"module": "./dist/bundles/mediabunny-prores.mjs",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/server",
|
"name": "@mediabunny/server",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.50.1",
|
"version": "1.50.2",
|
||||||
"description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
|
"description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
|
||||||
"main": "./dist/bundles/mediabunny-server.cjs",
|
"main": "./dist/bundles/mediabunny-server.cjs",
|
||||||
"module": "./dist/bundles/mediabunny-server.mjs",
|
"module": "./dist/bundles/mediabunny-server.mjs",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
Rotation,
|
Rotation,
|
||||||
roundIfAlmostInteger,
|
roundIfAlmostInteger,
|
||||||
textDecoder,
|
textDecoder,
|
||||||
|
toDataView,
|
||||||
TRANSFER_CHARACTERISTICS_MAP_INVERSE,
|
TRANSFER_CHARACTERISTICS_MAP_INVERSE,
|
||||||
UNDETERMINED_LANGUAGE,
|
UNDETERMINED_LANGUAGE,
|
||||||
} from '../misc';
|
} from '../misc';
|
||||||
@@ -141,6 +142,7 @@ type ClusterBlock = {
|
|||||||
data: Uint8Array;
|
data: Uint8Array;
|
||||||
lacing: BlockLacing;
|
lacing: BlockLacing;
|
||||||
decoded: boolean;
|
decoded: boolean;
|
||||||
|
postProcessed: boolean; // For codec-specific processing
|
||||||
mainAdditional: Uint8Array | null;
|
mainAdditional: Uint8Array | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -864,6 +866,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
data: frameData,
|
data: frameData,
|
||||||
lacing: BlockLacing.None,
|
lacing: BlockLacing.None,
|
||||||
decoded: true,
|
decoded: true,
|
||||||
|
postProcessed: false,
|
||||||
mainAdditional: originalBlock.mainAdditional,
|
mainAdditional: originalBlock.mainAdditional,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1502,6 +1505,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
data: blockData,
|
data: blockData,
|
||||||
lacing,
|
lacing,
|
||||||
decoded: !hasDecodingInstructions,
|
decoded: !hasDecodingInstructions,
|
||||||
|
postProcessed: false,
|
||||||
mainAdditional: null,
|
mainAdditional: null,
|
||||||
});
|
});
|
||||||
}; break;
|
}; break;
|
||||||
@@ -1538,6 +1542,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
data: blockData,
|
data: blockData,
|
||||||
lacing,
|
lacing,
|
||||||
decoded: !hasDecodingInstructions,
|
decoded: !hasDecodingInstructions,
|
||||||
|
postProcessed: false,
|
||||||
mainAdditional: null,
|
mainAdditional: null,
|
||||||
};
|
};
|
||||||
trackData.blocks.push(this.currentBlock);
|
trackData.blocks.push(this.currentBlock);
|
||||||
@@ -2194,6 +2199,35 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
|||||||
block.decoded = true;
|
block.decoded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!block.postProcessed) {
|
||||||
|
if (this.internalTrack.info?.codec === 'prores') {
|
||||||
|
// For some reason, ProRes packets are stored in Matroska without the frame container atom. FFmpeg cites
|
||||||
|
// the "Matroska spec" but the actual spec says nothing about this.
|
||||||
|
|
||||||
|
const hasFrameContainer = block.data.length >= 8
|
||||||
|
&& block.data[4] === 105 // 'i'
|
||||||
|
&& block.data[5] === 99 // 'c'
|
||||||
|
&& block.data[6] === 112 // 'p'
|
||||||
|
&& block.data[7] === 102; // 'f'
|
||||||
|
|
||||||
|
if (!hasFrameContainer) {
|
||||||
|
// Wrap the frame in a frame container
|
||||||
|
const newData = new Uint8Array(block.data.length + 8);
|
||||||
|
const newDataView = toDataView(newData);
|
||||||
|
|
||||||
|
newDataView.setUint32(0, newData.length, false);
|
||||||
|
newData[4] = 105; // 'i'
|
||||||
|
newData[5] = 99; // 'c'
|
||||||
|
newData[6] = 112; // 'p'
|
||||||
|
newData[7] = 102; // 'f'
|
||||||
|
newData.set(block.data, 8);
|
||||||
|
block.data = newData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block.postProcessed = true;
|
||||||
|
}
|
||||||
|
|
||||||
const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data;
|
const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data;
|
||||||
const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor;
|
const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor;
|
||||||
const duration = block.duration / this.internalTrack.segment.timestampFactor;
|
const duration = block.duration / this.internalTrack.segment.timestampFactor;
|
||||||
|
|||||||
@@ -897,6 +897,17 @@ export class MatroskaMuxer extends Muxer {
|
|||||||
try {
|
try {
|
||||||
const trackData = this.getVideoTrackData(track, packet, meta);
|
const trackData = this.getVideoTrackData(track, packet, meta);
|
||||||
|
|
||||||
|
let packetData = packet.data;
|
||||||
|
if (track.source._codec === 'prores') {
|
||||||
|
if (packetData.byteLength < 8) {
|
||||||
|
throw new Error('ProRes packet too small, expected at least 8 bytes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim off the frame container atom header. FFmpeg does this too and cites the "Matroska spec" as the
|
||||||
|
// reason, despite the spec not saying anything about this.
|
||||||
|
packetData = packetData.subarray(8);
|
||||||
|
}
|
||||||
|
|
||||||
const isKeyFrame = packet.type === 'key';
|
const isKeyFrame = packet.type === 'key';
|
||||||
this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame);
|
this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame);
|
||||||
|
|
||||||
@@ -913,7 +924,7 @@ export class MatroskaMuxer extends Muxer {
|
|||||||
? packet.sideData.alpha ?? null
|
? packet.sideData.alpha ?? null
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const videoChunk = this.createInternalChunk(packet.data, timestamp, duration, packet.type, additions);
|
const videoChunk = this.createInternalChunk(packetData, timestamp, duration, packet.type, additions);
|
||||||
if (track.source._codec === 'vp9') this.fixVP9ColorSpace(trackData, videoChunk);
|
if (track.source._codec === 'vp9') this.fixVP9ColorSpace(trackData, videoChunk);
|
||||||
|
|
||||||
trackData.chunkQueue.push(videoChunk);
|
trackData.chunkQueue.push(videoChunk);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Output } from '../../src/output.js';
|
|||||||
import { MkvOutputFormat, MovOutputFormat } from '../../src/output-format.js';
|
import { MkvOutputFormat, MovOutputFormat } from '../../src/output-format.js';
|
||||||
import { BufferTarget } from '../../src/target.js';
|
import { BufferTarget } from '../../src/target.js';
|
||||||
import { Conversion } from '../../src/conversion.js';
|
import { Conversion } from '../../src/conversion.js';
|
||||||
import { VideoSampleSink } from '../../src/media-sink.js';
|
import { EncodedPacketSink, VideoSampleSink } from '../../src/media-sink.js';
|
||||||
import { assert } from '../../src/misc.js';
|
import { assert } from '../../src/misc.js';
|
||||||
|
|
||||||
const SAMPLE_URL = 'https://pub-1ee78aacb848486482b20a72b55b3121.r2.dev/turbores-sample.mov';
|
const SAMPLE_URL = 'https://pub-1ee78aacb848486482b20a72b55b3121.r2.dev/turbores-sample.mov';
|
||||||
@@ -88,6 +88,10 @@ test.concurrent('ProRes transmuxing into MKV', { timeout: 10_000 }, async () =>
|
|||||||
});
|
});
|
||||||
await conversion.execute();
|
await conversion.execute();
|
||||||
|
|
||||||
|
// No 'icpf' means the frame container atom headers were successfully stripped from the ProRes packets
|
||||||
|
let str = new TextDecoder('ascii').decode(output.target.buffer!);
|
||||||
|
expect(str.includes('icpf')).toBe(false);
|
||||||
|
|
||||||
using newInput = new Input({
|
using newInput = new Input({
|
||||||
source: new BufferSource(output.target.buffer!),
|
source: new BufferSource(output.target.buffer!),
|
||||||
formats: ALL_FORMATS,
|
formats: ALL_FORMATS,
|
||||||
@@ -100,6 +104,14 @@ test.concurrent('ProRes transmuxing into MKV', { timeout: 10_000 }, async () =>
|
|||||||
const decoderConfig = (await videoTrack.getDecoderConfig())!;
|
const decoderConfig = (await videoTrack.getDecoderConfig())!;
|
||||||
expect(decoderConfig.codec).toBe('apch');
|
expect(decoderConfig.codec).toBe('apch');
|
||||||
expect(decoderConfig.description).toBeUndefined();
|
expect(decoderConfig.description).toBeUndefined();
|
||||||
|
|
||||||
|
const sink = new EncodedPacketSink(videoTrack);
|
||||||
|
const firstPacket = await sink.getFirstPacket();
|
||||||
|
assert(firstPacket);
|
||||||
|
|
||||||
|
// The frame container atom headers are added back when reading out the packets
|
||||||
|
str = new TextDecoder('ascii').decode(firstPacket.data);
|
||||||
|
expect(str.includes('icpf')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Custom coder registration', { timeout: 10_000 }, async () => {
|
test('Custom coder registration', { timeout: 10_000 }, async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user