Skip to content

Commit

Permalink
working solution with exif
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitry-brazhenko committed Jul 24, 2023
1 parent fe2ae21 commit c0ca82d
Show file tree
Hide file tree
Showing 14 changed files with 11,959 additions and 11,003 deletions.
22,805 changes: 11,889 additions & 10,916 deletions cli/src/api/open-api/api.ts

Large diffs are not rendered by default.

21 changes: 6 additions & 15 deletions server/immich-openapi-specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -4973,19 +4973,6 @@
"checksum": {
"type": "string",
"description": "base64 encoded sha1 hash"
},
"projectionType": {
"enum": [
"EQUIRECTANGULAR",
"CUBEMAP",
"CUBESTRIP",
"EQUIRECTANGULAR_STEREO",
"CUBEMAP_STEREO",
"CUBESTRIP_STEREO",
"CYLINDER",
"DEFAULT"
],
"type": "string"
}
},
"required": [
Expand All @@ -5004,8 +4991,7 @@
"isFavorite",
"isArchived",
"duration",
"checksum",
"projectionType"
"checksum"
]
},
"AssetStatsResponseDto": {
Expand Down Expand Up @@ -5567,6 +5553,11 @@
"type": "string",
"nullable": true,
"default": null
},
"projectionType": {
"type": "string",
"nullable": true,
"default": null
}
}
},
Expand Down
5 changes: 1 addition & 4 deletions server/src/domain/asset/response-dto/asset-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AssetEntity, AssetType, ProjectionType } from '@app/infra/entities';
import { AssetEntity, AssetType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { mapFace, PersonResponseDto } from '../../person/person.dto';
import { mapTag, TagResponseDto } from '../../tag';
Expand Down Expand Up @@ -31,7 +31,6 @@ export class AssetResponseDto {
people?: PersonResponseDto[];
/**base64 encoded sha1 hash */
checksum!: string;
projectionType!: ProjectionType;
}

export function mapAsset(entity: AssetEntity): AssetResponseDto {
Expand All @@ -57,7 +56,6 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto {
tags: entity.tags?.map(mapTag),
people: entity.faces?.map(mapFace).filter((person) => !person.isHidden),
checksum: entity.checksum.toString('base64'),
projectionType: entity.projectionType,
};
}

Expand All @@ -84,6 +82,5 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto {
tags: entity.tags?.map(mapTag),
people: entity.faces?.map(mapFace),
checksum: entity.checksum.toString('base64'),
projectionType: entity.projectionType,
};
}
2 changes: 2 additions & 0 deletions server/src/domain/asset/response-dto/exif-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class ExifResponseDto {
state?: string | null = null;
country?: string | null = null;
description?: string | null = null;
projectionType?: string | null = null;
}

