Version 1 of Driving ExifTool from Tcl

Updated 2013-08-26 21:23:10 by EMJ

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

-@ ARGFILE
Read command-line arguments from the specified file. The file contains one argument per line (NOT one option per line -- some options require additional arguments, and all arguments must be placed on separate lines). Blank lines and lines beginning with # and are ignored. Normal shell processing of arguments is not performed, which among other things means that arguments should not be quoted and spaces are treated as any other character. ARGFILE may exist relative to either the current directory or the exiftool directory unless an absolute pathname is given.

(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.

To be continued with some code to use it.