mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	Merge branch 'main' of github.com:immich-app/immich
This commit is contained in:
		
						commit
						0c61521521
					
				
							
								
								
									
										5
									
								
								.github/workflows/prepare-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/prepare-release.yml
									
									
									
									
										vendored
									
									
								
							@ -41,8 +41,9 @@ jobs:
 | 
				
			|||||||
        id: push-tag
 | 
					        id: push-tag
 | 
				
			||||||
        uses: EndBug/add-and-commit@v9
 | 
					        uses: EndBug/add-and-commit@v9
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          author_name: Immich Release Bot
 | 
					          author_name: Alex The Bot
 | 
				
			||||||
          author_email: bot@immich.app
 | 
					          author_email: alex.tran1502@gmail.com
 | 
				
			||||||
 | 
					          default_author: user_info 
 | 
				
			||||||
          message: "Version ${{ env.IMMICH_VERSION }}"
 | 
					          message: "Version ${{ env.IMMICH_VERSION }}"
 | 
				
			||||||
          tag: ${{ env.IMMICH_VERSION }}
 | 
					          tag: ${{ env.IMMICH_VERSION }}
 | 
				
			||||||
          push: true
 | 
					          push: true
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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": {
 | 
				
			||||||
 | 
				
			|||||||
@ -133,11 +133,10 @@ export class StorageTemplateCore {
 | 
				
			|||||||
    const substitutions: Record<string, string> = {
 | 
					    const substitutions: Record<string, string> = {
 | 
				
			||||||
      filename,
 | 
					      filename,
 | 
				
			||||||
      ext,
 | 
					      ext,
 | 
				
			||||||
 | 
					      filetype: asset.type == AssetType.IMAGE ? 'IMG' : 'VID',
 | 
				
			||||||
 | 
					      filetypefull: asset.type == AssetType.IMAGE ? 'IMAGE' : 'VIDEO',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const fileType = asset.type == AssetType.IMAGE ? 'IMG' : 'VID';
 | 
					 | 
				
			||||||
    const fileTypeFull = asset.type == AssetType.IMAGE ? 'IMAGE' : 'VIDEO';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const dt = luxon.DateTime.fromISO(new Date(asset.fileCreatedAt).toISOString());
 | 
					    const dt = luxon.DateTime.fromISO(new Date(asset.fileCreatedAt).toISOString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dateTokens = [
 | 
					    const dateTokens = [
 | 
				
			||||||
@ -153,10 +152,6 @@ export class StorageTemplateCore {
 | 
				
			|||||||
      substitutions[token] = dt.toFormat(token);
 | 
					      substitutions[token] = dt.toFormat(token);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Support file type token
 | 
					 | 
				
			||||||
    substitutions.filetype = fileType;
 | 
					 | 
				
			||||||
    substitutions.filetypefull = fileTypeFull;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return template(substitutions);
 | 
					    return template(substitutions);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user