cloudruby 1.1.0 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d65284ff7faa2d2be8f6b1ef10fb0ed825557fb
4
- data.tar.gz: 500dbcb28787f33896a1493abcff9bc96b49c1a1
3
+ metadata.gz: f8d5679ca3d885cee4beedaa63a78115a1c1d6b0
4
+ data.tar.gz: f49c26dcd1a7626dfbbfdef0786425013a40f401
5
5
  SHA512:
6
- metadata.gz: 8f9753df56f183867581be5cd7b601c525a23476684e040ffa593f6f57163d50bf1a6e31acf333712503fa22f4c0c3f86ebb883f182742f9531ee90ae8b9cce8
7
- data.tar.gz: 973cde30b03d01ef0922fcf41fad6f76330971522eaf39f512c2d0dac5ab98cf1fc2161944057b147a47ea6c721430ca2e89efae37ce92f11d740fdf76b5ec8d
6
+ metadata.gz: eb15c734a3a25d8285bcbc1fa968c19d31ad942603170b3f78e415bdf0bc8c6172861cb358b41deb877a9a44eb0ff1dce5f4d62de10f3bf81d1eee35b8c7c7be
7
+ data.tar.gz: a8e322c14749ed65694e1c2adbfb1367337d51afdf3a863aeae5c31182481503e85bb7068b387ff28c582b25a536f5d9c8e954e4721dae96d40902e786658853
@@ -1,8 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'json/pure'
4
- require 'cloudruby'
5
- # require_relative '../lib/cloudruby'
4
+ begin
5
+ require_relative '../lib/cloudruby' # from source dir
6
+ rescue LoadError
7
+ begin
8
+ require_relative '../lib/cloudruby/lib/cloudruby' # from /usr/bin/
9
+ rescue LoadError
10
+ begin
11
+ require 'cloudruby' # binary installed as gem
12
+ rescue LoadError
13
+ puts "Cannot load cloudruby library. Exiting."
14
+ exit false
15
+ end
16
+ end
17
+ end
18
+
6
19
 
7
20
  @config = {}
8
21
  @query = []
@@ -1,7 +1,11 @@
1
- require 'pp'
2
1
  require 'observer'
3
2
  require 'logger'
4
- require 'json/pure'
3
+ begin
4
+ require 'json/pure'
5
+ rescue LoadError
6
+ require 'json'
7
+ end
8
+
5
9
  require_relative 'soundcloud.rb'
6
10
  require_relative 'mpg123player.rb'
7
11
  require_relative 'gstplayer.rb'
@@ -1,4 +1,7 @@
1
- require 'gst'
1
+ begin
2
+ require 'gst'
3
+ rescue LoadError
4
+ end
2
5
 
3
6
  class GstPlayer
4
7
  include Observable
@@ -15,7 +18,12 @@ class GstPlayer
15
18
  end
16
19
 
17
20
  def initialize params = {}
21
+ @curvature = 4
18
22
  params.each { |key, value| send "#{key}=", value }
23
+ unless defined? Gst
24
+ puts "Gstream backend requires gstream gem"
25
+ exit false
26
+ end
19
27
 
20
28
  @logger.info version
21
29
  @inqueue = []
@@ -34,6 +42,10 @@ class GstPlayer
34
42
  @pipeline.set_property "audio-sink", sink unless sink.nil?
35
43
  when :"buffer-duration", :"buffer-size", :"mute", :"volume"
36
44
  @pipeline.set_property key, value
45
+ when :volume
46
+ @pipeline.set_property key, logscale(value)
47
+ when :"volume-curvature"
48
+ @curvature = value.to_f
37
49
  end
38
50
  end
39
51
  end
@@ -86,14 +98,23 @@ class GstPlayer
86
98
  notify_observers :state => :error, :error => err
87
99
  end
88
100
 
101
+ #approximation to a logarithmic scale
102
+ def logscale val, inverse = false
103
+ val = val.to_f
104
+ if inverse
105
+ 100 * (val ** (1.0/@curvature))
106
+ else
107
+ (val/100)**@curvature
108
+ end
109
+ end
110
+
89
111
  def volume= (val)
90
- @volume = volume
91
- @volume += val
92
- @volume = [@volume, 100].min
93
- @volume = [@volume, 0].max
94
- @pipeline.volume = @volume/100.0
112
+ vol = volume
113
+ vol += val
114
+ vol = [0, [vol, 100].min].max
115
+ @pipeline.volume = logscale(vol)
95
116
  changed
96
- notify_observers :state => :status, :type => "Volume", :value => "#{@volume}%"
117
+ notify_observers :state => :status, :type => "Volume", :value => "#{vol.to_i}%"
97
118
  rescue => err
