From 55b9acca7816a4d0759123a03def203c07d2e6c8 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:44:09 -0400 Subject: [PATCH] fix(server): hevc tag being set when copying a non-hevc stream (#8582) --- server/src/services/media.service.spec.ts | 61 +++++++++++++++++++++++ server/src/utils/media.ts | 12 +++-- server/test/fixtures/media.stub.ts | 4 ++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index 583a883bfb..50bc7c61c1 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -747,6 +747,67 @@ describe(MediaService.name, () => { ); }); + it('should not include hevc tag when target is hevc and video stream is copied from a different codec', async () => { + mediaMock.probe.mockResolvedValue(probeStub.videoStreamH264); + configMock.load.mockResolvedValue([ + { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC }, + { key: SystemConfigKey.FFMPEG_ACCEPTED_VIDEO_CODECS, value: [VideoCodec.H264, VideoCodec.HEVC] }, + { key: SystemConfigKey.FFMPEG_ACCEPTED_AUDIO_CODECS, value: [AudioCodec.AAC] }, + ]); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); + expect(mediaMock.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/encoded-video/user-id/as/se/asset-id.mp4', + { + inputOptions: [], + outputOptions: [ + '-c:v copy', + '-c:a aac', + '-movflags faststart', + '-fps_mode passthrough', + '-map 0:0', + '-map 0:1', + '-v verbose', + '-preset ultrafast', + '-crf 23', + ], + twoPass: false, + }, + ); + }); + + it('should include hevc tag when target is hevc and copying hevc video stream', async () => { + mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); + configMock.load.mockResolvedValue([ + { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC }, + { key: SystemConfigKey.FFMPEG_ACCEPTED_VIDEO_CODECS, value: [VideoCodec.H264, VideoCodec.HEVC] }, + { key: SystemConfigKey.FFMPEG_ACCEPTED_AUDIO_CODECS, value: [AudioCodec.AAC] }, + ]); + assetMock.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); + expect(mediaMock.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/encoded-video/user-id/as/se/asset-id.mp4', + { + inputOptions: [], + outputOptions: [ + '-c:v copy', + '-c:a aac', + '-movflags faststart', + '-fps_mode passthrough', + '-map 0:0', + '-map 0:1', + '-tag:v hvc1', + '-v verbose', + '-preset ultrafast', + '-crf 23', + ], + twoPass: false, + }, + ); + }); + it('should copy audio stream when audio matches target', async () => { mediaMock.probe.mockResolvedValue(probeStub.audioStreamAac); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]); diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index 01ffbec48d..261977028a 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -37,9 +37,12 @@ class BaseConfig implements VideoCodecSWConfig { } getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { + const videoCodec = [TranscodeTarget.ALL, TranscodeTarget.VIDEO].includes(target) ? this.getVideoCodec() : 'copy'; + const audioCodec = [TranscodeTarget.ALL, TranscodeTarget.AUDIO].includes(target) ? this.getAudioCodec() : 'copy'; + const options = [ - `-c:v ${[TranscodeTarget.ALL, TranscodeTarget.VIDEO].includes(target) ? this.getVideoCodec() : 'copy'}`, - `-c:a ${[TranscodeTarget.ALL, TranscodeTarget.AUDIO].includes(target) ? this.getAudioCodec() : 'copy'}`, + `-c:v ${videoCodec}`, + `-c:a ${audioCodec}`, // Makes a second pass moving the moov atom to the // beginning of the file for improved playback speed. '-movflags faststart', @@ -61,7 +64,10 @@ class BaseConfig implements VideoCodecSWConfig { options.push(`-g ${this.getGopSize()}`); } - if (this.config.targetVideoCodec === VideoCodec.HEVC) { + if ( + this.config.targetVideoCodec === VideoCodec.HEVC && + (videoCodec !== 'copy' || videoStream.codecName === 'hevc') + ) { options.push('-tag:v hvc1'); } diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts index 5070586ac9..323a5ac5cf 100644 --- a/server/test/fixtures/media.stub.ts +++ b/server/test/fixtures/media.stub.ts @@ -173,4 +173,8 @@ export const probeStub = { bitrate: 0, }, }), + videoStreamH264: Object.freeze({ + ...probeStubDefault, + videoStreams: [{ ...probeStubDefaultVideoStream[0], codecName: 'h264' }], + }), };