From 676a3b054b104f9d554235e61d40bda294b3c64f Mon Sep 17 00:00:00 2001 From: Graham Alderson Date: Thu, 30 Nov 2023 13:13:33 +1200 Subject: [PATCH 1/2] Refactor sidecar loading into async-await Refactoring to async/await to solve race condition, also resolves error sometimes occuring with undefined values and resolves overwrite of xmp values. xmp for image file has tag removed to validate additive tagging from both file xmp and sidecar xmp data. --- demo/images/IMG_5910.jpg.xmp | 1 - .../model/fileaccess/MetadataLoader.ts | 94 +++++++++++-------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/demo/images/IMG_5910.jpg.xmp b/demo/images/IMG_5910.jpg.xmp index 0043a4b1..0b139bd7 100644 --- a/demo/images/IMG_5910.jpg.xmp +++ b/demo/images/IMG_5910.jpg.xmp @@ -190,7 +190,6 @@ Alvin the Squirrel - Berkley USA test diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index 4da23716..76ffb073 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -23,7 +23,7 @@ export class MetadataLoader { @ExtensionDecorator(e=>e.gallery.MetadataLoader.loadVideoMetadata) public static loadVideoMetadata(fullPath: string): Promise { - return new Promise((resolve) => { + return new Promise(async (resolve) => { const metadata: VideoMetadata = { size: { width: 1, @@ -38,21 +38,24 @@ export class MetadataLoader { try { // search for sidecar and merge metadata - const fullPathWithoutExt = path.parse(fullPath).name; + const fullPathWithoutExt = await path.parse(fullPath).name; const sidecarPaths = [ fullPath + '.xmp', fullPath + '.XMP', fullPathWithoutExt + '.xmp', fullPathWithoutExt + '.XMP', ]; - for (const sidecarPath of sidecarPaths) { if (fs.existsSync(sidecarPath)) { - const sidecarData = exifr.sidecar(sidecarPath); - sidecarData.then((response) => { - metadata.keywords = [(response as any).dc.subject].flat(); - metadata.rating = (response as any).xmp.Rating; - }); + const sidecarData = await exifr.sidecar(sidecarPath); + if (sidecarData !== undefined) { + if ((sidecarData as any).dc.subject !== undefined) { + metadata.keywords = (sidecarData as any).dc.subject.flat(); + } + if ((sidecarData as any).xmp.Rating !== undefined) { + metadata.rating = (sidecarData as any).xmp.Rating; + } + } } } } catch (err) { @@ -159,18 +162,46 @@ export class MetadataLoader { @ExtensionDecorator(e=>e.gallery.MetadataLoader.loadPhotoMetadata) public static loadPhotoMetadata(fullPath: string): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { + const metadata: PhotoMetadata = { + size: {width: 1, height: 1}, + creationDate: 0, + fileSize: 0, + }; + + try { + // search for sidecar and merge metadata + const fullPathWithoutExt = await path.parse(fullPath).name; + const sidecarPaths = [ + fullPath + '.xmp', + fullPath + '.XMP', + fullPathWithoutExt + '.xmp', + fullPathWithoutExt + '.XMP', + ]; + for (const sidecarPath of sidecarPaths) { + if (fs.existsSync(sidecarPath)) { + const sidecarData = await exifr.sidecar(sidecarPath); + if (sidecarData !== undefined) { + if ((sidecarData as any).dc.subject !== undefined) { + metadata.keywords = (sidecarData as any).dc.subject.flat(); + } + if ((sidecarData as any).xmp.Rating !== undefined) { + metadata.rating = (sidecarData as any).xmp.Rating; + } + } + } + } + } catch (err) { + // ignoring errors + } + try { const fd = fs.openSync(fullPath, 'r'); const data = Buffer.allocUnsafe(Config.Media.photoMetadataSize); fs.read(fd, data, 0, Config.Media.photoMetadataSize, 0, (err) => { fs.closeSync(fd); - const metadata: PhotoMetadata = { - size: {width: 1, height: 1}, - creationDate: 0, - fileSize: 0, - }; + if (err) { Logger.error(LOG_TAG, 'Error during reading photo: ' + fullPath); console.error(err); @@ -185,29 +216,6 @@ export class MetadataLoader { // ignoring errors } - try { - // search for sidecar and merge metadata - const fullPathWithoutExt = path.parse(fullPath).name; - const sidecarPaths = [ - fullPath + '.xmp', - fullPath + '.XMP', - fullPathWithoutExt + '.xmp', - fullPathWithoutExt + '.XMP', - ]; - - for (const sidecarPath of sidecarPaths) { - if (fs.existsSync(sidecarPath)) { - const sidecarData = exifr.sidecar(sidecarPath); - sidecarData.then((response) => { - metadata.keywords = [(response as any).dc.subject].flat(); - metadata.rating = (response as any).xmp.Rating; - }); - } - } - } catch (err) { - // ignoring errors - } - try { const exif = ExifParserFactory.create(data).parse(); if ( @@ -343,7 +351,9 @@ export class MetadataLoader { metadata.caption = iptcData.caption.replace(/\0/g, '').trim(); } if (Array.isArray(iptcData.keywords)) { - metadata.keywords = iptcData.keywords; + if (metadata.keywords === undefined) { + metadata.keywords = iptcData.keywords; + } } if (iptcData.date_time) { @@ -363,9 +373,11 @@ export class MetadataLoader { // and keep the minimum amount only const exif: ExifReader.Tags & ExifReader.XmpTags & ExifReader.IccTags = ExifReader.load(data); if (exif.Rating) { - metadata.rating = parseInt(exif.Rating.value as string, 10) as 0 | 1 | 2 | 3 | 4 | 5; - if (metadata.rating < 0) { - metadata.rating = 0; + if (metadata.rating === undefined) { + metadata.rating = parseInt(exif.Rating.value as string, 10) as 0 | 1 | 2 | 3 | 4 | 5; + if (metadata.rating < 0) { + metadata.rating = 0; + } } } if ( From 3ea0dc914743859e567b54b93d8690fd892ad6e9 Mon Sep 17 00:00:00 2001 From: Graham Alderson Date: Mon, 4 Dec 2023 12:13:38 +1200 Subject: [PATCH 2/2] Sidecar load refactor Resolves error when sidecar metadata not complete, migrates from any to SideCar type, moves sidecar loading to after file loading, finishes async/await transition. --- .../model/fileaccess/MetadataLoader.ts | 117 +++++++++++------- src/common/entities/MediaDTO.ts | 13 ++ 2 files changed, 86 insertions(+), 44 deletions(-) diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index 8a6187fa..80df3587 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -1,5 +1,6 @@ import {VideoMetadata} from '../../../common/entities/VideoDTO'; import {FaceRegion, PhotoMetadata} from '../../../common/entities/PhotoDTO'; +import {SideCar} from '../../../common/entities/MediaDTO'; import {Config} from '../../../common/config/private/Config'; import {Logger} from '../../Logger'; import * as fs from 'fs'; @@ -37,28 +38,6 @@ export class MetadataLoader { fps: 0, }; - try { - // search for sidecar and merge metadata - const fullPathWithoutExt = path.parse(fullPath).name; - const sidecarPaths = [ - fullPath + '.xmp', - fullPath + '.XMP', - fullPathWithoutExt + '.xmp', - fullPathWithoutExt + '.XMP', - ]; - - for (const sidecarPath of sidecarPaths) { - if (fs.existsSync(sidecarPath)) { - const sidecarData = await exifr.sidecar(sidecarPath); - metadata.keywords = [(sidecarData as any).dc.subject].flat(); - metadata.rating = (sidecarData as any).xmp.Rating; - } - } - } catch (err) { - Logger.silly(LOG_TAG, 'Error loading sidecar metadata for : ' + fullPath); - Logger.silly(err); - } - try { const stat = fs.statSync(fullPath); metadata.fileSize = stat.size; @@ -147,6 +126,41 @@ export class MetadataLoader { Logger.silly(err); } metadata.creationDate = metadata.creationDate || 0; + + try { + // search for sidecar and merge metadata + const fullPathWithoutExt = path.parse(fullPath).name; + const sidecarPaths = [ + fullPath + '.xmp', + fullPath + '.XMP', + fullPathWithoutExt + '.xmp', + fullPathWithoutExt + '.XMP', + ]; + + for (const sidecarPath of sidecarPaths) { + if (fs.existsSync(sidecarPath)) { + const sidecarData = await exifr.sidecar(sidecarPath); + if (sidecarData !== undefined) { + if ((sidecarData as SideCar).dc.subject !== undefined) { + if (metadata.keywords === undefined) { + metadata.keywords = []; + } + for (const kw of (sidecarData as SideCar).dc.subject) { + if (metadata.keywords.indexOf(kw) === -1) { + metadata.keywords.push(kw); + } + } } + if ((sidecarData as SideCar).xmp.Rating !== undefined) { + metadata.rating = (sidecarData as SideCar).xmp.Rating; + } + } + } + } + } catch (err) { + Logger.silly(LOG_TAG, 'Error loading sidecar metadata for : ' + fullPath); + Logger.silly(err); + } + } catch (err) { Logger.silly(LOG_TAG, 'Error loading metadata for : ' + fullPath); Logger.silly(err); @@ -190,27 +204,6 @@ export class MetadataLoader { // ignoring errors } - try { - // search for sidecar and merge metadata - const fullPathWithoutExt = path.parse(fullPath).name; - const sidecarPaths = [ - fullPath + '.xmp', - fullPath + '.XMP', - fullPathWithoutExt + '.xmp', - fullPathWithoutExt + '.XMP', - ]; - - for (const sidecarPath of sidecarPaths) { - if (fs.existsSync(sidecarPath)) { - const sidecarData = await exifr.sidecar(sidecarPath); - metadata.keywords = [(sidecarData as any).dc.subject].flat(); - metadata.rating = (sidecarData as any).xmp.Rating; - } - } - } catch (err) { - // ignoring errors - } - try { const exif = ExifParserFactory.create(data).parse(); if ( @@ -313,7 +306,7 @@ export class MetadataLoader { } } catch (err) { Logger.debug(LOG_TAG, 'Error parsing exif', fullPath, err); - try { + try { const info = imageSize(fullPath); metadata.size = {width: info.width, height: info.height}; } catch (e) { @@ -508,6 +501,42 @@ export class MetadataLoader { // ignoring errors } + try { + // search for sidecar and merge metadata + const fullPathWithoutExt = path.parse(fullPath).name; + const sidecarPaths = [ + fullPath + '.xmp', + fullPath + '.XMP', + fullPathWithoutExt + '.xmp', + fullPathWithoutExt + '.XMP', + ]; + + for (const sidecarPath of sidecarPaths) { + if (fs.existsSync(sidecarPath)) { + const sidecarData = await exifr.sidecar(sidecarPath); + + if (sidecarData !== undefined) { + if ((sidecarData as SideCar).dc.subject !== undefined) { + if (metadata.keywords === undefined) { + metadata.keywords = []; + } + for (const kw of (sidecarData as SideCar).dc.subject) { + if (metadata.keywords.indexOf(kw) === -1) { + metadata.keywords.push(kw); + } + } + } + if ((sidecarData as SideCar).xmp.Rating !== undefined) { + metadata.rating = (sidecarData as SideCar).xmp.Rating; + } + } + } + } + } catch (err) { + Logger.silly(LOG_TAG, 'Error loading sidecar metadata for : ' + fullPath); + Logger.silly(err); + } + } catch (err) { Logger.error(LOG_TAG, 'Error during reading photo: ' + fullPath); console.error(err); diff --git a/src/common/entities/MediaDTO.ts b/src/common/entities/MediaDTO.ts index b56136ca..b53fc46e 100644 --- a/src/common/entities/MediaDTO.ts +++ b/src/common/entities/MediaDTO.ts @@ -26,6 +26,19 @@ export interface MediaDimension { height: number; } +export interface SideCar { + dc?: SideCarDc; + xmp?: SideCarXmp; +} + +export interface SideCarDc { + subject?: string[]; +} + +export interface SideCarXmp { + Rating?: RatingTypes; +} + export const MediaDTOUtils = { hasPositionData: (media: MediaDTO): boolean => { return (