175 lines
4.2 KiB
TypeScript
175 lines
4.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
assessBrowserPlayback,
|
|
assessGeneratedResultPreview,
|
|
buildPlaybackMimeCandidates,
|
|
} from '../../../src/media/browser-playback';
|
|
import type {
|
|
MediaProbe,
|
|
MediaStreamProbe,
|
|
} from '../../../src/media/media.types';
|
|
import {
|
|
selectDefaultStreams,
|
|
streamSelectionMapArgs,
|
|
validateStreamSelection,
|
|
} from '../../../src/media/stream-selection';
|
|
|
|
function stream(
|
|
index: number,
|
|
type: MediaStreamProbe['type'],
|
|
overrides: Partial<MediaStreamProbe> = {}
|
|
): MediaStreamProbe {
|
|
return {
|
|
index,
|
|
type,
|
|
disposition: {},
|
|
tags: {},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function probe(streams: MediaStreamProbe[]): MediaProbe {
|
|
return {
|
|
formatNames: ['mov', 'mp4'],
|
|
tags: {},
|
|
streams,
|
|
chapters: [],
|
|
warnings: [],
|
|
};
|
|
}
|
|
|
|
describe('stream selection', () => {
|
|
const media = probe([
|
|
stream(0, 'video', { codecName: 'h264' }),
|
|
stream(1, 'audio', {
|
|
language: 'eng',
|
|
disposition: { default: true },
|
|
}),
|
|
stream(2, 'audio', {
|
|
language: 'de-DE',
|
|
}),
|
|
stream(3, 'subtitle', { language: 'deu' }),
|
|
stream(4, 'video', { disposition: { attached_pic: true } }),
|
|
]);
|
|
|
|
it('chooses default streams, avoids cover art and can include subtitles', () => {
|
|
expect(
|
|
selectDefaultStreams(media, {
|
|
preferredAudioLanguages: ['de'],
|
|
preferredSubtitleLanguages: ['deu'],
|
|
includeSubtitles: true,
|
|
})
|
|
).toEqual({
|
|
videoStreamIndex: 0,
|
|
audioStreamIndexes: [2],
|
|
subtitleStreamIndexes: [3],
|
|
});
|
|
});
|
|
|
|
it('validates stream existence, type and duplicate mappings', () => {
|
|
const result = validateStreamSelection(media, {
|
|
videoStreamIndex: 1,
|
|
audioStreamIndexes: [2, 2, 99],
|
|
subtitleStreamIndexes: [],
|
|
});
|
|
expect(result.valid).toBe(false);
|
|
expect(result.issues.map((issue) => issue.code)).toEqual([
|
|
'wrong-stream-type',
|
|
'duplicate-stream',
|
|
'stream-not-found',
|
|
]);
|
|
});
|
|
|
|
it('generates structured map arguments in deterministic order', () => {
|
|
expect(
|
|
streamSelectionMapArgs({
|
|
videoStreamIndex: 0,
|
|
audioStreamIndexes: [2],
|
|
subtitleStreamIndexes: [3],
|
|
})
|
|
).toEqual(['-map', '0:0', '-map', '0:2', '-map', '0:3']);
|
|
});
|
|
});
|
|
|
|
describe('browser playback assessment', () => {
|
|
const mp4 = probe([
|
|
stream(0, 'video', { codecName: 'h264' }),
|
|
stream(1, 'audio', { codecName: 'aac' }),
|
|
]);
|
|
|
|
it('builds codec-qualified and container-only MIME candidates', () => {
|
|
expect(buildPlaybackMimeCandidates(mp4)).toEqual([
|
|
'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
|
|
'video/mp4',
|
|
]);
|
|
});
|
|
|
|
it('keeps browser support separate from FFmpeg readability', () => {
|
|
expect(
|
|
assessBrowserPlayback(mp4, (mime) =>
|
|
mime.includes('codecs') ? 'probably' : ''
|
|
)
|
|
).toMatchObject({
|
|
support: 'probably',
|
|
needsPreviewProxy: false,
|
|
});
|
|
expect(assessBrowserPlayback(mp4, () => '')).toMatchObject({
|
|
support: 'unsupported',
|
|
needsPreviewProxy: true,
|
|
});
|
|
});
|
|
|
|
it('does not treat an audio/video MIME prefix as playable when the browser rejects it', () => {
|
|
expect(
|
|
assessGeneratedResultPreview(
|
|
{
|
|
fileName: 'generated.flac',
|
|
mimeType: 'audio/flac',
|
|
},
|
|
() => ''
|
|
)
|
|
).toMatchObject({
|
|
kind: 'audio',
|
|
previewable: false,
|
|
support: 'unsupported',
|
|
});
|
|
expect(
|
|
assessGeneratedResultPreview(
|
|
{
|
|
fileName: 'generated.mp4',
|
|
mimeType: 'video/mp4',
|
|
probe: mp4,
|
|
},
|
|
(mime) => (mime.includes('codecs') ? 'probably' : '')
|
|
)
|
|
).toMatchObject({
|
|
kind: 'video',
|
|
previewable: true,
|
|
support: 'probably',
|
|
});
|
|
expect(
|
|
assessGeneratedResultPreview(
|
|
{
|
|
fileName: 'frame.png',
|
|
mimeType: 'image/png',
|
|
},
|
|
() => ''
|
|
)
|
|
).toMatchObject({ kind: 'image', previewable: true });
|
|
expect(
|
|
assessGeneratedResultPreview(
|
|
{
|
|
fileName: 'frame.tiff',
|
|
mimeType: 'image/tiff',
|
|
},
|
|
() => ''
|
|
)
|
|
).toMatchObject({
|
|
kind: 'image',
|
|
previewable: false,
|
|
support: 'unsupported',
|
|
});
|
|
});
|
|
});
|