refactor(server): use exiftool for file date metadata (#16453)

* use exiftool for file date metadata

* handle tag not existing in exifinfo (?)

* update medium tests

* fix typo

* set file size too

* set file size only if undefined
This commit is contained in:
Mert
2025-03-06 11:47:12 -05:00
committed by GitHub
parent d01b7a0d67
commit deb399ea15
4 changed files with 85 additions and 52 deletions
+20 -24
View File
@@ -171,21 +171,17 @@ export class MetadataService extends BaseService {
return JobStatus.FAILED;
}
const [stats, exifTags] = await Promise.all([
this.storageRepository.stat(asset.originalPath),
this.getExifTags(asset),
]);
const exifTags = await this.getExifTags(asset);
if (!exifTags.FileCreateDate || !exifTags.FileModifyDate || exifTags.FileSize === undefined) {
this.logger.warn(`Missing file creation or modification date for asset ${asset.id}: ${asset.originalPath}`);
const stat = await this.storageRepository.stat(asset.originalPath);
exifTags.FileCreateDate = stat.ctime.toISOString();
exifTags.FileModifyDate = stat.mtime.toISOString();
exifTags.FileSize = stat.size.toString();
}
this.logger.verbose('Exif Tags', exifTags);
if (!asset.fileCreatedAt) {
asset.fileCreatedAt = stats.mtime;
}
if (!asset.fileModifiedAt) {
asset.fileModifiedAt = stats.mtime;
}
const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags);
const { width, height } = this.getImageDimensions(exifTags);
@@ -216,7 +212,7 @@ export class MetadataService extends BaseService {
city: geo.city,
// image/file
fileSizeInByte: stats.size,
fileSizeInByte: Number.parseInt(exifTags.FileSize!),
exifImageHeight: validate(height),
exifImageWidth: validate(width),
orientation: validate(exifTags.Orientation)?.toString() ?? null,
@@ -251,13 +247,13 @@ export class MetadataService extends BaseService {
duration: exifTags.Duration?.toString() ?? null,
localDateTime,
fileCreatedAt: exifData.dateTimeOriginal ?? undefined,
fileModifiedAt: stats.mtime,
fileModifiedAt: exifData.modifyDate ?? undefined,
}),
this.applyTagList(asset, exifTags),
];
if (this.isMotionPhoto(asset, exifTags)) {
promises.push(this.applyMotionPhotos(asset, exifTags));
promises.push(this.applyMotionPhotos(asset, exifTags, exifData.fileSizeInByte!));
}
if (isFaceImportEnabled(metadata) && this.hasTaggedFaces(exifTags)) {
@@ -436,7 +432,7 @@ export class MetadataService extends BaseService {
return asset.type === AssetType.IMAGE && !!(tags.MotionPhoto || tags.MicroVideo);
}
private async applyMotionPhotos(asset: AssetEntity, tags: ImmichTags) {
private async applyMotionPhotos(asset: AssetEntity, tags: ImmichTags, fileSize: number) {
const isMotionPhoto = tags.MotionPhoto;
const isMicroVideo = tags.MicroVideo;
const videoOffset = tags.MicroVideoOffset;
@@ -470,8 +466,7 @@ export class MetadataService extends BaseService {
this.logger.debug(`Starting motion photo video extraction for asset ${asset.id}: ${asset.originalPath}`);
try {
const stat = await this.storageRepository.stat(asset.originalPath);
const position = stat.size - length - padding;
const position = fileSize - length - padding;
let video: Buffer;
// Samsung MotionPhoto video extraction
// HEIC-encoded
@@ -659,10 +654,12 @@ export class MetadataService extends BaseService {
this.logger.debug(`No timezone information found for asset ${asset.id}: ${asset.originalPath}`);
}
const modifyDate = this.toDate(exifTags.FileModifyDate!);
let dateTimeOriginal = dateTime?.toDate();
let localDateTime = dateTime?.toDateTime().setZone('UTC', { keepLocalTime: true }).toJSDate();
if (!localDateTime || !dateTimeOriginal) {
const earliestDate = this.earliestDate(asset.fileModifiedAt, asset.fileCreatedAt);
const fileCreatedAt = this.toDate(exifTags.FileCreateDate!);
const earliestDate = this.earliestDate(fileCreatedAt, modifyDate);
this.logger.debug(
`No exif date time found, falling back on ${earliestDate.toISOString()}, earliest of file creation and modification for assset ${asset.id}: ${asset.originalPath}`,
);
@@ -674,11 +671,6 @@ export class MetadataService extends BaseService {
`Found local date time ${localDateTime.toISOString()} for asset ${asset.id}: ${asset.originalPath}`,
);
let modifyDate = asset.fileModifiedAt;
try {
modifyDate = (exifTags.ModifyDate as ExifDateTime)?.toDate() ?? modifyDate;
} catch {}
return {
dateTimeOriginal,
timeZone,
@@ -687,6 +679,10 @@ export class MetadataService extends BaseService {
};
}
private toDate(date: string | ExifDateTime): Date {
return typeof date === 'string' ? new Date(date) : date.toDate();
}
private earliestDate(a: Date, b: Date) {
return new Date(Math.min(a.valueOf(), b.valueOf()));
}