diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index df6018b299..067ebbebcc 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -1137,7 +1137,7 @@ describe('/asset', () => { type: AssetTypeEnum.Image, originalFileName: '14bit-uncompressed-(3_2).arw', resized: true, - fileCreatedAt: '2016-01-08T15:08:01.000Z', + fileCreatedAt: '2016-01-08T14:08:01.000Z', exifInfo: { make: 'SONY', model: 'ILCE-7M2', @@ -1149,7 +1149,7 @@ describe('/asset', () => { iso: 100, lensModel: 'E 25mm F2', fileSizeInByte: 49_512_448, - dateTimeOriginal: '2016-01-08T15:08:01.000Z', + dateTimeOriginal: '2016-01-08T14:08:01.000Z', latitude: null, longitude: null, orientation: '1', diff --git a/server/package-lock.json b/server/package-lock.json index 6d311041ac..95ec5fa383 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -34,7 +34,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", - "exiftool-vendored": "26.0.0", + "exiftool-vendored": "^28.1.0", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", "geo-tz": "^8.0.0", @@ -9549,9 +9549,9 @@ } }, "node_modules/exiftool-vendored": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-26.0.0.tgz", - "integrity": "sha512-2TRxx21ovD95VvdSzHb/sTYYcwhiizQIhhVAbrgua9KoL902QRieREGvaUtfBZNjsptdjonuyku2kUBJCPqsgw==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.1.0.tgz", + "integrity": "sha512-Anlfl16gv0QuaNbkMuwutCfhzzPn/33Lio2fKCgIHk4m+udz5dsatwv1+tjk4eDMNT1Oj/zwG3hKhSX5zlHzAw==", "dependencies": { "@photostructure/tz-lookup": "^10.0.0", "@types/luxon": "^3.4.2", @@ -9560,23 +9560,23 @@ "luxon": "^3.4.4" }, "optionalDependencies": { - "exiftool-vendored.exe": "12.84.0", - "exiftool-vendored.pl": "12.84.0" + "exiftool-vendored.exe": "12.89.0", + "exiftool-vendored.pl": "12.89.0" } }, "node_modules/exiftool-vendored.exe": { - "version": "12.84.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.84.0.tgz", - "integrity": "sha512-9ocqJb0Pr9k0TownEMd75payF/XOQLF/swr/l0Ep49D+m609uIZsW09CtowhXmk1KrIFobS3+SkdXK04CSyUwQ==", + "version": "12.89.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.89.0.tgz", + "integrity": "sha512-GyayTRwH6v/3SCV7g80zV5oMw66AQFnPJVfPc/At9PMAe90/9N7GCM1o6U1FGOpa1ZvmSm38RvvBEMr667vnnw==", "optional": true, "os": [ "win32" ] }, "node_modules/exiftool-vendored.pl": { - "version": "12.84.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.84.0.tgz", - "integrity": "sha512-TxvMRaVYtd24Vupn48zy24LOYItIIWEu4dgt/VlqLwxQItTpvJTV9YH04iZRvaNh9ZdPRgVKWMuuUDBBHv+lAg==", + "version": "12.89.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.89.0.tgz", + "integrity": "sha512-pkkWBRmeylUEAfBOg/e6NO8dZ572yDA6kTsnw1gGiAhJUxgoLGP+ageuOD6Oxywag5/HFcT2syJ+L1/ch5hjfg==", "optional": true, "os": [ "!win32" @@ -23433,29 +23433,29 @@ } }, "exiftool-vendored": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-26.0.0.tgz", - "integrity": "sha512-2TRxx21ovD95VvdSzHb/sTYYcwhiizQIhhVAbrgua9KoL902QRieREGvaUtfBZNjsptdjonuyku2kUBJCPqsgw==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.1.0.tgz", + "integrity": "sha512-Anlfl16gv0QuaNbkMuwutCfhzzPn/33Lio2fKCgIHk4m+udz5dsatwv1+tjk4eDMNT1Oj/zwG3hKhSX5zlHzAw==", "requires": { "@photostructure/tz-lookup": "^10.0.0", "@types/luxon": "^3.4.2", "batch-cluster": "^13.0.0", - "exiftool-vendored.exe": "12.84.0", - "exiftool-vendored.pl": "12.84.0", + "exiftool-vendored.exe": "12.89.0", + "exiftool-vendored.pl": "12.89.0", "he": "^1.2.0", "luxon": "^3.4.4" } }, "exiftool-vendored.exe": { - "version": "12.84.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.84.0.tgz", - "integrity": "sha512-9ocqJb0Pr9k0TownEMd75payF/XOQLF/swr/l0Ep49D+m609uIZsW09CtowhXmk1KrIFobS3+SkdXK04CSyUwQ==", + "version": "12.89.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.89.0.tgz", + "integrity": "sha512-GyayTRwH6v/3SCV7g80zV5oMw66AQFnPJVfPc/At9PMAe90/9N7GCM1o6U1FGOpa1ZvmSm38RvvBEMr667vnnw==", "optional": true }, "exiftool-vendored.pl": { - "version": "12.84.0", - "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.84.0.tgz", - "integrity": "sha512-TxvMRaVYtd24Vupn48zy24LOYItIIWEu4dgt/VlqLwxQItTpvJTV9YH04iZRvaNh9ZdPRgVKWMuuUDBBHv+lAg==", + "version": "12.89.0", + "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.89.0.tgz", + "integrity": "sha512-pkkWBRmeylUEAfBOg/e6NO8dZ572yDA6kTsnw1gGiAhJUxgoLGP+ageuOD6Oxywag5/HFcT2syJ+L1/ch5hjfg==", "optional": true }, "express": { diff --git a/server/package.json b/server/package.json index ab67fbc5ee..678c695f05 100644 --- a/server/package.json +++ b/server/package.json @@ -60,7 +60,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", - "exiftool-vendored": "26.0.0", + "exiftool-vendored": "^28.1.0", "fast-glob": "^3.3.2", "fluent-ffmpeg": "^2.1.2", "geo-tz": "^8.0.0", diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 56b948208a..3ca088e2d7 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { DefaultReadTaskOptions, Tags, exiftool } from 'exiftool-vendored'; +import { DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored'; import geotz from 'geo-tz'; import { DummyValue, GenerateSql } from 'src/decorators'; import { ExifEntity } from 'src/entities/exif.entity'; @@ -12,6 +12,19 @@ import { Repository } from 'typeorm'; @Instrumentation() @Injectable() export class MetadataRepository implements IMetadataRepository { + private exiftool = new ExifTool({ + defaultVideosToUTC: true, + backfillTimezones: true, + inferTimezoneFromDatestamps: true, + useMWG: true, + numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength'], + /* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */ + geoTz: (lat, lon) => geotz.find(lat, lon)[0], + // Enable exiftool LFS to parse metadata for files larger than 2GB. + readArgs: ['-api', 'largefilesupport=1'], + writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'], + }); + constructor( @InjectRepository(ExifEntity) private exifRepository: Repository, @Inject(ILoggerRepository) private logger: ILoggerRepository, @@ -20,37 +33,23 @@ export class MetadataRepository implements IMetadataRepository { } async teardown() { - await exiftool.end(); + await this.exiftool.end(); } readTags(path: string): Promise { - return exiftool - .read(path, undefined, { - ...DefaultReadTaskOptions, - - // Enable exiftool LFS to parse metadata for files larger than 2GB. - optionalArgs: ['-api', 'largefilesupport=1'], - defaultVideosToUTC: true, - backfillTimezones: true, - inferTimezoneFromDatestamps: true, - useMWG: true, - numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength'], - /* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */ - geoTz: (lat, lon) => geotz.find(lat, lon)[0], - }) - .catch((error) => { - this.logger.warn(`Error reading exif data (${path}): ${error}`, error?.stack); - return null; - }) as Promise; + return this.exiftool.read(path).catch((error) => { + this.logger.warn(`Error reading exif data (${path}): ${error}`, error?.stack); + return null; + }) as Promise; } extractBinaryTag(path: string, tagName: string): Promise { - return exiftool.extractBinaryTagToBuffer(tagName, path); + return this.exiftool.extractBinaryTagToBuffer(tagName, path); } async writeTags(path: string, tags: Partial): Promise { try { - await exiftool.write(path, tags, ['-overwrite_original']); + await this.exiftool.write(path, tags); } catch (error) { this.logger.warn(`Error writing exif data (${path}): ${error}`); } diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 5b64bb4364..ee3b24fad5 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { ExifDateTime, Tags } from 'exiftool-vendored'; +import { ContainerDirectoryItem, ExifDateTime, Tags } from 'exiftool-vendored'; import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime'; import _ from 'lodash'; import { Duration } from 'luxon'; @@ -48,17 +48,6 @@ const EXIF_DATE_TAGS: Array = [ 'DateTimeCreated', ]; -interface DirectoryItem { - Length?: number; - Mime: string; - Padding?: number; - Semantic?: string; -} - -interface DirectoryEntry { - Item: DirectoryItem; -} - export enum Orientation { Horizontal = '1', MirrorHorizontal = '2', @@ -362,13 +351,14 @@ export class MetadataService implements OnEvents { return; } - const rawDirectory = tags.Directory; const isMotionPhoto = tags.MotionPhoto; const isMicroVideo = tags.MicroVideo; const videoOffset = tags.MicroVideoOffset; const hasMotionPhotoVideo = tags.MotionPhotoVideo; const hasEmbeddedVideoFile = tags.EmbeddedVideoType === 'MotionPhoto_Data' && tags.EmbeddedVideoFile; - const directory = Array.isArray(rawDirectory) ? (rawDirectory as DirectoryEntry[]) : null; + const directory = Array.isArray(tags.ContainerDirectory) + ? (tags.ContainerDirectory as ContainerDirectoryItem[]) + : null; let length = 0; let padding = 0;