Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
bpatrik committed Apr 15, 2024
2 parents f283a6e + c65868e commit 4d6795c
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 11 deletions.
8 changes: 4 additions & 4 deletions src/backend/model/fileaccess/MetadataLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 13 additions & 4 deletions src/common/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion test/backend/assets/date_issue.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"make": "HUAWEI",
"model": "HUAWEI G6-L11"
},
"creationDate": 1460826466000,
"creationDate": 1460819266000,
"creationDateOffset": "+02:00",
"fileSize": 1980,
"size": {
"height": 1,
Expand Down
2 changes: 1 addition & 1 deletion test/backend/assets/edge_case_exif_data/date_error.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"make": "NIKON",
"model": "E880"
},
"creationDate": 0,
"creationDate": "fileModificationTime",
"fileSize": 72850,
"size": {
"height": 768,
Expand Down
3 changes: 2 additions & 1 deletion test/backend/assets/orientation/broken_orientation_exif.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"width": 3024,
"height": 4032
},
"creationDate": 1518964712000,
"creationDate": 1518982712000,
"creationDateOffset": "-05:00",
"fileSize": 256001,
"cameraData": {
"model": "Pixel 2",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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"
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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"
]
}
32 changes: 32 additions & 0 deletions test/backend/unit/model/threading/MetaDataLoader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import {DatabaseType} from '../../../../../src/common/config/private/PrivateConf

declare const before: any;

function getFileModificationTime(filename: string): Promise<Date | null> {
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)

Expand Down Expand Up @@ -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'));
Expand Down Expand Up @@ -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];
Expand Down

0 comments on commit 4d6795c

Please sign in to comment.