From a6b24aa5519136c96d3b81f4f12116e30453b02a Mon Sep 17 00:00:00 2001 From: gras Date: Sat, 13 Apr 2024 18:16:18 +0200 Subject: [PATCH] update from master and fix merge comment --- .../model/fileaccess/MetadataLoader.ts | 8 ++--- src/common/Utils.ts | 17 +++++++--- test/backend/assets/date_issue.json | 3 +- .../edge_case_exif_data/date_error.json | 2 +- .../orientation/broken_orientation_exif.json | 3 +- ...en_no_tsoffset_but_gps_utc_off_by_1min.jpg | Bin 0 -> 18663 bytes ...n_no_tsoffset_but_gps_utc_off_by_1min.json | 25 ++++++++++++++ ...fset_but_gps_utc_off_by_1min_no_xmpgps.jpg | Bin 0 -> 18601 bytes ...set_but_gps_utc_off_by_1min_no_xmpgps.json | 25 ++++++++++++++ .../model/threading/MetaDataLoader.spec.ts | 32 ++++++++++++++++++ 10 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.jpg create mode 100644 test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.json create mode 100644 test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.jpg create mode 100644 test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.json diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index a7f474da..576db791 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -380,27 +380,27 @@ export class MetadataLoader { if (!offset) { //Find offset among other options if possible offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); } - metadata.creationDate = Utils.timestampToMS(exif.exif.DateTimeOriginal, offset); + metadata.creationDate = Utils.timestampToMS(exif.exif.DateTimeOriginal, offset) || metadata.creationDate; } else if (exif.exif.CreateDate) { //using else if here, because DateTimeOriginal has preceedence //Create is when the camera wrote the file (typically within the same ms as shutter close) offset = exif.exif.OffsetTimeDigitized; //OffsetTimeDigitized is the corresponding offset if (!offset) { //Find offset among other options if possible offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); } - metadata.creationDate = Utils.timestampToMS(exif.exif.CreateDate, offset); + metadata.creationDate = Utils.timestampToMS(exif.exif.CreateDate, offset) || metadata.creationDate; } else if (exif.ifd0?.ModifyDate) { //using else if here, because DateTimeOriginal and CreatDate have preceedence offset = exif.exif.OffsetTime; //exif.Offsettime is the offset corresponding to ifd0.ModifyDate if (!offset) { //Find offset among other options if possible offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || Utils.getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); } - metadata.creationDate = Utils.timestampToMS(exif.ifd0.ModifyDate, offset); + metadata.creationDate = Utils.timestampToMS(exif.ifd0.ModifyDate, offset) || metadata.creationDate; } else if (exif.ihdr && exif.ihdr["Creation Time"]) {// again else if (another fallback date if the good ones aren't there) { const any_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); metadata.creationDate = Utils.timestampToMS(exif.ihdr["Creation Time"], any_offset); offset = any_offset; } else if (exif.xmp?.MetadataDate) {// again else if (another fallback date if the good ones aren't there - metadata date is probably later than actual creation date, but much better than file time) { const any_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); - metadata.creationDate = Utils.timestampToMS(exif.xmp.MetadataDate, any_offset); + metadata.creationDate = Utils.timestampToMS(exif.xmp.MetadataDate, any_offset) || metadata.creationDate; offset = any_offset; } metadata.creationDateOffset = offset || metadata.creationDateOffset; diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 2750cf29..ce4f5269 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -186,12 +186,12 @@ export class Utils { gps.GPSDateStamp && gps.GPSTimeStamp) { //else use exif.gps.GPS*Stamp if available //GPS timestamp is always UTC (+00:00) - UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + gps.GPSTimeStamp.join(':'); + UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + " " + gps.GPSTimeStamp.map((num: any) => Utils.zeroPad(num ,2)).join(':'); } if (UTCTimestamp && timestamp) { //offset in minutes is the difference between gps timestamp and given timestamp //to calculate this correctly, we have to work with the same offset - const offsetMinutes = (Utils.timestampToMS(timestamp, '+00:00')- Utils.timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60; + const offsetMinutes: number = Math.round((Utils.timestampToMS(timestamp, '+00:00')- Utils.timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60); return Utils.getOffsetString(offsetMinutes); } else { return undefined; @@ -202,13 +202,22 @@ export class Utils { if (-720 <= offsetMinutes && offsetMinutes <= 840) { //valid offset is within -12 and +14 hrs (https://en.wikipedia.org/wiki/List_of_UTC_offsets) return (offsetMinutes < 0 ? "-" : "+") + //leading +/- - ("0" + Math.trunc(Math.abs(offsetMinutes) / 60)).slice(-2) + ":" + //zeropadded hours and ':' - ("0" + Math.abs(offsetMinutes) % 60).slice(-2); //zeropadded minutes + Utils.zeroPad(Math.trunc(Math.abs(offsetMinutes) / 60), 2) + ":" + //zeropadded hours and ':' + Utils.zeroPad((Math.abs(offsetMinutes) % 60), 2); //zeropadded minutes } else { return undefined; } } + static zeroPad(number: any, length: number): string { + if (!isNaN(number)) { + const zerosToAdd = Math.max(length - String(number).length, 0); + return '0'.repeat(zerosToAdd) + number; + } else { + return '0'.repeat(number); + } + } + static getOffsetMinutes(offsetString: string) { //Convert offset string (+HH:MM or -HH:MM) into a minute value const regex = /^([+-](0[0-9]|1[0-4]):[0-5][0-9])$/; //checks if offset is between -14:00 and +14:00. //-12:00 is the lowest valid UTC-offset, but we allow down to -14 for efficiency diff --git a/test/backend/assets/date_issue.json b/test/backend/assets/date_issue.json index ee6f2cf0..555977fc 100644 --- a/test/backend/assets/date_issue.json +++ b/test/backend/assets/date_issue.json @@ -7,7 +7,8 @@ "make": "HUAWEI", "model": "HUAWEI G6-L11" }, - "creationDate": 1460826466000, + "creationDate": 1460819266000, + "creationDateOffset": "+02:00", "fileSize": 1980, "size": { "height": 1, diff --git a/test/backend/assets/edge_case_exif_data/date_error.json b/test/backend/assets/edge_case_exif_data/date_error.json index 7d973302..889aaf1c 100644 --- a/test/backend/assets/edge_case_exif_data/date_error.json +++ b/test/backend/assets/edge_case_exif_data/date_error.json @@ -7,7 +7,7 @@ "make": "NIKON", "model": "E880" }, - "creationDate": 0, + "creationDate": "fileModificationTime", "fileSize": 72850, "size": { "height": 768, diff --git a/test/backend/assets/orientation/broken_orientation_exif.json b/test/backend/assets/orientation/broken_orientation_exif.json index 237c10d5..b66b9bf6 100644 --- a/test/backend/assets/orientation/broken_orientation_exif.json +++ b/test/backend/assets/orientation/broken_orientation_exif.json @@ -3,7 +3,8 @@ "width": 3024, "height": 4032 }, - "creationDate": 1518964712000, + "creationDate": 1518982712000, + "creationDateOffset": "-05:00", "fileSize": 256001, "cameraData": { "model": "Pixel 2", diff --git a/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.jpg b/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce8586eebd62d085dd1c4666d2deac81aebc3e96 GIT binary patch literal 18663 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 literal 0 HcmV?d00001 diff --git a/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.json b/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.json new file mode 100644 index 00000000..28844e96 --- /dev/null +++ b/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.json @@ -0,0 +1,25 @@ +{ + "size": { + "width": 200, + "height": 300 + }, + "creationDate": 1686141955000, + "creationDateOffset": "+01:00", + "fileSize": 18663, + "cameraData": { + "model": "Canon EOS R5", + "make": "Canon" + }, + "positionData": { + "GPSData": { + "longitude": -0.124575, + "latitude": 51.500694 + }, + "country": "Storbritannien", + "state": "England", + "city": "St James's" + }, + "keywords": [ + "Big Ben" + ] +} \ No newline at end of file diff --git a/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.jpg b/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c1cf0ff14e7c02e6f91c425142c5f85a94d1aa0a GIT binary patch literal 18601 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 Rz(TUNyJl@!9DFeGe*gky(((WR literal 0 HcmV?d00001 diff --git a/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.json b/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.json new file mode 100644 index 00000000..df48c9ee --- /dev/null +++ b/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.json @@ -0,0 +1,25 @@ +{ + "size": { + "width": 200, + "height": 300 + }, + "creationDate": 1686141955000, + "creationDateOffset": "+01:00", + "fileSize": 18601, + "cameraData": { + "model": "Canon EOS R5", + "make": "Canon" + }, + "positionData": { + "GPSData": { + "longitude": -0.124575, + "latitude": 51.500694 + }, + "country": "Storbritannien", + "state": "England", + "city": "St James's" + }, + "keywords": [ + "Big Ben" + ] +} \ No newline at end of file diff --git a/test/backend/unit/model/threading/MetaDataLoader.spec.ts b/test/backend/unit/model/threading/MetaDataLoader.spec.ts index c47f4975..525c6a88 100644 --- a/test/backend/unit/model/threading/MetaDataLoader.spec.ts +++ b/test/backend/unit/model/threading/MetaDataLoader.spec.ts @@ -11,6 +11,18 @@ import {DatabaseType} from '../../../../../src/common/config/private/PrivateConf declare const before: any; +function getFileModificationTime(filename: string): Promise { + return new Promise((resolve, reject) => { + fs.stat(filename, (err, stats) => { + if (err) { + reject(err); + } else { + resolve(stats.mtime); + } + }); + }); +} + describe('MetadataLoader', () => { // loading default settings (this might have been changed by other tests) @@ -114,6 +126,16 @@ describe('MetadataLoader', () => { const expected = require(path.join(__dirname, '/../../../assets/timestamps/big_ben_no_tsoffset_but_gps_utc.json')); expect(Utils.clone(data)).to.be.deep.equal(expected); }); + it('should load jpg with timestamps and gps (UTC) and calculate offset +1, but GPS is off by 1 min', async () => { + const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.jpg')); + const expected = require(path.join(__dirname, '/../../../assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min.json')); + expect(Utils.clone(data)).to.be.deep.equal(expected); + }); + it('should load jpg with timestamps and gps (UTC) and calculate offset +1, but GPS is off by 1 min - no XMP GPS', async () => { + const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.jpg')); + const expected = require(path.join(__dirname, '/../../../assets/timestamps/big_ben_no_tsoffset_but_gps_utc_off_by_1min_no_xmpgps.json')); + expect(Utils.clone(data)).to.be.deep.equal(expected); + }); it('should load jpg with timestamps but no offset and no GPS to calculate it from', async () => { const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/timestamps/big_ben_only_time.jpg')); const expected = require(path.join(__dirname, '/../../../assets/timestamps/big_ben_only_time.json')); @@ -213,6 +235,16 @@ describe('MetadataLoader', () => { it(item, async () => { const data = await MetadataLoader.loadPhotoMetadata(fullFilePath); const expected = require(fullFilePath.split('.').slice(0, -1).join('.') + '.json'); + + if (expected.creationDate == "fileModificationTime") { + await getFileModificationTime(fullFilePath).then((modificationTime: any) => { + if (modificationTime) { + expected.creationDate = new Date(modificationTime).getTime(); + } else { + expected.creationDate = 0; + } + }) + } if (expected.skip) { expected.skip.forEach((s: string) => { delete (data as any)[s];