Movie Metadata editor

Movie metadata editor for Windows, to display and edit metadata of MP4 and MKV video files. It uses external applications to read and write metadata to/from movie files. On startup the script looks for three external applications: AtomicParsley.exe, mkvpropedit.exe, mkvinfo.exe

For MP4 video: Atomic Parsley, from sourceforge For MKV video: mkvinfo (to read) and mkvpropedit (to write), from MKVToolNix

It has issues with writing metadata containing the symbols & and " atomicparsley refuses to modify tags on large files over 2.9 GB with message "doesn't have full 64-bit support"

Not my prettiest code but it functions well enough for my uses so I thought to share it.

# not fully tested with TCL v9.0 (as of 2024-11-27)
# workaround for images that wont display with error: Failed to create temporary file
set env(JPEGMEM) 1000M

catch {package require Img} result
set dir [file dirname [info script]]
array set var {}
set var(overwrite) 1        ;# overwrite original mp4 movie file instead of writing a new file
set var(gumball_delay) 2000
set var(display_thumb) 1        ;# display of artwork from file
set var(save_thumb) 0                ;# save of artwork to new file
set var(debug) 0
set var(textwidth) 50                ;# character width of data entry fields
set var(on_color)        green3        ;# color of on toggles
set var(MP4_EDIT) [file join $dir AtomicParsley.exe]
set var(MKV_EDIT) [file join $dir mkvpropedit.exe]
set var(MKV_INFO) [file join $dir mkvinfo.exe]
set var(last_dir) ""
set var(defer_exec)        0        ;# for mp4 save batfile to run later in background, then click Apply

catch {console show}
# find last used directory and look there first
if {[file readable [file join $dir settings.tcl]]} {
        if {[catch {source settings.tcl} result]} {
                puts $result
        } else {
                puts "loaded settings.tcl"
        }
}
# check external apps exist
foreach {lbl vrb} {
        AtomicParsley MP4_EDIT
        MKVInfo MKV_INFO
        MKVPropEdit MKV_EDIT        
} {
#        set var($vrb) $exe
        if {[file exists $var($vrb)]} {
                puts "found $lbl"
        } else {
                set filetypes {
                {{Executable Files} {.exe} }
                {{All Files} *}
                }
                set f [tk_getOpenFile -title "Find $lbl" -filetypes $filetypes]
                if {[string length $f] && [file exists $f]} {set var($vrb) $f}
        }
}
catch {cd $var(last_dir)}
# find movie player app, use last one found
foreach f {
        "C:/Program Files (x86)/Windows Media Player/wmplayer.exe"
        "C:/Program Files/VideoLAN/VLC/vlc.exe"
} {
        if {[file exists $f]} {
                set var(PLAY) $f
                puts "found player $f"
        }
}
# toggles to lock metadata values to apply to later files
array set lock {}
# metadata read from file or entered in gui to save back to file
array set data {}
# output message to console if debug flag is on
proc debug {args} {
        global var
        if {![info exists var(debug)]} {set var(debug) 0}
        if {[string length $args]} {
                if {$var(debug)} {puts $args}
        } else {
                puts "var(debug) is $var(debug)"
        }
}
# dump helpful info to the console
proc help {{what all}} {
        global var
        puts "HELP FOR THIS TOOL"
        switch -glob -nocase -- $what {
                mp4 {
                        puts [string repeat "-" 30]
                        puts "[file tail $var(MP4_EDIT)] is used for MP4 files.\n"
                        catch {exec $var(MP4_EDIT) -longhelp} result
                        puts $result
                        }
                mkv {
                        puts [string repeat "-" 30]
                        puts "[file tail $var(MKV_EDIT)] is used for MKV files.\n"
                        catch {exec $var(MKV_EDIT) -?} result
                        puts $result
                        catch {exec $var(MKV_EDIT) -l} result
                        puts $result
                        }
                default {help mp4; help mkv}
        }
}
# select a folder of movie files
proc choose_dir {args} {
        global var filelist
        if {[string length $args] < 1} {
                set tmp [tk_chooseDirectory]
                if {[string length $tmp] < 1} {return}
        } else {set tmp $args}
        if {[catch {cd $tmp} result]} {return}
        set var(last_dir) $tmp
        # create file list with mp4 first, then mkv
        set filelist [list \
                [lsort -dict [glob -types f -nocomplain *.mp4]] \
                [lsort -dict [glob -types f -nocomplain *.mkv]] ]
        set filelist [join $filelist]
        save_settings
}
# write to console and to text widget
proc log {{str ""} {tag ""}} {
        set w .f4.txt        ;# text log widget
        if {![winfo exists $w]} {puts "text widget $w not found."; return}
        if {$str == "clear"} {$w delete 1.0 end; return}
        puts $str
        $w insert end "$str" $tag
        $w insert end "\n"
        # seek to bottom
        $w yview end
}
# update write status gumball
proc status {{str "Write tags"} {fg SystemButtonText} {bg SystemButtonFace}} {
        if {[catch {.f1.write configure -text $str -foreground $fg -background $bg} result]} {
        puts $result}
        update
}
# save current settings before exit
proc quit {args} {
        catch {save_settings}
        exit
}
proc save_settings {args} {
        global dir var
        # remember this folder for next run
        set f [file join $dir settings.tcl]
        if {[catch {set fout [open $f w]} result]} {puts "error: $result"; return}
        puts $fout "# saved settings for metadata editor"
        puts $fout "catch {cd \"[pwd]\"}"
        # dump contents of var array
        puts $fout "array set var {"
        foreach v [lsort [array names var]] {
                puts $fout "[list $v] [list [set var($v)]]"
        }
        puts $fout "}"
        close $fout
        debug "wrote settings to $f"
}
# RETURN NICELY-FORMATTED FILE SIZE STRING WITH KB MB GB TB PB
proc nice_size {num {decimals 1} {fullname 0}} {
        set remainder 1
        if {$decimals > 0} {set remainder [expr {pow(10,$decimals) + 0.0}]}
        foreach {n short long} {80 YB YottaByte 70 ZB ZettaByte 60 EB ExaByte 50 PB PetaByte 40 TB TeraByte 30 GB GigaByte 20 MB MegaByte 10 KB KiloByte} {
                set amount [expr {pow(2,$n)}]
                if {$fullname} {set long " ($long)"} else {set long ""}
                if {$num >= $amount} {return "[expr {int($num * $remainder / $amount) / $remainder}] $short$long"}
        }
        return $num
}
# RETURN A FORMATTED NUMBER WITH DESIRED DECIMAL POINTS
proc decimals {n d} {
        global tcl_precision
        set decpts [expr {pow(10,$d)}]
        if {$d < 1} {
                set x [string first "." $n]
                # number is float, has decimal point
                if {$x > -1} {
                        incr x -1
                        incr x $d
                        return [string range $n 0 $x]}
        }
        # number is whole, no decimal
        set result [expr {wide($decpts * $n) / $decpts}]
}
# play movie file
proc play {args} {
        global var data
        if {![info exists data(filename)] || ![file readable $data(filename)]} {log "no file given to play"; return}
        if {[info exists var(PLAY)]} {
                if {[string length $var(PLAY)]} {
                        if {[catch {exec $var(PLAY) $data(filename) &} result]} {log $result}
                }
        } else {
                log "no player application found"
        }
}
# rename filename to match Title field, useful to recover mangled filenames
proc rename_file {args} {
        global data
        if {![file exists $data(filename)]} {log "File not found: $data($filename)" err; return}
        set newname $data(@nam)[file extension $data(filename)]
        if {[catch {file rename $data(filename) $newname} result]} {
                log $result err
        } else {
                log "Renamed $data(filename) to $newname"
                set data(filename) $newname
#                catch {.f1.refresh invoke}
        }
}
# write to console full output from metadata reader on selected file
# note mkvinfo refuses to allow some options: -a
proc full_info {args} {
        global var data
        if {[info exists data(filename)]} {set filename $data(filename)} else {log "no filename given"; return}
        log clear
        log "See Console for all metadata in $filename"
        set ext [string trimleft [string tolower [file extension $filename]] .]
        switch -- $ext {
        mp4 {catch {exec $var(MP4_EDIT) $filename -t} result}
        mkv {catch {exec $var(MKV_INFO) $filename} result}
        default {set result "unknown file type $ext"}
        }
        puts $result
}

