Skip to content

Commit

Permalink
return AVI tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
JonnyBurger committed Nov 25, 2024
1 parent 47ba4f4 commit 9fa98b8
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 7 deletions.
144 changes: 144 additions & 0 deletions packages/media-parser/src/boxes/riff/get-tracks-from-avi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import type {AudioTrack, OtherTrack, VideoTrack} from '../../get-tracks';
import type {RiffStructure} from '../../parse-result';
import type {StrfBoxAudio, StrfBoxVideo, StrhBox} from './riff-box';
import {
getAvihBox,
getStrfBox,
getStrhBox,
getStrlBoxes,
isRiffAvi,
} from './traversal';

export type AllTracks = {
videoTracks: VideoTrack[];
audioTracks: AudioTrack[];
otherTracks: OtherTrack[];
};

export const getNumberOfTracks = (structure: RiffStructure): number => {
const avihBox = getAvihBox(structure);
if (avihBox) {
return avihBox.streams;
}

throw new Error('No avih box found');
};

const makeAudioTrack = ({
strh,
strf,
index,
}: {
strh: StrhBox;
strf: StrfBoxAudio;
index: number;
}): AudioTrack => {
// 255 = AAC
if (strf.formatTag !== 255) {
throw new Error(`Unsupported audio format ${strf.formatTag}`);
}

return {
type: 'audio',
codec: 'mp4a.40.2', // According to Claude 3.5 Sonnet
codecPrivate: null,
codecWithoutConfig: 'aac',
description: undefined,
numberOfChannels: strf.numberOfChannels,
sampleRate: strf.sampleRate,
timescale: strh.rate,
trackId: index,
trakBox: null,
};
};

const makeVideoTrack = ({
strh,
strf,
index,
}: {
strh: StrhBox;
strf: StrfBoxVideo;
index: number;
}): VideoTrack => {
if (strh.handler !== 'H264') {
throw new Error(`Unsupported video codec ${strh.handler}`);
}

return {
codecPrivate: null,
// TODO: Which avc1?
codec: 'avc1',
codecWithoutConfig: 'h264',
codedHeight: strf.height,
codedWidth: strf.width,
width: strf.width,
height: strf.height,
type: 'video',
displayAspectHeight: strf.height,
timescale: strh.rate,
description: undefined,
trackId: index,
color: {
fullRange: null,
matrixCoefficients: null,
primaries: null,
transferCharacteristics: null,
},
displayAspectWidth: strf.width,
trakBox: null,
rotation: 0,
sampleAspectRatio: {
numerator: 1,
denominator: 1,
},
fps: strh.rate / strh.scale,
};
};

export const getTracksFromAvi = (structure: RiffStructure): AllTracks => {
if (!isRiffAvi(structure)) {
throw new Error('Not an AVI file');
}

const videoTracks: VideoTrack[] = [];
const audioTracks: AudioTrack[] = [];
const otherTracks: OtherTrack[] = [];

const boxes = getStrlBoxes(structure);

let i = 0;
for (const box of boxes) {
i++;
const strh = getStrhBox(box);
const strf = getStrfBox(box);
if (!strh || !strf) {
continue;
}

if (strf.type === 'strf-box-video') {
videoTracks.push(makeVideoTrack({strh, strf, index: i}));
} else if (strh.fccType === 'auds') {
audioTracks.push(makeAudioTrack({strh, strf, index: i}));
} else {
throw new Error(`Unsupported track type ${strh.fccType}`);
}
}

return {audioTracks, otherTracks, videoTracks};
};

export const hasAllTracksFromAvi = (structure: RiffStructure): boolean => {
if (!isRiffAvi(structure)) {
throw new Error('Not an AVI file');
}

const numberOfTracks = getNumberOfTracks(structure);
const tracks = getTracksFromAvi(structure);
return (
tracks.videoTracks.length +
tracks.audioTracks.length +
tracks.otherTracks.length ===
numberOfTracks
);
};
68 changes: 68 additions & 0 deletions packages/media-parser/src/boxes/riff/traversal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type {RiffStructure} from '../../parse-result';
import type {
AvihBox,
ListBox,
StrfBoxAudio,
StrfBoxVideo,
StrhBox,
} from './riff-box';

