mirror of
https://github.com/immich-app/immich.git
synced 2025-12-16 17:23:16 +03:00
Compare commits
1 Commits
feature/th
...
refactor/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8af7a9c1b9 |
@@ -1052,7 +1052,7 @@ describe('/asset', () => {
|
|||||||
dateTimeOriginal: '2010-07-20T17:27:12.000Z',
|
dateTimeOriginal: '2010-07-20T17:27:12.000Z',
|
||||||
latitude: null,
|
latitude: null,
|
||||||
longitude: null,
|
longitude: null,
|
||||||
orientation: '1',
|
orientation: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
98
mobile/openapi/lib/model/exif_response_dto.dart
generated
98
mobile/openapi/lib/model/exif_response_dto.dart
generated
@@ -71,7 +71,7 @@ class ExifResponseDto {
|
|||||||
|
|
||||||
DateTime? modifyDate;
|
DateTime? modifyDate;
|
||||||
|
|
||||||
String? orientation;
|
ExifResponseDtoOrientationEnum? orientation;
|
||||||
|
|
||||||
String? projectionType;
|
String? projectionType;
|
||||||
|
|
||||||
@@ -289,7 +289,9 @@ class ExifResponseDto {
|
|||||||
make: mapValueOfType<String>(json, r'make'),
|
make: mapValueOfType<String>(json, r'make'),
|
||||||
model: mapValueOfType<String>(json, r'model'),
|
model: mapValueOfType<String>(json, r'model'),
|
||||||
modifyDate: mapDateTime(json, r'modifyDate', r''),
|
modifyDate: mapDateTime(json, r'modifyDate', r''),
|
||||||
orientation: mapValueOfType<String>(json, r'orientation'),
|
orientation: json[r'orientation'] == null
|
||||||
|
? null
|
||||||
|
: ExifResponseDtoOrientationEnum.parse('${json[r'orientation']}'),
|
||||||
projectionType: mapValueOfType<String>(json, r'projectionType'),
|
projectionType: mapValueOfType<String>(json, r'projectionType'),
|
||||||
rating: json[r'rating'] == null
|
rating: json[r'rating'] == null
|
||||||
? null
|
? null
|
||||||
@@ -346,3 +348,95 @@ class ExifResponseDto {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ExifResponseDtoOrientationEnum {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const ExifResponseDtoOrientationEnum._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final num value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value.toString();
|
||||||
|
|
||||||
|
num toJson() => value;
|
||||||
|
|
||||||
|
static const n1 = ExifResponseDtoOrientationEnum._('1');
|
||||||
|
static const n2 = ExifResponseDtoOrientationEnum._('2');
|
||||||
|
static const n8 = ExifResponseDtoOrientationEnum._('8');
|
||||||
|
static const n7 = ExifResponseDtoOrientationEnum._('7');
|
||||||
|
static const n3 = ExifResponseDtoOrientationEnum._('3');
|
||||||
|
static const n4 = ExifResponseDtoOrientationEnum._('4');
|
||||||
|
static const n6 = ExifResponseDtoOrientationEnum._('6');
|
||||||
|
static const n5 = ExifResponseDtoOrientationEnum._('5');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][ExifResponseDtoOrientationEnum].
|
||||||
|
static const values = <ExifResponseDtoOrientationEnum>[
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
n8,
|
||||||
|
n7,
|
||||||
|
n3,
|
||||||
|
n4,
|
||||||
|
n6,
|
||||||
|
n5,
|
||||||
|
];
|
||||||
|
|
||||||
|
static ExifResponseDtoOrientationEnum? fromJson(dynamic value) => ExifResponseDtoOrientationEnumTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<ExifResponseDtoOrientationEnum> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <ExifResponseDtoOrientationEnum>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = ExifResponseDtoOrientationEnum.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [ExifResponseDtoOrientationEnum] to num,
|
||||||
|
/// and [decode] dynamic data back to [ExifResponseDtoOrientationEnum].
|
||||||
|
class ExifResponseDtoOrientationEnumTypeTransformer {
|
||||||
|
factory ExifResponseDtoOrientationEnumTypeTransformer() => _instance ??= const ExifResponseDtoOrientationEnumTypeTransformer._();
|
||||||
|
|
||||||
|
const ExifResponseDtoOrientationEnumTypeTransformer._();
|
||||||
|
|
||||||
|
num encode(ExifResponseDtoOrientationEnum data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a ExifResponseDtoOrientationEnum.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
ExifResponseDtoOrientationEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case '1': return ExifResponseDtoOrientationEnum.n1;
|
||||||
|
case '2': return ExifResponseDtoOrientationEnum.n2;
|
||||||
|
case '8': return ExifResponseDtoOrientationEnum.n8;
|
||||||
|
case '7': return ExifResponseDtoOrientationEnum.n7;
|
||||||
|
case '3': return ExifResponseDtoOrientationEnum.n3;
|
||||||
|
case '4': return ExifResponseDtoOrientationEnum.n4;
|
||||||
|
case '6': return ExifResponseDtoOrientationEnum.n6;
|
||||||
|
case '5': return ExifResponseDtoOrientationEnum.n5;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [ExifResponseDtoOrientationEnumTypeTransformer] instance.
|
||||||
|
static ExifResponseDtoOrientationEnumTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9074,8 +9074,18 @@
|
|||||||
},
|
},
|
||||||
"orientation": {
|
"orientation": {
|
||||||
"default": null,
|
"default": null,
|
||||||
|
"enum": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
8,
|
||||||
|
7,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
6,
|
||||||
|
5
|
||||||
|
],
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "string"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"projectionType": {
|
"projectionType": {
|
||||||
"default": null,
|
"default": null,
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export type ExifResponseDto = {
|
|||||||
make?: string | null;
|
make?: string | null;
|
||||||
model?: string | null;
|
model?: string | null;
|
||||||
modifyDate?: string | null;
|
modifyDate?: string | null;
|
||||||
orientation?: string | null;
|
orientation?: Orientation | null;
|
||||||
projectionType?: string | null;
|
projectionType?: string | null;
|
||||||
rating?: number | null;
|
rating?: number | null;
|
||||||
state?: string | null;
|
state?: string | null;
|
||||||
@@ -3266,6 +3266,16 @@ export enum AlbumUserRole {
|
|||||||
Editor = "editor",
|
Editor = "editor",
|
||||||
Viewer = "viewer"
|
Viewer = "viewer"
|
||||||
}
|
}
|
||||||
|
export enum Orientation {
|
||||||
|
$1 = 1,
|
||||||
|
$2 = 2,
|
||||||
|
$8 = 8,
|
||||||
|
$7 = 7,
|
||||||
|
$3 = 3,
|
||||||
|
$4 = 4,
|
||||||
|
$6 = 6,
|
||||||
|
$5 = 5
|
||||||
|
}
|
||||||
export enum SourceType {
|
export enum SourceType {
|
||||||
MachineLearning = "machine-learning",
|
MachineLearning = "machine-learning",
|
||||||
Exif = "exif"
|
Exif = "exif"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defaults } from './fetch-client.js';
|
import { defaults } from './fetch-client.js';
|
||||||
|
export { Orientation as LameGeneratedOrientation } from './fetch-client.js';
|
||||||
|
|
||||||
export * from './fetch-client.js';
|
export * from './fetch-client.js';
|
||||||
export * from './fetch-errors.js';
|
export * from './fetch-errors.js';
|
||||||
@@ -8,6 +9,17 @@ export interface InitOptions {
|
|||||||
apiKey: string;
|
apiKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Orientation {
|
||||||
|
Rotate0 = 1,
|
||||||
|
Rotate0Mirrored = 2,
|
||||||
|
Rotate90 = 8,
|
||||||
|
Rotate90Mirrored = 7,
|
||||||
|
Rotate180 = 3,
|
||||||
|
Rotate180Mirrored = 4,
|
||||||
|
Rotate270 = 6,
|
||||||
|
Rotate270Mirrored = 5,
|
||||||
|
}
|
||||||
|
|
||||||
export const init = ({ baseUrl, apiKey }: InitOptions) => {
|
export const init = ({ baseUrl, apiKey }: InitOptions) => {
|
||||||
setBaseUrl(baseUrl);
|
setBaseUrl(baseUrl);
|
||||||
setApiKey(apiKey);
|
setApiKey(apiKey);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
|
import { Orientation } from 'src/enum';
|
||||||
|
|
||||||
export class ExifResponseDto {
|
export class ExifResponseDto {
|
||||||
make?: string | null = null;
|
make?: string | null = null;
|
||||||
@@ -9,7 +10,9 @@ export class ExifResponseDto {
|
|||||||
|
|
||||||
@ApiProperty({ type: 'integer', format: 'int64' })
|
@ApiProperty({ type: 'integer', format: 'int64' })
|
||||||
fileSizeInByte?: number | null = null;
|
fileSizeInByte?: number | null = null;
|
||||||
orientation?: string | null = null;
|
|
||||||
|
@ApiProperty({ enum: Orientation })
|
||||||
|
orientation?: Orientation | null = null;
|
||||||
dateTimeOriginal?: Date | null = null;
|
dateTimeOriginal?: Date | null = null;
|
||||||
modifyDate?: Date | null = null;
|
modifyDate?: Date | null = null;
|
||||||
timeZone?: string | null = null;
|
timeZone?: string | null = null;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
|
import { Orientation } from 'src/enum';
|
||||||
import { Index, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
|
import { Index, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
|
||||||
import { Column } from 'typeorm/decorator/columns/Column.js';
|
import { Column } from 'typeorm/decorator/columns/Column.js';
|
||||||
import { Entity } from 'typeorm/decorator/entity/Entity.js';
|
import { Entity } from 'typeorm/decorator/entity/Entity.js';
|
||||||
@@ -25,8 +26,8 @@ export class ExifEntity {
|
|||||||
@Column({ type: 'bigint', nullable: true })
|
@Column({ type: 'bigint', nullable: true })
|
||||||
fileSizeInByte!: number | null;
|
fileSizeInByte!: number | null;
|
||||||
|
|
||||||
@Column({ type: 'varchar', nullable: true })
|
@Column({ type: 'enum', enum: Orientation, nullable: true })
|
||||||
orientation!: string | null;
|
orientation!: Orientation | null;
|
||||||
|
|
||||||
@Column({ type: 'timestamptz', nullable: true })
|
@Column({ type: 'timestamptz', nullable: true })
|
||||||
dateTimeOriginal!: Date | null;
|
dateTimeOriginal!: Date | null;
|
||||||
|
|||||||
@@ -198,3 +198,14 @@ export enum ManualJobName {
|
|||||||
TAG_CLEANUP = 'tag-cleanup',
|
TAG_CLEANUP = 'tag-cleanup',
|
||||||
USER_CLEANUP = 'user-cleanup',
|
USER_CLEANUP = 'user-cleanup',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Orientation {
|
||||||
|
Rotate0 = 1,
|
||||||
|
Rotate0Mirrored = 2,
|
||||||
|
Rotate90 = 8,
|
||||||
|
Rotate90Mirrored = 7,
|
||||||
|
Rotate180 = 3,
|
||||||
|
Rotate180Mirrored = 4,
|
||||||
|
Rotate270 = 6,
|
||||||
|
Rotate270Mirrored = 5,
|
||||||
|
}
|
||||||
|
|||||||
31
server/src/migrations/1726754669860-AddOrientationEnum.ts
Normal file
31
server/src/migrations/1726754669860-AddOrientationEnum.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AddOrientationEnum1726754669860 implements MigrationInterface {
|
||||||
|
name = 'AddOrientationEnum1726754669860'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TYPE "exif_orientation_enum" AS ENUM('1', '2', '3', '4', '5', '6', '7', '8')`)
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE "exif" SET "orientation" = CASE
|
||||||
|
WHEN "orientation" = '0' THEN '1'
|
||||||
|
WHEN "orientation" = '1' THEN '1'
|
||||||
|
WHEN "orientation" = '2' THEN '2'
|
||||||
|
WHEN "orientation" = '3' THEN '3'
|
||||||
|
WHEN "orientation" = '4' THEN '4'
|
||||||
|
WHEN "orientation" = '5' THEN '5'
|
||||||
|
WHEN "orientation" = '6' THEN '6'
|
||||||
|
WHEN "orientation" = '7' THEN '7'
|
||||||
|
WHEN "orientation" = '8' THEN '8'
|
||||||
|
WHEN "orientation" = '-90' THEN '6'
|
||||||
|
WHEN "orientation" = '90' THEN '8'
|
||||||
|
WHEN "orientation" = '180' THEN '3'
|
||||||
|
ELSE NULL
|
||||||
|
END`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "orientation" TYPE "exif_orientation_enum" USING "orientation"::"exif_orientation_enum"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "orientation" TYPE character varying USING "orientation"::text`);
|
||||||
|
await queryRunner.query(`DROP TYPE "exif_orientation_enum"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { randomBytes } from 'node:crypto';
|
|||||||
import { Stats } from 'node:fs';
|
import { Stats } from 'node:fs';
|
||||||
import { constants } from 'node:fs/promises';
|
import { constants } from 'node:fs/promises';
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
import { AssetType, SourceType } from 'src/enum';
|
import { AssetType, Orientation, SourceType } from 'src/enum';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
@@ -20,7 +20,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
|
|||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { MetadataService, Orientation } from 'src/services/metadata.service';
|
import { MetadataService } from 'src/services/metadata.service';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { fileStub } from 'test/fixtures/file.stub';
|
import { fileStub } from 'test/fixtures/file.stub';
|
||||||
import { probeStub } from 'test/fixtures/media.stub';
|
import { probeStub } from 'test/fixtures/media.stub';
|
||||||
@@ -537,9 +537,7 @@ describe(MetadataService.name, () => {
|
|||||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
||||||
|
|
||||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.video.id]);
|
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.video.id]);
|
||||||
expect(assetMock.upsertExif).toHaveBeenCalledWith(
|
expect(assetMock.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ orientation: Orientation.Rotate90 }));
|
||||||
expect.objectContaining({ orientation: Orientation.Rotate270CW.toString() }),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
|
it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => {
|
||||||
@@ -786,7 +784,7 @@ describe(MetadataService.name, () => {
|
|||||||
Make: 'test-factory',
|
Make: 'test-factory',
|
||||||
Model: "'mockel'",
|
Model: "'mockel'",
|
||||||
ModifyDate: ExifDateTime.fromISO(dateForTest.toISOString()),
|
ModifyDate: ExifDateTime.fromISO(dateForTest.toISOString()),
|
||||||
Orientation: 0,
|
Orientation: 1,
|
||||||
ProfileDescription: 'extensive description',
|
ProfileDescription: 'extensive description',
|
||||||
ProjectionType: 'equirectangular',
|
ProjectionType: 'equirectangular',
|
||||||
tz: 'UTC-11:30',
|
tz: 'UTC-11:30',
|
||||||
@@ -819,7 +817,7 @@ describe(MetadataService.name, () => {
|
|||||||
make: tags.Make,
|
make: tags.Make,
|
||||||
model: tags.Model,
|
model: tags.Model,
|
||||||
modifyDate: expect.any(Date),
|
modifyDate: expect.any(Date),
|
||||||
orientation: tags.Orientation?.toString(),
|
orientation: 1,
|
||||||
profileDescription: tags.ProfileDescription,
|
profileDescription: tags.ProfileDescription,
|
||||||
projectionType: 'EQUIRECTANGULAR',
|
projectionType: 'EQUIRECTANGULAR',
|
||||||
timeZone: tags.tz,
|
timeZone: tags.tz,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { OnEmit } from 'src/decorators';
|
|||||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { PersonEntity } from 'src/entities/person.entity';
|
import { PersonEntity } from 'src/entities/person.entity';
|
||||||
import { AssetType, SourceType } from 'src/enum';
|
import { AssetType, Orientation, SourceType } from 'src/enum';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
@@ -54,17 +54,6 @@ const EXIF_DATE_TAGS: Array<keyof Tags> = [
|
|||||||
'DateTimeCreated',
|
'DateTimeCreated',
|
||||||
];
|
];
|
||||||
|
|
||||||
export enum Orientation {
|
|
||||||
Horizontal = 1,
|
|
||||||
MirrorHorizontal = 2,
|
|
||||||
Rotate180 = 3,
|
|
||||||
MirrorVertical = 4,
|
|
||||||
MirrorHorizontalRotate270CW = 5,
|
|
||||||
Rotate90CW = 6,
|
|
||||||
MirrorHorizontalRotate90CW = 7,
|
|
||||||
Rotate270CW = 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
const validate = <T>(value: T): NonNullable<T> | null => {
|
const validate = <T>(value: T): NonNullable<T> | null => {
|
||||||
// handle lists of numbers
|
// handle lists of numbers
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
@@ -243,7 +232,7 @@ export class MetadataService {
|
|||||||
fileSizeInByte: stats.size,
|
fileSizeInByte: stats.size,
|
||||||
exifImageHeight: validate(exifTags.ImageHeight),
|
exifImageHeight: validate(exifTags.ImageHeight),
|
||||||
exifImageWidth: validate(exifTags.ImageWidth),
|
exifImageWidth: validate(exifTags.ImageWidth),
|
||||||
orientation: validate(exifTags.Orientation)?.toString() ?? null,
|
orientation: this.getOrientation(asset.id, exifTags.Orientation),
|
||||||
projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null,
|
projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null,
|
||||||
bitsPerSample: this.getBitsPerSample(exifTags),
|
bitsPerSample: this.getBitsPerSample(exifTags),
|
||||||
colorspace: exifTags.ColorSpace ?? null,
|
colorspace: exifTags.ColorSpace ?? null,
|
||||||
@@ -669,9 +658,32 @@ export class MetadataService {
|
|||||||
return tags.BurstID ?? tags.BurstUUID ?? tags.CameraBurstID ?? tags.MediaUniqueID ?? null;
|
return tags.BurstID ?? tags.BurstUUID ?? tags.CameraBurstID ?? tags.MediaUniqueID ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getOrientation(id: string, orientation: ImmichTags['Orientation']) {
|
||||||
|
if (!orientation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (orientation) {
|
||||||
|
case Orientation.Rotate0:
|
||||||
|
case Orientation.Rotate0Mirrored:
|
||||||
|
case Orientation.Rotate90:
|
||||||
|
case Orientation.Rotate90Mirrored:
|
||||||
|
case Orientation.Rotate180:
|
||||||
|
case Orientation.Rotate180Mirrored:
|
||||||
|
case Orientation.Rotate270:
|
||||||
|
case Orientation.Rotate270Mirrored: {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.warn(`Asset ${id} has unknown orientation: "${orientation}", setting to null`);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private getBitsPerSample(tags: ImmichTags): number | null {
|
private getBitsPerSample(tags: ImmichTags): number | null {
|
||||||
const bitDepthTags = [
|
const bitDepthTags = [
|
||||||
tags.BitsPerSample,
|
tags.Rotation,
|
||||||
tags.ComponentBitDepth,
|
tags.ComponentBitDepth,
|
||||||
tags.ImagePixelDepth,
|
tags.ImagePixelDepth,
|
||||||
tags.BitDepth,
|
tags.BitDepth,
|
||||||
@@ -695,15 +707,15 @@ export class MetadataService {
|
|||||||
if (videoStreams[0]) {
|
if (videoStreams[0]) {
|
||||||
switch (videoStreams[0].rotation) {
|
switch (videoStreams[0].rotation) {
|
||||||
case -90: {
|
case -90: {
|
||||||
tags.Orientation = Orientation.Rotate90CW;
|
tags.Orientation = Orientation.Rotate270;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0: {
|
case 0: {
|
||||||
tags.Orientation = Orientation.Horizontal;
|
tags.Orientation = Orientation.Rotate0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 90: {
|
case 90: {
|
||||||
tags.Orientation = Orientation.Rotate270CW;
|
tags.Orientation = Orientation.Rotate90;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 180: {
|
case 180: {
|
||||||
|
|||||||
6
server/test/fixtures/shared-link.stub.ts
vendored
6
server/test/fixtures/shared-link.stub.ts
vendored
@@ -5,7 +5,7 @@ import { SharedLinkResponseDto } from 'src/dtos/shared-link.dto';
|
|||||||
import { mapUser } from 'src/dtos/user.dto';
|
import { mapUser } from 'src/dtos/user.dto';
|
||||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { AssetOrder, AssetStatus, AssetType, SharedLinkType } from 'src/enum';
|
import { AssetOrder, AssetStatus, AssetType, Orientation, SharedLinkType } from 'src/enum';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
@@ -27,7 +27,7 @@ const assetInfo: ExifResponseDto = {
|
|||||||
exifImageWidth: 500,
|
exifImageWidth: 500,
|
||||||
exifImageHeight: 500,
|
exifImageHeight: 500,
|
||||||
fileSizeInByte: 100,
|
fileSizeInByte: 100,
|
||||||
orientation: 'orientation',
|
orientation: Orientation.Rotate0,
|
||||||
dateTimeOriginal: today,
|
dateTimeOriginal: today,
|
||||||
modifyDate: today,
|
modifyDate: today,
|
||||||
timeZone: 'America/Los_Angeles',
|
timeZone: 'America/Los_Angeles',
|
||||||
@@ -227,7 +227,7 @@ export const sharedLinkStub = {
|
|||||||
exifImageWidth: 500,
|
exifImageWidth: 500,
|
||||||
exifImageHeight: 500,
|
exifImageHeight: 500,
|
||||||
fileSizeInByte: 100,
|
fileSizeInByte: 100,
|
||||||
orientation: 'orientation',
|
orientation: Orientation.Rotate0,
|
||||||
dateTimeOriginal: today,
|
dateTimeOriginal: today,
|
||||||
modifyDate: today,
|
modifyDate: today,
|
||||||
timeZone: 'America/Los_Angeles',
|
timeZone: 'America/Los_Angeles',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import FormatBoldMessage from '$lib/components/i18n/format-bold-message.svelte';
|
import FormatBoldMessage from '$lib/components/i18n/format-bold-message.svelte';
|
||||||
import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification';
|
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
getBaseUrl,
|
getBaseUrl,
|
||||||
getDownloadInfo,
|
getDownloadInfo,
|
||||||
getStack,
|
getStack,
|
||||||
|
LameGeneratedOrientation,
|
||||||
|
Orientation,
|
||||||
tagAssets as tagAllAssets,
|
tagAssets as tagAllAssets,
|
||||||
untagAssets,
|
untagAssets,
|
||||||
updateAsset,
|
updateAsset,
|
||||||
@@ -290,17 +292,17 @@ export function getAssetFilename(asset: AssetResponseDto): string {
|
|||||||
return `${asset.originalFileName}.${fileExtension}`;
|
return `${asset.originalFileName}.${fileExtension}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRotated90CW(orientation: number) {
|
export function isFlipped(orientation?: LameGeneratedOrientation | null) {
|
||||||
return orientation === 5 || orientation === 6 || orientation === 90;
|
if (!orientation) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function isRotated270CW(orientation: number) {
|
return [
|
||||||
return orientation === 7 || orientation === 8 || orientation === -90;
|
Orientation.Rotate90,
|
||||||
}
|
Orientation.Rotate90Mirrored,
|
||||||
|
Orientation.Rotate270,
|
||||||
export function isFlipped(orientation?: string | null) {
|
Orientation.Rotate270Mirrored,
|
||||||
const value = Number(orientation);
|
].includes(orientation as unknown as Orientation);
|
||||||
return value && (isRotated270CW(value) || isRotated90CW(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFileSize(asset: AssetResponseDto): string {
|
export function getFileSize(asset: AssetResponseDto): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user