proc display_photo {filename} {
        set w .image
        if {![winfo exists $w.c]} {
                # create window
                toplevel $w -background black
                canvas $w.c -background black -width 600 -height 600
                label $w.t -background black -foreground white -font {* 14}
                pack $w.c -fill both -expand 1
                pack $w.t -fill x
        }
        if {[file readable $filename]} {
                # display file
                catch {image delete pic}
                image create photo pic -file $filename
                $w.c create image 1 1 -image pic -anchor nw -tag image                        
                $w.t configure -text "$filename"
        }
}
# read file
proc read_file {args} {
        global var data lock lockdata filelist
        # get filename from list
        set n [.f2.f1.list curselection]
        if {[string length $n] < 1} {return}
        # clear old data first
        foreach tag [array names data] {set data($tag) ""}
        log clear
        set data(filename) [lindex $filelist $n]
        puts "----"
        log $data(filename)
        # check for invalid filename chars
        foreach c {&} {
                if {[string first $c $data(filename)] > -1} {
                        log "Error: must rename file to remove $c symbol" err
                }
        }        
        if {![file exists $data(filename)]} {log "File not found $data(filename)"; return}
        log "File size: [nice_size [file size $data(filename)]]"
#        status "Reading tags"
        set ext [string trimleft [string tolower [file extension $data(filename)]] .]
        # delete thumbnail
        catch {image delete pic}
        # parse filename to find tv series season and episode
        set str [regexp -all -inline -nocase -- {s\d\de\d\d} $data(filename)]
        if {[string length $str] == 4} {
                set data(tvsn) [string index $str 1]
                set data(tves) [string index $str 3]
        } elseif {[string length $str] == 6} {
                set s [string range $str 1 2]
                if {$s == "00"} {set data(tvsn) 0} else {set data(tvsn) [string trimleft $s 0]}
                set s [string range $str 4 5]
                if {$s == "00"} {set data(tves) 0} else {set data(tves) [string trimleft $s 0]}
        }
        # parse filename for vertical resolution (720/1080/2160/4K)
        if {[string first 640 $data(filename)] > 0} {set data(@grp) 640}
        if {[string first 720 $data(filename)] > 0} {set data(@grp) 720}
        if {[string first 1080 $data(filename)] > 0} {set data(@grp) 1080}
        if {[string first 2160 $data(filename)] > 0} {set data(@grp) 2160}
        if {[string first 4K $data(filename)] > 0} {set data(@grp) 2160}
        # parse music video fields from filename format Artist - Song - Album (Year)
        set s [split [file rootname $data(filename)] "-"]
        if {[llength $s] == 3} {
                set data(@ART) [string trim [lindex $s 0]]
                set data(@alb) [string trim [lindex $s 2]]
                # remove year
                if {([string index $data(@alb) end] == ")") && ([string index $data(@alb) end-5] == "(")} {
                        set data(@alb) [string range $data(@alb) 0 end-7]
                }
        }
        # use appropriate tool for the file type mp4/mkv
        switch -- $ext {
                mp4 {
                        log "MP4 container"
                        if {[catch {exec $var(MP4_EDIT) $data(filename) -t} result]} {
                                log "error: $result" warn
                                write_file clear
                                return
                        }
                        # parse tags
                        foreach str [split $result "\n"] {
                                # handle itunes ---- tags
                                regsub -all {"\)} $str {"} str
                                if {[string first Atom $str] > -1} {
                                set atom [lindex $str 1]
                                set cont [string range $str [string first contains: $str]+10 end]
                                # remove junk
                                regsub [binary format c "-62"] $atom "" atom
                                regsub [binary format c "-87"] $atom "@" atom
                                # sub
                                if {$atom == "gnre"} {set atom "@gen"}
                                set data($atom) $cont
                                log "$atom : $cont"
                                }
                        }
                        # attempt artwork extract to file
                        if {[string length $data(covr)]} {
                                # extract artwork to display
                                if {![catch {exec $var(MP4_EDIT) $data(filename) --extractPix} result]} {
                                        set pix [string trim [lindex [split $result ":"] 1]]
                                        if {[info exists var(display_thumb)] && $var(display_thumb)} {
                                                if {[file exists $pix]} {display_photo $pix}
                                        }
                                        # delete artwork if not wanted
                                        if {[info exists var(save_thumb)] && !$var(save_thumb)} {
                                                catch {file delete $pix}
                                        }
                                }
                        }
                }
                mkv {
                        log "Matroska container"
                        if {[catch {exec $var(MKV_INFO) $data(filename)} result]} {
                                log "error: $result"
                                status "error reading" black red
                                after $var(gumball_delay) status
                        }
                        # tags section, tag data on second line
                        foreach {str tag} {
                                "Name: DATE_RELEASED" @day 
                                "+ Title:" @nam 
                                "+ Date:" @day 
                                "+ Writing application:" @too 
                                "Multiplexing application:" @too 
                                "Pixel height:" @grp
                                "+ Name: COMMENT" @cmt
                        } {
                                set idx1 [string first $str $result]
                                if {$idx1 > -1} {
                                        set idx1 [expr $idx1 + [string length $str] + 1]
                                        set idx2 [string first "\n" $result $idx1]
                                        if {$idx2 < 0} {set idx2 end} else {incr idx2 -1} ;# remove newline
                                        set data($tag) [string range $result $idx1 $idx2]
                                        log "$str: $data($tag)"
                                }
                        }
                        if {[string length $data(@day)] > 3} {
                                # make date format YYYY-mm-ddTHH:MM:SS+zz:zz
                                set data(@day) [clock format [clock scan $data(@day)] -format "%Y-%m-%dT%H:%M:%S+00:00"]
                        }
                }
                default {
                        log "unknown file type $ext"
                }
        }
        # if title is empty, copy filename
        if {[string length $data(@nam)] < 1} {
                log "Title field (@nam) empty." warn
                filename_to_title
        }
        # restore locked data over anything read from file
        foreach v [array names data] {
                if {[info exists lock($v)] && [info exists lockdata($v)]} {
                        if {$lock($v)} {set data($v) $lockdata($v)}
                }
        }
        status
}
# copy filename into title field, minus year if present
proc filename_to_title {args} {
        global data
        set f $data(filename)
        # find year
        set idx [string last ( $f]
        if {($idx > -1) && ([string index $f $idx+5] == ")")} {
                set year [string range $f $idx+1 $idx+4]
                if {[string is integer $year]} {
                        set data(@day) $year
                        set data(@nam) [string trim [string range $f 0 $idx-1]]
                }
        } else {
                set data(@nam) [string trim [file rootname $f]]
        }
        log "copied filename to Title field" warn
}
# write file tags
proc write_file {args} {
        global var data lock lockdata
        if {[info exists data(filename)]} {
                set filename $data(filename)
                unset data(filename)
        } else {log "no filename given"; return}
        if {![file readable $filename]} {log "File not found: $filename"; return}
        if {$args == "clear"} {log "Clearing existing tags..." warn}
        if {$args == "clearartwork"} {log "Clearing artwork only..." warn}
        # check for readonly flag
        if {![file writable $filename]} {log "$filename is not writable" err}
        log "Writing tags to $filename"
        set time1 [clock seconds]
        set ext [string trimleft [string tolower [file extension $filename]] .]
        . configure -cursor watch
        log clear
        set err 0
        status "Writing..." black yellow
        # use appropriate tool for the file type mp4/mkv
        switch -- $ext {
                mp4 {
                        if {$var(overwrite)} {set params "--overWrite"} else {set params ""}
                        foreach v [array names data] {
                                # replace tag with parameter for parsley
                                # omit if data is blank, to remove tag
                                switch -- $v {
                                @nam {append params " --title \"$data($v)\""}
                                @ART {append params " --artist \"$data($v)\""}
                                @alb {append params " --album \"$data($v)\""}
                                @grp {append params " --grouping \"$data($v)\""}
                                gnre {append params " --genre \"$data($v)\""}
                                @gen {append params " --genre \"$data($v)\""}
                                @day {append params " --year \"$data($v)\""}
                                desc {append params " --description \"$data($v)\""}
                                @cmt {append params " --comment \"$data($v)\""}
                                @too {append params " --encodingTool \"$data($v)\""}
                                disc {append params " --disk \"$data($v)\""}
                                track {append params " --tracknum \"$data($v)\""}
                                stik {append params " --stik \"$data($v)\""}
                                tvnn {append params " --TVNetwork \"$data($v)\""}
                                tvsh {append params " --TVShowName \"$data($v)\""}
                                tven {append params " --TVEpisode \"$data($v)\""}
                                tvsn {append params " --TVSeasonNum \"$data($v)\""}
                                tves {append params " --TVEpisodeNum \"$data($v)\""}
                                bpm {append params " --bpm \"$data($v)\""}
                                cprt {append params " --copyright \"$data($v)\""}
                                covr -
                                artw { # only add artwork if filename present and not "piece of artwork"
                                        if {[string length $data($v)] && ([string first "piece of artwork" $data($v)] < 0)} {
                                                append params " --artwork \"$data($v)\""
                                        }
                                        }
                                filename {}
                                default {log "unknown how to assign tag $v"}
                                }
                        }
                        if {$args == "clear"} {set params "-P --overWrite"}
                        if {$args == "clearartwork"} {set params " --artwork REMOVE_ALL --overWrite"}
                        puts "$var(MP4_EDIT) $filename $params"
                        # write to bat file then exec it, to support variable number of parameters
                        # bat file name
                        set batfile "$filename.update.bat"
                        if {[file exists $batfile]} {log "previous edit found, overwriting"}
                        set fout [open $batfile w]
                        puts $fout "rem Script to update movie metadata, okay to delete"
                        puts $fout "echo Applying changes to $filename"
                        puts $fout "\"$var(MP4_EDIT)\" \"$filename\" $params"
                        # delete bat file when done
#                        puts $fout "del \"$batfile\""
                        close $fout
                        debug "created bat file to edit mp4"
                        debug "$var(MP4_EDIT) $filename $params"
                        if {$var(defer_exec)} {
                                # RUN LATER WHEN APPLY BUTTON CLICKED
                                log "success writing tags" ok
                                log "Defer toggle is on, must click Apply to update the movie file"
                                status "Success" black green2
                                after $var(gumball_delay) status
                        } else {
                                # RUN NOW
                                if {[catch {exec cmd /c $batfile} result]} {
                                        log "error: $result" err
                                        status "error writing" black red
                                        after $var(gumball_delay) status
                                        set err 1
                                } else {
                                        log "success writing tags" ok
                                        status "Success" black green2
                                        after $var(gumball_delay) status
                                }
                                # delete bat file
                                catch {file delete $batfile}
                        }
                }
                mkv {
                        if {$args == "clear"} {
                                catch {exec $var(MKV_EDIT) $filename --tags all:} result
                                return
                        }
                        # make date format YYYY-mm-ddTHH:MM:SS+zz:zz
                        set datelen [string length $data(@day)]
                        if {$datelen < 1} {
                                set data(@day) [clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%S+00:00"]
                                debug "created date formatted for mkv (%Y-%m-%dT%H:%M:%S+00:00)"
                        }
                        if {$datelen == "4"} {
                                append data(@day) [clock format [clock seconds] -format "-%m-%dT%H:%M:%S+00:00"]
                                debug "appended month-day-time to the year for mkv"
                        }
                        if {![info exists data(covr)]} {set data(covr) ""}
                        # add artwork attachment
                        if {[string length $data(covr)]} {
                                debug "exec $var(MKV_EDIT) $filename --edit info --set title=$data(@nam) --set writing-application=$data(@too) --set date=$data(@day) --attachment-name normal cover --add-attachment $data(covr)"
                                if {[catch {exec $var(MKV_EDIT) $filename --edit info --set "title=$data(@nam)" --set "writing-application=$data(@too)" --set "date=$data(@day)" --attachment-name "normal cover" --add-attachment "$data(covr)"} result]} {
                                        log "error: $result" err
                                        status "error writing" black red
                                        after $var(gumball_delay) status
                                        set err 1
                                } else {
                                        log "success writing tags" ok
                                        status "Success writing" black green2
                                        after $var(gumball_delay) status
                                }
                        } else {
                                # normal tags without artwork
                                debug "exec $var(MKV_EDIT) $filename --edit info --set title=$data(@nam) --set writing-application=$data(@too) --set date=$data(@day)"
                                if {[catch {exec $var(MKV_EDIT) $filename --edit info --set "title=$data(@nam)" --set "writing-application=$data(@too)" --set "date=$data(@day)"} result]} {
                                        log "error: $result" err
                                        status "error writing" black red
                                        after $var(gumball_delay) status
                                        set err 1
                                } else {
                                        log "success writing tags" ok
                                        status "Success writing" black green2
                                        after $var(gumball_delay) status
                                }
                        }
                }
                default {
                        log "unknown file type $ext"
                }
        }
        set time2 [clock seconds]
        set timed [expr {$time2 - $time1}]
        if {$timed > 2} {log "elapsed time $timed seconds"}
        # confirm by re-reading file
#        if {!$err} {read_file}
        . configure -cursor {}
        update_que
}

# update que count
proc update_que {args} {
        set n [llength [glob -nocomplain -- *update.bat]]
        .f1.process configure -text "Apply $n"
}
# tooltip balloon help
proc tooltip {w h} {
        global val
        bind $w <Any-Enter> "after 500 [list tooltip:show %W [list $h]]"
        bind $w <Any-Leave> "destroy %W.tooltip"
}
proc tooltip:show {w arg} {
        if {[eval winfo containing  [winfo pointerxy .]]!=$w} {return}
        set top $w.tooltip
        catch {destroy $top}
        toplevel $top -bd 1 -bg gray
        wm overrideredirect $top 1
        set font TkDefaultFont
        if {$::tcl_platform(platform) == "unix"} {set font fixed}
        pack [message $top.txt -aspect 10000 -bg lightyellow -font $font -width 450 -text $arg]
        set wmx [winfo rootx $w]
        set wmy [expr {[winfo rooty $w] + [winfo height $w]}]
        wm geometry $top [winfo reqwidth $top.txt]x[winfo reqheight $top.txt]+$wmx+$wmy
        raise $top
}

# create GUI
wm minsize . 150 150
wm title . "Movie Metadata Editor 1.5 (FB)"
#wm protocol . WM_DELETE_WINDOW quit_program
catch {wm iconbitmap . -default Metadata.ico}
wm protocol . WM_DELETE_WINDOW quit
set f .f1
frame $f
        pack $f -side top -anchor nw
button $f.choosedir -text "Directory" -command choose_dir
        tooltip $f.choosedir "Choose a folder of files to tag"
image create photo pic_folder -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9
kT1Iw0AcxV9TpaIVBzuIOESoTi2IijhKFYtgobQVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg
+AHi5Oik6CIl/i8ptIjx4Lgf7+497t4BQqPCVLNrAlA1y0jFY2I2tyoGXtGHIEIYRURipp5IL2bg
Ob7u4ePrXZRneZ/7c/QreZMBPpF4jumGRbxBPLNp6Zz3iUOsJCnE58QRgy5I/Mh12eU3zkWHBZ4Z
MjKpeeIQsVjsYLmDWclQiaeJw4qqUb6QdVnhvMVZrdRY6578hcG8tpLmOs0RxLGEBJIQIaOGMiqw
EKVVI8VEivZjHv5hx58kl0yuMhg5FlCFCsnxg//B727NwtSkmxSMAd0vtv0xBgR2gWbdtr+Pbbt5
AvifgSut7a82gNlP0uttLXwEDGwDF9dtTd4DLneAoSddMiRH8tMUCgXg/Yy+KQcM3gK9a25vrX2c
PgAZ6mr5Bjg4BMaLlL3u8e6ezt7+PdPq7we1+nLCDPs9JgAAADZQTFRFYQAAAAAADw8PHx8fLy8v
Pz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v////7d6FnAAAAAF0Uk5TAEDm2GYA
AAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5AsMFCQwsBmnOwAAALFJ
REFUOMvN00kShCAMQFGZhMiY+1+2EWSoDpTL7r/1FUbA4/iPsPXyeEMQvW75lcAoWUuGFfBiBvR1
MxAu9tIKzJm2yg4oTKmIHRAAYPNMHQhJY3oCVyQFdt4glH3gHkmhrhBVmSlQAMzeQ1ag6fMkWBrg
osAxwAEiXcBwP8C5GFHost0VOApsHrEDRd8QOa/nVQAkUv3GB3AN3xmungPP+6FWnaFdGdy2vNb4
8g/8pA80MRt8RpVCvwAAAABJRU5ErkJggg==}
        $f.choosedir configure -compound top -image pic_folder
        
button $f.refresh -text "Refresh" -command {
        # separate mp4 and mkv
        set filelist [list [lsort -dict [glob -types f -nocomplain *.mp4]] [lsort -dict [glob -types f -nocomplain *.mkv]]]
        set filelist [join $filelist]
#        set filelist [lsort -dict [glob -types f -nocomplain *.mp4 *.mkv]]
}
        tooltip $f.refresh "Refresh file list"
image create photo pic_refresh -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBclpPgIVwAABT5JREFUWMOtV19oU1cY/93MSNMWG1tdyWwJbk4tOLKHBmwrJb4I
m6OrI7WOCSl1SB982HwQOp+kb5kDcYQWKqxDwb1oIXQvfZgaKEpFMHcPtSGSxNFebdOm2W1iTJr8
9rDcLKc3aRrdgQPnz+/7vt853znfd46EKgtJCYAZQMOmqagkSevV6pOqMNwK4GsAn5chsAIgAmAa
wG+SJMXxroXkDpLtJCdYXVkh6SZ58K13gORuAD8C+ArAbgB4/fo1Hjx4gHA4jEQiIeAbGhpgs9lg
t9uLhxcAjAL4SZKkVDUr/5TkLElms1kuLi7S4/GwpaWFALasR48epdfrZTQa1XYjS/JWfkHbNh7S
pCcmJtjR0VHR8Oba19dHn89X7BZvRRIk95EMkqSqqhweHq7a8OZ6+/ZtvnnzRiNxi2RNOeO1JH8n
yWQyyUuXLr2z8WISRe64XI6Ak2Qmk8nQ7XYXhGVZZjAYpCzLQn369CkvXLiwbRLT09MaCYXkJ5uN
12h+n5ubY3Nzc0Hwxo0bTKfTJe/a1atXdYZaW1tpsVh04ydPnuTS0pIm+stmAt9oJ97pdOqEZVmu
SKC2tpYTE/+Gi3IunJyc1ESTJD8oDjbTJPno0SOd0MDAQPGVKkugo6ODiqL8F4lWVjgwMCDoOnDg
AJPJpAa5DAAGAC0APgaAmZkZ3dkYHBxEU1NTxeu7traGdDpd6Dc2NmJwcFDABINB3L9/X+t+RtJo
ANAEYE8ikUA4HBYEOjs70dXVJYzdu3evJIG5uTl4PB5hrKurC52dncLY/Py81mwG8L4hn1TqVVWF
3+8XwGfOnIHBYCj0p6amcO7cOTx+/LgkCbfbjTt37hT6BoMBZ8+eFVNmNKrtVD2AegOAjwBgY2MD
L168EMDHjx8vtHO5HGRZRigUQn9/P8bHx0u6TJZlZDKZQt/hcAjzS0tLSKVSAGACULsj30Aul0Ms
FhPA9fX1hXYikSgQDIVCOH/+fMldePXqFVKpFIxGIwDAZDJteXYMAJ4DgNFoxOHDh3XbpZW6ujpY
rdaKh9FqtaKurq6kjnIE1gHQaDTCYrEIkz6fT/CnzWarSMBmswnnplgHABw6dAi7du0CgFUAqyB5
hOTyxsYGR0ZGhHtrt9uFe59Op7dMUMPDw7qoabfbBcz4+Lg25SfZCJK78x3evHlTp9Tr9QoKVVXl
2NgYu7u7C5ju7m6OjY1RVVUBOzU1pdP37NkzbfpucSi+TpLxeJwmk0kQOHXqFJeXlwXF2WyWiqIw
HA4zHA5TURRms1kBE41Gefr0aUGX0+kshvQVE2gnmSbJ0dHRklsbj8e3/SCMx+M6V5nNZj558kSD
hEql419JcnV1lf39/ToSQ0NDjEQiFY1HIhEODQ3p5K9cuaKdjxxJVykCB0n+RZIPHz7k/v37dUpM
JhPdbjcDgQAVRWEsFmMsFqOiKAwEAnS73ToXAmBvby8XFxc1jjNln2Ykv9VQs7OzJUlo1eFw0OVy
0eVy0eFwlMWdOHGCgUCgOA0fqfQu/JlkhiR9Ph97enre+il28eLF4pX/TfKL7byKa/IkSJIvX77k
tWvXSm5tubp3715OTk4W5/4kyS9JvlfNx+R7AD8A2AMACwsL8Hq98Pv98Pv9kGVZwB87dgzt7e1o
a2tDT0+PFu0A4E8A30mS9Ee1PyMJQBuAEQC9+bANVVWxvr4uZDwAqKmpgdlsxs6dOwv5C4AHwHVJ
khbe5X9oIOkgeZdkgGRii1u4lo+q10l++H//jiUAFgD7AFgBtG6CzANQAIQlSYptV+8/hTq9Na83
oLgAAAAASUVORK5CYII=}
        $f.refresh configure -compound top -image pic_refresh