export function mapExif(entity: ExifEntity): ExifResponseDto {
Expand All @@ -48,5 +49,6 @@ export function mapExif(entity: ExifEntity): ExifResponseDto {
state: entity.state,
country: entity.country,
description: entity.description,
projectionType: entity.projectionType,
};
}
3 changes: 1 addition & 2 deletions server/src/immich/api-v1/asset/asset.core.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuthUserDto, IJobRepository, JobName, mimeTypes, UploadFile } from '@app/domain';
import { AssetEntity, ProjectionType, UserEntity } from '@app/infra/entities';
import { AssetEntity, UserEntity } from '@app/infra/entities';
import { parse } from 'node:path';
import { IAssetRepository } from './asset-repository';
import { CreateAssetDto, ImportAssetDto } from './dto/create-asset.dto';
Expand Down Expand Up @@ -28,7 +28,6 @@ export class AssetCore {

type: mimeTypes.assetType(file.originalPath),
isFavorite: dto.isFavorite,
projectionType: ProjectionType.DEFAULT,
isArchived: dto.isArchived ?? false,
duration: dto.duration || null,
isVisible: dto.isVisible ?? true,
Expand Down
14 changes: 0 additions & 14 deletions server/src/infra/entities/asset.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ export class AssetEntity {
@Index()
checksum!: Buffer; // sha1 checksum

@Column()
projectionType!: ProjectionType;

@Column({ type: 'varchar', nullable: true })
duration!: string | null;

Expand Down Expand Up @@ -131,14 +128,3 @@ export enum AssetType {
AUDIO = 'AUDIO',
OTHER = 'OTHER',
}

export enum ProjectionType {
EQUIRECTANGULAR = 'EQUIRECTANGULAR',
CUBEMAP = 'CUBEMAP',
CUBESTRIP = 'CUBESTRIP',
EQUIRECTANGULAR_STEREO = 'EQUIRECTANGULAR_STEREO',
CUBEMAP_STEREO = 'CUBEMAP_STEREO',
CUBESTRIP_STEREO = 'CUBESTRIP_STEREO',
CYLINDER = 'CYLINDER',
DEFAULT = 'DEFAULT',
}
23 changes: 20 additions & 3 deletions server/src/infra/entities/exif.entity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { Index, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
import { Column } from 'typeorm/decorator/columns/Column';
import { Entity } from 'typeorm/decorator/entity/Entity';
import { AssetEntity, ProjectionType } from './asset.entity';
import { AssetEntity } from './asset.entity';

export enum ProjectionType {
EQUIRECTANGULAR = 'EQUIRECTANGULAR',
CUBEMAP = 'CUBEMAP',
CUBESTRIP = 'CUBESTRIP',
EQUIRECTANGULAR_STEREO = 'EQUIRECTANGULAR_STEREO',
CUBEMAP_STEREO = 'CUBEMAP_STEREO',
CUBESTRIP_STEREO = 'CUBESTRIP_STEREO',
CYLINDER = 'CYLINDER',
NONE = 'NONE',
}

@Entity('exif')
export class ExifEntity {
Expand Down Expand Up @@ -43,8 +54,14 @@ export class ExifEntity {
@Column({ type: 'float', nullable: true })
longitude!: number | null;

@Column({ type: 'enum', enum: ProjectionType, enumName: 'projectionType', nullable: true })
projectionType!: string | null;
@Column({
type: 'enum',
enum: ProjectionType,
enumName: 'projectionType',
default: ProjectionType.NONE,
nullable: true,
})
projectionType!: ProjectionType | null;

@Column({ type: 'varchar', nullable: true })
city!: string | null;
Expand Down
15 changes: 0 additions & 15 deletions server/src/infra/migrations/1690140002097-Panoramas.ts

This file was deleted.

16 changes: 16 additions & 0 deletions server/src/infra/migrations/1690217088596-Panoramas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class Panoramas1690217088596 implements MigrationInterface {
name = 'Panoramas1690217088596'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "public"."projectionType" AS ENUM('EQUIRECTANGULAR', 'CUBEMAP', 'CUBESTRIP', 'EQUIRECTANGULAR_STEREO', 'CUBEMAP_STEREO', 'CUBESTRIP_STEREO', 'CYLINDER', 'NONE')`);
await queryRunner.query(`ALTER TABLE "exif" ADD "projectionType" "public"."projectionType" DEFAULT 'NONE'`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "projectionType"`);
await queryRunner.query(`DROP TYPE "public"."projectionType"`);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,19 @@ export class MetadataExtractionProcessor {
await this.extractEmbeddedVideo(asset, offset, null, fileCreatedAt);
}
}
newExif.projectionType = getExifProperty('ProjectionType');

const exifProperty = getExifProperty('ProjectionType');
let projectionType: ProjectionType | null = null; // Initialize it as null

if (exifProperty) {
const exifPropertyUpper = exifProperty.toUpperCase();

if (exifPropertyUpper in ProjectionType) {
projectionType = ProjectionType[exifPropertyUpper as keyof typeof ProjectionType];
}
}

newExif.projectionType = projectionType;
newExif.livePhotoCID = getExifProperty('MediaGroupUUID');
if (newExif.livePhotoCID && !asset.livePhotoVideoId) {
const motionAsset = await this.assetRepository.findLivePhotoMatch({
Expand Down Expand Up @@ -373,13 +385,8 @@ export class MetadataExtractionProcessor {
}
}

// Determine if the image is a panorama
let projectionType = ProjectionType.DEFAULT;
if (newExif.projectionType == 'equirectangular') {
projectionType = ProjectionType.EQUIRECTANGULAR; //currently support only for 360 equirectangular panoramas
}
await this.exifRepository.upsert(newExif, { conflictPaths: ['assetId'] });
await this.assetRepository.save({ id: asset.id, fileCreatedAt: fileCreatedAt || undefined, projectionType });
await this.assetRepository.save({ id: asset.id, fileCreatedAt: fileCreatedAt || undefined });

return true;
}
Expand Down
12 changes: 1 addition & 11 deletions server/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ export const assetEntityStub = {
createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
projectionType: ProjectionType.DEFAULT,
isArchived: false,
duration: null,
isVisible: true,
Expand Down Expand Up @@ -248,7 +247,6 @@ export const assetEntityStub = {
createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
isFavorite: true,
projectionType: ProjectionType.DEFAULT,
isArchived: false,
duration: null,
isVisible: true,
Expand Down Expand Up @@ -293,7 +291,6 @@ export const assetEntityStub = {
originalFileName: 'asset-id.ext',
faces: [],
sidecarPath: null,
projectionType: ProjectionType.DEFAULT,
}),
image: Object.freeze<AssetEntity>({
id: 'asset-id',
Expand Down Expand Up @@ -327,7 +324,6 @@ export const assetEntityStub = {
exifInfo: {
fileSizeInByte: 5_000,
} as ExifEntity,
projectionType: ProjectionType.DEFAULT,
}),
image1: Object.freeze<AssetEntity>({
id: 'asset-id-1',
Expand Down Expand Up @@ -361,7 +357,6 @@ export const assetEntityStub = {
exifInfo: {
fileSizeInByte: 5_000,
} as ExifEntity,
projectionType: ProjectionType.DEFAULT,
}),
video: Object.freeze<AssetEntity>({
id: 'asset-id',
Expand Down Expand Up @@ -395,7 +390,6 @@ export const assetEntityStub = {
exifInfo: {
fileSizeInByte: 100_000,
} as ExifEntity,
projectionType: ProjectionType.DEFAULT,
}),
livePhotoMotionAsset: Object.freeze({
id: 'live-photo-motion-asset',
Expand Down Expand Up @@ -458,7 +452,6 @@ export const assetEntityStub = {
longitude: 100,
fileSizeInByte: 23_456,
} as ExifEntity,
projectionType: ProjectionType.DEFAULT,
}),
sidecar: Object.freeze<AssetEntity>({
id: 'asset-id',
Expand Down Expand Up @@ -489,7 +482,6 @@ export const assetEntityStub = {
originalFileName: 'asset-id.ext',
faces: [],
sidecarPath: '/original/path.ext.xmp',
projectionType: ProjectionType.DEFAULT,
}),
};

Expand Down Expand Up @@ -650,7 +642,6 @@ const assetResponse: AssetResponseDto = {
fileCreatedAt: today,
updatedAt: today,
isFavorite: false,
projectionType: ProjectionType.DEFAULT,
isArchived: false,
smartInfo: {
tags: [],
Expand Down Expand Up @@ -888,12 +879,11 @@ export const sharedLinkStub = {
encodedVideoPath: '',
duration: null,
isVisible: true,
projectionType: ProjectionType.DEFAULT,
livePhotoVideo: null,
livePhotoVideoId: null,
originalFileName: 'asset_1.jpeg',
exifInfo: {
projectionType: ProjectionType.DEFAULT,
projectionType: ProjectionType.NONE,
livePhotoCID: null,
assetId: 'id_1',
description: 'description',
Expand Down
21 changes: 7 additions & 14 deletions web/src/api/open-api/api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/src/lib/components/asset-viewer/asset-viewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@
on:close={closeViewer}
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
/>
{:else if asset.projectionType == ProjectionTypeEnum.Equirectangular}
{:else if asset.exifInfo && asset.exifInfo.projectionType == ProjectionTypeEnum.Equirectangular}
<PanoramaViewer {publicSharedKey} {asset} />
{:else}
<PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
Expand Down
2 changes: 1 addition & 1 deletion web/src/lib/components/assets/thumbnail/thumbnail.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
</div>
{/if}

{#if asset.type === AssetTypeEnum.Image && asset.projectionType == ProjectionTypeEnum.Equirectangular}
{#if asset.type === AssetTypeEnum.Image && asset.exifInfo && asset.exifInfo.projectionType == ProjectionTypeEnum.Equirectangular}
<div class="absolute right-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white">
<span class="pr-2 pt-2">
<Rotate360Icon size="24" />
Expand Down

0 comments on commit c0ca82d

Please sign in to comment.