mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(server): add transcode presets (#2084)
* feat: add transcode presets * Add migration * chore: generate api * refactor: use enum type instead of string for transcode option * chore: generate api * refactor: enhance readability of runVideoEncode method * refactor: reuse SettingSelect for transcoding presets * refactor: simplify return statement * chore: regenerate api * fix: correct label attribute * Update import * fix test --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
		
							
								
								
									
										2
									
								
								mobile/openapi/doc/SystemConfigFFmpegDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/doc/SystemConfigFFmpegDto.md
									
									
									
										generated
									
									
									
								
							| @@ -13,7 +13,7 @@ Name | Type | Description | Notes | |||||||
| **targetVideoCodec** | **String** |  |  | **targetVideoCodec** | **String** |  |  | ||||||
| **targetAudioCodec** | **String** |  |  | **targetAudioCodec** | **String** |  |  | ||||||
| **targetScaling** | **String** |  |  | **targetScaling** | **String** |  |  | ||||||
| **transcodeAll** | **bool** |  |  | **transcode** | **String** |  |  | ||||||
| 
 | 
 | ||||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ class SystemConfigFFmpegDto { | |||||||
|     required this.targetVideoCodec, |     required this.targetVideoCodec, | ||||||
|     required this.targetAudioCodec, |     required this.targetAudioCodec, | ||||||
|     required this.targetScaling, |     required this.targetScaling, | ||||||
|     required this.transcodeAll, |     required this.transcode, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   String crf; |   String crf; | ||||||
| @@ -31,7 +31,7 @@ class SystemConfigFFmpegDto { | |||||||
| 
 | 
 | ||||||
|   String targetScaling; |   String targetScaling; | ||||||
| 
 | 
 | ||||||
|   bool transcodeAll; |   SystemConfigFFmpegDtoTranscodeEnum transcode; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegDto && |   bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegDto && | ||||||
| @@ -40,7 +40,7 @@ class SystemConfigFFmpegDto { | |||||||
|      other.targetVideoCodec == targetVideoCodec && |      other.targetVideoCodec == targetVideoCodec && | ||||||
|      other.targetAudioCodec == targetAudioCodec && |      other.targetAudioCodec == targetAudioCodec && | ||||||
|      other.targetScaling == targetScaling && |      other.targetScaling == targetScaling && | ||||||
|      other.transcodeAll == transcodeAll; |      other.transcode == transcode; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   int get hashCode => |   int get hashCode => | ||||||
| @@ -50,10 +50,10 @@ class SystemConfigFFmpegDto { | |||||||
|     (targetVideoCodec.hashCode) + |     (targetVideoCodec.hashCode) + | ||||||
|     (targetAudioCodec.hashCode) + |     (targetAudioCodec.hashCode) + | ||||||
|     (targetScaling.hashCode) + |     (targetScaling.hashCode) + | ||||||
|     (transcodeAll.hashCode); |     (transcode.hashCode); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   String toString() => 'SystemConfigFFmpegDto[crf=$crf, preset=$preset, targetVideoCodec=$targetVideoCodec, targetAudioCodec=$targetAudioCodec, targetScaling=$targetScaling, transcodeAll=$transcodeAll]'; |   String toString() => 'SystemConfigFFmpegDto[crf=$crf, preset=$preset, targetVideoCodec=$targetVideoCodec, targetAudioCodec=$targetAudioCodec, targetScaling=$targetScaling, transcode=$transcode]'; | ||||||
| 
 | 
 | ||||||
|   Map<String, dynamic> toJson() { |   Map<String, dynamic> toJson() { | ||||||
|     final json = <String, dynamic>{}; |     final json = <String, dynamic>{}; | ||||||
| @@ -62,7 +62,7 @@ class SystemConfigFFmpegDto { | |||||||
|       json[r'targetVideoCodec'] = this.targetVideoCodec; |       json[r'targetVideoCodec'] = this.targetVideoCodec; | ||||||
|       json[r'targetAudioCodec'] = this.targetAudioCodec; |       json[r'targetAudioCodec'] = this.targetAudioCodec; | ||||||
|       json[r'targetScaling'] = this.targetScaling; |       json[r'targetScaling'] = this.targetScaling; | ||||||
|       json[r'transcodeAll'] = this.transcodeAll; |       json[r'transcode'] = this.transcode; | ||||||
|     return json; |     return json; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @@ -90,7 +90,7 @@ class SystemConfigFFmpegDto { | |||||||
|         targetVideoCodec: mapValueOfType<String>(json, r'targetVideoCodec')!, |         targetVideoCodec: mapValueOfType<String>(json, r'targetVideoCodec')!, | ||||||
|         targetAudioCodec: mapValueOfType<String>(json, r'targetAudioCodec')!, |         targetAudioCodec: mapValueOfType<String>(json, r'targetAudioCodec')!, | ||||||
|         targetScaling: mapValueOfType<String>(json, r'targetScaling')!, |         targetScaling: mapValueOfType<String>(json, r'targetScaling')!, | ||||||
|         transcodeAll: mapValueOfType<bool>(json, r'transcodeAll')!, |         transcode: SystemConfigFFmpegDtoTranscodeEnum.fromJson(json[r'transcode'])!, | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     return null; |     return null; | ||||||
| @@ -145,7 +145,84 @@ class SystemConfigFFmpegDto { | |||||||
|     'targetVideoCodec', |     'targetVideoCodec', | ||||||
|     'targetAudioCodec', |     'targetAudioCodec', | ||||||
|     'targetScaling', |     'targetScaling', | ||||||
|     'transcodeAll', |     'transcode', | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class SystemConfigFFmpegDtoTranscodeEnum { | ||||||
|  |   /// Instantiate a new enum with the provided [value]. | ||||||
|  |   const SystemConfigFFmpegDtoTranscodeEnum._(this.value); | ||||||
|  | 
 | ||||||
|  |   /// The underlying value of this enum member. | ||||||
|  |   final String value; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   String toString() => value; | ||||||
|  | 
 | ||||||
|  |   String toJson() => value; | ||||||
|  | 
 | ||||||
|  |   static const all = SystemConfigFFmpegDtoTranscodeEnum._(r'all'); | ||||||
|  |   static const optimal = SystemConfigFFmpegDtoTranscodeEnum._(r'optimal'); | ||||||
|  |   static const required_ = SystemConfigFFmpegDtoTranscodeEnum._(r'required'); | ||||||
|  | 
 | ||||||
|  |   /// List of all possible values in this [enum][SystemConfigFFmpegDtoTranscodeEnum]. | ||||||
|  |   static const values = <SystemConfigFFmpegDtoTranscodeEnum>[ | ||||||
|  |     all, | ||||||
|  |     optimal, | ||||||
|  |     required_, | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   static SystemConfigFFmpegDtoTranscodeEnum? fromJson(dynamic value) => SystemConfigFFmpegDtoTranscodeEnumTypeTransformer().decode(value); | ||||||
|  | 
 | ||||||
|  |   static List<SystemConfigFFmpegDtoTranscodeEnum>? listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <SystemConfigFFmpegDtoTranscodeEnum>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = SystemConfigFFmpegDtoTranscodeEnum.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Transformation class that can [encode] an instance of [SystemConfigFFmpegDtoTranscodeEnum] to String, | ||||||
|  | /// and [decode] dynamic data back to [SystemConfigFFmpegDtoTranscodeEnum]. | ||||||
|  | class SystemConfigFFmpegDtoTranscodeEnumTypeTransformer { | ||||||
|  |   factory SystemConfigFFmpegDtoTranscodeEnumTypeTransformer() => _instance ??= const SystemConfigFFmpegDtoTranscodeEnumTypeTransformer._(); | ||||||
|  | 
 | ||||||
|  |   const SystemConfigFFmpegDtoTranscodeEnumTypeTransformer._(); | ||||||
|  | 
 | ||||||
|  |   String encode(SystemConfigFFmpegDtoTranscodeEnum data) => data.value; | ||||||
|  | 
 | ||||||
|  |   /// Decodes a [dynamic value][data] to a SystemConfigFFmpegDtoTranscodeEnum. | ||||||
|  |   /// | ||||||
|  |   /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, | ||||||
|  |   /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] | ||||||
|  |   /// cannot be decoded successfully, then an [UnimplementedError] is thrown. | ||||||
|  |   /// | ||||||
|  |   /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, | ||||||
|  |   /// and users are still using an old app with the old code. | ||||||
|  |   SystemConfigFFmpegDtoTranscodeEnum? decode(dynamic data, {bool allowNull = true}) { | ||||||
|  |     if (data != null) { | ||||||
|  |       switch (data.toString()) { | ||||||
|  |         case r'all': return SystemConfigFFmpegDtoTranscodeEnum.all; | ||||||
|  |         case r'optimal': return SystemConfigFFmpegDtoTranscodeEnum.optimal; | ||||||
|  |         case r'required': return SystemConfigFFmpegDtoTranscodeEnum.required_; | ||||||
|  |         default: | ||||||
|  |           if (!allowNull) { | ||||||
|  |             throw ArgumentError('Unknown enum value to decode: $data'); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Singleton [SystemConfigFFmpegDtoTranscodeEnumTypeTransformer] instance. | ||||||
|  |   static SystemConfigFFmpegDtoTranscodeEnumTypeTransformer? _instance; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|   | |||||||
| @@ -41,8 +41,8 @@ void main() { | |||||||
|       // TODO |       // TODO | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // bool transcodeAll |     // String transcode | ||||||
|     test('to test the property `transcodeAll`', () async { |     test('to test the property `transcode`', () async { | ||||||
|       // TODO |       // TODO | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -8,10 +8,11 @@ import { | |||||||
|   QueueName, |   QueueName, | ||||||
|   StorageCore, |   StorageCore, | ||||||
|   StorageFolder, |   StorageFolder, | ||||||
|  |   SystemConfigFFmpegDto, | ||||||
|   SystemConfigService, |   SystemConfigService, | ||||||
|   WithoutProperty, |   WithoutProperty, | ||||||
| } from '@app/domain'; | } from '@app/domain'; | ||||||
| import { AssetEntity, AssetType } from '@app/infra/db/entities'; | import { AssetEntity, AssetType, TranscodePreset } from '@app/infra/db/entities'; | ||||||
| import { Process, Processor } from '@nestjs/bull'; | import { Process, Processor } from '@nestjs/bull'; | ||||||
| import { Inject, Logger } from '@nestjs/common'; | import { Inject, Logger } from '@nestjs/common'; | ||||||
| import { Job } from 'bull'; | import { Job } from 'bull'; | ||||||
| @@ -74,10 +75,41 @@ export class VideoTranscodeProcessor { | |||||||
|   async runVideoEncode(asset: AssetEntity, savedEncodedPath: string): Promise<void> { |   async runVideoEncode(asset: AssetEntity, savedEncodedPath: string): Promise<void> { | ||||||
|     const config = await this.systemConfigService.getConfig(); |     const config = await this.systemConfigService.getConfig(); | ||||||
|  |  | ||||||
|     if (config.ffmpeg.transcodeAll) { |     const transcode = await this.needsTranscoding(asset, config.ffmpeg); | ||||||
|  |     if (transcode) { | ||||||
|  |       //TODO: If video or audio are already the correct format, don't re-encode, copy the stream | ||||||
|       return this.runFFMPEGPipeLine(asset, savedEncodedPath); |       return this.runFFMPEGPipeLine(asset, savedEncodedPath); | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async needsTranscoding(asset: AssetEntity, ffmpegConfig: SystemConfigFFmpegDto): Promise<boolean> { | ||||||
|  |     switch (ffmpegConfig.transcode) { | ||||||
|  |       case TranscodePreset.ALL: | ||||||
|  |         return true; | ||||||
|  |  | ||||||
|  |       case TranscodePreset.REQUIRED: | ||||||
|  |         { | ||||||
|  |           const videoStream = await this.getVideoStream(asset); | ||||||
|  |           if (videoStream.codec_name !== ffmpegConfig.targetVideoCodec) { | ||||||
|  |             return true; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case TranscodePreset.OPTIMAL: { | ||||||
|  |         const videoStream = await this.getVideoStream(asset); | ||||||
|  |         if (videoStream.codec_name !== ffmpegConfig.targetVideoCodec) { | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const videoHeightThreshold = 1080; | ||||||
|  |         return !videoStream.height || videoStream.height > videoHeightThreshold; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async getVideoStream(asset: AssetEntity): Promise<ffmpeg.FfprobeStream> { | ||||||
|     const videoInfo = await this.runFFProbePipeline(asset); |     const videoInfo = await this.runFFProbePipeline(asset); | ||||||
|  |  | ||||||
|     const videoStreams = videoInfo.streams.filter((stream) => { |     const videoStreams = videoInfo.streams.filter((stream) => { | ||||||
| @@ -90,10 +122,7 @@ export class VideoTranscodeProcessor { | |||||||
|       return stream2Frames - stream1Frames; |       return stream2Frames - stream1Frames; | ||||||
|     })[0]; |     })[0]; | ||||||
|  |  | ||||||
|     //TODO: If video or audio are already the correct format, don't re-encode, copy the stream |     return longestVideoStream; | ||||||
|     if (longestVideoStream.codec_name !== config.ffmpeg.targetVideoCodec) { |  | ||||||
|       return this.runFFMPEGPipeLine(asset, savedEncodedPath); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise<void> { |   async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise<void> { | ||||||
|   | |||||||
| @@ -4601,8 +4601,13 @@ | |||||||
|           "targetScaling": { |           "targetScaling": { | ||||||
|             "type": "string" |             "type": "string" | ||||||
|           }, |           }, | ||||||
|           "transcodeAll": { |           "transcode": { | ||||||
|             "type": "boolean" |             "type": "string", | ||||||
|  |             "enum": [ | ||||||
|  |               "all", | ||||||
|  |               "optimal", | ||||||
|  |               "required" | ||||||
|  |             ] | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         "required": [ |         "required": [ | ||||||
| @@ -4611,7 +4616,7 @@ | |||||||
|           "targetVideoCodec", |           "targetVideoCodec", | ||||||
|           "targetAudioCodec", |           "targetAudioCodec", | ||||||
|           "targetScaling", |           "targetScaling", | ||||||
|           "transcodeAll" |           "transcode" | ||||||
|         ] |         ] | ||||||
|       }, |       }, | ||||||
|       "SystemConfigOAuthDto": { |       "SystemConfigOAuthDto": { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import { IsBoolean, IsString } from 'class-validator'; | import { IsEnum, IsString } from 'class-validator'; | ||||||
|  | import { TranscodePreset } from '@app/infra/db/entities'; | ||||||
|  |  | ||||||
| export class SystemConfigFFmpegDto { | export class SystemConfigFFmpegDto { | ||||||
|   @IsString() |   @IsString() | ||||||
| @@ -16,6 +17,6 @@ export class SystemConfigFFmpegDto { | |||||||
|   @IsString() |   @IsString() | ||||||
|   targetScaling!: string; |   targetScaling!: string; | ||||||
|  |  | ||||||
|   @IsBoolean() |   @IsEnum(TranscodePreset) | ||||||
|   transcodeAll!: boolean; |   transcode!: TranscodePreset; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { SystemConfig, SystemConfigEntity, SystemConfigKey } from '@app/infra/db/entities'; | import { SystemConfig, SystemConfigEntity, SystemConfigKey, TranscodePreset } from '@app/infra/db/entities'; | ||||||
| import { BadRequestException, Injectable, Logger } from '@nestjs/common'; | import { BadRequestException, Injectable, Logger } from '@nestjs/common'; | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
| import { Subject } from 'rxjs'; | import { Subject } from 'rxjs'; | ||||||
| @@ -14,7 +14,7 @@ const defaults: SystemConfig = Object.freeze({ | |||||||
|     targetVideoCodec: 'h264', |     targetVideoCodec: 'h264', | ||||||
|     targetAudioCodec: 'aac', |     targetAudioCodec: 'aac', | ||||||
|     targetScaling: '1280:-2', |     targetScaling: '1280:-2', | ||||||
|     transcodeAll: false, |     transcode: TranscodePreset.REQUIRED, | ||||||
|   }, |   }, | ||||||
|   oauth: { |   oauth: { | ||||||
|     enabled: false, |     enabled: false, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { SystemConfigEntity, SystemConfigKey } from '@app/infra/db/entities'; | import { SystemConfigEntity, SystemConfigKey, TranscodePreset } from '@app/infra/db/entities'; | ||||||
| import { BadRequestException } from '@nestjs/common'; | import { BadRequestException } from '@nestjs/common'; | ||||||
| import { newJobRepositoryMock, newSystemConfigRepositoryMock, systemConfigStub } from '../../test'; | import { newJobRepositoryMock, newSystemConfigRepositoryMock, systemConfigStub } from '../../test'; | ||||||
| import { IJobRepository, JobName } from '../job'; | import { IJobRepository, JobName } from '../job'; | ||||||
| @@ -18,7 +18,7 @@ const updatedConfig = Object.freeze({ | |||||||
|     targetAudioCodec: 'aac', |     targetAudioCodec: 'aac', | ||||||
|     targetScaling: '1280:-2', |     targetScaling: '1280:-2', | ||||||
|     targetVideoCodec: 'h264', |     targetVideoCodec: 'h264', | ||||||
|     transcodeAll: false, |     transcode: TranscodePreset.REQUIRED, | ||||||
|   }, |   }, | ||||||
|   oauth: { |   oauth: { | ||||||
|     autoLaunch: true, |     autoLaunch: true, | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import { | |||||||
|   SharedLinkEntity, |   SharedLinkEntity, | ||||||
|   SharedLinkType, |   SharedLinkType, | ||||||
|   SystemConfig, |   SystemConfig, | ||||||
|  |   TranscodePreset, | ||||||
|   UserEntity, |   UserEntity, | ||||||
|   UserTokenEntity, |   UserTokenEntity, | ||||||
| } from '@app/infra/db/entities'; | } from '@app/infra/db/entities'; | ||||||
| @@ -401,7 +402,7 @@ export const systemConfigStub = { | |||||||
|       targetAudioCodec: 'aac', |       targetAudioCodec: 'aac', | ||||||
|       targetScaling: '1280:-2', |       targetScaling: '1280:-2', | ||||||
|       targetVideoCodec: 'h264', |       targetVideoCodec: 'h264', | ||||||
|       transcodeAll: false, |       transcode: TranscodePreset.REQUIRED, | ||||||
|     }, |     }, | ||||||
|     oauth: { |     oauth: { | ||||||
|       autoLaunch: false, |       autoLaunch: false, | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ export enum SystemConfigKey { | |||||||
|   FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg.targetVideoCodec', |   FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg.targetVideoCodec', | ||||||
|   FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg.targetAudioCodec', |   FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg.targetAudioCodec', | ||||||
|   FFMPEG_TARGET_SCALING = 'ffmpeg.targetScaling', |   FFMPEG_TARGET_SCALING = 'ffmpeg.targetScaling', | ||||||
|   FFMPEG_TRANSCODE_ALL = 'ffmpeg.transcodeAll', |   FFMPEG_TRANSCODE = 'ffmpeg.transcode', | ||||||
|   OAUTH_ENABLED = 'oauth.enabled', |   OAUTH_ENABLED = 'oauth.enabled', | ||||||
|   OAUTH_ISSUER_URL = 'oauth.issuerUrl', |   OAUTH_ISSUER_URL = 'oauth.issuerUrl', | ||||||
|   OAUTH_CLIENT_ID = 'oauth.clientId', |   OAUTH_CLIENT_ID = 'oauth.clientId', | ||||||
| @@ -33,6 +33,12 @@ export enum SystemConfigKey { | |||||||
|   STORAGE_TEMPLATE = 'storageTemplate.template', |   STORAGE_TEMPLATE = 'storageTemplate.template', | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export enum TranscodePreset { | ||||||
|  |   ALL = 'all', | ||||||
|  |   OPTIMAL = 'optimal', | ||||||
|  |   REQUIRED = 'required', | ||||||
|  | } | ||||||
|  |  | ||||||
| export interface SystemConfig { | export interface SystemConfig { | ||||||
|   ffmpeg: { |   ffmpeg: { | ||||||
|     crf: string; |     crf: string; | ||||||
| @@ -40,7 +46,7 @@ export interface SystemConfig { | |||||||
|     targetVideoCodec: string; |     targetVideoCodec: string; | ||||||
|     targetAudioCodec: string; |     targetAudioCodec: string; | ||||||
|     targetScaling: string; |     targetScaling: string; | ||||||
|     transcodeAll: boolean; |     transcode: TranscodePreset; | ||||||
|   }; |   }; | ||||||
|   oauth: { |   oauth: { | ||||||
|     enabled: boolean; |     enabled: boolean; | ||||||
|   | |||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | import { MigrationInterface, QueryRunner } from 'typeorm'; | ||||||
|  |  | ||||||
|  | export class UpdateTranscodeOption1679751316282 implements MigrationInterface { | ||||||
|  |   public async up(queryRunner: QueryRunner): Promise<void> { | ||||||
|  |     await queryRunner.query(` | ||||||
|  |           UPDATE system_config | ||||||
|  |           SET  | ||||||
|  |             key = 'ffmpeg.transcode',  | ||||||
|  |             value = '"all"' | ||||||
|  |           WHERE  | ||||||
|  |             key = 'ffmpeg.transcodeAll' AND value = 'true' | ||||||
|  |         `); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public async down(queryRunner: QueryRunner): Promise<void> { | ||||||
|  |     await queryRunner.query(` | ||||||
|  |           UPDATE system_config | ||||||
|  |           SET  | ||||||
|  |             key = 'ffmpeg.transcodeAll', | ||||||
|  |             value = 'true' | ||||||
|  |           WHERE  | ||||||
|  |             key = 'ffmpeg.transcode' AND value = '"all"' | ||||||
|  |         `); | ||||||
|  |  | ||||||
|  |     await queryRunner.query(`DELETE FROM "system_config" WHERE key = 'ffmpeg.transcode'`); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -1987,11 +1987,20 @@ export interface SystemConfigFFmpegDto { | |||||||
|     'targetScaling': string; |     'targetScaling': string; | ||||||
|     /** |     /** | ||||||
|      *  |      *  | ||||||
|      * @type {boolean} |      * @type {string} | ||||||
|      * @memberof SystemConfigFFmpegDto |      * @memberof SystemConfigFFmpegDto | ||||||
|      */ |      */ | ||||||
|     'transcodeAll': boolean; |     'transcode': SystemConfigFFmpegDtoTranscodeEnum; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export const SystemConfigFFmpegDtoTranscodeEnum = { | ||||||
|  |     All: 'all', | ||||||
|  |     Optimal: 'optimal', | ||||||
|  |     Required: 'required' | ||||||
|  | } as const; | ||||||
|  | 
 | ||||||
|  | export type SystemConfigFFmpegDtoTranscodeEnum = typeof SystemConfigFFmpegDtoTranscodeEnum[keyof typeof SystemConfigFFmpegDtoTranscodeEnum]; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  * @export |  * @export | ||||||
|   | |||||||
| @@ -3,11 +3,10 @@ | |||||||
| 		notificationController, | 		notificationController, | ||||||
| 		NotificationType | 		NotificationType | ||||||
| 	} from '$lib/components/shared-components/notification/notification'; | 	} from '$lib/components/shared-components/notification/notification'; | ||||||
| 	import { api, SystemConfigFFmpegDto } from '@api'; | 	import { api, SystemConfigFFmpegDto, SystemConfigFFmpegDtoTranscodeEnum } from '@api'; | ||||||
| 	import SettingButtonsRow from '../setting-buttons-row.svelte'; | 	import SettingButtonsRow from '../setting-buttons-row.svelte'; | ||||||
| 	import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; | 	import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; | ||||||
| 	import SettingSelect from '../setting-select.svelte'; | 	import SettingSelect from '../setting-select.svelte'; | ||||||
| 	import SettingSwitch from '../setting-switch.svelte'; |  | ||||||
| 	import { isEqual } from 'lodash-es'; | 	import { isEqual } from 'lodash-es'; | ||||||
| 	import { fade } from 'svelte/transition'; | 	import { fade } from 'svelte/transition'; | ||||||
|  |  | ||||||
| @@ -105,7 +104,12 @@ | |||||||
| 					<SettingSelect | 					<SettingSelect | ||||||
| 						label="VIDEO CODEC (-vcodec)" | 						label="VIDEO CODEC (-vcodec)" | ||||||
| 						bind:value={ffmpegConfig.targetVideoCodec} | 						bind:value={ffmpegConfig.targetVideoCodec} | ||||||
| 						options={['h264', 'hevc', 'vp9']} | 						options={[ | ||||||
|  | 							{ value: 'h264', text: 'h264' }, | ||||||
|  | 							{ value: 'hevc', text: 'hevc' }, | ||||||
|  | 							{ value: 'vp9', text: 'vp9' } | ||||||
|  | 						]} | ||||||
|  | 						name="vcodec" | ||||||
| 						isEdited={!(ffmpegConfig.targetVideoCodec == savedConfig.targetVideoCodec)} | 						isEdited={!(ffmpegConfig.targetVideoCodec == savedConfig.targetVideoCodec)} | ||||||
| 					/> | 					/> | ||||||
|  |  | ||||||
| @@ -117,11 +121,22 @@ | |||||||
| 						isEdited={!(ffmpegConfig.targetScaling == savedConfig.targetScaling)} | 						isEdited={!(ffmpegConfig.targetScaling == savedConfig.targetScaling)} | ||||||
| 					/> | 					/> | ||||||
|  |  | ||||||
| 					<SettingSwitch | 					<SettingSelect | ||||||
| 						title="TRANSCODE ALL" | 						label="TRANSCODE" | ||||||
| 						subtitle="Transcode all files, even if they already match the specified format?" | 						bind:value={ffmpegConfig.transcode} | ||||||
| 						bind:checked={ffmpegConfig.transcodeAll} | 						name="transcode" | ||||||
| 						isEdited={!(ffmpegConfig.transcodeAll == savedConfig.transcodeAll)} | 						options={[ | ||||||
|  | 							{ value: SystemConfigFFmpegDtoTranscodeEnum.All, text: 'All videos' }, | ||||||
|  | 							{ | ||||||
|  | 								value: SystemConfigFFmpegDtoTranscodeEnum.Optimal, | ||||||
|  | 								text: 'Videos higher than 1080p or not in the desired format' | ||||||
|  | 							}, | ||||||
|  | 							{ | ||||||
|  | 								value: SystemConfigFFmpegDtoTranscodeEnum.Required, | ||||||
|  | 								text: 'Only videos not in the desired format' | ||||||
|  | 							} | ||||||
|  | 						]} | ||||||
|  | 						isEdited={!(ffmpegConfig.transcode == savedConfig.transcode)} | ||||||
| 					/> | 					/> | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,8 +3,9 @@ | |||||||
| 	import { fly } from 'svelte/transition'; | 	import { fly } from 'svelte/transition'; | ||||||
|  |  | ||||||
| 	export let value: string; | 	export let value: string; | ||||||
| 	export let options: string[]; | 	export let options: { value: string; text: string }[]; | ||||||
| 	export let label = ''; | 	export let label = ''; | ||||||
|  | 	export let name = ''; | ||||||
| 	export let isEdited = false; | 	export let isEdited = false; | ||||||
|  |  | ||||||
| 	const handleChange = (e: Event) => { | 	const handleChange = (e: Event) => { | ||||||
| @@ -14,7 +15,7 @@ | |||||||
|  |  | ||||||
| <div class="w-full"> | <div class="w-full"> | ||||||
| 	<div class={`flex place-items-center gap-1 h-[26px]`}> | 	<div class={`flex place-items-center gap-1 h-[26px]`}> | ||||||
| 		<label class={`immich-form-label text-sm`} for={label}>{label}</label> | 		<label class={`immich-form-label text-sm`} for="{name}-select">{label}</label> | ||||||
|  |  | ||||||
| 		{#if isEdited} | 		{#if isEdited} | ||||||
| 			<div | 			<div | ||||||
| @@ -27,13 +28,13 @@ | |||||||
| 	</div> | 	</div> | ||||||
| 	<select | 	<select | ||||||
| 		class="immich-form-input w-full" | 		class="immich-form-input w-full" | ||||||
| 		name="presets" | 		{name} | ||||||
| 		id="preset-select" | 		id="{name}-select" | ||||||
| 		bind:value | 		bind:value | ||||||
| 		on:change={handleChange} | 		on:change={handleChange} | ||||||
| 	> | 	> | ||||||
| 		{#each options as option} | 		{#each options as option} | ||||||
| 			<option value={option}>{option}</option> | 			<option value={option.value}>{option.text}</option> | ||||||
| 		{/each} | 		{/each} | ||||||
| 	</select> | 	</select> | ||||||
| </div> | </div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user