button $f.play -text "Play" -command play
image create photo pic_play -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBcdjPqwyQAAAh5JREFUWMPll8FqE1EUhv+pYtwoaaDb4qIIgpIiQ0UIXRi60AmC
q0CgWelCUguCi7gp5CHyAN258DFmUSQUAtUuTLoJkyhJJomRxhIyn5u5EBAhYnM3vXAWcxnm//jn
3HvOka76chZ5Cbgl6a6kRLzVchynZYUQuA18AH4AF3E0gCe2AB4B59PplHa7Tb/fJ17fgawNgG0g
6nQ6eJ5HsVik1WrNQ+SsAARBgOu6SCKTycxDdIGnVgEk4XkezWbTQPSBglUASaTTaRqNBlEUAYTA
C6sAktjZ2eH09NQ4MQBeAo41AElsbGxwcnJinBgBu8CKNQCTmPV63TgxBt5cCsSiAJJYX1+nVqsx
m80AfgKvgevWACSxtbXF0dGRceIceP9fEP8KIIlUKoXv+8aJCfDqb99fWcbRDcNQ+/v78n1fkm5K
emcVQJKSyaRWV1fN44XVX7C5uTl/QQ2B59YAcrkcZ2dnJglDYNfaKchmswRBMF+knlm7BwqFAp1O
x4h/u7QyvQhAPp+n1+sZ8QDYtlYL9vb2CMPQiDeBx9aqYalUYjAYGPEG8NBaOS6XywyHQyP+Bbhv
rSGpVCqMRiMjfgzcW3ZLNut2uxwcHFCtVhmPx0b8E3Bn2U3pA6AfRRGTyYTpdGrE/aWLxwDXgLfA
5zjRvgIfgTWbo5kjaU3SjXir5zjOr6sxvQIrQCIIgoTruol4QP0jDg8PE4CJhTug35pCRdYcVPz6
AAAAAElFTkSuQmCC}
        $f.play configure -compound top -image pic_play

