cloudruby 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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