iplayer-dl 0.1.16 → 0.1.17
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/Rakefile +3 -1
- data/bin/iplayer-dl +3 -3
- data/lib/iplayer.rb +1 -0
- data/lib/iplayer/browser.rb +2 -2
- data/lib/iplayer/constants.rb +4 -0
- data/lib/iplayer/downloader.rb +19 -21
- data/lib/iplayer/errors.rb +14 -41
- data/lib/iplayer/gui/app.rb +1 -1
- data/lib/iplayer/gui/frame.rb +78 -0
- data/lib/iplayer/gui/main_frame.rb +33 -43
- data/lib/iplayer/metadata.rb +2 -1
- data/lib/iplayer/translations.rb +60 -0
- data/lib/iplayer/version.rb +2 -2
- data/test/test_metadata.rb +2 -2
- metadata +21 -24
data/Rakefile
CHANGED
@@ -77,7 +77,9 @@ begin
|
|
77
77
|
# s.add_dependency("some_other_gem", "~> 0.1.0")
|
78
78
|
|
79
79
|
# If your tests use any gems, include them here
|
80
|
-
s.add_development_dependency
|
80
|
+
if s.respond_to?(:add_development_dependency)
|
81
|
+
s.add_development_dependency("mocha")
|
82
|
+
end
|
81
83
|
end
|
82
84
|
|
83
85
|
# This task actually builds the gem. We also regenerate a static
|
data/bin/iplayer-dl
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
# Download iPlayer programmes by spoofing an iPhone
|
4
4
|
# Paul Battley - http://po-ru.com/
|
5
5
|
#
|
6
|
-
# Get the latest version
|
7
|
-
#
|
6
|
+
# Get the latest version:
|
7
|
+
# http://github.com/threedaymonk/iplayer-dl
|
8
8
|
|
9
9
|
require 'iplayer'
|
10
10
|
require 'optparse'
|
@@ -134,7 +134,7 @@ pids.each_with_index do |pid, i|
|
|
134
134
|
$stderr.puts "Resuming download at #{position} bytes." if position > 0
|
135
135
|
first_chunk = false
|
136
136
|
end
|
137
|
-
|
137
|
+
|
138
138
|
percentage = "%.1f" % [((1000 * position) / max) / 10.0]
|
139
139
|
if percentage != old_percentage
|
140
140
|
old_percentage = percentage
|
data/lib/iplayer.rb
CHANGED
data/lib/iplayer/browser.rb
CHANGED
@@ -22,8 +22,8 @@ module IPlayer
|
|
22
22
|
class Browser
|
23
23
|
|
24
24
|
# Used by Safari Mobile
|
25
|
-
IPHONE_UA = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en)
|
26
|
-
'(KHTML, like Gecko) Version/
|
25
|
+
IPHONE_UA = 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_1_2 like Mac OS X; en-us)'+
|
26
|
+
'AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7D11 Safari/528.16'
|
27
27
|
|
28
28
|
# Used by Quicktime
|
29
29
|
QT_UA = 'Apple iPhone v1.1.4 CoreMedia v1.0.0.4A102'
|
data/lib/iplayer/downloader.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require '
|
1
|
+
require 'cgi'
|
2
2
|
|
3
3
|
module IPlayer
|
4
4
|
class Downloader
|
5
5
|
|
6
6
|
IPHONE_URL = 'http://www.bbc.co.uk/mobile/iplayer/'
|
7
|
+
EPISODE_URL = 'http://www.bbc.co.uk/mobile/iplayer/episode/%s'
|
7
8
|
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
9
|
MAX_SEGMENT = 4 * 1024 * 1024
|
10
10
|
COPY_BUFFER = 4 * 1024 * 1024
|
11
11
|
|
@@ -43,7 +43,7 @@ class Downloader
|
|
43
43
|
options['Cookie'] = cookies if cookies
|
44
44
|
browser.get(url, options, &blk)
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def available_versions
|
48
48
|
metadata.versions.map{ |name, vpid| Version.new(name, vpid) }
|
49
49
|
end
|
@@ -58,12 +58,12 @@ class Downloader
|
|
58
58
|
else
|
59
59
|
offset = 0
|
60
60
|
end
|
61
|
-
|
62
|
-
File.open(path, 'a+b') do |io|
|
63
|
-
location = real_stream_location(
|
61
|
+
|
62
|
+
File.open(path, 'a+b') do |io|
|
63
|
+
location = real_stream_location(@pid)
|
64
64
|
content_length = content_length_from_initial_request(location)
|
65
65
|
yield(offset, content_length) if block_given?
|
66
|
-
|
66
|
+
|
67
67
|
offset.step(content_length - 1, MAX_SEGMENT) do |first_byte|
|
68
68
|
last_byte = [first_byte + MAX_SEGMENT - 1, content_length - 1].min
|
69
69
|
get(location, Browser::QT_UA, 'Range'=>"bytes=#{first_byte}-#{last_byte}") do |response|
|
@@ -85,25 +85,23 @@ private
|
|
85
85
|
self.cookies = response.cookies.join('; ')
|
86
86
|
end
|
87
87
|
|
88
|
-
def
|
89
|
-
get(
|
88
|
+
def request_episode_page(pid)
|
89
|
+
response = get(EPISODE_URL % pid, Browser::IPHONE_UA)
|
90
|
+
raise ProgrammeDoesNotExist unless response.is_a?(Net::HTTPSuccess)
|
91
|
+
response.body
|
90
92
|
end
|
91
93
|
|
92
|
-
def real_stream_location(
|
94
|
+
def real_stream_location(pid)
|
93
95
|
request_iphone_page
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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/
|
96
|
+
html = request_episode_page(pid)
|
97
|
+
location = html[%r{http://download\.iplayer\.bbc\.co\.uk/iplayer_streaming[^"']+}]
|
98
|
+
|
99
|
+
unless location
|
104
100
|
raise FileUnavailable
|
105
101
|
end
|
106
|
-
|
102
|
+
|
103
|
+
# The Beeb appear not to believe in escaping URLs, but let's unescape them in case they have an outbreak of standards compliance:
|
104
|
+
CGI.unescapeHTML(location)
|
107
105
|
end
|
108
106
|
|
109
107
|
def content_length_from_initial_request(location)
|
data/lib/iplayer/errors.rb
CHANGED
@@ -1,52 +1,25 @@
|
|
1
|
+
require 'iplayer/translations'
|
2
|
+
|
1
3
|
module IPlayer
|
2
4
|
module Errors
|
3
5
|
class RecognizedError < RuntimeError
|
4
|
-
|
5
|
-
|
6
|
-
class ParsingError < RecognizedError
|
7
|
-
def to_str
|
8
|
-
"Unable to parse the programme page. Perhaps the iPlayer has changed."
|
9
|
-
end
|
10
|
-
end
|
6
|
+
include Translations
|
7
|
+
translation_namespace :errors
|
11
8
|
|
12
|
-
class OutsideUK < RecognizedError
|
13
9
|
def to_str
|
14
|
-
"
|
15
|
-
"You can try using a UK proxy."
|
10
|
+
t(self.class.name.split("::").last, :message => message)
|
16
11
|
end
|
17
12
|
end
|
18
13
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"This programme is not currently available in an MP3 or MPEG4 version."
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class MetadataError < RecognizedError
|
33
|
-
def to_str
|
34
|
-
"Unable to parse the metadata for this programme.\n"+
|
35
|
-
"As a workaround, you can use the -f option to specify a filename manually."
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class ProgrammeDoesNotExist < RecognizedError
|
40
|
-
def to_str
|
41
|
-
"There is no page for this programme.\n"+
|
42
|
-
"This probably means that the programme does not exist."
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class NotAPid < RecognizedError
|
47
|
-
def to_str
|
48
|
-
"This does not look like a programme ID or a recognised programme URL: "+ message
|
49
|
-
end
|
14
|
+
[ :ParsingError,
|
15
|
+
:OutsideUK,
|
16
|
+
:FileUnavailable,
|
17
|
+
:MP4Unavailable,
|
18
|
+
:MetadataError,
|
19
|
+
:ProgrammeDoesNotExist,
|
20
|
+
:NotAPid
|
21
|
+
].each do |klass|
|
22
|
+
const_set(klass, Class.new(RecognizedError))
|
50
23
|
end
|
51
24
|
|
52
25
|
end
|
data/lib/iplayer/gui/app.rb
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'wx'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module IPlayer
|
5
|
+
module GUI
|
6
|
+
class Frame < Wx::Frame
|
7
|
+
include Wx
|
8
|
+
extend Forwardable
|
9
|
+
AUTO = -1
|
10
|
+
|
11
|
+
class SizerProxy
|
12
|
+
def initialize(frame, sizer)
|
13
|
+
@frame, @sizer = frame, sizer
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method, *args, &blk)
|
17
|
+
default_border =
|
18
|
+
case method
|
19
|
+
when :h_sizer, :v_sizer
|
20
|
+
0
|
21
|
+
else
|
22
|
+
4
|
23
|
+
end
|
24
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
25
|
+
proportion = options.delete(:proportion) || 0
|
26
|
+
flags = options.delete(:flags) || Wx::ALL
|
27
|
+
border = options.delete(:border) || default_border
|
28
|
+
args << options unless options.empty?
|
29
|
+
control = @frame.__send__(method, *args, &blk)
|
30
|
+
@sizer.add(control, proportion, flags, border)
|
31
|
+
control
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def_delegators :@status_bar, :set_status_text
|
36
|
+
|
37
|
+
def sizer(alignment, &blk)
|
38
|
+
s = BoxSizer.new(alignment)
|
39
|
+
proxy = SizerProxy.new(self, s)
|
40
|
+
blk.call(proxy) if block_given?
|
41
|
+
s
|
42
|
+
end
|
43
|
+
def v_sizer(&blk) sizer(VERTICAL, &blk); end
|
44
|
+
def h_sizer(&blk) sizer(HORIZONTAL, &blk); end
|
45
|
+
|
46
|
+
def label(text)
|
47
|
+
StaticText.new(self, -1, text)
|
48
|
+
end
|
49
|
+
|
50
|
+
def field(default_text="", options={})
|
51
|
+
width = options[:width] || -1
|
52
|
+
height = options[:height] || -1
|
53
|
+
tool_tip = options[:tool_tip]
|
54
|
+
enter = options[:enter]
|
55
|
+
f = TextCtrl.new(self, -1, default_text, DEFAULT_POSITION, Size.new(width, height))
|
56
|
+
f.set_tool_tip(tool_tip) if tool_tip
|
57
|
+
f
|
58
|
+
end
|
59
|
+
|
60
|
+
def gauge
|
61
|
+
Gauge.new(self, -1, 1, DEFAULT_POSITION, DEFAULT_SIZE, GA_HORIZONTAL|GA_SMOOTH)
|
62
|
+
end
|
63
|
+
|
64
|
+
def button(handler, text)
|
65
|
+
b = Button.new(self, -1, text)
|
66
|
+
evt_button(b.get_id){ |e| __send__(handler, e) }
|
67
|
+
b
|
68
|
+
end
|
69
|
+
|
70
|
+
def status_bar(widths)
|
71
|
+
@status_bar = StatusBar.new(self, -1, 0)
|
72
|
+
@status_bar.set_fields_count(widths.length)
|
73
|
+
@status_bar.set_status_widths(widths)
|
74
|
+
set_status_bar(@status_bar)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,34 +1,18 @@
|
|
1
|
-
require '
|
1
|
+
require 'iplayer/gui/frame'
|
2
2
|
require 'iplayer/errors'
|
3
|
+
require 'iplayer/translations'
|
3
4
|
|
4
5
|
module IPlayer
|
5
6
|
module GUI
|
6
|
-
class MainFrame <
|
7
|
-
include Wx
|
7
|
+
class MainFrame < Frame
|
8
8
|
include IPlayer::Errors
|
9
|
+
include Translations
|
10
|
+
translation_namespace :gui
|
9
11
|
|
10
12
|
def initialize(app)
|
11
13
|
@app = app
|
12
|
-
|
13
14
|
super(nil, -1, @app.name, DEFAULT_POSITION, DEFAULT_SIZE, CAPTION|MINIMIZE_BOX|CLOSE_BOX|SYSTEM_MENU)
|
14
15
|
|
15
|
-
@pid_label = StaticText.new(self, -1, "Programme ID")
|
16
|
-
@pid_field = TextCtrl.new(self, -1, "", DEFAULT_POSITION, Size.new(300,-1))
|
17
|
-
@pid_field.set_tool_tip("Use either the short alphanumeric programme identifier or the URL of the viewing page on the iPlayer website.")
|
18
|
-
@download_progress = Gauge.new(self, -1, 1, DEFAULT_POSITION, DEFAULT_SIZE, GA_HORIZONTAL|GA_SMOOTH)
|
19
|
-
@stop_button = Button.new(self, -1, "Stop")
|
20
|
-
evt_button(@stop_button.get_id){ |e| stop_button_clicked(e) }
|
21
|
-
@stop_button.disable
|
22
|
-
@download_button = Button.new(self, -1, "Download...")
|
23
|
-
evt_button(@download_button.get_id){ |e| download_button_clicked(e) }
|
24
|
-
@about_button = Button.new(self, -1, "About...")
|
25
|
-
evt_button(@about_button.get_id){ |e| about_button_clicked(e) }
|
26
|
-
@status_bar = StatusBar.new(self, -1, 0)
|
27
|
-
@status_bar.set_fields_count(3)
|
28
|
-
@status_bar.set_status_widths([-1, 60, 60])
|
29
|
-
set_status_bar(@status_bar)
|
30
|
-
@status_bar.set_status_text("Waiting", 0)
|
31
|
-
|
32
16
|
set_properties
|
33
17
|
do_layout
|
34
18
|
end
|
@@ -44,34 +28,40 @@ class MainFrame < Wx::Frame
|
|
44
28
|
end
|
45
29
|
|
46
30
|
def do_layout
|
47
|
-
sizer_main =
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
31
|
+
sizer_main = v_sizer{ |main|
|
32
|
+
main.h_sizer(:flags => EXPAND){ |input|
|
33
|
+
input.label(t(:pid), :flags => ALL|ALIGN_CENTER_VERTICAL)
|
34
|
+
@pid_field = input.field("",
|
35
|
+
:width => 300,
|
36
|
+
:tool_tip => t(:pid_tool_tip),
|
37
|
+
:flags => ALL|EXPAND|ALIGN_CENTER_VERTICAL)
|
38
|
+
}
|
39
|
+
@download_progress = main.gauge(:flags => ALL|EXPAND)
|
40
|
+
main.h_sizer(:flags => ALIGN_RIGHT|ALIGN_CENTER_HORIZONTAL){ |buttons|
|
41
|
+
buttons.button(:show_about, t(:about_button))
|
42
|
+
@stop_button = buttons.button(:abort_download, t(:stop_button))
|
43
|
+
@download_button = buttons.button(:begin_download, t(:download_button))
|
44
|
+
}
|
45
|
+
}
|
46
|
+
status_bar([AUTO, 60, 60])
|
58
47
|
self.set_sizer(sizer_main)
|
59
48
|
sizer_main.fit(self)
|
60
49
|
layout
|
61
50
|
centre
|
51
|
+
set_status_text(t(:status_waiting), 0)
|
62
52
|
end
|
63
53
|
|
64
|
-
def
|
54
|
+
def abort_download(event)
|
65
55
|
@app.stop_download!
|
66
|
-
|
56
|
+
set_status_text(t(:status_stopped), 0)
|
67
57
|
@download_button.enable
|
68
58
|
@stop_button.disable
|
69
59
|
end
|
70
60
|
|
71
|
-
def
|
61
|
+
def begin_download(event)
|
72
62
|
pid = @pid_field.get_value
|
73
63
|
if pid.empty?
|
74
|
-
message_box(
|
64
|
+
message_box(t(:no_pid_given))
|
75
65
|
return
|
76
66
|
else
|
77
67
|
begin
|
@@ -85,11 +75,11 @@ class MainFrame < Wx::Frame
|
|
85
75
|
@download_button.disable
|
86
76
|
filename = @app.get_default_filename(pid)
|
87
77
|
|
88
|
-
fd = FileDialog.new(nil,
|
78
|
+
fd = FileDialog.new(nil, t(:save_dialog_title), '', filename, "#{t(:file_types)}|*.#{VIDEO_FILETYPE};*.#{AUDIO_FILETYPE}|", FD_SAVE)
|
89
79
|
|
90
80
|
if fd.show_modal == ID_OK
|
91
81
|
path = fd.get_path
|
92
|
-
|
82
|
+
set_status_text(File.basename(path), 0)
|
93
83
|
@download_button.disable
|
94
84
|
@stop_button.enable
|
95
85
|
begin
|
@@ -97,20 +87,20 @@ class MainFrame < Wx::Frame
|
|
97
87
|
@download_progress.set_range(max)
|
98
88
|
@download_progress.set_value(position)
|
99
89
|
percentage = "%.1f" % [((1000.0 * position) / max).round / 10.0]
|
100
|
-
|
101
|
-
|
90
|
+
set_status_text("#{(max.to_f / 2**20).round} MiB", 1)
|
91
|
+
set_status_text(percentage+"%", 2)
|
102
92
|
end
|
103
93
|
rescue RecognizedError => error
|
104
|
-
message_box(error.to_str, :title =>
|
94
|
+
message_box(error.to_str, :title => t(:error_title))
|
105
95
|
rescue Exception => error
|
106
|
-
message_box("#{error.message} (#{error.class})\n#{error.backtrace.first}", :title =>
|
96
|
+
message_box("#{error.message} (#{error.class})\n#{error.backtrace.first}", :title => t(:error_title))
|
107
97
|
end
|
108
98
|
@stop_button.disable
|
109
99
|
end
|
110
100
|
@download_button.enable
|
111
101
|
end
|
112
102
|
|
113
|
-
def
|
103
|
+
def show_about(event)
|
114
104
|
@app.show_about_box
|
115
105
|
end
|
116
106
|
|
data/lib/iplayer/metadata.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rexml/document'
|
2
2
|
require 'iplayer/errors'
|
3
|
+
require 'iplayer/constants'
|
3
4
|
|
4
5
|
module IPlayer
|
5
6
|
class Metadata
|
@@ -21,7 +22,7 @@ class Metadata
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def filetype
|
24
|
-
radio? ?
|
25
|
+
radio? ? AUDIO_FILETYPE : VIDEO_FILETYPE
|
25
26
|
end
|
26
27
|
|
27
28
|
def versions
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Translations
|
2
|
+
STRINGS = {
|
3
|
+
:gui => {
|
4
|
+
:pid => "Programme ID",
|
5
|
+
:stop_button => "Stop",
|
6
|
+
:download_button => "Download ...",
|
7
|
+
:about_button => "About ...",
|
8
|
+
:status_waiting => "Waiting",
|
9
|
+
:pid_tool_tip => "Use either the short alphanumeric programme identifier or "+
|
10
|
+
"the URL of the viewing page on the iPlayer website.",
|
11
|
+
:status_waiting => "Waiting",
|
12
|
+
:no_pid_given => "You must specify a programme ID before I can download it.",
|
13
|
+
:save_dialog_title => "Save As",
|
14
|
+
:file_types => "iPlayer programmes",
|
15
|
+
:error_title => "Error",
|
16
|
+
},
|
17
|
+
:errors => {
|
18
|
+
:OutsideUK =>
|
19
|
+
"The BBC's geolocation has determined that you are outside the UK.\n"+
|
20
|
+
"You can try using a UK proxy.",
|
21
|
+
:FileUnavailable =>
|
22
|
+
"The programme file is not currently available.\n"+
|
23
|
+
"If it's new, try again later.",
|
24
|
+
:MP4Unavailable =>
|
25
|
+
"This programme is not currently available in an MP3 or MPEG4 version.",
|
26
|
+
:MetadataError =>
|
27
|
+
"Unable to parse the metadata for this programme.\n"+
|
28
|
+
"As a workaround, you can use the -f option to specify a filename manually.",
|
29
|
+
:ProgrammeDoesNotExist =>
|
30
|
+
"There is no page for this programme.\n"+
|
31
|
+
"This probably means that the programme does not exist.",
|
32
|
+
:NotAPid =>
|
33
|
+
"This does not look like a programme ID or a recognised programme URL: {{message}}",
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
def translation_namespace(ns)
|
39
|
+
@translation_namespace = ns
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_translation_namespace
|
43
|
+
if ancestors[1].respond_to?(:get_translation_namespace)
|
44
|
+
ancestors[1].get_translation_namespace
|
45
|
+
else
|
46
|
+
[]
|
47
|
+
end + [@translation_namespace]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def t(key, methods={})
|
52
|
+
[self.class.get_translation_namespace, key.to_s].flatten.compact.join(".").split(".").inject(STRINGS){ |hash, subkey|
|
53
|
+
hash[subkey.to_sym]
|
54
|
+
}.gsub(/\{\{([^\}]+)\}\}/){ methods[$1.to_sym] }
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.included(klass)
|
58
|
+
klass.extend ClassMethods
|
59
|
+
end
|
60
|
+
end
|
data/lib/iplayer/version.rb
CHANGED
data/test/test_metadata.rb
CHANGED
@@ -5,7 +5,7 @@ require 'mocha'
|
|
5
5
|
|
6
6
|
class MetadataTest < Test::Unit::TestCase
|
7
7
|
|
8
|
-
def
|
8
|
+
def test_should_return_mp4_for_tv_filetype
|
9
9
|
xml = %{
|
10
10
|
<?xml version="1.0" encoding="UTF-8"?>
|
11
11
|
<playlist xmlns="http://bbc.co.uk/2008/emp/playlist" revision="1">
|
@@ -16,7 +16,7 @@ class MetadataTest < Test::Unit::TestCase
|
|
16
16
|
pid = 'abc'
|
17
17
|
browser = stub(:get => stub(:body => xml))
|
18
18
|
metadata = IPlayer::Metadata.new(pid, browser)
|
19
|
-
assert_equal '
|
19
|
+
assert_equal 'mp4', metadata.filetype
|
20
20
|
end
|
21
21
|
|
22
22
|
def test_should_return_mp3_for_radio_filetype
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iplayer-dl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Battley
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-16 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -35,36 +35,33 @@ files:
|
|
35
35
|
- README
|
36
36
|
- setup.rb
|
37
37
|
- Rakefile
|
38
|
-
- lib/
|
39
|
-
- lib/iplayer
|
38
|
+
- lib/iplayer.rb
|
40
39
|
- lib/iplayer/version.rb
|
41
|
-
- lib/iplayer/
|
42
|
-
- lib/iplayer/
|
40
|
+
- lib/iplayer/translations.rb
|
41
|
+
- lib/iplayer/subtitles.rb
|
43
42
|
- lib/iplayer/preferences.rb
|
44
|
-
- lib/iplayer/
|
45
|
-
- lib/iplayer/gui/app.rb
|
43
|
+
- lib/iplayer/metadata.rb
|
46
44
|
- lib/iplayer/gui/main_frame.rb
|
47
|
-
- lib/iplayer/
|
45
|
+
- lib/iplayer/gui/frame.rb
|
46
|
+
- lib/iplayer/gui/app.rb
|
48
47
|
- lib/iplayer/errors.rb
|
49
|
-
- lib/iplayer/
|
50
|
-
- lib/iplayer.rb
|
51
|
-
-
|
48
|
+
- lib/iplayer/downloader.rb
|
49
|
+
- lib/iplayer/constants.rb
|
50
|
+
- lib/iplayer/browser.rb
|
51
|
+
- test/test_subtitles.rb
|
52
52
|
- test/test_preferences.rb
|
53
53
|
- test/test_metadata.rb
|
54
|
-
- test/test_subtitles.rb
|
55
|
-
- bin/
|
56
|
-
- bin/iplayer-dl
|
57
54
|
- bin/iplayer-dl-gui
|
58
|
-
-
|
59
|
-
- share/pixmaps
|
60
|
-
- share/pixmaps/iplayer-dl
|
61
|
-
- share/pixmaps/iplayer-dl/icon128.png
|
55
|
+
- bin/iplayer-dl
|
56
|
+
- share/pixmaps/iplayer-dl/icon64.png
|
62
57
|
- share/pixmaps/iplayer-dl/icon48.png
|
63
|
-
- share/pixmaps/iplayer-dl/icon16.png
|
64
58
|
- share/pixmaps/iplayer-dl/icon32.png
|
65
|
-
- share/pixmaps/iplayer-dl/
|
66
|
-
|
59
|
+
- share/pixmaps/iplayer-dl/icon16.png
|
60
|
+
- share/pixmaps/iplayer-dl/icon128.png
|
61
|
+
has_rdoc: true
|
67
62
|
homepage: http://po-ru.com/projects/iplayer-downloader
|
63
|
+
licenses: []
|
64
|
+
|
68
65
|
post_install_message:
|
69
66
|
rdoc_options: []
|
70
67
|
|
@@ -85,9 +82,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
82
|
requirements: []
|
86
83
|
|
87
84
|
rubyforge_project:
|
88
|
-
rubygems_version: 1.3.
|
85
|
+
rubygems_version: 1.3.5
|
89
86
|
signing_key:
|
90
|
-
specification_version:
|
87
|
+
specification_version: 3
|
91
88
|
summary: Download iPlayer content
|
92
89
|
test_files: []
|
93
90
|
|