All pastes #942327 Raw Edit

Stu

public text v1 · immutable
#942327 ·published 2008-03-14 13:26 UTC
rendered paste body
#!/usr/bin/env ruby

# Download iPlayer programmes by spoofing an iPhone
# Paul Battley - http://po-ru.com/

require 'net/http'
require 'uri'

# Used by Safari Mobile
IPHONE_UA  = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ '+
             '(KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3'
# Used by Quicktime
QT_UA      = 'Apple iPhone v1.1.3 CoreMedia v1.0.0.4A93'

DEFAULT_HEADERS = {
  'Accept'          => '*/*',
  'Accept-Language' => 'en',
  'Accept-Encoding' => 'gzip, deflate',
  'Connection'      => 'keep-alive',
  'Pragma'          => 'no-cache'
}

class Net::HTTPResponse # Monkey-patch in some 21st-century functionality
  include Enumerable
  
  def cookies
    inject([]){ |acc, (key, value)|
      key == 'set-cookie' ? acc << value.split(/;/).first : acc
    }
  end
  
  def to_hash
    @to_hash ||= inject({}){ |hash, (key, value)|
      hash[key] = value
      hash
    }
  end
end

def http_get(location, headers={}, &blk)
  url = URI.parse(location)
  http = Net::HTTP.new(url.host, url.port)
  path = url.path
  if url.query
    path << '?' << url.query
  end
  http.request_get(path, DEFAULT_HEADERS.merge(headers), &blk)
end

page_url = ARGV[0]

unless page_url
  puts "Download DRM-free videos from the BBC iPlayer,"
  puts "courtesy of their iPhone interface."
  puts
  puts "Usage: #{$0} URL"
  puts "Where URL is the iPlayer viewing page."
  print "Or enter the URL here: "
  gets page_url
end

# Get the actual programme page
response = http_get( page_url,
                     'User-Agent' => IPHONE_UA )
cookies = response.cookies.join('; ')
html = response.body

# The only information we really need is the pid
pid = html[/\bpid[ \t]+:[ \t]+'([a-z0-9]+)'/, 1]
title = ( html[%r!<title>([^<]+)</title>!, 1].split(/ - /).last +
          ' - ' +
          html[%r!<h2>([^<]+)</h2>!, 1] ).gsub(/[^a-z0-9 \-]+/i, '')

# Get the auth URL
r = (rand * 10000000).floor
selector = "http://www.bbc.co.uk/mediaselector/3/auth/iplayer_streaming_http_mp4/#{ pid }?#{r}"
response = http_get( selector, 
                     'Cookie'     => cookies,
                     'User-Agent' => QT_UA,
                     'Range'      => 'bytes=0-1' )

# It redirects us to the real stream location
location = response.to_hash['location']
response = http_get( location,
                     'Cookie'     => cookies,
                     'User-Agent' => QT_UA,
                     'Range'      => 'bytes=0-1' )

# We now know the full length of the content
max = response.to_hash['content-range'][/\d+$/].to_i

bytes_got = 0
old_percentage = nil
File.open("#{ title }.mov", 'wb') do |io|
  http_get( location,
            'Cookie'     => cookies,
            'User-Agent' => QT_UA,
            'Range'      => "bytes=0-#{max}" ) do |response|
    response.read_body do |data|
      bytes_got += data.length
      percentage = "%.1f" % [((1000 * bytes_got) / max) / 10.0]
      if percentage != old_percentage
        old_percentage = percentage
        print "\r#{percentage}%"
        $stdout.flush
      end
      io << data
    end
  end
end
puts