mirror of
https://github.com/immich-app/immich.git
synced 2024-11-28 09:33:27 +02:00
chore(server) harden EXIF extraction (#1347)
* chore(server) Harden EXIF extraction * Remove unused function in timeutil * Remove deadcode
This commit is contained in:
parent
dff10e89fe
commit
3e4a14b299
@ -188,16 +188,12 @@ export class AssetService {
|
||||
isVisible: boolean,
|
||||
livePhotoAssetEntity?: AssetEntity,
|
||||
): Promise<AssetEntity> {
|
||||
// Check valid time.
|
||||
const createdAt = createAssetDto.createdAt;
|
||||
const modifiedAt = createAssetDto.modifiedAt;
|
||||
|
||||
if (!timeUtils.checkValidTimestamp(createdAt)) {
|
||||
createAssetDto.createdAt = await timeUtils.getTimestampFromExif(originalPath);
|
||||
if (!timeUtils.checkValidTimestamp(createAssetDto.createdAt)) {
|
||||
createAssetDto.createdAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
if (!timeUtils.checkValidTimestamp(modifiedAt)) {
|
||||
createAssetDto.modifiedAt = await timeUtils.getTimestampFromExif(originalPath);
|
||||
if (!timeUtils.checkValidTimestamp(createAssetDto.modifiedAt)) {
|
||||
createAssetDto.modifiedAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
const assetEntity = await this._assetRepository.create(
|
||||
|
@ -18,8 +18,7 @@ import { Repository } from 'typeorm/repository/Repository';
|
||||
import geocoder, { InitOptions } from 'local-reverse-geocoder';
|
||||
import { getName } from 'i18n-iso-countries';
|
||||
import fs from 'node:fs';
|
||||
import { ExifDateTime, ExifTool } from 'exiftool-vendored';
|
||||
import { timeUtils } from '@app/common';
|
||||
import { ExifDateTime, exiftool } from 'exiftool-vendored';
|
||||
|
||||
function geocoderInit(init: InitOptions) {
|
||||
return new Promise<void>(function (resolve) {
|
||||
@ -140,43 +139,52 @@ export class MetadataExtractionProcessor {
|
||||
async extractExifInfo(job: Job<IExifExtractionProcessor>) {
|
||||
try {
|
||||
const { asset, fileName }: { asset: AssetEntity; fileName: string } = job.data;
|
||||
const exiftool = new ExifTool();
|
||||
|
||||
const exifData = await exiftool.read(asset.originalPath).catch((e) => {
|
||||
this.logger.warn(`The exifData parsing failed due to: ${e} on file ${asset.originalPath}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
const exifToDate = (exifDate: string | ExifDateTime | undefined) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
exifDate ? new Date(exifDate.toString()!) : null;
|
||||
const exifToDate = (exifDate: string | ExifDateTime | undefined) => {
|
||||
if (!exifDate) return null;
|
||||
|
||||
let createdAt = exifToDate(asset.createdAt);
|
||||
const newExif = new ExifEntity();
|
||||
if (exifData) {
|
||||
createdAt = exifToDate(exifData.DateTimeOriginal ?? exifData.CreateDate ?? asset.createdAt);
|
||||
const modifyDate = exifToDate(exifData.ModifyDate);
|
||||
newExif.make = exifData['Make'] || null;
|
||||
newExif.model = exifData['Model'] || null;
|
||||
newExif.exifImageHeight = exifData['ExifImageHeight'] || exifData['ImageHeight'] || null;
|
||||
newExif.exifImageWidth = exifData['ExifImageWidth'] || exifData['ImageWidth'] || null;
|
||||
newExif.exposureTime = (await timeUtils.parseStringToNumber(exifData['ExposureTime'])) || null;
|
||||
newExif.orientation = exifData['Orientation']?.toString() || null;
|
||||
newExif.dateTimeOriginal = createdAt;
|
||||
newExif.modifyDate = modifyDate || null;
|
||||
newExif.lensModel = exifData['LensModel'] || null;
|
||||
newExif.fNumber = exifData['FNumber'] || null;
|
||||
newExif.focalLength = (await timeUtils.parseStringToNumber(exifData['FocalLength'])) || null;
|
||||
newExif.iso = exifData['ISO'] || null;
|
||||
newExif.latitude = exifData['GPSLatitude'] || null;
|
||||
newExif.longitude = exifData['GPSLongitude'] || null;
|
||||
} else {
|
||||
newExif.dateTimeOriginal = createdAt;
|
||||
newExif.modifyDate = exifToDate(asset.modifiedAt);
|
||||
}
|
||||
if (typeof exifDate === 'string') {
|
||||
return new Date(exifDate);
|
||||
}
|
||||
|
||||
return exifDate.toDate();
|
||||
};
|
||||
|
||||
const getExposureTimeDenominator = (exposureTime: string | undefined) => {
|
||||
if (!exposureTime) return null;
|
||||
|
||||
const exposureTimeSplit = exposureTime.split('/');
|
||||
return exposureTimeSplit.length === 2 ? parseInt(exposureTimeSplit[1]) : null;
|
||||
};
|
||||
|
||||
const createdAt = exifToDate(exifData?.DateTimeOriginal ?? exifData?.CreateDate ?? asset.createdAt);
|
||||
const modifyDate = exifToDate(exifData?.ModifyDate ?? asset.modifiedAt);
|
||||
const fileStats = fs.statSync(asset.originalPath);
|
||||
const fileSizeInBytes = fileStats.size;
|
||||
|
||||
const newExif = new ExifEntity();
|
||||
newExif.assetId = asset.id;
|
||||
newExif.imageName = path.parse(fileName).name || null;
|
||||
newExif.fileSizeInByte = fileSizeInBytes || null;
|
||||
newExif.imageName = path.parse(fileName).name;
|
||||
newExif.fileSizeInByte = fileSizeInBytes;
|
||||
newExif.make = exifData?.Make || null;
|
||||
newExif.model = exifData?.Model || null;
|
||||
newExif.exifImageHeight = exifData?.ExifImageHeight || exifData?.ImageHeight || null;
|
||||
newExif.exifImageWidth = exifData?.ExifImageWidth || exifData?.ImageWidth || null;
|
||||
newExif.exposureTime = getExposureTimeDenominator(exifData?.ExposureTime);
|
||||
newExif.orientation = exifData?.Orientation?.toString() || null;
|
||||
newExif.dateTimeOriginal = createdAt;
|
||||
newExif.modifyDate = modifyDate;
|
||||
newExif.lensModel = exifData?.LensModel || null;
|
||||
newExif.fNumber = exifData?.FNumber || null;
|
||||
newExif.focalLength = exifData?.FocalLength ? parseFloat(exifData.FocalLength) : null;
|
||||
newExif.iso = exifData?.ISO || null;
|
||||
newExif.latitude = exifData?.GPSLatitude || null;
|
||||
newExif.longitude = exifData?.GPSLongitude || null;
|
||||
|
||||
await this.assetRepository.save({
|
||||
id: asset.id,
|
||||
@ -217,7 +225,6 @@ export class MetadataExtractionProcessor {
|
||||
}
|
||||
|
||||
await this.exifRepository.save(newExif);
|
||||
await exiftool.end();
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Error extracting EXIF ${error}`, error?.stack);
|
||||
}
|
||||
|
@ -1,12 +1,4 @@
|
||||
// This is needed as resolving for the vendored
|
||||
// exiftool fails in tests otherwise but as it's not meant to be a requirement
|
||||
// of a project directly I had to include the line below the comment.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { exiftool } from 'exiftool-vendored.pl';
|
||||
|
||||
function createTimeUtils() {
|
||||
const floatRegex = /[+-]?([0-9]*[.])?[0-9]+/;
|
||||
const checkValidTimestamp = (timestamp: string): boolean => {
|
||||
const parsedTimestamp = Date.parse(timestamp);
|
||||
|
||||
@ -23,32 +15,7 @@ function createTimeUtils() {
|
||||
return date.getFullYear() > 0;
|
||||
};
|
||||
|
||||
const getTimestampFromExif = async (originalPath: string): Promise<string> => {
|
||||
try {
|
||||
const exifData = await exiftool.read(originalPath);
|
||||
|
||||
if (exifData && exifData['DateTimeOriginal']) {
|
||||
await exiftool.end();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return exifData['DateTimeOriginal'].toString()!;
|
||||
} else {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
} catch (error) {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
};
|
||||
|
||||
const parseStringToNumber = async (original: string | undefined): Promise<number | null> => {
|
||||
const match = original?.match(floatRegex)?.[0];
|
||||
if (match) {
|
||||
return parseFloat(match);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return { checkValidTimestamp, getTimestampFromExif, parseStringToNumber };
|
||||
return { checkValidTimestamp };
|
||||
}
|
||||
|
||||
export const timeUtils = createTimeUtils();
|
||||
|
@ -152,7 +152,7 @@
|
||||
<p>{`ƒ/${asset.exifInfo.fNumber.toLocaleString(locale)}` || ''}</p>
|
||||
|
||||
{#if asset.exifInfo.exposureTime}
|
||||
<p>{`1/${Math.floor(1 / asset.exifInfo.exposureTime)}`}</p>
|
||||
<p>{`1/${asset.exifInfo.exposureTime}`}</p>
|
||||
{/if}
|
||||
|
||||
{#if asset.exifInfo.focalLength}
|
||||
|
Loading…
Reference in New Issue
Block a user