98
119
  @error = err
99
120
  changed
@@ -101,13 +122,14 @@ class GstPlayer
101
122
  end
102
123
 
103
124
  def volume
104
- (@pipeline.get_property("volume") * 100).to_int
125
+ logval = @pipeline.get_property("volume").to_f
126
+ logscale logval, true
105
127
  end
106
128
 
107
129
  def mute
108
130
  @pipeline.set_property "mute", !@pipeline.get_property("mute")
109
131
  changed
110
- notify_observers :state => :status, :type => "Volume", :value => "#{muted? ? 0 : @volume}%"
132
+ notify_observers :state => :status, :type => "Volume", :value => "#{muted? ? 0 : volume}%"
111
133
  rescue => err
112
134
  @error = err
113
135
  changed
@@ -178,7 +200,7 @@ class GstPlayer
178
200
  when Gst::MessageType::BUFFERING
179
201
  percent = message.parse_buffering
180
202
  changed
181
- notify_observers :state => :status, :type => "Buffering", :value => "#{percent}%"
203
+ notify_observers :state => :buffer, :value => percent
182
204
  when Gst::MessageType::STATE_CHANGED
183
205
  error, state = message.parse_state_changed
184
206
  case state
@@ -81,6 +81,8 @@ class MPG123Player
81
81
  @volume = [@volume, 100].min
82
82
  @volume = [@volume, 0].max
83
83
  mpg123puts "V #{@volume}"
84
+ changed
85
+ notify_observers :state => :status, :type => "Volume", :value => "#{@volume.to_i}%"
84
86
  rescue => err
85
87
  @error = err
86
88
  changed
@@ -27,10 +27,12 @@ class NCursesUI
27
27
  :playlist_active => [:white, :blue],
28
28
  :progress => [:cyan, :blue],
29
29
  :progress_bar => [:blue, :cyan],
30
+ :buffer_bar => [:blue, :magenta],
30
31
  :title => [:cyan, :black],
31
32
  :artist => [:cyan, :black],
32
33
  :status => [:magenta, :black]
33
- }
34
+ },
35
+ :palette => {}
34
36
  }.freeze
35
37
 
36
38
  @options = defaults.deep_merge(options || {})
@@ -48,14 +50,14 @@ class NCursesUI
48
50
  begin
49
51
  stdscr = Curses.init_screen
50
52
  Curses.start_color
51
- Colors.init @options[:colors]
53
+ Colors.init @options[:colors], @options[:palette]
52
54
  stdscr.keypad true
53
55
  Curses.nonl
54
56
  Curses.cbreak
55
57
  Curses.noecho
56
58
  Curses.curs_set 0
57
59
  Curses.timeout = 5
58
- @p = NProgress.new stdscr, 0, 0, :progress, :progress_bar
60
+ @p = NProgress.new stdscr, 0, 0, :progress, :progress_bar, :buffer_bar
59
61
  @l = NPlaylist.new stdscr, 4, 0, :playlist, :playlist_active, 0, 0, @playlist
60
62
  @i = NInfobox.new self, stdscr, 4, 0, :playlist, 0, 9
61
63
  @d = NDownloadBox.new stdscr, Curses.lines-1, 0, :default, 0, 1
@@ -95,15 +97,14 @@ class NCursesUI
95
97
  @cloud.pause
96
98
  end
97
99
 
98
- statusLine = @status || @error
100
+ statusLine = @status || @error || ""
99
101
 
100
- if statusLine
101
- Nutils.print stdscr, 3, 0, "#{statusLine}", :status
102
- Curses.refresh
103
- end
102
+ Nutils.print stdscr, 3, 0, "#{statusLine}", :status
103
+ Curses.refresh
104
104
  tr = " %s " % [Nutils.timestr(@timetotal)]
105
105
  t = " %-#{Curses.cols-tr.size-1}s%s" % [Nutils.timestr(@time), tr]
106
106
  @p.value = @frac
107
+ @p.subvalue = (@bufferPercentage || 0)/100.0
107
108
  @p.text = t
108
109
  @p.refresh
109
110
  Nutils.print stdscr, 1, 0, "#{@op} #{@title}", :title
@@ -117,7 +118,7 @@ class NCursesUI
117
118
  ensure
118
119
  @l.close if @l
119
120
  @p.close if @p
120
- stdscr.close
121
+ stdscr.close if stdscr
121
122
  Curses.echo
122
123
  Curses.nocbreak
123
124
  Curses.nl
@@ -130,6 +131,8 @@ class NCursesUI
130
131
 
131
132
  def cloud_update(arg)
