From the Exiftool website[L1 ]:
ExifTool is a platform-independent Perl library plus a command-line application for reading, writing and editing meta information in a wide variety of files.
The command-line has a large variety of options to do various things to one file or many.
I (EMJ) use ExifTool a lot with my photo collection, adding metadata as well retrieving it for a variety of purposes. Doing the same thing with lots of files is easy and efficient, but doing the same sort of updates but with different values to a lot of files can be very slow, needing many invocations of the program. Howver, ExifTool provides for this (again from the website):
... the -execute option may be used to perform multiple independent operations with a single invocation of exiftool, and together with the -stay_open option provides a method for calling applications to avoid (the) startup overhead
Add this to
(which doesn't mention -@ - for stdin) and you have something that can easily be driven from Tcl using open (as open |) and fileevent. After a fair bit of messing about, I have ended up with the following:
#!/usr/bin/tclsh
#---------------------------------------------------------------------------
# start of "package"
#
namespace eval ::run_exiftool {
variable et_vars
set et_vars [dict create]
# the caller can use this dict, we only care about the keys "chan" and
# "func"
}
# called by the user to start using ExifTool
proc ::run_exiftool::start { func } {
variable et_vars
# remember the user-supplied function
dict set et_vars func $func
# start ExifTool in the right way and remember the channel for the pipe
set et [open "|exiftool -stay_open true -@ -" r+]
fconfigure $et -blocking false
fileevent $et readable [list [namespace current]::isReadable]
dict set et_vars chan $et
# call the user-supplied function with the NAME of the dictionary and the
# special string "INIT"
$func [namespace current]::et_vars INIT
# presumably the user-supplied code will feed arguments to ExifTool,
# deal with its output and, eventually, stop it properly.
vwait ::DONE
close [dict get $et_vars chan]
}
# this is how to stop ExifTool properly
proc ::run_exiftool::stop {} {
variable et_vars
set et [dict get $et_vars chan]
puts $et "-stay_open"
puts $et "false"
flush $et
}
# pretty much a standard fileevent handler
proc ::run_exiftool::isReadable {} {
variable et_vars
set et [dict get $et_vars chan]
# The channel is readable; try to read it.
set status [catch { gets $et line } result]
if { $status != 0 } {
# Error on the channel
puts "error reading $et: $result"
set ::DONE 2
} elseif { $result >= 0 } {
# Successfully read the channel, so tell the user (dict passed
# by NAME)
set usr_status [catch { [dict get $et_vars func] [namespace current]::et_vars $line } usr_result]
if { $usr_status != 0 } {
puts "error in user function: $usr_result"
close $et
set ::DONE 99
}
} elseif { [eof $et] } {
# End of file on the channel
set ::DONE 1
} elseif { [fblocked $et] } {
# Read blocked. Just return
} else {
# Something else
puts "can't happen"
set ::DONE 3
}
}
# end of "package"
#---------------------------------------------------------------------------Not actually a package yet, of course.
When everthing is ready, we will want to do
# ::run_exiftool::start do_one_line
so here is a suitable proc:
proc do_one_line { et_dict line } {
# the dict is available for user data needed across calls, which is why
# we get it by name, and pass it on by name as well
upvar 1 $et_dict et_vars
# the channel to ExifTool is the most important thing we get
set et [dict get $et_vars chan]
# process a line of ExifTool's output
switch -regexp -- $line {
{^INIT$} {
# This is the special case to allow us to initialize -
# in this case we are driven by a file, so open it, save the
# channel, and call our function to read from it and send something
# to ExifTool
dict set et_vars fp [open captionlist]
dict set et_vars dates -
sendnext et_vars
}
{^date} {
# this output will result from what we sent first, so remember it
dict set et_vars dates [lindex [split $line] 1]
}
{^{ready1}$} {
# this is from -execute1, now we can use what we remembered to
# build some updates
updnext et_vars
}
{^{ready2}$} {
# this is from -execute2, our updates are done, so go get the data
# for the next and send it
sendnext et_vars
}
default {
# not from anything we did or from -execute, so just show it
# to the user
puts $line
}
}
}To be continued ...