#! /usr/bin/env ruby

# This code is in the public domain
# Written by Robbert Haarman

require 'net/http'
require 'uri'

## Defaults
$loglevel = 10

def log priority, message
	$stderr.puts message if priority < $loglevel
end

def firstmatch subject, pattern
  match = subject.match(pattern)
  match ? match[1] : nil
end

def human_readable n
	if n < 0x400
		"#{n} bytes"
	elsif n < 0x100000
		"#{(n * 10 / 0x400).to_i / 10.0} KB"
	elsif n < 0x40000000
		"#{(n * 10 / 0x100000).to_i / 10.0} MB"
	else
		"#{(n * 10 / 0x40000000).to_i / 10.0} GB"
	end
end

def log_fetched n, total = nil
	if total
		$stderr.print "\r\x1b[KFetched #{human_readable n} of " +
			"#{human_readable total}\r"
	else
		$stderr.print "\r\x1b[KFetched #{human_readable n}\r"
	end
end

## Parse command line
uri = ARGV[0]

## Get video id
#match = uri.match(/watch\?v=([^&]*)/)
match = uri.match(/\?v=([^&]*)/)

unless match
	log 0, "Could not extract video id from URI"
	exit 1
end

video_id = match[1]
log 4, "Video ID: #{video_id}"

## Get video page
log 1, "Fetching video page: #{uri}"
page = Net::HTTP::get_response URI.parse(uri)

unless page.instance_of? Net::HTTPOK
	log 0, "Error fetching #{uri}"
	exit 1
end

## Extract params
#match = page.body.match(/player2\.swf\?([^"]*)/)
match = page.body.match(/watch_fullscreen\?([^"']*)/)

unless match
	log 0, "Could not extract video parameters"
	exit 1
end

params = match[1]
log 5, "Video Parameters: #{params.inspect}"

## Extract needed parameters
t = firstmatch params, /(?:\?|&)t=([^&]*)/

unless t
  log 0, "Could not extract t"
  exit 1
end

## Compute video URI
video_uri = "http://www.youtube.com/get_video?video_id=#{video_id}&t=#{t}"

## Follow redirects
while true
	log 6, "Fetching headers for #{video_uri}"

	uri = URI.parse video_uri
	resp = Net::HTTP::start(uri.host, uri.port) do |http|
		http.request_head "#{uri.path}?#{uri.query}"
	end

	case resp
	when Net::HTTPSuccess
		break
	when Net::HTTPRedirection
		video_uri = resp['location']
		log 5, "Redirected to #{video_uri}"
	else
		log 0, "Unexpected response: #{resp}"
		exit 1
	end
end

log 4, "Real video URI: #{video_uri}"

## Compute filename
filename = "#{video_id}.flv"
log 2, "Saving video to #{filename}"

## Save video to file
File.open(filename, 'wb') do |file|
	Net::HTTP::get_response(URI.parse(video_uri)) do |resp|
		begin
			size = resp['content-length'].to_i
		rescue
			size = nil
		end

		fetched = 0
		msgtime = 0.0
		resp.read_body do |chunk|
			file.print chunk
			fetched = fetched + chunk.size
			if $loglevel > 0 && Time.now.to_f - msgtime >= 1.0
				log_fetched fetched, size
				msgtime = Time.now.to_f
			end
		end

		$stderr.print "\r\x1b[KDone" if $loglevel > 0
	end
end