132
133
  case arg[:state]
134
+ when :error
135
+ @error = "Error: #{arg[:error]}"
133
136
  when :load
134
137
  @playlist |= arg[:tracks]
135
138
  @l.list = @playlist if @l
@@ -156,6 +159,10 @@ class NCursesUI
156
159
  @d.title = ""
157
160
  end
158
161
  end
162
+ when :status
163
+ if arg[:type]
164
+ status("#{arg[:type]}: #{arg[:value]}")
165
+ end
159
166
  end
160
167
  end
161
168
 
@@ -186,17 +193,11 @@ class NCursesUI
186
193
  @op = "\u25FC"
187
194
  when :error
188
195
  @error = "Error: #{arg[:error]}"
196
+ when :buffer
197
+ @bufferPercentage = arg[:value].to_i || 0
189
198
  when :status
190
199
  if arg[:type]
191
- @status = "#{arg[:type]}: #{arg[:value]}"
192
- if @statusTimeout
193
- @statusTimeout.exit
194
- end
195
- @statusTimeout = Thread.new do
196
- sleep 5
197
- @status = nil
198
- @statusTimeout = nil
199
- end
200
+ status("#{arg[:type]}: #{arg[:value]}")
200
201
  end
201
202
  end
202
203
  end
@@ -204,16 +205,28 @@ class NCursesUI
204
205
  def close
205
206
  @state = :close
206
207
  end
208
+
209
+ def status msg
210
+ @status = msg
211
+ if @statusTimeout
212
+ @statusTimeout.exit
213
+ end
214
+ @statusTimeout = Thread.new do
215
+ sleep 5
216
+ @status = nil
217
+ @statusTimeout = nil
218
+ end
219
+ end
207
220
  end
208
221
 
209
222
  class Nutils
210
223
  def self.print(scr, row, col, text, color, width = (Curses.cols))
211
224
  width = [Curses.cols, col+width].min - col
212
225
  t = "%-#{width}s" % [scroll(text, width)]
213
- scr.attron(Colors.map(color)) if color
226
+ scr.attron(Colors.pairMap(color)) if color
214
227
  scr.setpos row, col
215
228
  scr.addstr t
216
- scr.attroff(Colors.map(color)) if color
229
+ scr.attroff(Colors.pairMap(color)) if color
217
230
  end
218
231
 
219
232
  def self.scroll(text, width, offset=0)
@@ -234,47 +247,110 @@ class Nutils
234
247
  end
235
248
 
236
249
  class Colors
237
- $map = {}
238
- $counter = 0
239
- def self.init colormap = {}
240
- colormap.each do |key, colors|
241
- self.add(key, colors[0], colors[1])
250
+ $pairMap = {}
251
+ $colorMap = {}
252
+ $attrMap = {}
253
+ $pairCounter = 0
254
+ $colorCounter = 0
255
+ def self.init colormap = {}, palette = {}
256
+ Curses.use_default_colors
257
+ $pairCounter += 1
258
+
259
+ Curses::constants.grep(/COLOR_/).each do |c|
260
+ color = c.to_s.split("_")[1]
261
+ $colorMap[color.downcase.to_sym] = Curses.const_get "#{c}"
262
+ $colorCounter += 1;
242
263
  end
243
- end
244
264
 
245
- def self.map(key)
246
- $map[key] || $map[:default]
265
+ if Curses.can_change_color?
266
+ palette.each do |name, definition|
267
+ if $colorCounter >= Curses.colors || name.length == 0
268
+ $colorMap[name] = $colorMap[:white]
269
+ elsif $colorMap[name]
270
+ if definition.is_a? Integer
271
+ $colorMap[name] = definition
272
+ elsif definition.is_a?(Array) and definition.length == 3
273
+ Curses.init_color $colorMap[name], definition[0], definition[1], definition[2]
274
+ end
275
+ else
276
+ if definition.is_a? Integer
277
+ $colorMap[name] = definition
278
+ elsif definition.is_a?(Array) and definition.length == 3
279
+ $colorMap[name] = $colorCounter;
280
+ Curses.init_color $colorCounter, definition[0], definition[1], definition[2]
281
+ $colorCounter += 1;
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ colormap.each do |key, colors|
288
+ self.add(key, colors[0], colors[1], colors[2..-1])
289
+ end
247
290
  end
248
291
 
