A gui for youtube-dl, written in TCL/TK 8.6 - and a small script to compile source into one file.

in HiveDevs3 years ago

A gui for youtube-dl, written in TCL/TK 8.6

requires:

Tcl/Tk 8.6 http://tcl.tk/software/tcltk/

also requires tclthread. using your package manager, i'd search:

pkg search tcl | grep thread

or

apt search tcl | grep thread

depending upon your system. to install yourself you compile and install Tcl using link above. it is included there.

youtube-dl https://github.com/ytdl-org/youtube-dl is also usually available on linux systems (but I had trouble with it there) .. i use freeBSD and it works here. you might need to compile it yourself to get a working copy on linux.

screenshot01.png

it works really well for me, when I want to play around with it.

using XFCE, and using 'focus follows mouse' means I can double click a youtube link in the title bar (highlighting it) and then use the roller-wheel click in the 'Enter YouTube address here' bar.. and move the mouse away.. and it will begin downloading.

after you watch it you can use the red Xto delete the file.

unfortunately the Play button does not work. i appear to be giving xdg-open the proper link, but it doesn't want to open it to play even tho the exact same line work in the command line. perhaps it works in linux? only used it in freeBSD

running this project requires tcl/tk 8.6 and youtube-dl.

Here is the project link : youtube-dl-gui

a fun little offshoot of this project was the desire to present this in a single file format.

it reads in a source file that is used to run the program and then reads all the source files listed within and adds them to a new file that holds all the combined files. And it had the added bonus of making it simple to remove all the comments from the single file, as well as lines with no spaces. If I was feeling truly efficient I could clean up the spaces all around to provide the smallest download possible, but that was not the goal.

this single file also had the added benefit of providing a stable version during periods of development.

I also have a love of simplicity, so the fact I write this in less than 50 lines of code made me very happy. it was even smaller but I had to start adding features like including a header (so you can launch it from the command line) and making it executable. I call it makeOneFile.tcl

#!/usr/bin/tclsh
# Marc Bookmeyer 2022
set sourceFile youtube-dl-gui.tcl
set destFile run_ytDLgui.tcl
proc openFile { path } {
    set fl [open $path]
    set data [split [read $fl] "\n"]
    close $fl
    return $data
}
proc addSource { destFl path } {
    foreach line [openFile $path] {
        if { [string range $line 0 6] == "source " } {
            puts $destFl "###START### Source:[string range $line 7 end] ###START###"
            addSource $destFl [string range $line 7 end]
            puts $destFl "###END### Source:[string range $line 7 end] ###END###"
        } else {
            if { $line == "" || [string range [string trim $line] 0 0] == "#"  } {
                #comments and spaces
                #puts $destFl $line
            } else {
                puts $destFl $line
            }
        }
    }
}
proc addHeader { destFl path } {
    set sFile [openFile $path]
    puts $destFl [lindex $sFile 0]
    puts $destFl [lindex $sFile 1]
}
set destFl [open $destFile w]
addHeader $destFl $sourceFile
addSource $destFl $sourceFile
close $destFl
exec chmod u+x $destFile

Using this little script above I was able to create the script below from the multiple sources. Which also means I now have a nice copy here on the block chain. So what follows is nearly 800 lines of code comprising the entire project.

