mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
reworked metadata caching + updated peer feature (not done yet)...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
f9398462c8
commit
49245ba9d1
@ -57,8 +57,7 @@ var MetadataActions = actions.Actions({
|
||||
if(this.images && this.images[gid]){
|
||||
return this.images[gid].metadata || {}
|
||||
}
|
||||
return null
|
||||
}],
|
||||
return null }],
|
||||
setMetadata: ['- Image/Set metadata data',
|
||||
function(image, metadata, merge){
|
||||
var that = this
|
||||
@ -70,12 +69,8 @@ var MetadataActions = actions.Actions({
|
||||
Object.keys(metadata).forEach(function(k){
|
||||
m[k] = metadata[k]
|
||||
})
|
||||
|
||||
} else {
|
||||
this.images[gid].metadata = metadata
|
||||
}
|
||||
}
|
||||
}]
|
||||
this.images[gid].metadata = metadata } } }]
|
||||
})
|
||||
|
||||
var Metadata =
|
||||
@ -119,21 +114,19 @@ var MetadataReaderActions = actions.Actions({
|
||||
var img = this.images && this.images[gid]
|
||||
|
||||
if(!image && !img){
|
||||
return false
|
||||
}
|
||||
return false }
|
||||
|
||||
//var full_path = path.normalize(img.base_path +'/'+ img.path)
|
||||
var full_path = this.getImagePath(gid)
|
||||
|
||||
return new Promise(function(resolve, reject){
|
||||
if(!force && img.metadata){
|
||||
return resolve(img.metadata)
|
||||
}
|
||||
if(!force
|
||||
&& !(img.metadata || {}).ImageGridPartialMetadata){
|
||||
return resolve(img.metadata) }
|
||||
|
||||
fs.readFile(full_path, function(err, file){
|
||||
if(err){
|
||||
return reject(err)
|
||||
}
|
||||
return reject(err) }
|
||||
|
||||
// read stat...
|
||||
if(!that.images[gid].birthtime){
|
||||
@ -173,10 +166,7 @@ var MetadataReaderActions = actions.Actions({
|
||||
that.markChanged
|
||||
&& that.markChanged('images', [gid]) }
|
||||
|
||||
resolve(data) })
|
||||
})
|
||||
})
|
||||
}],
|
||||
resolve(data) }) }) }) }],
|
||||
|
||||
// XXX STUB: add support for this to .readMetadata(..)
|
||||
readAllMetadata: ['File/Read all metadata',
|
||||
@ -202,8 +192,7 @@ var MetadataReaderActions = actions.Actions({
|
||||
q.enqueue('metadata', read(gid))
|
||||
})
|
||||
|
||||
return q
|
||||
}],
|
||||
return q }],
|
||||
|
||||
// XXX take image Metadata and write it to target...
|
||||
writeMetadata: ['- Image/Set metadata data',
|
||||
@ -651,9 +640,7 @@ var MetadataUIActions = actions.Actions({
|
||||
&& (that.config['metadata-graph-config'] = {
|
||||
graph: this.graph.graph,
|
||||
mode: this.graph.mode,
|
||||
})
|
||||
})
|
||||
})],
|
||||
}) }) })],
|
||||
|
||||
metadataSection: ['- Image/',
|
||||
{ sortedActionPriority: 'normal' },
|
||||
@ -663,16 +650,18 @@ var MetadataUIActions = actions.Actions({
|
||||
var field_order = this.config['metadata-field-order'] || []
|
||||
var x = field_order.length + 1
|
||||
|
||||
// NOTE: this is called on showMetadata.pre in the .handlers
|
||||
// feature section...
|
||||
make.dialog.updateMetadata =
|
||||
function(metadata){
|
||||
metadata = metadata || that.getMetadata()
|
||||
metadata = metadata
|
||||
|| that.getMetadata()
|
||||
|
||||
// build new data set and update view...
|
||||
//this.options.data = _buildInfoList(image, metadata)
|
||||
this.update()
|
||||
|
||||
return this
|
||||
}
|
||||
return this }
|
||||
|
||||
// build fields...
|
||||
var fields = []
|
||||
@ -691,15 +680,12 @@ var MetadataUIActions = actions.Actions({
|
||||
return
|
||||
|
||||
} else if(mode == 'disabled') {
|
||||
opts.disabled = true
|
||||
}
|
||||
}
|
||||
opts.disabled = true } }
|
||||
|
||||
fields.push([
|
||||
[ n + ': ', metadata[k] ],
|
||||
opts,
|
||||
])
|
||||
})
|
||||
]) })
|
||||
|
||||
// make fields...
|
||||
fields
|
||||
@ -717,8 +703,7 @@ var MetadataUIActions = actions.Actions({
|
||||
this.length > 0
|
||||
&& make.Separator() })
|
||||
.forEach(function(e){
|
||||
make(...e) })
|
||||
})],
|
||||
make(...e) }) })],
|
||||
|
||||
|
||||
// shorthands...
|
||||
|
||||
@ -19,114 +19,13 @@ var features = require('lib/features')
|
||||
var core = require('features/core')
|
||||
|
||||
var object = require('lib/object')
|
||||
var types = require('lib/types')
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// helpers...
|
||||
|
||||
// Cooperative promise object...
|
||||
//
|
||||
// This is like a promise but is not resolved internally, rather this
|
||||
// resolves (is set) via a different promise of value passed to it via
|
||||
// the .set(..) method...
|
||||
//
|
||||
// Example:
|
||||
// // create a promise...
|
||||
// var p = (new CooperativePromise())
|
||||
// // bind normally...
|
||||
// .then(function(){ .. })
|
||||
//
|
||||
// // this will resolve p and trigger all the .then(..) callbacks...
|
||||
// p.set(new Promise(function(resolve, reject){ resolve() }))
|
||||
//
|
||||
// Note that .set(..) can be passed any value, passing a non-promise has
|
||||
// the same effect as passing the same value to resolve(..) of a Promise
|
||||
// object...
|
||||
//
|
||||
// XXX should this be a separate package???
|
||||
// XXX can we make this an instance of Promise for passing the
|
||||
// x instanceof Promise test???
|
||||
var CooperativePromisePrototype = {
|
||||
__base: null,
|
||||
__promise: null,
|
||||
|
||||
// XXX error if already set...
|
||||
set: function(promise){
|
||||
if(this.__promise == null){
|
||||
// setting a non-promise...
|
||||
if(promise.catch == null && promise.then == null){
|
||||
Object.defineProperty(this, '__promise', {
|
||||
value: false,
|
||||
enumerable: false,
|
||||
})
|
||||
this.__resolve(promise)
|
||||
|
||||
// setting a promise...
|
||||
} else {
|
||||
Object.defineProperty(this, '__promise', {
|
||||
value: promise,
|
||||
enumerable: false,
|
||||
})
|
||||
|
||||
// connect the base and the set promises...
|
||||
promise.catch(this.__reject.bind(this))
|
||||
promise.then(this.__resolve.bind(this))
|
||||
|
||||
// cleanup...
|
||||
delete this.__base
|
||||
}
|
||||
|
||||
// cleanup...
|
||||
delete this.__resolve
|
||||
delete this.__reject
|
||||
|
||||
} else {
|
||||
// XXX throw err???
|
||||
console.error('Setting a cooperative promise twice', this)
|
||||
}
|
||||
},
|
||||
|
||||
// Promise API...
|
||||
catch: function(func){
|
||||
return (this.__promise || this.__base).catch(func) },
|
||||
then: function(func){
|
||||
return (this.__promise || this.__base).then(func) },
|
||||
|
||||
__init__: function(){
|
||||
var that = this
|
||||
var base = new Promise(function(resolve, reject){
|
||||
Object.defineProperties(that, {
|
||||
__resolve: {
|
||||
value: resolve,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
__reject: {
|
||||
value: reject,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Object.defineProperty(this, '__base', {
|
||||
value: base,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
var CooperativePromise =
|
||||
module.CooperativePromise =
|
||||
object.Constructor('CooperativePromise',
|
||||
Promise,
|
||||
CooperativePromisePrototype)
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// XXX would be nice to list the protocols supported by the action in
|
||||
// an action attr...
|
||||
var makeProtocolHandler =
|
||||
@ -192,8 +91,7 @@ var PeerActions = actions.Actions({
|
||||
return that.getActionAttr(action, '__peer__') == id }
|
||||
// get all peer actions...
|
||||
: function(action){
|
||||
return that.getActionAttr(action, '__peer__') })
|
||||
}],
|
||||
return that.getActionAttr(action, '__peer__') }) }],
|
||||
// XXX should this also check props???
|
||||
isPeerAction: ['- System/Peer/',
|
||||
function(name){
|
||||
@ -208,9 +106,11 @@ var PeerActions = actions.Actions({
|
||||
// XXX the events should get called on the peer too -- who is
|
||||
// responsible for this???
|
||||
peerConnect: ['- System/Peer/',
|
||||
function(id, options){ return new CooperativePromise() }],
|
||||
function(id, options){
|
||||
return new Promise.cooperative() }],
|
||||
peerDisconnect: ['- System/Peer/',
|
||||
function(id){ return new CooperativePromise() }],
|
||||
function(id){
|
||||
return new Promise.cooperative() }],
|
||||
|
||||
// events...
|
||||
// XXX do proper docs...
|
||||
@ -229,13 +129,14 @@ var PeerActions = actions.Actions({
|
||||
peerCall: ['- System/Peer/',
|
||||
function(id, action){
|
||||
var args = [...arguments].slice(2)
|
||||
return this.peerApply(id, action, args)
|
||||
}],
|
||||
return this.peerApply(id, action, args) }],
|
||||
peerApply: ['- System/Peer/',
|
||||
function(id, action, args){ return new CooperativePromise() }],
|
||||
function(id, action, args){
|
||||
return new Promise.cooperative() }],
|
||||
|
||||
peerList: ['- System/Peer/',
|
||||
function(){ return Object.keys(this.__peers || {}) }],
|
||||
function(){
|
||||
return Object.keys(this.__peers || {}) }],
|
||||
|
||||
// XXX do we need these???
|
||||
// XXX format spec!!!
|
||||
@ -287,8 +188,7 @@ var ChildProcessPeerActions = actions.Actions({
|
||||
if(this.__peers
|
||||
&& id in this.__peers
|
||||
&& this.__peers[id].peer.connected){
|
||||
return resolve(id)
|
||||
}
|
||||
return resolve(id) }
|
||||
|
||||
this.__peers = this.__peers || {}
|
||||
|
||||
@ -307,13 +207,9 @@ var ChildProcessPeerActions = actions.Actions({
|
||||
|
||||
callback
|
||||
&& (delete this.__peer_result_callbacks[msg.id])
|
||||
&& callback(msg.value, msg.error)
|
||||
}
|
||||
}).bind(this))
|
||||
&& callback(msg.value, msg.error) } }).bind(this))
|
||||
|
||||
resolve(id)
|
||||
}).bind(this))
|
||||
})],
|
||||
resolve(id) }).bind(this)) })],
|
||||
// XXX should this call .stop() on the child???
|
||||
// ...does the child handle kill gracefully???
|
||||
peerDisconnect: ['- System/Peer/',
|
||||
@ -329,8 +225,7 @@ var ChildProcessPeerActions = actions.Actions({
|
||||
that.__peers[id].peer.kill()
|
||||
delete that.__peers[id]
|
||||
|
||||
}).bind(this))
|
||||
})],
|
||||
}).bind(this)) })],
|
||||
|
||||
// XXX can we do sync???
|
||||
// ...this would be useful to 100% match the action api and
|
||||
@ -358,8 +253,7 @@ var ChildProcessPeerActions = actions.Actions({
|
||||
var handlers = this.__peer_result_callbacks = this.__peer_result_callbacks || {}
|
||||
handlers[call_id] = function(res, err){ err ? reject(err) : resolve(res) }
|
||||
|
||||
}).bind(this))
|
||||
})],
|
||||
}).bind(this)) })],
|
||||
})
|
||||
|
||||
|
||||
|
||||
@ -32,13 +32,19 @@ if(typeof(process) != 'undefined'){
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// helpers...
|
||||
|
||||
if(typeof(process) != 'undefined'){
|
||||
var copy = file.denodeify(fse.copy)
|
||||
var ensureDir = file.denodeify(fse.ensureDir)
|
||||
}
|
||||
|
||||
function normalizeOrientation(orientation){
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var normalizeOrientation =
|
||||
module.normalizeOrientation =
|
||||
function(orientation){
|
||||
return {
|
||||
orientation: ({
|
||||
0: 0,
|
||||
@ -65,9 +71,94 @@ function normalizeOrientation(orientation){
|
||||
} }
|
||||
|
||||
|
||||
var exifReader2exiftool = {
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Convert image metadata from exif-reader output to format compatible
|
||||
// with exiftool (features/metadata.js)
|
||||
|
||||
// Format:
|
||||
// {
|
||||
// // simple key-key pair...
|
||||
// 'path.to.value': 'output-key',
|
||||
//
|
||||
// // key with value handler...
|
||||
// 'path.to.other.value': ['output-key', handler],
|
||||
//
|
||||
// // alias to handler...
|
||||
// 'path.to.yet.another.value': ['output-key', 'path.to.other.value'],
|
||||
// }
|
||||
//
|
||||
var EXIF_FORMAT =
|
||||
module.EXIF_FORMAT = {
|
||||
// camera / lens...
|
||||
'image.Make': 'make',
|
||||
'image.Model': 'cameraModelName',
|
||||
'image.Software': 'software',
|
||||
'exif.LensModel': 'lensModel',
|
||||
|
||||
// exposure...
|
||||
'exif.ISO': 'iso',
|
||||
'exif.FNumber': ['fNumber',
|
||||
function(v){ return 'f/'+v }],
|
||||
'exif.ExposureTime': ['exposureTime',
|
||||
// NOTE: this is a bit of a brute-fore approach but for shutter
|
||||
// speeds this should not matter...
|
||||
function(v){
|
||||
if(v > 0.5){
|
||||
return ''+ v }
|
||||
for(var d = 1; (v * d) % 1 != 0; d++){}
|
||||
return (v * d) +'/'+ d }],
|
||||
|
||||
// dates...
|
||||
'exif.DateTimeOriginal': ['date/timeOriginal',
|
||||
function(v){
|
||||
return v.toShortDate() }],
|
||||
'image.ModifyDate': ['modifyDate',
|
||||
'exif.DateTimeOriginal'],
|
||||
|
||||
// IPCT...
|
||||
'image.Artist': 'artist',
|
||||
'image.Copyright': 'copyright',
|
||||
|
||||
// XXX anything else???
|
||||
}
|
||||
|
||||
var exifReader2exiftool =
|
||||
module.exifReader2exiftool =
|
||||
function(data){
|
||||
return Object.entries(EXIF_FORMAT)
|
||||
// handle exif/image/...
|
||||
.reduce(function(res, [path, to]){
|
||||
var handler
|
||||
;[to, handler] = to instanceof Array ?
|
||||
to
|
||||
: [to]
|
||||
// resolve handler reference/alias...
|
||||
while(typeof(handler) == typeof('str')){
|
||||
handler = EXIF_FORMAT[handler][1] }
|
||||
// resolve source path...
|
||||
var value = path.split(/\./g)
|
||||
.reduce(function(res, e){
|
||||
return res && res[e] }, data)
|
||||
// set the value...
|
||||
if(value !== undefined){
|
||||
res[to] = handler ?
|
||||
handler(value)
|
||||
: value }
|
||||
return res }, {})
|
||||
// handle xmp...
|
||||
.run(function(){
|
||||
var rating = data.xmp
|
||||
// NOTE: we do not need the full XML
|
||||
// fluff here, just get some values...
|
||||
&& parseInt(
|
||||
(data.xmp.toString()
|
||||
.match(/(?<match><(xmp:Rating)[^>]*>(?<value>.*)<\/\2>)/i)
|
||||
|| {groups: {}})
|
||||
.groups.value)
|
||||
rating
|
||||
&& (this.rating = rating) }) }
|
||||
|
||||
|
||||
|
||||
|
||||
@ -423,9 +514,10 @@ var SharpActions = actions.Actions({
|
||||
.flat()) }],
|
||||
|
||||
|
||||
// XXX should this update all images or just the ones that have no metadata???
|
||||
// XXX add support for offloading the processing to a thread/worker...
|
||||
// XXX would be nice to be able to abort this...
|
||||
// ...and/or have a generic abort protocol triggered when loading...
|
||||
// ...use task queue???
|
||||
// XXX make each section optional...
|
||||
// XXX revise name...
|
||||
cacheImageMetadata: ['- Sharp|Image/',
|
||||
@ -498,6 +590,20 @@ var SharpActions = actions.Actions({
|
||||
img.orientation = o.orientation || 0
|
||||
img.flipped = o.flipped
|
||||
|
||||
// read the metadata...
|
||||
var exif = metadata.exif
|
||||
&& exifReader(metadata.exif)
|
||||
exif
|
||||
&& Object.assign(
|
||||
(img.metadata = img.metadata || {}),
|
||||
exifReader2exiftool(exif),
|
||||
// mark metadata as partial read...
|
||||
//
|
||||
// NOTE: partial metadata will get reread by
|
||||
// the metadata feature upon request...
|
||||
// XXX revise name...
|
||||
{ ImageGridPartialMetadata: true })
|
||||
|
||||
// if image too large, generate preview(s)...
|
||||
// XXX EXPERIMENTAL...
|
||||
var size_threshold = that.config['preview-generate-threshold']
|
||||
@ -513,31 +619,11 @@ var SharpActions = actions.Actions({
|
||||
base_path,
|
||||
logger) }
|
||||
|
||||
// XXX EXIF -- keep compatible with exiftool...
|
||||
// - dates
|
||||
// - camera / lens / ...
|
||||
var exif = metadata.exif
|
||||
&& exifReader(metadata.exif)
|
||||
// XXX
|
||||
|
||||
// xmp:Rating...
|
||||
var rating = metadata.xmp
|
||||
// NOTE: we do not need the full XML
|
||||
// fluff here, just get some values...
|
||||
&& parseInt(
|
||||
(metadata.xmp.toString()
|
||||
.match(/(?<match><(xmp:Rating)[^>]*>(?<value>.*)<\/\2>)/i)
|
||||
|| {groups: {}})
|
||||
.groups.value)
|
||||
rating
|
||||
&& (img.metadata = img.metadata || {})
|
||||
&& (img.metadata.rating = rating || 0)
|
||||
|
||||
that.markChanged('images', [gid])
|
||||
|
||||
logger && logger.emit('done', gid)
|
||||
|
||||
// update image to use the orientation...
|
||||
// update loaded image to use the orientation...
|
||||
loaded
|
||||
&& loaded.has(gid)
|
||||
&& that.ribbons.updateImage(gid)
|
||||
@ -564,9 +650,11 @@ module.Sharp = core.ImageGridFeatures.Feature({
|
||||
|
||||
handlers: [
|
||||
/* XXX not sure if we need this...
|
||||
// XXX this is best done in a thread + needs to be abortable...
|
||||
['loadImages',
|
||||
function(){
|
||||
this.cacheImageMetadata('all', false) }],
|
||||
//this.cacheImageMetadata('all', false) }],
|
||||
this.cacheImageMetadata('all') }],
|
||||
//*/
|
||||
|
||||
// set orientation if not defined...
|
||||
|
||||
6
Viewer/package-lock.json
generated
6
Viewer/package-lock.json
generated
@ -1117,9 +1117,9 @@
|
||||
"integrity": "sha512-EzT4CP6d6lI8bnknNgT3W8mUQhSVXflO0yPbKD4dKsFcINiC6npjoEBz+8m3VQmWJhc+36pXD4JLwNxUEgzi+Q=="
|
||||
},
|
||||
"ig-types": {
|
||||
"version": "2.0.21",
|
||||
"resolved": "https://registry.npmjs.org/ig-types/-/ig-types-2.0.21.tgz",
|
||||
"integrity": "sha512-s+Hu9MU50iohoS/5SUwuoS+P2EHk7Z2zKx9wX3syiBsaL9HYq8g/0Yp/4yz9JkME1zpbS8r9aviR0O2rjBXwHQ==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ig-types/-/ig-types-3.0.1.tgz",
|
||||
"integrity": "sha512-oa0Tq+LFyy/2SoQHfhRoa39AtttslJFm95FaF7wH5FNcjxRn3dJ/C/4LscXIoeGaDcXe2DyneVIPNnvg8IOsNw==",
|
||||
"requires": {
|
||||
"ig-object": "^5.2.8",
|
||||
"object-run": "^1.0.1"
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
"ig-argv": "^2.15.0",
|
||||
"ig-features": "^3.4.2",
|
||||
"ig-object": "^5.2.8",
|
||||
"ig-types": "^2.0.21",
|
||||
"ig-types": "^3.0.1",
|
||||
"moment": "^2.29.1",
|
||||
"object-run": "^1.0.1",
|
||||
"requirejs": "^2.3.6",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user