249
- def self.add(key, fg, bg)
250
- Curses.init_pair $counter, ncg(fg), ncg(bg)
251
- $map[key] = Curses.color_pair $counter
252
- $counter += 1
292
+ def self.pairMap(key)
293
+ ($pairMap[key] || $pairMap[:default] ) | ($attrMap[key] || 0)
294
+ end
295
+
296
+ def self.add(key, fg, bg, attrs=[])
297
+ Curses.init_pair $pairCounter, ncg(fg), ncg(bg)
298
+ $pairMap[key] = Curses.color_pair $pairCounter
299
+ $attrMap[key] = attrs.map do |a|
300
+ case a.to_sym
301
+ when :bold
302
+ Curses::A_BOLD
303
+ when :underline
304
+ Curses::A_UNDERLINE
305
+ when :blink
306
+ Curses::A_BLINK
307
+ when :dim
308
+ Curses::A_DIM
309
+ when :normal
310
+ Curses::A_NORMAL
311
+ when :standout
312
+ Curses::A_STANDOUT
313
+ when :reverse
314
+ Curses::A_REVERSE
315
+ end
316
+ end.compact.reduce :|
317
+ $pairCounter += 1
253
318
  end
254
319
 
255
320
  def self.debug
256
321
  puts "colors supported: #{Curses.colors}"
257
- puts "map: #{$map}"
322
+ puts "pairMap: #{$pairMap}"
323
+ puts "attributes: #{$attrMap}"
324
+ puts "colorPalette: #{$colorMap}"
258
325
  end
326
+
259
327
  # get ncurses color constant
260
328
  def self.ncg(color)
261
- color = :black unless color
262
- Curses.const_get "COLOR_#{color.upcase}"
329
+ if color.is_a? Integer
330
+ color
331
+ else
332
+ color = -1 unless color
333
+ color = color.to_sym
334
+ $colorMap[color] || -1
335
+ end
263
336
  end
264
337
  end
265
338
 
266
339
  class NProgress
267
- attr_reader :value
340
+ attr_reader :value, :subvalue
268
341
  attr_accessor :text
269
- def initialize scr, row, col, color, bar_color, width=0, value = 0, text = ""
342
+ def initialize scr, row, col, color, bar_color, subbar_color, width=0, value = 0, subvalue = 0, text = ""
270
343
  @width = width
271
344
  @color = color
272
345
  @bar_color = bar_color
346
+ @subbar_color = subbar_color
273
347
  @row = row
274
348
  @col = col
275
349
  @winfg = Curses::Window.new 1, 1, @row, @col
350
+ @winsfg = Curses::Window.new 1, 1, @row, @col
276
351
  @winbg = Curses::Window.new 1, self.width, @row, @col
277
352
  @value = value
353
+ @subvalue = subvalue
278
354
  @text = text
279
355
  refresh
280
356
  end
@@ -288,11 +364,19 @@ class NProgress
288
364
  @winfg.resize(1, fgw) if fgw > 0
289
365
  end
290
366
 
367
+ def subvalue=(val)
368
+ @subvalue = val
369
+ @winsfg.resize(1, sfgw) if sfgw > 0
370
+ end
371
+
291
372
  def refresh
292
- offset = fgw
293
- Nutils.print @winbg, 0, offset, @text[offset..-1], @color
373
+ bgOffset = [fgw, sfgw].max
374
+ sbOffset = fgw
375
+ Nutils.print @winbg, 0, bgOffset, @text[bgOffset..-1], @color
376
+ Nutils.print @winsfg, 0, sbOffset, @text[sbOffset..-1], @subbar_color if sfgw > 0
294
377
  Nutils.print @winfg, 0, 0, @text, @bar_color if fgw > 0
295
378
  @winbg.refresh
379
+ @winsfg.refresh if sfgw > 0
296
380
  @winfg.refresh if fgw > 0
297
381
  end
298
382
 
@@ -301,6 +385,7 @@ class NProgress
301
385
 
302
386
  def close
303
387
  @winbg.close
388
+ @winsfg.close
304
389
  @winfg.close
305
390
  end
306
391
 
@@ -309,6 +394,10 @@ class NProgress
309
394
  w = width() == 0 ? Curses.cols - @col : width()
310
395
  (w * @value).floor
311
396
  end
397
+ def sfgw
398
+ w = width() == 0 ? Curses.cols - @col : width()
399
+ (w * @subvalue).floor
400
+ end
312
401
  end
313
402
 
314
403
  class NPlaylist
@@ -399,9 +488,13 @@ class NPlaylist
399
488
  end
400
489
  end
401
490
  end
402
- @win.attron(Colors.map(@color)) if @color
491
+ while r < height
492
+ Nutils.print @win, r, 0, "", @color
493
+ r += 1
494
+ end
495
+ @win.attron(Colors.pairMap(@color)) if @color
403
496
  @win.box 0, 0