button $f.prev -text "Prev" -command {
        set x [.f2.f1.list curselection]
        if {[string length $x] < 1} {set x [.f2.f1.list size]}
        if {$x > 0} {
                incr x -1
                .f2.f1.list selection clear 0 end
                .f2.f1.list selection set $x
                .f2.f1.list see $x
                read_file
        }
}
        image create photo pic_prev -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH6AcJEzEgGriahgAAAgJJREFUWMO9lzGLGlEUhc+TFbVI
YMVCjP6IgH/ANDZJ2tinCVttmoiQOloEUgim00rt0tuksdhUcfsEtFkiOqzMEBgDm/3SOGE2jI5m
nTlwu8e93znv8d6MdE8BaeCh4haQAF4CF8BXoAOcxjXcAK8Al7t6H9fwujfccRzm87kH4Mbq3LZt
6vU6g8HgbwSxObdtm0ajgSSGw2G0AEHOveGRA+xyfgyAkz3WPJVUl5R2HEetVkvNZvPOgkKh4Ad+
vaPXtaQvkr4ZY273cf8CuN7m3KvZbMYBugKehCYAPJP0UdLper0OdO5psVgolUrtNGOMUTabVTKZ
LEh6K+mzJJltzv3De72ezs7OtjYvl8vKZDKhifb7fZVKJUn6aYx5sNW5F7vrunS73cDY/6em06m3
DZehe+66Lp1O52jDQwGAx8AyCuf7AryLyvkugISPIStJ6/Vak8kk9uddwHPgFmC5XFKtVmPfgtTm
o+K3B1Gr1eID8EE0/RDHTCIUIOokggBOAq7MX5sHxZb0JpfLJdrttlarlUajUSD0eDxWsVgMPWf5
fP6gQ7l3Ej5n++gGOD8EIvRM/ANwuaMugHMgc9Qk/ABR3RE7k4gcICyJWAC2JVGpVOIDCErCsiws
y4rnx2RbEj5143y80sAH4PumPgGPDu1j7vvPIMm7An8YY24O7fEHG8pO3TtfA0kAAAAASUVORK5C
YII=}
        $f.prev configure -compound top -image pic_prev
button $f.next -text "Next" -command {
        set x [.f2.f1.list curselection]
        if {[string length $x] < 1} {set x -1}
        if {$x < [expr {[.f2.f1.list size]-1}]} {
                incr x 1
                .f2.f1.list selection clear 0 end
                .f2.f1.list selection set $x
                .f2.f1.list see $x
                read_file
        }
}
        image create photo pic_next -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH6AcJEzEnhNwPJQAAAl5JREFUWMPNlr+L02Acxp8cpZgc
CHXpoDek0qFwCKWr7dCMgtx4iiBCu+jUwcFZcPAf6NAKLqKLIIpwS2bD3VAHFyk09Bc1luCWppTk
cWmOXJrrtZG++IUXkveF9/18n/d9n+8LJAySeyT3ITqWCz8keULyjORLktdEAtwj6fJivCeZEQXQ
IknHcWjbdhjisxAIkm9Jst/vM5/Ps9friVUiDCDLMjVNi0LsVokwgKIoBEBVVcUpEQcAILESUmTy
PIAjAIfLiePiLoDbg8EAhUIBjuOcD6iqCl3Xoapq0PUBwFNJkv5sklme5E9uGFEFkiohhQCeA3jt
eR5s24bv+2uBR6MRKpUKZrPZytg2SqRC34cAYNs2Go0GhsPhWgDXdeG6buyYaZqo1+totVoBxDGA
fZKPL92O4HBNJhOWy+UVaZO0TW7H3i49I1DCNM2g6xjAG5LXhQAAgK7r0DQtDHEE4IkwgECJWq0G
y7KCg/9IKEDckRMKoKoq2u02stls0PVJGICmaVFP+AKg+d9cw1TUFSVJQjqdhqIoazPzfR/z+Rwk
L808ZERB5ituGLbiVwBeLBYLWJZ1pRWPx2NUq9VYN0xUlEjeIfnrX4tRsVhkt9vduBiFt+AHgPsA
HgC4cVU5jhuQZRnNZhO5XG77crzFg+RjnAKlUomGYYQzP9nJq4jk9yiALMs0DIOe5wWLn5LM7upJ
dgEgJvMzkge7fJSeA2QymbjMD3Zr4uQ3kpxOp+x0OuIyDwE8I+lHbuWpkMWXABmS70iay/aV5M2k
80kJIVIAbi1/f0uS5CQF+Av6cTDVWV9omwAAAABJRU5ErkJggg==}
        $f.next configure -compound top -image pic_next

