feat(web): rotate image

This commit is contained in:
Jason Rasmussen
2025-02-13 17:02:44 -05:00
parent dbbefde98d
commit 9cd0871178
24 changed files with 441 additions and 53 deletions
+7 -1
View File
@@ -14,7 +14,7 @@ import {
ValidateIf,
} from 'class-validator';
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
import { AssetType } from 'src/enum';
import { AssetType, ExifOrientation } from 'src/enum';
import { AssetStats } from 'src/repositories/asset.repository';
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
@@ -54,6 +54,12 @@ export class UpdateAssetBase {
@Max(5)
@Min(-1)
rating?: number;
@Optional()
@Min(1)
@Max(8)
@ApiProperty({ type: 'integer' })
orientation?: ExifOrientation;
}
export class AssetBulkUpdateDto extends UpdateAssetBase {
@@ -101,6 +101,7 @@ export class MetadataRepository {
}
async writeTags(path: string, tags: Partial<Tags>): Promise<void> {
this.logger.verbose(`Writing tags ${JSON.stringify(tags)} to ${path}`);
try {
await this.exiftool.write(path, tags);
} catch (error) {
+10 -6
View File
@@ -100,7 +100,7 @@ export class AssetService extends BaseService {
async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids: [id] });
const { description, dateTimeOriginal, latitude, longitude, rating, ...rest } = dto;
const { description, dateTimeOriginal, latitude, longitude, rating, orientation, ...rest } = dto;
const repos = { asset: this.assetRepository, event: this.eventRepository };
let previousMotion: AssetEntity | null = null;
@@ -113,7 +113,7 @@ export class AssetService extends BaseService {
}
}
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude, rating });
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude, rating, orientation });
const asset = await this.assetRepository.update({ id, ...rest });
@@ -129,11 +129,12 @@ export class AssetService extends BaseService {
}
async updateAll(auth: AuthDto, dto: AssetBulkUpdateDto): Promise<void> {
const { ids, dateTimeOriginal, latitude, longitude, ...options } = dto;
const { ids, dateTimeOriginal, latitude, longitude, orientation, ...options } = dto;
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids });
// TODO rewrite this to support batching
for (const id of ids) {
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude });
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude, orientation });
}
if (
@@ -284,11 +285,14 @@ export class AssetService extends BaseService {
}
private async updateMetadata(dto: ISidecarWriteJob) {
const { id, description, dateTimeOriginal, latitude, longitude, rating } = dto;
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, rating }, _.isUndefined);
const { id, description, dateTimeOriginal, latitude, longitude, rating, orientation } = dto;
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, rating, orientation }, _.isUndefined);
if (Object.keys(writes).length > 0) {
await this.assetRepository.upsertExif({ assetId: id, ...writes });
await this.jobRepository.queue({ name: JobName.SIDECAR_WRITE, data: { id, ...writes } });
if (orientation !== undefined) {
await this.jobRepository.queue({ name: JobName.GENERATE_THUMBNAILS, data: { id, notify: true } });
}
}
}
}
+1 -1
View File
@@ -215,7 +215,7 @@ export class MediaService extends BaseService {
const colorspace = this.isSRGB(asset) ? Colorspace.SRGB : image.colorspace;
const processInvalidImages = process.env.IMMICH_PROCESS_INVALID_IMAGES === 'true';
const orientation = useExtracted && asset.exifInfo?.orientation ? Number(asset.exifInfo.orientation) : undefined;
const orientation = asset.exifInfo?.orientation ? Number(asset.exifInfo.orientation) : undefined;
const decodeOptions = { colorspace, processInvalidImages, size: image.preview.size, orientation };
const { data, info } = await this.mediaRepository.decodeImage(inputPath, decodeOptions);
+2 -1
View File
@@ -295,7 +295,7 @@ export class MetadataService extends BaseService {
@OnJob({ name: JobName.SIDECAR_WRITE, queue: QueueName.SIDECAR })
async handleSidecarWrite(job: JobOf<JobName.SIDECAR_WRITE>): Promise<JobStatus> {
const { id, description, dateTimeOriginal, latitude, longitude, rating, tags } = job;
const { id, description, dateTimeOriginal, latitude, longitude, rating, tags, orientation } = job;
const [asset] = await this.assetRepository.getByIds([id], { tags: true });
if (!asset) {
return JobStatus.FAILED;
@@ -311,6 +311,7 @@ export class MetadataService extends BaseService {
DateTimeOriginal: dateTimeOriginal,
GPSLatitude: latitude,
GPSLongitude: longitude,
'Orientation#': orientation,
Rating: rating,
TagsList: tags ? tagsList : undefined,
},
+1
View File
@@ -222,6 +222,7 @@ export interface ISidecarWriteJob extends IEntityJob {
latitude?: number;
longitude?: number;
rating?: number;
orientation?: ExifOrientation;
tags?: true;
}