Skip to content

Commit

Permalink
feat(): add interfaces and detailed descriptions for all track types
Browse files Browse the repository at this point in the history
Add interfaces `GeneralTrack`, `VideoTrack`, `AudioTrack`,
`TextTrack`, `ImageTrack`, `MenuTrack`, and `OtherTrack`.

Add descriptions for all track fields. Rename various types and interfaces.
Improve on doc comments. Add `isTrackType` type guard.

BREAKING CHANGE: Consumers of the library need to update their types.
  • Loading branch information
buzz committed May 31, 2024
1 parent dc6a901 commit 69482a7
Show file tree
Hide file tree
Showing 24 changed files with 4,014 additions and 1,125 deletions.
6 changes: 3 additions & 3 deletions __tests__/AudioVideoInterleave.avi.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { analyzeFile, expectToBeDefined, fixturePath } from './utils.ts'
import { analyzeFile, expectToBeDefined, expectTrackType, fixturePath } from './utils.ts'

it('should parse file', async () => {
const result = await analyzeFile(fixturePath('AudioVideoInterleave.avi'))
Expand All @@ -8,12 +8,12 @@ it('should parse file', async () => {
expect(track).toHaveLength(2)
const [track0, track1] = track

expect(track0['@type']).toBe('General')
expectTrackType(track0, 'General')
expect(track0.Format).toBe('AVI')
expect(track0.FileSize).toBe('5686')
expect(track0.Encoded_Application).toBe('Lavf57.41.100')

expect(track1['@type']).toBe('Video')
expectTrackType(track1, 'Video')
expect(track1.Format).toBe('MPEG-4 Visual')
expect(track1.CodecID).toBe('FMP4')
expect(track1.Height).toBe(1)
Expand Down
6 changes: 4 additions & 2 deletions __tests__/args.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import mediaInfoFactory from '..'
import { expectToBeDefined } from './utils'
import { expectToBeDefined, expectTrackType } from './utils'
import type { MediaInfo, SizeArg } from '..'

it.each([
Expand All @@ -15,7 +15,9 @@ it.each([
expectToBeDefined(result.media)
const { track } = result.media
expect(track).toHaveLength(1)
expect(track[0].FileSize).toBe('20')
const [track0] = track
expectTrackType(track0, 'General')
expect(track0.FileSize).toBe('20')
} finally {
if (mi) {
mi.close()
Expand Down
92 changes: 49 additions & 43 deletions __tests__/coverData.test.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,74 @@
import crypto from 'node:crypto'

import { analyzeFile, expectToBeDefined, fixturePath } from './utils.ts'
import type { MediaInfoType } from '..'
import { analyzeFile, expectToBeDefined, expectTrackType, fixturePath } from './utils.ts'
import type { MediaInfoResult } from '..'

const filePath = fixturePath('Dead_Combo_-_01_-_Povo_Que_Cas_Descalo.mp3')

const testFields = (result: MediaInfoType) => {
const testFields = (result: MediaInfoResult) => {
const track = result.media?.track
expectToBeDefined(track)

expect(track).toHaveLength(3)

expect(track[0]['@type']).toBe('General')
expect(track[0].Format).toBe('MPEG Audio')
expect(track[0].FileSize).toBe('6357777')
expect(track[0].Duration).toBeCloseTo(203.494)
expect(track[0].OverallBitRate_Mode).toBe('VBR')
expect(track[0].OverallBitRate).toBeNear(243_043, 2)
expect(track[0].StreamSize).toBe(175_116)
expect(track[0].Title).toBe('Povo Que Caís Descalço')
expect(track[0].Album).toBe('CC Affiliates Mixtape #1')
expect(track[0].Album_Performer).toBe('Creative Commons')
expect(track[0].Track).toBe('Povo Que Caís Descalço')
expect(track[0].Track_Position).toBe(1)
expect(track[0].Compilation).toBe('Yes')
expect(track[0].Performer).toBe('Dead Combo')
expect(track[0].Genre).toBe('International')
expect(track[0].Recorded_Date).toBe('2017-03-03 15:14:12 UTC')
expect(track[0].Encoded_Library).toBe('LAME3.99r')
expect(track[0].Copyright).toBe(
const [track0, track1] = track

expectTrackType(track0, 'General')
expect(track0.Format).toBe('MPEG Audio')
expect(track0.FileSize).toBe('6357777')
expect(track0.Duration).toBeCloseTo(203.494)
expect(track0.OverallBitRate_Mode).toBe('VBR')
expect(track0.OverallBitRate).toBeNear(243_043, 2)
expect(track0.StreamSize).toBe(175_116)
expect(track0.Title).toBe('Povo Que Caís Descalço')
expect(track0.Album).toBe('CC Affiliates Mixtape #1')
expect(track0.Album_Performer).toBe('Creative Commons')
expect(track0.Track).toBe('Povo Que Caís Descalço')
expect(track0.Track_Position).toBe(1)
expect(track0.Compilation).toBe('Yes')
expect(track0.Performer).toBe('Dead Combo')
expect(track0.Genre).toBe('International')
expect(track0.Recorded_Date).toBe('2017-03-03 15:14:12 UTC')
expect(track0.Encoded_Library).toBe('LAME3.99r')
expect(track0.Copyright).toBe(
'Attribution-NonCommercial 3.0 International: http://creativecommons.org/licenses/by-nc/3.0/'
)
expect(track[0].Cover).toBe('Yes')
expect(track[0].Cover_Mime).toBe('image/jpeg')
expect(track[0].Comment).toBe(
expect(track0.Cover).toBe('Yes')
expect(track0.Cover_Mime).toBe('image/jpeg')
expect(track0.Comment).toBe(
'URL: http://freemusicarchive.org/music/Dead_Combo/Creative_Commons_The_2015_Unofficial_Mixtape/01_Povo_Que_Cais_Descalco / Comments: http://freemusicarchive.org/ / Curator: Creative Commons / Copyright: Attribution-NonCommercial 3.0 International: http://creativecommons.org/licenses/by-nc/3.0/'
)

expect(track[1]['@type']).toBe('Audio')
expect(track[1].Format).toBe('MPEG Audio')
expect(track[1].Format_Version).toBe('1')
expect(track[1].Format_Profile).toBe('Layer 3')
expect(track[1].Format_Settings_Mode).toBe('Joint stereo')
expect(track[1].Duration).toBeCloseTo(203.493)
expect(track[1].BitRate_Mode).toBe('VBR')
expect(track[1].BitRate).toBeNear(243_043, 2)
expect(track[1].BitRate_Minimum).toBeCloseTo(32_000)
expect(track[1].Channels).toBe(2)
expect(track[1].SamplesPerFrame).toBeCloseTo(1152)
expect(track[1].SamplingRate).toBe(44_100)
expect(track[1].SamplingCount).toBe(8_974_080)
expect(track[1].FrameRate).toBeCloseTo(38.281)
expect(track[1].Compression_Mode).toBe('Lossy')
expect(track[1].StreamSize).toBe(6_182_244)
expect(track[1].Encoded_Library).toBe('LAME3.99r')
expect(track[1].Encoded_Library_Settings).toBe('-m j -V 0 -q 0 -lowpass 22.1 --vbr-mt -b 32')
expectTrackType(track1, 'Audio')
expect(track1.Format).toBe('MPEG Audio')
expect(track1.Format_Version).toBe('1')
expect(track1.Format_Profile).toBe('Layer 3')
expect(track1.Format_Settings_Mode).toBe('Joint stereo')
expect(track1.Duration).toBeCloseTo(203.493)
expect(track1.BitRate_Mode).toBe('VBR')
expect(track1.BitRate).toBeNear(243_043, 2)
expect(track1.BitRate_Minimum).toBeCloseTo(32_000)
expect(track1.Channels).toBe(2)
expect(track1.SamplesPerFrame).toBeCloseTo(1152)
expect(track1.SamplingRate).toBe(44_100)
expect(track1.SamplingCount).toBe(8_974_080)
expect(track1.FrameRate).toBeCloseTo(38.281)
expect(track1.Compression_Mode).toBe('Lossy')
expect(track1.StreamSize).toBe(6_182_244)
expect(track1.Encoded_Library).toBe('LAME3.99r')
expect(track1.Encoded_Library_Settings).toBe('-m j -V 0 -q 0 -lowpass 22.1 --vbr-mt -b 32')
}

describe('coverData: Dead_Combo_-_01_-_Povo_Que_Cas_Descalo.mp3', () => {
it('should return cover data', async () => {
const result = await analyzeFile(filePath, { coverData: true })
testFields(result)

const coverData = result.media?.track[0].Cover_Data ?? ''
const track = result.media?.track
expectToBeDefined(track)
const [track0] = track
expectTrackType(track0, 'General')
const coverData = track0.Cover_Data ?? ''

// Check cover data
const coverDataDigest = crypto.createHash('md5').update(coverData).digest('hex')
Expand Down
8 changes: 7 additions & 1 deletion __tests__/file_example_MP4_480_1_5MG.mp4.full.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { analyzeFile, expectToBeDefined, fixturePath } from './utils.ts'
import { analyzeFile, expectToBeDefined, expectTrackType, fixturePath } from './utils.ts'

const filePath = fixturePath('file_example_MP4_480_1_5MG.mp4')

Expand All @@ -10,19 +10,22 @@ describe('full: file_example_MP4_480_1_5MG.mp4', () => {
const { track } = result.media
const [track0, track1, track2] = track

expectTrackType(track0, 'General')
expect(track0.InternetMediaType).toBe('video/mp4')
expect(track0.FileSize_String).toBe('4.34 MiB')
expect(track0.Duration_String).toBe('53 s 760 ms')
expect(track0.OverallBitRate_String).toBe('678 kb/s')
expect(track0.StreamSize_String).toBe('46.5 KiB (1%)')

expectTrackType(track1, 'Video')
expect(track1.Format_Info).toBe('Advanced Video Codec')
expect(track1.Format_Url).toBe('http://developers.videolan.org/x264.html')
expect(track1.InternetMediaType).toBe('video/H264')
expect(track1.CodecID_Info).toBe('Advanced Video Coding')
expect(track1.DisplayAspectRatio_String).toBe('16:9')
expect(track1.StreamSize_String).toBe('3.48 MiB (80%)')

expectTrackType(track2, 'Audio')
expect(track2.Format_Info).toBe('Advanced Audio Codec Low Complexity')
expect(track2.BitRate_Mode_String).toBe('Constant')
expect(track2.Channels_String).toBe('2 channels')
Expand All @@ -37,19 +40,22 @@ describe('full: file_example_MP4_480_1_5MG.mp4', () => {
const { track } = result.media
const [track0, track1, track2] = track

expectTrackType(track0, 'General')
expect(track0.InternetMediaType).not.toBeDefined()
expect(track0.FileSize_String).not.toBeDefined()
expect(track0.Duration_String).not.toBeDefined()
expect(track0.OverallBitRate_String).not.toBeDefined()
expect(track0.StreamSize_String).not.toBeDefined()

expectTrackType(track1, 'Video')
expect(track1.Format_Info).not.toBeDefined()
expect(track1.Format_Url).not.toBeDefined()
expect(track1.InternetMediaType).not.toBeDefined()
expect(track1.CodecID_Info).not.toBeDefined()
expect(track1.DisplayAspectRatio_String).not.toBeDefined()
expect(track1.StreamSize_String).not.toBeDefined()

expectTrackType(track2, 'Audio')
expect(track2.Format_Info).not.toBeDefined()
expect(track2.BitRate_Mode_String).not.toBeDefined()
expect(track2.Channels_String).not.toBeDefined()
Expand Down
8 changes: 4 additions & 4 deletions __tests__/file_example_MP4_480_1_5MG.mp4.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { analyzeFile, expectToBeDefined, fixturePath } from './utils'
import { analyzeFile, expectToBeDefined, expectTrackType, fixturePath } from './utils'

describe('file_example_MP4_480_1_5MG.mp4', () => {
it('should parse file', async () => {
Expand All @@ -9,7 +9,7 @@ describe('file_example_MP4_480_1_5MG.mp4', () => {
expect(track).toHaveLength(3)
const [track0, track1, track2] = track

expect(track0['@type']).toBe('General')
expectTrackType(track0, 'General')
expect(track0.Format).toBe('MPEG-4')
expect(track0.Format_Profile).toBe('Base Media')
expect(track0.CodecID).toBe('isom')
Expand All @@ -25,7 +25,7 @@ describe('file_example_MP4_480_1_5MG.mp4', () => {
expect(track0.FooterSize).toBe(46_461)
expect(track0.IsStreamable).toBe('No')

expect(track1['@type']).toBe('Video')
expectTrackType(track1, 'Video')
expect(track1.StreamOrder).toBe('0')
expect(track1.ID).toBe('1')
expect(track1.Format).toBe('AVC')
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('file_example_MP4_480_1_5MG.mp4', () => {
}
expect(track1.extra.CodecConfigurationBox).toBe('avcC')

expect(track2['@type']).toBe('Audio')
expectTrackType(track2, 'Audio')
expect(track2.StreamOrder).toBe('1')
expect(track2.ID).toBe('2')
expect(track2.Format).toBe('AAC')
Expand Down
18 changes: 11 additions & 7 deletions __tests__/many_tracks.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { analyzeFile, expectToBeDefined, fixturePath } from './utils'
import { analyzeFile, expectToBeDefined, expectTrackType, fixturePath } from './utils'

it('should parse file with many tracks', async () => {
const result = await analyzeFile(fixturePath('many_tracks.mp4'))
Expand All @@ -7,11 +7,15 @@ it('should parse file with many tracks', async () => {
const { track } = result.media
expect(track).toHaveLength(102)

expect(track[0]['@type']).toBe('General')
expect(track[0].Format).toBe('MPEG-4')
expect(track[0].FileSize).toBe('208534')
expect(track[1]['@type']).toBe('Video')
expect(track[1].Format).toBe('AVC')
expect(track[101]['@type']).toBe('Text')
const [track0, track1] = track
expectTrackType(track0, 'General')
expect(track0.Format).toBe('MPEG-4')
expect(track0.FileSize).toBe('208534')

expectTrackType(track1, 'Video')
expect(track1.Format).toBe('AVC')

const track101 = track[101]
expectTrackType(track101, 'Text')
expect(track[101].Format).toBe('Timed Text')
})
15 changes: 10 additions & 5 deletions __tests__/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DOMParser } from '@xmldom/xmldom'
import xpath from 'xpath'

import mediaInfoFactory from '..'
import { expectToBeDefined } from './utils'
import { expectToBeDefined, expectTrackType } from './utils'
import type { FormatType, MediaInfo, ResultMap } from '..'

function analyzeFakeData<TFormat extends FormatType>(mi: MediaInfo<TFormat>) {
Expand Down Expand Up @@ -30,7 +30,7 @@ it('should use default options', async () => {
})

it('should accepts options', async () => {
expect.assertions(4)
expect.assertions(6)
let mi: MediaInfo | undefined

try {
Expand All @@ -44,7 +44,10 @@ it('should accepts options', async () => {
expect(mi.options.coverData).toBe(true)
expect(mi.options.full).toBe(true)
const result = await analyzeFakeData(mi)
expect(result.media?.track[0].FileSize).toBe('20')
expectToBeDefined(result.media)
const [track0] = result.media.track
expectTrackType(track0, 'General')
expect(track0.FileSize).toBe('20')
} finally {
if (mi) {
mi.close()
Expand All @@ -53,7 +56,7 @@ it('should accepts options', async () => {
})

it('should return JSON string', async () => {
expect.assertions(4)
expect.assertions(5)
let mi: MediaInfo<'JSON'> | undefined

try {
Expand All @@ -64,7 +67,9 @@ it('should return JSON string', async () => {
expect(() => {
const resultObj = JSON.parse(result) as ResultMap['object']
expectToBeDefined(resultObj.media)
fileSize = resultObj.media.track[0].FileSize
const [track0] = resultObj.media.track
expectTrackType(track0, 'General')
fileSize = track0.FileSize
}).not.toThrow()
expect(fileSize).toBe('20')
} finally {
Expand Down
21 changes: 18 additions & 3 deletions __tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import fs from 'node:fs/promises'
import path from 'node:path'

import mediaInfoFactory from '..'
import type { FormatType, MediaInfo, MediaInfoFactoryOptions, ReadChunkFunc, ResultMap } from '..'
import mediaInfoFactory, { isTrackType } from '..'
import type {
FormatType,
MediaInfo,
MediaInfoFactoryOptions,
ReadChunkFunc,
ResultMap,
Track,
} from '..'

function fixturePath(name: string) {
return path.resolve(import.meta.dirname, 'fixtures', name)
Expand Down Expand Up @@ -56,11 +63,19 @@ function expectToBeDefined<T>(arg: T): asserts arg is Exclude<T, undefined> {
expect(arg).toBeDefined()
}

function expectTrackType<T extends Track['@type']>(
thing: unknown,
type: T
): asserts thing is Extract<Track, { '@type': T }> {
expect(isTrackType(thing, type)).toBeTruthy()
}

function expectToBeError(error: unknown): asserts error is Error {
expect(
error !== null &&
typeof error === 'object' &&
Object.prototype.hasOwnProperty.call(error, 'message')
).toBeTruthy()
}
export { analyzeFile, expectToBeDefined, expectToBeError, fixturePath }

export { analyzeFile, expectToBeDefined, expectToBeError, expectTrackType, fixturePath }
3 changes: 3 additions & 0 deletions gulp/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const BUILD_DIR = path.join(PROJECT_DIR, 'build')
const VENDOR_DIR = path.join(BUILD_DIR, 'vendor')
const WASM_FILE = 'MediaInfoModule.wasm'

const TRACK_TYPES = ['General', 'Video', 'Audio', 'Text', 'Image', 'Menu', 'Other']

const WASM_INITIAL_MEMORY = 2 ** 25 // 32 MiB

// Global variable name for UMD build
Expand Down Expand Up @@ -74,6 +76,7 @@ export {
MediaInfoLib_CXXFLAGS,
PROJECT_DIR,
SRC_DIR,
TRACK_TYPES,
UMD_NAME,
VENDOR_DIR,
WASM_FILE,
Expand Down
4 changes: 2 additions & 2 deletions gulp/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import decompress from 'decompress'
import gulp from 'gulp'

import { LIBMEDIAINFO_VERSION, LIBZEN_VERSION, VENDOR_DIR } from './constants.ts'
import { downloadFile } from './utils.ts'
import { downloadFileToDir } from './utils.ts'

const urls = { libmediainfo: LIBMEDIAINFO_VERSION, libzen: LIBZEN_VERSION }

Expand All @@ -26,7 +26,7 @@ const task = gulp.parallel(
try {
await access(filepath)
} catch {
await downloadFile(dlUrl, filepath)
await downloadFileToDir(dlUrl, filepath)
}

await decompress(filepath, VENDOR_DIR)
Expand Down
Loading

0 comments on commit 69482a7

Please sign in to comment.