# write all metadata back to file, overwriting
button $f.write -text "Write tags" -command write_file -borderwidth 4
        tooltip $f.write "Write tag fields to file"
image create photo pic_save -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBcrQ0AlUAAAAy9JREFUWMPtl99LU2EYx9+dQSKWUwYh3pQERUFBQUQIE/SyLiL6
XZJpQ2gNhbzqIuheRaY0g0IQRPyxi4E3EnghaJY3kfU3CHPb0e1I7ezHp4vOO16XO6dVC4JeeO/O
eT7f5/t83/dsQvxfVViAG7gDJPi+PgBn/qaAB0CcveszcLHaYBfwENABdF1ncXGRQqEAULBEnKsm
/K60PRaL0dvbS1NTE9FolHw+L534BJythoBuaXsymaSvrw8hBEIIGhsbmZ2dLR3HhT8F1oD7QFLC
Ozs7i3C5W1pamJmZkU4ULCeO2RX2AIcBzUHAHWALYGtra0/npdvr9bKwsKCO43a5ok+so/MReAUc
srE9IQMXCATKwuWemJggl8tJAb2lBQ8Az4CvyrxywAvAU+6cJxIJuru7bcEej4dQKEQ6nZZ148D5
0lk+BXZlijc2NtTgvAQOWs/eBWIA8XicgYEBx87D4bAKTwLXAbd6hJ4DXwBSqRR+vx+fz8fy8rJ8
KQu8Bh7Jzre3t/H7/bbgmpoawuEwhmHIOingUjFbQI3V+Rdpp5pin8/H2tqaGhxk54FAAJfLZStg
ZGSEVColX4sBV/cEG3gMGLJoMBj8oUh7ezurq6tFuK7rP2X7+Pi4anvKsl0rDV4cwDAMurq60DRt
32Ktra2srKyg6zo9PT22YK/XWxq4beDyvkfaSjlLS0uOHbW1tdHR0eH4XIntceBm2fsEeANgmibR
aNSxeIVpN6yZu+1usqPAW6BgmiZTU1M0NzdXDK6vr2d4eFjtPAlccbpJpYiTlghyuRxzc3MVCwiF
Quzs7BRzal1U7ko+KtIJMpkMkUiEhoYGR7CmaYyNjamdG1bn7l/5sh0H1gCy2SyTk5PU1tbaChgc
HFQ7jwM3fsp2h3EURUQikbLw0dFRFZ6u2HaHcbwDCplMhunp6T3BrKurY2hoSLU9/cu224g4ZYko
jkMK6O/vZ3NzU8ITwL0/CldEnJAiTNNkfn6eYDCowneBW1WBKyKOAe+BQj6fJ5vNqtfrtarCFRGn
gXXlY7hj/Q78bbirAhFHhBCXhRC1QogVIcS6y+XK/f8f+M+vb2N0CS88sgyfAAAAAElFTkSuQmCC}
        $f.write configure -compound top -image pic_save

# apply updates to mp4 files which can be slow and take minutes
button $f.process -text "Apply" -command {
        # get list of bat Files
        set files [glob -nocomplain -- *.update.bat]
        set num [llength $files]
        log clear
        log "$num files to update in [pwd]"
        set count 0
        foreach w [winfo children .f1] {$w configure -state disabled}
        update
        foreach f $files {
                set now [clock seconds]
                incr count
                # update progress in text widget
                log "$f"
                update
                if {[catch {exec cmd /c "$f"} result]} {
                        log "        error: $result" err
                } else {
                        # elapsed time
                        set delta [expr {[clock seconds] - $now}]
                        log "        complete in $delta seconds"
                }
                # delete bat file even if error occurred
                if {[file exists $f]} {catch {file delete $f}}
                set n [expr {$num-$count}]
                log "$n files to go"
                .f1.process configure -text "$n to go"
                progressbar [expr {100*$count/$num}]
                update
        }
        foreach w [winfo children .f1] {$w configure -state normal}
        progressbar 0
        update_que
}
        tooltip $f.process "Apply updates to MP4 files (can take minutes)"
        image create photo pic_apply -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH6AcJEzETpWj7kAAAA4tJREFUWMO1l0tIa1cUhr9zkhqj
OFDx1aLUiSI+msT4QMoVRYhWB4LKvYiTCI6KIeCsDoQWHBQ66AUdVAgIEQMKEqSWgKmIqFcQtQOH
egY2Uky8DkTTesHVUYI3BvN0wRnsvRdnf/tfaz8WACJiEJHvRORPyb79LSI/iciXxDMR0YvIexH5
V17XPohIZez8ioi8Af54fHzMOz8/x+VycXd3R7asu7ubvr4+cnJyAOYVRfk+VoFfRUQCgYCYTCYB
sv4tLy9HVAiLSGGsAvtAu9/vp6enB6vVyuDgILm5uRmvfmdnB6/XS2trKwcHB5Fuk6Iof0UaesAI
cHV1BYDFYsHhcFBQUJAxgNFoxOv1vuijj0qhKJGQICJRh4uLCw4PD6msrKSqqorS0tKkAZ7+JyFA
PAuHw0xMTODz+QCw2Wz09/djs9moqanJSpKqLw0GAoHo5AA+nw+Hw0FtbS0jIyMcHx9zfX39egDV
1dV0dHRE2y6Xi6OjI4aHh1ldXcViseB0OgmFQq8DoNPpsNvtAMzMzDA6OorZbGZlZYX9/X2mpqZw
u92MjY2lDaEmcujs7ASguLgYg8EQ7W9vb6e3tzcamsnJybQgEgIUFRUxNDTE9vb2s3hHti6Ax+NJ
S4mEAIWFhVitVjY2Nj47ojVNY3Fx8TPfdJTQJyRUVZqamgiHw/j9frq6uri/v2dgYABN0575ezwe
bm5uWFpaip4tGQFELpSSkhLGx8eTWpXP58PpdGI0GjMPAUBubi7r6+s0NzcnLa3b7WZhYSHe0Fcp
AwC0tLQwNzeX1KoS2JyImFIGUFWVtrY2tra2UlICIBgMPk3Mr4HfROSLlAAyUULTtNjd0QK8SQsg
XSU8Hg97e3vPckFNN5DpKBHvKk8bIBUlVFVlfn4ekymae5+AzYwAUlGisbERu93+9Jn3i6Iol1kB
SEYJg8HwdPLfgZ8zDkEiJXQ6HWVlZfFcf1QU5ebZURx5vz08PHB7e5sWRF1dHV6vl7W1Nerr67m8
vGR2djbW7b/YuyAAfNPQ0ADA7u4u09PTkUIirZCoqsrp6SknJycA5Ofnv/hy/UFEJBgMyrt377Je
lJSXl8vm5makMPkYrzSrB/wiUhYKhTg7O0PTtKSu0kSWl5eH2WymoqICvV4vgFNRlPfxVHgrIv+8
YmH6SURWYssyAOUJRD3wFvgWKCJ79gFYB7YURbmPHfwfVdHBbH7urkkAAAAASUVORK5CYII=}
        $f.process configure -compound top -image pic_apply

# remove all metadata from file, used when tool throws error on read, to allow edits to succeed
button $f.clear -text "Clear tags" -command {write_file clear}
        tooltip $f.clear "Remove all metadata tags from file"
