Updated 2014-01-25 15:56:10 by dkf


The following Tcl code can be used to automate the uploading of files to Flickr [1]. You will need to have tcllib installed to use this.

I already had a web site that had lots of photos in a gallery. To manage this, I used bins [2]. One of the nice things about bins is that it keeps all of the information about images (title, location, comments, etc.) in XML files. This allows me to provide the same information to Flickr when uploading files without any manual labor.
set posturl {http://www.flickr.com/tools/uploader_go.gne}
package require dom
package require http
package require base64
proc getFileInfo {fname} {
    set xmlfile "${fname}.xml"
    set tagList {}
    if {![file exists $fname]} {
        return ""

    set fd [open $xmlfile r]
    set xml [read $fd]
    close $fd

    set tree [dom::parse $xml]

    set titleNode [dom::selectNode $tree \
    set title [string trim [dom::node stringValue $titleNode]]

    set descripNode [dom::selectNode $tree \
    set description [string trim [dom::node stringValue $descripNode]]

    set locationNode [dom::selectNode $tree \
    if {$locationNode != ""} {
        set location [string trim [dom::node stringValue $locationNode]]
        if {$location != ""} {
            lappend tagList "\"$location\""

    set yearNode [dom::selectNode $tree \
    if {$yearNode != ""} {
        set year [string trim [dom::node stringValue $yearNode]]
        if {$year != ""} {
            lappend tagList "\"$year\""

    set countryNode [dom::selectNode $tree \
    if {$countryNode != ""} {
        set country [string trim [dom::node stringValue $countryNode]]
        if {$country != ""} {
            lappend tagList "\"$country\""

    dom::destroy $tree
    return [list $title $description $tagList]
proc postImage {email password file info} {
    global posturl

    set fd [open "${file}" r]
    fconfigure $fd -translation binary
    set photo [read $fd]
    close $fd

    set title [lindex $info 0]
    set description [lindex $info 1]
    set tags ""
    foreach tag [lindex $info 2] {
        append tags "$tag "
    set tags [string trim $tags]

    set outputData {}
    set bound "-----NEXT_PART_[clock seconds].[pid]"

    append outputData "--$bound\nContent-Disposition: form-data;\
    append outputData "--$bound\nContent-Disposition: form-data;\
    append outputData "--$bound\nContent-Disposition: form-data;\
    append outputData "--$bound\nContent-Disposition: form-data;\
    append outputData "--$bound\nContent-Disposition: form-data;\
    append outputData "--$bound\nContent-Disposition: form-data;\
                name=\"photo\"; filename=\"[file tail $file]\"\n\n$photo\n"

    set token [http::geturl $posturl -type "multipart/form-data; boundary=$bound" \
        -query $outputData]
    set body [http::data $token]
    http::cleanup $token

    set tree [dom::parse $body]
    set statusNode [dom::selectNode $tree \
    if {$statusNode != ""} {
        set status [string trim [dom::node stringValue $statusNode]]
    set photoidNode [dom::selectNode $tree \
    if {$photoidNode != ""} {
        set photoid [string trim [dom::node stringValue $photoidNode]]

    return [list $status $photoid]

proc processDir {dir} {
    global email password
    if {![file isdirectory $dir]} {
        error "$dir does not exist or is not a directory"

    set fileList [glob -nocomplain [file join $dir *.jpg]]

    foreach file $fileList {
        puts -nonewline "processing $file ... "
        flush stdout
        set finfo [getFileInfo $file]
        set statusList [postImage $email $password $file $finfo]
        if {[lindex $statusList 0] != "ok"} {
            puts "error: [lindex $statusList 0] [lindex $statusList 1]"
        } else {
            puts "done"

set email "[email protected]" ; # Your email to login to flickr
set password "secret" ; # Your flickr password
set dirlist { images/dir1 images/dir2 } ; # Directories that you want to process
foreach dir $dirlist {
    processDir $dir