diff --git a/server/src/domain/media/media.service.spec.ts b/server/src/domain/media/media.service.spec.ts index 401c8796e3..5cdcabdd85 100644 --- a/server/src/domain/media/media.service.spec.ts +++ b/server/src/domain/media/media.service.spec.ts @@ -658,6 +658,21 @@ describe(MediaService.name, () => { expect(mediaMock.transcode).not.toHaveBeenCalled(); }); + it('should delete existing transcode if current policy does not require transcoding', async () => { + const asset = assetStub.hasEncodedVideo; + mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p); + configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.DISABLED }]); + assetMock.getByIds.mockResolvedValue([asset]); + + await sut.handleVideoConversion({ id: asset.id }); + + expect(mediaMock.transcode).not.toHaveBeenCalled(); + expect(jobMock.queue).toHaveBeenCalledWith({ + name: JobName.DELETE_FILES, + data: { files: [asset.encodedVideoPath] }, + }); + }); + it('should set max bitrate if above 0', async () => { mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer); configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '4500k' }]); diff --git a/server/src/domain/media/media.service.ts b/server/src/domain/media/media.service.ts index 463bff4826..e3e408913d 100644 --- a/server/src/domain/media/media.service.ts +++ b/server/src/domain/media/media.service.ts @@ -241,11 +241,22 @@ export class MediaService { return false; } + if (!mainVideoStream.height || !mainVideoStream.width) { + this.logger.warn(`Skipped transcoding for asset ${asset.id}: no video streams found`); + return false; + } + const { ffmpeg: config } = await this.configCore.getConfig(); const required = this.isTranscodeRequired(asset, mainVideoStream, mainAudioStream, containerExtension, config); if (!required) { - return false; + if (asset.encodedVideoPath) { + this.logger.log(`Transcoded video exists for asset ${asset.id}, but is no longer required. Deleting...`); + await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [asset.encodedVideoPath] } }); + await this.assetRepository.save({ id: asset.id, encodedVideoPath: null }); + } + + return true; } let transcodeOptions; @@ -289,11 +300,6 @@ export class MediaService { containerExtension: string, ffmpegConfig: SystemConfigFFmpegDto, ): boolean { - if (!videoStream.height || !videoStream.width) { - this.logger.error('Skipping transcode, height or width undefined for video stream'); - return false; - } - const isTargetVideoCodec = videoStream.codecName === ffmpegConfig.targetVideoCodec; const isTargetContainer = ['mov,mp4,m4a,3gp,3g2,mj2', 'mp4', 'mov'].includes(containerExtension); const isTargetAudioCodec = audioStream == null || audioStream.codecName === ffmpegConfig.targetAudioCodec; diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index a4a2208c22..2c4c373f2c 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -545,4 +545,44 @@ export const assetStub = { sidecarPath: '/original/path.ext.xmp', deletedAt: null, }), + + hasEncodedVideo: Object.freeze({ + id: 'asset-id', + originalFileName: 'asset-id.ext', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/original/path.ext', + resizePath: '/uploads/user-id/thumbs/path.ext', + checksum: Buffer.from('file hash', 'utf8'), + type: AssetType.VIDEO, + webpPath: null, + thumbhash: null, + encodedVideoPath: '/encoded/video/path.mp4', + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + isReadOnly: false, + isExternal: false, + isOffline: false, + libraryId: 'library-id', + library: libraryStub.uploadLibrary1, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + tags: [], + sharedLinks: [], + faces: [], + sidecarPath: null, + exifInfo: { + fileSizeInByte: 100_000, + } as ExifEntity, + deletedAt: null, + }), };