image create photo pic_clear -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBcDdvWNqgAAA1FJREFUWMPFlz1IG2EYx/9vE5WIBqzix1DKKc4iFexYIlUQOhSE
RhxqxAwdVFBQRAQdNBgEcXERkquDZ8EM0kULLk4dOnXQFjFHoH4UT8UTokPMv0vvSEy0d/GgDxzc
Pc+99/zu/7yfAhlGUgBoAfAWwEsAT2HNzgF8BbAmhPiOQoykIDlG8pyF2wnJd3byigyAZgBf0ul0
1cnJCXZ2dix/pLS0FK2traipqQGACwDvhRCf7SowS5KHh4dsbm4mAFuXz+djPB43lDi3qwRIyiS5
sbFhO7lxSZJ0F+LNv/K67zqSyaR5v7i4iOrq6odrKAQ2NzchyzJUVUUwGMTy8jIkSaoA8JHkByHE
J8sKKIpi/pGqqpZ6XjKZ5Ojo6KOUeBQASV5dXWVBtLW12esTjwUgyaOjIwYCgcKUcAKAJI+Pj7Mg
LCvhFMB9SiQSCSN8QPJZwQCpVIqpVOqf/rtKdHd3U9M0Izxg5H1iZ67QdR0TExNYWVnBzc2N6dc0
DZ2dndje3kY6nQYA1NbWYmZmBoFAAACgKAr29/eNJi9sK3B5ecnx8XEztrS0xOvra56entLv95v+
9fV13t7emu12d3fN2OrqquGWbQPIspwz80UiEXZ0dGT5XC4X9/b2zHaqqpoxRVFyACyXoL293ZTT
sL6+PmxtbWX55ubmUF9fb7mslgHq6uqyappvRZyfn8fIyAiKi4udBzAgxsbGUFlZmRPz+/0IBoO2
9yG2ADRNw9DQEM7OznJikUgEsVgsa3Q4CqBpGgYGBnJqfrdPRKNRWxCWAWKxGNbW1sznoqIihEKh
nD4xOzuLRCJhGcBt9cWenh7E43GEw2EAQCgUwuDgoFmOaDQKAFhYWEBDQ4PzAGVlZZiamoLL5UJV
VRWGh4ezZrySkhL4fD50dXXZ6gNuOy97PB5MTk7C7XbnjI5wOAyPx2N7FLjtNrgvSXl5eUHHgSf4
z+bOsyaY99PT0/B6vY9KoOt63m/fC5C5C5Zl2dG/lSTpwRL8BICmpib09vY6LnV/fz8aGxuNx2/5
jmYNALYBPL+4uICu63klK8SEEPB6vaioqACAAwCvhBC/8u0JXpP8QTJN5+2W5B7JlryH0wyI2r9H
c8nhKsQBfBVC/M50/gG1d08Kc4+JFQAAAABJRU5ErkJggg==}
        $f.clear configure -compound top -image pic_clear
        
# read file and send to console all discovered metadata
button $f.alltags -text "File Metadata" -command full_info
        tooltip $f.alltags "Show all metadata of file in Console"
image create photo pic_metadata -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBcXbC9Z1wAAA5pJREFUWMPtl81LY2cUxn/vzVVjokMiWOkiNRalYEm0EiKIaael
m4ogrYKLFqQT3cymTHEziyIO9Q+o2JXYRaF1MaJ0Y3cWlCxaYz8EBbtoQr+EGU3mphkjOsnpwptw
negwXhO66QMv9+M9957nvPe8zzlX8RRE5AXgFeBV89YOsKeUemDOa0AjINiDAFmllAAoi2MN+BD4
2CTgMKfywB7wKZAD7pgEuAaBB8AnSqm4lcAt4HPAmclkyGQyAHg8HhoaGopEsBC7Ln4FbirT+cvA
T8CNZDLJzMwMa2trAAwMDDA5OUlraysAmUyGlZUVstmsLa8tLS0MDg7idDofAW8Wo/9MRMQwDGlr
axNzmUojGAxKLpcTEZH9/X2JRCJlNs87JiYmxDAMEZFHIvKaJiJ1QBhgdXWVRCJRxnp7e5uNjQ2q
AQ2oMQeGYVxqWMyJihNQSmXNrKS7u/tSw66urqoQ0M3jMvBOb28vU1NTTE9PnzOanZ2lvb39jLGm
4fP5iEQithx6vV40TStdF3eBG/gWiORyOeLxOEtLS7jdboaHhwkGg9TU1JyJQj7P4eEhhULBXsS6
TlNTE5qmpUq7wCTxkojE5GIUROR7EflBKoMnIrIgInWltVBK/Q68B3wE/GIh/SNw25zbq+DnTwAn
z6+fImEReSyVw58i0q5fgXEj4EqlUiwuLnJ8fGwr7ObmZkZGRnC5XI1Xqiki8lZVlJD/GP8TuEoS
KoDa2lpGR0cZGhqynYS6rpfepy5INh9wC3jd7IA2gS+AF4HvAFeFgv8LuGl17BSReyJydMGePRKR
u6YaVgpfiYhubcnuAXcBPZ1OE4vFqKurIxQK4fV6iy2ZXPGzPQu/AW8Xi1EX8DPA1tYWY2Nj7Ozs
ABAKhZifny+V6nQ6zdzcHAcHB7a8dnR0EI1Gqa+vN6wt2ZciIolEQvx+f5l4dHZ2SiqVKrVk/f39
toVofHy8rCVzAh0A6+vrJJPJMta7u7vEYjEshcv2uj/9rGa22TrA6enppQ+enJxUR4iUUo+BPwDC
4TAOR3nb73a76enpqaoQLQLvBgIBFhYWiEaj5PP5ktHy8jJ+v//sr8ThwOfzEQ6HbTn0eDzngrRu
w/vAcKFQUJubm8TjcXRdp6+vj0AgUI3gHwJvWIXIKyL3nyEcX5ttVCVwJCK3zwmRhcj7wAdAi3nr
b2BeKfWNRaqbrivDSqmDC2uBhcgN8/Sf4q90NfAvDrs9IzerBxgAAAAASUVORK5CYII=}
        $f.alltags configure -compound top -image pic_metadata

# intended to apply all LOCKED fields to all the files in the current folder
button $f.writeall -text "Tag All Files" -command {
        set stop 0
        set max [.f2.f1.list size]
        if {$max < 1} {log "No files in folder."; return}
        log "Tagging all files in folder..."
        for {set n 0} {$n <= $max} {incr n} {
                .f2.f1.list selection clear 0 end
                .f2.f1.list selection set $n
                if {$stop} {break}
                log clear
                log "----"
                log "reading file list item $n"
                read_file
                log "writing $n"
                progressbar [expr {100*$n/$max}]
                write_file
        }
        after $var(gumball_delay) status
        progressbar 0
        . configure -cursor {}
        log "Done."
}
        tooltip $f.writeall "Write tags to all files in list\nUseful when some fields are Locked\nLike Movie, Genre, Comments"
        image create photo pic_tagall -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBYhuo79DwAABXBJREFUWMPNl19ok1kaxn8naZtoUrXpHxSslW1ZXJGmSlU0N0bq
GhgJSge8KEpRelEvFsERKqU33qwovZLtalHZi9WKrmBgcdFRxEpFpOKfVTozHabC1I7tpCWpNdm2
SZ69Md8mNmnH0YF94cB33vO8nOc83/u95/0Mn8EkfQm0ARXAD8B3wLfAADAGfGOMmeK3MEm1kqaU
3+KSrkly5oq3feRmRtIeSUOSxiX9GQgCixOJBFevXuXJkyeEw2EmJiaYnJwklUo5gV1A4+c47R8l
TWacLpU+/cDAgJYtWyZAgAoLC3XgwAGFw2FJmpX0p09SQNLvgG6geGRkhKdPn5JIJAzgAhgaGsIY
Y+FnZ2d5+/YtpaWlAEngR0nFkioklUqy/WICkiqAi0DV9PQ0Z86cYf369Vy6dIlXr17R29vLzZs3
iUajWXFutzv9WAgcBf4FfA/0A/vSJNKb2CQVSXJ8MIol/U1SUpI6OzstmT0ej/x+v8rKyixf5vB6
vbp3754SiYT1zpLJZPpxSFK1eb/574G9gBdwfCBAIdCQSqXsN27coLm5mfHx8Y/KnTt37lBVVUU0
GmVoaIhAIIDL5UoCXyBpnaTvJCmVSuUckpRIJHTixAkVFRXlPC0gh8Mhh8Mxx19ZWSmfz2fN37x5
k1bhL0bSP4EvIpEIjx49YnR0NIt9QUEBfr+f5cuXE4vFuHLlCh0dHQwPD2fhDh06xJYtWzDGcP/+
fc6ePZtXkVu3brFjxw6An5A0LUlHjhzJearq6mr19fVZSszMzGjPnj1ZmNbWVo2Pj1vveWxsTIcP
H86rVHNzs4W1AUUAz549m8O0pKSEUCjE1q1bMcaQTCaJRqO8ePEiC+fz+fB4PNa8vLycQCCQ+RVY
VlpaSiwW+5/C8yWP3++nurraSqTBwUFu377N4OBgFm7x4sVzYu12O4WFhdbcGMOFCxfYvHkzS5cu
/WUEUqmU9RwKhTh9+nRO3MOHDwkEAixatAiAWCzG48ePiUQiFqayspJgMJip1H+Af8xL4Pr168Tj
cZxOJ/X19Xlx3d3drFixgvr6eiTx/Plz2trasjAej4eJiYlMAt8AX5FOhoaGhpwJEwqFJEmjo6N5
kwqQzWZTVVWVVq1aJbvdnhOzfft29fb2Wgkt6csFCRw8eFCSNDs7q127dsnr9erkyZNqb2+fl1C+
0dTUpEgkkt72UMFCVSydhHa7nYsXLyIJp9NJKpXC7XZz/Phx4vF4zlibzZaVRzU1NbS2trJkyRKA
GeDfCypw9+7d/J1GPK6Ojg4ZY+bE7dy5U+fOndO+ffss37Vr1zLDf5L0hwUVqKury7vmdDqpqKiY
4z969ChtbW2UlJQQDAZxuVzU1tYSDAYzYT8Ar+cl0NLSgtPpzLueLkySsvxer9fK9vLycrq6upia
mqKgwNpOwENjzOS8/cDu3btxOBx51xOJBBs2bKCzs5O9e/eybt06ANauXZuFM8ZQXFycVWKAS+k+
IGcO1NTUqL+/XwtZ+sZMJpPWyPjMJEl9fX06deqUHjx4YLkyG5GcBBobG/Xu3Tt9qo2MjGjbtm2q
q6tTNBpNuxsXLMWbNm3KWeM/sonl5cuX9Pf309XVlb6cvgceLdiU7t+//5O76JmZGc6fP8/q1avZ
uHEjNptNwE1geF4F2tvbcbvdTE9P/+rNjTGEQiEuX75MS0sLa9asSV9Af31fNywCcWBRWVmZFdzT
00N3d3dWFfs1BMLhMD6fj2PHjqXdXxtjXmbhJH0NNLx+/Zqenp45rfWnmMvloqmpiZUrV2KMGQO2
GWMGPkyUrZKG9dvaz5IOSDJzlHrfltUCzYAPsH/Gf9ck0Av83RjzhP9H+y8GtqbOn+sMEQAAAABJ
RU5ErkJggg==}
        $f.writeall configure -compound top -image pic_tagall

