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