export const isRiffAvi = (structure: RiffStructure): boolean => {
return structure.boxes.some(
(box) => box.type === 'riff-header' && box.fileType === 'AVI',
);
};

export const getHdlrBox = (structure: RiffStructure): ListBox | null => {
return structure.boxes.find(
(box) => box.type === 'list-box' && box.listType === 'hdrl',
) as ListBox | null;
};

export const getAvihBox = (structure: RiffStructure): AvihBox | null => {
const hdlrBox = getHdlrBox(structure);

if (!hdlrBox) {
return null;
}

return hdlrBox.children.find(
(box) => box.type === 'avih-box',
) as AvihBox | null;
};

export const getStrlBoxes = (structure: RiffStructure): ListBox[] => {
const hdlrBox = getHdlrBox(structure);

if (!hdlrBox) {
return [];
}

return hdlrBox.children.filter(
(box) => box.type === 'list-box' && box.listType === 'strl',
) as ListBox[];
};

export const getStrhBox = (strlBox: ListBox): StrhBox | null => {
if (strlBox.listType !== 'strl') {
return null;
}

return strlBox.children.find(
(box) => box.type === 'strh-box',
) as StrhBox | null;
};

export const getStrfBox = (
strlBox: ListBox,
): StrfBoxAudio | StrfBoxVideo | null => {
if (strlBox.listType !== 'strl') {
return null;
}

return (
(strlBox.children.find(
(box) => box.type === 'strf-box-audio' || box.type === 'strf-box-video',
) as StrfBoxAudio | StrfBoxVideo) ?? null
);
};
18 changes: 12 additions & 6 deletions packages/media-parser/src/get-tracks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
getMvhdBox,
getTraks,
} from './boxes/iso-base-media/traversal';
import {
getTracksFromAvi,
hasAllTracksFromAvi,
} from './boxes/riff/get-tracks-from-avi';
import {getTracksFromMatroska} from './boxes/webm/get-ready-tracks';
import type {MatroskaSegment} from './boxes/webm/segments';
import {getMainSegment, getTracksSegment} from './boxes/webm/traversal';
Expand Down Expand Up @@ -119,13 +123,11 @@ export const hasTracks = (structure: Structure): boolean => {
return tracks.length === numberOfTracks;
}

throw new Error('Unknown container');
};
if (structure.type === 'riff') {
return hasAllTracksFromAvi(structure);
}

type AllTracks = {
videoTracks: VideoTrack[];
audioTracks: AudioTrack[];
otherTracks: OtherTrack[];
throw new Error('Unknown container');
};

const getTracksFromMa = (
Expand Down Expand Up @@ -213,5 +215,9 @@ export const getTracks = (
return getTracksFromIsoBaseMedia(segments.boxes);
}

if (segments.type === 'riff') {
return getTracksFromAvi(segments);
}

throw new Error('Unknown container');
};
47 changes: 46 additions & 1 deletion packages/media-parser/src/test/parse-avi-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,58 @@ import {parseMedia} from '../parse-media';
import {nodeReader} from '../readers/from-node';

test('AVI file', async () => {
const {structure} = await parseMedia({
const {structure, audioTracks, videoTracks} = await parseMedia({
src: exampleVideos.avi,
reader: nodeReader,
fields: {
structure: true,
tracks: true,
},
});
expect(audioTracks).toEqual([
{
codec: 'mp4a.40.2',
codecPrivate: null,
codecWithoutConfig: 'aac',
description: undefined,
numberOfChannels: 2,
sampleRate: 48000,
timescale: 375,
trackId: 2,
trakBox: null,
type: 'audio',
},
]);
expect(videoTracks).toEqual([
{
codec: 'avc1',
codecPrivate: null,
codecWithoutConfig: 'h264',
codedHeight: 270,
codedWidth: 480,
color: {
fullRange: null,
matrixCoefficients: null,
primaries: null,
transferCharacteristics: null,
},
description: undefined,
displayAspectHeight: 270,
displayAspectWidth: 480,
fps: 30,
height: 270,
rotation: 0,
sampleAspectRatio: {
denominator: 1,
numerator: 1,
},
timescale: 30,
trackId: 1,
trakBox: null,
type: 'video',
width: 480,
},
]);
expect(structure).toEqual({
type: 'riff',
boxes: [
Expand Down

0 comments on commit 9fa98b8

Please sign in to comment.