button $f.help -text "Help" -command help
        tooltip $f.help "Show usage and commands in console"
image create photo pic_help -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBcQ8kvMdAAAAVFJREFUSMfVlL1KA0EUhc+msBAiPoFgaQQLEcTOVoI+QUDExkqf
QVGwsUlrK1ikshQbQaIGLRTFKoWghcRCUAzB1f0sNo7ZuJudnVTeaZa7c87cc/+kf29enBMSLntW
BCH8UVXVdK17jWtK05rRUAJFDMEbZdR1JjkDkmLrIjj6Aw9PLYYiF0dxJ2lXz/rUq65UMv41tWze
D5hjg19rUDAx3KaLgAfEjbkIsG4ITm0k1FXUWMSTN1/DNhKIpAp8Zk0lmnZ1iNIdGwEHtoXshD8x
0YZv4Wd/v8lyG17mIzs8YBsh8hwSuKgPO7JIPav2n4koIFZ5cYFLcI4o8Z4CzyX/akla0WDKEPcg
GJE0kLoDehCMake4rbToYusdgZcGl1sOQKpqSQuqyJdTES/NCO25dcFmxzr9cusDq0wnR3BhIth3
kwAnLDJPJWWM+y5j3/YND6wkTBwWJDsAAAAASUVORK5CYII=}
        $f.help configure -compound top -image pic_help
        
button $f.unlockall -text "Unlock all" -command {
        foreach n [array names lock] {
                # toggle all locks off
                if {$lock($n)} {set lock($n) 0}
        }
        # update symbols
        catch {.f2.f3.lock configure -compound top -image pic_unlocked_16}
        for {set x 1} {$x <= 16} {incr x} {catch {.f2.f4.lock$x configure -compound top -image pic_unlocked_16}}
}
        tooltip $f.unlockall "Unlock all fields"
image create photo pic_unlocked -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA
CwwAAAsMAT9AIsgAAAAHdElNRQfoBR8RNSfshkVsAAABHUlEQVRIx+2VoU4DQRCGP5qCI6UBBA9B
05YmSEgdCCwGbH0FkmCw1GBwNfQF6gFTR9ISEpJqcEVACKGo+xG3ucBey+7mQkAwo25m/m925m5z
kNFmAmp32WGJIedchzcq0UPG3zmhGCavcp/IY++y4C9f5s6SC3HsDzgiQogRTbY4SwbZ8O0fCx5Y
NZEGY4ToMesDOEWIN7aTSJ4OQryw5pYvcosQF8x9iq6YMxy6ATVeEWLPircRYugG7JuF2bZpNuO0
eOdXEzKjGJBzANYBeKSeylinksOfKU/vUnfKv5k3l/U6Zwbkvzwd0LfyES0q/oA+l6mKpx8e4R9g
A6IJFQr5DlqplyaqIYDKL+3A7/d2Mz1VYOC8ygPm+aP2AfGtlRA73z9TAAAAAElFTkSuQmCC}
        $f.unlockall configure -compound top -image pic_unlocked


# top row buttons in this order
# unused widgets: $f.help
        grid $f.choosedir $f.refresh $f.play $f.prev $f.next $f.write $f.clear $f.alltags $f.writeall $f.unlockall $f.process -padx 3


# use top row buttons as a progress bar with background color highlight
# input: number percent (0-100)
proc progressbar {pct} {
# toggle button backgrounds on if pct >= their position up to 100
        set buttons {choosedir refresh help play prev next write clear alltags writeall unlockall process}
        # number of button to light up
        set num [expr {[llength $buttons] * $pct / 100}]
        foreach w [lrange $buttons 0 $num-1] {.f1.$w configure -background green3}
        foreach w [lrange $buttons $num end] {.f1.$w configure -background SystemButtonFace}
        update
}

image create photo pic_locked_16 -data {
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA
CwwAAAsMAT9AIsgAAAAHdElNRQfoBR8SACzbI+ALAAAA2UlEQVQoz4XQLU4DQRwF8F+725YgSBCQ
4EuCQlBHVuAJd6jiErjegIoK7kCCImiSShIMGJDgEAhoaJd2EZ0ls1sS3sh5X/+XiNFxpOfNxJ/Y
culL7lGmsfqdGvl279rUg51VwoFPt7Z1DOTO6h5NA1MnYNOzO2tVQsuNFxsSiZaRid0yeYk9PXND
C7Cv7di5otQnLhS19249dkjxZBw0XZl0WTONeoydhoi+Q6odoLAwh0AL5/2D2KGrH7TR1DEh+01u
aJYnpiH91Udl3MKV2ZJbLtmuhc/k8APBOjlCVIIkYgAAAABJRU5ErkJggg==}
image create photo pic_unlocked_16 -data {
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA
CwwAAAsMAT9AIsgAAAAHdElNRQfoBR8SABtjnkUEAAAA2klEQVQoz4XRvS4EARSG4Wf+kgnNRCPR
KCcalYToRKcRjWjcgrgAqr0C10BLQS1uwAWothEJuw02YtjsjGJZM2MT3ynPe77zF/mrwKIts56U
U7JCux4MvTkxMw1Y1jNwoevTobCdjp15ty+R67o33wYWPLqRInDgw17bYknmSoHKuUIeC21anwAr
YhtO9dHTcUvuVdWI0lF9qFBi5NLz95LbMnEdgKFjdyC1KmuuNVZpBEaq9t3+0dghsmMNJM0GP0Ci
M7GOqLeJDfTNNYpeXNdfG0hbk5SKX48vZ/A5OLH2FA8AAAAASUVORK5CYII=}

# right column container
set f .f2
frame $f
        pack $f -side top -anchor nw -expand 1 -fill both

# filename listing
set f .f2.f1
frame $f
        pack $f -side left -anchor nw -fill both -expand 1
#scrollbar $f.x -orient horizontal -command "$f.list xview"
scrollbar $f.y -orient vertical -command "$f.list yview"
listbox $f.list -listvariable filelist -yscrollcommand "$f.y set" -width 40 -selectmode single
        grid $f.list $f.y -sticky nsew
#        grid $f.x -sticky nsew
        grid rowconfigure $f $f.list -weight 1
        grid columnconfigure $f $f.list -weight 1
bind $f.list <<ListboxSelect>> read_file

# column container to right of listbox
set f .f2.f2
frame $f
pack $f -side top -anchor nw
# filename
label $f.filename -textvariable data(filename) -font {-size 12}
        pack $f.filename -side top

set f .f2.f3
frame $f
        pack $f -side top -anchor nw -fill x
label $f.lbl -text "Type: "
        pack $f.lbl -side left
