From b2be0a976307cd478555a879b974d8064c59af7d Mon Sep 17 00:00:00 2001 From: grasdk <115414609+grasdk@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:55:26 +0100 Subject: [PATCH 1/9] consolidate exif parsing libraries - rework of timestamps (#4) * exifr is used for most tags now * New timestamp handling, removed exif-parser, date supported for png * Removed offset from testhelper. It's optional * explanations * Feature/timestamps (#3) * preparing for further timestamp test * Added more test and fixed offset calculation bug * Revered old dimension test, added new timestamp tests, some bug fixes * Renamed png-test because faces overrule keywords --- package-lock.json | 1 - package.json | 1 - .../model/database/enitites/MediaEntity.ts | 4 + .../model/fileaccess/MetadataLoader.ts | 403 ++++++++++-------- src/common/entities/MediaDTO.ts | 1 + src/common/entities/PhotoDTO.ts | 1 + src/common/entities/VideoDTO.ts | 1 + test/backend/assets/Chars.json | 3 +- .../edge_case_exif_data/before_epoch.json | 3 +- .../edge_case_exif_data/date_error.json | 2 +- ...tes.json => png_with_faces_and_dates.json} | 3 +- ...dates.png => png_with_faces_and_dates.png} | Bin test/backend/assets/test_png.json | 3 +- test/backend/assets/timestamps/big_ben.jpg | Bin 0 -> 18532 bytes test/backend/assets/timestamps/big_ben.json | 25 ++ .../big_ben_no_tsoffset_but_gps_utc.jpg | Bin 0 -> 18663 bytes .../big_ben_no_tsoffset_but_gps_utc.json | 25 ++ .../assets/timestamps/big_ben_only_time.jpg | Bin 0 -> 17850 bytes .../assets/timestamps/big_ben_only_time.json | 20 + .../assets/timestamps/sydney_opera_house.jpg | Bin 0 -> 22755 bytes .../assets/timestamps/sydney_opera_house.json | 25 ++ ...ey_opera_house_no_tsoffset_but_gps_utc.jpg | Bin 0 -> 22653 bytes ...y_opera_house_no_tsoffset_but_gps_utc.json | 25 ++ test/backend/assets/two_ratings.json | 3 +- test/backend/assets/xmp/xmp_subject.json | 3 +- .../model/threading/MetaDataLoader.spec.ts | 32 +- 26 files changed, 408 insertions(+), 176 deletions(-) rename test/backend/assets/{png_with_keyword_and_dates.json => png_with_faces_and_dates.json} (85%) rename test/backend/assets/{png_with_keyword_and_dates.png => png_with_faces_and_dates.png} (100%) create mode 100644 test/backend/assets/timestamps/big_ben.jpg create mode 100644 test/backend/assets/timestamps/big_ben.json create mode 100644 test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc.jpg create mode 100644 test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc.json create mode 100644 test/backend/assets/timestamps/big_ben_only_time.jpg create mode 100644 test/backend/assets/timestamps/big_ben_only_time.json create mode 100644 test/backend/assets/timestamps/sydney_opera_house.jpg create mode 100644 test/backend/assets/timestamps/sydney_opera_house.json create mode 100644 test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg create mode 100644 test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.json diff --git a/package-lock.json b/package-lock.json index db09d1724..51b2286a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,6 @@ "nodemailer": "6.9.4", "reflect-metadata": "0.1.13", "sharp": "0.31.3", - "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", "typeconfig": "2.1.2", "typeorm": "0.3.12", diff --git a/package.json b/package.json index f3dac2e6b..7219c2c0d 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "nodemailer": "6.9.4", "reflect-metadata": "0.1.13", "sharp": "0.31.3", - "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", "typeconfig": "2.1.2", "typeorm": "0.3.12", diff --git a/src/backend/model/database/enitites/MediaEntity.ts b/src/backend/model/database/enitites/MediaEntity.ts index 52bbf6006..57c131327 100644 --- a/src/backend/model/database/enitites/MediaEntity.ts +++ b/src/backend/model/database/enitites/MediaEntity.ts @@ -105,6 +105,10 @@ export class MediaMetadataEntity implements MediaMetadata { }) @Index() creationDate: number; + + @Column('text') + creationDateOffset?: string; + @Column('int', {unsigned: true}) fileSize: number; diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index 8ded9e1d7..3e10e1fae 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -12,7 +12,6 @@ import { FfprobeData } from 'fluent-ffmpeg'; import { FileHandle } from 'fs/promises'; import * as util from 'node:util'; import * as path from 'path'; -import { ExifParserFactory, OrientationTypes } from 'ts-exif-parser'; import { IptcParser } from 'ts-node-iptc'; import { Utils } from '../../../common/Utils'; import { FFmpegFactory } from '../FFmpegFactory'; @@ -135,7 +134,7 @@ export class MetadataLoader { fullPathWithoutExt + '.xmp', fullPathWithoutExt + '.XMP', ]; - + for (const sidecarPath of sidecarPaths) { if (fs.existsSync(sidecarPath)) { const sidecarData = await exifr.sidecar(sidecarPath); @@ -148,7 +147,8 @@ export class MetadataLoader { if (metadata.keywords.indexOf(kw) === -1) { metadata.keywords.push(kw); } - } } + } + } if ((sidecarData as SideCar).xmp.Rating !== undefined) { metadata.rating = (sidecarData as SideCar).xmp.Rating; } @@ -168,7 +168,7 @@ export class MetadataLoader { } private static readonly EMPTY_METADATA: PhotoMetadata = { - size: {width: 1, height: 1}, + size: { width: 0, height: 0 }, creationDate: 0, fileSize: 0, }; @@ -177,10 +177,55 @@ export class MetadataLoader { public static async loadPhotoMetadata(fullPath: string): Promise { let fileHandle: FileHandle; const metadata: PhotoMetadata = { - size: {width: 1, height: 1}, + size: { width: 0, height: 0 }, creationDate: 0, fileSize: 0, }; + const exifrOptions = { + tiff: true, + xmp: true, + icc: false, + jfif: false, //not needed and not supported for png + ihdr: true, + iptc: false, //exifr reads UTF8-encoded data wrongly, using IptcParser instead + exif: true, + gps: true, + reviveValues: false, //don't convert timestamps + translateValues: false, //don't translate orientation from numbers to strings etc. + mergeOutput: false //don't merge output, because things like Microsoft Rating (percent) and xmp.rating will be merged + }; + + //function to convert timestamp into milliseconds taking offset into account + const timestampToMS = (timestamp: string, offset: string) => { + "replace first two : with - in timestamp string and add offset if exists (else +00:00 - UTC), parse this into MS" + return Date.parse(timestamp.replace(':', '-').replace(':', '-') + (offset ? offset : '+00:00')); + } + + const getTimeOffsetByGPSStamp = (timestamp: string, gpsTimeStamp: string, gps: any) => { + let UTCTimestamp = gpsTimeStamp; + if (!UTCTimestamp && + gps && + gps.GPSDateStamp && + gps.GPSTimeStamp) { + //GPS timestamp is always UTC (+00:00) + UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + gps.GPSTimeStamp.join(':') + '+00:00'; + } + if (UTCTimestamp) { + //offset in minutes is the difference between gps timestamp and given timestamp + let offsetMinutes = (Date.parse(UTCTimestamp) - Date.parse(timestamp.replace(':', '-').replace(':', '-'))) / 1000 / 60; + 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 + } else { + return undefined; + } + } else { + return undefined; + } + } + try { const data = Buffer.allocUnsafe(Config.Media.photoMetadataSize); fileHandle = await fs.promises.open(fullPath, 'r'); @@ -193,7 +238,6 @@ export class MetadataLoader { } finally { await fileHandle.close(); } - try { try { const stat = fs.statSync(fullPath); @@ -202,118 +246,17 @@ export class MetadataLoader { } catch (err) { // ignoring errors } - - try { - const exif = ExifParserFactory.create(data).parse(); - if ( - exif.tags.ISO || - exif.tags.Model || - exif.tags.Make || - exif.tags.FNumber || - exif.tags.ExposureTime || - exif.tags.FocalLength || - exif.tags.LensModel - ) { - if (exif.tags.Model && exif.tags.Model !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.model = '' + exif.tags.Model; - } - if (exif.tags.Make && exif.tags.Make !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.make = '' + exif.tags.Make; - } - if (exif.tags.LensModel && exif.tags.LensModel !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.lens = '' + exif.tags.LensModel; - } - if (Utils.isUInt32(exif.tags.ISO)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.ISO = parseInt('' + exif.tags.ISO, 10); - } - if (Utils.isFloat32(exif.tags.FocalLength)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.focalLength = parseFloat( - '' + exif.tags.FocalLength - ); - } - if (Utils.isFloat32(exif.tags.ExposureTime)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.exposure = parseFloat( - parseFloat('' + exif.tags.ExposureTime).toFixed(6) - ); - } - if (Utils.isFloat32(exif.tags.FNumber)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.fStop = parseFloat( - parseFloat('' + exif.tags.FNumber).toFixed(2) - ); - } - } - if ( - !isNaN(exif.tags.GPSLatitude) || - exif.tags.GPSLongitude || - exif.tags.GPSAltitude - ) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.GPSData = {}; - - if (Utils.isFloat32(exif.tags.GPSLongitude)) { - metadata.positionData.GPSData.longitude = parseFloat( - exif.tags.GPSLongitude.toFixed(6) - ); - } - if (Utils.isFloat32(exif.tags.GPSLatitude)) { - metadata.positionData.GPSData.latitude = parseFloat( - exif.tags.GPSLatitude.toFixed(6) - ); - } - } - if ( - exif.tags.CreateDate || - exif.tags.DateTimeOriginal || - exif.tags.ModifyDate - ) { - metadata.creationDate = - (exif.tags.DateTimeOriginal || - exif.tags.CreateDate || - exif.tags.ModifyDate) * 1000; - } - if (exif.imageSize) { - metadata.size = { - width: exif.imageSize.width, - height: exif.imageSize.height, - }; - } else if ( - exif.tags.RelatedImageWidth && - exif.tags.RelatedImageHeight - ) { - metadata.size = { - width: exif.tags.RelatedImageWidth, - height: exif.tags.RelatedImageHeight, - }; - } else if ( - exif.tags.ImageWidth && - exif.tags.ImageHeight - ) { - metadata.size = { - width: exif.tags.ImageWidth, - height: exif.tags.ImageHeight, - }; - } else { - const info = imageSize(fullPath); - metadata.size = {width: info.width, height: info.height}; - } - } catch (err) { - Logger.debug(LOG_TAG, 'Error parsing exif', fullPath, err); try { - const info = imageSize(fullPath); - metadata.size = {width: info.width, height: info.height}; - } catch (e) { - metadata.size = {width: 1, height: 1}; - } + //read the actual image size, don't rely on tags for this + const info = imageSize(fullPath); + metadata.size = { width: info.width, height: info.height }; + } catch (e) { + //in case of failure, set dimensions to 0 so they may be read via tags + metadata.size = { width: 0, height: 0 }; } - try { + + try { //Parse iptc data using the IptcParser, which works correctly for both UTF-8 and ASCII const iptcData = IptcParser.parse(data); if (iptcData.country_or_primary_location_name) { metadata.positionData = metadata.positionData || {}; @@ -351,61 +294,187 @@ export class MetadataLoader { // Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err); } - if (!metadata.creationDate) { - // creationDate can be negative, when it was created before epoch (1970) - metadata.creationDate = 0; - } - try { - const exifrOptions = { - tiff: true, - xmp: true, - icc: false, - jfif: false, //not needed and not supported for png - ihdr: true, - iptc: false, //exifr reads UTF8-encoded data wrongly - exif: true, - gps: true, - translateValues: false, //don't translate orientation from numbers to strings etc. - mergeOutput: false //don't merge output, because things like Microsoft Rating (percent) and xmp.rating will be merged - }; - - const exif = await exifr.parse(data, exifrOptions); - if (exif.xmp && exif.xmp.Rating) { - metadata.rating = exif.xmp.Rating; - if (metadata.rating < 0) { - metadata.rating = 0; - } - } + let orientation = 1; //Orientation 1 is normal + const exif = await exifr.parse(data, exifrOptions); + //exif is structured in sections, we read the data by section + + //dc-section (subject is the only tag we want from dc) if (exif.dc && - exif.dc.subject && - exif.dc.subject.length > 0) { + exif.dc.subject && + exif.dc.subject.length > 0) { const subj = Array.isArray(exif.dc.subject) ? exif.dc.subject : [exif.dc.subject]; if (metadata.keywords === undefined) { - metadata.keywords = []; + metadata.keywords = []; } for (const kw of subj) { - if (metadata.keywords.indexOf(kw) === -1) { - metadata.keywords.push(kw); - } + if (metadata.keywords.indexOf(kw) === -1) { + metadata.keywords.push(kw); + } } - } - let orientation = OrientationTypes.TOP_LEFT; - if (exif.ifd0 && - exif.ifd0.Orientation) { - orientation = parseInt( - exif.ifd0.Orientation as any, - 10 - ) as number; } - if (OrientationTypes.BOTTOM_LEFT < orientation) { + + //ifd0 section + if (exif.ifd0) { + if (exif.ifd0.ImageWidth && metadata.size.width <= 0) { + metadata.size.width = exif.ifd0.ImageWidth; + } + if (exif.ifd0.ImageHeight && metadata.size.height <= 0) { + metadata.size.height = exif.ifd0.ImageHeight; + } + if (exif.ifd0.Orientation) { + orientation = parseInt( + exif.ifd0.Orientation as any, + 10 + ) as number; + } + if (exif.ifd0.Make && exif.ifd0.Make !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.make = '' + exif.ifd0.Make; + } + if (exif.ifd0.Model && exif.ifd0.Model !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.model = '' + exif.ifd0.Model; + } + //if (exif.ifd0.ModifyDate) {} //Deferred to the exif-section where the other timestamps are + } + + //exif section + if (exif.exif) { + if (exif.exif.DateTimeOriginal) { + //DateTimeOriginal is when the camera shutter closed + if (exif.exif.OffsetTimeOriginal) { //OffsetTimeOriginal is the corresponding offset + metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, exif.exif.OffsetTimeOriginal); + metadata.creationDateOffset = exif.exif.OffsetTimeOriginal; + } else { + let alt_offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); + metadata.creationDateOffset = alt_offset; + } + } 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) + if (exif.exif.OffsetTimeDigitized) { //OffsetTimeDigitized is the corresponding offset + metadata.creationDate = timestampToMS(exif.exif.CreateDate, exif.exif.OffsetTimeDigitized); + metadata.creationDateOffset = exif.exif.OffsetTimeDigitized; + } else { + let alt_offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); + metadata.creationDateOffset = alt_offset; + } + } else if (exif.ifd0?.ModifyDate) { //using else if here, because DateTimeOriginal and CreatDate have preceedence + if (exif.exif.OffsetTime) { + //exif.Offsettime is the offset corresponding to ifd0.ModifyDate + metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, exif.exif?.OffsetTime); + metadata.creationDateOffset = exif.exif?.OffsetTime + } else { + let alt_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, alt_offset); + metadata.creationDateOffset = alt_offset; + } + } + if (exif.exif.LensModel && exif.exif.LensModel !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.lens = '' + exif.exif.LensModel; + } + if (Utils.isUInt32(exif.exif.ISO)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.ISO = parseInt('' + exif.exif.ISO, 10); + } + if (Utils.isFloat32(exif.exif.FocalLength)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.focalLength = parseFloat( + '' + exif.exif.FocalLength + ); + } + if (Utils.isFloat32(exif.exif.ExposureTime)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.exposure = parseFloat( + parseFloat('' + exif.exif.ExposureTime).toFixed(6) + ); + } + if (Utils.isFloat32(exif.exif.FNumber)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.fStop = parseFloat( + parseFloat('' + exif.exif.FNumber).toFixed(2) + ); + } + if (exif.exif.ExifImageWidth && metadata.size.width <= 0) { + metadata.size.width = exif.exif.ExifImageWidth; + } + if (exif.exif.ExifImageHeight && metadata.size.height <= 0) { + metadata.size.height = exif.exif.ExifImageHeight; + } + } + + //gps section + if (exif.gps) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.GPSData = metadata.positionData.GPSData || {}; + + if (Utils.isFloat32(exif.gps.longitude)) { + metadata.positionData.GPSData.longitude = parseFloat( + exif.gps.longitude.toFixed(6) + ); + } + if (Utils.isFloat32(exif.gps.latitude)) { + metadata.positionData.GPSData.latitude = parseFloat( + exif.gps.latitude.toFixed(6) + ); + } + + if (metadata.positionData) { + if (!metadata.positionData.GPSData || + Object.keys(metadata.positionData.GPSData).length === 0) { + metadata.positionData.GPSData = undefined; + metadata.positionData = undefined; + } + } + } + //photoshop section (sometimes has City, Country and State) + if (exif.photoshop) { + function unescape(tag: string) { + return tag.replace(/&#([0-9]{1,3});/gi, function (match, numStr) { + return String.fromCharCode(parseInt(numStr, 10)); + }); + } + + if (!metadata.positionData?.country && exif.photoshop.Country) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.country = unescape(exif.photoshop.Country); + } + if (!metadata.positionData?.state && exif.photoshop.State) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.state = unescape(exif.photoshop.State); + } + if (!metadata.positionData?.city && exif.photoshop.City) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.city = unescape(exif.photoshop.City); + } + } + + /////////////////////////////////////// + metadata.size.height = Math.max(metadata.size.height, 1); //ensure height dimension is positive + metadata.size.width = Math.max(metadata.size.width, 1); //ensure width dimension is positive + + //Before moving on to the XMP section (particularly the regions (mwg-rs)) + //we need to switch width and height for images that are rotated sideways + if (4 < orientation) { //Orientation is sideways (rotated 90% or 270%) // noinspection JSSuspiciousNameCombination const height = metadata.size.width; // noinspection JSSuspiciousNameCombination metadata.size.width = metadata.size.height; metadata.size.height = height; } + /////////////////////////////////////// + //xmp section + if (exif.xmp && exif.xmp.Rating) { + metadata.rating = exif.xmp.Rating; + if (metadata.rating < 0) { + metadata.rating = 0; + } + } + //xmp."mwg-rs" section if (Config.Faces.enabled && exif["mwg-rs"] && exif["mwg-rs"].Regions) { @@ -422,24 +491,24 @@ export class MetadataLoader { x: string, y: string ) => { - if (OrientationTypes.BOTTOM_LEFT < orientation) { + if (4 < orientation) { //roation is sidewards (90 or 270 degrees) [x, y] = [y, x]; [w, h] = [h, w]; } let swapX = 0; let swapY = 0; switch (orientation) { - case OrientationTypes.TOP_RIGHT: - case OrientationTypes.RIGHT_TOP: + case 2: //TOP RIGHT (Mirror horizontal): + case 6: //RIGHT TOP (Rotate 90 CW) swapX = 1; break; - case OrientationTypes.BOTTOM_RIGHT: - case OrientationTypes.RIGHT_BOTTOM: + case 3: // BOTTOM RIGHT (Rotate 180) + case 7: // RIGHT BOTTOM (Mirror horizontal and rotate 90 CW) swapX = 1; swapY = 1; break; - case OrientationTypes.BOTTOM_LEFT: - case OrientationTypes.LEFT_BOTTOM: + case 4: //BOTTOM_LEFT (Mirror vertical) + case 8: //LEFT_BOTTOM (Rotate 270 CW) swapY = 1; break; } @@ -451,7 +520,6 @@ export class MetadataLoader { top: Math.round(Math.abs(parseFloat(y) - swapY) * metadata.size.height), }; }; - /* Adobe Lightroom based face region structure */ if ( regionRoot && @@ -497,7 +565,7 @@ export class MetadataLoader { box.top = Math.round(Math.max(0, box.top - box.height / 2)); - faces.push({name, box}); + faces.push({ name, box }); } } if (faces.length > 0) { @@ -517,6 +585,11 @@ export class MetadataLoader { // ignoring errors } + if (!metadata.creationDate) { + // creationDate can be negative, when it was created before epoch (1970) + metadata.creationDate = 0; + } + try { // search for sidecar and merge metadata const fullPathWithoutExt = path.parse(fullPath).name; @@ -564,7 +637,5 @@ export class MetadataLoader { return MetadataLoader.EMPTY_METADATA; } return metadata; - - } } diff --git a/src/common/entities/MediaDTO.ts b/src/common/entities/MediaDTO.ts index 9c282abec..7bcdcd992 100644 --- a/src/common/entities/MediaDTO.ts +++ b/src/common/entities/MediaDTO.ts @@ -17,6 +17,7 @@ export interface MediaMetadata { size: MediaDimension; creationDate: number; fileSize: number; + creationDateOffset?: string; keywords?: string[]; rating?: RatingTypes; title?: string; diff --git a/src/common/entities/PhotoDTO.ts b/src/common/entities/PhotoDTO.ts index a4af1c24a..6184bbd20 100644 --- a/src/common/entities/PhotoDTO.ts +++ b/src/common/entities/PhotoDTO.ts @@ -33,6 +33,7 @@ export interface PhotoMetadata extends MediaMetadata { positionData?: PositionMetaData; size: MediaDimension; creationDate: number; + creationDateOffset?: string; fileSize: number; faces?: FaceRegion[]; } diff --git a/src/common/entities/VideoDTO.ts b/src/common/entities/VideoDTO.ts index d9cefefcc..ef0e39fdb 100644 --- a/src/common/entities/VideoDTO.ts +++ b/src/common/entities/VideoDTO.ts @@ -11,6 +11,7 @@ export interface VideoDTO extends MediaDTO { export interface VideoMetadata extends MediaMetadata { size: MediaDimension; creationDate: number; + creationDateOffset?: string; bitRate: number; duration: number; // in milliseconds fileSize: number; diff --git a/test/backend/assets/Chars.json b/test/backend/assets/Chars.json index 281eb7c9f..83bec784c 100644 --- a/test/backend/assets/Chars.json +++ b/test/backend/assets/Chars.json @@ -3,7 +3,8 @@ "width": 1920, "height": 1080 }, - "creationDate": 1706659327000, + "creationDate": 1706655727000, + "creationDateOffset": "+01:00", "fileSize": 111432, "positionData": { "GPSData": { diff --git a/test/backend/assets/edge_case_exif_data/before_epoch.json b/test/backend/assets/edge_case_exif_data/before_epoch.json index 51ddb9a6d..982679ddb 100644 --- a/test/backend/assets/edge_case_exif_data/before_epoch.json +++ b/test/backend/assets/edge_case_exif_data/before_epoch.json @@ -9,7 +9,8 @@ "model": "Canon EOS 600D" }, "caption": "Bambi Caption", - "creationDate": -11630935227000, + "creationDate": -11630942427000, + "creationDateOffset": "+02:00", "faces": [ { "box": { 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 7600f4da0..7d9733028 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": -2211753600000, + "creationDate": 0, "fileSize": 72850, "size": { "height": 768, diff --git a/test/backend/assets/png_with_keyword_and_dates.json b/test/backend/assets/png_with_faces_and_dates.json similarity index 85% rename from test/backend/assets/png_with_keyword_and_dates.json rename to test/backend/assets/png_with_faces_and_dates.json index 2f4ad94e5..a72722495 100644 --- a/test/backend/assets/png_with_keyword_and_dates.json +++ b/test/backend/assets/png_with_faces_and_dates.json @@ -4,7 +4,8 @@ "width": 26, "height": 26 }, - "creationDate": 1707167247786, + "creationDate": 1599990007000, + "creationDateOffset": "+05:00", "fileSize": 5758, "keywords": [ ], diff --git a/test/backend/assets/png_with_keyword_and_dates.png b/test/backend/assets/png_with_faces_and_dates.png similarity index 100% rename from test/backend/assets/png_with_keyword_and_dates.png rename to test/backend/assets/png_with_faces_and_dates.png diff --git a/test/backend/assets/test_png.json b/test/backend/assets/test_png.json index 88800147e..7b528aac1 100644 --- a/test/backend/assets/test_png.json +++ b/test/backend/assets/test_png.json @@ -24,5 +24,6 @@ "size": { "height": 26, "width": 26 - } + }, + "creationDate": 1707171121504 } diff --git a/test/backend/assets/timestamps/big_ben.jpg b/test/backend/assets/timestamps/big_ben.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0649be533dc6f48db824dfd3c56bfce117247e64 GIT binary patch literal 18532 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 literal 0 HcmV?d00001 diff --git a/test/backend/assets/timestamps/big_ben.json b/test/backend/assets/timestamps/big_ben.json new file mode 100644 index 000000000..c4080036a --- /dev/null +++ b/test/backend/assets/timestamps/big_ben.json @@ -0,0 +1,25 @@ +{ + "size": { + "width": 200, + "height": 300 + }, + "creationDate": 1686141955000, + "creationDateOffset": "+01:00", + "fileSize": 18532, + "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.jpg b/test/backend/assets/timestamps/big_ben_no_tsoffset_but_gps_utc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48897cf140d88e31f01c5fcbd89cbf46ce8c4916 GIT binary patch literal 18663 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 W2-!cur~wv|^}Tf)tK#6p$^Qq^NGX8; literal 0 HcmV?d00001 diff --git a/test/backend/assets/timestamps/big_ben_only_time.json b/test/backend/assets/timestamps/big_ben_only_time.json new file mode 100644 index 000000000..5113607de --- /dev/null +++ b/test/backend/assets/timestamps/big_ben_only_time.json @@ -0,0 +1,20 @@ +{ + "size": { + "width": 200, + "height": 300 + }, + "creationDate": 1686145555000, + "fileSize": 17850, + "cameraData": { + "model": "Canon EOS R5", + "make": "Canon" + }, + "positionData": { + "country": "Storbritannien", + "state": "England", + "city": "St James's" + }, + "keywords": [ + "Big Ben" + ] +} \ No newline at end of file diff --git a/test/backend/assets/timestamps/sydney_opera_house.jpg b/test/backend/assets/timestamps/sydney_opera_house.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b595487ed5c187ce6f0430dd7e5505bbb262fc50 GIT binary patch literal 22755 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 literal 0 HcmV?d00001 diff --git a/test/backend/assets/timestamps/sydney_opera_house.json b/test/backend/assets/timestamps/sydney_opera_house.json new file mode 100644 index 000000000..dda6cff5d --- /dev/null +++ b/test/backend/assets/timestamps/sydney_opera_house.json @@ -0,0 +1,25 @@ +{ + "size": { + "width": 300, + "height": 200 + }, + "creationDate": 1600512957000, + "creationDateOffset": "+10:00", + "fileSize": 22755, + "cameraData": { + "model": "ILCE-7RM3", + "make": "Sony" + }, + "positionData": { + "GPSData": { + "longitude": 151.210381, + "latitude": -33.855698 + }, + "country": "Australien", + "state": "New South Wales", + "city": "Dawes Point" + }, + "keywords": [ + "Sydney Opera House" + ] +} \ No newline at end of file diff --git a/test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg b/test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1c9a652d337a90e9c251477c8cd2121fa807884 GIT binary patch literal 22653 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�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 literal 0 HcmV?d00001 diff --git a/test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.json b/test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.json new file mode 100644 index 000000000..e5f783687 --- /dev/null +++ b/test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.json @@ -0,0 +1,25 @@ +{ + "size": { + "width": 300, + "height": 200 + }, + "creationDate": 1600512957000, + "creationDateOffset": "+10:00", + "fileSize": 22653, + "cameraData": { + "model": "ILCE-7RM3", + "make": "Sony" + }, + "positionData": { + "GPSData": { + "longitude": 151.210381, + "latitude": -33.855698 + }, + "country": "Australien", + "state": "New South Wales", + "city": "Dawes Point" + }, + "keywords": [ + "Sydney Opera House" + ] +} \ No newline at end of file diff --git a/test/backend/assets/two_ratings.json b/test/backend/assets/two_ratings.json index 924232b03..67f1b08e2 100644 --- a/test/backend/assets/two_ratings.json +++ b/test/backend/assets/two_ratings.json @@ -7,7 +7,8 @@ "make": "samsung", "model": "SM-G975F" }, - "creationDate": 1619181527000, + "creationDate": 1619174327000, + "creationDateOffset": "+02:00", "fileSize": 4877, "rating":3, "size": { diff --git a/test/backend/assets/xmp/xmp_subject.json b/test/backend/assets/xmp/xmp_subject.json index a9a46f4a3..a8a641efe 100644 --- a/test/backend/assets/xmp/xmp_subject.json +++ b/test/backend/assets/xmp/xmp_subject.json @@ -7,7 +7,8 @@ "make": "samsung", "model": "SM-G975F" }, - "creationDate": 1614703656000, + "creationDate": 1614700056000, + "creationDateOffset": "+01:00", "fileSize": 4709, "keywords": [ "Max", diff --git a/test/backend/unit/model/threading/MetaDataLoader.spec.ts b/test/backend/unit/model/threading/MetaDataLoader.spec.ts index 0e0740696..5a0e7e49c 100644 --- a/test/backend/unit/model/threading/MetaDataLoader.spec.ts +++ b/test/backend/unit/model/threading/MetaDataLoader.spec.ts @@ -24,11 +24,16 @@ describe('MetadataLoader', () => { it('should load png', async () => { const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/test_png.png')); - delete data.creationDate; // creation time for png not supported const expected = require(path.join(__dirname, '/../../../assets/test_png.json')); expect(Utils.clone(data)).to.be.deep.equal(expected); }); + it('should load png with faces and dates', async () => { + const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/png_with_faces_and_dates.png')); + const expected = require(path.join(__dirname, '/../../../assets/png_with_faces_and_dates.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')); @@ -69,6 +74,31 @@ describe('MetadataLoader', () => { expect(Utils.clone(data)).to.be.deep.equal(expected); }); + it('should load jpg with timestamps, timezone AEST (UTC+10) and gps (UTC)', async () => { + const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/timestamps/sydney_opera_house.jpg')); + const expected = require(path.join(__dirname, '/../../../assets/timestamps/sydney_opera_house.json')); + expect(Utils.clone(data)).to.be.deep.equal(expected); + }); + it('should load jpg with timestamps and gps (UTC) and calculate offset +10', async () => { + const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg')); + const expected = require(path.join(__dirname, '/../../../assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.json')); + expect(Utils.clone(data)).to.be.deep.equal(expected); + }); + it('should load jpg with timestamps, timezone BST (UTC+1) and gps (UTC)', async () => { + const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/timestamps/big_ben.jpg')); + const expected = require(path.join(__dirname, '/../../../assets/timestamps/big_ben.json')); + expect(Utils.clone(data)).to.be.deep.equal(expected); + }); + it('should load jpg with timestamps and gps (UTC) and calculate offset +1', async () => { + const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/timestamps/big_ben_no_tsoffset_but_gps_utc.jpg')); + 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 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')); + expect(Utils.clone(data)).to.be.deep.equal(expected); + }); describe('should load jpg with proper height and orientation', () => { it('jpg 1', async () => { const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/orientation/broken_orientation_exif.jpg')); From a6035f5b6570f255a597251301c6aa4196339867 Mon Sep 17 00:00:00 2001 From: gras Date: Sun, 11 Feb 2024 17:08:21 +0100 Subject: [PATCH 2/9] fixed style errors --- .../model/fileaccess/MetadataLoader.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index 3e10e1fae..bb5e07b35 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -201,18 +201,19 @@ export class MetadataLoader { return Date.parse(timestamp.replace(':', '-').replace(':', '-') + (offset ? offset : '+00:00')); } + //function to calculate offset from exif.exif.gpsTimeStamp or exif.gps.GPSDateStamp + exif.gps.GPSTimestamp const getTimeOffsetByGPSStamp = (timestamp: string, gpsTimeStamp: string, gps: any) => { - let UTCTimestamp = gpsTimeStamp; + let UTCTimestamp = gpsTimeStamp; //use the exif.exif.gpsTimestamp if available if (!UTCTimestamp && gps && gps.GPSDateStamp && - gps.GPSTimeStamp) { + gps.GPSTimeStamp) { //else use exif.gps.GPS*Stamp if available //GPS timestamp is always UTC (+00:00) UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + gps.GPSTimeStamp.join(':') + '+00:00'; } if (UTCTimestamp) { //offset in minutes is the difference between gps timestamp and given timestamp - let offsetMinutes = (Date.parse(UTCTimestamp) - Date.parse(timestamp.replace(':', '-').replace(':', '-'))) / 1000 / 60; + const offsetMinutes = (Date.parse(UTCTimestamp) - Date.parse(timestamp.replace(':', '-').replace(':', '-'))) / 1000 / 60; 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 +/- @@ -226,6 +227,13 @@ export class MetadataLoader { } } + //Function to convert html code for special characters into their corresponding character (used in exif.photoshop-section) + const unescape = (tag: string) => { + return tag.replace(/&#([0-9]{1,3});/gi, function (match, numStr) { + return String.fromCharCode(parseInt(numStr, 10)); + }); + } + try { const data = Buffer.allocUnsafe(Config.Media.photoMetadataSize); fileHandle = await fs.promises.open(fullPath, 'r'); @@ -347,7 +355,7 @@ export class MetadataLoader { metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, exif.exif.OffsetTimeOriginal); metadata.creationDateOffset = exif.exif.OffsetTimeOriginal; } else { - let alt_offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); + const alt_offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); metadata.creationDateOffset = alt_offset; } @@ -357,7 +365,7 @@ export class MetadataLoader { metadata.creationDate = timestampToMS(exif.exif.CreateDate, exif.exif.OffsetTimeDigitized); metadata.creationDateOffset = exif.exif.OffsetTimeDigitized; } else { - let alt_offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); + const alt_offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); metadata.creationDateOffset = alt_offset; } @@ -367,7 +375,7 @@ export class MetadataLoader { metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, exif.exif?.OffsetTime); metadata.creationDateOffset = exif.exif?.OffsetTime } else { - let alt_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); + const alt_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, alt_offset); metadata.creationDateOffset = alt_offset; } @@ -432,12 +440,6 @@ export class MetadataLoader { } //photoshop section (sometimes has City, Country and State) if (exif.photoshop) { - function unescape(tag: string) { - return tag.replace(/&#([0-9]{1,3});/gi, function (match, numStr) { - return String.fromCharCode(parseInt(numStr, 10)); - }); - } - if (!metadata.positionData?.country && exif.photoshop.Country) { metadata.positionData = metadata.positionData || {}; metadata.positionData.country = unescape(exif.photoshop.Country); From a2c92087067c1d4f40ae64ab8abaa3f0180bd726 Mon Sep 17 00:00:00 2001 From: gras Date: Sun, 11 Feb 2024 21:41:42 +0100 Subject: [PATCH 3/9] Fixed serious error with offset calculation. Fixed bad test-data. --- .../model/fileaccess/MetadataLoader.ts | 24 +++++++++++++----- ...ey_opera_house_no_tsoffset_but_gps_utc.jpg | Bin 22653 -> 22641 bytes ...y_opera_house_no_tsoffset_but_gps_utc.json | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index bb5e07b35..03e095438 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -197,23 +197,35 @@ export class MetadataLoader { //function to convert timestamp into milliseconds taking offset into account const timestampToMS = (timestamp: string, offset: string) => { - "replace first two : with - in timestamp string and add offset if exists (else +00:00 - UTC), parse this into MS" - return Date.parse(timestamp.replace(':', '-').replace(':', '-') + (offset ? offset : '+00:00')); + if (!timestamp) { + return undefined; + } + //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'); + } else if (formattedTimestamp.indexOf("+") > 0) { //don't do anything + } else { //add offset + formattedTimestamp = formattedTimestamp + (offset ? offset : '+00:00'); + } + //parse into MS and return + return Date.parse(formattedTimestamp); } //function to calculate offset from exif.exif.gpsTimeStamp or exif.gps.GPSDateStamp + exif.gps.GPSTimestamp const getTimeOffsetByGPSStamp = (timestamp: string, gpsTimeStamp: string, gps: any) => { - let UTCTimestamp = gpsTimeStamp; //use the exif.exif.gpsTimestamp if available + let UTCTimestamp = gpsTimeStamp; if (!UTCTimestamp && gps && 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(':') + '+00:00'; + UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + gps.GPSTimeStamp.join(':'); } - if (UTCTimestamp) { + if (UTCTimestamp && timestamp) { //offset in minutes is the difference between gps timestamp and given timestamp - const offsetMinutes = (Date.parse(UTCTimestamp) - Date.parse(timestamp.replace(':', '-').replace(':', '-'))) / 1000 / 60; + //to calculate this correctly, we have to work with the same offset + const offsetMinutes = (timestampToMS(timestamp, '+00:00')- timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60; 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 +/- diff --git a/test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg b/test/backend/assets/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg index f1c9a652d337a90e9c251477c8cd2121fa807884..184331398738dbff246dd6f1d9e316c383ba9acc 100644 GIT binary patch delta 44 zcmV+{0Mq~dumSO~0kE3_0Va|Q9J9Cr`vjA12M@Ez2Oa?hF*#H*Fth6j$pW*w3&Tu? C0ua3b delta 55 zcmeykf${GK#tpNX7>y Date: Sun, 11 Feb 2024 22:35:19 +0100 Subject: [PATCH 4/9] Added two more metadata dates to fall back to, instead of file date. This fixes the png-test --- src/backend/model/fileaccess/MetadataLoader.ts | 14 ++++++++++++-- test/backend/assets/test_png.json | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index 03e095438..ae34c3e09 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -39,7 +39,7 @@ export class MetadataLoader { try { const stat = fs.statSync(fullPath); metadata.fileSize = stat.size; - metadata.creationDate = stat.mtime.getTime(); + metadata.creationDate = stat.mtime.getTime(); //Default date is file system time of last modification } catch (err) { console.log(err); // ignoring errors @@ -359,8 +359,10 @@ export class MetadataLoader { //if (exif.ifd0.ModifyDate) {} //Deferred to the exif-section where the other timestamps are } - //exif section + //exif section starting with the date sectino if (exif.exif) { + //Preceedence of dates: exif.DateTimeOriginal, exif.CreateDate, ifd0.ModifyDate, ihdr["Creation Time"], xmp.MetadataDate, file system date + //Filesystem is the absolute last resort, and it's hard to write tests for, since file system dates are changed on e.g. git clone. if (exif.exif.DateTimeOriginal) { //DateTimeOriginal is when the camera shutter closed if (exif.exif.OffsetTimeOriginal) { //OffsetTimeOriginal is the corresponding offset @@ -391,6 +393,14 @@ export class MetadataLoader { metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, alt_offset); metadata.creationDateOffset = alt_offset; } + } 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 || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.ihdr["Creation Time"], any_offset); + metadata.creationDateOffset = 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 || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.xmp.MetadataDate, any_offset); + metadata.creationDateOffset = any_offset; } if (exif.exif.LensModel && exif.exif.LensModel !== '') { metadata.cameraData = metadata.cameraData || {}; diff --git a/test/backend/assets/test_png.json b/test/backend/assets/test_png.json index 7b528aac1..0edd56615 100644 --- a/test/backend/assets/test_png.json +++ b/test/backend/assets/test_png.json @@ -25,5 +25,5 @@ "height": 26, "width": 26 }, - "creationDate": 1707171121504 + "creationDate": 1544748139000 } From 49e861d35819df3c6423c44653b3d787317ae0da Mon Sep 17 00:00:00 2001 From: gras Date: Tue, 13 Feb 2024 23:00:24 +0100 Subject: [PATCH 5/9] package-lock auto updated --- package-lock.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51b2286a0..e71ebb12d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20051,12 +20051,6 @@ "node": ">=6.10" } }, - "node_modules/ts-exif-parser": { - "version": "0.2.2", - "dependencies": { - "sax": "1.2.4" - } - }, "node_modules/ts-helpers": { "version": "1.1.2", "dev": true, @@ -35084,12 +35078,6 @@ "dev": true, "optional": true }, - "ts-exif-parser": { - "version": "0.2.2", - "requires": { - "sax": "1.2.4" - } - }, "ts-helpers": { "version": "1.1.2", "dev": true, From 5e94220d6db8bc8ba1b3634c6d3536049408fbb7 Mon Sep 17 00:00:00 2001 From: grasdk <115414609+grasdk@users.noreply.github.com> Date: Fri, 16 Feb 2024 19:17:31 +0100 Subject: [PATCH 6/9] Use of offset value in the UI (#5) Added recognition of the offset value in the UI. It will be displayed if available. Caveat: Search will not take offset into account. A new year's picture taken in Sydney the 1st of January 2019 00:15:00 GMT+11, is technically taken in 31st of December 2018 in UTC. Therefore this picture won't show of in seaches where the after: parameter is set to 1st of january 2019. This is both correct and wrong at the same time. UTC-wise it is correct, local time it is not correct. I guess most people would find local time most untuitive, so there is room for improvement of the search. :) --- demo/images/dupl/big_ben_only_time.jpg | Bin 0 -> 17850 bytes demo/images/dupl/sydney_opera_house.jpg | Bin 0 -> 22755 bytes demo/images/timestamps/big_ben.jpg | Bin 0 -> 18532 bytes .../big_ben_no_tsoffset_but_gps_utc.jpg | Bin 0 -> 18663 bytes demo/images/timestamps/big_ben_only_time.jpg | Bin 0 -> 17850 bytes demo/images/timestamps/newyear_london.jpg | Bin 0 -> 77654 bytes demo/images/timestamps/newyear_sydney.jpg | Bin 0 -> 67879 bytes demo/images/timestamps/sydney_opera_house.jpg | Bin 0 -> 22755 bytes ...ey_opera_house_no_tsoffset_but_gps_utc.jpg | Bin 0 -> 22641 bytes .../model/fileaccess/MetadataLoader.ts | 1330 ++++++++--------- src/backend/model/messenger/EmailMessenger.ts | 3 +- src/backend/routes/GalleryRouter.ts | 572 +++---- src/common/Utils.ts | 14 + .../ui/duplicates/duplicates.component.html | 3 +- .../app/ui/gallery/filter/filter.service.ts | 2 +- .../controls.lightbox.gallery.component.ts | 2 +- ...info-panel.lightbox.gallery.component.html | 4 +- .../info-panel.lightbox.gallery.component.ts | 2 +- .../ui/gallery/navigator/sorting.service.ts | 2 +- 19 files changed, 975 insertions(+), 959 deletions(-) create mode 100644 demo/images/dupl/big_ben_only_time.jpg create mode 100644 demo/images/dupl/sydney_opera_house.jpg create mode 100644 demo/images/timestamps/big_ben.jpg create mode 100644 demo/images/timestamps/big_ben_no_tsoffset_but_gps_utc.jpg create mode 100644 demo/images/timestamps/big_ben_only_time.jpg create mode 100644 demo/images/timestamps/newyear_london.jpg create mode 100644 demo/images/timestamps/newyear_sydney.jpg create mode 100644 demo/images/timestamps/sydney_opera_house.jpg create mode 100644 demo/images/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg diff --git a/demo/images/dupl/big_ben_only_time.jpg b/demo/images/dupl/big_ben_only_time.jpg new file mode 100644 index 0000000000000000000000000000000000000000..88d3bc35cf3018b385363a92f1ef9d59af8eef8b GIT binary patch literal 17850 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 W2-!cur~wv|^}Tf)tK#6p$^Qq^NGX8; literal 0 HcmV?d00001 diff --git a/demo/images/dupl/sydney_opera_house.jpg b/demo/images/dupl/sydney_opera_house.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b595487ed5c187ce6f0430dd7e5505bbb262fc50 GIT binary patch literal 22755 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 literal 0 HcmV?d00001 diff --git a/demo/images/timestamps/big_ben.jpg b/demo/images/timestamps/big_ben.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0649be533dc6f48db824dfd3c56bfce117247e64 GIT binary patch literal 18532 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 literal 0 HcmV?d00001 diff --git a/demo/images/timestamps/big_ben_no_tsoffset_but_gps_utc.jpg b/demo/images/timestamps/big_ben_no_tsoffset_but_gps_utc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48897cf140d88e31f01c5fcbd89cbf46ce8c4916 GIT binary patch literal 18663 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 W2-!cur~wv|^}Tf)tK#6p$^Qq^NGX8; literal 0 HcmV?d00001 diff --git a/demo/images/timestamps/newyear_london.jpg b/demo/images/timestamps/newyear_london.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a21185374823c0c5589d86cd38b2e2ef10d65c7 GIT binary patch literal 77654 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

=`L?wK% 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

9vbqO8Tp@;M12cqSzKJ62akMCKW<5jw1kV};M+ zmL^r_&N33g8*lx`W&w_=CvoQ~s~X7yK|xmshFqlfOyumd<%k3p6dOG-9Q;^6?6J^3 zOs2*JQ4Sa#5`;cJKb#iwBcJTpLkb+fwBmTviSS}|H$A>_ zVrC-HuDdbnNkoN++$<17?SPFDTvpz&U;T2cGt8;+ft)e2mb`Lx#xN6HnW78{I@g?2 zh>U=ujgr?`N1O1)vs2t~#Hq=rzoy8JlL{Sy$7%tNR{n96EBSDgQWE5TFe4vnB)@Ch z;|nlprBRN1-0otdEGaG72F<*-`ua@d9aE4l( z<99cmOn(?5-miR#6{d+BUmsPyz48+zoCoxPqAmHrVl+s>{ODY)Z4+Tkj+eRL zwi1_Mf3qZ-<&nP_F>yL7yfcmDGItAajBGj)Kxk`NcIbqW@ZL#ml?Tk?v(b_Y&Ov{h z7I8=-W~(70UuEy(HU`2h->yfzV0aVkVo`$B9c3pBZuW>d22mS(ubgq(qKzBik?Qo%*pnz9hLIm)ttKhCjXh_Y(c!;Dr6M@aJb(T4_dO|Xy$ zGPz$!XpK;R)8hi7xty^`F!sry-C=F+r`N2P6$GjAiHaJF?znM7ip2Y(xqdLqrix=t zKt{Oh7pO+=@=Os)CNkUUB1Fza$jB+L71kgf7XE&4sWPx(w%Dy16$Qf#zTWr~&^ArP z%0}>E<+;P-uk_<>7eo%Z>Eka{ERpDw{;ym z932^wzd6WD3FRH}vliN#mI9_Hdj0S@9Cc!H+dA-C;zJJ$}s*tUW(o5-h1}^~G z)jw~XiU3rB(Eai@Y=o(Z$q{EzB$fVihh`}dM1Bj(EfP-8c`PBxNjt)g4v!8nx=C%w zKb($?X(S#lu^RWC=@{0Y6}+m6ON+Q$>5~*PfFf`iQBRy`OWDpSKySg|P3qs4jHd_HIFTPm-ro6O zAa%o!t%q#nhXNubV+$NWmAq(?d}JO+-wvZrf^n@DH1UCw2w|C@C*utbRR?&MjY&OW zQ4*S%aIyaDA;$2=`X7vr9h?XI#Fb=(g;EJbB2;27j%N-`Pfh?m7H^$oI;25(*sIzw z`+^+^b7^LZ>8yt;Hi2=c#A>zE2c{kxhwk*80t$uTX;*n?uo-d?5m9MgJ#jS`eBDS% zEKiPil(i@!Jm2+(T(b%gIcNRnF2tTO4*q!FR|zLZ*oz_Y@0Y&`9R>;8?~n}9hlW&b z@sYUp{1h=3zuOqy0tFQTIxA)=VF^>Q9?$!mSW|Mu)||tdSazK@@@wyr-hx>v#@S3x z5Qmq)gen5iX0Q7robg!|XZPgX*3-Vlp^HxJzU~;Z&9(plQLpU>bmxqeZ5&$hN9Sj)wcWalyI@kykNYQ|BYiq^L!N zKs{%i0QVIdR~20GQGgIkC^HZD=L5I0&bEEy+ao54n@P|8#4eHd!QCz3qIPds=2h#K zbf_^(21)B#1BfL6ThozZiAf-27LpB@kjJBl7NI2UwMo<-FriX+-WSQ%9#?e-UcBOm zU+*9z7SWc&AA{O(%b`bcr0MP64KhZUy{2#ajI@h}1f0u$*46jk*tWa9 zee;v)Bp*a?_{S_gRa({jWZ=kKXa^^Y^vlaCg^z0ezZpE&5`H(O=*l%4Y?_*v3*Ksu zrd3Pzq~iCIUaA^8I-hKlNU%6acqBwu8ui4&ipyfWwSMg5Ok3ftD&(w#(px$8U&|_ zh0$27(1~KPHEU`1%R$&9jxpy&V(iW>xi|{SSt=_kz83qn&P0MC4tbtzg&%1#oKp~K zBH&)5DIE+<^sKb-_tq-Xg=VMcILUHT6JY-Uca5->A@@(la|B56C&o;|KtqTfddV14 zUnEQ)CR=&-AUJwoc)_Z56*-IN{W#m?T!>aKeNvh)4l#K^ES?^$HSaUYcXBrkU31v+ zjI)cg;F8r5r>MgbNCF>08r6}v+yk`^TWFDfyk~WQOfZ(~f`R@Uco+e=0<~Saz6m6p zx;i**;|v=R8`VOaI(xw%Ir&IU^&iF~0Yith$Qyt~b2 z*A)mdX>D`gHX0Z|kd*ZNW{Gk5;ZzDpZ(kGm)5&>*ufW)Wm zF(6hv&3{?D0-%pB@!oPo(a8kz<0@PjL>*$hZn2>N0nsL5EzVn76pmM#iNPfBM*6+= zUfA0yMPWwclRur{Ki z03KV$5g}3@onA>_VqGF<%BK=*P&#R@6WN9bYr)}pfv-}9#wax~*k5rNu?19?gy+W@N5;5^=-egBu?+OV-YH>Hcj`51#a6&tMeMiv*$X5GHD&K95I`%kW0LUb@P+F z6DVNQYYuQqIVvu8-+6V>HU+Gg{O2B30t-6b;n1G>JFV8p^xS%2!6_`@s1E$=*A9(5 zUh<+1FRIMlWc=l-(G!ig-{rL znKV&~D+dyAQGd=5sJ+Warv2~;&9Noh>-=SEAx$7~pt*ip!sJk21(7$D0<;4YB`zyu zcA5$;2TH*~~%F-Z9m)F!D)p zO@cs3ias|Jk=L0HLFktW7?3QcO?k>rKxpn4{rSgj(P4GztU1YK;$NJQ@?qj)&Wwz; zP!_Nx0!L`uDD_R3)@hG*X(mrEj2Ka8H=9{&KCB+JN0YNQb- zpBV-mgo3>$GHXDSc{>>2SUMEYoVjiP02{>ILpM$=IHAFWmlE8cZNC_s&D21|x_4*J zNs#diE)!tKQ`SKzXO3BPkKYy;P_|MQtxx>KFBsaW${=^5)*LJu&eFoK0e2h6Bo(7e zqM~26Nip^N1|dXI5m*PnL6L_W4tL`5=o6UfDDlN}h^X?GgNOTH+akg0r?n)M{N(_f zG`f|CA_3hBCZ9&KeH%6&9{IZ%Q^>>f#z_j5C?l5Ts?q3TQa=LE&?G1r*;u|Y%iGf( z*6@x!_4FU5zof1`#%BkXcmDu#4BMHFy9KP1gi~g(^YNS$CSi3#J(#dog@6KW<^5-k z_dv58V0EAvbKssxbNI@4=>dC=zouI~P+aq5zb857MB+FI?(Nfr1F2ZwUPIPU38M!R z{?6X`E~s=~0D6g3&M{(8^+%V0`MjD8D$YTzK8$fByD>~l)?Pg0An*p923odm@S!or zM~_rLoTb4vkAt6j({|g2^x*>|@lR=!@Pob)*sTDXIJ8w_#v{-IKQD;mBRjhj^;!Ee)ZWoZNQPBusPllOEeh<2gCWcyOvCEC{{T)1xJz>} ze~B>WB#1BpgpuMp>jNhdnh2hHmsuC$v^!&&H8bZo`h!963SuLT=LFf=$p*hO>)Ta|oX%kLwFKU~)}IKDapc0y`j%1NzAt zVI&)fZL7s%GgD7RT{@iWMwO8qiQLJK7~&x$Jo#P=F#iBr73Urov`AaVxW$@DHg6Vw z_;Uunf0|#!WcVfASIFWNM{NYmd$44#YAZa=Kjs8VAswIB^@Ic1GigV$#9!V%p~=D` z@cjk#AT*wGsMc1*&GxeGr>0me{RRJi+Dm)FqD~w@}@LTI7;b`oSta` zWJ?eM-BjS_*l3e)!!7>+La_92FG4aiUnlx-f*eX9-#q>@H1?mjF7kN0+v6PA_j93g zedY3NjGUa9$-{r3HH$doK@#9XMB;LJPk%vhh*3Le$z&&3*@eTClk@>7xIAS8l;@Kj z=>@J4)27_gcByHr_wk#oDnZFQ#=0r(1=XlztF-aYb@;1YtdQDHni4~FcF2BdFq8J3)i zT)aj2HhBpD0HEstw)LBoN%WXPgUqtnAP@Xbii)1Z0AT&S{WZsS%xQfHEWdCotPem5 znVR)XT;wENn3Uv9gOd4_M|27RRda4fu^oD+WodjXcIMQp^LwDZv`5LL<=?t`qc2SFK4f{e3IGw zjm{|&&YXY!z4;0PV83tf`6jNYV6a?5mqvLO=ZGDHOtVSx+dDUNCWtf2I3zI>)|y-X zti_3)p=(hz?g{Kn%WoAg;7Jux=biE9Ao&JIFiQUb@^|qbbAkxJ@uRI(Z@nCn0~IrTgC1d z{rPett3tME*Q4F;KFC+EL=3Qp53+H*;r{@XsafAMee0}lV;cc??3&7@V9eabH0K+! zL(NcZy@QLgx)&9XyideZpl-{qaFmB+i39$D@Aaxf7*O?x-%xz`S43a!`J?i|okR8p z^Eq12YZ5ZH=-7}x-Ag|zLhd>(=SZ}d7GnU_Jk{$H_ikd)Fd&!&ymF>E0kBR!b}9>p zXE@>8ZB{7j5$-x;yV&Q1XO)WzB8hycJs(GZQDu@o!td-}@IA z((?!5oSbA4RKZNa*&#_`$+q?@hHHV*=xR#_0oPE-ImbaETHJC00000000315g{=_QDG2qfsvu`vBA;d@gV=&00;pC0RcY{mEgz0@O&@A z{0IIn9u?tN2*vQfA20a7`+Oe?jAmitM3>;Q<;MIjrNx(X5%GWNY8iyD25-XrE?n(46v6j;U5$pEKBJK!d**;{MY@^c({p~U0_6&E-YSBoh z2~wX0Ar&fAsQ4(68;Y?9Sz!|MYP^nh}RH;(PJC!IA zt6ab{SYQKBGWkb@drHo0oe|0~0%DBRNv{oxTo7GAl`p~YT83g=JStRRf#6$Iu}nCl z4u&%+_q=aBx^=m91+T=p>X;vU!tIeIDgxKK{Yr-2M5hE)ZRq z5rx8!Q+_NZO-q+99u)++qBa8CjDhVR23^aFnic6Dn!j(KNUdPPkPhjq^6xK^u3;6f z_vIGwpFe&jF<7Omxj4sszS50{H7x_5zZr*CzSqnkR>Dw&4QS8a-@MG&mb|bvh(VYj z2BF?H;4zm`Z@`I|Ap!#yi02S%OYM)|JLYwt+V|VitE5wGK`r$9#KOz3f9?QhP%|y# ze?P7~;}y&4E(6*+2bYPRq&S93tp2{&5p5%$ck1KPzY?Lzt^WWO5VKFeSOaLV0Bx5D|U1Qz=uZX6hl4=uB*MN9F7>9^+0hS%&D)MXZrOk#=qpVGW-^W-7OFHiT z?J0V?)qV1oG|zYdbLsci1DRya!UL5}>W<4$Oh~QG7`A@v9#^016Fw@N>jdhR`iL5L zo8L>F0(pNkt!_WPM-D%I-~|g4GC=DT^D5;`_%h+*74X)RRRC%t2RM3l{Sm5L#}zYV zybgHn6+u^*_c;=bSKcQZC+1W^PojDRy(5NbmjGHL<%1ELD1v8?T8LbU5yFKHXYOO% zKKn~Dlxc1B#r@_HX2^zViGhMpxOf`yW%!VSipsP~!UaNTHP#)17}-+hnQ3m4vJh$@ z47C>v)KR;Gco7R}?21!rE2#efaB@2If!sR(05L=_563+s;!Dgmki=oX)9VgNUlCd& z)~5FX2xd2gDlFV#6aE4a3kaQO8Rx7`)r0MMad0p(*;6sV_Ve_b4ziFsn3h|l>RE24 z1;C34)uDqH#Kmi!+^IT-LC?1HE?H0%E#8cNb>1parIx5{f)j&KG#iDrZ7{V+2Ibf# z=7=|JSj=+`W-w*Oj~mPwra*J|-c$_11&C#+?OWPfw-iv9vJ%i}g9HjbwGfY_V@8#% z{(PaKtKVNJU|u(S$KA^W1^zlhESXP~6P6&gU<^V6h}DdxMH4TCBSbM7XAzix*%!ce zxV|7tA+=FS6euFxuR5yR1?(BmX?K-PSgt&)IEeICE&K8Njx|LWqrK;N_m(!pHW!Y# zm@bzN9lPfqU1DUTmJHUay_@YXGrGF{=`Ib9Gx_Z{pj~;)AA#ZZO#M=a>7HKrZbY#f%#HPY^Qr^H^#c*w-r>kM+c)=IDl!8?BO`Bs(K!9YYnZNwRz4rri$xw ztiUiUBSN1udk8yI3>Ym_1@Fz+C;bgX%6jrC}b8=}t1>ie(|T5jBV<`i^LW4TAnrBmUfURC}^ zT@mA`AxYBrP(rCy;bo)9D?WxuGSsT{v%kDRB8qQbqlThnRNqskoSWm&m(AN#!Ls?CRvZ4ia=DL~WVa!$on&Pvb)km%3Bm##TiKwM$ zzPqTi?yMJjyyG1hElW+g>SuTNqyS(U@8#w35!kg@;;tiR6tlnFL!=QlF^QnsW8)U&By^3ygGvMvH6Folctd z{{UhMm_00S)Ob)}CN^Q1d@*X+0Yo1INI=V#Q4%Zz0WxuvF}Z`?7mDS2pd2fY>q#pAne^Tb>HYKNDhUwtL8N>vy1&NJIw%R#l|^2N8) zzcD<@I5ah|yak)SDpKhwzK5ZFysC_`>96IzhoRr3c#zGAXBfJ^`gzRf+1nVe?r*jM ze=T>6b8bI>d0@GnM+4W_^%PK1`@Q@a?U)uY&F$T*zN;TVV)@2TFSvx|fhy%_ug~sk zvNTrP3Qiq!t|b&xMZLCbue=MezP0V!&QMYNl}a)9J>bw+`1G7K>+doOnZeEO`u6Do zY>(Xah3dkW$6NhkSWwocRdocHH$)q;2TV069XX5Q=&%Pi@}va(g=Yi_-!{Z650yuecdLuFxQ{l!oX-BYc) z!2$vSmbmlj)&~b~Y`_4V3A0U`>F*6e2cz}W(_g8%0A+?Cfi(?|wUdy~YzptI*EE0> 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 GKmXapdXj|z literal 0 HcmV?d00001 diff --git a/demo/images/timestamps/sydney_opera_house.jpg b/demo/images/timestamps/sydney_opera_house.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b595487ed5c187ce6f0430dd7e5505bbb262fc50 GIT binary patch literal 22755 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 literal 0 HcmV?d00001 diff --git a/demo/images/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg b/demo/images/timestamps/sydney_opera_house_no_tsoffset_but_gps_utc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..184331398738dbff246dd6f1d9e316c383ba9acc GIT binary patch literal 22641 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 literal 0 HcmV?d00001 diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index ae34c3e09..cc4c05b86 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -1,665 +1,665 @@ -import * as fs from 'fs'; -import { imageSize } from 'image-size'; -import { Config } from '../../../common/config/private/Config'; -import { SideCar } from '../../../common/entities/MediaDTO'; -import { FaceRegion, PhotoMetadata } from '../../../common/entities/PhotoDTO'; -import { VideoMetadata } from '../../../common/entities/VideoDTO'; -import { Logger } from '../../Logger'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import * as exifr from 'exifr'; -import { FfprobeData } from 'fluent-ffmpeg'; -import { FileHandle } from 'fs/promises'; -import * as util from 'node:util'; -import * as path from 'path'; -import { IptcParser } from 'ts-node-iptc'; -import { Utils } from '../../../common/Utils'; -import { FFmpegFactory } from '../FFmpegFactory'; -import { ExtensionDecorator } from '../extension/ExtensionDecorator'; - -const LOG_TAG = '[MetadataLoader]'; -const ffmpeg = FFmpegFactory.get(); - -export class MetadataLoader { - - @ExtensionDecorator(e => e.gallery.MetadataLoader.loadVideoMetadata) - public static async loadVideoMetadata(fullPath: string): Promise { - const metadata: VideoMetadata = { - size: { - width: 1, - height: 1, - }, - bitRate: 0, - duration: 0, - creationDate: 0, - fileSize: 0, - fps: 0, - }; - - try { - const stat = fs.statSync(fullPath); - metadata.fileSize = stat.size; - metadata.creationDate = stat.mtime.getTime(); //Default date is file system time of last modification - } catch (err) { - console.log(err); - // ignoring errors - } - try { - - - const data: FfprobeData = await util.promisify( - // wrap to arrow function otherwise 'this' is lost for ffprobe - (cb) => ffmpeg(fullPath).ffprobe(cb) - )(); - - try { - for (const stream of data.streams) { - if (stream.width) { - metadata.size.width = stream.width; - metadata.size.height = stream.height; - - if ( - Utils.isInt32(parseInt('' + stream.rotation, 10)) && - (Math.abs(parseInt('' + stream.rotation, 10)) / 90) % 2 === 1 - ) { - // noinspection JSSuspiciousNameCombination - metadata.size.width = stream.height; - // noinspection JSSuspiciousNameCombination - metadata.size.height = stream.width; - } - - if ( - Utils.isInt32(Math.floor(parseFloat(stream.duration) * 1000)) - ) { - metadata.duration = Math.floor( - parseFloat(stream.duration) * 1000 - ); - } - - if (Utils.isInt32(parseInt(stream.bit_rate, 10))) { - metadata.bitRate = parseInt(stream.bit_rate, 10) || null; - } - if (Utils.isInt32(parseInt(stream.avg_frame_rate, 10))) { - metadata.fps = parseInt(stream.avg_frame_rate, 10) || null; - } - metadata.creationDate = - Date.parse(stream.tags.creation_time) || - metadata.creationDate; - break; - } - } - - // For some filetypes (for instance Matroska), bitrate and duration are stored in - // the format section, not in the stream section. - - // Only use duration from container header if necessary (stream duration is usually more accurate) - if ( - metadata.duration === 0 && - data.format.duration !== undefined && - Utils.isInt32(Math.floor(data.format.duration * 1000)) - ) { - metadata.duration = Math.floor(data.format.duration * 1000); - } - - // Prefer bitrate from container header (includes video and audio) - if ( - data.format.bit_rate !== undefined && - Utils.isInt32(data.format.bit_rate) - ) { - metadata.bitRate = data.format.bit_rate; - } - - if ( - data.format.tags !== undefined && - typeof data.format.tags.creation_time === 'string' - ) { - metadata.creationDate = - Date.parse(data.format.tags.creation_time) || - metadata.creationDate; - } - - // eslint-disable-next-line no-empty - } catch (err) { - Logger.silly(LOG_TAG, 'Error loading metadata for : ' + fullPath); - 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); - } - return metadata; - } - - private static readonly EMPTY_METADATA: PhotoMetadata = { - size: { width: 0, height: 0 }, - creationDate: 0, - fileSize: 0, - }; - - @ExtensionDecorator(e => e.gallery.MetadataLoader.loadPhotoMetadata) - public static async loadPhotoMetadata(fullPath: string): Promise { - let fileHandle: FileHandle; - const metadata: PhotoMetadata = { - size: { width: 0, height: 0 }, - creationDate: 0, - fileSize: 0, - }; - const exifrOptions = { - tiff: true, - xmp: true, - icc: false, - jfif: false, //not needed and not supported for png - ihdr: true, - iptc: false, //exifr reads UTF8-encoded data wrongly, using IptcParser instead - exif: true, - gps: true, - reviveValues: false, //don't convert timestamps - translateValues: false, //don't translate orientation from numbers to strings etc. - mergeOutput: false //don't merge output, because things like Microsoft Rating (percent) and xmp.rating will be merged - }; - - //function to convert timestamp into milliseconds taking offset into account - const timestampToMS = (timestamp: string, offset: string) => { - if (!timestamp) { - return undefined; - } - //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'); - } else if (formattedTimestamp.indexOf("+") > 0) { //don't do anything - } else { //add offset - formattedTimestamp = formattedTimestamp + (offset ? offset : '+00:00'); - } - //parse into MS and return - return Date.parse(formattedTimestamp); - } - - //function to calculate offset from exif.exif.gpsTimeStamp or exif.gps.GPSDateStamp + exif.gps.GPSTimestamp - const getTimeOffsetByGPSStamp = (timestamp: string, gpsTimeStamp: string, gps: any) => { - let UTCTimestamp = gpsTimeStamp; - if (!UTCTimestamp && - gps && - 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(':'); - } - 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 = (timestampToMS(timestamp, '+00:00')- timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60; - 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 - } else { - return undefined; - } - } else { - return undefined; - } - } - - //Function to convert html code for special characters into their corresponding character (used in exif.photoshop-section) - const unescape = (tag: string) => { - return tag.replace(/&#([0-9]{1,3});/gi, function (match, numStr) { - return String.fromCharCode(parseInt(numStr, 10)); - }); - } - - try { - const data = Buffer.allocUnsafe(Config.Media.photoMetadataSize); - fileHandle = await fs.promises.open(fullPath, 'r'); - try { - await fileHandle.read(data, 0, Config.Media.photoMetadataSize, 0); - } catch (err) { - Logger.error(LOG_TAG, 'Error during reading photo: ' + fullPath); - console.error(err); - return MetadataLoader.EMPTY_METADATA; - } finally { - await fileHandle.close(); - } - try { - try { - const stat = fs.statSync(fullPath); - metadata.fileSize = stat.size; - metadata.creationDate = stat.mtime.getTime(); - } catch (err) { - // ignoring errors - } - try { - //read the actual image size, don't rely on tags for this - const info = imageSize(fullPath); - metadata.size = { width: info.width, height: info.height }; - } catch (e) { - //in case of failure, set dimensions to 0 so they may be read via tags - metadata.size = { width: 0, height: 0 }; - } - - - try { //Parse iptc data using the IptcParser, which works correctly for both UTF-8 and ASCII - const iptcData = IptcParser.parse(data); - if (iptcData.country_or_primary_location_name) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.country = - iptcData.country_or_primary_location_name - .replace(/\0/g, '') - .trim(); - } - if (iptcData.province_or_state) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.state = iptcData.province_or_state - .replace(/\0/g, '') - .trim(); - } - if (iptcData.city) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.city = iptcData.city - .replace(/\0/g, '') - .trim(); - } - if (iptcData.object_name) { - metadata.title = iptcData.object_name.replace(/\0/g, '').trim(); - } - if (iptcData.caption) { - metadata.caption = iptcData.caption.replace(/\0/g, '').trim(); - } - if (Array.isArray(iptcData.keywords)) { - metadata.keywords = iptcData.keywords; - } - - if (iptcData.date_time) { - metadata.creationDate = iptcData.date_time.getTime(); - } - } catch (err) { - // Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err); - } - - try { - let orientation = 1; //Orientation 1 is normal - const exif = await exifr.parse(data, exifrOptions); - //exif is structured in sections, we read the data by section - - //dc-section (subject is the only tag we want from dc) - if (exif.dc && - exif.dc.subject && - exif.dc.subject.length > 0) { - const subj = Array.isArray(exif.dc.subject) ? exif.dc.subject : [exif.dc.subject]; - if (metadata.keywords === undefined) { - metadata.keywords = []; - } - for (const kw of subj) { - if (metadata.keywords.indexOf(kw) === -1) { - metadata.keywords.push(kw); - } - } - } - - //ifd0 section - if (exif.ifd0) { - if (exif.ifd0.ImageWidth && metadata.size.width <= 0) { - metadata.size.width = exif.ifd0.ImageWidth; - } - if (exif.ifd0.ImageHeight && metadata.size.height <= 0) { - metadata.size.height = exif.ifd0.ImageHeight; - } - if (exif.ifd0.Orientation) { - orientation = parseInt( - exif.ifd0.Orientation as any, - 10 - ) as number; - } - if (exif.ifd0.Make && exif.ifd0.Make !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.make = '' + exif.ifd0.Make; - } - if (exif.ifd0.Model && exif.ifd0.Model !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.model = '' + exif.ifd0.Model; - } - //if (exif.ifd0.ModifyDate) {} //Deferred to the exif-section where the other timestamps are - } - - //exif section starting with the date sectino - if (exif.exif) { - //Preceedence of dates: exif.DateTimeOriginal, exif.CreateDate, ifd0.ModifyDate, ihdr["Creation Time"], xmp.MetadataDate, file system date - //Filesystem is the absolute last resort, and it's hard to write tests for, since file system dates are changed on e.g. git clone. - if (exif.exif.DateTimeOriginal) { - //DateTimeOriginal is when the camera shutter closed - if (exif.exif.OffsetTimeOriginal) { //OffsetTimeOriginal is the corresponding offset - metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, exif.exif.OffsetTimeOriginal); - metadata.creationDateOffset = exif.exif.OffsetTimeOriginal; - } else { - const alt_offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); - metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); - metadata.creationDateOffset = alt_offset; - } - } 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) - if (exif.exif.OffsetTimeDigitized) { //OffsetTimeDigitized is the corresponding offset - metadata.creationDate = timestampToMS(exif.exif.CreateDate, exif.exif.OffsetTimeDigitized); - metadata.creationDateOffset = exif.exif.OffsetTimeDigitized; - } else { - const alt_offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); - metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); - metadata.creationDateOffset = alt_offset; - } - } else if (exif.ifd0?.ModifyDate) { //using else if here, because DateTimeOriginal and CreatDate have preceedence - if (exif.exif.OffsetTime) { - //exif.Offsettime is the offset corresponding to ifd0.ModifyDate - metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, exif.exif?.OffsetTime); - metadata.creationDateOffset = exif.exif?.OffsetTime - } else { - const alt_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); - metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, alt_offset); - metadata.creationDateOffset = alt_offset; - } - } 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 || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); - metadata.creationDate = timestampToMS(exif.ihdr["Creation Time"], any_offset); - metadata.creationDateOffset = 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 || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); - metadata.creationDate = timestampToMS(exif.xmp.MetadataDate, any_offset); - metadata.creationDateOffset = any_offset; - } - if (exif.exif.LensModel && exif.exif.LensModel !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.lens = '' + exif.exif.LensModel; - } - if (Utils.isUInt32(exif.exif.ISO)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.ISO = parseInt('' + exif.exif.ISO, 10); - } - if (Utils.isFloat32(exif.exif.FocalLength)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.focalLength = parseFloat( - '' + exif.exif.FocalLength - ); - } - if (Utils.isFloat32(exif.exif.ExposureTime)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.exposure = parseFloat( - parseFloat('' + exif.exif.ExposureTime).toFixed(6) - ); - } - if (Utils.isFloat32(exif.exif.FNumber)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.fStop = parseFloat( - parseFloat('' + exif.exif.FNumber).toFixed(2) - ); - } - if (exif.exif.ExifImageWidth && metadata.size.width <= 0) { - metadata.size.width = exif.exif.ExifImageWidth; - } - if (exif.exif.ExifImageHeight && metadata.size.height <= 0) { - metadata.size.height = exif.exif.ExifImageHeight; - } - } - - //gps section - if (exif.gps) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.GPSData = metadata.positionData.GPSData || {}; - - if (Utils.isFloat32(exif.gps.longitude)) { - metadata.positionData.GPSData.longitude = parseFloat( - exif.gps.longitude.toFixed(6) - ); - } - if (Utils.isFloat32(exif.gps.latitude)) { - metadata.positionData.GPSData.latitude = parseFloat( - exif.gps.latitude.toFixed(6) - ); - } - - if (metadata.positionData) { - if (!metadata.positionData.GPSData || - Object.keys(metadata.positionData.GPSData).length === 0) { - metadata.positionData.GPSData = undefined; - metadata.positionData = undefined; - } - } - } - //photoshop section (sometimes has City, Country and State) - if (exif.photoshop) { - if (!metadata.positionData?.country && exif.photoshop.Country) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.country = unescape(exif.photoshop.Country); - } - if (!metadata.positionData?.state && exif.photoshop.State) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.state = unescape(exif.photoshop.State); - } - if (!metadata.positionData?.city && exif.photoshop.City) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.city = unescape(exif.photoshop.City); - } - } - - /////////////////////////////////////// - metadata.size.height = Math.max(metadata.size.height, 1); //ensure height dimension is positive - metadata.size.width = Math.max(metadata.size.width, 1); //ensure width dimension is positive - - //Before moving on to the XMP section (particularly the regions (mwg-rs)) - //we need to switch width and height for images that are rotated sideways - if (4 < orientation) { //Orientation is sideways (rotated 90% or 270%) - // noinspection JSSuspiciousNameCombination - const height = metadata.size.width; - // noinspection JSSuspiciousNameCombination - metadata.size.width = metadata.size.height; - metadata.size.height = height; - } - /////////////////////////////////////// - - //xmp section - if (exif.xmp && exif.xmp.Rating) { - metadata.rating = exif.xmp.Rating; - if (metadata.rating < 0) { - metadata.rating = 0; - } - } - //xmp."mwg-rs" section - if (Config.Faces.enabled && - exif["mwg-rs"] && - exif["mwg-rs"].Regions) { - const faces: FaceRegion[] = []; - const regionListVal = Array.isArray(exif["mwg-rs"].Regions.RegionList) ? exif["mwg-rs"].Regions.RegionList : [exif["mwg-rs"].Regions.RegionList]; - if (regionListVal) { - for (const regionRoot of regionListVal) { - let type; - let name; - let box; - const createFaceBox = ( - w: string, - h: string, - x: string, - y: string - ) => { - if (4 < orientation) { //roation is sidewards (90 or 270 degrees) - [x, y] = [y, x]; - [w, h] = [h, w]; - } - let swapX = 0; - let swapY = 0; - switch (orientation) { - case 2: //TOP RIGHT (Mirror horizontal): - case 6: //RIGHT TOP (Rotate 90 CW) - swapX = 1; - break; - case 3: // BOTTOM RIGHT (Rotate 180) - case 7: // RIGHT BOTTOM (Mirror horizontal and rotate 90 CW) - swapX = 1; - swapY = 1; - break; - case 4: //BOTTOM_LEFT (Mirror vertical) - case 8: //LEFT_BOTTOM (Rotate 270 CW) - swapY = 1; - break; - } - // converting ratio to px - return { - width: Math.round(parseFloat(w) * metadata.size.width), - height: Math.round(parseFloat(h) * metadata.size.height), - left: Math.round(Math.abs(parseFloat(x) - swapX) * metadata.size.width), - top: Math.round(Math.abs(parseFloat(y) - swapY) * metadata.size.height), - }; - }; - /* Adobe Lightroom based face region structure */ - if ( - regionRoot && - regionRoot['rdf:Description'] && - regionRoot['rdf:Description'] && - regionRoot['rdf:Description']['mwg-rs:Area'] - ) { - const region = regionRoot['rdf:Description']; - const regionBox = region['mwg-rs:Area'].attributes; - - name = region['mwg-rs:Name']; - type = region['mwg-rs:Type']; - box = createFaceBox( - regionBox['stArea:w'], - regionBox['stArea:h'], - regionBox['stArea:x'], - regionBox['stArea:y'] - ); - /* Load exiftool edited face region structure, see github issue #191 */ - } else if ( - regionRoot && - regionRoot.Name && - regionRoot.Type && - regionRoot.Area - ) { - const regionBox = regionRoot.Area; - name = regionRoot.Name; - type = regionRoot.Type; - box = createFaceBox( - regionBox.w, - regionBox.h, - regionBox.x, - regionBox.y - ); - } - - if (type !== 'Face' || !name) { - continue; - } - - // convert center base box to corner based box - box.left = Math.round(Math.max(0, box.left - box.width / 2)); - box.top = Math.round(Math.max(0, box.top - box.height / 2)); - - - faces.push({ name, box }); - } - } - if (faces.length > 0) { - metadata.faces = faces; // save faces - if (Config.Faces.keywordsToPersons) { - // remove faces from keywords - metadata.faces.forEach((f) => { - const index = metadata.keywords.indexOf(f.name); - if (index !== -1) { - metadata.keywords.splice(index, 1); - } - }); - } - } - } - } catch (err) { - // ignoring errors - } - - if (!metadata.creationDate) { - // creationDate can be negative, when it was created before epoch (1970) - 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.error(LOG_TAG, 'Error during reading photo: ' + fullPath); - console.error(err); - return MetadataLoader.EMPTY_METADATA; - } - } catch (err) { - Logger.error(LOG_TAG, 'Error during reading photo: ' + fullPath); - console.error(err); - return MetadataLoader.EMPTY_METADATA; - } - return metadata; - } -} +import * as fs from 'fs'; +import { imageSize } from 'image-size'; +import { Config } from '../../../common/config/private/Config'; +import { SideCar } from '../../../common/entities/MediaDTO'; +import { FaceRegion, PhotoMetadata } from '../../../common/entities/PhotoDTO'; +import { VideoMetadata } from '../../../common/entities/VideoDTO'; +import { Logger } from '../../Logger'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import * as exifr from 'exifr'; +import { FfprobeData } from 'fluent-ffmpeg'; +import { FileHandle } from 'fs/promises'; +import * as util from 'node:util'; +import * as path from 'path'; +import { IptcParser } from 'ts-node-iptc'; +import { Utils } from '../../../common/Utils'; +import { FFmpegFactory } from '../FFmpegFactory'; +import { ExtensionDecorator } from '../extension/ExtensionDecorator'; + +const LOG_TAG = '[MetadataLoader]'; +const ffmpeg = FFmpegFactory.get(); + +export class MetadataLoader { + + @ExtensionDecorator(e => e.gallery.MetadataLoader.loadVideoMetadata) + public static async loadVideoMetadata(fullPath: string): Promise { + const metadata: VideoMetadata = { + size: { + width: 1, + height: 1, + }, + bitRate: 0, + duration: 0, + creationDate: 0, + fileSize: 0, + fps: 0, + }; + + try { + const stat = fs.statSync(fullPath); + metadata.fileSize = stat.size; + metadata.creationDate = stat.mtime.getTime(); //Default date is file system time of last modification + } catch (err) { + console.log(err); + // ignoring errors + } + try { + + + const data: FfprobeData = await util.promisify( + // wrap to arrow function otherwise 'this' is lost for ffprobe + (cb) => ffmpeg(fullPath).ffprobe(cb) + )(); + + try { + for (const stream of data.streams) { + if (stream.width) { + metadata.size.width = stream.width; + metadata.size.height = stream.height; + + if ( + Utils.isInt32(parseInt('' + stream.rotation, 10)) && + (Math.abs(parseInt('' + stream.rotation, 10)) / 90) % 2 === 1 + ) { + // noinspection JSSuspiciousNameCombination + metadata.size.width = stream.height; + // noinspection JSSuspiciousNameCombination + metadata.size.height = stream.width; + } + + if ( + Utils.isInt32(Math.floor(parseFloat(stream.duration) * 1000)) + ) { + metadata.duration = Math.floor( + parseFloat(stream.duration) * 1000 + ); + } + + if (Utils.isInt32(parseInt(stream.bit_rate, 10))) { + metadata.bitRate = parseInt(stream.bit_rate, 10) || null; + } + if (Utils.isInt32(parseInt(stream.avg_frame_rate, 10))) { + metadata.fps = parseInt(stream.avg_frame_rate, 10) || null; + } + metadata.creationDate = + Date.parse(stream.tags.creation_time) || + metadata.creationDate; + break; + } + } + + // For some filetypes (for instance Matroska), bitrate and duration are stored in + // the format section, not in the stream section. + + // Only use duration from container header if necessary (stream duration is usually more accurate) + if ( + metadata.duration === 0 && + data.format.duration !== undefined && + Utils.isInt32(Math.floor(data.format.duration * 1000)) + ) { + metadata.duration = Math.floor(data.format.duration * 1000); + } + + // Prefer bitrate from container header (includes video and audio) + if ( + data.format.bit_rate !== undefined && + Utils.isInt32(data.format.bit_rate) + ) { + metadata.bitRate = data.format.bit_rate; + } + + if ( + data.format.tags !== undefined && + typeof data.format.tags.creation_time === 'string' + ) { + metadata.creationDate = + Date.parse(data.format.tags.creation_time) || + metadata.creationDate; + } + + // eslint-disable-next-line no-empty + } catch (err) { + Logger.silly(LOG_TAG, 'Error loading metadata for : ' + fullPath); + 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); + } + return metadata; + } + + private static readonly EMPTY_METADATA: PhotoMetadata = { + size: { width: 0, height: 0 }, + creationDate: 0, + fileSize: 0, + }; + + @ExtensionDecorator(e => e.gallery.MetadataLoader.loadPhotoMetadata) + public static async loadPhotoMetadata(fullPath: string): Promise { + let fileHandle: FileHandle; + const metadata: PhotoMetadata = { + size: { width: 0, height: 0 }, + creationDate: 0, + fileSize: 0, + }; + const exifrOptions = { + tiff: true, + xmp: true, + icc: false, + jfif: false, //not needed and not supported for png + ihdr: true, + iptc: false, //exifr reads UTF8-encoded data wrongly, using IptcParser instead + exif: true, + gps: true, + reviveValues: false, //don't convert timestamps + translateValues: false, //don't translate orientation from numbers to strings etc. + mergeOutput: false //don't merge output, because things like Microsoft Rating (percent) and xmp.rating will be merged + }; + + //function to convert timestamp into milliseconds taking offset into account + const timestampToMS = (timestamp: string, offset: string) => { + if (!timestamp) { + return undefined; + } + //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'); + } else if (formattedTimestamp.indexOf("+") > 0) { //don't do anything + } else { //add offset + formattedTimestamp = formattedTimestamp + (offset ? offset : '+00:00'); + } + //parse into MS and return + return Date.parse(formattedTimestamp); + } + + //function to calculate offset from exif.exif.gpsTimeStamp or exif.gps.GPSDateStamp + exif.gps.GPSTimestamp + const getTimeOffsetByGPSStamp = (timestamp: string, gpsTimeStamp: string, gps: any) => { + let UTCTimestamp = gpsTimeStamp; + if (!UTCTimestamp && + gps && + 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(':'); + } + 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 = (timestampToMS(timestamp, '+00:00')- timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60; + 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 + } else { + return undefined; + } + } else { + return undefined; + } + } + + //Function to convert html code for special characters into their corresponding character (used in exif.photoshop-section) + const unescape = (tag: string) => { + return tag.replace(/&#([0-9]{1,3});/gi, function (match, numStr) { + return String.fromCharCode(parseInt(numStr, 10)); + }); + } + + try { + const data = Buffer.allocUnsafe(Config.Media.photoMetadataSize); + fileHandle = await fs.promises.open(fullPath, 'r'); + try { + await fileHandle.read(data, 0, Config.Media.photoMetadataSize, 0); + } catch (err) { + Logger.error(LOG_TAG, 'Error during reading photo: ' + fullPath); + console.error(err); + return MetadataLoader.EMPTY_METADATA; + } finally { + await fileHandle.close(); + } + try { + try { + const stat = fs.statSync(fullPath); + metadata.fileSize = stat.size; + metadata.creationDate = stat.mtime.getTime(); + } catch (err) { + // ignoring errors + } + try { + //read the actual image size, don't rely on tags for this + const info = imageSize(fullPath); + metadata.size = { width: info.width, height: info.height }; + } catch (e) { + //in case of failure, set dimensions to 0 so they may be read via tags + metadata.size = { width: 0, height: 0 }; + } + + + try { //Parse iptc data using the IptcParser, which works correctly for both UTF-8 and ASCII + const iptcData = IptcParser.parse(data); + if (iptcData.country_or_primary_location_name) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.country = + iptcData.country_or_primary_location_name + .replace(/\0/g, '') + .trim(); + } + if (iptcData.province_or_state) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.state = iptcData.province_or_state + .replace(/\0/g, '') + .trim(); + } + if (iptcData.city) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.city = iptcData.city + .replace(/\0/g, '') + .trim(); + } + if (iptcData.object_name) { + metadata.title = iptcData.object_name.replace(/\0/g, '').trim(); + } + if (iptcData.caption) { + metadata.caption = iptcData.caption.replace(/\0/g, '').trim(); + } + if (Array.isArray(iptcData.keywords)) { + metadata.keywords = iptcData.keywords; + } + + if (iptcData.date_time) { + metadata.creationDate = iptcData.date_time.getTime(); + } + } catch (err) { + // Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err); + } + + try { + let orientation = 1; //Orientation 1 is normal + const exif = await exifr.parse(data, exifrOptions); + //exif is structured in sections, we read the data by section + + //dc-section (subject is the only tag we want from dc) + if (exif.dc && + exif.dc.subject && + exif.dc.subject.length > 0) { + const subj = Array.isArray(exif.dc.subject) ? exif.dc.subject : [exif.dc.subject]; + if (metadata.keywords === undefined) { + metadata.keywords = []; + } + for (const kw of subj) { + if (metadata.keywords.indexOf(kw) === -1) { + metadata.keywords.push(kw); + } + } + } + + //ifd0 section + if (exif.ifd0) { + if (exif.ifd0.ImageWidth && metadata.size.width <= 0) { + metadata.size.width = exif.ifd0.ImageWidth; + } + if (exif.ifd0.ImageHeight && metadata.size.height <= 0) { + metadata.size.height = exif.ifd0.ImageHeight; + } + if (exif.ifd0.Orientation) { + orientation = parseInt( + exif.ifd0.Orientation as any, + 10 + ) as number; + } + if (exif.ifd0.Make && exif.ifd0.Make !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.make = '' + exif.ifd0.Make; + } + if (exif.ifd0.Model && exif.ifd0.Model !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.model = '' + exif.ifd0.Model; + } + //if (exif.ifd0.ModifyDate) {} //Deferred to the exif-section where the other timestamps are + } + + //exif section starting with the date sectino + if (exif.exif) { + //Preceedence of dates: exif.DateTimeOriginal, exif.CreateDate, ifd0.ModifyDate, ihdr["Creation Time"], xmp.MetadataDate, file system date + //Filesystem is the absolute last resort, and it's hard to write tests for, since file system dates are changed on e.g. git clone. + if (exif.exif.DateTimeOriginal) { + //DateTimeOriginal is when the camera shutter closed + if (exif.exif.OffsetTimeOriginal) { //OffsetTimeOriginal is the corresponding offset + metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, exif.exif.OffsetTimeOriginal); + metadata.creationDateOffset = exif.exif.OffsetTimeOriginal; + } else { + const alt_offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); + metadata.creationDateOffset = alt_offset; + } + } 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) + if (exif.exif.OffsetTimeDigitized) { //OffsetTimeDigitized is the corresponding offset + metadata.creationDate = timestampToMS(exif.exif.CreateDate, exif.exif.OffsetTimeDigitized); + metadata.creationDateOffset = exif.exif.OffsetTimeDigitized; + } else { + const alt_offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, alt_offset); + metadata.creationDateOffset = alt_offset; + } + } else if (exif.ifd0?.ModifyDate) { //using else if here, because DateTimeOriginal and CreatDate have preceedence + if (exif.exif.OffsetTime) { + //exif.Offsettime is the offset corresponding to ifd0.ModifyDate + metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, exif.exif?.OffsetTime); + metadata.creationDateOffset = exif.exif?.OffsetTime + } else { + const alt_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, alt_offset); + metadata.creationDateOffset = alt_offset; + } + } 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 || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.ihdr["Creation Time"], any_offset); + metadata.creationDateOffset = 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 || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps); + metadata.creationDate = timestampToMS(exif.xmp.MetadataDate, any_offset); + metadata.creationDateOffset = any_offset; + } + if (exif.exif.LensModel && exif.exif.LensModel !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.lens = '' + exif.exif.LensModel; + } + if (Utils.isUInt32(exif.exif.ISO)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.ISO = parseInt('' + exif.exif.ISO, 10); + } + if (Utils.isFloat32(exif.exif.FocalLength)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.focalLength = parseFloat( + '' + exif.exif.FocalLength + ); + } + if (Utils.isFloat32(exif.exif.ExposureTime)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.exposure = parseFloat( + parseFloat('' + exif.exif.ExposureTime).toFixed(6) + ); + } + if (Utils.isFloat32(exif.exif.FNumber)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.fStop = parseFloat( + parseFloat('' + exif.exif.FNumber).toFixed(2) + ); + } + if (exif.exif.ExifImageWidth && metadata.size.width <= 0) { + metadata.size.width = exif.exif.ExifImageWidth; + } + if (exif.exif.ExifImageHeight && metadata.size.height <= 0) { + metadata.size.height = exif.exif.ExifImageHeight; + } + } + + //gps section + if (exif.gps) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.GPSData = metadata.positionData.GPSData || {}; + + if (Utils.isFloat32(exif.gps.longitude)) { + metadata.positionData.GPSData.longitude = parseFloat( + exif.gps.longitude.toFixed(6) + ); + } + if (Utils.isFloat32(exif.gps.latitude)) { + metadata.positionData.GPSData.latitude = parseFloat( + exif.gps.latitude.toFixed(6) + ); + } + + if (metadata.positionData) { + if (!metadata.positionData.GPSData || + Object.keys(metadata.positionData.GPSData).length === 0) { + metadata.positionData.GPSData = undefined; + metadata.positionData = undefined; + } + } + } + //photoshop section (sometimes has City, Country and State) + if (exif.photoshop) { + if (!metadata.positionData?.country && exif.photoshop.Country) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.country = unescape(exif.photoshop.Country); + } + if (!metadata.positionData?.state && exif.photoshop.State) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.state = unescape(exif.photoshop.State); + } + if (!metadata.positionData?.city && exif.photoshop.City) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.city = unescape(exif.photoshop.City); + } + } + + /////////////////////////////////////// + metadata.size.height = Math.max(metadata.size.height, 1); //ensure height dimension is positive + metadata.size.width = Math.max(metadata.size.width, 1); //ensure width dimension is positive + + //Before moving on to the XMP section (particularly the regions (mwg-rs)) + //we need to switch width and height for images that are rotated sideways + if (4 < orientation) { //Orientation is sideways (rotated 90% or 270%) + // noinspection JSSuspiciousNameCombination + const height = metadata.size.width; + // noinspection JSSuspiciousNameCombination + metadata.size.width = metadata.size.height; + metadata.size.height = height; + } + /////////////////////////////////////// + + //xmp section + if (exif.xmp && exif.xmp.Rating) { + metadata.rating = exif.xmp.Rating; + if (metadata.rating < 0) { + metadata.rating = 0; + } + } + //xmp."mwg-rs" section + if (Config.Faces.enabled && + exif["mwg-rs"] && + exif["mwg-rs"].Regions) { + const faces: FaceRegion[] = []; + const regionListVal = Array.isArray(exif["mwg-rs"].Regions.RegionList) ? exif["mwg-rs"].Regions.RegionList : [exif["mwg-rs"].Regions.RegionList]; + if (regionListVal) { + for (const regionRoot of regionListVal) { + let type; + let name; + let box; + const createFaceBox = ( + w: string, + h: string, + x: string, + y: string + ) => { + if (4 < orientation) { //roation is sidewards (90 or 270 degrees) + [x, y] = [y, x]; + [w, h] = [h, w]; + } + let swapX = 0; + let swapY = 0; + switch (orientation) { + case 2: //TOP RIGHT (Mirror horizontal): + case 6: //RIGHT TOP (Rotate 90 CW) + swapX = 1; + break; + case 3: // BOTTOM RIGHT (Rotate 180) + case 7: // RIGHT BOTTOM (Mirror horizontal and rotate 90 CW) + swapX = 1; + swapY = 1; + break; + case 4: //BOTTOM_LEFT (Mirror vertical) + case 8: //LEFT_BOTTOM (Rotate 270 CW) + swapY = 1; + break; + } + // converting ratio to px + return { + width: Math.round(parseFloat(w) * metadata.size.width), + height: Math.round(parseFloat(h) * metadata.size.height), + left: Math.round(Math.abs(parseFloat(x) - swapX) * metadata.size.width), + top: Math.round(Math.abs(parseFloat(y) - swapY) * metadata.size.height), + }; + }; + /* Adobe Lightroom based face region structure */ + if ( + regionRoot && + regionRoot['rdf:Description'] && + regionRoot['rdf:Description'] && + regionRoot['rdf:Description']['mwg-rs:Area'] + ) { + const region = regionRoot['rdf:Description']; + const regionBox = region['mwg-rs:Area'].attributes; + + name = region['mwg-rs:Name']; + type = region['mwg-rs:Type']; + box = createFaceBox( + regionBox['stArea:w'], + regionBox['stArea:h'], + regionBox['stArea:x'], + regionBox['stArea:y'] + ); + /* Load exiftool edited face region structure, see github issue #191 */ + } else if ( + regionRoot && + regionRoot.Name && + regionRoot.Type && + regionRoot.Area + ) { + const regionBox = regionRoot.Area; + name = regionRoot.Name; + type = regionRoot.Type; + box = createFaceBox( + regionBox.w, + regionBox.h, + regionBox.x, + regionBox.y + ); + } + + if (type !== 'Face' || !name) { + continue; + } + + // convert center base box to corner based box + box.left = Math.round(Math.max(0, box.left - box.width / 2)); + box.top = Math.round(Math.max(0, box.top - box.height / 2)); + + + faces.push({ name, box }); + } + } + if (faces.length > 0) { + metadata.faces = faces; // save faces + if (Config.Faces.keywordsToPersons) { + // remove faces from keywords + metadata.faces.forEach((f) => { + const index = metadata.keywords.indexOf(f.name); + if (index !== -1) { + metadata.keywords.splice(index, 1); + } + }); + } + } + } + } catch (err) { + // ignoring errors + } + + if (!metadata.creationDate) { + // creationDate can be negative, when it was created before epoch (1970) + 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.error(LOG_TAG, 'Error during reading photo: ' + fullPath); + console.error(err); + return MetadataLoader.EMPTY_METADATA; + } + } catch (err) { + Logger.error(LOG_TAG, 'Error during reading photo: ' + fullPath); + console.error(err); + return MetadataLoader.EMPTY_METADATA; + } + return metadata; + } +} diff --git a/src/backend/model/messenger/EmailMessenger.ts b/src/backend/model/messenger/EmailMessenger.ts index c412c55f8..c9afd8a98 100644 --- a/src/backend/model/messenger/EmailMessenger.ts +++ b/src/backend/model/messenger/EmailMessenger.ts @@ -5,6 +5,7 @@ import {MediaDTOWithThPath, Messenger} from './Messenger'; import {backendTexts} from '../../../common/BackendTexts'; import {DynamicConfig} from '../../../common/entities/DynamicConfig'; import {DefaultMessengers} from '../../../common/entities/job/JobDTO'; +import {Utils} from '../../../common/Utils'; export class EmailMessenger extends Messenger<{ emailTo: string, @@ -69,7 +70,7 @@ export class EmailMessenger extends Messenger<{ (media[i].metadata as PhotoMetadata).positionData?.country : ((media[i].metadata as PhotoMetadata).positionData?.city ? (media[i].metadata as PhotoMetadata).positionData?.city : ''); - const caption = (new Date(media[i].metadata.creationDate)).getFullYear() + (location ? ', ' + location : ''); + const caption = Utils.getFullYear(media[i].metadata.creationDate, media[i].metadata.creationDateOffset) + (location ? ', ' + location : ''); attachments.push({ filename: media[i].name, path: media[i].thumbnailPath, diff --git a/src/backend/routes/GalleryRouter.ts b/src/backend/routes/GalleryRouter.ts index 526da5a0f..d21832a44 100644 --- a/src/backend/routes/GalleryRouter.ts +++ b/src/backend/routes/GalleryRouter.ts @@ -1,286 +1,286 @@ -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {Express} from 'express'; -import {GalleryMWs} from '../middlewares/GalleryMWs'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {ThumbnailSourceType} from '../model/fileaccess/PhotoWorker'; -import {VersionMWs} from '../middlewares/VersionMWs'; -import {SupportedFormats} from '../../common/SupportedFormats'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; -import {MetaFileMWs} from '../middlewares/MetaFileMWs'; -import {Config} from '../../common/config/private/Config'; - -export class GalleryRouter { - public static route(app: Express): void { - this.addGetImageIcon(app); - this.addGetVideoIcon(app); - this.addGetResizedPhoto(app); - this.addGetBestFitVideo(app); - this.addGetVideoThumbnail(app); - this.addGetImage(app); - this.addGetVideo(app); - this.addGetMetaFile(app); - this.addGetBestFitMetaFile(app); - this.addRandom(app); - this.addDirectoryList(app); - this.addDirectoryZip(app); - - this.addSearch(app); - this.addAutoComplete(app); - } - - protected static addDirectoryList(app: Express): void { - app.get( - [Config.Server.apiPath + '/gallery/content/:directory(*)', Config.Server.apiPath + '/gallery/', Config.Server.apiPath + '/gallery//'], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('directory'), - AuthenticationMWs.authorisePath('directory', true), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.listDirectory, - ThumbnailGeneratorMWs.addThumbnailInformation, - GalleryMWs.cleanUpGalleryResults, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderResult - ); - } - - protected static addDirectoryZip(app: Express): void { - app.get( - [Config.Server.apiPath + '/gallery/zip/:directory(*)'], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('directory'), - AuthenticationMWs.authorisePath('directory', true), - - // specific part - ServerTimingMWs.addServerTiming, - GalleryMWs.zipDirectory - ); - } - - protected static addGetImage(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Photos.join('|') + - '))', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetVideo(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetBestFitVideo(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))/bestFit', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - GalleryMWs.loadBestFitVideo, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetMetaFile(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.MetaFiles.join('|') + - '))', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetBestFitMetaFile(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.MetaFiles.join('|') + - '))/bestFit', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - MetaFileMWs.compressGPX, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addRandom(app: Express): void { - app.get( - [Config.Server.apiPath + '/gallery/random/:searchQueryDTO'], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.authorise(UserRoles.Guest), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.getRandomImage, - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - /** - * Used for serving photo thumbnails and previews - * @param app - * @protected - */ - protected static addGetResizedPhoto(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Photos.join('|') + - '))/:size', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetVideoThumbnail(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))/:size?', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetVideoIcon(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))/icon', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetImageIcon(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Photos.join('|') + - '))/icon', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addSearch(app: Express): void { - app.get( - Config.Server.apiPath + '/search/:searchQueryDTO(*)', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.authorise(UserRoles.Guest), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.search, - ThumbnailGeneratorMWs.addThumbnailInformation, - GalleryMWs.cleanUpGalleryResults, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderResult - ); - } - - protected static addAutoComplete(app: Express): void { - app.get( - Config.Server.apiPath + '/autocomplete/:text(*)', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.authorise(UserRoles.Guest), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.autocomplete, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderResult - ); - } -} +import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; +import {Express} from 'express'; +import {GalleryMWs} from '../middlewares/GalleryMWs'; +import {RenderingMWs} from '../middlewares/RenderingMWs'; +import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; +import {UserRoles} from '../../common/entities/UserDTO'; +import {ThumbnailSourceType} from '../model/fileaccess/PhotoWorker'; +import {VersionMWs} from '../middlewares/VersionMWs'; +import {SupportedFormats} from '../../common/SupportedFormats'; +import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import {MetaFileMWs} from '../middlewares/MetaFileMWs'; +import {Config} from '../../common/config/private/Config'; + +export class GalleryRouter { + public static route(app: Express): void { + this.addGetImageIcon(app); + this.addGetVideoIcon(app); + this.addGetResizedPhoto(app); + this.addGetBestFitVideo(app); + this.addGetVideoThumbnail(app); + this.addGetImage(app); + this.addGetVideo(app); + this.addGetMetaFile(app); + this.addGetBestFitMetaFile(app); + this.addRandom(app); + this.addDirectoryList(app); + this.addDirectoryZip(app); + + this.addSearch(app); + this.addAutoComplete(app); + } + + protected static addDirectoryList(app: Express): void { + app.get( + [Config.Server.apiPath + '/gallery/content/:directory(*)', Config.Server.apiPath + '/gallery/', Config.Server.apiPath + '/gallery//'], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('directory'), + AuthenticationMWs.authorisePath('directory', true), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.listDirectory, + ThumbnailGeneratorMWs.addThumbnailInformation, + GalleryMWs.cleanUpGalleryResults, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderResult + ); + } + + protected static addDirectoryZip(app: Express): void { + app.get( + [Config.Server.apiPath + '/gallery/zip/:directory(*)'], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('directory'), + AuthenticationMWs.authorisePath('directory', true), + + // specific part + ServerTimingMWs.addServerTiming, + GalleryMWs.zipDirectory + ); + } + + protected static addGetImage(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetVideo(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetBestFitVideo(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/bestFit', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + GalleryMWs.loadBestFitVideo, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetMetaFile(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.MetaFiles.join('|') + + '))', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetBestFitMetaFile(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.MetaFiles.join('|') + + '))/bestFit', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + MetaFileMWs.compressGPX, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addRandom(app: Express): void { + app.get( + [Config.Server.apiPath + '/gallery/random/:searchQueryDTO'], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Guest), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.getRandomImage, + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + /** + * Used for serving photo thumbnails and previews + * @param app + * @protected + */ + protected static addGetResizedPhoto(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))/:size', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetVideoThumbnail(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/:size?', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetVideoIcon(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/icon', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetImageIcon(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))/icon', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addSearch(app: Express): void { + app.get( + Config.Server.apiPath + '/search/:searchQueryDTO(*)', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Guest), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.search, + ThumbnailGeneratorMWs.addThumbnailInformation, + GalleryMWs.cleanUpGalleryResults, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderResult + ); + } + + protected static addAutoComplete(app: Express): void { + app.get( + Config.Server.apiPath + '/autocomplete/:text(*)', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Guest), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.autocomplete, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderResult + ); + } +} diff --git a/src/common/Utils.ts b/src/common/Utils.ts index ceef88112..67bc435ac 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -110,6 +110,20 @@ export class Utils { return d; } + static getUTCFullYear(d: number | Date, offset: string) { + if (!(d instanceof Date)) { + d = new Date(d); + } + return new Date(new Date(d).toISOString().substring(0,19) + (offset ? offset : '')).getUTCFullYear(); + } + + static getFullYear(d: number | Date, offset: string) { + if (!(d instanceof Date)) { + d = new Date(d); + } + return new Date(new Date(d).toISOString().substring(0,19) + (offset ? offset : '')).getFullYear(); + } + static renderDataSize(size: number): string { const postFixes = ['B', 'KB', 'MB', 'GB', 'TB']; let index = 0; diff --git a/src/frontend/app/ui/duplicates/duplicates.component.html b/src/frontend/app/ui/duplicates/duplicates.component.html index e1044534e..f1168a734 100644 --- a/src/frontend/app/ui/duplicates/duplicates.component.html +++ b/src/frontend/app/ui/duplicates/duplicates.component.html @@ -24,7 +24,8 @@ {{media.metadata.fileSize | fileSize}}

diff --git a/src/frontend/app/ui/gallery/filter/filter.service.ts b/src/frontend/app/ui/gallery/filter/filter.service.ts index 220483e17..e6bbf2345 100644 --- a/src/frontend/app/ui/gallery/filter/filter.service.ts +++ b/src/frontend/app/ui/gallery/filter/filter.service.ts @@ -205,7 +205,7 @@ export class FilterService { const startMediaDate = new Date(floorDate(minDate)); prefiltered.media.forEach(m => { - const key = Math.floor((floorDate(m.metadata.creationDate) - startMediaDate.getTime()) / 1000 / usedDiv); + const key = Math.floor((floorDate(m.metadata.creationDate) - startMediaDate.getTime()) / 1000 / usedDiv); //TODO const getDate = (index: number) => { let d: Date; diff --git a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts index 1fe5309e5..b96576e73 100644 --- a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts +++ b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts @@ -496,7 +496,7 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { case LightBoxTitleTexts.persons: return m.metadata.faces?.map(f => f.name)?.join(', '); case LightBoxTitleTexts.date: - return this.datePipe.transform(m.metadata.creationDate, 'longDate'); + return this.datePipe.transform(m.metadata.creationDate, 'longDate', m.metadata.creationDateOffset); case LightBoxTitleTexts.location: return ( m.metadata.positionData?.city || diff --git a/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html b/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html index 3e1cbbfa7..341aa65ac 100644 --- a/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html +++ b/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html @@ -54,10 +54,10 @@

- {{ media.metadata.creationDate | date: (isThisYear() ? 'MMMM d' : 'longDate') : 'UTC' }} + {{ media.metadata.creationDate | date: (isThisYear() ? 'MMMM d' : 'longDate') : (media.metadata.creationDateOffset ? media.metadata.creationDateOffset : 'UTC') }}
-
{{ media.metadata.creationDate | date : 'EEEE, HH:mm:ss' : 'UTC' }}
+
{{ media.metadata.creationDate | date : (media.metadata.creationDateOffset ? 'EEEE, HH:mm:ss ZZZZZ' : 'EEEE, HH:mm:ss') : (media.metadata.creationDateOffset ? media.metadata.creationDateOffset : 'UTC') }}
diff --git a/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.ts b/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.ts index a1254d2a1..542cc42f9 100644 --- a/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.ts +++ b/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.ts @@ -148,7 +148,7 @@ export class InfoPanelLightboxComponent implements OnInit, OnChanges { isThisYear(): boolean { return ( new Date().getFullYear() === - new Date(this.media.metadata.creationDate).getUTCFullYear() + Utils.getUTCFullYear(this.media.metadata.creationDate, this.media.metadata.creationDateOffset) ); } diff --git a/src/frontend/app/ui/gallery/navigator/sorting.service.ts b/src/frontend/app/ui/gallery/navigator/sorting.service.ts index 2a3cca0d6..7e74d7375 100644 --- a/src/frontend/app/ui/gallery/navigator/sorting.service.ts +++ b/src/frontend/app/ui/gallery/navigator/sorting.service.ts @@ -184,7 +184,7 @@ export class GallerySortingService { private getGroupByNameFn(grouping: GroupingMethod) { switch (grouping.method) { case SortByTypes.Date: - return (m: MediaDTO) => this.datePipe.transform(m.metadata.creationDate, 'longDate', 'UTC'); + return (m: MediaDTO) => this.datePipe.transform(m.metadata.creationDate, 'longDate', m.metadata.creationDateOffset); case SortByTypes.Name: return (m: MediaDTO) => m.name.at(0).toUpperCase(); From e1e70fc13c47260e42c64cae7f66f0818696f210 Mon Sep 17 00:00:00 2001 From: grasdk Date: Fri, 16 Feb 2024 19:53:58 +0100 Subject: [PATCH 7/9] Reverted GalleryRouter, did not change it --- src/backend/routes/GalleryRouter.ts | 572 ++++++++++++++-------------- 1 file changed, 286 insertions(+), 286 deletions(-) diff --git a/src/backend/routes/GalleryRouter.ts b/src/backend/routes/GalleryRouter.ts index d21832a44..526da5a0f 100644 --- a/src/backend/routes/GalleryRouter.ts +++ b/src/backend/routes/GalleryRouter.ts @@ -1,286 +1,286 @@ -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {Express} from 'express'; -import {GalleryMWs} from '../middlewares/GalleryMWs'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {ThumbnailSourceType} from '../model/fileaccess/PhotoWorker'; -import {VersionMWs} from '../middlewares/VersionMWs'; -import {SupportedFormats} from '../../common/SupportedFormats'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; -import {MetaFileMWs} from '../middlewares/MetaFileMWs'; -import {Config} from '../../common/config/private/Config'; - -export class GalleryRouter { - public static route(app: Express): void { - this.addGetImageIcon(app); - this.addGetVideoIcon(app); - this.addGetResizedPhoto(app); - this.addGetBestFitVideo(app); - this.addGetVideoThumbnail(app); - this.addGetImage(app); - this.addGetVideo(app); - this.addGetMetaFile(app); - this.addGetBestFitMetaFile(app); - this.addRandom(app); - this.addDirectoryList(app); - this.addDirectoryZip(app); - - this.addSearch(app); - this.addAutoComplete(app); - } - - protected static addDirectoryList(app: Express): void { - app.get( - [Config.Server.apiPath + '/gallery/content/:directory(*)', Config.Server.apiPath + '/gallery/', Config.Server.apiPath + '/gallery//'], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('directory'), - AuthenticationMWs.authorisePath('directory', true), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.listDirectory, - ThumbnailGeneratorMWs.addThumbnailInformation, - GalleryMWs.cleanUpGalleryResults, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderResult - ); - } - - protected static addDirectoryZip(app: Express): void { - app.get( - [Config.Server.apiPath + '/gallery/zip/:directory(*)'], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('directory'), - AuthenticationMWs.authorisePath('directory', true), - - // specific part - ServerTimingMWs.addServerTiming, - GalleryMWs.zipDirectory - ); - } - - protected static addGetImage(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Photos.join('|') + - '))', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetVideo(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetBestFitVideo(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))/bestFit', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - GalleryMWs.loadBestFitVideo, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetMetaFile(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.MetaFiles.join('|') + - '))', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetBestFitMetaFile(app: Express): void { - app.get( - [ - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.MetaFiles.join('|') + - '))/bestFit', - ], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - MetaFileMWs.compressGPX, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addRandom(app: Express): void { - app.get( - [Config.Server.apiPath + '/gallery/random/:searchQueryDTO'], - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.authorise(UserRoles.Guest), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.getRandomImage, - GalleryMWs.loadFile, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - /** - * Used for serving photo thumbnails and previews - * @param app - * @protected - */ - protected static addGetResizedPhoto(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Photos.join('|') + - '))/:size', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetVideoThumbnail(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))/:size?', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetVideoIcon(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Videos.join('|') + - '))/icon', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addGetImageIcon(app: Express): void { - app.get( - Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + - SupportedFormats.Photos.join('|') + - '))/icon', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.normalizePathParam('mediaPath'), - AuthenticationMWs.authorisePath('mediaPath', false), - - // specific part - GalleryMWs.loadFile, - ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo), - ServerTimingMWs.addServerTiming, - RenderingMWs.renderFile - ); - } - - protected static addSearch(app: Express): void { - app.get( - Config.Server.apiPath + '/search/:searchQueryDTO(*)', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.authorise(UserRoles.Guest), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.search, - ThumbnailGeneratorMWs.addThumbnailInformation, - GalleryMWs.cleanUpGalleryResults, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderResult - ); - } - - protected static addAutoComplete(app: Express): void { - app.get( - Config.Server.apiPath + '/autocomplete/:text(*)', - // common part - AuthenticationMWs.authenticate, - AuthenticationMWs.authorise(UserRoles.Guest), - VersionMWs.injectGalleryVersion, - - // specific part - GalleryMWs.autocomplete, - ServerTimingMWs.addServerTiming, - RenderingMWs.renderResult - ); - } -} +import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; +import {Express} from 'express'; +import {GalleryMWs} from '../middlewares/GalleryMWs'; +import {RenderingMWs} from '../middlewares/RenderingMWs'; +import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; +import {UserRoles} from '../../common/entities/UserDTO'; +import {ThumbnailSourceType} from '../model/fileaccess/PhotoWorker'; +import {VersionMWs} from '../middlewares/VersionMWs'; +import {SupportedFormats} from '../../common/SupportedFormats'; +import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import {MetaFileMWs} from '../middlewares/MetaFileMWs'; +import {Config} from '../../common/config/private/Config'; + +export class GalleryRouter { + public static route(app: Express): void { + this.addGetImageIcon(app); + this.addGetVideoIcon(app); + this.addGetResizedPhoto(app); + this.addGetBestFitVideo(app); + this.addGetVideoThumbnail(app); + this.addGetImage(app); + this.addGetVideo(app); + this.addGetMetaFile(app); + this.addGetBestFitMetaFile(app); + this.addRandom(app); + this.addDirectoryList(app); + this.addDirectoryZip(app); + + this.addSearch(app); + this.addAutoComplete(app); + } + + protected static addDirectoryList(app: Express): void { + app.get( + [Config.Server.apiPath + '/gallery/content/:directory(*)', Config.Server.apiPath + '/gallery/', Config.Server.apiPath + '/gallery//'], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('directory'), + AuthenticationMWs.authorisePath('directory', true), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.listDirectory, + ThumbnailGeneratorMWs.addThumbnailInformation, + GalleryMWs.cleanUpGalleryResults, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderResult + ); + } + + protected static addDirectoryZip(app: Express): void { + app.get( + [Config.Server.apiPath + '/gallery/zip/:directory(*)'], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('directory'), + AuthenticationMWs.authorisePath('directory', true), + + // specific part + ServerTimingMWs.addServerTiming, + GalleryMWs.zipDirectory + ); + } + + protected static addGetImage(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetVideo(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetBestFitVideo(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/bestFit', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + GalleryMWs.loadBestFitVideo, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetMetaFile(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.MetaFiles.join('|') + + '))', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetBestFitMetaFile(app: Express): void { + app.get( + [ + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.MetaFiles.join('|') + + '))/bestFit', + ], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + MetaFileMWs.compressGPX, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addRandom(app: Express): void { + app.get( + [Config.Server.apiPath + '/gallery/random/:searchQueryDTO'], + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Guest), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.getRandomImage, + GalleryMWs.loadFile, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + /** + * Used for serving photo thumbnails and previews + * @param app + * @protected + */ + protected static addGetResizedPhoto(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))/:size', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetVideoThumbnail(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/:size?', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetVideoIcon(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/icon', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addGetImageIcon(app: Express): void { + app.get( + Config.Server.apiPath + '/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))/icon', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.normalizePathParam('mediaPath'), + AuthenticationMWs.authorisePath('mediaPath', false), + + // specific part + GalleryMWs.loadFile, + ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo), + ServerTimingMWs.addServerTiming, + RenderingMWs.renderFile + ); + } + + protected static addSearch(app: Express): void { + app.get( + Config.Server.apiPath + '/search/:searchQueryDTO(*)', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Guest), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.search, + ThumbnailGeneratorMWs.addThumbnailInformation, + GalleryMWs.cleanUpGalleryResults, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderResult + ); + } + + protected static addAutoComplete(app: Express): void { + app.get( + Config.Server.apiPath + '/autocomplete/:text(*)', + // common part + AuthenticationMWs.authenticate, + AuthenticationMWs.authorise(UserRoles.Guest), + VersionMWs.injectGalleryVersion, + + // specific part + GalleryMWs.autocomplete, + ServerTimingMWs.addServerTiming, + RenderingMWs.renderResult + ); + } +} From 6009ac649f994e761125f15565836e6b20a701a8 Mon Sep 17 00:00:00 2001 From: grasdk <115414609+grasdk@users.noreply.github.com> Date: Wed, 21 Feb 2024 23:06:24 +0100 Subject: [PATCH 8/9] effective storage of offset (#6) --- .gitignore | 3 ++- .../model/database/enitites/MediaEntity.ts | 8 ++++++- .../model/fileaccess/MetadataLoader.ts | 9 +------- src/common/Utils.ts | 23 +++++++++++++++++++ src/common/entities/ConentWrapper.ts | 13 +++++++++++ test/TestHelper.ts | 7 ++++++ 6 files changed, 53 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 2f9ed1b76..6efad7e38 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ test.* *.sublime-workspace .DS_Store /coverage/ -.nyc_output/ \ No newline at end of file +.nyc_output/ +.vscode* \ No newline at end of file diff --git a/src/backend/model/database/enitites/MediaEntity.ts b/src/backend/model/database/enitites/MediaEntity.ts index 57c131327..5eb172242 100644 --- a/src/backend/model/database/enitites/MediaEntity.ts +++ b/src/backend/model/database/enitites/MediaEntity.ts @@ -4,6 +4,7 @@ import {MediaDimension, MediaDTO, MediaMetadata,} from '../../../../common/entit import {PersonJunctionTable} from './PersonJunctionTable'; import {columnCharsetCS} from './EntityUtils'; import {CameraMetadata, FaceRegion, GPSMetadata, PositionMetaData,} from '../../../../common/entities/PhotoDTO'; +import { Utils } from '../../../../common/Utils'; export class MediaDimensionEntity implements MediaDimension { @Column('int') @@ -106,7 +107,12 @@ export class MediaMetadataEntity implements MediaMetadata { @Index() creationDate: number; - @Column('text') + @Column('smallint', { + transformer: { + from: (v) => Utils.getOffsetString(v), //from database repr. as smallint (minutes) to string (+/-HH:MM) + to: (v) => Utils.getOffsetMinutes(v), //from entiry repr. as string (+/-HH:MM) to smallint (minutes) + }, + }) creationDateOffset?: string; diff --git a/src/backend/model/fileaccess/MetadataLoader.ts b/src/backend/model/fileaccess/MetadataLoader.ts index cc4c05b86..908e01163 100644 --- a/src/backend/model/fileaccess/MetadataLoader.ts +++ b/src/backend/model/fileaccess/MetadataLoader.ts @@ -226,14 +226,7 @@ export class MetadataLoader { //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 = (timestampToMS(timestamp, '+00:00')- timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60; - 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 - } else { - return undefined; - } + return Utils.getOffsetString(offsetMinutes); } else { return undefined; } diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 67bc435ac..2e6ae0f43 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -124,6 +124,29 @@ export class Utils { return new Date(new Date(d).toISOString().substring(0,19) + (offset ? offset : '')).getFullYear(); } + static getOffsetString(offsetMinutes: number) { + 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 + } else { + return undefined; + } + } + + 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 + if (regex.test(offsetString)) { + let hhmm = offsetString.split(":"); + let hours = parseInt(hhmm[0]); + return hours < 0 ? ((hours*60) - parseInt(hhmm[1])) : ((hours*60) + parseInt(hhmm[1])); + } else { + return undefined; + } + } + static renderDataSize(size: number): string { const postFixes = ['B', 'KB', 'MB', 'GB', 'TB']; let index = 0; diff --git a/src/common/entities/ConentWrapper.ts b/src/common/entities/ConentWrapper.ts index f03215756..aedc17940 100644 --- a/src/common/entities/ConentWrapper.ts +++ b/src/common/entities/ConentWrapper.ts @@ -79,6 +79,11 @@ export class ContentWrapper { (media as MediaDTO).metadata['t'] = (media as MediaDTO).metadata.creationDate / 1000; // skip millies delete (media as MediaDTO).metadata.creationDate; + if ((media as MediaDTO).metadata.creationDateOffset) { + // @ts-ignore + (media as MediaDTO).metadata['o'] = Utils.getOffsetMinutes((media as MediaDTO).metadata.creationDateOffset); // offset in minutes + delete (media as MediaDTO).metadata.creationDateOffset; + } if ((media as PhotoDTO).metadata.rating) { // @ts-ignore @@ -338,6 +343,14 @@ export class ContentWrapper { delete (media as PhotoDTO).metadata['t']; } + // @ts-ignore + if (typeof (media as PhotoDTO).metadata['o'] !== 'undefined') { + // @ts-ignore + (media as PhotoDTO).metadata.creationDateOffset = Utils.getOffsetString((media as PhotoDTO).metadata['o']) ;//convert offset from minutes to String + // @ts-ignore + delete (media as PhotoDTO).metadata['o']; + } + // @ts-ignore if (typeof (media as PhotoDTO).metadata['r'] !== 'undefined') { // @ts-ignore diff --git a/test/TestHelper.ts b/test/TestHelper.ts index 0c11a5cc9..8c24e85c2 100644 --- a/test/TestHelper.ts +++ b/test/TestHelper.ts @@ -55,6 +55,7 @@ export class TestHelper { m.caption = null; m.size = sd; m.creationDate = 1656069387772; + m.creationDateOffset = "+02:00" m.fileSize = 123456789; // m.rating = 0; no rating by default @@ -101,6 +102,7 @@ export class TestHelper { m.positionData = pd; m.size = sd; m.creationDate = 1656069387772; + m.creationDateOffset = "-05:00"; m.fileSize = 123456789; // m.rating = 0; no rating by default @@ -177,6 +179,7 @@ export class TestHelper { p.metadata.positionData.GPSData.latitude = 10; p.metadata.positionData.GPSData.longitude = 10; p.metadata.creationDate = 1656069387772 - 1000; + p.metadata.creationDateOffset = "+00:00"; p.metadata.rating = 1; p.metadata.size.height = 1000; p.metadata.size.width = 1000; @@ -215,6 +218,7 @@ export class TestHelper { p.metadata.positionData.GPSData.latitude = -10; p.metadata.positionData.GPSData.longitude = -10; p.metadata.creationDate = 1656069387772 - 2000; + p.metadata.creationDateOffset = "+11:00"; p.metadata.rating = 2; p.metadata.size.height = 2000; p.metadata.size.width = 1000; @@ -247,6 +251,7 @@ export class TestHelper { p.metadata.positionData.GPSData.latitude = 10; p.metadata.positionData.GPSData.longitude = 15; p.metadata.creationDate = 1656069387772 - 3000; + p.metadata.creationDateOffset = "-03:45"; p.metadata.rating = 3; p.metadata.size.height = 1000; p.metadata.size.width = 2000; @@ -275,6 +280,7 @@ export class TestHelper { p.metadata.positionData.GPSData.latitude = 15; p.metadata.positionData.GPSData.longitude = 10; p.metadata.creationDate = 1656069387772 - 4000; + p.metadata.creationDateOffset = "+04:30"; p.metadata.size.height = 3000; p.metadata.size.width = 2000; @@ -394,6 +400,7 @@ export class TestHelper { positionData: pd, size: sd, creationDate: Date.now() + ++TestHelper.creationCounter, + creationDateOffset: "+01:00", fileSize: rndInt(10000), caption: rndStr(), rating: rndInt(5) as any, From e0d9bdf2b2ead483c4a8ebc0602804a95cd7481f Mon Sep 17 00:00:00 2001 From: grasdk <115414609+grasdk@users.noreply.github.com> Date: Wed, 21 Feb 2024 23:38:07 +0100 Subject: [PATCH 9/9] Feature/timestamp use2 (#7) * effective storage of offset * added comments to searchmanager.ts fixed linting error in utils --- src/backend/model/database/SearchManager.ts | 25 ++++++++++++++------- src/common/Utils.ts | 6 ++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/backend/model/database/SearchManager.ts b/src/backend/model/database/SearchManager.ts index 609dc776c..3037ddfe2 100644 --- a/src/backend/model/database/SearchManager.ts +++ b/src/backend/model/database/SearchManager.ts @@ -364,7 +364,7 @@ export class SearchManager { for (const sort of sortings) { switch (sort.method) { case SortByTypes.Date: - query.addOrderBy('media.metadata.creationDate', sort.ascending ? 'ASC' : 'DESC'); + query.addOrderBy('media.metadata.creationDate', sort.ascending ? 'ASC' : 'DESC'); //If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). If taken into account, it will alter the sort order. Probably should not be done. break; case SortByTypes.Rating: query.addOrderBy('media.metadata.rating', sort.ascending ? 'ASC' : 'DESC'); @@ -563,7 +563,12 @@ export class SearchManager { const textParam: { [key: string]: unknown } = {}; textParam['from' + queryId] = (query as FromDateSearch).value; q.where( - `media.metadata.creationDate ${relation} :from${queryId}`, + `media.metadata.creationDate ${relation} :from${queryId}`, //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). + //Example: -600 means in the database UTC-10:00. The time 20:00 in the evening in the UTC-10 timezone, is actually 06:00 the next morning + //in UTC+00:00. To make search take that into account, one can subtract the offset from the creationDate to "pretend" the photo is taken + //in UTC time. Subtracting -600 minutes (because it's the -10:00 timezone), corresponds to adding 10 hours to the photo's timestamp, thus + //bringing it into the next day as if it was taken at UTC+00:00. Similarly subtracting a positive timezone from a timestamp will "pretend" + //the photo is taken earlier in time (e.g. subtracting 300 from the UTC+05:00 timezone). textParam ); @@ -585,8 +590,8 @@ export class SearchManager { const textParam: { [key: string]: unknown } = {}; textParam['to' + queryId] = (query as ToDateSearch).value; q.where( - `media.metadata.creationDate ${relation} :to${queryId}`, - textParam + `media.metadata.creationDate ${relation} :to${queryId}`, //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. + textParam ); return q; @@ -790,15 +795,15 @@ export class SearchManager { if (tq.negate) { q.where( - `media.metadata.creationDate >= :to${queryId}`, + `media.metadata.creationDate >= :to${queryId}`, //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. textParam - ).orWhere(`media.metadata.creationDate < :from${queryId}`, + ).orWhere(`media.metadata.creationDate < :from${queryId}`, //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. textParam); } else { q.where( - `media.metadata.creationDate < :to${queryId}`, + `media.metadata.creationDate < :to${queryId}`, //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. textParam - ).andWhere(`media.metadata.creationDate >= :from${queryId}`, + ).andWhere(`media.metadata.creationDate >= :from${queryId}`, //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. textParam); } @@ -821,10 +826,12 @@ export class SearchManager { if (Config.Database.type === DatabaseType.sqlite) { if (tq.daysLength == 0) { q.where( + //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. `CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationEql} CAST(strftime('${duration}','now') AS INTEGER)` ); } else { q.where( + //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. `CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('${duration}','now') AS INTEGER)` )[whereFN](`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('${duration}','now','-:diff${queryId} day') AS INTEGER)`, textParam); @@ -832,10 +839,12 @@ export class SearchManager { } else { if (tq.daysLength == 0) { q.where( + //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. `CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationEql} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` ); } else { q.where( + //TODO: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. `CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationTop} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` )[whereFN](`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationBottom} CAST(DATE_FORMAT((DATE_ADD(curdate(), INTERVAL -:diff${queryId} DAY)),'${duration}') AS SIGNED)`, textParam); diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 2e6ae0f43..e0a475d6e 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -136,11 +136,11 @@ export class Utils { } 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. + 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 if (regex.test(offsetString)) { - let hhmm = offsetString.split(":"); - let hours = parseInt(hhmm[0]); + const hhmm = offsetString.split(":"); + const hours = parseInt(hhmm[0]); return hours < 0 ? ((hours*60) - parseInt(hhmm[1])) : ((hours*60) + parseInt(hhmm[1])); } else { return undefined;

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 afHe(70nV>}a3N0)*fO2C?*&o$&;QxG-M5DT literal 0 HcmV?d00001 diff --git a/demo/images/timestamps/newyear_sydney.jpg b/demo/images/timestamps/newyear_sydney.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8576e37f56e54013488e138305b897a28662ffce GIT binary patch literal 67879 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