olly-iplayer-dl 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
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,67 @@
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 f =~ /\.svn/
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 'rubygems'
57
+ require 'jeweler'
58
+ Jeweler::Tasks.new do |gemspec|
59
+ gemspec.name = "iplayer-dl"
60
+ gemspec.summary = "Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone."
61
+ gemspec.homepage = "http://po-ru.com/projects/iplayer-downloader/"
62
+ gemspec.description = "Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone."
63
+ gemspec.authors = ["Paul Battley"]
64
+ end
65
+ rescue LoadError
66
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
67
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 15
3
+ :patch: 2
4
+ :major: 0
data/application.ico ADDED
Binary file
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
@@ -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/init.rb ADDED
@@ -0,0 +1,12 @@
1
+ if defined?(RUBYSCRIPT2EXE)
2
+ app_root = File.expand_path(File.dirname(__FILE__))
3
+ $:.unshift(File.join(app_root, 'lib'))
4
+ if RUBYSCRIPT2EXE.respond_to?(:is_compiling?) && RUBYSCRIPT2EXE.is_compiling?
5
+ require 'iplayer'
6
+ require 'iplayer/gui/app'
7
+ require 'iplayer/gui/main_frame'
8
+ exit
9
+ end
10
+ end
11
+
12
+ load File.join(app_root, 'bin', 'iplayer-dl-gui')
@@ -0,0 +1,67 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{iplayer-dl}
5
+ s.version = "0.15.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Paul Battley"]
9
+ s.date = %q{2009-05-18}
10
+ s.description = %q{Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone.}
11
+ s.executables = ["iplayer-dl", "iplayer-dl-gui"]
12
+ s.extra_rdoc_files = [
13
+ "README"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "COPYING",
18
+ "README",
19
+ "Rakefile",
20
+ "VERSION.yml",
21
+ "application.ico",
22
+ "bin/iplayer-dl",
23
+ "bin/iplayer-dl-gui",
24
+ "init.rb",
25
+ "iplayer-dl.gemspec",
26
+ "lib/iplayer.rb",
27
+ "lib/iplayer/browser.rb",
28
+ "lib/iplayer/downloader.rb",
29
+ "lib/iplayer/errors.rb",
30
+ "lib/iplayer/gui/app.rb",
31
+ "lib/iplayer/gui/main_frame.rb",
32
+ "lib/iplayer/metadata.rb",
33
+ "lib/iplayer/preferences.rb",
34
+ "lib/iplayer/subtitles.rb",
35
+ "lib/iplayer/version.rb",
36
+ "setup.rb",
37
+ "share/pixmaps/iplayer-dl/icon128.png",
38
+ "share/pixmaps/iplayer-dl/icon16.png",
39
+ "share/pixmaps/iplayer-dl/icon32.png",
40
+ "share/pixmaps/iplayer-dl/icon48.png",
41
+ "share/pixmaps/iplayer-dl/icon64.png",
42
+ "test/test_metadata.rb",
43
+ "test/test_preferences.rb",
44
+ "test/test_subtitles.rb"
45
+ ]
46
+ s.has_rdoc = true
47
+ s.homepage = %q{http://po-ru.com/projects/iplayer-downloader/}
48
+ s.rdoc_options = ["--charset=UTF-8"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = %q{1.3.2}
51
+ s.summary = %q{Downloads DRM-free video (h.264) and audio (MP3) files from the BBC iPlayer service by pretending to be an iPhone.}
52
+ s.test_files = [
53
+ "test/test_metadata.rb",
54
+ "test/test_preferences.rb",
55
+ "test/test_subtitles.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ else
64
+ end
65
+ else
66
+ end
67
+ end
data/lib/iplayer.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'iplayer/errors'
2
+ require 'iplayer/browser'
3
+ require 'iplayer/metadata'
4
+ require 'iplayer/subtitles'
5
+ require 'iplayer/downloader'
@@ -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