404
- @win.attroff(Colors.map(@color)) if @color
497
+ @win.attroff(Colors.pairMap(@color)) if @color
405
498
  @win.refresh
406
499
  @dirty = false
407
500
  end
@@ -450,16 +543,16 @@ class NInfobox
450
543
 
451
544
  def refresh
452
545
  return unless @visible
453
- Nutils.print @win, 1, 2, "Cloudruby v1.1", :default
454
- Nutils.print @win, 2, 4, "UI Toolkit: #{(Curses.const_defined?"VERSION")?Curses::VERSION : "N/A"}", :default
546
+ Nutils.print @win, 1, 2, "Cloudruby v1.2", :default
547
+ Nutils.print @win, 2, 4, "UI Toolkit: #{(Curses.const_defined?"VERSION")?"#{Curses::VERSION}, #colors: #{Curses::colors}" : "N/A"}", :default
455
548
  Nutils.print @win, 3, 4, "#{@parent.audio_backend.version}", :default
456
549
  Nutils.print @win, 4, 4, "Ruby version: #{RUBY_VERSION}", :default
457
550
  Nutils.print @win, 5, 4, "Author: kulpae <my.shando@gmail.com>", :artist
458
551
  Nutils.print @win, 6, 4, "Website: uraniumlane.net", :title
459
552
  Nutils.print @win, 7, 4, "License: MIT", :default
460
- @win.attron(Colors.map(@color)) if @color
553
+ @win.attron(Colors.pairMap(@color)) if @color
461
554
  @win.box 0, 0
462
- @win.attroff(Colors.map(@color)) if @color
555
+ @win.attroff(Colors.pairMap(@color)) if @color
463
556
  @win.refresh
464
557
  end
465
558
 
@@ -1,6 +1,12 @@
1
1
  require 'cgi'
2
2
  require 'open-uri'
3
- require 'json/pure'
3
+ require 'httpclient'
4
+
5
+ begin
6
+ require 'json/pure'
7
+ rescue LoadError
8
+ require 'json'
9
+ end
4
10
 
5
11
  class SoundCloud
6
12
  include Observable
@@ -13,6 +19,7 @@ class SoundCloud
13
19
  @playlist_pos = -1
14
20
  @download_queue = []
15
21
  @dthread = Thread.new do downloader end
22
+ @dthread.run
16
23
  end
17
24
 
18
25
  def load_playlist(search = nil, offset = 0)
@@ -91,46 +98,48 @@ class SoundCloud
91
98
  filename = "#{t["permalink"]}.#{t["original_format"]}"
92
99
  path = File.join(target_dir, filename)
93
100
  pair = [path, t['download']]
101
+ @logger.debug {"download queued: #{pair}"}
94
102
  @download_queue << pair unless @download_queue.include? pair
95
- @dthread.run
96
103
 
97
104
  changed
98
105
  notify_observers :state => :download, :count => @download_queue.size
99
106
  else
100
107
  changed
101
- notify_observers :state => :download, :error => "Not downloadable"
108
+ notify_observers :state => :status, :type => "Download", :value => "Not granted by content owner"
102
109
  end
103
110
  end
104
111
 
105
112
  # download thread
106
113
  def downloader
107
114
  loop do
108
- size = @download_queue.size
109
115
  while d = @download_queue.shift
110
116
  path = d[0]
111
117
  uri = d[1]
118
+ size = @download_queue.size
112
119
  changed
113
120
  notify_observers :state => :download, :name => path, :count => size
114
- size = @download_queue.size
115
121
  begin
116
122
  path = File.expand_path path
117
- file = File.new(path, "wb")
118
123
  File.open(path, "wb") do |file|
119
- file.print open(uri).read
124
+ cl = HTTPClient.new
125
+ cl.redirect_uri_callback = ->(uri, res) {
126
+ res.header['location'][0]
127
+ }
128
+ cl.get_content(uri) do |chunk|
129
+ file.write(chunk)
130
+ end
120
131
  end
121
- changed
122
- notify_observers :state => :download, :name => path, :count => size
123
- rescue OpenURI::HTTPError => e
132
+ rescue Exception => e
133
+ @logger.error {"Download error: #{e}"}
124
134
  changed
125
135
  notify_observers :state => :error, :error => e
126
- ensure
127
- file.close
128
136
  end
129
137
  end
130
138
  sleep 5
131
139
  end
132
140
  rescue => e
133
141
  changed
142
+ @logger.error {"Downloader thread: #{e}"}
134
143
  notify_observers :state => "error", :error => e
135
144
  end
136
145
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Koch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-02 00:00:00.000000000 Z
11
+ date: 2015-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses