1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-02 03:37:54 +02:00

Feature/specialchars (#2)

* switched from ExifReader to exifr and updated tests to fix issue 794
This commit is contained in:
grasdk 2024-02-05 22:35:25 +01:00 committed by GitHub
parent 649e7a9a7e
commit 1b579269ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 221 additions and 55 deletions

8
.gitignore vendored
View File

@ -34,3 +34,11 @@ test.*
*.sublime-project
*.sublime-workspace
.DS_Store
test/folder-reset.js
test/folder-reset.js.map
test/TestHelper.js
test/TestHelper.js.map
test/frontend/translation.spec.js
test/frontend/translation.spec.js.map
/coverage/
.nyc_output/

View File

@ -13,7 +13,8 @@ In general, I'm happy to merge PRs, but I recommend filling a ticket and ask fir
1. Download the source files
2. install dependencies `npm install`
3. Build client `npm run run-dev`
* This will build the client with english localization and will keep building if you change the source files
* This will build the client with english localization and will keep building if you change the source files.
* Note: This process does not exit, so you need another terminal to run the next step.
4. Build the backend `npm run build-backend`
* This runs `tsc` that transpiles `.ts` files to `.js` so node can run them.
* To rebuild on change run `tsc -w`

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -1,23 +1,22 @@
import {VideoMetadata} from '../../../common/entities/VideoDTO';
import {FaceRegion, PhotoMetadata} from '../../../common/entities/PhotoDTO';
import {SideCar} from '../../../common/entities/MediaDTO';
import {Config} from '../../../common/config/private/Config';
import {Logger} from '../../Logger';
import * as fs from 'fs';
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 ExifReader from 'exifreader';
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 { ExifParserFactory, OrientationTypes } from 'ts-exif-parser';
import { IptcParser } from 'ts-node-iptc';
import {FFmpegFactory} from '../FFmpegFactory';
import {FfprobeData} from 'fluent-ffmpeg';
import { Utils } from '../../../common/Utils';
import { FFmpegFactory } from '../FFmpegFactory';
import { ExtensionDecorator } from '../extension/ExtensionDecorator';
import * as exifr from 'exifr';
import * as path from 'path';
import * as util from 'node:util';
import {FileHandle} from 'fs/promises';
const LOG_TAG = '[MetadataLoader]';
const ffmpeg = FFmpegFactory.get();
@ -358,33 +357,44 @@ export class MetadataLoader {
}
try {
// TODO: clean up the three different exif readers,
// and keep the minimum amount only
const exif: ExifReader.Tags & ExifReader.XmpTags & ExifReader.IccTags = ExifReader.load(data);
if (exif.Rating) {
metadata.rating = parseInt(exif.Rating.value as string, 10) as 0 | 1 | 2 | 3 | 4 | 5;
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;
}
}
if (
exif.subject &&
exif.subject.value &&
exif.subject.value.length > 0
) {
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 exif.subject.value as ExifReader.XmpTag[]) {
if (metadata.keywords.indexOf(kw.description) === -1) {
metadata.keywords.push(kw.description);
for (const kw of subj) {
if (metadata.keywords.indexOf(kw) === -1) {
metadata.keywords.push(kw);
}
}
}
let orientation = OrientationTypes.TOP_LEFT;
if (exif.Orientation) {
if (exif.ifd0 &&
exif.ifd0.Orientation) {
orientation = parseInt(
exif.Orientation.value as any,
exif.ifd0.Orientation as any,
10
) as number;
}
@ -396,9 +406,11 @@ export class MetadataLoader {
metadata.size.height = height;
}
if (Config.Faces.enabled) {
if (Config.Faces.enabled &&
exif["mwg-rs"] &&
exif["mwg-rs"].Regions) {
const faces: FaceRegion[] = [];
const regionListVal = ((exif.Regions?.value as any)?.RegionList)?.value;
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;
@ -442,16 +454,16 @@ export class MetadataLoader {
/* Adobe Lightroom based face region structure */
if (
regionRoot.value &&
regionRoot.value['rdf:Description'] &&
regionRoot.value['rdf:Description'].value &&
regionRoot.value['rdf:Description'].value['mwg-rs:Area']
regionRoot &&
regionRoot['rdf:Description'] &&
regionRoot['rdf:Description'] &&
regionRoot['rdf:Description']['mwg-rs:Area']
) {
const region = regionRoot.value['rdf:Description'];
const regionBox = region.value['mwg-rs:Area'].attributes;
const region = regionRoot['rdf:Description'];
const regionBox = region['mwg-rs:Area'].attributes;
name = region.attributes['mwg-rs:Name'];
type = region.attributes['mwg-rs:Type'];
name = region['mwg-rs:Name'];
type = region['mwg-rs:Type'];
box = createFaceBox(
regionBox['stArea:w'],
regionBox['stArea:h'],
@ -460,18 +472,19 @@ export class MetadataLoader {
);
/* Load exiftool edited face region structure, see github issue #191 */
} else if (
regionRoot.Area &&
regionRoot &&
regionRoot.Name &&
regionRoot.Type
regionRoot.Type &&
regionRoot.Area
) {
const regionBox = regionRoot.Area.value;
name = regionRoot.Name.value;
type = regionRoot.Type.value;
const regionBox = regionRoot.Area;
name = regionRoot.Name;
type = regionRoot.Type;
box = createFaceBox(
regionBox.w.value,
regionBox.h.value,
regionBox.x.value,
regionBox.y.value
regionBox.w,
regionBox.h,
regionBox.x,
regionBox.y
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -0,0 +1,49 @@
{
"size": {
"width": 1920,
"height": 1080
},
"creationDate": 1706616000000,
"fileSize": 111378,
"positionData": {
"GPSData": {
"longitude": 14.162922,
"latitude": 57.780696
},
"country": "Sverige",
"state": "Jönköping",
"city": "Jönköping"
},
"keywords": [
],
"rating": 0,
"faces": [
{
"box": {
"width": 206,
"height": 257,
"left": 566,
"top": 144
},
"name": "æÆøØåÅéÉüÜäÄöÖïÏñÑ"
},
{
"name": "abcdefghijklmnopqrstuvwxyz",
"box": {
"width": 212,
"height": 265,
"left": 866,
"top": 144
}
},
{
"name": "abcdefghijklmnopqrstuvwxyz",
"box": {
"width": 212,
"height": 265,
"left": 1162,
"top": 150
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -0,0 +1,49 @@
{
"size": {
"width": 1920,
"height": 1080
},
"creationDate": 1706616000000,
"fileSize": 111050,
"positionData": {
"GPSData": {
"longitude": 14.162922,
"latitude": 57.780696
},
"country": "Sverige",
"state": "Jönköping",
"city": "Jönköping"
},
"keywords": [
],
"rating": 0,
"faces": [
{
"box": {
"width": 206,
"height": 257,
"left": 566,
"top": 144
},
"name": "æÆøØåÅéÉüÜäÄöÖïÏñÑ"
},
{
"name": "abcdefghijklmnopqrstuvwxyz",
"box": {
"width": 212,
"height": 265,
"left": 866,
"top": 144
}
},
{
"name": "abcdefghijklmnopqrstuvwxyz",
"box": {
"width": 212,
"height": 265,
"left": 1162,
"top": 150
}
}
]
}

View File

@ -0,0 +1,31 @@
{
"size": {
"width": 26,
"height": 26
},
"creationDate": 1707167247786,
"fileSize": 5758,
"keywords": [
],
"faces": [
{
"name": "raspberry",
"box": {
"width": 21,
"height": 18,
"left": 3,
"top": 8
}
},
{
"name": "leaf",
"box": {
"width": 9,
"height": 7,
"left": 14,
"top": 1
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -24,12 +24,11 @@ describe('DiskMangerWorker', () => {
ProjectPath.ImageFolder = path.join(__dirname, '/../../../assets');
const dir = await DiskManager.scanDirectory('/');
// should match the number of media (photo/video) files in the assets folder
expect(dir.media.length).to.be.equals(11);
expect(dir.media.length).to.be.equals(14);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const expected = require(path.join(__dirname, '/../../../assets/test image öüóőúéáű-.,.json'));
const i = dir.media.findIndex(m => m.name === 'test image öüóőúéáű-.,.jpg');
expect(Utils.clone(dir.media[i].name)).to.be.deep.equal('test image öüóőúéáű-.,.jpg');
expect(Utils.clone(dir.media[i].metadata)).to.be.deep.equal(expected);
});
});

View File

@ -58,6 +58,22 @@ describe('MetadataLoader', () => {
const expected = require(path.join(__dirname, '/../../../assets/old_photo.json'));
expect(Utils.clone(data)).to.be.deep.equal(expected);
});
it('should load jpg with special characters', async () => {
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/Chars.jpg'));
const expected = require(path.join(__dirname, '/../../../assets/Chars.json'));
expect(Utils.clone(data)).to.be.deep.equal(expected);
});
it('should load jpg with special characters saved by exiftool', async () => {
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/Chars_exiftool.jpg'));
const expected = require(path.join(__dirname, '/../../../assets/Chars_exiftool.json'));
expect(Utils.clone(data)).to.be.deep.equal(expected);
});
it('should load png with keyword and dates', async () => {
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/png_with_keyword_and_dates.png'));
const expected = require(path.join(__dirname, '/../../../assets/png_with_keyword_and_dates.json'));
expect(Utils.clone(data)).to.be.deep.equal(expected);
});
describe('should load jpg with proper height and orientation', () => {
it('jpg 1', async () => {