1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-12-25 02:04:15 +02:00

Merge branch 'master' of https://github.com/grasdk/pigallery2 into bug/heic-dimensions

This commit is contained in:
gras 2024-04-09 16:35:54 +02:00
commit 333678f6e9
6 changed files with 194 additions and 67 deletions

1
package-lock.json generated
View File

@ -26,7 +26,6 @@
"nodemailer": "6.9.4",
"reflect-metadata": "0.1.13",
"sharp": "0.31.3",
"ts-node-iptc": "1.0.11",
"typeconfig": "2.2.11",
"typeorm": "0.3.12",
"xml2js": "0.6.2"

View File

@ -53,7 +53,6 @@
"nodemailer": "6.9.4",
"reflect-metadata": "0.1.13",
"sharp": "0.31.3",
"ts-node-iptc": "1.0.11",
"typeconfig": "2.2.11",
"typeorm": "0.3.12",
"xml2js": "0.6.2"

View File

@ -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 { IptcParser } from 'ts-node-iptc';
import { Utils } from '../../../common/Utils';
import { FFmpegFactory } from '../FFmpegFactory';
import { ExtensionDecorator } from '../extension/ExtensionDecorator';
@ -181,7 +180,7 @@ export class MetadataLoader {
icc: false,
jfif: false, //not needed and not supported for png
ihdr: true,
iptc: false, //exifr reads UTF8-encoded data wrongly, using IptcParser instead
iptc: true,
exif: true,
gps: true,
reviveValues: false, //don't convert timestamps
@ -228,46 +227,6 @@ export class MetadataLoader {
await fileHandle.close();
}
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 || {};
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 {
const exif = await exifr.parse(data, exifrOptions);
MetadataLoader.mapMetadata(metadata, exif);
@ -377,20 +336,35 @@ export class MetadataLoader {
}
}
}
if (exif.iptc &&
exif.iptc.Keywords &&
exif.iptc.Keywords.length > 0) {
const subj = Array.isArray(exif.iptc.Keywords) ? exif.iptc.Keywords : [exif.iptc.Keywords];
if (metadata.keywords === undefined) {
metadata.keywords = [];
}
for (let kw of subj) {
kw = Utils.asciiToUTF8(kw);
if (metadata.keywords.indexOf(kw) === -1) {
metadata.keywords.push(kw);
}
}
}
}
private static mapTitle(metadata: PhotoMetadata, exif: any) {
metadata.title = exif.dc?.title?.value || metadata.title || exif.photoshop?.Headline || exif.acdsee?.caption; //acdsee caption holds the title when data is saved by digikam. Used as last resort if iptc and dc do not contain the data
metadata.title = exif.dc?.title?.value || Utils.asciiToUTF8(exif.iptc?.ObjectName) || metadata.title || exif.photoshop?.Headline || exif.acdsee?.caption; //acdsee caption holds the title when data is saved by digikam. Used as last resort if iptc and dc do not contain the data
}
private static mapCaption(metadata: PhotoMetadata, exif: any) {
metadata.caption = exif.dc?.description?.value || metadata.caption || exif.ifd0?.ImageDescription || exif.exif?.UserComment?.value || exif.Iptc4xmpCore?.ExtDescrAccessibility?.value ||exif.acdsee?.notes;
metadata.caption = exif.dc?.description?.value || Utils.asciiToUTF8(exif.iptc?.Caption) || metadata.caption || exif.ifd0?.ImageDescription || exif.exif?.UserComment?.value || exif.Iptc4xmpCore?.ExtDescrAccessibility?.value ||exif.acdsee?.notes;
}
private static mapTimestampAndOffset(metadata: PhotoMetadata, exif: any) {
metadata.creationDate = Utils.timestampToMS(exif?.photoshop?.DateCreated, null) ||
Utils.timestampToMS(exif?.xmp?.CreateDate, null) ||
Utils.timestampToMS(exif?.xmp?.ModifyDate, null) ||
Utils.timestampToMS(Utils.toIsoTimestampString(exif?.iptc?.DateCreated, exif?.iptc?.TimeCreated), null) ||
metadata.creationDate;
metadata.creationDateOffset = Utils.timestampToOffsetString(exif?.photoshop?.DateCreated) ||
@ -497,24 +471,15 @@ export class MetadataLoader {
private static mapToponyms(metadata: PhotoMetadata, exif: any) {
//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));
});
}
//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.positionData = metadata.positionData || {};
metadata.positionData.country = Utils.asciiToUTF8(exif.iptc?.Country) || Utils.decodeHTMLChars(exif.photoshop?.Country);
metadata.positionData.state = Utils.asciiToUTF8(exif.iptc?.State) || Utils.decodeHTMLChars(exif.photoshop?.State);
metadata.positionData.city = Utils.asciiToUTF8(exif.iptc?.City) || Utils.decodeHTMLChars(exif.photoshop?.City);
if (metadata.positionData) {
Utils.removeNullOrEmptyObj(metadata.positionData);
if (Object.keys(metadata.positionData).length === 0) {
delete metadata.positionData;
}
}
}

118
src/common/HTMLCharCodes.ts Normal file
View File

