can't read "::snackAmpSettings(ebg)": no such variable
    while executing
"option add *ID3Editors*Entry.background $::snackAmpSettings(ebg) interactive "
    (in namespace eval "::request::id3Editor" script line 2)
    invoked from within
"namespace eval id3Editor {
   option add *ID3Editors*Entry.background $::snackAmpSettings(ebg) interactive ;# high priority
   option add *ID3Editors*..."
    (in namespace eval "::request" script line 26)
    invoked from within
"namespace eval ::request $script"
    ("::try" body line 12)

OUTPUT BUFFER:

#------------------------------------------------------------------------------ # id3Editor 1.0 # # ID3 single/group editor windows for snackAmp Player in Tcl/Tk # Uses id3Tag package # Copyright (C) 2003 Tom Wilkason # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA # # Please send any comments/bug reports to # tom.wilkason@cox.net (Tom Wilkason) # #------------------------------------------------------------------------------ namespace eval id3Editor { option add *ID3Editors*Entry.background $::snackAmpSettings(ebg) interactive ;# high priority option add *ID3Editors*Entry.foreground $::snackAmpSettings(efg) interactive ;# high priority } #------------------------------------------------------------------------------ # Function : id3Editor # Description: Window to edit the ID3 info for a file # Author : Tom Wilkason # Date : 2/8/2001 #------------------------------------------------------------------------------ proc id3Editor::Edit {fnames} { set i 0 option add *ID3Editors*Label.font entry interactive ;# high priority foreach fname $fnames { if {![file exists $fname]} { saLog "'$fname' Does not exist" } } if {[llength $fnames]==0} { tk_messageBox -type ok -icon warning -message "No files specified" return } elseif {[llength $fnames]>10} { waitWindow::show "Reading Tag data for [llength $fnames] tracks" update idletasks } set here [focus] ## # Get a unique window, note $W is also a global variable # holding stuff for this instance of the GUI # while {[winfo exists .ide$i]} {incr i} set W .ide$i upvar #0 $W data upvar #0 $W.vars valueList toplevel $W -cursor $::TA(arrow) -class ID3Editors ## # Determine if one or multiple files are being edited, behaviour # is different for multi-edits # if {[llength $fnames] == 1} { set multiEdit 0 wm title $W $fname } else { set multiEdit 1 wm title $W "Editing [llength $fnames] tracks" } wm protocol $W WM_DELETE_WINDOW [list id3Editor::closeWindow $W $multiEdit] settings::Hide $W set useFnames [list] ## # Process tags for each file, if a particular tag is different between # the files than set it to the special text $id3Tag::mmMatch to indicate # that field should not be re-applyed in common to all files upon an apply. # foreach {primTagType altTagType} $::snackAmpSettings(ID3ReadOrder) {break} set tagType "Unknown" foreach fname $fnames { Unset fnameData set fnameData(Tag) "$primTagType" #set hasTag [id3Tag::id3Label $fname fnameData $::snackAmpSettings(ID3ReadOrder)] ;# use primary data set hasTag [id3Tag::id3Label $fname fnameData $primTagType] if {$hasTag==0} { set hasTag [id3Tag::id3Label $fname fnameData $altTagType] } if {$::snackAmpSettings(ID3OKCreate)==0 && ([llength $fnames] > 1 && $hasTag == 0)} { # Remove file name from consideration continue } else { set tagType "ID3$fnameData(Tag)" lappend useFnames $fname } # Note: TFW, files with no tag data don't set the multi-match field!! fnameData is blank!! # Bug, if all but one are blank then the non-blank field will be used instead of multi-match foreach {retField} $id3Tag::v1Parts { # If previously found one, see if it is different set fTag [id3Tag::blankForNull fnameData $retField] #todo convert todb lappendUnqiue valueList($retField) $fTag if {[info exists data($retField)]} { # Set to multi match tag if all the fields are not the same. if {$data($retField) ne $fTag} { set data($retField) $id3Tag::mmMatch } } else { # First time check, use actual data set data($retField) $fTag } } } # alternate options foreach {retField} $id3Tag::v1Parts { lappendUnqiue valueList($retField) $id3Tag::mmMatch lappendUnqiue valueList($retField) $id3Tag::rmMatch } # Get V2/ogg unique data so user can copy it over (if one file selected) if {! $multiEdit} { set data(fname) $fnames # This is a bug AFAIAK, file commands don't work if item is a single list length set ftest [lindex $fnames 0] if {[id3Tag::isogg $fname]} { id3Tag::id3Label $fname id3altData OGG set altTag "OGG" } else { id3Tag::id3Label $fname id3altData $altTagType if {[info exists id3altData(Tag)]} { set altTag "ID3$id3altData(Tag)" } else { set altTag "No Alternate" } } } else { set altTag "Not Shown" ## # Only edit files with existing tags unless # no files have tags, then use them. # if {[llength $useFnames] == 0} { set data(fname) $fnames } else { set data(fname) $useFnames } } ## # Build the GUI # set i 0 set row 0 # header if {$multiEdit} { set span 4 grid [label $W.v1 -text "$tagType Tag"] -columnspan $span } else { set span 5 grid [label $W.tag -text "Field"] [label $W.v2 -text "$altTag Tag"] x [label $W.v1 -text "$tagType Tag"] } # TFW: Mod to only show the V1 labels/entries if multi-edit in process # Rows of all editable fields foreach {Index} $id3Tag::v1Parts { if {![info exists id3altData($Index)]} { set id3altData($Index) {} } label $W.v2lb$Index -text $id3altData($Index) \ -anchor w -relief sunken -width 40 balloon::define $W.v2lb$Index "$altTag Data for $Index" button $W.v2bt$Index -bd 0 -image saImages.NavForward \ -command [list set ::${W}($Index) $id3altData($Index)] balloon::define $W.v2bt$Index "Copy $altTag Data '$id3altData($Index)' to V1 $Index field" label $W.lb$Index -text $Index -anchor e # TODO: Support Note field as a text box if {$Index=="Genre"} { if {![info exists data($Index)] || $data($Index)==""} { set data($Index) "Not Set" } # Make a cascaded menu like font names (or reduce options) menubutton $W.en$row -indicatoron 1 -pady 0 -font listbox \ -textvariable ::${W}($Index) -menu $W.en$row.menu -direction flush # button $W.en$row -pady 0 -font listbox \ # -textvariable ::${W}($Index) \ # -image saImages.menubutton -compound right balloon::define $W.en$row "Click to select a genre" widgetWatch::setWatch $W.en$row ::${W}($Index) menu $W.en$row.menu \ -postcommand [list id3Editor::_postGenreNames $W.en$row.menu ::${W}($Index)] # bind $W.en$row [list tk_popup $W.en$row.menu %X %Y] } else { ComboBox $W.en$row \ -borderwidth 1 \ -width 50 \ -height 20 \ -hottrack true \ -textvariable ::${W}($Index) \ -editable true \ -values $valueList($Index) $W.en$row.e configure -background $::snackAmpSettings(ebg) \ -foreground $::snackAmpSettings(efg) widgetWatch::setWatch $W.en$row.e ::${W}($Index) ;# not .e is hardcoded for ComboBox # Bind Control-e to erase tag # hack combobox hull binding is hardcoded to .e bind $W.en$row.e [list set ::${W}($Index) $id3Tag::rmMatch] if {$Index=="Note"} { bind $W.en$row.e [list noteEdit::create ::${W}($Index)] balloon::define $W.en$row.e "$tagType Data for $Index, double-click to edit" } else { balloon::define $W.en$row.e "$tagType Data for $Index" } } ## # Bind up/down keys to move in column # bind $W.en$row [list Focus $W.en[expr {$row-1}]] bind $W.en$row [list Focus $W.en[expr {$row+1}]] ## # Links to allmusic site for certain fields # TODO: For multi-edit, don't pack fields not used if {[lsearch -exact [list Title Artist Album Genre] $Index] > -1} { button $W.http$Index -bd 0 -image saImages.web -command [list browser::allMusic ::${W}($Index) $Index] balloon::define $W.http$Index "Search the All Music Guide web site for $Index information" if {$multiEdit} { grid $W.lb$Index $W.en$row $W.http$Index -sticky nsew } else { grid $W.lb$Index $W.v2lb$Index $W.v2bt$Index $W.en$row $W.http$Index -sticky nsew } } else { if {$multiEdit} { grid $W.lb$Index $W.en$row x -sticky nsew } else { grid $W.lb$Index $W.v2lb$Index $W.v2bt$Index $W.en$row -sticky nsew } } incr row } ## # Rename file based on tag # if {! $multiEdit} { button $W.lbTag2File -text "Rename File" \ -command [list id3Editor::id3Rename ::${W}(fname) $W.enTag2File] balloon::define $W.lbTag2File "Push this to Rename the file to the name on the right" set data(newName) [file rootname [file tail $data(fname)]] entry $W.enTag2File -textvariable ::${W}(newName) widgetWatch::setWatch $W.enTag2File ::${W}(newName) grid $W.lbTag2File $W.enTag2File - - -sticky nsew ## # If a URL shortcut, put that info in also # if {[playurl::isusk $ftest]} { set data(URL) [playurl::readurl $ftest] label $W.lbURL -text "Change URL" balloon::define $W.lbURL "Change the URL this URL shortcut refers to" set data(newURL) $data(URL) entry $W.enlbURL -textvariable ::${W}(newURL) widgetWatch::setWatch $W.enlbURL ::${W}(newURL) grid $W.lbURL $W.enlbURL - - -sticky ew } grid columnconfigure $W 3 -weight 2 } grid columnconfigure $W 1 -weight 1 # Line grid [horzLine $W.l[incr i]] -pady 3 -columnspan $span -sticky nsew ## # Frame for OK/Cancel/Apply buttons # grid [frame $W.but] -columnspan $span button $W.but.lbcddb -text "Freedb" -default normal -image saImages.web -compound left\ -command [list browser::cddb ::$W] balloon::define $W.but.lbcddb "Push this to guess a file name based on the tag" button $W.but.coverart -text "Album Art" -default normal -image saImages.web -compound left\ -command [list browser::coverart ::$W] balloon::define $W.but.coverart "Push this to search google for cover art images" if {! $multiEdit} { ## # Tag file based on name # button $W.but.lbMakeFname -text "Name" -default normal \ -command [list id3Editor::id3Editor_guessFileName ::$W newName] \ -image saImages.Guess -compound left balloon::define $W.but.lbMakeFname "Push this to guess a file name based on the tag" button $W.but.lbFile2Tag -text "Tag" -default normal \ -command [list id3Editor::cbSmartTagName ::${W}(fname) ::$W] \ -image saImages.Guess -compound left balloon::define $W.but.lbFile2Tag "Push this to create a tag based on the file name" } #fix set fix [button $W.but.[incr i] -width 8 -default normal -text "Fix Case" \ -command [list id3Editor::id3Editor_SingleTagToNameFix ::$W]] balloon::define $fix "Push this to automatically correct file name spacing and capitalization" # Apply set app [button $W.but.[incr i] -default normal -text "Apply" \ -command [list id3Editor::id3Editor_cbModify ::${W}(fname) $W "[me].$multiEdit" 0] \ -compound left -image saImages.Save] balloon::define $app "Push this apply and save the current settings\n[join [set ::${W}(fname)]\n]" # Apply and close set ok [button $W.but.[incr i] -default active -text "OK" -padx 4 \ -command [list id3Editor::id3Editor_cbModify ::${W}(fname) $W "[me].$multiEdit" 1] \ -compound left -image saImages.Apply] balloon::define $ok \ "Push this to apply and save the current settings then close this dialog\n[join [set ::${W}(fname)]\n]" # Cancel set can [button $W.but.[incr i] -default normal -text "Cancel" \ -command [list id3Editor::closeWindow $W "[me].$multiEdit"] \ -compound left -image saImages.Close] balloon::define $can "Push this to ignore changes made and restore the previous settings" if {! $multiEdit} { grid $W.but.coverart $W.but.lbcddb $W.but.lbMakeFname $W.but.lbFile2Tag $fix $app $ok $can -padx [extraPad 1] -pady 1 -sticky ns } else { grid $W.but.coverart $W.but.lbcddb $fix $ok $app $can -padx [extraPad 1] -pady 1 -sticky ns } ## # Key bindings # waitWindow::close $ok configure -default active bind $W [list $ok invoke] bind $W [list $can invoke] settings::restoreWidth $W "[me].$multiEdit" ontop::pinOnSystemMenu {} $W myIcon $W focus $W settings::Restore $W } #------------------------------------------------------------------------------ # Function : id3Editor::id3Editor_SingleTagToNameFix # Description: Clean the file name # Author : Tom Wilkason #------------------------------------------------------------------------------ proc id3Editor::id3Editor_SingleTagToNameFix {_W {patt *}} { upvar #0 $_W W global fnameData ;# needs to be global foreach {element} [lsort [array names W $patt]] { set fname $W($element) # Only fix certain fields if {[lsearch -exact [list Title Artist Album newName] $element]>= 0} { set fn [fixplus::cleanName $fname] } else { set fn $fname } if {($fn ne "") && ($fn ne $fname)} { # Use orig name, traced variable set ${_W}($element) $fn } } } #------------------------------------------------------------------------------ # Function : id3Editor_cbModify # Description: Callback to apply the ID3 changes # Author : Tom Wilkason # Date : 2/7/2001 #------------------------------------------------------------------------------ proc id3Editor::id3Editor_cbModify {_fnames W winTag {close 0}} { upvar $_fnames fnames upvar $W data set todo [llength $fnames] set i 0 if {$::snackAmpSettings(ID3ReadOnly)==0} { foreach {fname} $fnames { # Make a copy of the common array since it is modified in place unset -nocomplain localArray array set localArray [array get ::$W] # Write the data # todo: convert from DB if {[id3Tag::id3Modify $fname localArray]} { waitWindow::percent [incr i] $todo "Setting Tag for\n[file tail $fname]" 1 if {[waitWindow::ifbreak]} { break } } ## # Update the URL data at the beginning of the data if required. # if {[info exists data(URL)] && [info exists data(newURL)] && ($data(URL) ne $data(newURL))} { if {[catch { set fid [open $fname a] seek $fid 0 start playurl::writeurl $data(newURL) $fid close $fid } result] } then { debug $result catch {close $fid} } } ## # If this song is the currently linked song then # update the array that is linked to the displayed # fields. if {[string equal [soundControl::fileName] $fname]} { array set ::songArray [array get localArray] } } # Update any data that is currently shown trackList::updateFiles $fnames LB::updateFiles $fnames waitWindow::close } # Can't close window till done, need data from it above if {$close} { id3Editor::closeWindow $W $winTag } } #------------------------------------------------------------------------------ # Function : id3Editor::id3Editor_guessFileName # Description: Guess a file name # Author : Tom Wilkason # Date : 3/19/2002 #------------------------------------------------------------------------------ proc id3Editor::id3Editor_guessFileName {_W index} { upvar $_W Data set Data($index) [catalog::smartFileName Data $::snackAmpSettings(smartName)] } #---------------------------------------------------------------------------- # Common Functions between editors #---------------------------------------------------------------------------- #------------------------------------------------------------------------------ # Callback to set/clear checkboxes #------------------------------------------------------------------------------ proc id3Editor::cbSetOK {_W how} { upvar $_W W foreach {field} [lsort [array names W *,ok]] { set W($field) $how } } #------------------------------------------------------------------------------ # Function : id3Editor::id3Rename # Description: Rename a file based on the tag name # Author : Tom Wilkason # Date : 3/18/2002 #------------------------------------------------------------------------------ proc id3Editor::id3Rename {_fname widget} { upvar $_fname fnames # Note: TFW: Can only rename one file now, can make more intellegent # with substitutions and such for multiple files later. set fname [lindex $fnames 0] set newName [$widget get] set newfname [file join [file dirname $fname] "$newName[file extension $fname]"] if {[string equal $newfname $fname] || [string length newName] < 1} { return $fname } if {[catalog::RenameFile $fname $newfname]} { tk_messageBox -icon warning -message "Could not rename\n$fname to \n$newfname\n$result\n$::errorInfo" } else { set fnames [list $newfname] } return $newfname } #------------------------------------------------------------------------------ # Callback used to save the width of each of the editors then close them # Also removes global data associated with the window #------------------------------------------------------------------------------ proc id3Editor::closeWindow {W tag} { # Return the top level window name only regexp {(\..+?)\.} $W -> W settings::pushWidth $W $tag destroy $W foreach {g} [info globals $W*] { unset ::$g } } #------------------------------------------------------------------------------ # Function : _postGenreNames # Description: On pop-up, post the genres, if already built then post menu # Menu is shared for all buttons to save memory # Author : Tom Wilkason #------------------------------------------------------------------------------ proc id3Editor::_postGenreNames {Menu _Element} { if {[$Menu index end] == "none" || [$Menu index end] == 0} { set genres [concat $id3Tag::v1Genres $::snackAmpSettings(customGenres)] foreach {genre} $genres { set first [string index $genre 0] lappend genreNames($first) $genre } $Menu delete 0 end foreach {Value} [lsort [array names genreNames]] { $Menu add cascade -label $Value -menu $Menu.f$Value menu $Menu.f$Value foreach {item} $genreNames($Value) { $Menu.f$Value add command -label "$item" \ -command [list set $_Element $item] } } } } #------------------------------------------------------------------------------ # Function : cbSmartTagName # Description: Fill in tag data based on a file name # Author : Tom Wilkason #------------------------------------------------------------------------------ proc id3Editor::cbSmartTagName {_fname _Array} { upvar $_Array Data upvar $_fname fname # TFW: Can be smarter about which entry is the track number set sp [string trim $::snackAmpSettings(splitChar)] set parts [split [file rootname [file tail [fixplus::cleanName $fname]]] $sp] switch -- [llength $parts] { 1 { set Data(Title) [lindex [string trim $parts] 0] } 2 { # Artist - Title # Track - Title foreach {First Title} $parts { if {[string is double $First]} { set Data(Track) [string trim $First] } else { set Data(Artist) [string trim $First] } set Data(Title) [string trim $Title] } } 3 { # Artist - Album - Title # Album - Track - Title # Track - Artist - Title foreach {Artist Album Title} $parts { if {[string is double $Album]} { set Data(Album) [string trim $Artist] set Data(Track) [string trim $Album] set Data(Title) [string trim $Title] } else { if {[string is double $Artist]} { set Data(Track) [string trim $Artist] set Data(Artist) [string trim $Album] set Data(Title) [string trim $Title] } else { # Default set Data(Artist) [string trim $Artist] set Data(Album) [string trim $Album] set Data(Title) [string trim $Title] } } } } 4 { foreach {Artist Album Track Title} $parts { set Data(Artist) [string trim $Artist] set Data(Album) [string trim $Album] set Data(Title) [string trim $Title] set Data(Track) [string trim $Track] } } 5 { # TBD: handle Artist AlbA - AlbB - Track - Title foreach {Artist Album xx Track Title} $parts { set Data(Artist) [string trim $Artist] set Data(Album) "[string trim $Album], [string trim $xx]" set Data(Title) [string trim $Title] set Data(Track) [string trim $Track] } } default {} } if {[info exists Data(Track)]} { if {[string is double $Data(Track)]} { # Handle octal interpretation set Data(Track) [scan $Data(Track) %d] } else { set Data(Track) "" } } if {[info exists Data(Genre)] && $Data(Genre) == "Not Set" && [info exists lastGenre]} { set Data(Genre) $lastGenre } return } #------------------------------------------------------------------------------ # Common labeled frame #------------------------------------------------------------------------------ proc id3Editor::Frame {W Title} { set hull [labeledFrame $W -text $Title -bd 2 -relief groove -font listbox] return $hull } package provide id3Editor 1.0