#!/usr/bin/wish package require tk 8.4.9 package require jpeg 0.2 proc make_image {filename desiredSize {width 0} {height 0}} { if {$width == 0} { foreach {width height} [::jpeg::dimensions $filename] break } if {$width > $height} { set dim $width } else { set dim $height } set scale 1 while {$dim>$scale*$desiredSize} { incr scale } set f [open "|djpeg -fast -scale 1/$scale $filename"] fconfigure $f -translation binary set data [read $f] close $f return [image create photo -data $data] } proc get_data {filename var} { upvar $var data catch {array set data [jpeg::formatExif [::jpeg::getExif $filename]]} set data(Comment) [join [encoding convertfrom [::jpeg::getComments $filename]] "\n"] foreach {data(Width) data(Height)} [::jpeg::dimensions $filename] break } proc rotate {filename angle} { if {[lsearch {90 180 270} $angle]==-1} { return -code error "Invalid rotation angle - should be multiple of 90" } exec exiftran -[string range $angle 0 0] -i $filename 2>/dev/null } proc set_comment {filename comment} { puts $filename ::jpeg::replaceComment $filename [encoding convertto $comment] } proc getname {index} { global filelist return [lindex $filelist $index] } proc clearExecBit {filename} { if {[file executable $filename]} { file attributes $filename -permissions -x } } proc set_file {index} { global filecount filename exifdata filelist if {[info exists filename]} { save_file_info } set buttonSize [expr [option get . imageSize ImageSize]/2] set filename [getname $index] clearExecBit $filename if {[regexp -nocase {img_(\d+)\.jpg} $filename => number]} { set audio snd_${number}.wav if {![file exists $audio]} { set audio [string toupper $audio] if {![file exists $audio]} { unset audio } } } if {[info exists audio]} { puts "Found audiofile $audio" clearExecBit $audio .info.sound configure -state normal -command [list exec play $audio] } else { .info.sound configure -state disabled } if {$index == 0} { .preview.prev configure -state disabled -image {} } else { .preview.prev configure -state normal\ -image [make_image [lindex $filelist [expr $index-1]] $buttonSize] } if {$index == $filecount-1} { .preview.next configure -state disabled -image {} } else { .preview.next configure -state normal\ -image [make_image [lindex $filelist [expr $index+1]] $buttonSize] } .info.filename configure -text "$filename ([expr $index+1]/$filecount)" array set exifdata [::jpeg::formatExif [::jpeg::getExif $filename]] if {[info exists exifdata(DateTime)]} { set tm $exifdata(DateTime) } else { set tm "unknown" } .info.exif configure -state normal .info.exif delete 0.0 end foreach {key value} [array get exifdata] { if {$key == "MakerNote"} break if {$key == "UserComment"} { set value [string trim $value "\0"] } .info.exif insert end $key key "\t: $value\n" {} } .info.datetime configure -text "Date: $tm" foreach {width height} [jpeg::dimensions $filename] break show_image $filename $width $height .info.comment delete 0.0 end .info.comment insert 0.0 [encoding convertfrom [join [jpeg::getComments $filename] "\n"]] .info.comment edit reset .info.comment edit modified n } proc delete_file {index} { global filename filelist filecount if {[tk_messageBox -message "Really delete file $filename" -type yesno -title\ Confirm -icon warning] != "yes"} { return } file delete $filename .info.comment edit reset .info.comment edit modified n if {$index == $filecount-1} { global current set filelist [lrange $filelist 0 [expr $index-1]] incr current -1 set index $current } else { set filelist [lreplace $filelist $index $index] } incr filecount -1 set_file $index } proc rotateGUI {filename angle} { global exifdata rotate $filename $angle show_image $filename } proc show_image {filename {width 0} {height 0}} { global exifdata if {$width == 0} { foreach {width height} [jpeg::dimensions $filename] break } set img [make_image $filename [option get . imageSize ImageSize] $width $height] .preview.l configure -image $img foreach img [image names] { if { ![string match ::tk::* $img]&& ![image inuse $img]} { image delete $img } } .info.size configure -text "Size: ${width}x$height" } proc save_file_info {} { global filename if {![.info.comment edit modified]} { return } set_comment $filename [string trim [.info.comment get 0.0 end] "\n"] } # # Interface construction # option add *Text.Font -rfx-courier-medium-r-normal--12-120-75-75-m-70-iso10646-1 option add *Font -rfx-times-bold-r-normal--12-120-75-75-p-67-iso10646-1 widgetDefault image create bitmap speaker -data { #define speaker_width 24 #define speaker_height 24 static unsigned char speaker_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x00, 0x00, 0x24, 0x00, 0x00, 0x49, 0x00, 0x80, 0x51, 0x00, 0xc0, 0x91, 0x00, 0xe7, 0xa5, 0x00, 0xf7, 0xa5, 0x00, 0xff, 0x29, 0x01, 0xff, 0x49, 0x01, 0xff, 0x49, 0x01, 0xff, 0x29, 0x01, 0xf7, 0xa9, 0x00, 0xe7, 0xa5, 0x00, 0xc0, 0x95, 0x00, 0x80, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, 0x24, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; } frame .info label .info.filename -width 32 -anchor w button .info.rotateleft -text "90\u00b0" -command {rotateGUI [getname $current] 90} button .info.rotateright -text "270\u00b0" -command {rotateGUI [getname $current] 270} button .info.upsidedown -text "180\u00b0" -command {rotateGUI [getname $current] 180} button .info.delete -text "Delete" -command {delete_file $current} label .info.size -text "Size:" -anchor w label .info.datetime -text "Date:" -anchor w text .info.exif -height 20 -width 40 -state disabled -yscrollcommand ".info.yexif set" -tabs {3c} text .info.comment -width 40 -height 5 -undo y -wrap word -yscrollcommand ".info.ycomment set" scrollbar .info.yexif -orient vert -command ".info.exif yview" scrollbar .info.ycomment -orient vert -command ".info.comment yview" button .info.sound -state disabled -image speaker frame .preview -width 200 -height 200 button .preview.next -text ">>" -command {set_file [incr current]} button .preview.prev -text "<<" -command {set_file [incr current -1]} -state disabled label .preview.l grid .info.filename - - - - - -sticky news grid .info.rotateleft .info.upsidedown .info.rotateright .info.sound .info.delete - grid .info.size - - - -sticky news grid .info.datetime - - - -sticky news grid .info.exif - - - - .info.yexif -sticky news grid .info.comment - - - - .info.ycomment -sticky news grid rowconfigure .info 5 -weight 1 grid columnconfigure .info 4 -weight 1 grid .preview.prev .preview.next -sticky news grid .preview.l - -sticky news grid rowconfigure .preview 1 -weight 1 grid columnconfigure .preview 0 -weight 1 grid columnconfigure .preview 1 -weight 1 grid .info .preview -sticky news wm protocol . WM_DELETE_WINDOW {save_file_info;destroy .} focus .info.comment bind .info.comment {.preview.next invoke} bind .info.comment {.preview.prev invoke} bind .info.comment {eval [wm protocol . WM_DELETE_WINDOW]} option add [winfo class .].imageSize 400 widgetDefault if {[llength $argv]>1} { puts stderr "Usage $argv0 [image-file]" } set filelist [lsort -dictionary [concat [glob -nocomplain *.jpg] [glob -nocomplain *.JPG]]] set filecount [llength $filelist] if {!$filecount} { puts stderr "No image files in the current directory!" exit 1 } if {[llength $argv]} { set current [lsearch $filelist [lindex $argv 0]] if {$current == -1} { puts stderr "File $argv not found in the current dir\n" exit 1 } } else { set current 0 } set_file $current