feat(web): rotate image
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -222,6 +222,7 @@ export interface ISidecarWriteJob extends IEntityJob {
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
rating?: number;
|
||||
orientation?: ExifOrientation;
|
||||
tags?: true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user