chore(core): simplify keyword/people handling

In the future metadata mappings could be configurable.
We should then look at a generic system for all metadata
for example including captions.

Until then let's keep it simple and consistent with the other fields.
pull/241/head
Romain 3 years ago
parent 19737e7c4a
commit 2330540ba5

@ -469,15 +469,6 @@ exports.get = (args) => {
opts.logFile = changeExtension(opts.databaseFile, '.log')
}
// Default keyword fields
if (!opts.keywordFields) {
opts.keywordFields = ['XMP.Subject', 'IPTC.Keywords', 'Picasa:Keywords']
}
// Default people fields
if (!opts.peopleFields) {
opts.peopleFields = ['XMP.PersonInImage']
}
// Better to work with absolute paths
opts.input = path.resolve(opts.input)
opts.output = path.resolve(opts.output)

@ -5,6 +5,7 @@ This is based on parsing "provider data" such as Exiftool or Picasa
--------------------------------------------------------------------------------
*/
const _ = require('lodash')
const moment = require('moment')
const path = require('path')
@ -25,8 +26,8 @@ class Metadata {
// standardise metadata
this.date = getDate(exiftool)
this.caption = caption(exiftool)
this.keywords = keywords(exiftool, picasa, opts)
this.people = people(exiftool, opts)
this.keywords = keywords(exiftool, picasa)
this.people = people(exiftool)
this.video = video(exiftool)
this.animated = animated(exiftool)
this.rating = rating(exiftool)
@ -88,34 +89,17 @@ function caption (exif, picasa) {
tagValue(exif, 'QuickTime', 'Title')
}
function keywords (exif, picasa, opts) {
if (opts && opts.keywordFields) {
return findTags(opts.keywordFields, exif, picasa)
}
return []
function keywords (exif, picasa) {
const sources = [
tagValue(exif, 'IPTC', 'Keywords'),
tagValue(exif, 'XMP', 'Subject'),
picasaValue(picasa, 'keywords')
]
return _.chain(sources).flatMap(makeArray).uniq().value()
}
function people (exif, opts) {
if (opts && opts.peopleFields) {
return findTags(opts.peopleFields, exif)
}
return []
}
function findTags (fields, exif, picasa) {
var words = new Set()
fields.forEach(field => {
const fieldComponents = field.split(/[.:]/, 2)
let values = []
if (/Picasa/i.test(fieldComponents[0])) {
const valuesString = picasaValue(picasa, fieldComponents[1])
if (valuesString) { values = valuesString.split(',') } // Picasa comma-separated
} else {
values = tagValue(exif, ...fieldComponents)
}
if (values) makeArray(values).forEach(word => words.add(word)) // value could be string or array
})
return [...words]
function people (exif) {
return tagValue(exif, 'XMP', 'PersonInImage') || []
}
function video (exif) {
@ -148,7 +132,8 @@ function picasaValue (picasa, name) {
}
function makeArray (value) {
return Array.isArray(value) ? value : [value]
if (!value) return []
return Array.isArray(value) ? value : value.split(',')
}
function dimensions (exif) {

@ -57,42 +57,36 @@ describe('AlbumPattern', function () {
})
})
describe('keywords', () => {
const opts = {
keywordFields: ['IPTC:Keywords']
}
it('can return a single keyword', () => {
const func = pattern.create('%keywords', opts)
const func = pattern.create('%keywords', {})
const file = fixtures.photo({
keywords: ['beach']
}, opts)
}, {})
should(func(file)).eql(['beach'])
})
it('can return multiple keyword', () => {
const func = pattern.create('%keywords', opts)
const func = pattern.create('%keywords', {})
const file = fixtures.photo({
keywords: ['beach', 'sunset']
}, opts)
}, {})
should(func(file)).eql(['beach', 'sunset'])
})
it('can use plain text around the keywords', () => {
const func = pattern.create('Tags/%keywords', opts)
const func = pattern.create('Tags/%keywords', {})
const file = fixtures.photo({
keywords: ['beach', 'sunset']
}, opts)
}, {})
should(func(file)).eql(['Tags/beach', 'Tags/sunset'])
})
it('can find keywords in a specified tag', () => {
const func = pattern.create('%keywords')
const file = fixtures.photo({
subjects: ['sunny beach']
}, {
keywordFields: ['XMP.Subject']
})
}, {})
should(func(file)).eql(['sunny beach'])
})
it('can deal with keyword includes and excludes', () => {
const opts = {
keywordFields: ['XMP.Subject'],
includeKeywords: ['sunny beach', 'sandy shore', 'waves'],
excludeKeywords: ['sandy shore']
}
@ -113,8 +107,6 @@ describe('AlbumPattern', function () {
const func = pattern.create('%people')
const file = fixtures.photo({
people: ['john doe']
}, {
peopleFields: ['XMP.PersonInImage']
})
should(func(file)).eql(['john doe'])
})
@ -155,16 +147,13 @@ describe('AlbumPattern', function () {
})
})
describe('Complex patterns', () => {
const opts = {
keywordFields: ['IPTC:Keywords']
}
it('can mix several tokens inside a complex pattern', () => {
const func = pattern.create('{YYYY}/%path/%keywords', opts)
const func = pattern.create('{YYYY}/%path/%keywords', {})
const file = fixtures.photo({
path: 'Holidays/IMG_0001.jpg',
date: '2016:07:14 12:07:41',
keywords: ['beach', 'sunset']
}, opts)
}, {})
should(func(file)).eql(['2016/Holidays/beach', '2016/Holidays/sunset'])
})
})