#!/usr/bin/tclsh
# Marc Bookmeyer 2022
###START### Source:ytDownloader.tcl ###START###
oo::class create ytDownloader {
    variable link pipe extHandler ytCmd
    constructor { _link _extHandler } {
        set link $_link
        set extHandler $_extHandler
        set ytCmd "/usr/local/bin/youtube-dl"
    }
    method download {} {
        puts "ytDownloader>>download>>$ytCmd $link"
        set pipe [open |[concat $ytCmd [list $link 2>@stderr]] r]
        fileevent $pipe readable [list [self] ytReadable]
    }
    method ytReadable {} {
        if {![eof $pipe]} {
            if {[gets $pipe line] >= 0 } {
                if { $extHandler != "" } {
                    {*}$extHandler $line
                }
            }
        } else {
            catch {close $pipe}
            puts "ytDownloader pipe has closed."
            if { $extHandler != "" } {
                {*}$extHandler "EOF"
            }
        }
    }
}
###END### Source:ytDownloader.tcl ###END###
###START### Source:ytDownloader-thread.tcl ###START###
package require Thread
oo::class create ytDownloader_thread {
    variable ytThread ytLink ytDownloader ytParse parseResults q loopDone n
    constructor { _n sourceDir downloadDir } {
        set n $_n
        set ytThread [thread::create]
        cd $sourceDir
        my initiateThread $sourceDir $downloadDir
        tsv::set ytDownloader_thread$n lineReady no
    }
    method initiateThread {sourceDir downloadDir} {
        puts "initiateThread>>sourceDir: $sourceDir downloadDir: $downloadDir"
        thread::send -async $ytThread [list set sourceDir $sourceDir]
        thread::send -async $ytThread [list set downloadDir $downloadDir]       
        thread::send -async $ytThread [list set n $n]       
        thread::send -async $ytThread {
            cd $sourceDir
            source ytDownloader.tcl
            oo::class create ytDownloader_inThread {
                variable q ytParse qNum n ytDownloader
                constructor { } {
                    set q ""
                    set qNum 0
                    set n $::n
                    tsv::set ytDownloader_thread$n lineReady no
                }
                method downloadLink { link } {
                    set ytDownloader [ytDownloader new $link [list [self] ytResultsHandler]]
                    $ytDownloader download
                }
                method ytResultsHandler { line } {
                    if { $line != "" } {
                        lappend q $line
                        incr qNum
                        tsv::set ytDownloader_thread$n lineReady yes
                    }
                }
                method lineReady {} {
                    if { $qNum > 0 } {
                        return true
                    } else {
                        return false
                    }
                }
                method linePopped {} {
                    incr qNum -1
                }
                method linePop {} {
                    incr qNum -1
                    set returnVal [lindex $q 0]
                    set q [lrange $q 1 end]
                    return $returnVal
                }
                method destroyDownloader {} {
                    $ytDownloader destroy
                }
            }
            proc tDownloadLink { link } {
                cd $::downloadDir
                set ::ytHandle [ytDownloader_inThread new]
                $::ytHandle downloadLink $link
            }
            proc lineReady {} {
                $::ytHandle lineReady
            }
            proc linePopped {} {
                $::ytHandle linePopped
            }
            proc linePop {} {
                $::ytHandle linePop
            }
            proc exitThread {} {
                $::ytHandle destroyDownloader
                $::ytHandle destroy
                
            }
        }
    }
    method downloadLink { link } {
        thread::send $ytThread [list tDownloadLink $link]
    }
    method popResults {} {
        puts "popResults>>[thread::send $ytThread lineReady]"
        if { [thread::send $ytThread lineReady] } {
            
            return [thread::send $ytThread linePop]
        } else {
            return ""
        }
    }
    method exitThread {} {
        thread::send $ytThread exitThread
        thread::release $ytThread
    }       
}
 
