Skip to content

Commit 27ac72b

Browse files
committed
feat(server): extract full-size previews from RAW images
1 parent 1bb6926 commit 27ac72b

File tree

16 files changed

+130
-43
lines changed

16 files changed

+130
-43
lines changed

mobile/openapi/lib/model/asset_media_size.dart

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mobile/openapi/lib/model/path_type.dart

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

open-api/immich-openapi-specs.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8294,6 +8294,7 @@
82948294
},
82958295
"AssetMediaSize": {
82968296
"enum": [
8297+
"original",
82978298
"preview",
82988299
"thumbnail"
82998300
],
@@ -10020,6 +10021,7 @@
1002010021
"PathType": {
1002110022
"enum": [
1002210023
"original",
10024+
"extracted",
1002310025
"preview",
1002410026
"thumbnail",
1002510027
"encoded_video",

open-api/typescript-sdk/src/fetch-client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3429,6 +3429,7 @@ export enum AssetJobName {
34293429
TranscodeVideo = "transcode-video"
34303430
}
34313431
export enum AssetMediaSize {
3432+
Original = "original",
34323433
Preview = "preview",
34333434
Thumbnail = "thumbnail"
34343435
}
@@ -3479,6 +3480,7 @@ export enum PathEntityType {
34793480
}
34803481
export enum PathType {
34813482
Original = "original",
3483+
Extracted = "extracted",
34823484
Preview = "preview",
34833485
Thumbnail = "thumbnail",
34843486
EncodedVideo = "encoded_video",

server/src/cores/storage.core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface MoveRequest {
2626
};
2727
}
2828

29-
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL;
29+
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL | AssetPathType.EXTRACTED;
3030
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
3131

3232
let instance: StorageCore | null;

server/src/dtos/asset-media.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { ArrayNotEmpty, IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested }
44
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
55

66
export enum AssetMediaSize {
7+
/**
8+
* An original-sized JPG extracted from the RAW image,
9+
* or otherwise the original non-RAW image itself.
10+
*/
11+
ORIGINAL = 'original',
712
PREVIEW = 'preview',
813
THUMBNAIL = 'thumbnail',
914
}

server/src/enum.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export enum AssetType {
3333
}
3434

3535
export enum AssetFileType {
36+
/**
37+
* An full/large-size image extracted/converted from RAW photos
38+
*/
39+
EXTRACTED = 'extracted',
3640
PREVIEW = 'preview',
3741
THUMBNAIL = 'thumbnail',
3842
}
@@ -237,6 +241,7 @@ export enum ManualJobName {
237241

238242
export enum AssetPathType {
239243
ORIGINAL = 'original',
244+
EXTRACTED = 'extracted',
240245
PREVIEW = 'preview',
241246
THUMBNAIL = 'thumbnail',
242247
ENCODED_VIDEO = 'encoded_video',

server/src/interfaces/asset.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export interface IAssetRepository {
180180
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
181181
update(asset: AssetUpdateOptions): Promise<void>;
182182
remove(asset: AssetEntity): Promise<void>;
183+
removeAssetFile(path: string): Promise<void>;
183184
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
184185
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
185186
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;

server/src/repositories/asset.repository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ export class AssetRepository implements IAssetRepository {
281281
await this.repository.remove(asset);
282282
}
283283

284+
async removeAssetFile(path: string): Promise<void> {
285+
await this.fileRepository.delete({ path });
286+
}
287+
284288
@GenerateSql({ params: [{ ownerId: DummyValue.UUID, libraryId: DummyValue.UUID, checksum: DummyValue.BUFFER }] })
285289
getByChecksum({
286290
ownerId,

server/src/repositories/media.repository.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export class MediaRepository implements IMediaRepository {
4444

4545
async extract(input: string, output: string): Promise<boolean> {
4646
try {
47+
// remove existing output file if it exists
48+
// as exiftool-vendord does not support overwriting via "-w!" flag
49+
// and throws "1 files could not be read" error when the output file exists
50+
await fs.unlink(output).catch(() => null);
51+
this.logger.debug('Extracting JPEG from RAW image:', input);
4752
await exiftool.extractJpgFromRaw(input, output);
4853
} catch (error: any) {
4954
this.logger.debug('Could not extract JPEG from image, trying preview', error.message);
@@ -98,6 +103,10 @@ export class MediaRepository implements IMediaRepository {
98103
pipeline = pipeline.extract(options.crop);
99104
}
100105

106+
// Infinity is a special value that means no resizing
107+
if (options.size === Infinity) {
108+
return pipeline;
109+
}
101110
return pipeline.resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true });
102111
}
103112

0 commit comments

Comments
 (0)