olly-iplayer-dl 0.15.2

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/.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