###END### Source:ytDownloader-thread.tcl ###END###
###START### Source:ytProgressBars.tcl ###START###
###START### Source:progressBar.tcl ###START###
package require Tk 
oo::class create progressBar {
    variable w pt bar0 bar1 wd ht bg fg tl mySeed seed c percent
    constructor { { _w "" } { _wd 400 } { _ht 25 } { _bg lightblue } { _fg orange } } {
        set ns [info object namespace [self class]]
        my eval [list namespace upvar $ns seed seed]
        if {![info exists seed]} {
            set seed 0
        } else {
            incr seed
        }
        set mySeed $seed
        set w $_w
        set wd $_wd
        set ht $_ht
        set bg $_bg
        set fg $_fg
        set tl ""
        set c "$w.c$mySeed"
        
                canvas $c \
                        -bg $bg \
                        -width $wd \
                        -height $ht
                my drawbar
                my drawtext
                set pt [$c create text [expr { $wd / 2 }] [expr { $ht / 2 }]]
        my setPercent
        grid $c -sticky ew
        grid columnconfigure $w $c -weight 1
    }
    method mycanvas {} {
        return $c
    }
    method setWidth { x } {
        set wd $x
        my redraw
        my setPercent $percent
    }
    method redraw {} {
        $c delete $bar0 $bar1 $pt
        my drawbar
        my drawtext
    }
    method drawbar { } {
                set bar0 [$c create rectangle 0 0 [expr {$wd-1}] [expr {$ht-1}] -fill $fg]
        set bar1 [$c create line 1 0 1 [expr {$ht-1}]]
    }       
    method drawtext { } {
                set pt [$c create text [expr { $wd / 2 }] [expr { $ht / 2 }] -text $tl]
    }       
    method setbar { x } {
        $c moveto $bar0 [expr { -$wd+($x*.01*$wd)  }] 0
    }
    method setText { _tl } {
        set tl $_tl
        $c itemconfigure $pt -text $tl
    }       
    method setPercent { { n 0 } } {
        set percent $n
        my setbar $percent
    }
        
}
###END### Source:progressBar.tcl ###END###
oo::class create ytProgressBars {
    variable w seed mySeed pbVideo pbAudio frameName syncResize
    constructor { { _w "." } } {
        set w $_w
        my createBars
    }
    method createBars {} {
        set pbVideo [progressBar new $w]
        set pbAudio [progressBar new $w]
        grid columnconfigure $w [$pbVideo mycanvas] -weight 1
        grid columnconfigure $w [$pbAudio mycanvas] -weight 1
    }
    method setPercentVideo { n } {
        $pbVideo setPercent $n
    }
    method setPercentAudio { n } {
        $pbAudio setPercent $n
    }
    method setWidthVideo { n } {
        $pbVideo setWidth $n
    }
    method setWidthAudio { n } {
        $pbAudio setWidth $n
    }
    method setTextVideo { n } {
        $pbVideo setText $n
    }
    method setTextAudio { n } {
        $pbAudio setText $n
    }
}
###END### Source:ytProgressBars.tcl ###END###
###START### Source:ytEntry.tcl ###START###
oo::class create ytEntry {
    variable w ytEntry frameName e g seed entryTextVars ytUnique ytGo ytRemove dlLocation
    constructor { { _w "." } _seed } {
        set seed $_seed
        set w $_w
        if {$w == "." } {
            set frameName ".ytEntryFrame$seed"
        } else {
            set frameName "$w.ytEntryFrame$seed"
        }
        frame $frameName
        grid $frameName -sticky ew
        grid columnconfigure $w $frameName -weight 1
        
        set ytEntry $frameName.ytEntry
        set ytGo $frameName.ytGo
        set ytRemove $frameName.ytRemove
        set dlLocation ""
        my myEntry
    }
    method myEntry {} {
        set ::entryTextVars($seed) "Enter YouTube address here"
        set e [ttk::entry $ytEntry -textvariable entryTextVars($seed) -validatecommand [list [self] entryValidate %V %P] -validate all -background lightgreen]
        set g [button $ytGo -text Go -command [list [self] go]]
        grid $ytEntry $ytGo -sticky ew
        grid columnconfigure $frameName $ytEntry -weight 1
    }
    method entryText {} {
        return $::entryTextVars($seed)
    }
    method go {} {
        my entryValidate focusout $::entryTextVars($seed)
    }
    method show {} {
    }
    method removeEntry {} {
        puts "deleteEntry>>seed>>$seed"
        set ::ytRemove($seed) yes
    }
    method retry {} {
        puts "deleteEntry>>seed>>$seed"
        set ::ytRemove($seed) yes
    }
    method downloadFinished { args } {
        if { $::ytRun($seed) } {
        } else {
        }
    }
    method setCancelDisabled {} {
        $g configure -state disabled
    }
    method showGoRun {} {
        puts "showGoRun>>"
        set ::ytRun($seed) go
        trace add variable ::ytRun($seed) write "[self] downloadFinished"
        $g configure -text "Cancel" -command [list set ::ytRun($seed) true]
    }
    method entryValidate { validationCallReason entryString } {
        if { $validationCallReason == "focusout" } {
            if { $entryString == "" } {
                set ::entryTextVars($seed) "Enter YouTube address here"
            } else {
                if { [my isLink $entryString] } {
                    puts "isLink>>"
                    set ::entryTextVars($seed) $entryString
                    set dlLocation $::entryDownLocation
                    [self] showGoRun
                }
            }
        } elseif { $validationCallReason == "focusin" } {
            if { $entryString == "Enter YouTube address here" } {
                set ::entryTextVars($seed) ""
            }
        }
            
        return true
    }
    method isLink { line } {
        set returnVal false
        puts "state:[$ytEntry cget -state] [string range $line 32 end] -- [string range $line 0 11]"
        if { [$ytEntry cget -state] == "disabled" } {
        } elseif { [string length [set ytUnique [string range $line) 32 42]]] == 11 | [string length [set ytUnique [string range $line) 17 28]]] } {
            puts "entryValidate1>>$ytUnique"
            set returnVal true
        } elseif { [string length [set ytUnique [string range $line 0 11]]] == 11 && $ytUnique != "https://www" } {
            puts "entryValidate2>>$ytUnique"
            set returnVal true
        }
        return $returnVal
    }
        
    method focus {} {
        focus $e
    }
    method setStateEnabled {} {
        $ytEntry configure -state disabled
    }
    method setStateDisabled {} {
        $ytEntry configure -state disabled
    }
    method disableCancel {} {
        $g configure -state disabled
    }
    method showRetryEntry { } {
        trace remove variable ::ytRun($seed) write "[self] downloadFinished"
        $g configure -text "Retry" -command [list set ::ytRun($seed) true]
        my deleteRemoveEntry
    }
    method showRemoveEntry {} {
        puts "showRemoveEntry"
        button $ytRemove -text removeEntry -command [list [self] removeEntry]
        grid $ytRemove -column 0 -row 0
    }
    method deleteRemoveEntry {} {
        destroy $ytRemove
    }
    method createGoEntry {} {
        puts "createGoEntry"
        set g [button $ytGo -text Go -command [list [self] go]]
    }
    method showOpenDirectory {} {
        puts "showOpenDirectory"
        $g configure -text "Open file location" -command [list [self] openDirectory]
    }
    method openDirectory {} {
        puts "openDirectory"
        exec xdg-open $dlLocation
    }
        
}
###END### Source:ytEntry.tcl ###END###
###START### Source:ytPrefs.tcl ###START###
oo::class create ytPrefs {
    variable w frameName downloadLocation ytLabel ytEntry ytSetDirectory prefLocation ytChooseDirectory
    constructor { { _w "." } } {
        set w $_w
        if {$w == "." } {
            set frameName ".prefsFrame"
        } else {
            set frameName "$w.prefsFrame"
        }
        set configDir [file join $::env(HOME) .config]
        set prefLocation [file join $configDir "youtube-dl-gui.prefs"]
        if { ! [file isdirectory $configDir] } {
            file mkdir $configDir
            set ::entryDownLocation $::env(HOME)
            my savePrefs
        } else {
            my readPrefs
        }
            
        puts "ytPrefs>>$prefLocation"
        set ytLabel $frameName.ytLabel
        set ytEntry $frameName.ytEntry
        set ytSetDirectory $frameName.ytSetDirectory
        set ytChooseDirectory $frameName.ytChooseDirectory
        my createFrame
        my createWidgets
    }
    method downloadLocation {} {
        return $downloadLocation
    }
    method readPrefs {} {
        set fd9 [open $prefLocation r]
        set ::entryDownLocation [lindex [split [read $fd9] "\n"] 0]
        set downloadLocation $::entryDownLocation
        close $fd9
    }       
    method createFrame {} {
        frame $frameName
        grid $frameName -sticky ew
        grid columnconfigure $w $frameName -weight 1
    }
    method createWidgets {} {
        ttk::label $ytLabel -text "Save To:"
        ttk::entry $ytEntry -textvariable entryDownLocation -validatecommand [list [self] entryValidate %V %P] -validate all -background lightgreen
        button $ytChooseDirectory -text "\[\]" -command [list [self] chooseDirectory]
        grid $ytLabel $ytEntry $ytChooseDirectory -sticky ew -padx 1
        grid columnconfigure $frameName $ytEntry -weight 1
    }
    method entryValidate { validationCallReason entryString } {
            if { [my setDirectory $entryString] } {
                $ytEntry configure -foreground black
            } else {
                $ytEntry configure -foreground red
            }
        return true
    }
    method chooseDirectory {} {
        set dir [tk_chooseDirectory \
            -initialdir $::entryDownLocation -title "Choose a directory"]
        if {$dir eq ""} {
        } else {
            set ::entryDownLocation $dir
            set downloadLocation $dir
            my savePrefs
        }
    }
    method saveDirectory {} {
        my setDirectory $::entryDownLocation
    }           
    method setDirectory { newDirectory } {
        if { [file isdirectory $newDirectory] } {
            if { [catch {cd $newDirectory}] } {
                return false
            } else {
                puts "setDirectory>>$newDirectory"
                set downloadLocation $newDirectory
                my savePrefs
            }
                
        } else {
                return false
        }
        return true
    }
    method savePrefs {} {
        puts "savePrefs>>$downloadLocation"
        set fd9 [open $prefLocation w]
        puts $fd9 $downloadLocation
        close $fd9
    }
        
}
###END### Source:ytPrefs.tcl ###END###
###START### Source:ytParse.tcl ###START###
oo::class create ytParse {
    variable info
    variable percent
    variable speed
    variable eta
    variable total
    variable total_label
    variable rate
    variable rate_label
    variable timeFinished
    variable dlCount
    constructor {} {
        set dlCount 0
    }
    method parseLine { line } {
        set returnVal ""
        set type [string range [scan $line {[%s}] 0 end-2]
        switch $type {
            download {
                set returnVal [my parseDownload $line]
            }
            youtube {
                puts $line
            }
            youtube:tab {
                set returnVal [list youtube:tab yes]
                puts $line
            }
            info {
                set info $line
            }
            Merger {
                set returnVal [my parseMerger $line]
            }
            default {
                puts "d-$line"
            }
        }
        return $returnVal   
    }
    method parseDownload { line } {
        set returnVal ""
        set sc [scan $line {[download] %i.%i%% of %i.%i%s at %i.%i%s ETA %s}]
        if { [lindex $sc 0] == 100 } {
            if { [lindex $sc 1] == "" } {
                set sc [scan $line {[download] %i%% of %i.%i%s in %s}]
                puts "parseDownload>>$sc"
                if { [lindex $sc 4] != "" } {
                    set total "[lindex $sc 1].[lindex $sc 2]"
                    set total_label [lindex $sc 3]
                    set timeFinished [lindex $sc 4]
                    set returnVal [list total $total total_label $total_label timeFinished $timeFinished dlCount $dlCount]
                    incr dlCount
                }
            }
        } elseif { [lindex $sc 8] != "" } {
            set percent "[lindex $sc 0].[lindex $sc 1]"
            set total "[lindex $sc 2].[lindex $sc 3]"
            set total_label [lindex $sc 4]
            set rate "[lindex $sc 5].[lindex $sc 6]"
            set rate_label [lindex $sc 7]
            set eta [lindex $sc 8]
            set returnVal [list percent $percent total $total total_label $total_label rate $rate rate_label $rate_label eta $eta dlCount $dlCount]
        } elseif { [lindex [scan $line {[download] Destination: %s}] 0] != "" } {
            set returnVal [list destination [string range $line 24 end] dlCount $dlCount]
        } elseif {  [string range $line end-27 end] == " has already been downloaded" } {
            set returnVal [list alreadyDownloaded yes dlCount $dlCount]
        }
            
            
        return $returnVal
    }
    method parseMerger { line } {
        set returnVal ""
        set fileName [string range $line 31 end-1]
        set returnVal [list fileName $fileName dlCount $dlCount]
        return $returnVal
    }
}
###END### Source:ytParse.tcl ###END###
###START### Source:ytFile.tcl ###START###
oo::class create ytFile {
    variable w frameName ytFile ytOpenDir ytPlayFile ytDeleteFile e g seed dlLocation basePath
    constructor { { _w "." } _seed } {
        set seed $_seed
        set w $_w
        if {$w == "." } {
            set frameName ".ytFileFrame$seed"
        } else {
            set frameName "$w.ytFileFrame$seed"
        }
        frame $frameName
        grid $frameName -sticky ew
        grid columnconfigure $w $frameName -weight 1
        
        set ytFile $frameName.ytFile
        set ytOpenDir $frameName.ytOpenDir
        set ytPlayFile $frameName.ytPlayFile
        set ytDeleteFile $frameName.ytDeleteFile
        set dlLocation ""
        set basePath ""
        my ytFile
    }
    method ytFile {} {
        ttk::label $ytFile -text "" -background lightgreen
        button $ytDeleteFile -text "X" -command [list [self] deleteFile] -background red
        button $ytOpenDir -text "\[\]" -command [list [self] openDirectory] -background lightblue
        button $ytPlayFile -text "Play" -command [list [self] playFile] -state disabled -background lightgreen
        grid $ytDeleteFile $ytFile $ytOpenDir $ytPlayFile -sticky ew -padx 2
        grid columnconfigure $frameName $ytFile -weight 1
    }
    method setBasePath { newPath } {
        set basePath $newPath
    }
    method setFilePath { newPath } {
        $ytFile configure -text $newPath
    }
    method setPlayEnabled {} {
        $ytPlayFile configure -state normal
    }
    method openDirectory {} {
        puts "openDirectory>>$dlLocation>>[file dirname [$ytFile cget -text]]"
        exec xdg-open [file dirname [$ytFile cget -text]]
    }       
    method playFile {} {
        set cmd "\"[file join $basePath [$ytFile cget -text]]\""
        puts "playFile>>$cmd"
        exec xdg-open $cmd
    }
    method deleteFile {} {
        puts "deleteFile"
        set answer [tk_messageBox -message "Really Delete?" \
            -icon question -type yesno \
            -detail "Select \"Yes\" to DELETE this file: [$ytFile cget -text]"]
        switch -- $answer {
            yes {
            file delete [$ytFile cget -text]
            }
            no {tk_messageBox -message "I know you like this application!" \
                -type ok}
        }       
    }       
}
###END### Source:ytFile.tcl ###END###
###START### Source:ytGui.tcl ###START###
oo::class create yt_gui {
    variable w ytProgressBarser ytParser ytEntry ytPrefs ytFile syncResize seed frameName ytDownloader_thread dl_loop sourceDir resultsLoopAfter retrying
    constructor { { _w "." } } {
        set sourceDir [pwd]
        set seed -1
        if {$_w == "" } {
            set w .
        } else {
            set w $_w
        }
        bind $w <Configure> [list [self] resize.bind %W %w %h %x %y]
        set n 0
        set syncResize(winX) 0
        set syncResize(winY) 0
        set syncResize(update) 0
        set retrying no
        set ytPrefs [ytPrefs new]
        my newDownloader
    }
    method newDownloader {} {
        incr seed
        if {$w == "." } {
            set frameName($seed) ".f$seed"
        } else {
            set frameName($seed) "$w.f$seed"
        }
        labelframe $frameName($seed) -text "Enter Youtube video link below:"
        grid $frameName($seed) -sticky ew
        grid columnconfigure $w $frameName($seed) -weight 1
        set ytEntry($seed) [ytEntry new $frameName($seed) $seed]
        set ytFile($seed) [ytFile new $frameName($seed) $seed]
        set ytProgressBarser($seed) [ytProgressBars new $frameName($seed)]
        set ytParser($seed) [ytParse new]
        $ytEntry($seed) focus
        trace add variable ::ytRun($seed) write "[self] startDownloader $seed"
        trace add variable ::ytRemove($seed) write "[self] removeDownloader $seed"
        puts "newDownloader>>created new \[$seed\]"
    }
    method startDownloader { n args } {
        puts "startDownloader>>$n $args"
        set ytDownloader_thread($n) [ytDownloader_thread new $n $sourceDir [$ytPrefs downloadLocation]]
        $ytFile($n) setBasePath [$ytPrefs downloadLocation]
        trace remove variable ::ytRun($n) write "[self] startDownloader $n"
        set ::ytRun($n) true
        trace add variable ::ytRun($n) write "[self] cancelDownloader $n"
        $ytEntry($n) setStateDisabled
        $frameName($n) configure -text "Download has begun.."
        if { ! $retrying } {
            my newDownloader; #create a new one for the next
        } else {
            set retrying no
        }   
        $ytDownloader_thread($n) downloadLink [$ytEntry($n) entryText]
        set resultsLoopAfter [after 1000 [list [self] getResultsLoop $n]]
    }
    method cancelDownloader { n args } {
        puts "cancelDownloader>>$n $args"
        $frameName($n) configure -text "Download CANCELLED"
        trace remove variable ::ytRun($n) write "[self] cancelDownloader $n"
        after cancel $resultsLoopAfter
        $ytDownloader_thread($n) exitThread
        $ytDownloader_thread($n) destroy
        $ytEntry($n) showRetryEntry
        $ytEntry($n) showRemoveEntry
        trace add variable ::ytRun($n) write "[self] retryDownload $n"
    }
    method retryDownload { n args } {
        trace remove variable ::ytRun($n) write "[self] retryDownload $n"
        $ytEntry($n) deleteRemoveEntry
        set retrying yes
        $ytEntry($n) showGoRun
        [self] startDownloader $n
    }
    method getResultsLoop { n } {
        set dl_loop($n) no
        [self] resultsLoop $n
    }
    method resultsLoop { n } {
        puts "resultsLoop>>$n"
        if { [set line [$ytDownloader_thread($n) popResults]] != "" } {
            if { $line == "EOF" } {
                puts "getResultsLoop>>DONE"
                set dl_loop($n) yes
                $ytEntry($n) showOpenDirectory
            } else {
                [self] updateGUI $n $line
            }
        }
        if { !$dl_loop($n) }  {
            set resultsLoopAfter [after 100 [list [self] resultsLoop $n]]
        }
    }
    method setProgressBarText { n } {
        if { $n } {
            return setTextAudio
        } else {
            return setTextVideo
        }
    }       
    method setProgressBarPercent { n } {
        if { $n } {
            return setPercentAudio
        } else {
            return setPercentVideo
        }
    }       
    method updateGUI { n line } {
        puts "updateGUI>>$line"
        set k [$ytParser($n) parseLine $line]
        if { [lsearch [dict keys $k] youtube:tab] != -1 } {
            
        } elseif { [lsearch [dict keys $k] percent] != -1 } {
            $ytProgressBarser($n) [my setProgressBarText [dict get $k dlCount]] "[dict get $k percent]% of [dict get $k total][dict get $k total_label] at [dict get $k rate][dict get $k rate_label] ETA [dict get $k eta]"
            $ytProgressBarser($n) [my setProgressBarPercent [dict get $k dlCount]] [dict get $k percent]
        } elseif { [lsearch [dict keys $k] timeFinished] != -1 } {
            puts "updateGUI>>timeFinished"
            $ytProgressBarser($n) [my setProgressBarText [dict get $k dlCount]] "Finished downloading [dict get $k total][dict get $k total_label] in [dict get $k timeFinished]"
            $ytProgressBarser($n) [my setProgressBarPercent [dict get $k dlCount]] 100
            if { [dict get $k dlCount] } {
                $ytEntry($n) showRemoveEntry
                $ytFile($n) setPlayEnabled
            }
        } elseif { [lsearch [dict keys $k] alreadyDownloaded] != -1 } {
            puts "updateGUI>>alreadyDownloaded"
            $frameName($n) configure -text "Download cancelled."
            $ytProgressBarser($n) [my setProgressBarText 0] "Already been downloaded"
            $ytProgressBarser($n) [my setProgressBarPercent 0] 100
            $ytProgressBarser($n) [my setProgressBarText 1] ""
            $ytProgressBarser($n) [my setProgressBarPercent 1] 100
            $ytEntry($n) showRemoveEntry
            $ytEntry($n) showOpenDirectory
            $ytFile($n) setPlayEnabled
        } elseif { [lsearch [dict keys $k] fileName] != -1 } {
            puts "fileName [pwd] [dict get $k fileName]"
        } elseif { [lsearch [dict keys $k] destination] != -1 } {
            $frameName($n) configure -text "[dict get $k destination]"
            $ytFile($n) setFilePath [dict get $k destination]
        }
    }       
    method test1 { n } {
        puts "test1>>n:$n"
        set x [open ex-dl-1.txt]
        while {[gets $x line]>=0} {
            set k [$ytParser($n) parseLine $line]
            if { [lsearch [dict keys $k] percent] != -1 } {
                if { [dict get $k dlCount] == 0 } {
                    $ytProgressBarser($n) setPercentVideo [dict get $k percent]
                    puts "setPercentVideo [dict get $k percent]"
                } elseif { [dict get $k dlCount] == 1 } {
                    $ytProgressBarser($n) setPercentAudio [dict get $k percent]
                    puts "setPercentAudio [dict get $k percent]"
                }
            } elseif { [lsearch [dict keys $k] fileName] != -1 } {
                puts "fileName [pwd] [dict get $k fileName]"
            } elseif { [lsearch [dict keys $k] destination] != -1 } {
                $frameName($n) configure -text "[dict get $k destination]"
            }
            sleep 210
        }
    }       
    method removeDownloader { n args } {
        puts "deleteDownloader>>seed>>$n"
        $ytEntry($n) destroy
        $ytProgressBarser($n) destroy
        $ytParser($n) destroy
        unset ytEntry($n)
        unset ytProgressBarser($n)
        unset ytParser($n)
        destroy $frameName($n)
    }   
    method resize.bind { W width height x y } {
            puts "resize.bind>>$W>>$width>>$height>>$x>>$y"
        if {"$W" eq [winfo toplevel $W]} {
            if { $width == $syncResize(winX) && $height == $syncResize(winY) } {
                return
            } else {
                set syncResize(winX) $width
                set syncResize(winY) $height
                [self] resize.wait
            }
        }
    }
    method resize.wait { } {
                    after cancel $syncResize(update)
                    set syncResize(update) [after 25 [list [self] resize.go]]
                 
    }
    method resize.go { } {
        puts "resize.go>>$syncResize(winX)"
        set resizeVal [expr { $syncResize(winX) - 7 }]
        foreach n [array names ytProgressBarser] {
            $ytProgressBarser($n) setWidthVideo $syncResize(winX)
            $ytProgressBarser($n) setWidthAudio $syncResize(winX)
        }
    }
}
###END### Source:ytGui.tcl ###END###
set z [yt_gui new]

Watch that first line, it tells the shell how to run the file, make sure your tclsh is located in the same place as the script has it, or simply start it using .

tclsh scriptName.tcl

.. and this will run it.

Thanks for having a look. have a great day.

namaste