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 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("mocha")
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
@@ -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 via subversion:
7
- # svn co http://paulbattley.googlecode.com/svn/iplayer-dl
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
@@ -3,3 +3,4 @@ require 'iplayer/browser'
3
3
  require 'iplayer/metadata'
4
4
  require 'iplayer/subtitles'
5
5
  require 'iplayer/downloader'
6
+ require 'iplayer/constants'
@@ -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) AppleWebKit/420+ '+
26
- '(KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3'
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'
@@ -0,0 +1,4 @@
1
+ module IPlayer
2
+ VIDEO_FILETYPE = "mp4"
3
+ AUDIO_FILETYPE = "mp3"
4
+ end
@@ -1,11 +1,11 @@
1
- require 'tempfile'
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(version_pid)
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 request_image_bugs
89
- get(BUG_URL % [(rand * 100000).floor], Browser::IPHONE_UA)
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(version_pid)
94
+ def real_stream_location(pid)
93
95
  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/
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
- return location
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)
@@ -1,52 +1,25 @@
1
+ require 'iplayer/translations'
2
+
1
3
  module IPlayer
2
4
  module Errors
3
5
  class RecognizedError < RuntimeError
4
- end
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
- "The BBC's geolocation has determined that you are outside the UK.\n"+
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
- class FileUnavailable < RecognizedError
20
- def to_str
21
- "The programme file is not currently available.\n"+
22
- "If it's new, try again later."
23
- end
24
- end
25
-
26
- class MP4Unavailable < RecognizedError
27
- def to_str
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
@@ -56,7 +56,7 @@ class App < Wx::App
56
56
  filetype = metadata.filetype
57
57
  rescue MetadataError
58
58
  title = pid
59
- filetype = 'mov'
59
+ filetype = VIDEO_FILETYPE
60
60
  end
61
61
  "#{ title }.#{ filetype }".gsub(/[^a-z0-9 \-\.]+/i, '')
62
62
  end
@@ -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 'wx'
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 < Wx::Frame
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 = BoxSizer.new(VERTICAL)
48
- sizer_buttons = BoxSizer.new(HORIZONTAL)
49
- sizer_input = BoxSizer.new(HORIZONTAL)
50
- sizer_input.add(@pid_label, 0, ALL|ALIGN_CENTER_VERTICAL, 4)
51
- sizer_input.add(@pid_field, 0, ALL|EXPAND|ALIGN_CENTER_VERTICAL, 4)
52
- sizer_main.add(sizer_input, 0, EXPAND, 0)
53
- sizer_main.add(@download_progress, 0, ALL|EXPAND, 4)
54
- sizer_buttons.add(@about_button, 0, ALL, 4)
55
- sizer_buttons.add(@stop_button, 0, ALL, 4)
56
- sizer_buttons.add(@download_button, 0, ALL, 4)
57
- sizer_main.add(sizer_buttons, 0, ALIGN_RIGHT|ALIGN_CENTER_HORIZONTAL, 0)
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 stop_button_clicked(event)
54
+ def abort_download(event)
65
55
  @app.stop_download!
66
- @status_bar.set_status_text("Stopped", 0)
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 download_button_clicked(event)
61
+ def begin_download(event)
72
62
  pid = @pid_field.get_value
73
63
  if pid.empty?
74
- message_box('You must specify a programme ID before I can download it.')
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, 'Save as', '', filename, 'iPlayer Programmes|*.mov;*.mp3|', FD_SAVE)
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
- @status_bar.set_status_text(File.basename(path), 0)
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
- @status_bar.set_status_text("#{(max.to_f / 2**20).round} MiB", 1)
101
- @status_bar.set_status_text(percentage+"%", 2)
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 => 'Error')
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 => 'Error')
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 about_button_clicked(event)
103
+ def show_about(event)
114
104
  @app.show_about_box
115
105
  end
116
106
 
@@ -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? ? 'mp3' : 'mov'
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
@@ -1,4 +1,4 @@
1
1
  module IPlayer
2
- VERSION = '0.1.16'
3
- GUI_VERSION = '1.12'
2
+ VERSION = '0.1.17'
3
+ GUI_VERSION = '1.13'
4
4
  end
@@ -5,7 +5,7 @@ require 'mocha'
5
5
 
6
6
  class MetadataTest < Test::Unit::TestCase
7
7
 
8
- def test_should_return_mov_for_tv_filetype
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 'mov', metadata.filetype
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.16
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-10-11 00:00:00 +01:00
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/browser.rb
42
- - lib/iplayer/downloader.rb
40
+ - lib/iplayer/translations.rb
41
+ - lib/iplayer/subtitles.rb
43
42
  - lib/iplayer/preferences.rb
44
- - lib/iplayer/gui
45
- - lib/iplayer/gui/app.rb
43
+ - lib/iplayer/metadata.rb
46
44
  - lib/iplayer/gui/main_frame.rb
47
- - lib/iplayer/subtitles.rb
45
+ - lib/iplayer/gui/frame.rb
46
+ - lib/iplayer/gui/app.rb
48
47
  - lib/iplayer/errors.rb
49
- - lib/iplayer/metadata.rb
50
- - lib/iplayer.rb
51
- - test/
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
- - share/
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/icon64.png
66
- has_rdoc: false
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.1
85
+ rubygems_version: 1.3.5
89
86
  signing_key:
90
- specification_version: 2
87
+ specification_version: 3
91
88
  summary: Download iPlayer content
92
89
  test_files: []
93
90