iplayer-dl 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +26 -0
- data/README +14 -0
- data/Rakefile +95 -0
- data/bin/iplayer-dl +152 -0
- data/bin/iplayer-dl-gui +26 -0
- data/lib/iplayer.rb +5 -0
- data/lib/iplayer/browser.rb +69 -0
- data/lib/iplayer/downloader.rb +132 -0
- data/lib/iplayer/errors.rb +53 -0
- data/lib/iplayer/gui/app.rb +84 -0
- data/lib/iplayer/gui/main_frame.rb +123 -0
- data/lib/iplayer/metadata.rb +63 -0
- data/lib/iplayer/preferences.rb +70 -0
- data/lib/iplayer/subtitles.rb +33 -0
- data/lib/iplayer/version.rb +4 -0
- data/setup.rb +1596 -0
- data/share/pixmaps/iplayer-dl/icon128.png +0 -0
- data/share/pixmaps/iplayer-dl/icon16.png +0 -0
- data/share/pixmaps/iplayer-dl/icon32.png +0 -0
- data/share/pixmaps/iplayer-dl/icon48.png +0 -0
- data/share/pixmaps/iplayer-dl/icon64.png +0 -0
- data/test/test_metadata.rb +75 -0
- data/test/test_preferences.rb +175 -0
- data/test/test_subtitles.rb +57 -0
- metadata +93 -0
data/COPYING
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2008 Paul Battley <pbattley@gmail.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
21
|
+
If you have received this program as a self-executing bundle, the following
|
22
|
+
licences apply to individual components:
|
23
|
+
|
24
|
+
ruby: GNU General Public License (GPL), version 2; Ruby License
|
25
|
+
rubyscript2exe: GNU General Public License (GPL), version 2
|
26
|
+
wxruby: wxWindows Licence
|
data/README
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
I've refactored iplayer-dl in order to make it easier to extend and wrap. As a
|
2
|
+
direct result, it's no longer just one script. As a result, you'll need to
|
3
|
+
install the libraries and command-line script in order to use it.
|
4
|
+
|
5
|
+
The good news is that this is as easy as typing:
|
6
|
+
|
7
|
+
ruby setup.rb config
|
8
|
+
sudo ruby setup.rb install
|
9
|
+
|
10
|
+
Windows users should omit 'sudo' in the second line.
|
11
|
+
|
12
|
+
You can then run the downloader just by typing:
|
13
|
+
|
14
|
+
iplayer-dl
|
data/Rakefile
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/packagetask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake'
|
5
|
+
require 'find'
|
6
|
+
require 'lib/iplayer/version'
|
7
|
+
|
8
|
+
# Globals
|
9
|
+
|
10
|
+
PKG_NAME = 'iplayer-dl'
|
11
|
+
|
12
|
+
PKG_FILES = %w[ COPYING README setup.rb Rakefile ]
|
13
|
+
Find.find('lib/', 'test/', 'bin/', 'share/') do |f|
|
14
|
+
if FileTest.directory?(f) and File.basename(f) =~ /^\./
|
15
|
+
Find.prune
|
16
|
+
else
|
17
|
+
PKG_FILES << f
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
EXE_FILES = PKG_FILES + %w[ application.ico init.rb ]
|
22
|
+
|
23
|
+
# Tasks
|
24
|
+
|
25
|
+
task :default => :test
|
26
|
+
|
27
|
+
Rake::TestTask.new do |t|
|
28
|
+
t.libs << "test"
|
29
|
+
t.test_files = FileList['test/test_*.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::PackageTask.new(PKG_NAME, IPlayer::VERSION) do |p|
|
33
|
+
p.need_tar_gz = true
|
34
|
+
p.package_files = PKG_FILES
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Build a Windows executable. Needs rubyscript2exe.rb in the current path and the wx gem installed."
|
38
|
+
task :exe do |t|
|
39
|
+
mkdir_p 'tmp'
|
40
|
+
build_dir = File.join('tmp', "ipdl-#{IPlayer::GUI_VERSION}")
|
41
|
+
rm_rf build_dir
|
42
|
+
mkdir build_dir
|
43
|
+
EXE_FILES.each do |file|
|
44
|
+
next if File.directory?(file)
|
45
|
+
loc = File.join(build_dir, File.dirname(file))
|
46
|
+
mkdir_p loc
|
47
|
+
cp_r file, loc
|
48
|
+
end
|
49
|
+
sh "ruby rubyscript2exe.rb #{build_dir} --rubyscript2exe-verbose --rubyscript2exe-rubyw"
|
50
|
+
rm_rf build_dir
|
51
|
+
mkdir_p 'pkg'
|
52
|
+
mv "ipdl-#{IPlayer::GUI_VERSION}.exe", "pkg"
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
require "rake/gempackagetask"
|
57
|
+
|
58
|
+
spec = Gem::Specification.new do |s|
|
59
|
+
# Change these as appropriate
|
60
|
+
s.name = "iplayer-dl"
|
61
|
+
s.version = IPlayer::VERSION
|
62
|
+
s.summary = "Download iPlayer content"
|
63
|
+
s.author = "Paul Battley"
|
64
|
+
s.email = "pbattley@gmail.com"
|
65
|
+
s.homepage = "http://po-ru.com/projects/iplayer-downloader"
|
66
|
+
|
67
|
+
s.has_rdoc = false
|
68
|
+
|
69
|
+
# Add any extra files to include in the gem
|
70
|
+
s.files = PKG_FILES
|
71
|
+
s.executables = ["iplayer-dl"]
|
72
|
+
|
73
|
+
s.require_paths = ["lib"]
|
74
|
+
|
75
|
+
# If you want to depend on other gems, add them here, along with any
|
76
|
+
# relevant versions
|
77
|
+
# s.add_dependency("some_other_gem", "~> 0.1.0")
|
78
|
+
|
79
|
+
# If your tests use any gems, include them here
|
80
|
+
s.add_development_dependency("mocha")
|
81
|
+
end
|
82
|
+
|
83
|
+
# This task actually builds the gem. We also regenerate a static
|
84
|
+
# .gemspec file, which is useful if something (i.e. GitHub) will
|
85
|
+
# be automatically building a gem for this project. If you're not
|
86
|
+
# using GitHub, edit as appropriate.
|
87
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
88
|
+
pkg.gem_spec = spec
|
89
|
+
|
90
|
+
# Generate the gemspec file for github.
|
91
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
92
|
+
File.open(file, "w") {|f| f << spec.to_ruby }
|
93
|
+
end
|
94
|
+
rescue LoadError
|
95
|
+
end
|
data/bin/iplayer-dl
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Download iPlayer programmes by spoofing an iPhone
|
4
|
+
# Paul Battley - http://po-ru.com/
|
5
|
+
#
|
6
|
+
# Get the latest version via subversion:
|
7
|
+
# svn co http://paulbattley.googlecode.com/svn/iplayer-dl
|
8
|
+
|
9
|
+
require 'iplayer'
|
10
|
+
require 'optparse'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'iplayer/version'
|
13
|
+
require 'iplayer/preferences'
|
14
|
+
|
15
|
+
include IPlayer
|
16
|
+
include IPlayer::Errors
|
17
|
+
|
18
|
+
preferences = IPlayer::Preferences.new
|
19
|
+
filenames = []
|
20
|
+
pid_list = nil
|
21
|
+
dry_run = false
|
22
|
+
|
23
|
+
opts = ARGV.options{ |o|
|
24
|
+
o.banner << ' IDENTIFIER [IDENTIFIER [...]]'
|
25
|
+
o.define_head 'Download DRM-free videos from the BBC iPlayer, courtesy of their iPhone interface.'
|
26
|
+
o.separator 'IDENTIFIER is the iPlayer viewing page URL or the PID of the programme.'
|
27
|
+
o.separator ''
|
28
|
+
o.on(
|
29
|
+
'-t', '--type-preference=VERSION', String,
|
30
|
+
'Video types in order of preference.',
|
31
|
+
"Default is '#{preferences.type_preference.join(',')}'."
|
32
|
+
) { |s| preferences.type_preference = s.split(/,\s*/) }
|
33
|
+
o.on(
|
34
|
+
'-d', '--download-path=PATH', String,
|
35
|
+
'Location into which downloaded files will be saved.',
|
36
|
+
'Default is current working directory.'
|
37
|
+
) { |d| preferences.download_path = d }
|
38
|
+
o.on(
|
39
|
+
'-s', '--title-subdir',
|
40
|
+
'Place downloaded files in a sub-directory named after the title of the programme.'
|
41
|
+
) { preferences.subdirs = true }
|
42
|
+
o.on(
|
43
|
+
'-u', '--subtitles',
|
44
|
+
'Also download subtitles.'
|
45
|
+
) { preferences.subtitles = true }
|
46
|
+
o.on(
|
47
|
+
'-f', '--filename=FILENAME', String,
|
48
|
+
'Manually specify a name for the downloaded file.',
|
49
|
+
'The default is constructed from the programme metadata.',
|
50
|
+
'You can specify this multiple times for each download in order.'
|
51
|
+
) { |f| filenames << f }
|
52
|
+
o.on(
|
53
|
+
'-p', '--http-proxy=HOST:PORT', String,
|
54
|
+
'Specify an HTTP proxy.',
|
55
|
+
'Default is taken from the http_proxy environment variable.'
|
56
|
+
) { |p| preferences.http_proxy = p }
|
57
|
+
o.on(
|
58
|
+
'-l', '--pid-list=FILENAME', String,
|
59
|
+
'List PIDs to be downloaded in a file, one per line.'
|
60
|
+
) { |l| pid_list = l }
|
61
|
+
o.on(
|
62
|
+
'-n', '--dry-run', String,
|
63
|
+
'Parse the iPlayer page and output the filename but don\'t actually download (useful for scripting).'
|
64
|
+
) { |n| dry_run = true }
|
65
|
+
o.on(
|
66
|
+
'-v', '--version',
|
67
|
+
'Show the software version.'
|
68
|
+
) { puts IPlayer::VERSION; exit }
|
69
|
+
o.on_tail(
|
70
|
+
'-h', '--help',
|
71
|
+
'Show this help message.'
|
72
|
+
) { puts o; exit }
|
73
|
+
}
|
74
|
+
|
75
|
+
begin
|
76
|
+
opts.parse!
|
77
|
+
pids = ARGV
|
78
|
+
if pid_list
|
79
|
+
pids += File.read(pid_list).strip.split(/\s*\r?\n\s*/)
|
80
|
+
end
|
81
|
+
raise 'no programme identifier specified' if pids.empty?
|
82
|
+
rescue => exception
|
83
|
+
$stderr.puts 'Error: '+exception, ''
|
84
|
+
puts opts
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
|
88
|
+
if http_proxy = preferences.http_proxy
|
89
|
+
http_proxy = 'http://' + http_proxy unless http_proxy =~ %r{^http://}
|
90
|
+
u = URI.parse(http_proxy)
|
91
|
+
$stderr.puts "Using proxy #{u.host}:#{u.port}"
|
92
|
+
http = Net::HTTP::Proxy(u.host, u.port)
|
93
|
+
else
|
94
|
+
http = Net::HTTP
|
95
|
+
end
|
96
|
+
|
97
|
+
pids.each_with_index do |pid, i|
|
98
|
+
browser = Browser.new(http)
|
99
|
+
|
100
|
+
begin
|
101
|
+
pid = Downloader.extract_pid(pid)
|
102
|
+
downloader = Downloader.new(browser, pid)
|
103
|
+
|
104
|
+
available_versions = downloader.available_versions
|
105
|
+
raise MP4Unavailable if available_versions.empty?
|
106
|
+
version = available_versions.sort_by{ |v|
|
107
|
+
preferences.type_preference.index(v.name) || 100
|
108
|
+
}.first
|
109
|
+
|
110
|
+
filename = filenames[i]
|
111
|
+
if filename.nil? || preferences.subdirs
|
112
|
+
metadata = downloader.metadata
|
113
|
+
filename ||= "#{ metadata.full_title }.#{ metadata.filetype }".gsub(/[^a-z0-9 \-\.]+/i, '')
|
114
|
+
end
|
115
|
+
if preferences.subdirs
|
116
|
+
subdir = metadata.title.gsub(/[^a-z0-9 \-\.]+/i, '')
|
117
|
+
path = File.expand_path( File.join( preferences.download_path, subdir ))
|
118
|
+
FileUtils.makedirs(path)
|
119
|
+
path = File.join( path, filename )
|
120
|
+
else
|
121
|
+
path = File.expand_path( File.join( preferences.download_path, filename ))
|
122
|
+
end
|
123
|
+
|
124
|
+
if dry_run
|
125
|
+
$stdout.puts filename
|
126
|
+
else
|
127
|
+
old_percentage = nil
|
128
|
+
first_chunk = true
|
129
|
+
|
130
|
+
$stderr.puts "#{ filename } (version: #{ version.name })"
|
131
|
+
download_options = {:subtitles => preferences.subtitles}
|
132
|
+
downloader.download(version.pid, path, download_options) do |position, max|
|
133
|
+
if first_chunk
|
134
|
+
$stderr.puts "Resuming download at #{position} bytes." if position > 0
|
135
|
+
first_chunk = false
|
136
|
+
end
|
137
|
+
|
138
|
+
percentage = "%.1f" % [((1000 * position) / max) / 10.0]
|
139
|
+
if percentage != old_percentage
|
140
|
+
old_percentage = percentage
|
141
|
+
$stderr.print "\r#{ percentage }%"
|
142
|
+
$stderr.flush
|
143
|
+
end
|
144
|
+
end
|
145
|
+
$stderr.puts
|
146
|
+
end
|
147
|
+
|
148
|
+
rescue RecognizedError => error
|
149
|
+
$stderr.puts(error.to_str)
|
150
|
+
next
|
151
|
+
end
|
152
|
+
end
|
data/bin/iplayer-dl-gui
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Download iPlayer programmes by spoofing an iPhone
|
4
|
+
# WxWidgets GUI interface
|
5
|
+
# Paul Battley - http://po-ru.com/
|
6
|
+
#
|
7
|
+
# Get the latest version via subversion:
|
8
|
+
# svn co http://paulbattley.googlecode.com/svn/iplayer-dl
|
9
|
+
|
10
|
+
require 'iplayer'
|
11
|
+
require 'iplayer/gui/app'
|
12
|
+
require 'iplayer/gui/main_frame'
|
13
|
+
require 'iplayer/version'
|
14
|
+
|
15
|
+
options = {
|
16
|
+
:type_preference => %w[original signed],
|
17
|
+
:http_proxy => ENV['http_proxy']
|
18
|
+
}
|
19
|
+
about = {
|
20
|
+
:name => 'iPlayer Downloader',
|
21
|
+
:version => "#{IPlayer::GUI_VERSION} (library #{IPlayer::VERSION})",
|
22
|
+
:developers => ['Paul Battley'],
|
23
|
+
:description => "Download programmes from the BBC iPlayer.\nVisit http://po-ru.com/projects/iplayer-downloader/\nfor more information."
|
24
|
+
}
|
25
|
+
app = IPlayer::GUI::App.new(IPlayer::GUI::MainFrame, about, options)
|
26
|
+
app.main_loop
|
data/lib/iplayer.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class Net::HTTPResponse # Monkey-patch in some 21st-century functionality
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def cookies
|
8
|
+
inject([]){ |acc, (key, value)|
|
9
|
+
key == 'set-cookie' ? acc << value.split(/;/).first : acc
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
@to_hash ||= inject({}){ |hash, (key, value)|
|
15
|
+
hash[key] = value
|
16
|
+
hash
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module IPlayer
|
22
|
+
class Browser
|
23
|
+
|
24
|
+
# Used by Safari Mobile
|
25
|
+
IPHONE_UA = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ '+
|
26
|
+
'(KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3'
|
27
|
+
|
28
|
+
# Used by Quicktime
|
29
|
+
QT_UA = 'Apple iPhone v1.1.4 CoreMedia v1.0.0.4A102'
|
30
|
+
|
31
|
+
# Safari, for no good reason
|
32
|
+
DESKTOP_UA = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_4; en-gb) '+
|
33
|
+
'AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1'
|
34
|
+
|
35
|
+
DEFAULT_HEADERS = {
|
36
|
+
'Accept' => '*/*',
|
37
|
+
'Accept-Language' => 'en',
|
38
|
+
'Connection' => 'keep-alive',
|
39
|
+
'Pragma' => 'no-cache'
|
40
|
+
}
|
41
|
+
|
42
|
+
def initialize(http_class = Net::HTTP)
|
43
|
+
@http_class = http_class
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(location, headers={}, &blk)
|
47
|
+
url = URI.parse(location)
|
48
|
+
http = @http_class.new(url.host, url.port)
|
49
|
+
path = url.path
|
50
|
+
if url.query
|
51
|
+
path << '?' << url.query
|
52
|
+
end
|
53
|
+
if defined? DEBUG
|
54
|
+
puts path
|
55
|
+
DEFAULT_HEADERS.merge(headers).each do |k,v|
|
56
|
+
puts " -> #{k}: #{v}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
response = http.request_get(path, DEFAULT_HEADERS.merge(headers), &blk)
|
60
|
+
if defined? DEBUG
|
61
|
+
response.each do |k,v|
|
62
|
+
puts "<- #{k}: #{v}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
response
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module IPlayer
|
4
|
+
class Downloader
|
5
|
+
|
6
|
+
IPHONE_URL = 'http://www.bbc.co.uk/mobile/iplayer/'
|
7
|
+
SELECTOR_URL = 'http://www.bbc.co.uk/mediaselector/3/auth/iplayer_streaming_http_mp4/%s?%s'
|
8
|
+
BUG_URL = 'http://www.bbc.co.uk/iplayer/framework/img/o.gif?%d'
|
9
|
+
MAX_SEGMENT = 4 * 1024 * 1024
|
10
|
+
COPY_BUFFER = 4 * 1024 * 1024
|
11
|
+
|
12
|
+
include IPlayer::Errors
|
13
|
+
|
14
|
+
Version = Struct.new(:name, :pid)
|
15
|
+
|
16
|
+
attr_reader :browser, :pid
|
17
|
+
attr_accessor :cookies
|
18
|
+
|
19
|
+
def self.extract_pid(pid_or_url)
|
20
|
+
case pid_or_url
|
21
|
+
when %r!/(?:item|episode|programmes)/([a-z0-9]{8})!
|
22
|
+
$1
|
23
|
+
when %r!^[a-z0-9]{8}$!
|
24
|
+
pid_or_url
|
25
|
+
when %r!(b0[a-z0-9]{6})!
|
26
|
+
$1
|
27
|
+
else
|
28
|
+
raise NotAPid, pid_or_url
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(browser, pid)
|
33
|
+
@browser = browser
|
34
|
+
@pid = pid
|
35
|
+
end
|
36
|
+
|
37
|
+
def metadata
|
38
|
+
@metadata = Metadata.new(@pid, @browser)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get(url, user_agent, options={}, &blk)
|
42
|
+
options['User-Agent'] = user_agent
|
43
|
+
options['Cookie'] = cookies if cookies
|
44
|
+
browser.get(url, options, &blk)
|
45
|
+
end
|
46
|
+
|
47
|
+
def available_versions
|
48
|
+
metadata.versions.map{ |name, vpid| Version.new(name, vpid) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def download(version_pid, path, options={}, &blk)
|
52
|
+
if options[:subtitles]
|
53
|
+
download_subtitles(version_pid, path)
|
54
|
+
end
|
55
|
+
|
56
|
+
if File.exist?(path)
|
57
|
+
offset = File.size(path)
|
58
|
+
else
|
59
|
+
offset = 0
|
60
|
+
end
|
61
|
+
|
62
|
+
File.open(path, 'a+b') do |io|
|
63
|
+
location = real_stream_location(version_pid)
|
64
|
+
content_length = content_length_from_initial_request(location)
|
65
|
+
yield(offset, content_length) if block_given?
|
66
|
+
|
67
|
+
offset.step(content_length - 1, MAX_SEGMENT) do |first_byte|
|
68
|
+
last_byte = [first_byte + MAX_SEGMENT - 1, content_length - 1].min
|
69
|
+
get(location, Browser::QT_UA, 'Range'=>"bytes=#{first_byte}-#{last_byte}") do |response|
|
70
|
+
response.read_body do |data|
|
71
|
+
offset += data.length
|
72
|
+
io << data
|
73
|
+
yield(offset, content_length) if block_given?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def request_iphone_page
|
83
|
+
response = get(IPHONE_URL, Browser::IPHONE_UA)
|
84
|
+
raise ProgrammeDoesNotExist unless response.is_a?(Net::HTTPSuccess)
|
85
|
+
self.cookies = response.cookies.join('; ')
|
86
|
+
end
|
87
|
+
|
88
|
+
def request_image_bugs
|
89
|
+
get(BUG_URL % [(rand * 100000).floor], Browser::IPHONE_UA)
|
90
|
+
end
|
91
|
+
|
92
|
+
def real_stream_location(version_pid)
|
93
|
+
request_iphone_page
|
94
|
+
request_image_bugs
|
95
|
+
|
96
|
+
# Get the auth URL
|
97
|
+
r = (rand * 10000000).floor
|
98
|
+
selector = SELECTOR_URL % [version_pid, r]
|
99
|
+
response = get(selector, Browser::QT_UA, 'Range'=>'bytes=0-1')
|
100
|
+
|
101
|
+
# It redirects us to the real stream location
|
102
|
+
location = response.to_hash['location']
|
103
|
+
if location =~ /error\.shtml/
|
104
|
+
raise FileUnavailable
|
105
|
+
end
|
106
|
+
return location
|
107
|
+
end
|
108
|
+
|
109
|
+
def content_length_from_initial_request(location)
|
110
|
+
# The first request of CoreMedia is always for the first byte
|
111
|
+
response = get(location, Browser::QT_UA, 'Range'=>'bytes=0-1')
|
112
|
+
|
113
|
+
# We now know the full length of the content
|
114
|
+
content_range = response.to_hash['content-range']
|
115
|
+
unless content_range
|
116
|
+
raise FileUnavailable
|
117
|
+
end
|
118
|
+
return content_range[/\d+$/].to_i
|
119
|
+
end
|
120
|
+
|
121
|
+
def download_subtitles(version_pid, media_path)
|
122
|
+
subtitles = Subtitles.new(version_pid, browser)
|
123
|
+
xml = subtitles.w3c_timed_text
|
124
|
+
return if xml.nil?
|
125
|
+
subtitles_path = media_path.sub(/\.[^\.]+$/, '.xml')
|
126
|
+
return if File.exist?(subtitles_path)
|
127
|
+
File.open(subtitles_path, 'w') do |f|
|
128
|
+
f << xml
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|