Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions server/src/repositories/media.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ export class MediaRepository {
isHDR: stream.color_transfer === 'smpte2084' || stream.color_transfer === 'arib-std-b67',
bitrate: this.parseInt(stream.bit_rate),
pixelFormat: stream.pix_fmt || 'yuv420p',
colorPrimaries: stream.color_primaries,
colorSpace: stream.color_space,
colorTransfer: stream.color_transfer,
})),
audioStreams: results.streams
.filter((stream) => stream.codec_type === 'audio')
Expand Down
20 changes: 20 additions & 0 deletions server/src/services/media.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ describe(MediaService.name, () => {
}),
);
});

it('should not skip intra frames for MTS file', async () => {
mocks.media.probe.mockResolvedValue(probeStub.videoStreamMTS);
mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video);
Expand All @@ -462,6 +463,25 @@ describe(MediaService.name, () => {
);
});

it('should override reserved color metadata', async () => {
mocks.media.probe.mockResolvedValue(probeStub.videoStreamReserved);
mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video);
await sut.handleGenerateThumbnails({ id: assetStub.video.id });

expect(mocks.media.transcode).toHaveBeenCalledWith(
'/original/path.ext',
expect.any(String),
expect.objectContaining({
inputOptions: expect.arrayContaining([
'-bsf:v hevc_metadata=colour_primaries=1:matrix_coefficients=1:transfer_characteristics=1',
]),
outputOptions: expect.any(Array),
progress: expect.any(Object),
twoPass: false,
}),
);
});

it('should use scaling divisible by 2 even when using quick sync', async () => {
mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p);
mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv } });
Expand Down
3 changes: 3 additions & 0 deletions server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export interface VideoStreamInfo {
isHDR: boolean;
bitrate: number;
pixelFormat: string;
colorPrimaries?: string;
colorSpace?: string;
colorTransfer?: string;
}

export interface AudioStreamInfo {
Expand Down
27 changes: 24 additions & 3 deletions server/src/utils/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,30 @@ export class ThumbnailConfig extends BaseConfig {

getBaseInputOptions(videoStream: VideoStreamInfo, format?: VideoFormat): string[] {
// skip_frame nointra skips all frames for some MPEG-TS files. Look at ffmpeg tickets 7950 and 7895 for more details.
return format?.formatName === 'mpegts'
? ['-sws_flags accurate_rnd+full_chroma_int']
: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'];
const options =
format?.formatName === 'mpegts'
? ['-sws_flags accurate_rnd+full_chroma_int']
: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'];

const metadataOverrides = [];
if (videoStream.colorPrimaries === 'reserved') {
metadataOverrides.push('colour_primaries=1');
}

if (videoStream.colorSpace === 'reserved') {
metadataOverrides.push('matrix_coefficients=1');
}

if (videoStream.colorTransfer === 'reserved') {
metadataOverrides.push('transfer_characteristics=1');
}

if (metadataOverrides.length > 0) {
// workaround for https://fftrac-bg.ffmpeg.org/ticket/11020
options.push(`-bsf:v ${videoStream.codecName}_metadata=${metadataOverrides.join(':')}`);
}

return options;
}

getBaseOutputOptions() {
Expand Down
11 changes: 11 additions & 0 deletions server/test/fixtures/media.stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,15 @@ export const probeStub = {
bitrate: 0,
},
}),
videoStreamReserved: Object.freeze<VideoInfo>({
...probeStubDefault,
videoStreams: [
{
...probeStubDefaultVideoStream[0],
colorPrimaries: 'reserved',
colorSpace: 'reserved',
colorTransfer: 'reserved',
},
],
}),
};
Loading