Read metadata from Picasa.ini if present

pull/77/head
Romain 7 years ago
parent 3e64d2ab38
commit 0b27d37881

@ -32,6 +32,7 @@
"fs-extra": "^2.0.0",
"fs-readdir-recursive": "^1.0.0",
"handlebars": "~4.0.5",
"ini": "^1.3.4",
"less": "^2.7.1",
"lightgallery": "1.2.14",
"lodash": "^4.16.6",
@ -47,6 +48,7 @@
"injectmd": "^1.0.0",
"markdown-toc": "^0.13.0",
"mocha": "^3.1.2",
"mock-fs": "^4.4.1",
"require-lint": "^1.1.2",
"should": "^11.1.1",
"standard": "^9.0.0"

@ -4,6 +4,7 @@ const path = require('path')
const os = require('os')
const cleanup = require('./output-media/cleanup')
const database = require('./input/database')
const Picasa = require('./input/picasa')
const progress = require('./utils/progress')
const hierarchy = require('./input/hierarchy.js')
const mapper = require('./input/mapper')
@ -25,11 +26,14 @@ exports.build = function (opts) {
async.series([
function updateDatabase (callback) {
const picasaReader = new Picasa()
database.update(opts.input, databaseFile, (err, entries) => {
if (err) return callback(err)
files = entries.map(entry => {
// create standarised metadata model
const meta = new Metadata(entry)
const picasa = picasaReader.file(entry.SourceFile)
const meta = new Metadata(entry, picasa || {})
// create a file entry for the albums
return new File(entry, meta, opts)
})
callback()

@ -0,0 +1,70 @@
/*
--------------------------------------------------------------------------------
Provides Picasa metadata based on <picasa.ini> files in the input folder
--------------------------------------------------------------------------------
*/
const ini = require('ini')
const fs = require('fs')
const path = require('path')
class Picasa {
constructor () {
this.folders = {}
}
album (dir) {
if (!this.folders[dir]) {
this.folders[dir] = loadPicasa(dir)
}
// album metadata is stored in a section called [Picasa]
const entry = this.folders[dir]
return entry.Picasa || null
}
file (filepath) {
const dir = path.dirname(filepath)
if (!this.folders[dir]) {
this.folders[dir] = loadPicasa(dir)
}
// file metadata is stored in a section called [FileName.ext]
const entry = this.folders[dir]
const filename = path.basename(filepath)
const fileParts = filename.split('.')
return getIniValue(entry, fileParts)
}
}
function loadPicasa (dirname) {
const inipath = path.join(dirname, 'picasa.ini')
const content = loadIfExists(inipath)
if (!content) {
// return an empty hash, as if the picasa.ini file existed but was empty
return {}
} else {
return ini.parse(content)
}
}
function loadIfExists (filepath) {
try {
return fs.readFileSync(filepath, 'utf-8')
} catch (ex) {
return null
}
}
// the INI parser creates nested objects when the key contains a '.'
// this is a problem for sections like [IMG_0001.jpg]
// this might get fixed with https://github.com/npm/ini/issues/60
// but for now we have to recursively get the value
function getIniValue (iniObject, keyParts) {
const current = iniObject[keyParts[0]]
if (!current) {
return null
} else if (keyParts.length === 1) {
return current
} else {
return getIniValue(current, keyParts.slice(1))
}
}
module.exports = Picasa

@ -21,14 +21,15 @@ const FILENAME_DATE_REGEX = /\d{4}[_\-.\s]?(\d{2}[_\-.\s]?){5}\..{3,4}/
const FILENAME_DATE_FORMAT = 'YYYYMMDD HHmmss'
class Metadata {
constructor (exiftool) {
constructor (exiftool, picasa) {
// standardise metadata
this.date = getDate(exiftool)
this.caption = caption(exiftool)
this.keywords = keywords(exiftool)
this.keywords = keywords(exiftool, picasa)
this.video = video(exiftool)
this.animated = animated(exiftool)
this.rating = rating(exiftool)
this.favourite = favourite(picasa)
// metadata could also include fields like
// - lat = 51.5
// - long = 0.12
@ -55,7 +56,8 @@ function getDate (exif) {
}
function caption (exif, picasa) {
return tagValue(exif, 'EXIF', 'ImageDescription') ||
return picasaValue(picasa, 'caption') ||
tagValue(exif, 'EXIF', 'ImageDescription') ||
tagValue(exif, 'IPTC', 'Caption-Abstract') ||
tagValue(exif, 'IPTC', 'Headline') ||
tagValue(exif, 'XMP', 'Description') ||
@ -64,7 +66,8 @@ function caption (exif, picasa) {
}
function keywords (exif, picasa) {
const values = tagValue(exif, 'IPTC', 'Keywords')
const values = picasaValue(picasa, 'keywords') ||
tagValue(exif, 'IPTC', 'Keywords')
return values ? values.split(',') : []
}
@ -83,9 +86,18 @@ function rating (exif) {
return exif.XMP['Rating'] || 0
}
function favourite (picasa) {
return picasaValue(picasa, 'star') === 'yes'
}
function tagValue (exif, type, name) {
if (!exif[type]) return null
return exif[type][name]
}
function picasaValue (picasa, name) {
if (typeof picasa !== 'object') return null
return picasa[name]
}
module.exports = Metadata

@ -0,0 +1,64 @@
const should = require('should/as-function')
const mock = require('mock-fs')
const Picasa = require('../../src/input/picasa.js')
const PICASA_INI = `
[Picasa]
name=My holidays
[IMG_0001.jpg]
star=yes
caption=Nice sunset
keywords=beach,sunset
`
describe('Picasa', function () {
afterEach(function () {
mock.restore()
})
it('reads album metadata', function () {
mock({
'holidays/picasa.ini': PICASA_INI
})
const picasa = new Picasa()
const meta = picasa.album('holidays')
should(meta).eql({
name: 'My holidays'
})
})
it('returns <null> if there is no album metadata', function () {
const picasa = new Picasa()
const meta = picasa.album('holidays')
should(meta).eql(null)
})
it('returns raw file metadata as read from the INI file', function () {
mock({
'holidays/picasa.ini': PICASA_INI
})
const picasa = new Picasa()
const meta = picasa.file('holidays/IMG_0001.jpg')
should(meta).eql({
star: 'yes',
caption: 'Nice sunset',
keywords: 'beach,sunset'
})
})
it('can read metadata of a file with several dots in the name', function () {
mock({
'holidays/picasa.ini': '[IMG.0001.small.jpg]\ncaption=dots'
})
const picasa = new Picasa()
const meta = picasa.file('holidays/IMG.0001.small.jpg')
should(meta).eql({
caption: 'dots'
})
})
it('returns <null> if a file has no metadata', function () {
mock({
'holidays/picasa.ini': PICASA_INI
})
const picasa = new Picasa()
const meta = picasa.album('holidays/IMG_0002.jpg')
should(meta).eql(null)
})
})

@ -131,7 +131,7 @@ describe('Metadata', function () {
should(meta.keywords).eql(['beach', 'sunset'])
})
xit('can read Picasa keywords', function () {
it('can read Picasa keywords', function () {
const exiftool = fixtures.exiftool()
const picasa = {keywords: 'beach,sunset'}
const meta = new Metadata(exiftool, picasa)
@ -154,7 +154,7 @@ describe('Metadata', function () {
})
})
xdescribe('favourite', function () {
describe('favourite', function () {
it('defaults to false', function () {
const exiftool = fixtures.exiftool()
const meta = new Metadata(exiftool)

Loading…
Cancel
Save