Qusai Ismael 2026-06-03 08:18:13 -05:00 committed by GitHub
commit ec0c84db7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 99 additions and 2 deletions

View File

@ -244,6 +244,7 @@ export class MediaRepository {
formatLongName: results.format.format_long_name,
duration: this.parseFloat(results.format.duration),
bitrate: this.parseInt(results.format.bit_rate),
tags: results.format.tags,
},
videoStreams: results.streams
.filter((stream) => stream.codec_type === 'video' && !stream.disposition?.attached_pic)

View File

@ -2092,6 +2092,58 @@ describe(MediaService.name, () => {
expect(mocks.media.transcode).not.toHaveBeenCalled();
});
it('should remux fragmented MP4 (fMP4) files based on probe metadata', async () => {
mocks.media.probe.mockResolvedValue(probeStub.fragmentedMp4);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Required } });
await sut.handleVideoConversion({ id: 'video-id' });
expect(mocks.media.transcode).toHaveBeenCalledWith(
'/original/path.ext',
expect.any(String),
expect.objectContaining({
inputOptions: expect.any(Array),
outputOptions: expect.arrayContaining(['-c:v copy', '-c:a copy', '-movflags faststart']),
twoPass: false,
}),
);
expect(mocks.asset.upsertFile).toHaveBeenCalledWith(
expect.objectContaining({
type: AssetFileType.EncodedVideo,
isEdited: false,
}),
);
});
it('should remux fragmented MP4 when only compatible_brands indicates fragmentation', async () => {
mocks.media.probe.mockResolvedValue(probeStub.fragmentedMp4CompatibleBrands);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Required } });
await sut.handleVideoConversion({ id: 'video-id' });
expect(mocks.media.transcode).toHaveBeenCalledWith(
'/original/path.ext',
expect.any(String),
expect.objectContaining({
outputOptions: expect.arrayContaining(['-c:v copy', '-c:a copy', '-movflags faststart']),
twoPass: false,
}),
);
});
it('should not include encoding options when remuxing', async () => {
mocks.media.probe.mockResolvedValue(probeStub.fragmentedMp4);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Required } });
await sut.handleVideoConversion({ id: 'video-id' });
expect(mocks.media.transcode).toHaveBeenCalledWith(
'/original/path.ext',
expect.any(String),
expect.objectContaining({
outputOptions: expect.not.arrayContaining([
expect.stringContaining('-preset'),
expect.stringContaining('-crf'),
expect.stringContaining('scale'),
]),
}),
);
});
it('should not scale resolution if no target resolution', async () => {
mocks.assetJob.getForVideoConversion.mockResolvedValue({ ...asset, ...probeStub.videoStream2160p });
mocks.systemMetadata.get.mockResolvedValue({

View File

@ -56,6 +56,7 @@ interface UpsertFileOptions {
}
type ThumbnailAsset = NonNullable<Awaited<ReturnType<AssetJobRepository['getForGenerateThumbnailJob']>>>;
const FRAGMENTED_MP4_BRANDS = ['iso5', 'iso6', 'dash', 'msdh', 'msix', 'cmfc'];
@Injectable()
export class MediaService extends BaseService {
@ -731,7 +732,7 @@ export class MediaService extends BaseService {
}
}
private isRemuxRequired(ffmpegConfig: SystemConfigFFmpegDto, { formatName, formatLongName }: VideoFormat): boolean {
private isRemuxRequired(ffmpegConfig: SystemConfigFFmpegDto, { formatName, formatLongName, tags }: VideoFormat): boolean {
if (ffmpegConfig.transcode === TranscodePolicy.Disabled) {
return false;
}
@ -742,8 +743,25 @@ export class MediaService extends BaseService {
};
const name = (formatLongName ? formatLongNameMapping[formatLongName] : undefined) ?? (formatName as VideoContainer);
if (name !== VideoContainer.Mp4 && !ffmpegConfig.acceptedContainers.includes(name)) {
return true;
}
return name !== VideoContainer.Mp4 && !ffmpegConfig.acceptedContainers.includes(name);
if (!formatName?.includes('mp4') && formatLongName !== 'QuickTime / MOV') {
return false;
}
const majorBrand = tags?.major_brand;
if (majorBrand && FRAGMENTED_MP4_BRANDS.includes(majorBrand)) {
return true;
}
const compatibleBrands = tags?.compatible_brands;
if (!compatibleBrands) {
return false;
}
return FRAGMENTED_MP4_BRANDS.some((brand) => compatibleBrands.includes(brand));
}
isSRGB({

View File

@ -134,6 +134,7 @@ export interface VideoFormat {
formatLongName?: string;
duration: number;
bitrate: number;
tags?: Record<string, string>;
}
export interface ImageDimensions {

View File

@ -129,6 +129,13 @@ export class BaseConfig implements VideoCodecSWConfig {
twoPass: this.eligibleForTwoPass(),
progress: { frameCount: video.frameCount, percentInterval: 5 },
} as TranscodeCommand;
// Skip two-pass and encoder-specific options when we're only remuxing streams.
if (target === TranscodeTarget.None) {
options.twoPass = false;
return options;
}
if ([TranscodeTarget.All, TranscodeTarget.Video].includes(target)) {
const filters = this.getFilterOptions(video);
if (filters.length > 0) {

View File

@ -380,6 +380,24 @@ export const videoInfoStub = {
...probeStubDefault,
videoStreams: [{ ...probeStubDefaultVideoStream[0], codecName: 'h264' }],
}),
fragmentedMp4: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [{ ...probeStubDefaultVideoStream[0], codecName: 'h264' }],
format: {
...probeStubDefaultFormat,
formatName: 'mov,mp4,m4a,3gp,3g2,mj2',
tags: { major_brand: 'iso6', compatible_brands: 'isomiso6dashmp41' },
},
}),
fragmentedMp4CompatibleBrands: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [{ ...probeStubDefaultVideoStream[0], codecName: 'h264' }],
format: {
...probeStubDefaultFormat,
formatName: 'mov,mp4,m4a,3gp,3g2,mj2',
tags: { major_brand: 'isom', compatible_brands: 'isomiso6dashmp41' },
},
}),
videoStreamAvi: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [{ ...probeStubDefaultVideoStream[0], codecName: 'h264' }],