FPX: Watching videos on YouTube [
1] or Google Video [
2] is fun. Sometimes, I want to keep them. You never know when they will be (re-)moved for whatever reason, and it's also nice to have the file available for offline viewing.
For YouTube, there is youtube-dl [
3]. I could not find an equivalent utility for Google Video, so here's one. Please upgrade as necessary.
This code is based on the hints at [
4], and some trial-and-error.
Compared with Google's own "download" feature, this one does not require a Google-specific player. The files that you receive are in Flash Video format and can be played using, e.g., VideoLAN [
5]. More information about the Flash Video format is at
http://en.wikipedia.org/wiki/FLV.
Update 2006-11-15: Sometimes, the initial web page directly links directly to the video data, sometimes the page resolves to a redirection. Account for both cases.
#! /bin/sh
# the next line restarts using tclsh \
exec tclsh8.4 $0 "$@"
package require http
if {[llength $argv] != 1} {
puts "usage: $argv0 <google video URL>"
exit 0
}
set url [lindex $argv 0]
puts -nonewline "Downloading $url ..."
flush stdout
if {[catch {set urlToken [http::geturl $url]} oops]} {
puts "$oops"
exit 1
}
if {[http::status $urlToken] ne "ok"} {
puts [http::error $urlToken]
http::cleanup $urlToken
exit 1
}
set urlData [http::data $urlToken]
http::cleanup $urlToken
puts "done."
#
# Looking for ...googleplayer.swf?&videoUrl\u003d[<video URL>]&
#
puts -nonewline "Extracting encoded video URL ... "
flush stdout
if {[set googleplayerIndex [string first "googleplayer.swf" $urlData]] == -1} {
puts "failed."
puts "Error: magic string \"googleplayer.swf\" not found."
exit 1
}
if {[string range $urlData \
[expr {$googleplayerIndex + 18}] \
[expr {$googleplayerIndex + 25}]] ne "videoUrl"} {
puts "failed."
puts "Error: magic string \"videoUrl\" not found."
exit 1
}
if {[string range $urlData \
[expr {$googleplayerIndex + 26}] \
[expr {$googleplayerIndex + 31}]] ne "\\u003d"} {
puts "failed."
puts "Error: magic URL marker not found."
exit 1
}
set urlBeginIndex [expr {$googleplayerIndex + 32}]
if {[set urlEndIndex [string first "&" $urlData $urlBeginIndex]] == -1} {
puts "failed."
puts "Error: magic end of URL marker not found."
exit 1
}
incr urlEndIndex -1
set encodedVideoUrl [string range $urlData $urlBeginIndex $urlEndIndex]
if {[string first "\n" $encodedVideoUrl] != -1 || \
[string first "\"" $encodedVideoUrl] != -1} {
puts "failed."
puts "Error: video URL looks fishy."
exit 1
}
puts "done."
#
# Replace all URL-encoded characters, e.g., replace "%3D" with "="
#
puts -nonewline "Unencoding video URL ... "
flush stdout
lappend ud_map + { }
for {set i 0} {$i < 256} {incr i} {
set c [format %c $i]
set x %[format %02x $i]
if {![string match {[a-zA-Z0-9]} $c]} {
lappend ud_map $x $c
}
}
set videoUrl [string map -nocase $ud_map $encodedVideoUrl]
puts "done."
#
# At this URL, we may get the video data. Or, we might be redirected.
#
puts -nonewline "Determining file name ... "
flush stdout
if {[catch {set videoToken [http::geturl $videoUrl -validate 1]} oops]} {
puts "$oops"
exit 1
}
upvar \#0 $videoToken videoState
array set videoMeta $videoState(meta)
http::cleanup $videoToken
if {[info exists videoMeta(Location)]} {
puts -nonewline "Following redirection ... "
flush stdout
set videoUrl $videoMeta(Location)
if {[catch {set videoToken [http::geturl $videoUrl -validate 1]} oops]} {
puts "$oops"
exit 1
}
upvar \#0 $videoToken videoState
array set videoMeta $videoState(meta)
http::cleanup $videoToken
}
#
# There should be a Content-Disposition: attachment; filename=<file name> header.
#
if {![info exists videoMeta(Content-Disposition)]} {
puts "failed."
puts "Error: no Content-Disposition header found."
exit 1
}
set disposition $videoMeta(Content-Disposition)
if {[set filenameIndex [string first "filename=" $disposition]] == -1} {
puts "failed."
puts "Error: filename not found in \"$disposition\""
exit 1
}
set filenameFirst [expr {$filenameIndex + 9}]
set filename [string range $disposition $filenameFirst end]
puts "done."
#
# Download the video for real.
#
proc progressIndicator {token total current} {
set currentKb [expr {$current / 1024}]
set totalKb [expr {$total / 1024}]
puts -nonewline "\rDownloading $::filename ... $currentKb "
if {$total != 0} {
puts -nonewline "/ $totalKb "
}
}
puts -nonewline "Downloading $filename ... "
if {[catch {set output [open $filename "w"]} oops]} {
puts "failed."
puts "Error: can not open \"$filename\" for writing: $oops"
exit 1
}
if {[catch {set videoToken [http::geturl $videoUrl -channel $output -progress progressIndicator]} oops]} {
puts "$oops"
catch {close $output}
exit 1
}
if {[http::status $videoToken] ne "ok"} {
puts [http::error $videoToken]
http::cleanup $videoToken
exit 1
}
http::cleanup $videoToken
puts "done."