@ -151,13 +151,6 @@ describe('Metadata', function () {
})
describe('keywords', function () {
const picasaOpts = {
keywordFields: ['Picasa:keywords']
}
const iptcOpts = {
keywordFields: ['IPTC:Keywords']
}
it('defaults to an empty array', function () {
const exiftool = fixtures.exiftool()
const meta = new Metadata(exiftool)
@ -168,7 +161,7 @@ describe('Metadata', function () {
// a single keyword is returned as a string by <exiftool>
const exiftool = fixtures.exiftool()
exiftool.IPTC['Keywords'] = 'beach'
const meta = new Metadata(exiftool, {}, iptcOpts)
const meta = new Metadata(exiftool, {})
should(meta.keywords).eql(['beach'])
})
@ -176,14 +169,14 @@ describe('Metadata', function () {
// multiple keywords are returned as an array by <exiftool>
const exiftool = fixtures.exiftool()
exiftool.IPTC['Keywords'] = ['beach', 'sunset']
const meta = new Metadata(exiftool, {}, iptcOpts)
const meta = new Metadata(exiftool)
should(meta.keywords).eql(['beach', 'sunset'])
})
it('can read a single Picasa keywords', function () {
const exiftool = fixtures.exiftool()
const picasa = { keywords: 'beach' }
const meta = new Metadata(exiftool, picasa, picasaOpts)
const meta = new Metadata(exiftool, picasa)
should(meta.keywords).eql(['beach'])
})
@ -191,9 +184,26 @@ describe('Metadata', function () {
// because it's a simple INI file, multiple keywords are comma-separated
const exiftool = fixtures.exiftool()
const picasa = { keywords: 'beach,sunset' }
const meta = new Metadata(exiftool, picasa, picasaOpts)
const meta = new Metadata(exiftool, picasa)
should(meta.keywords).eql(['beach', 'sunset'])
})
it('combines all keyword sources', function () {
const exiftool = fixtures.exiftool()
exiftool.IPTC['Keywords'] = ['beach', 'sunset']
exiftool.XMP['Subject'] = 'holiday'
const picasa = { keywords: 'sandiego' }
const meta = new Metadata(exiftool, picasa, {})
should(meta.keywords).eql(['beach', 'sunset', 'holiday', 'sandiego'])
})
it('only keeps unique keywords', function () {
const exiftool = fixtures.exiftool()
exiftool.IPTC['Keywords'] = ['beach', 'sunset']
exiftool.XMP['Subject'] = ['beach', 'holiday']
const meta = new Metadata(exiftool)
should(meta.keywords).eql(['beach', 'sunset', 'holiday'])
})
})
describe('rating', function () {

Loading…
Cancel
Save