diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts
index 5a61af21..8feb8427 100644
--- a/src/backend/model/fileaccess/MetadataLoader.ts
+++ b/src/backend/model/fileaccess/MetadataLoader.ts
@@ -139,13 +139,13 @@ export class MetadataLoader {
fullPathWithoutExt + '.xmp',
fullPathWithoutExt + '.XMP',
];
-
for (const sidecarPath of sidecarPaths) {
if (fs.existsSync(sidecarPath)) {
const sidecarData: any = await exifr.sidecar(sidecarPath);
if (sidecarData !== undefined) {
MetadataLoader.mapMetadata(metadata, sidecarData);
}
+ break; //Break the loop as soon as a sidecar is found, no need to check the extra sidecar paths
}
}
} catch (err) {
@@ -316,6 +316,8 @@ export class MetadataLoader {
const orientation = MetadataLoader.getOrientation(exif);
MetadataLoader.mapImageDimensions(metadata, exif, orientation);
MetadataLoader.mapKeywords(metadata, exif);
+ MetadataLoader.mapTitle(metadata, exif);
+ MetadataLoader.mapCaption(metadata, exif);
MetadataLoader.mapTimestampAndOffset(metadata, exif);
MetadataLoader.mapCameraData(metadata, exif);
MetadataLoader.mapGPS(metadata, exif);
@@ -370,6 +372,15 @@ export class MetadataLoader {
}
}
+ private static mapTitle(metadata: PhotoMetadata, exif: any) {
+ metadata.title = exif.dc?.title?.value || metadata.title || exif.photoshop?.Headline || exif.acdsee?.caption; //acdsee caption holds the title when data is saved by digikam. Used as last resort if iptc and dc do not contain the data
+ }
+
+ private static mapCaption(metadata: PhotoMetadata, exif: any) {
+ metadata.caption = exif.dc?.description?.value || metadata.caption || exif.ifd0?.ImageDescription || exif.exif?.UserComment?.value || exif.Iptc4xmpCore?.ExtDescrAccessibility?.value ||exif.acdsee?.notes;
+ }
+
+
private static mapTimestampAndOffset(metadata: PhotoMetadata, exif: any) {
metadata.creationDate = Utils.timestampToMS(exif?.photoshop?.DateCreated, null) ||
Utils.timestampToMS(exif?.xmp?.CreateDate, null) ||
diff --git a/src/common/Utils.ts b/src/common/Utils.ts
index db21bb56..a9a7a862 100644
--- a/src/common/Utils.ts
+++ b/src/common/Utils.ts
@@ -132,7 +132,7 @@ export class Utils {
//replace : with - in the yyyy-mm-dd part of the timestamp.
let formattedTimestamp = timestamp.substring(0,9).replaceAll(':', '-') + timestamp.substring(9,timestamp.length);
if (formattedTimestamp.indexOf("Z") > 0) { //replace Z (and what comes after the Z) with offset
- formattedTimestamp.substring(0, formattedTimestamp.indexOf("Z")) + (offset ? offset : '+00:00');
+ formattedTimestamp = formattedTimestamp.substring(0, formattedTimestamp.indexOf("Z")) + (offset ? offset : '+00:00');
} else if (formattedTimestamp.indexOf("+") > 0) { //don't do anything
} else { //add offset
formattedTimestamp = formattedTimestamp + (offset ? offset : '+00:00');
diff --git a/test/backend/assets/description.json b/test/backend/assets/description.json
new file mode 100644
index 00000000..677f3940
--- /dev/null
+++ b/test/backend/assets/description.json
@@ -0,0 +1,35 @@
+{
+ "size": {
+ "width": 3144,
+ "height": 2288
+ },
+ "creationDate": -2195790002000,
+ "fileSize": 8909621,
+ "keywords": [],
+ "faces": [
+ {
+ "name": "Hulda Stein",
+ "box": {
+ "width": 645,
+ "height": 645,
+ "left": 1919,
+ "top": 547
+ }
+ },
+ {
+ "name": "John Amschler",
+ "box": {
+ "width": 709,
+ "height": 709,
+ "left": 544,
+ "top": 394
+ }
+ }
+ ],
+ "cameraData": {
+ "make": "NIKON CORPORATION",
+ "model": "NIKON D200"
+ },
+ "caption": "Hulda and John's Wedding",
+ "title": "The Wedding"
+}
\ No newline at end of file
diff --git a/test/backend/assets/description.png b/test/backend/assets/description.png
new file mode 100644
index 00000000..6f906d67
Binary files /dev/null and b/test/backend/assets/description.png differ
diff --git a/test/backend/assets/digikam.jpg b/test/backend/assets/digikam.jpg
new file mode 100644
index 00000000..534135e4
Binary files /dev/null and b/test/backend/assets/digikam.jpg differ
diff --git a/test/backend/assets/digikam.json b/test/backend/assets/digikam.json
new file mode 100644
index 00000000..87a627a1
--- /dev/null
+++ b/test/backend/assets/digikam.json
@@ -0,0 +1,43 @@
+{
+ "cameraData": {
+ "ISO": 200,
+ "exposure": 0.008,
+ "fStop": 2.8,
+ "focalLength": 9.4,
+ "make": "FUJIFILM",
+ "model": "FinePix F601 ZOOM"
+ },
+ "creationDate": 1126423382000,
+ "creationDateOffset": "+09:00",
+ "fileSize": 14134,
+ "size": {
+ "height": 5,
+ "width": 7
+ },
+ "title": "Digikam Title field",
+ "caption": "Digikam Caption field",
+ "faces": [
+ {
+ "box": {
+ "height": 2,
+ "left": 3,
+ "top": 2,
+ "width": 2
+ },
+ "name": "Æske Øllegård"
+ }
+ ],
+ "keywords": [
+ "æÆøØåÅéÉüÜäÄöÖïÏñÑ"
+ ],
+ "positionData": {
+ "GPSData": {
+ "latitude": 35.9524,
+ "longitude": 139.863355
+ },
+ "city": "Shimizu",
+ "country": "Japan",
+ "state": "Chiba Ken"
+ },
+ "rating": 4
+}
\ No newline at end of file
diff --git a/test/backend/assets/sidecar/flatxmp.jpg b/test/backend/assets/sidecar/flatxmp.jpg
new file mode 100644
index 00000000..c119808d
Binary files /dev/null and b/test/backend/assets/sidecar/flatxmp.jpg differ
diff --git a/test/backend/assets/sidecar/flatxmp.json b/test/backend/assets/sidecar/flatxmp.json
new file mode 100644
index 00000000..008108db
--- /dev/null
+++ b/test/backend/assets/sidecar/flatxmp.json
@@ -0,0 +1,33 @@
+{
+ "size": {
+ "width": 10,
+ "height": 5
+ },
+ "caption": "Description of image",
+ "creationDate": 328817998000,
+ "faces": [
+ {
+ "box": {
+ "height": 1,
+ "left": 6,
+ "top": 2,
+ "width": 2
+ },
+ "name": "Person1"
+ },
+ {
+ "box": {
+ "height": 2,
+ "left": 2,
+ "top": 1,
+ "width": 2
+ },
+ "name": "Person2"
+ }
+ ],
+ "fileSize": 1430,
+ "keywords": [
+ "Thing3"
+ ],
+ "title": "The title"
+}
\ No newline at end of file
diff --git a/test/backend/assets/sidecar/flatxmp.xmp b/test/backend/assets/sidecar/flatxmp.xmp
new file mode 100644
index 00000000..74506a10
--- /dev/null
+++ b/test/backend/assets/sidecar/flatxmp.xmp
@@ -0,0 +1,329 @@
+
+
+
+
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Digitized By Testmaster
+
+
+
+
+ The title
+
+
+
+
+ © Testmaster 2024
+
+
+
+
+ Person1
+ Person2
+ Thing3
+
+
+
+
+ Description of image
+
+
+
+
+ All Rights Reserved
+
+
+
+
+ User comment
+
+
+
+
+ 400
+
+
+
+
+
+
+ alt text
+
+
+
+
+ extended description
+
+
+
+
+ Person1
+ Person2
+
+
+
+
+ Person1
+ Person2
+
+
+
+
+ Person1
+ Person2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/backend/assets/sidecar/headline.jpg b/test/backend/assets/sidecar/headline.jpg
new file mode 100644
index 00000000..c119808d
Binary files /dev/null and b/test/backend/assets/sidecar/headline.jpg differ
diff --git a/test/backend/assets/sidecar/headline.json b/test/backend/assets/sidecar/headline.json
new file mode 100644
index 00000000..7d6cb058
--- /dev/null
+++ b/test/backend/assets/sidecar/headline.json
@@ -0,0 +1,12 @@
+{
+ "size": {
+ "width": 10,
+ "height": 5
+ },
+ "creationDate": 1185683698000,
+ "creationDateOffset": "+13:45",
+ "fileSize": 1430,
+ "keywords": ["Person 1", "Person 2"],
+ "caption": "XMP description",
+ "title": "Photoshop Headline"
+}
\ No newline at end of file
diff --git a/test/backend/assets/sidecar/headline.xmp b/test/backend/assets/sidecar/headline.xmp
new file mode 100644
index 00000000..db1f80cf
--- /dev/null
+++ b/test/backend/assets/sidecar/headline.xmp
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+ alt text
+
+
+
+ Testville
+ Testica
+ Testroad 2
+ 7357
+ Testas
+ test@example.com
+
+
+
+ extended description
+
+
+
+
+
+ 4244
+ 28.0-80.0 mm f/2.8
+ 28/1 80/1 14/5 14/5
+
+
+
+ True
+ 0
+ 0
+ +50
+ ACR 3.3
+ 0
+ 0
+ 25
+ +58
+ False
+ -0.35
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ family-220.png
+ 0
+ 0
+ -24
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 32
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4150
+ +32
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+ Medium Contrast
+ 3.7
+ 0
+ 0
+ Custom
+
+
+
+
+
+ XMP Creator
+
+
+
+
+ XMP description
+
+
+ image/png
+
+
+ XMP Creator
+
+
+
+
+ Person 1
+ Person 2
+
+
+
+
+
+ 24361/8200
+ 1
+ 0
+ 0
+ 2007-07-29T18:19:58+13:45
+ 1/1
+ 0221
+ 1/1
+ 0
+ 2
+ 1/8
+ 14/5
+ 3
+
+ False
+ False
+ 0
+ False
+ 0
+
+ 28/1
+ 42
+ 1
+
+
+ 400
+
+
+ 0
+ 24361/8200
+ 3
+ 0
+ 0
+ 1
+ 2
+ 0
+ 3/1
+ 0
+ 0
+
+
+
+
+ Archivalist
+ 1980-06-02T18:19:58
+ Photoshop Headline
+
+
+
+ CamMake
+ CamModel
+ 1
+
+
+
+ 2007-07-29T18:19:58+13:45
+ Adobe Photoshop Lightroom 13.2 Classic (Windows)
+ 2014-03-23T01:17:49-05:00
+ 2014-03-22T19:09:41-05:00
+
+
+
+
+
\ No newline at end of file
diff --git a/test/backend/assets/sidecar/testimagedesc1.jpg b/test/backend/assets/sidecar/testimagedesc1.jpg
new file mode 100644
index 00000000..f0fa299a
Binary files /dev/null and b/test/backend/assets/sidecar/testimagedesc1.jpg differ
diff --git a/test/backend/assets/sidecar/testimagedesc1.json b/test/backend/assets/sidecar/testimagedesc1.json
new file mode 100644
index 00000000..bc0aac3b
--- /dev/null
+++ b/test/backend/assets/sidecar/testimagedesc1.json
@@ -0,0 +1,54 @@
+{
+ "cameraData": {
+ "ISO": 3200,
+ "exposure": 0.00125,
+ "fStop": 5.6,
+ "focalLength": 85,
+ "lens": "EF-S15-85mm f/3.5-5.6 IS USM",
+ "make": "Canon",
+ "model": "óüöúőűáé ÓÜÖÚŐŰÁÉ"
+ },
+ "caption": "Test caption",
+ "creationDate": 1434018566690,
+ "faces": [
+ {
+ "box": {
+ "height": 2,
+ "width": 2,
+ "left": 7,
+ "top": 3
+ },
+ "name": "squirrel"
+ },
+ {
+ "box": {
+ "height": 3,
+ "width": 2,
+ "left": 4,
+ "top": 4
+ },
+ "name": "special_chars űáéúőóüío?._:"
+ }
+ ],
+ "fileSize": 39424,
+ "keywords": [
+ "Berkley",
+ "USA",
+ "űáéúőóüö ŰÁÉÚŐÓÜÖ"
+ ],
+ "positionData": {
+ "GPSData": {
+ "latitude": 37.871093,
+ "longitude": -122.25678
+ },
+ "city": "test city őúéáűóöí-.,)(=",
+ "country": "test country őúéáűóöí-.,)(=/%!+\"'",
+ "state": "test state őúéáűóöí-.,)("
+ },
+ "rating": 3,
+ "size": {
+ "height": 10,
+ "width": 14
+ },
+ "title": "Test caption"
+}
diff --git a/test/backend/assets/sidecar/testimagedesc1.xmp b/test/backend/assets/sidecar/testimagedesc1.xmp
new file mode 100644
index 00000000..f27c3386
--- /dev/null
+++ b/test/backend/assets/sidecar/testimagedesc1.xmp
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+ Patrik
+
+
+
+
+ Test caption
+
+
+
+
+
+ Copyright test
+
+
+
+
+ Berkley
+ USA
+ special_chars űáéúőóüío?._:
+ squirrel
+ űáéúőóüö ŰÁÉÚŐÓÜÖ
+
+
+
+
+ Test caption
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Berkley
+ USA
+ űáéúőóüö ŰÁÉÚŐÓÜÖ
+
+
+
+
+ Berkley
+ USA
+ űáéúőóüö ŰÁÉÚŐÓÜÖ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Berkley
+ USA
+ special_chars űáéúőóüío?._:
+ squirrel
+ űáéúőóüö ŰÁÉÚŐÓÜÖ
+
+
+
+
+
\ No newline at end of file
diff --git a/test/backend/assets/sidecar/testimagedesc2.jpg b/test/backend/assets/sidecar/testimagedesc2.jpg
new file mode 100644
index 00000000..f0fa299a
Binary files /dev/null and b/test/backend/assets/sidecar/testimagedesc2.jpg differ
diff --git a/test/backend/assets/sidecar/testimagedesc2.json b/test/backend/assets/sidecar/testimagedesc2.json
new file mode 100644
index 00000000..803e2afd
--- /dev/null
+++ b/test/backend/assets/sidecar/testimagedesc2.json
@@ -0,0 +1,56 @@
+{
+ "cameraData": {
+ "ISO": 3200,
+ "exposure": 0.00125,
+ "fStop": 5.6,
+ "focalLength": 85,
+ "lens": "EF-S15-85mm f/3.5-5.6 IS USM",
+ "make": "Canon",
+ "model": "óüöúőűáé ÓÜÖÚŐŰÁÉ"
+ },
+ "caption": "Test caption",
+ "creationDate": 1434018566000,
+ "faces": [
+ {
+ "box": {
+ "height": 2,
+ "width": 2,
+ "left": 7,
+ "top": 3
+ },
+ "name": "squirrel"
+ },
+ {
+ "box": {
+ "height": 3,
+ "width": 2,
+ "left": 4,
+ "top": 4
+ },
+ "name": "special_chars űáéúőóüío?._:"
+ }
+ ],
+ "fileSize": 39424,
+ "keywords": [
+ "Berkley",
+ "USA",
+ "űáéúőóüö ŰÁÉÚŐÓÜÖ",
+ "special_chars űáéúőóüío?._:",
+ "squirrel"
+ ],
+ "positionData": {
+ "GPSData": {
+ "latitude": 37.871093,
+ "longitude": -122.25678
+ },
+ "city": "test city őúéáűóöí-.,)(=",
+ "country": "test country őúéáűóöí-.,)(=/%!+\"'",
+ "state": "test state őúéáűóöí-.,)("
+ },
+ "rating": 3,
+ "size": {
+ "height": 10,
+ "width": 14
+ },
+ "title": "Test caption"
+}
diff --git a/test/backend/assets/sidecar/testimagedesc2.xmp b/test/backend/assets/sidecar/testimagedesc2.xmp
new file mode 100644
index 00000000..ca499497
--- /dev/null
+++ b/test/backend/assets/sidecar/testimagedesc2.xmp
@@ -0,0 +1,139 @@
+
+
+
+
+
+ US
+ Sublocation test
+
+
+
+
+
+ Patrik
+
+
+
+
+ Test caption
+
+
+
+
+ Copyright test
+
+
+
+
+ Berkley
+ USA
+ special_chars űáéúőóüío?._:
+ squirrel
+ űáéúőóüö ŰÁÉÚŐÓÜÖ
+
+
+
+
+ Test caption
+
+
+
+
+
+ 40761/8200
+ 1
+ 0
+ 2015-06-11T10:29:26
+ 0230
+ 0/1
+ 0
+ 6
+ 1/800
+ 28/5
+
+ False
+ False
+ 2
+ False
+ 0
+
+ 85/1
+ 2
+ 1036800/181
+ 691200/119
+ 90/1
+ 37,52.2656N
+ 122,15.4068W
+ 2.2.0.0
+
+
+ 3200
+
+
+ 18325/3649
+ 5
+ 0
+ 19307/2002
+ 0
+
+
+
+ 123063022888
+ EF-S15-85mm f/3.5-5.6 IS USM
+ 0000129324
+
+
+ 15/1
+ 85/1
+ 0/0
+ 0/0
+
+
+ 3200
+ 2
+
+
+
+ test city őúéáűóöí-.,)(=
+ test country őúéáűóöí-.,)(=/%!+"'
+ 2015-06-11T10:29:26
+ test state őúéáűóöí-.,)(
+
+
+
+ Patrik
+ 6
+
+
+ Copyright test
+
+
+
+
+ Test caption
+
+
+ Canon
+ óüöúőűáé ÓÜÖÚŐŰÁÉ
+ 1
+ 3
+ Adobe Photoshop Lightroom 6.1 (Windows)
+ 94/1
+ 94/1
+
+
+
+ 2015-06-11T10:29:26
+ Adobe Photoshop Lightroom 6.1 (Windows)
+ 2015-07-24T22:45:50
+
+
+
+
\ No newline at end of file
diff --git a/test/backend/unit/model/threading/DiskManagerWorker.spec.ts b/test/backend/unit/model/threading/DiskManagerWorker.spec.ts
index a04ee32f..2b163d58 100644
--- a/test/backend/unit/model/threading/DiskManagerWorker.spec.ts
+++ b/test/backend/unit/model/threading/DiskManagerWorker.spec.ts
@@ -24,7 +24,7 @@ describe('DiskMangerWorker', () => {
ProjectPath.ImageFolder = path.join(__dirname, '/../../../assets');
const dir = await DiskManager.scanDirectory('/');
// should match the number of media (photo/video) files in the assets folder
- expect(dir.media.length).to.be.equals(15);
+ expect(dir.media.length).to.be.equals(17);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const expected = require(path.join(__dirname, '/../../../assets/test image öüóőúéáű-.,.json'));
const i = dir.media.findIndex(m => m.name === 'test image öüóőúéáű-.,.jpg');
diff --git a/test/backend/unit/model/threading/MetaDataLoader.spec.ts b/test/backend/unit/model/threading/MetaDataLoader.spec.ts
index 9b62eb58..4e21c25d 100644
--- a/test/backend/unit/model/threading/MetaDataLoader.spec.ts
+++ b/test/backend/unit/model/threading/MetaDataLoader.spec.ts
@@ -35,6 +35,12 @@ describe('MetadataLoader', () => {
expect(Utils.clone(data)).to.be.deep.equal(expected);
});
+ it('should load png with description', async () => {
+ const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/description.png'));
+ const expected = require(path.join(__dirname, '/../../../assets/description.json'));
+ expect(Utils.clone(data)).to.be.deep.equal(expected);
+ });
+
it('should load jpg', async () => {
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/test image öüóőúéáű-.,.jpg'));
const expected = require(path.join(__dirname, '/../../../assets/test image öüóőúéáű-.,.json'));
@@ -179,6 +185,11 @@ describe('MetadataLoader', () => {
const expected = require(path.join(__dirname, '/../../../assets/wild-1-small.json'));
expect(Utils.clone(data)).to.be.deep.equal(expected);
});
+ it('should load image with metadata saved by digikam', async () => {
+ const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/digikam.jpg'));
+ const expected = require(path.join(__dirname, '/../../../assets/digikam.json'));
+ expect(Utils.clone(data)).to.be.deep.equal(expected);
+ });
describe('should load jpg with edge case exif data', () => {
const root = path.join(__dirname, '/../../../assets/edge_case_exif_data');