FPX: Watching videos on YouTube [L1 ] or Google Video [L2 ] 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 [L3 ]. 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 [L4 ], 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 [L5 ]. 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."