mirror of
https://github.com/immich-app/immich.git
synced 2025-12-06 12:51:32 -08:00
Compare commits
1 Commits
refactor/m
...
feat/serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f05e9ca9 |
9
mobile/openapi/README.md
generated
9
mobile/openapi/README.md
generated
@@ -129,6 +129,7 @@ Class | Method | HTTP request | Description
|
||||
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive |
|
||||
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info |
|
||||
*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates |
|
||||
*EditorApi* | [**createAssetFromEdits**](doc//EditorApi.md#createassetfromedits) | **POST** /editor |
|
||||
*FacesApi* | [**getFaces**](doc//FacesApi.md#getfaces) | **GET** /faces |
|
||||
*FacesApi* | [**reassignFacesById**](doc//FacesApi.md#reassignfacesbyid) | **PUT** /faces/{id} |
|
||||
*FileReportsApi* | [**fixAuditFiles**](doc//FileReportsApi.md#fixauditfiles) | **POST** /reports/fix |
|
||||
@@ -314,6 +315,14 @@ Class | Method | HTTP request | Description
|
||||
- [DownloadUpdate](doc//DownloadUpdate.md)
|
||||
- [DuplicateDetectionConfig](doc//DuplicateDetectionConfig.md)
|
||||
- [DuplicateResponseDto](doc//DuplicateResponseDto.md)
|
||||
- [EditorActionAdjust](doc//EditorActionAdjust.md)
|
||||
- [EditorActionBlur](doc//EditorActionBlur.md)
|
||||
- [EditorActionCrop](doc//EditorActionCrop.md)
|
||||
- [EditorActionRotate](doc//EditorActionRotate.md)
|
||||
- [EditorActionType](doc//EditorActionType.md)
|
||||
- [EditorCreateAssetDto](doc//EditorCreateAssetDto.md)
|
||||
- [EditorCreateAssetDtoEditsInner](doc//EditorCreateAssetDtoEditsInner.md)
|
||||
- [EditorCropRegion](doc//EditorCropRegion.md)
|
||||
- [EmailNotificationsResponse](doc//EmailNotificationsResponse.md)
|
||||
- [EmailNotificationsUpdate](doc//EmailNotificationsUpdate.md)
|
||||
- [EntityType](doc//EntityType.md)
|
||||
|
||||
9
mobile/openapi/lib/api.dart
generated
9
mobile/openapi/lib/api.dart
generated
@@ -38,6 +38,7 @@ part 'api/authentication_api.dart';
|
||||
part 'api/deprecated_api.dart';
|
||||
part 'api/download_api.dart';
|
||||
part 'api/duplicates_api.dart';
|
||||
part 'api/editor_api.dart';
|
||||
part 'api/faces_api.dart';
|
||||
part 'api/file_reports_api.dart';
|
||||
part 'api/jobs_api.dart';
|
||||
@@ -125,6 +126,14 @@ part 'model/download_response_dto.dart';
|
||||
part 'model/download_update.dart';
|
||||
part 'model/duplicate_detection_config.dart';
|
||||
part 'model/duplicate_response_dto.dart';
|
||||
part 'model/editor_action_adjust.dart';
|
||||
part 'model/editor_action_blur.dart';
|
||||
part 'model/editor_action_crop.dart';
|
||||
part 'model/editor_action_rotate.dart';
|
||||
part 'model/editor_action_type.dart';
|
||||
part 'model/editor_create_asset_dto.dart';
|
||||
part 'model/editor_create_asset_dto_edits_inner.dart';
|
||||
part 'model/editor_crop_region.dart';
|
||||
part 'model/email_notifications_response.dart';
|
||||
part 'model/email_notifications_update.dart';
|
||||
part 'model/entity_type.dart';
|
||||
|
||||
65
mobile/openapi/lib/api/editor_api.dart
generated
Normal file
65
mobile/openapi/lib/api/editor_api.dart
generated
Normal file
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class EditorApi {
|
||||
EditorApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Performs an HTTP 'POST /editor' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [EditorCreateAssetDto] editorCreateAssetDto (required):
|
||||
Future<Response> createAssetFromEditsWithHttpInfo(EditorCreateAssetDto editorCreateAssetDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/editor';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = editorCreateAssetDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [EditorCreateAssetDto] editorCreateAssetDto (required):
|
||||
Future<AssetResponseDto?> createAssetFromEdits(EditorCreateAssetDto editorCreateAssetDto,) async {
|
||||
final response = await createAssetFromEditsWithHttpInfo(editorCreateAssetDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetResponseDto',) as AssetResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
16
mobile/openapi/lib/api_client.dart
generated
16
mobile/openapi/lib/api_client.dart
generated
@@ -308,6 +308,22 @@ class ApiClient {
|
||||
return DuplicateDetectionConfig.fromJson(value);
|
||||
case 'DuplicateResponseDto':
|
||||
return DuplicateResponseDto.fromJson(value);
|
||||
case 'EditorActionAdjust':
|
||||
return EditorActionAdjust.fromJson(value);
|
||||
case 'EditorActionBlur':
|
||||
return EditorActionBlur.fromJson(value);
|
||||
case 'EditorActionCrop':
|
||||
return EditorActionCrop.fromJson(value);
|
||||
case 'EditorActionRotate':
|
||||
return EditorActionRotate.fromJson(value);
|
||||
case 'EditorActionType':
|
||||
return EditorActionTypeTypeTransformer().decode(value);
|
||||
case 'EditorCreateAssetDto':
|
||||
return EditorCreateAssetDto.fromJson(value);
|
||||
case 'EditorCreateAssetDtoEditsInner':
|
||||
return EditorCreateAssetDtoEditsInner.fromJson(value);
|
||||
case 'EditorCropRegion':
|
||||
return EditorCropRegion.fromJson(value);
|
||||
case 'EmailNotificationsResponse':
|
||||
return EmailNotificationsResponse.fromJson(value);
|
||||
case 'EmailNotificationsUpdate':
|
||||
|
||||
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
@@ -82,6 +82,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is Colorspace) {
|
||||
return ColorspaceTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is EditorActionType) {
|
||||
return EditorActionTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is EntityType) {
|
||||
return EntityTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
||||
130
mobile/openapi/lib/model/editor_action_adjust.dart
generated
Normal file
130
mobile/openapi/lib/model/editor_action_adjust.dart
generated
Normal file
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EditorActionAdjust {
|
||||
/// Returns a new [EditorActionAdjust] instance.
|
||||
EditorActionAdjust({
|
||||
required this.action,
|
||||
required this.brightness,
|
||||
required this.hue,
|
||||
required this.lightness,
|
||||
required this.saturation,
|
||||
});
|
||||
|
||||
EditorActionType action;
|
||||
|
||||
int brightness;
|
||||
|
||||
int hue;
|
||||
|
||||
int lightness;
|
||||
|
||||
int saturation;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EditorActionAdjust &&
|
||||
other.action == action &&
|
||||
other.brightness == brightness &&
|
||||
other.hue == hue &&
|
||||
other.lightness == lightness &&
|
||||
other.saturation == saturation;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(brightness.hashCode) +
|
||||
(hue.hashCode) +
|
||||
(lightness.hashCode) +
|
||||
(saturation.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EditorActionAdjust[action=$action, brightness=$brightness, hue=$hue, lightness=$lightness, saturation=$saturation]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'brightness'] = this.brightness;
|
||||
json[r'hue'] = this.hue;
|
||||
json[r'lightness'] = this.lightness;
|
||||
json[r'saturation'] = this.saturation;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EditorActionAdjust] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EditorActionAdjust? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EditorActionAdjust(
|
||||
action: EditorActionType.fromJson(json[r'action'])!,
|
||||
brightness: mapValueOfType<int>(json, r'brightness')!,
|
||||
hue: mapValueOfType<int>(json, r'hue')!,
|
||||
lightness: mapValueOfType<int>(json, r'lightness')!,
|
||||
saturation: mapValueOfType<int>(json, r'saturation')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EditorActionAdjust> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorActionAdjust>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorActionAdjust.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EditorActionAdjust> mapFromJson(dynamic json) {
|
||||
final map = <String, EditorActionAdjust>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EditorActionAdjust.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EditorActionAdjust-objects as value to a dart map
|
||||
static Map<String, List<EditorActionAdjust>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EditorActionAdjust>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EditorActionAdjust.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'brightness',
|
||||
'hue',
|
||||
'lightness',
|
||||
'saturation',
|
||||
};
|
||||
}
|
||||
|
||||
98
mobile/openapi/lib/model/editor_action_blur.dart
generated
Normal file
98
mobile/openapi/lib/model/editor_action_blur.dart
generated
Normal file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EditorActionBlur {
|
||||
/// Returns a new [EditorActionBlur] instance.
|
||||
EditorActionBlur({
|
||||
required this.action,
|
||||
});
|
||||
|
||||
EditorActionType action;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EditorActionBlur &&
|
||||
other.action == action;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EditorActionBlur[action=$action]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EditorActionBlur] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EditorActionBlur? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EditorActionBlur(
|
||||
action: EditorActionType.fromJson(json[r'action'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EditorActionBlur> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorActionBlur>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorActionBlur.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EditorActionBlur> mapFromJson(dynamic json) {
|
||||
final map = <String, EditorActionBlur>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EditorActionBlur.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EditorActionBlur-objects as value to a dart map
|
||||
static Map<String, List<EditorActionBlur>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EditorActionBlur>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EditorActionBlur.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
};
|
||||
}
|
||||
|
||||
106
mobile/openapi/lib/model/editor_action_crop.dart
generated
Normal file
106
mobile/openapi/lib/model/editor_action_crop.dart
generated
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EditorActionCrop {
|
||||
/// Returns a new [EditorActionCrop] instance.
|
||||
EditorActionCrop({
|
||||
required this.action,
|
||||
required this.region,
|
||||
});
|
||||
|
||||
EditorActionType action;
|
||||
|
||||
EditorCropRegion region;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EditorActionCrop &&
|
||||
other.action == action &&
|
||||
other.region == region;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(region.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EditorActionCrop[action=$action, region=$region]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'region'] = this.region;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EditorActionCrop] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EditorActionCrop? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EditorActionCrop(
|
||||
action: EditorActionType.fromJson(json[r'action'])!,
|
||||
region: EditorCropRegion.fromJson(json[r'region'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EditorActionCrop> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorActionCrop>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorActionCrop.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EditorActionCrop> mapFromJson(dynamic json) {
|
||||
final map = <String, EditorActionCrop>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EditorActionCrop.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EditorActionCrop-objects as value to a dart map
|
||||
static Map<String, List<EditorActionCrop>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EditorActionCrop>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EditorActionCrop.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'region',
|
||||
};
|
||||
}
|
||||
|
||||
106
mobile/openapi/lib/model/editor_action_rotate.dart
generated
Normal file
106
mobile/openapi/lib/model/editor_action_rotate.dart
generated
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EditorActionRotate {
|
||||
/// Returns a new [EditorActionRotate] instance.
|
||||
EditorActionRotate({
|
||||
required this.action,
|
||||
required this.angle,
|
||||
});
|
||||
|
||||
EditorActionType action;
|
||||
|
||||
int angle;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EditorActionRotate &&
|
||||
other.action == action &&
|
||||
other.angle == angle;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(angle.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EditorActionRotate[action=$action, angle=$angle]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'angle'] = this.angle;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EditorActionRotate] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EditorActionRotate? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EditorActionRotate(
|
||||
action: EditorActionType.fromJson(json[r'action'])!,
|
||||
angle: mapValueOfType<int>(json, r'angle')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EditorActionRotate> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorActionRotate>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorActionRotate.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EditorActionRotate> mapFromJson(dynamic json) {
|
||||
final map = <String, EditorActionRotate>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EditorActionRotate.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EditorActionRotate-objects as value to a dart map
|
||||
static Map<String, List<EditorActionRotate>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EditorActionRotate>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EditorActionRotate.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'angle',
|
||||
};
|
||||
}
|
||||
|
||||
91
mobile/openapi/lib/model/editor_action_type.dart
generated
Normal file
91
mobile/openapi/lib/model/editor_action_type.dart
generated
Normal file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class EditorActionType {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const EditorActionType._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const crop = EditorActionType._(r'crop');
|
||||
static const rotate = EditorActionType._(r'rotate');
|
||||
static const blur = EditorActionType._(r'blur');
|
||||
static const adjust = EditorActionType._(r'adjust');
|
||||
|
||||
/// List of all possible values in this [enum][EditorActionType].
|
||||
static const values = <EditorActionType>[
|
||||
crop,
|
||||
rotate,
|
||||
blur,
|
||||
adjust,
|
||||
];
|
||||
|
||||
static EditorActionType? fromJson(dynamic value) => EditorActionTypeTypeTransformer().decode(value);
|
||||
|
||||
static List<EditorActionType> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorActionType>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorActionType.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [EditorActionType] to String,
|
||||
/// and [decode] dynamic data back to [EditorActionType].
|
||||
class EditorActionTypeTypeTransformer {
|
||||
factory EditorActionTypeTypeTransformer() => _instance ??= const EditorActionTypeTypeTransformer._();
|
||||
|
||||
const EditorActionTypeTypeTransformer._();
|
||||
|
||||
String encode(EditorActionType data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a EditorActionType.
|
||||
///
|
||||
/// 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.
|
||||
EditorActionType? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'crop': return EditorActionType.crop;
|
||||
case r'rotate': return EditorActionType.rotate;
|
||||
case r'blur': return EditorActionType.blur;
|
||||
case r'adjust': return EditorActionType.adjust;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [EditorActionTypeTypeTransformer] instance.
|
||||
static EditorActionTypeTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
126
mobile/openapi/lib/model/editor_create_asset_dto.dart
generated
Normal file
126
mobile/openapi/lib/model/editor_create_asset_dto.dart
generated
Normal file
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EditorCreateAssetDto {
|
||||
/// Returns a new [EditorCreateAssetDto] instance.
|
||||
EditorCreateAssetDto({
|
||||
this.edits = const [],
|
||||
required this.id,
|
||||
this.stack,
|
||||
});
|
||||
|
||||
/// list of edits
|
||||
List<EditorCreateAssetDtoEditsInner> edits;
|
||||
|
||||
/// Source asset id
|
||||
String id;
|
||||
|
||||
/// Stack the edit and the original
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? stack;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EditorCreateAssetDto &&
|
||||
_deepEquality.equals(other.edits, edits) &&
|
||||
other.id == id &&
|
||||
other.stack == stack;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(edits.hashCode) +
|
||||
(id.hashCode) +
|
||||
(stack == null ? 0 : stack!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EditorCreateAssetDto[edits=$edits, id=$id, stack=$stack]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'edits'] = this.edits;
|
||||
json[r'id'] = this.id;
|
||||
if (this.stack != null) {
|
||||
json[r'stack'] = this.stack;
|
||||
} else {
|
||||
// json[r'stack'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EditorCreateAssetDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EditorCreateAssetDto? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EditorCreateAssetDto(
|
||||
edits: EditorCreateAssetDtoEditsInner.listFromJson(json[r'edits']),
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
stack: mapValueOfType<bool>(json, r'stack'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EditorCreateAssetDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorCreateAssetDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorCreateAssetDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EditorCreateAssetDto> mapFromJson(dynamic json) {
|
||||
final map = <String, EditorCreateAssetDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EditorCreateAssetDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EditorCreateAssetDto-objects as value to a dart map
|
||||
static Map<String, List<EditorCreateAssetDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EditorCreateAssetDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EditorCreateAssetDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'edits',
|
||||
'id',
|
||||
};
|
||||
}
|
||||
|
||||
146
mobile/openapi/lib/model/editor_create_asset_dto_edits_inner.dart
generated
Normal file
146
mobile/openapi/lib/model/editor_create_asset_dto_edits_inner.dart
generated
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EditorCreateAssetDtoEditsInner {
|
||||
/// Returns a new [EditorCreateAssetDtoEditsInner] instance.
|
||||
EditorCreateAssetDtoEditsInner({
|
||||
required this.action,
|
||||
required this.region,
|
||||
required this.angle,
|
||||
required this.brightness,
|
||||
required this.hue,
|
||||
required this.lightness,
|
||||
required this.saturation,
|
||||
});
|
||||
|
||||
EditorActionType action;
|
||||
|
||||
EditorCropRegion region;
|
||||
|
||||
int angle;
|
||||
|
||||
int brightness;
|
||||
|
||||
int hue;
|
||||
|
||||
int lightness;
|
||||
|
||||
int saturation;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EditorCreateAssetDtoEditsInner &&
|
||||
other.action == action &&
|
||||
other.region == region &&
|
||||
other.angle == angle &&
|
||||
other.brightness == brightness &&
|
||||
other.hue == hue &&
|
||||
other.lightness == lightness &&
|
||||
other.saturation == saturation;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(region.hashCode) +
|
||||
(angle.hashCode) +
|
||||
(brightness.hashCode) +
|
||||
(hue.hashCode) +
|
||||
(lightness.hashCode) +
|
||||
(saturation.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EditorCreateAssetDtoEditsInner[action=$action, region=$region, angle=$angle, brightness=$brightness, hue=$hue, lightness=$lightness, saturation=$saturation]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'region'] = this.region;
|
||||
json[r'angle'] = this.angle;
|
||||
json[r'brightness'] = this.brightness;
|
||||
json[r'hue'] = this.hue;
|
||||
json[r'lightness'] = this.lightness;
|
||||
json[r'saturation'] = this.saturation;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EditorCreateAssetDtoEditsInner] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EditorCreateAssetDtoEditsInner? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EditorCreateAssetDtoEditsInner(
|
||||
action: EditorActionType.fromJson(json[r'action'])!,
|
||||
region: EditorCropRegion.fromJson(json[r'region'])!,
|
||||
angle: mapValueOfType<int>(json, r'angle')!,
|
||||
brightness: mapValueOfType<int>(json, r'brightness')!,
|
||||
hue: mapValueOfType<int>(json, r'hue')!,
|
||||
lightness: mapValueOfType<int>(json, r'lightness')!,
|
||||
saturation: mapValueOfType<int>(json, r'saturation')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EditorCreateAssetDtoEditsInner> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorCreateAssetDtoEditsInner>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorCreateAssetDtoEditsInner.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EditorCreateAssetDtoEditsInner> mapFromJson(dynamic json) {
|
||||
final map = <String, EditorCreateAssetDtoEditsInner>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EditorCreateAssetDtoEditsInner.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EditorCreateAssetDtoEditsInner-objects as value to a dart map
|
||||
static Map<String, List<EditorCreateAssetDtoEditsInner>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EditorCreateAssetDtoEditsInner>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EditorCreateAssetDtoEditsInner.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'region',
|
||||
'angle',
|
||||
'brightness',
|
||||
'hue',
|
||||
'lightness',
|
||||
'saturation',
|
||||
};
|
||||
}
|
||||
|
||||
122
mobile/openapi/lib/model/editor_crop_region.dart
generated
Normal file
122
mobile/openapi/lib/model/editor_crop_region.dart
generated
Normal file
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EditorCropRegion {
|
||||
/// Returns a new [EditorCropRegion] instance.
|
||||
EditorCropRegion({
|
||||
required this.height,
|
||||
required this.left,
|
||||
required this.top,
|
||||
required this.width,
|
||||
});
|
||||
|
||||
int height;
|
||||
|
||||
int left;
|
||||
|
||||
int top;
|
||||
|
||||
int width;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EditorCropRegion &&
|
||||
other.height == height &&
|
||||
other.left == left &&
|
||||
other.top == top &&
|
||||
other.width == width;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(height.hashCode) +
|
||||
(left.hashCode) +
|
||||
(top.hashCode) +
|
||||
(width.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EditorCropRegion[height=$height, left=$left, top=$top, width=$width]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'height'] = this.height;
|
||||
json[r'left'] = this.left;
|
||||
json[r'top'] = this.top;
|
||||
json[r'width'] = this.width;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EditorCropRegion] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EditorCropRegion? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EditorCropRegion(
|
||||
height: mapValueOfType<int>(json, r'height')!,
|
||||
left: mapValueOfType<int>(json, r'left')!,
|
||||
top: mapValueOfType<int>(json, r'top')!,
|
||||
width: mapValueOfType<int>(json, r'width')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EditorCropRegion> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EditorCropRegion>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EditorCropRegion.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EditorCropRegion> mapFromJson(dynamic json) {
|
||||
final map = <String, EditorCropRegion>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EditorCropRegion.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EditorCropRegion-objects as value to a dart map
|
||||
static Map<String, List<EditorCropRegion>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EditorCropRegion>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EditorCropRegion.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'height',
|
||||
'left',
|
||||
'top',
|
||||
'width',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2469,6 +2469,48 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/editor": {
|
||||
"post": {
|
||||
"operationId": "createAssetFromEdits",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/EditorCreateAssetDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetResponseDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Editor"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/faces": {
|
||||
"get": {
|
||||
"operationId": "getFaces",
|
||||
@@ -8562,6 +8604,144 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EditorActionAdjust": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"$ref": "#/components/schemas/EditorActionType"
|
||||
},
|
||||
"brightness": {
|
||||
"type": "integer"
|
||||
},
|
||||
"hue": {
|
||||
"type": "integer"
|
||||
},
|
||||
"lightness": {
|
||||
"type": "integer"
|
||||
},
|
||||
"saturation": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"brightness",
|
||||
"hue",
|
||||
"lightness",
|
||||
"saturation"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EditorActionBlur": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"$ref": "#/components/schemas/EditorActionType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EditorActionCrop": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"$ref": "#/components/schemas/EditorActionType"
|
||||
},
|
||||
"region": {
|
||||
"$ref": "#/components/schemas/EditorCropRegion"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"region"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EditorActionRotate": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"$ref": "#/components/schemas/EditorActionType"
|
||||
},
|
||||
"angle": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"angle"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EditorActionType": {
|
||||
"enum": [
|
||||
"crop",
|
||||
"rotate",
|
||||
"blur",
|
||||
"adjust"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"EditorCreateAssetDto": {
|
||||
"properties": {
|
||||
"edits": {
|
||||
"description": "list of edits",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EditorActionCrop"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EditorActionRotate"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EditorActionBlur"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EditorActionAdjust"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"description": "Source asset id",
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"stack": {
|
||||
"description": "Stack the edit and the original",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"edits",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EditorCropRegion": {
|
||||
"properties": {
|
||||
"height": {
|
||||
"type": "integer"
|
||||
},
|
||||
"left": {
|
||||
"type": "integer"
|
||||
},
|
||||
"top": {
|
||||
"type": "integer"
|
||||
},
|
||||
"width": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"height",
|
||||
"left",
|
||||
"top",
|
||||
"width"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EmailNotificationsResponse": {
|
||||
"properties": {
|
||||
"albumInvite": {
|
||||
|
||||
@@ -444,6 +444,38 @@ export type DuplicateResponseDto = {
|
||||
assets: AssetResponseDto[];
|
||||
duplicateId: string;
|
||||
};
|
||||
export type EditorCropRegion = {
|
||||
height: number;
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
};
|
||||
export type EditorActionCrop = {
|
||||
action: EditorActionType;
|
||||
region: EditorCropRegion;
|
||||
};
|
||||
export type EditorActionRotate = {
|
||||
action: EditorActionType;
|
||||
angle: number;
|
||||
};
|
||||
export type EditorActionBlur = {
|
||||
action: EditorActionType;
|
||||
};
|
||||
export type EditorActionAdjust = {
|
||||
action: EditorActionType;
|
||||
brightness: number;
|
||||
hue: number;
|
||||
lightness: number;
|
||||
saturation: number;
|
||||
};
|
||||
export type EditorCreateAssetDto = {
|
||||
/** list of edits */
|
||||
edits: (EditorActionCrop | EditorActionRotate | EditorActionBlur | EditorActionAdjust)[];
|
||||
/** Source asset id */
|
||||
id: string;
|
||||
/** Stack the edit and the original */
|
||||
stack?: boolean;
|
||||
};
|
||||
export type PersonResponseDto = {
|
||||
birthDate: string | null;
|
||||
id: string;
|
||||
@@ -1836,6 +1868,18 @@ export function getAssetDuplicates(opts?: Oazapfts.RequestOpts) {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
export function createAssetFromEdits({ editorCreateAssetDto }: {
|
||||
editorCreateAssetDto: EditorCreateAssetDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 201;
|
||||
data: AssetResponseDto;
|
||||
}>("/editor", oazapfts.json({
|
||||
...opts,
|
||||
method: "POST",
|
||||
body: editorCreateAssetDto
|
||||
})));
|
||||
}
|
||||
export function getFaces({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
@@ -3138,6 +3182,12 @@ export enum EntityType {
|
||||
Asset = "ASSET",
|
||||
Album = "ALBUM"
|
||||
}
|
||||
export enum EditorActionType {
|
||||
Crop = "crop",
|
||||
Rotate = "rotate",
|
||||
Blur = "blur",
|
||||
Adjust = "adjust"
|
||||
}
|
||||
export enum JobName {
|
||||
ThumbnailGeneration = "thumbnailGeneration",
|
||||
MetadataExtraction = "metadataExtraction",
|
||||
|
||||
19
server/src/controllers/editor.controller.ts
Normal file
19
server/src/controllers/editor.controller.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { EditorCreateAssetDto } from 'src/dtos/editor.dto';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { EditorService } from 'src/services/editor.service';
|
||||
|
||||
@ApiTags('Editor')
|
||||
@Controller('editor')
|
||||
export class EditorController {
|
||||
constructor(private service: EditorService) {}
|
||||
|
||||
@Post()
|
||||
@Authenticated()
|
||||
createAssetFromEdits(@Auth() auth: AuthDto, @Body() dto: EditorCreateAssetDto): Promise<AssetResponseDto> {
|
||||
return this.service.createAssetFromEdits(auth, dto);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { AuditController } from 'src/controllers/audit.controller';
|
||||
import { AuthController } from 'src/controllers/auth.controller';
|
||||
import { DownloadController } from 'src/controllers/download.controller';
|
||||
import { DuplicateController } from 'src/controllers/duplicate.controller';
|
||||
import { EditorController } from 'src/controllers/editor.controller';
|
||||
import { FaceController } from 'src/controllers/face.controller';
|
||||
import { ReportController } from 'src/controllers/file-report.controller';
|
||||
import { JobController } from 'src/controllers/job.controller';
|
||||
@@ -43,6 +44,7 @@ export const controllers = [
|
||||
AuthController,
|
||||
DownloadController,
|
||||
DuplicateController,
|
||||
EditorController,
|
||||
FaceController,
|
||||
JobController,
|
||||
LibraryController,
|
||||
|
||||
98
server/src/dtos/editor.dto.ts
Normal file
98
server/src/dtos/editor.dto.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { ClassConstructor, plainToInstance, Transform, Type } from 'class-transformer';
|
||||
import { IsEnum, IsInt, ValidateNested } from 'class-validator';
|
||||
import { ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
export enum EditorActionType {
|
||||
Crop = 'crop',
|
||||
Rotate = 'rotate',
|
||||
Blur = 'blur',
|
||||
Adjust = 'adjust',
|
||||
}
|
||||
|
||||
export class EditorActionItem {
|
||||
@IsEnum(EditorActionType)
|
||||
@ApiProperty({ enum: EditorActionType, enumName: 'EditorActionType' })
|
||||
action!: EditorActionType;
|
||||
}
|
||||
|
||||
export class EditorActionAdjust extends EditorActionItem {
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
brightness!: number;
|
||||
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
saturation!: number;
|
||||
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
hue!: number;
|
||||
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
lightness!: number;
|
||||
}
|
||||
|
||||
export class EditorActionBlur extends EditorActionItem {}
|
||||
|
||||
class EditorCropRegion {
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
left!: number;
|
||||
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
top!: number;
|
||||
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
width!: number;
|
||||
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
height!: number;
|
||||
}
|
||||
|
||||
export class EditorActionCrop extends EditorActionItem {
|
||||
@Type(() => EditorCropRegion)
|
||||
@ValidateNested()
|
||||
region!: EditorCropRegion;
|
||||
}
|
||||
|
||||
export class EditorActionRotate extends EditorActionItem {
|
||||
@IsInt()
|
||||
@ApiProperty({ type: 'integer' })
|
||||
angle!: number;
|
||||
}
|
||||
|
||||
export type EditorAction = EditorActionRotate | EditorActionBlur | EditorActionCrop | EditorActionAdjust;
|
||||
|
||||
const actionToClass: Record<EditorActionType, ClassConstructor<EditorAction>> = {
|
||||
[EditorActionType.Crop]: EditorActionCrop,
|
||||
[EditorActionType.Rotate]: EditorActionRotate,
|
||||
[EditorActionType.Blur]: EditorActionBlur,
|
||||
[EditorActionType.Adjust]: EditorActionAdjust,
|
||||
};
|
||||
|
||||
const getActionClass = (item: EditorActionItem): ClassConstructor<EditorAction> =>
|
||||
actionToClass[item.action] || EditorActionItem;
|
||||
|
||||
@ApiExtraModels(EditorActionRotate, EditorActionBlur, EditorActionCrop, EditorActionAdjust)
|
||||
export class EditorCreateAssetDto {
|
||||
/** Source asset id */
|
||||
@ValidateUUID()
|
||||
id!: string;
|
||||
|
||||
/** Stack the edit and the original */
|
||||
@ValidateBoolean({ optional: true })
|
||||
stack?: boolean;
|
||||
|
||||
/** list of edits */
|
||||
@ValidateNested({ each: true })
|
||||
@Transform(({ value: edits }) =>
|
||||
Array.isArray(edits) ? edits.map((item) => plainToInstance(getActionClass(item), item)) : edits,
|
||||
)
|
||||
@ApiProperty({ anyOf: Object.values(actionToClass).map((target) => ({ $ref: getSchemaPath(target) })) })
|
||||
edits!: EditorAction[];
|
||||
}
|
||||
@@ -117,7 +117,7 @@ export interface IBaseJob {
|
||||
|
||||
export interface IEntityJob extends IBaseJob {
|
||||
id: string;
|
||||
source?: 'upload' | 'sidecar-write' | 'copy';
|
||||
source?: 'upload' | 'sidecar-write' | 'copy' | 'editor';
|
||||
}
|
||||
|
||||
export interface IAssetDeleteJob extends IEntityJob {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Writable } from 'node:stream';
|
||||
import { Region } from 'sharp';
|
||||
import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/config';
|
||||
|
||||
export const IMediaRepository = 'IMediaRepository';
|
||||
@@ -71,6 +72,20 @@ export interface BitrateDistribution {
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export type MediaEditItem =
|
||||
| { action: 'crop'; region: Region }
|
||||
| { action: 'rotate'; angle: number }
|
||||
| { action: 'blur' }
|
||||
| {
|
||||
action: 'modulate';
|
||||
brightness?: number;
|
||||
saturation?: number;
|
||||
hue?: number;
|
||||
lightness?: number;
|
||||
};
|
||||
|
||||
export type MediaEdits = MediaEditItem[];
|
||||
|
||||
export interface VideoCodecSWConfig {
|
||||
getCommand(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream: AudioStreamInfo): TranscodeCommand;
|
||||
}
|
||||
@@ -89,4 +104,7 @@ export interface IMediaRepository {
|
||||
// video
|
||||
probe(input: string): Promise<VideoInfo>;
|
||||
transcode(input: string, output: string | Writable, command: TranscodeCommand): Promise<void>;
|
||||
|
||||
// editor
|
||||
applyEdits(input: string, output: string, edits: MediaEditItem[]): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import sharp from 'sharp';
|
||||
import { Colorspace } from 'src/config';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import {
|
||||
IMediaRepository,
|
||||
ImageDimensions,
|
||||
IMediaRepository,
|
||||
MediaEdits,
|
||||
ThumbnailOptions,
|
||||
TranscodeCommand,
|
||||
VideoInfo,
|
||||
@@ -44,6 +45,41 @@ export class MediaRepository implements IMediaRepository {
|
||||
return true;
|
||||
}
|
||||
|
||||
async applyEdits(input: string, output: string, edits: MediaEdits) {
|
||||
const pipeline = sharp(input, { failOn: 'error', limitInputPixels: false }).keepMetadata();
|
||||
|
||||
for (const edit of edits) {
|
||||
switch (edit.action) {
|
||||
case 'crop': {
|
||||
pipeline.extract(edit.region);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'rotate': {
|
||||
pipeline.rotate(edit.angle);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'blur': {
|
||||
pipeline.blur(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'modulate': {
|
||||
pipeline.modulate({
|
||||
brightness: edit.brightness,
|
||||
saturation: edit.saturation,
|
||||
hue: edit.hue,
|
||||
lightness: edit.lightness,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await pipeline.toFile(output);
|
||||
}
|
||||
|
||||
async generateThumbnail(input: string | Buffer, output: string, options: ThumbnailOptions): Promise<void> {
|
||||
// some invalid images can still be processed by sharp, but we want to fail on them by default to avoid crashes
|
||||
const pipeline = sharp(input, { failOn: options.processInvalidImages ? 'none' : 'error', limitInputPixels: false })
|
||||
|
||||
128
server/src/services/editor.service.ts
Normal file
128
server/src/services/editor.service.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { BadRequestException, Inject, Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||
import { dirname } from 'node:path';
|
||||
import { AccessCore, Permission } from 'src/cores/access.core';
|
||||
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
|
||||
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import {
|
||||
EditorAction,
|
||||
EditorActionAdjust,
|
||||
EditorActionBlur,
|
||||
EditorActionCrop,
|
||||
EditorActionRotate,
|
||||
EditorActionType,
|
||||
EditorCreateAssetDto,
|
||||
} from 'src/dtos/editor.dto';
|
||||
import { AssetType } from 'src/entities/asset.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { IMediaRepository, MediaEditItem } from 'src/interfaces/media.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
|
||||
@Injectable()
|
||||
export class EditorService {
|
||||
private access: AccessCore;
|
||||
|
||||
constructor(
|
||||
@Inject(IAccessRepository) accessRepository: IAccessRepository,
|
||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||
@Inject(IMediaRepository) private mediaRepository: IMediaRepository,
|
||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||
) {
|
||||
this.access = AccessCore.create(accessRepository);
|
||||
}
|
||||
|
||||
async createAssetFromEdits(auth: AuthDto, dto: EditorCreateAssetDto): Promise<AssetResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_VIEW, dto.id);
|
||||
|
||||
const asset = await this.assetRepository.getById(dto.id);
|
||||
if (!asset) {
|
||||
throw new BadRequestException('Asset not found');
|
||||
}
|
||||
|
||||
if (asset.type !== AssetType.IMAGE) {
|
||||
throw new BadRequestException('Only images can be edited');
|
||||
}
|
||||
|
||||
const uuid = this.cryptoRepository.randomUUID();
|
||||
const outputFile = StorageCore.getNestedPath(StorageFolder.UPLOAD, auth.user.id, uuid);
|
||||
this.storageRepository.mkdirSync(dirname(outputFile));
|
||||
|
||||
await this.mediaRepository.applyEdits(asset.originalPath, outputFile, this.asMediaEdits(dto.edits));
|
||||
|
||||
try {
|
||||
const checksum = await this.cryptoRepository.hashFile(outputFile);
|
||||
const { size } = await this.storageRepository.stat(outputFile);
|
||||
|
||||
const newAsset = await this.assetRepository.create({
|
||||
id: uuid,
|
||||
ownerId: auth.user.id,
|
||||
deviceId: 'immich-editor',
|
||||
deviceAssetId: asset.deviceAssetId + `-edit-${Date.now()}`,
|
||||
libraryId: null,
|
||||
type: asset.type,
|
||||
originalPath: outputFile,
|
||||
localDateTime: asset.localDateTime,
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
checksum,
|
||||
isVisible: true,
|
||||
originalFileName: asset.originalFileName,
|
||||
sidecarPath: null,
|
||||
tags: asset.tags,
|
||||
duplicateId: null,
|
||||
});
|
||||
|
||||
await this.assetRepository.upsertExif({ assetId: newAsset.id, fileSizeInByte: size });
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.METADATA_EXTRACTION,
|
||||
data: { id: newAsset.id, source: 'editor' },
|
||||
});
|
||||
|
||||
return mapAsset(newAsset, { auth });
|
||||
} catch (error: Error | any) {
|
||||
this.logger.error(`Failed to create asset from edits: ${error}`, error?.stack);
|
||||
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [outputFile] } });
|
||||
throw new InternalServerErrorException('Failed to create asset from edits');
|
||||
}
|
||||
}
|
||||
|
||||
private asMediaEdits(edits: EditorAction[]) {
|
||||
const mediaEdits: MediaEditItem[] = [];
|
||||
for (const { action, ...options } of edits) {
|
||||
switch (action) {
|
||||
case EditorActionType.Crop: {
|
||||
mediaEdits.push({ ...(options as EditorActionCrop), action: 'crop' });
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorActionType.Rotate: {
|
||||
mediaEdits.push({ ...(options as EditorActionRotate), action: 'rotate' });
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorActionType.Blur: {
|
||||
mediaEdits.push({ ...(options as EditorActionBlur), action: 'blur' });
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorActionType.Adjust: {
|
||||
mediaEdits.push({ ...(options as EditorActionAdjust), action: 'modulate' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mediaEdits;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { CliService } from 'src/services/cli.service';
|
||||
import { DatabaseService } from 'src/services/database.service';
|
||||
import { DownloadService } from 'src/services/download.service';
|
||||
import { DuplicateService } from 'src/services/duplicate.service';
|
||||
import { EditorService } from 'src/services/editor.service';
|
||||
import { JobService } from 'src/services/job.service';
|
||||
import { LibraryService } from 'src/services/library.service';
|
||||
import { MapService } from 'src/services/map.service';
|
||||
@@ -50,6 +51,7 @@ export const services = [
|
||||
DatabaseService,
|
||||
DownloadService,
|
||||
DuplicateService,
|
||||
EditorService,
|
||||
JobService,
|
||||
LibraryService,
|
||||
MapService,
|
||||
|
||||
@@ -250,7 +250,7 @@ export class JobService {
|
||||
}
|
||||
|
||||
case JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE: {
|
||||
if (item.data.source === 'upload' || item.data.source === 'copy') {
|
||||
if (item.data.source === 'upload' || item.data.source === 'copy' || item.data.source === 'editor') {
|
||||
await this.jobRepository.queue({ name: JobName.GENERATE_PREVIEW, data: item.data });
|
||||
}
|
||||
break;
|
||||
@@ -271,7 +271,7 @@ export class JobService {
|
||||
{ name: JobName.GENERATE_THUMBHASH, data: item.data },
|
||||
];
|
||||
|
||||
if (item.data.source === 'upload') {
|
||||
if (item.data.source === 'upload' || item.data.source === 'editor') {
|
||||
jobs.push({ name: JobName.SMART_SEARCH, data: item.data }, { name: JobName.FACE_DETECTION, data: item.data });
|
||||
|
||||
const [asset] = await this.assetRepository.getByIds([item.data.id]);
|
||||
@@ -289,7 +289,7 @@ export class JobService {
|
||||
}
|
||||
|
||||
case JobName.GENERATE_THUMBNAIL: {
|
||||
if (item.data.source !== 'upload') {
|
||||
if (item.data.source !== 'upload' && item.data.source !== 'editor') {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,5 +9,6 @@ export const newMediaRepositoryMock = (): Mocked<IMediaRepository> => {
|
||||
probe: vitest.fn(),
|
||||
transcode: vitest.fn(),
|
||||
getImageDimensions: vitest.fn(),
|
||||
applyEdits: vitest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user