# file type tags supported by MP4 only, not MKV
set num 0
foreach txt {Movie Normal Audiobook "Music Video" "Short Film" "TV Show"} {
        radiobutton $f.type$num -text $txt -variable data(stik) -value $txt \
                -indicatoron 0 -relief raised -bd 1 -highlightthickness 0 -anchor c \
                -selectcolor $var(on_color)
        pack $f.type$num -side left -padx 3
        incr num
}
checkbutton $f.lock -text "" -variable lock(stik) \
                -indicatoron 0 -relief raised -bd 1 -highlightthickness 0 -anchor c \
                -takefocus 0 -selectcolor $var(on_color) -command "set_lockdata $f.lock stik"
        tooltip $f.lock "Lock this value"
        $f.lock configure -compound top -image pic_unlocked_16
        pack $f.lock -side left
# toggle for mp4 write later
checkbutton $f.deferwrite -text D -variable var(defer_exec) -indicatoron 0 -relief raised \
        -bd 1 -highlightthickness 0 -anchor c -takefocus 0 -selectcolor $var(on_color)
        tooltip $f.deferwrite "MP4 bat files run when Apply clicked"
        pack $f.deferwrite -side left -padx 5

# metadata table
set f .f2.f4
frame $f
        pack $f -side top -anchor nw
set count 1
# table of tags in order of display
foreach {tag lbl} {
        @nam Title 
        @ART Artist 
        @alb Album 
        @grp Grouping 
        @gen Genre 
        @day Year 
        track {Track Num}
        disc {Disc Num}
        desc Description
        @cmt Comments 
        cprt Copyright
        @too Encoder 
        tvnn TVNetwork 
        tvsh TVShowName 
        tven TVEpisode 
        tvsn TVSeasonNum 
        tves TVEpisodeNum 
} {
        label $f.lbl$count -text $lbl
        entry $f.ent$count -textvariable data($tag) -width $var(textwidth)
        checkbutton $f.lock$count -text "" -variable lock($tag) \
                -indicatoron 0 -relief raised -bd 1 -highlightthickness 0 -anchor c \
                -takefocus 0 -selectcolor $var(on_color) -command "set_lockdata $f.lock$count $tag"
                tooltip $f.lock$count "Lock this value"
                $f.lock$count configure -compound top -image pic_unlocked_16
        button $f.clr$count -text "X" -command "set data($tag) {}" -takefocus 0
                tooltip $f.clr$count "Clear field"
        if {$tag == "@nam"} {
                # TITLE FIELD
                button $f.copy -text "C" -command filename_to_title -takefocus 0
                        tooltip $f.copy "Copy filename to Title"
                button $f.rename -text "P" -command rename_file -takefocus 0
                        tooltip $f.rename "Paste Title to filename"
                grid $f.lbl$count $f.ent$count $f.lock$count $f.clr$count $f.copy $f.rename -sticky w
        } else {
                # layout left-to-right text label, entry field, lock, clear
                grid $f.lbl$count $f.ent$count $f.lock$count $f.clr$count -sticky w
        }
        incr count
}
# save or clear lock data from table
# pass in widget number and variable to change
proc set_lockdata {w v} {
        global data lock lockdata
        if {$lock($v)} {
                set lockdata($v) $data($v)
                puts "locked $v: $data($v)"
                catch {$w configure -compound top -image pic_locked_16}
        } else {
                set lockdata($v) ""
                puts "unlocked $v"
                catch {$w configure -compound top -image pic_unlocked_16}
        }
}

# artwork hack
set f .f2.f5
frame $f
        pack $f -side top -anchor nw
set tag covr
button $f.lbl$count -text Artwork -command {
        set f [tk_getOpenFile -title "Select picture file" -filetypes {
        {{JPEG Files} {.jpg}}
        {{PNG Files} {.png}}
        {{All Files} *} } ]
        if {[file exists $f]} {set data(covr) $f}
        # check file size of artwork, warn if too large
        
}
        tooltip $f.lbl$count "Select artwork file to embed\nPrefer JPG under 300KB"
image create photo pic_attach -data {
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
B3RJTUUH6AUfEBY2OV14yAAAA41JREFUWMPtV01IY1cYPS+JCdokWBdFAioB3ZSxI2iFEnRVu9ON
G0WmxkWHgRkRCgm4tMaFMIJQfyhSDOhCdKmUNIXCqNUQiBSVEkEIKqPVIfqGxJeY9OXMQt9DG40x
iZ1ND7zNdy/fd+6537vnXgH3gKQGQAOAlwCeANAiO8QA/ArgZ0EQDpArSH5F8h1zh4+k5a78QhYE
fgPwtSRJWF5ehiiKWRGvqalBbW0t9Ho9ADwXBGEqVwUuSHJxcZEAHvTt7e0pKtxZXJcFBz0ARKNR
AEB1dTVsNpuysjRIkgSPx4NwOIxUKqWEi/IhcAM2mw1DQ0MwmUy3jh8fHyMUCmFtbS2rfLoM0usA
PE2TQ6+HyWSC2Wy+UwGtVpv1gjQZxp4BeJNh/BzACwA/XO15TtDdsXojgNfxePyTk5MTGI3G26b9
A8AP4EcAgiiKWF1dhcVigUajyVuBbwCUeb1eVFVVweFwZMrxJQDMzMygtbUVTqcTRUVFeRMwAsDB
weUBtr29fe9fsru7e3mwCMKDtkCDj4z/CeRLgB+TQDGAxv+cQCQSgSRJSvf/BACpVEr1ikcjUFJS
AgCYm5vD4OAgdnZ2AADJZBLz8/NYWFh4XALNzc1wuVwAgImJCbS3t8Pv92N2dhadnZ2IRCJoa2tD
b28vZFnOs7PIb0lybGyMANjY2EiSjMfj9Hq9LC8vT/P+np4e7u/v8+joiE1NTQTAUCik3AfcBekB
g8GAlpYWLC0twW63q3Gn04nx8XFUVFQUxozuQ319PUZGRlBXVwez2YyOjg4UFxcXzg3/jUQigYuL
CxgMBjVWVlaGvr6+dI8+P1f+kvybUKe75BcMBjE8PIyzs7OMyba2tuByuRAIBHIyputN+AVJORgM
0m63q41mtVq5srLCRCJx494tyzJ9Ph+tVqs61+12MxaLKVNePZSAhuQvJCmKIicnJ9XEpaWlHBgY
4OHhIUkyFovR7XbfIOnz+SjLslI8QPKzXFSwkPSQTJFkIBBgV1eXWqihoYF+v5/9/f1qrLu7m5ub
m0rhFMk/Mj1KsiFhIvmKZJQkT09POT09Ta1Wm3YOOBwOhsPh6zszSdJcEMsk2ULyLyXz+vq62huV
lZUcHR29Xvg9yRcF922S5SRnlCqiKNLj8XBjY4PJZFIJv716Swp4LJD8juTftzxCfydZ+eg3GJIC
yc9JTpH8k6Sf5PckP80l3wcl6a2QCOhG6QAAAABJRU5ErkJggg==}
#        $f.lbl$count configure -compound top -image pic_attach

label $f.ent$count -textvariable data($tag) -width 40
button $f.clr$count -text "X"  -takefocus 0 -command "write_file clearartwork"
        tooltip $f.clr$count "Remove all artwork from file"
checkbutton $f.disp$count -text "D" -variable var(display_thumb) \
                -indicatoron 0 -relief raised -bd 1 -highlightthickness 0 -anchor c \
                -takefocus 0 -selectcolor $var(on_color)
        tooltip $f.disp$count "Display embedded artwork"
checkbutton $f.save$count -text "S" -variable var(save_thumb) \
                -indicatoron 0 -relief raised -bd 1 -highlightthickness 0 -anchor c \
                -takefocus 0 -selectcolor $var(on_color)
        tooltip $f.save$count "Save embedded artwork to file"
 grid $f.lbl$count $f.ent$count $f.clr$count $f.disp$count $f.save$count
                
# text widget console
set f .f4
frame $f
scrollbar $f.x -orient horizontal -command [list $f.txt xview]
scrollbar $f.y -orient vertical -command [list $f.txt yview]
# 80 columns, 10 rows
text $f.txt -width 80 -height 10 -bg white -wrap word \
        -undo 0 -maxundo 0 -font {Courier 8} \
        -xscrollcommand [list $f.x set] -yscrollcommand [list $f.y set]
        pack [ttk::sizegrip $f.grip] -side right -anchor se
        pack $f.x -side bottom -fill x
        pack $f.y -side right -fill y
        pack $f.txt -side left -fill both -expand 1
        pack $f -side bottom -fill x -anchor nw

# MAIN LOG ADD ALL KNOWN COLORS AS TAGS
foreach {tag fg bg} {
 debug        black gray70
 ok black white
 warn        black yellow
 err        red white
} {
        $f.txt tag configure $tag -foreground $fg -background $bg
}

.f1.refresh invoke

2024-05-31 FB I am actively updating this script and will backport the changes here when it is tested stable again. Added artwork to MKV files, images onto the buttons, and more error handling and console support.

2024-11-27 FB replaced script with latest version, currently testing with TCL v9.0

[Category movie] [Category Matroska]