|
@@ -1,4 +1,4 @@
|
|
|
-import { AssetType, SystemConfigKey, TranscodePolicy, VideoCodec } from '@app/infra/entities';
|
|
|
+import { AssetType, SystemConfigKey, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@app/infra/entities';
|
|
|
import {
|
|
|
assetStub,
|
|
|
newAssetRepositoryMock,
|
|
@@ -272,6 +272,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
|
],
|
|
@@ -309,6 +310,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
|
],
|
|
@@ -331,6 +333,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -357,6 +360,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
|
],
|
|
@@ -380,6 +384,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=720:-2',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -404,6 +409,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -428,6 +434,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -476,6 +483,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -505,6 +513,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-b:v 3104k',
|
|
@@ -531,6 +540,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -559,6 +569,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-cpu-used 5',
|
|
|
'-row-mt 1',
|
|
@@ -589,6 +600,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-cpu-used 2',
|
|
|
'-row-mt 1',
|
|
@@ -618,6 +630,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-row-mt 1',
|
|
|
'-crf 23',
|
|
@@ -646,6 +659,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-cpu-used 5',
|
|
|
'-row-mt 1',
|
|
@@ -673,6 +687,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-threads 2',
|
|
@@ -700,6 +715,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -727,6 +743,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-threads 2',
|
|
@@ -757,6 +774,7 @@ describe(MediaService.name, () => {
|
|
|
'-acodec aac',
|
|
|
'-movflags faststart',
|
|
|
'-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
'-vf scale=-2:720',
|
|
|
'-preset ultrafast',
|
|
|
'-crf 23',
|
|
@@ -765,5 +783,508 @@ describe(MediaService.name, () => {
|
|
|
},
|
|
|
);
|
|
|
});
|
|
|
+
|
|
|
+ it('should skip transcoding for audioless videos with optimal policy if video codec is correct', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.noAudioStreams);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
|
|
|
+ { key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL },
|
|
|
+ { key: SystemConfigKey.FFMPEG_TARGET_RESOLUTION, value: '1080p' },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false if hwaccel is enabled for an unsupported codec', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
|
|
|
+ { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
|
|
+ expect(mediaMock.transcode).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false if hwaccel option is invalid', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: 'invalid' }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
|
|
+ expect(mediaMock.transcode).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should set two pass options for nvenc when enabled', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
|
|
|
+ { key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
|
|
|
+ { key: SystemConfigKey.FFMPEG_TWO_PASS, value: true },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_nvenc`,
|
|
|
+ '-tune hq',
|
|
|
+ '-qmin 0',
|
|
|
+ '-g 250',
|
|
|
+ '-bf 3',
|
|
|
+ '-b_ref_mode middle',
|
|
|
+ '-temporal-aq 1',
|
|
|
+ '-rc-lookahead 20',
|
|
|
+ '-i_qfactor 0.75',
|
|
|
+ '-b_qfactor 1.1',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf hwupload_cuda,scale_cuda=-2:720',
|
|
|
+ '-preset p1',
|
|
|
+ '-b:v 6897k',
|
|
|
+ '-maxrate 10000k',
|
|
|
+ '-bufsize 6897k',
|
|
|
+ '-multipass 2',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should set vbr options for nvenc when max bitrate is enabled', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
|
|
|
+ { key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_nvenc`,
|
|
|
+ '-tune hq',
|
|
|
+ '-qmin 0',
|
|
|
+ '-g 250',
|
|
|
+ '-bf 3',
|
|
|
+ '-b_ref_mode middle',
|
|
|
+ '-temporal-aq 1',
|
|
|
+ '-rc-lookahead 20',
|
|
|
+ '-i_qfactor 0.75',
|
|
|
+ '-b_qfactor 1.1',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf hwupload_cuda,scale_cuda=-2:720',
|
|
|
+ '-preset p1',
|
|
|
+ '-cq:v 23',
|
|
|
+ '-maxrate 10000k',
|
|
|
+ '-bufsize 6897k',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should set cq options for nvenc when max bitrate is disabled', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_nvenc`,
|
|
|
+ '-tune hq',
|
|
|
+ '-qmin 0',
|
|
|
+ '-g 250',
|
|
|
+ '-bf 3',
|
|
|
+ '-b_ref_mode middle',
|
|
|
+ '-temporal-aq 1',
|
|
|
+ '-rc-lookahead 20',
|
|
|
+ '-i_qfactor 0.75',
|
|
|
+ '-b_qfactor 1.1',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf hwupload_cuda,scale_cuda=-2:720',
|
|
|
+ '-preset p1',
|
|
|
+ '-cq:v 23',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should omit preset for nvenc if invalid', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC },
|
|
|
+ { key: SystemConfigKey.FFMPEG_PRESET, value: 'invalid' },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_nvenc`,
|
|
|
+ '-tune hq',
|
|
|
+ '-qmin 0',
|
|
|
+ '-g 250',
|
|
|
+ '-bf 3',
|
|
|
+ '-b_ref_mode middle',
|
|
|
+ '-temporal-aq 1',
|
|
|
+ '-rc-lookahead 20',
|
|
|
+ '-i_qfactor 0.75',
|
|
|
+ '-b_qfactor 1.1',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf hwupload_cuda,scale_cuda=-2:720',
|
|
|
+ '-cq:v 23',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should ignore two pass for nvenc if max bitrate is disabled', async () => {
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.NVENC }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_nvenc`,
|
|
|
+ '-tune hq',
|
|
|
+ '-qmin 0',
|
|
|
+ '-g 250',
|
|
|
+ '-bf 3',
|
|
|
+ '-b_ref_mode middle',
|
|
|
+ '-temporal-aq 1',
|
|
|
+ '-rc-lookahead 20',
|
|
|
+ '-i_qfactor 0.75',
|
|
|
+ '-b_qfactor 1.1',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf hwupload_cuda,scale_cuda=-2:720',
|
|
|
+ '-preset p1',
|
|
|
+ '-cq:v 23',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should set options for qsv', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.QSV },
|
|
|
+ { key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device qsv=hw', '-filter_hw_device hw'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_qsv`,
|
|
|
+ '-g 256',
|
|
|
+ '-extbrc 1',
|
|
|
+ '-refs 5',
|
|
|
+ '-bf 7',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720',
|
|
|
+ '-preset 7',
|
|
|
+ '-global_quality 23',
|
|
|
+ '-maxrate 10000k',
|
|
|
+ '-bufsize 20000k',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should omit preset for qsv if invalid', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.QSV },
|
|
|
+ { key: SystemConfigKey.FFMPEG_PRESET, value: 'invalid' },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device qsv=hw', '-filter_hw_device hw'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_qsv`,
|
|
|
+ '-g 256',
|
|
|
+ '-extbrc 1',
|
|
|
+ '-refs 5',
|
|
|
+ '-bf 7',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720',
|
|
|
+ '-global_quality 23',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should set low power mode for qsv if target video codec is vp9', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.QSV },
|
|
|
+ { key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.VP9 },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device qsv=hw', '-filter_hw_device hw'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec vp9_qsv`,
|
|
|
+ '-g 256',
|
|
|
+ '-extbrc 1',
|
|
|
+ '-refs 5',
|
|
|
+ '-bf 7',
|
|
|
+ '-low_power 1',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload=extra_hw_frames=64,scale_qsv=-1:720',
|
|
|
+ '-preset 7',
|
|
|
+ '-q:v 23',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for qsv if no hw devices', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue([]);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.QSV }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
|
|
+ expect(mediaMock.transcode).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should set vbr options for vaapi when max bitrate is enabled', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI },
|
|
|
+ { key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD128', '-filter_hw_device accel'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_vaapi`,
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload,scale_vaapi=-2:720',
|
|
|
+ '-compression_level 7',
|
|
|
+ '-b:v 6897k',
|
|
|
+ '-maxrate 10000k',
|
|
|
+ '-minrate 3448.5k',
|
|
|
+ '-rc_mode 3',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should set cq options for vaapi when max bitrate is disabled', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD128', '-filter_hw_device accel'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_vaapi`,
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload,scale_vaapi=-2:720',
|
|
|
+ '-compression_level 7',
|
|
|
+ '-qp 23',
|
|
|
+ '-global_quality 23',
|
|
|
+ '-rc_mode 1',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should omit preset for vaapi if invalid', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([
|
|
|
+ { key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI },
|
|
|
+ { key: SystemConfigKey.FFMPEG_PRESET, value: 'invalid' },
|
|
|
+ ]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD128', '-filter_hw_device accel'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_vaapi`,
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload,scale_vaapi=-2:720',
|
|
|
+ '-qp 23',
|
|
|
+ '-global_quality 23',
|
|
|
+ '-rc_mode 1',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should prefer gpu for vaapi if available', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD129', 'card1', 'card0', 'renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/card1', '-filter_hw_device accel'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_vaapi`,
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload,scale_vaapi=-2:720',
|
|
|
+ '-compression_level 7',
|
|
|
+ '-qp 23',
|
|
|
+ '-global_quality 23',
|
|
|
+ '-rc_mode 1',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD129', 'renderD128']);
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: ['-init_hw_device vaapi=accel:/dev/dri/renderD129', '-filter_hw_device accel'],
|
|
|
+ outputOptions: [
|
|
|
+ `-vcodec h264_vaapi`,
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf format=nv12,hwupload,scale_vaapi=-2:720',
|
|
|
+ '-compression_level 7',
|
|
|
+ '-qp 23',
|
|
|
+ '-global_quality 23',
|
|
|
+ '-rc_mode 1',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should fallback to sw transcoding if hw transcoding fails', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue(['renderD128']);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ mediaMock.transcode.mockRejectedValueOnce(new Error('error'));
|
|
|
+ await sut.handleVideoConversion({ id: assetStub.video.id });
|
|
|
+ expect(mediaMock.transcode).toHaveBeenCalledTimes(2);
|
|
|
+ expect(mediaMock.transcode).toHaveBeenLastCalledWith(
|
|
|
+ '/original/path.ext',
|
|
|
+ 'upload/encoded-video/user-id/asset-id.mp4',
|
|
|
+ {
|
|
|
+ inputOptions: [],
|
|
|
+ outputOptions: [
|
|
|
+ '-vcodec h264',
|
|
|
+ '-acodec aac',
|
|
|
+ '-movflags faststart',
|
|
|
+ '-fps_mode passthrough',
|
|
|
+ '-v verbose',
|
|
|
+ '-vf scale=-2:720',
|
|
|
+ '-preset ultrafast',
|
|
|
+ '-crf 23',
|
|
|
+ ],
|
|
|
+ twoPass: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should return false for vaapi if no hw devices', async () => {
|
|
|
+ storageMock.readdir.mockResolvedValue([]);
|
|
|
+ mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
|
+ configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.VAAPI }]);
|
|
|
+ assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
|
+ await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toEqual(false);
|
|
|
+ expect(mediaMock.transcode).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
});
|
|
|
});
|