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 [email protected] - 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 [email protected] -" 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 ...