An easy wrapper for the famous mplayer to embed videos into Tcl/Tk applications.
Provide
#!/usr/bin/tclsh
# Author: Detlef Groth
# License MIT
# Version 0.2 working and usable
# Version 0.2 adds support for direct play of youtube videos
# Version 0.3 fixes issue if youtube-dl is not installed but tried to load an youtube video
# Version 0.4 fix vnc issues and missing medium and small tags issues in youtube videos
# youtube-dl script is required for youtube urls see https://rg3.github.io/youtube-dl/
package require Tk
package require snit
package provide SnitMPlayer 0.4
snit::widget SnitMPlayer {
option -infile ""
variable mplayer ""
variable position 0:00
variable container ""
variable toolbar ""
variable treeview
constructor {args} {
$self configurelist $args
pack [panedwindow $win.pan -orient horizontal] -side left -expand yes -fill both -pady 2 -padx 2
ttk::treeview $win.pan.tv -columns [list title id] -show headings
$win.pan.tv heading title -text Title
$win.pan.tv heading id -text VideoId
set treeview $win.pan.tv
bind $treeview <Double-1> [mymethod tvOnClick %W %x %y]
frame $win.pan.f
frame $win.pan.f.container -container yes
set container $win.pan.f.container
#$win.pan.tv configure -width 200
#.$win.pan.f configure -width 100
$win.pan add $win.pan.tv $win.pan.f
pack $win.pan.f.container -fill both -expand yes
#-cookies -cookies-file ${COOKIE_FILE} $($1)
set toolbar [frame $win.pan.f.toolbar]
pack [button $toolbar.btnopen -command [mymethod fileOpen] -image fileopen-16] -side left -padx 5 -pady 5
pack [button $toolbar.btnpaase -command [mymethod mplayerCmd] -image playpause16] -side left -padx 5 -pady 5
pack [button $toolbar.btnstart -command [mymethod mplayerCmd "set_property time_pos 0"] -image player_start-16] -side left -padx 5 -pady 5
pack [button $toolbar.btnforward -command [mymethod mplayerCmd "seek -10"] -image 1leftarrow-16] -side left -padx 5 -pady 5
pack [button $toolbar.btnbackward -command [mymethod mplayerCmd "seek +10"] -image 1rightarrow-16] -side left -padx 5 -pady 5
pack [button $toolbar.btnend -command [mymethod mplayerCmd "seek 99.5 1"] -image player_end-16] -side left -padx 5 -pady 5
pack [button $toolbar.btnaudiodn -command [mymethod mplayerCmd "volume -10"] -image actitemdelete16] -side left -padx 5 -pady 5
pack [button $toolbar.btnaudioup -command [mymethod mplayerCmd "volume +10"] -image actitemadd16] -side left -padx 5 -pady 5
pack [entry $toolbar.pos -textvariable [myvar position] -width 6] -side left -padx 5 -pady 5
bind $toolbar.pos <Return> [mymethod gotoPosition]
pack [button $toolbar.btnexit -command [mymethod exit] -image actcross16] -side left -padx 5 -pady 5
#$self openVideo $options(-infile)
}
typeconstructor {
image create photo 1leftarrow-16 -data {
R0lGODlhEAAQAIAAAP///wAAACH5BAEAAAAALAAAAAAQABAAAAIdhI+pyxqd
woNGTmgvy9px/IEWBWRkKZ2oWrKu4hcAIf5oQ3JlYXRlZCBieSBCTVBUb0dJ
RiBQcm8gdmVyc2lvbiAyLjUNCqkgRGV2ZWxDb3IgMTk5NywxOTk4LiBBbGwg
cmlnaHRzIHJlc2VydmVkLg0KaHR0cDovL3d3dy5kZXZlbGNvci5jb20AOw==
}
image create photo 1rightarrow-16 -data {
R0lGODlhEAAQAIAAAPwCBAQCBCH5BAEAAAAALAAAAAAQABAAAAIdhI+pyxCt
woNHTmpvy3rxnnwQh1mUI52o6rCu6hcAIf5oQ3JlYXRlZCBieSBCTVBUb0dJ
RiBQcm8gdmVyc2lvbiAyLjUNCqkgRGV2ZWxDb3IgMTk5NywxOTk4LiBBbGwg
cmlnaHRzIHJlc2VydmVkLg0KaHR0cDovL3d3dy5kZXZlbGNvci5jb20AOw==
}
image create photo player_start-16 -data {
R0lGODlhEAAQAIAAAPwCBAQCBCH5BAEAAAAALAAAAAAQABAAAAIjhI+pyxud
wlNyguqkqRZh3h0gl43hpoElqlHt9UKw7NG27BcAIf5oQ3JlYXRlZCBieSBC
TVBUb0dJRiBQcm8gdmVyc2lvbiAyLjUNCqkgRGV2ZWxDb3IgMTk5NywxOTk4
LiBBbGwgcmlnaHRzIHJlc2VydmVkLg0KaHR0cDovL3d3dy5kZXZlbGNvci5j
b20AOw==
}
image create photo player_end-16 -data {
R0lGODlhEAAQAIAAAPwCBAQCBCH5BAEAAAAALAAAAAAQABAAAAIjhI+py8Eb
3ENRggrxjRnrVIWcIoYd91FaenysMU6wTNeLXwAAIf5oQ3JlYXRlZCBieSBC
TVBUb0dJRiBQcm8gdmVyc2lvbiAyLjUNCqkgRGV2ZWxDb3IgMTk5NywxOTk4
LiBBbGwgcmlnaHRzIHJlc2VydmVkLg0KaHR0cDovL3d3dy5kZXZlbGNvci5j
b20AOw==
}
image create photo player_stop-16 -data {
R0lGODlhEAAQAIAAAPwCBAQCBCH5BAEAAAAALAAAAAAQABAAAAIahI+py+1v
gpySUWpvXXqrHmSaeJEYhKYq6hcAIf5oQ3JlYXRlZCBieSBCTVBUb0dJRiBQ
cm8gdmVyc2lvbiAyLjUNCqkgRGV2ZWxDb3IgMTk5NywxOTk4LiBBbGwgcmln
aHRzIHJlc2VydmVkLg0KaHR0cDovL3d3dy5kZXZlbGNvci5jb20AOw==
}
image create photo fileopen-16 -data {
R0lGODlhEAAQAIUAAPwCBAQCBOSmZPzSnPzChPzGhPyuZEwyHExOTFROTFxa
VFRSTMSGTPT29Ozu7Nze3NTS1MzKzMTGxLy6vLS2tLSytDQyNOTm5OTi5Ly+
vKyqrKSmpIyOjLR+RNTW1MzOzJyenGxqZBweHKSinJSWlExKTMTCxKyurGxu
bBQSFAwKDJyanERCRERGRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAaR
QIBwGCgGhkhkEWA8HpNPojFJFU6ryitTiw0IBgRBkxsYFAiGtDodDZwPCERC
EV8sEk0CI9FoOB4BEBESExQVFgEEBw8PFxcYEBIZGhscCEwdCxAPGA8eHxkU
GyAhIkwHEREQqxEZExUjJCVWCBAZJhEmGRUnoygpQioZGxsnxsQrHByzQiJx
z3EsLSwWpkJ+QQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9u
IDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2
ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7
}
image create photo playpause16 -data {
R0lGODlhEAAQAIAAAPwCBAQCBCH5BAEAAAAALAAAAAAQABAAAAIfhI+py+1v
goxzyUCxrZd18ClfmIyVyJ1lqkHuC0N+AQAh/mhDcmVhdGVkIGJ5IEJNUFRv
R0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFs
bCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7
}
image create photo actitemadd16 -data {
R0lGODlhEAAQAIQAAPwCBERCBKSepJyenJyWnJyanJSWlJSOlJSSlIyKjIyG
jISGhIR+hGRiZHx+fHx2fERGBHx6fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAVpICCOZGkG
aGqSgTAMRLCOQTEY8SzWBHHIpRTKgED8hDtBoWBoHhKKxYIRaABqBYTh8Iwy
HI/qtdA7GBQJKYPxgFgDRKPC64hEwm9heuEQillTdVUNhFYsEQ5sfysBYA94
On6LK4WFJH4hACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24g
Mi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZl
ZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=
}
image create photo actitemdelete16 -data {
R0lGODlhEAAQAIMAAPwCBERCBKSmpGRiZJSWlJyanIyKjIR+hHx6fAAAAAAA
AAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAQ4EMhJq704681n
+GAYSoFgnqgZDEBAFEbxxnOxtoeh77xxB4jgIYgYEn+i5IcFGDifUGhnSq1a
/BEAIf5oQ3JlYXRlZCBieSBCTVBUb0dJRiBQcm8gdmVyc2lvbiAyLjUNCqkg
RGV2ZWxDb3IgMTk5NywxOTk4LiBBbGwgcmlnaHRzIHJlc2VydmVkLg0KaHR0
cDovL3d3dy5kZXZlbGNvci5jb20AOw==
}
image create photo actcross16 -data {
R0lGODlhEAAQAIIAAASC/PwCBMQCBEQCBIQCBAAAAAAAAAAAACH5BAEAAAAA
LAAAAAAQABAAAAMuCLrc/hCGFyYLQjQsquLDQ2ScEEJjZkYfyQKlJa2j7AQn
MM7NfucLze1FLD78CQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJz
aW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVz
ZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7
}
}
destructor {
if {$mplayer ne ""} {
puts $mplayer "quit"
flush $mplayer
}
}
method tvOnClick {w x y} {
puts "$w $x $y"
set row [$treeview identify item $x $y]
set id [lindex [$treeview item $row -values] 1]
$self mplayerCmd "stop"
$self openVideo https://www.youtube.com/watch?v=$id
}
method addToolbar {} {
pack $toolbar -side top -expand false -anchor c
}
method ReadPipe {chan} {
set d [read $chan]
foreach line [split $d \n] {
set line [string trim $line]
puts $line
}
if {[eof $chan]} {
fileevent $chan readable {}
close $chan
set mplayer ""
}
}
method getPlaylist {playlist} {
if {[auto_execok youtube-dl] eq ""} {
tk_messageBox -title "Error!" -icon error -message "This feature requires youtube-dl commandline application" -type ok
return
}
set pll [list]
if {[file exists /tmp/$playlist.txt]} {
if [catch {open /tmp/$playlist.txt r} infh] {
puts stderr "Cannot open $playlist.txt: $infh"
exit
} else {
while {[gets $infh line] >= 0} {
lappend pll [lindex [split $line "\t"] 0]
lappend pll [lindex [split $line "\t"] 1]
}
close $infh
}
} else {
set out [open /tmp/$playlist.txt w 0600]
set res [exec youtube-dl --get-id --get-title $playlist -i]
foreach {title id} [split $res "\n"] {
lappend pll $id
lappend pll $title
puts $out "$id\t$title"
}
close $out
}
set x 0
foreach {id title} $pll {
if {[incr x] == 1} {
$self openVideo https://www.youtube.com/watch?v=$id
}
$treeview insert {} end -values [list $title $id]
}
}
method getFormat {url} {
set res [exec youtube-dl -F $url]
array set has [list]
foreach line [split $res "\n"] {
if {[regexp {([0-9]{2})\s+(3gp|mp4)\s+(320|640).+(small|medium)} $line -> f format size res] } {
#puts $line
set has($res) $f
}
if {[regexp {([0-9]{2,3})\s+(3gp|mp4)\s+([56][0-9]{2}x[34][0-9]{2})} $line -> f format size] } {
#puts $line
if {![regexp {only} $line]} {
set has(medium) $f
}
}
if {[regexp {([0-9]{2,3})\s+(3gp|mp4)\s+([2-4][0-9]{3}x3[0-9]{2})} $line -> f format size] } {
#puts $line
if {![regexp {only} $line]} {
set has(small) $f
}
}
}
if {[info exists has(medium)]} {
return $has(medium)
} elseif {[info exists has(small)]} {
return $has(small)
} else {
error no-video-stream-found
}
}
method openVideo {filename} {
if {[regexp {^PL} $filename]} {
$self getPlaylist $filename
return
}
if {[regexp {https://www.youtube} $filename]} {
if {[auto_execok youtube-dl] eq ""} {
tk_messageBox -title "Error!" -icon error -message "This feature requires youtube-dl commandline application" -type ok
# dummy pipe for local files starting
# but does not work :( channel closes
# just start an idle pipe waiting for local file input
set fid [open "|mplayer -zoom -slave -idle -fstype fullscreen -wid [winfo id $container]" r+]
#puts "fid=$fid"
} else {
set f [$self getFormat $filename]
set url [exec youtube-dl -g -f $f --cookies /tmp/cookie.txt $filename]
# need to remove -quit
set fid [open "|mplayer -zoom -slave -idle -fstype fullscreen -wid [winfo id $container] -fs -cookies -cookies-file /tmp/cookie.txt $url" r+]
}
} else {
set fid [open "|mplayer -zoom -slave -ss 0:00 -quiet -idle -fstype fullscreen -wid [winfo id $container] $filename" r+]
}
fconfigure $fid -blocking 0 -buffering line
fileevent $fid readable [mymethod ReadPipe $fid]
set mplayer $fid
}
method fileOpen {} {
set types {
{{Video Files} {.avi .mp4} }
{{Audio Files} {.mp3 .ogg} }
{{All Files} * }
}
set filename [tk_getOpenFile -filetypes $types]
if {$filename != ""} {
$self mplayerCmd stop
after 500
# Open the file ...
$self mplayerCmd "loadfile $filename"
}
}
method mplayerCmd {{arg pause}} {
puts $mplayer $arg
flush $mplayer
}
method gotoPosition {} {
set pos [split $position ":"]
set seconds [expr {[lindex $pos 0]*60+[lindex $pos 1]}]
$self mplayerCmd "seek $seconds 2"
}
method exit {} {
$self mplayerCmd "stop"
#rename $win ""
}
}
if {$argv0 eq [info script]} {
#SnitMPlayer .top -infile /run/media/dgroth/0123-4567/Videos/Bariuke/Cortez_Killer_Young_Bariuke_D.mp4
#exit
if {[llength $argv] <= 1 } {
SnitMPlayer .smp
pack .smp -fill both -expand yes
.smp addToolbar
if {[llength $argv] == 1} {
.smp openVideo [lindex $argv 0]
} else {
# default with no arguments
.smp openVideo https://www.youtube.com/watch?v=ATUyKlQpC4k
}
} else {
set nb [ttk::notebook .nb]
set x 0
foreach video $argv {
puts $argv
SnitMPlayer $nb.smp[incr x]
$nb add $nb.smp$x -text "# $x"
$nb.smp$x addToolbar
$nb.smp$x openVideo [lindex $argv [expr {$x - 1}]]
if {$x > 1} {
after 200
$nb.smp$x mplayerCmd pause
}
}
pack $nb -fill both -expand yes
}
}
Command line check:
$ tclsh SnitMPlayer.tcl https://www.youtube.com/watch?v=zE_3ZYi6iTU
2025-12-10: Inspired by the example shown above, I have created a simple Tcl wrapper module for mplayer (mplayer-1.tm) aimed at audio (not video) use. I've provided a simple GUI application to illustrate how the module can be used: PlayTrack . The only dependencies are Tcl/Tk 9, Tcllib, Tklib, and the mplayer executable itself.