@ -0,0 +1,118 @@
interface HTMLCharDictionary {
[key: string]: string;
}
export const HTMLChar: HTMLCharDictionary = {
""": "\"",
"&": "&",
"&lt;": "<",
"&gt;": ">",
"&nbsp;": " ",
"&iexcl;": "¡",
"&cent;": "¢",
"&pound;": "£",
"&curren;": "¤",
"&yen;": "¥",
"&brvbar;": "¦",
"&sect;": "§",
"&uml;": "¨",
"&copy;": "©",
"&reg;": "®",
"&trade;": "™",
"&ordf;": "ª",
"&laquo;": "«",
"&not;": "¬",
"&shy;": "­",
"&macr;": "¯",
"&deg;": "°",
"&plusmn;": "±",
"&sup2;": "²",
"&sup3;": "³",
"&acute;": "´",
"&micro;": "µ",
"&para;": "¶",
"&middot;": "·",
"&cedil;": "¸",
"&sup1;": "¹",
"&ordm;": "º",
"&raquo;": "»",
"&frac14;": "¼",
"&frac12;": "½",
"&frac34;": "¾",
"&iquest;": "¿",
"&times;": "×",
"&divide;": "÷",
"&ETH;": "Ð",
"&eth;": "ð",
"&THORN;": "Þ",
"&thorn;": "þ",
"&AElig;": "Æ",
"&aelig;": "æ",
"&OElig;": "Œ",
"&oelig;": "œ",
"&Aring;": "Å",
"&Oslash;": "Ø",
"&Ccedil;": "Ç",
"&ccedil;": "ç",
"&szlig;": "ß",
"&Ntilde;": "Ñ",
"&ntilde;": "ñ",
"&Aacute;": "Á",
"&Agrave;": "À",
"&Acirc;": "Â",
"&Auml;": "Ä",
"&Atilde;": "Ã",
"&aacute;": "á",
"&agrave;": "à",
"&acirc;": "â",
"&auml;": "ä",
"&atilde;": "ã",
"&aring;": "å",
"&Eacute;": "É",
"&Egrave;": "È",
"&Ecirc;": "Ê",
"&Euml;": "Ë",
"&Etilde;": "Ẽ",
"&eacute;": "é",
"&egrave;": "è",
"&ecirc;": "ê",
"&euml;": "ë",
"&Iacute;": "Í",
"&Igrave;": "Ì",
"&Icirc;": "Î",
"&Iuml;": "Ï",
"&Itilde;": "Ĩ",
"&iacute;": "í",
"&igrave;": "ì",
"&icirc;": "î",
"&iuml;": "ï",
"&itilde;": "ĩ",
"&Oacute;": "Ó",
"&Ograve;": "Ò",
"&Ocirc;": "Ô",
"&Ouml;": "Ö",
"&Otilde;": "Õ",
"&oacute;": "ó",
"&ograve;": "ò",
"&ocirc;": "ô",
"&ouml;": "ö",
"&otilde;": "õ",
"&Uacute;": "Ú",
"&Ugrave;": "Ù",
"&Ucirc;": "Û",
"&Uuml;": "Ü",
"&Utilde;": "Ũ",
"&Uring;": "Ů",
"&uacute;": "ú",
"&ugrave;": "ù",
"&ucirc;": "û",
"&uuml;": "ü",
"&utilde;": "ũ",
"&uring;": "ů",
"&Yacute;": "Ý",
"&Ycirc;": "Ŷ",
"&Yuml;": "Ÿ",
"&yacute;": "ý",
"&ycirc;": "ŷ",
"&yuml;": "ÿ"
};

View File

@ -1,3 +1,5 @@
import { HTMLChar } from './HTMLCharCodes';
export class Utils {
static GUID(): string {
const s4 = (): string =>
@ -97,6 +99,25 @@ export class Utils {
return d.getUTCFullYear() + '-' + d.getUTCMonth() + '-' + d.getUTCDate();
}
static toIsoTimestampString(YYYYMMDD: string, hhmmss: string): string {
if (YYYYMMDD && hhmmss) {
// Regular expression to match YYYYMMDD format
const dateRegex = /^(\d{4})(\d{2})(\d{2})$/;
// Regular expression to match hhmmss+/-ohom format
const timeRegex = /^(\d{2})(\d{2})(\d{2})([+-]\d{2})?(\d{2})?$/;
const [, year, month, day] = YYYYMMDD.match(dateRegex);
const [, hour, minute, second, offsetHour, offsetMinute] = hhmmss.match(timeRegex);
const isoTimestamp = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
if (offsetHour && offsetMinute) {
return isoTimestamp + `${offsetHour}:${offsetMinute}`;
} else {
return isoTimestamp;
}
} else {
return undefined;
}
}
static makeUTCMidnight(d: number | Date) {
if (!(d instanceof Date)) {
@ -125,7 +146,7 @@ export class Utils {
}
//function to convert timestamp into milliseconds taking offset into account
static timestampToMS(timestamp: string, offset: string) {
static timestampToMS(timestamp: string, offset: string): number {
if (!timestamp) {
return undefined;
}
@ -371,6 +392,31 @@ export class Utils {
return curr;
}
public static asciiToUTF8(text: string): string {
if (text) {
return Buffer.from(text, 'ascii').toString('utf-8');
} else {
return text;
}
}
public static decodeHTMLChars(text: string): string {
if (text) {
const newtext = text.replace(/&#([0-9]{1,3});/gi, function (match, numStr) {
return String.fromCharCode(parseInt(numStr, 10));
});
return newtext.replace(/&[^;]+;/g, function (match) {
const char = HTMLChar[match];
return char ? char : match;
});
} else {
return text;
}
}
public static isUInt32(value: number, max = 4294967295): boolean {
value = parseInt('' + value, 10);
return !isNaN(value) && value >= 0 && value <= max;

View File

@ -41,9 +41,9 @@
"latitude": 37.871093,
"longitude": -122.25678
},
"city": "test city őúéáűóöí-.,)(=",
"city": "test city őúéáűóöí-.,)(=/%!+\"'",
"country": "test country őúéáűóöí-.,)(=/%!+\"'",
"state": "test state őúéáűóöí-.,)("
"state": "test state őúéáűóöí-.,)(=/%!+\"'"
},
"rating": 3,
"size": {