nezetic-ncXBMC 0.2.1

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.
Files changed (3) hide show
  1. data/README +14 -0
  2. data/bin/ncxbmc.rb +632 -0
  3. metadata +73 -0
data/README ADDED
@@ -0,0 +1,14 @@
1
+ ncXBMC
2
+ ==================
3
+
4
+ ncXBMC is a remote XBMC client, which aims to provide a full control of
5
+ the music player over a local network.
6
+
7
+ It can be used to browse library, manage playlist, and control playback.
8
+
9
+ The interface as been greatly inspired by ncmpc (http://hem.bredband.net/kaw/ncmpc/),
10
+ a curses client for the Music Player Daemon (MPD) (I'm a fan).
11
+
12
+
13
+ Usage: ncxbmc.rb [options] hostname
14
+
data/bin/ncxbmc.rb ADDED
@@ -0,0 +1,632 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+ ncXBMC
5
+
6
+ ncXBMC is a remote XBMC client, which aims to provide a full control of
7
+ the music player over a local network.
8
+
9
+ It can be used to browse library, manage playlist, and control playback.
10
+
11
+ The interface as been greatly inspired by ncmpc (http://hem.bredband.net/kaw/ncmpc/),
12
+ a curses client for the Music Player Daemon (MPD) (I'm a fan).
13
+
14
+
15
+ Copyright (C) 2009 Cedric TESSIER
16
+
17
+ Contact: nezetic.info
18
+
19
+ This program is free software: you can redistribute it and/or modify
20
+ it under the terms of the GNU General Public License as published by
21
+ the Free Software Foundation, either version 3 of the License, or
22
+ (at your option) any later version.
23
+
24
+ This program is distributed in the hope that it will be useful,
25
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
26
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
+ GNU General Public License for more details.
28
+
29
+ You should have received a copy of the GNU General Public License
30
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
31
+ =end
32
+
33
+
34
+ require 'optparse'
35
+
36
+ begin
37
+ require 'rubygems'
38
+ rescue LoadError
39
+ end
40
+
41
+ require 'ncurses'
42
+ require 'ruby-xbmc'
43
+
44
+ NCXMBC_DEFAULTPORT="8080"
45
+ NCXBMC_VERSION=0.2
46
+
47
+ module Interface
48
+ KEY_TAB = 9
49
+ KEY_ENTER = 10
50
+ KEY_SPACE = 32
51
+ KEY_RIGHT = 261
52
+ KEY_LEFT = 260
53
+ KEY_UP = 259
54
+ KEY_DOWN = 258
55
+ KEY_b = 98
56
+ KEY_c = 99
57
+ KEY_d = 100
58
+ KEY_f = 102
59
+ KEY_h = 104
60
+ KEY_m = 109
61
+ KEY_p = 112
62
+ KEY_RETURN = 127
63
+
64
+ class NcurseInterface
65
+ REFRESH_DELAY=1 # seconds
66
+ KEY_ESC=27
67
+
68
+ def initialize(xbmc)
69
+ @stdscr = Ncurses.initscr
70
+
71
+ Ncurses.curs_set(0)
72
+ Ncurses.noecho
73
+ Ncurses.keypad(@stdscr,TRUE)
74
+ Ncurses.cbreak
75
+ @stdscr.nodelay(true)
76
+
77
+ ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
78
+
79
+ if Ncurses.has_colors?
80
+ Ncurses.start_color
81
+ Ncurses.use_default_colors
82
+ init_bgcolor()
83
+ end
84
+
85
+ @maxy=Ncurses.getmaxy(@stdscr)
86
+ @maxx=Ncurses.getmaxx(@stdscr)
87
+
88
+ @xbmc = xbmc
89
+
90
+ @wins = []
91
+ @wins << PlaylistWin.new(@stdscr, @xbmc)
92
+ @wins << LibraryWin.new(@stdscr, @xbmc)
93
+
94
+ @currentwin = 0
95
+ end
96
+
97
+ def refresh
98
+ @stdscr.refresh
99
+ @wins[@currentwin].refresh
100
+ end
101
+
102
+ def drawCurrentWin
103
+ @wins[@currentwin].draw
104
+ end
105
+
106
+ def UpdateCurrentWin
107
+ @wins[@currentwin].update
108
+ end
109
+
110
+ def run
111
+ self.drawCurrentWin
112
+ self.refresh
113
+
114
+ key=0
115
+ timespent=0
116
+ while key!=KEY_ESC
117
+ key=@stdscr.getch
118
+
119
+ if(key == KEY_TAB) # Tabs Cycling
120
+ @currentwin += 1
121
+ @currentwin = 0 if(@currentwin > (@wins.length - 1))
122
+
123
+ self.drawCurrentWin
124
+ self.refresh
125
+ next
126
+ end
127
+
128
+ @wins[@currentwin].handleKey(key) if key != -1
129
+
130
+ sleep(0.01)
131
+ timespent += 0.01
132
+
133
+ if(timespent >= REFRESH_DELAY)
134
+ timespent = 0
135
+ self.UpdateCurrentWin
136
+ end
137
+ end
138
+ end
139
+
140
+ protected
141
+
142
+ def init_bgcolor(pair=1, bgcolor=Ncurses::COLOR_BLACK)
143
+ Ncurses.init_pair(1, Ncurses::COLOR_WHITE, bgcolor)
144
+ Ncurses.init_pair(2, Ncurses::COLOR_YELLOW, bgcolor)
145
+ Ncurses.init_pair(3, Ncurses::COLOR_RED, bgcolor)
146
+ Ncurses.init_pair(4, Ncurses::COLOR_GREEN, bgcolor)
147
+ Ncurses.init_pair(5, Ncurses::COLOR_BLUE, bgcolor)
148
+ Ncurses.init_pair(6, Ncurses::COLOR_CYAN, bgcolor)
149
+ Ncurses.init_pair(7, Ncurses::COLOR_MAGENTA, bgcolor)
150
+ Ncurses.init_pair(8, Ncurses::COLOR_BLACK, bgcolor)
151
+ Ncurses.bkgd(Ncurses.COLOR_PAIR(pair))
152
+ end
153
+
154
+ private
155
+
156
+ def self.finalize(id)
157
+ Ncurses.echo()
158
+ Ncurses.nocbreak()
159
+ Ncurses.nl()
160
+ Ncurses.curs_set(1)
161
+
162
+ Ncurses.endwin
163
+ end
164
+ end
165
+
166
+ class TabWins
167
+
168
+ def initialize(pscreen, xbmc)
169
+ @stdscr = pscreen
170
+ @xbmc = xbmc
171
+
172
+ @title = ""
173
+
174
+ @maxy=Ncurses.getmaxy(@stdscr)
175
+ @maxx=Ncurses.getmaxx(@stdscr)
176
+
177
+ @header = Ncurses::WINDOW.new(2,@maxx,0,0)
178
+ @main = Ncurses::WINDOW.new(@maxy-2-2,@maxx,2,0)
179
+ @footer = Ncurses::WINDOW.new(2,@maxx,@maxy-2,0)
180
+
181
+ @selected = 0
182
+ @scroll = 0
183
+
184
+ @helpMSGCommon = "TAB switch between windows\nESC quit ncXBMC\n\n"
185
+ @helpMSG = ""
186
+ end
187
+
188
+ def refresh
189
+ @header.refresh
190
+ @main.refresh
191
+ @footer.refresh
192
+ end
193
+
194
+ def draw
195
+ self.drawHeader
196
+ self.drawMain
197
+ self.drawFooter
198
+ end
199
+
200
+ def update(rheader=false, rmain=false, rfooter=false)
201
+ if(rheader)
202
+ drawHeader
203
+ @header.refresh
204
+ end
205
+ if(rmain)
206
+ self.drawMain
207
+ @main.refresh
208
+ end
209
+ if(rfooter)
210
+ self.drawFooter
211
+ @footer.refresh
212
+ end
213
+ end
214
+
215
+ def handleKey(key)
216
+ #Ncurses.mvwaddstr(@main, 20, 1, "KeyCode: " + key.to_s)
217
+ #@main.refresh
218
+
219
+ case key
220
+ when KEY_h then
221
+ self.showHelp
222
+ end
223
+ end
224
+
225
+ protected
226
+
227
+ def drawHeader
228
+ @header.erase
229
+ @header.attron(Ncurses::A_BOLD)
230
+ Ncurses.mvwaddstr(@header, 0, 1, "Ncurses XBMC Client v." + NCXBMC_VERSION.to_s + " -- " + @title)
231
+ @header.attroff(Ncurses::A_BOLD)
232
+ @header.mvhline(1,0,Ncurses::ACS_HLINE, @maxx)
233
+ end
234
+
235
+ def drawMain
236
+ @main.erase
237
+ end
238
+
239
+ def drawFooter
240
+ @footer.erase
241
+ @footer.mvhline(0,0,Ncurses::ACS_HLINE, @maxx)
242
+ end
243
+
244
+ def handleScroll
245
+ maxwy = Ncurses.getmaxy(@main) - 1
246
+ if((@selected - @scroll) > maxwy)
247
+ @scroll += 1
248
+ elsif((@selected - @scroll) < 0)
249
+ @scroll -= 1
250
+ end
251
+ return maxwy
252
+ end
253
+
254
+ def showHelp
255
+ border = 15
256
+ title = "Help"
257
+
258
+ helpmsg = @helpMSGCommon + @helpMSG
259
+
260
+ lines = helpmsg.count("\n") + 3
261
+ helpwin = Ncurses::WINDOW.new(lines, @maxx-border, 2, (@maxx/2 - (@maxx-border)/2))
262
+ Ncurses.box(helpwin, Ncurses::ACS_VLINE, Ncurses::ACS_HLINE)
263
+ helpwin.attron(Ncurses::A_REVERSE)
264
+ Ncurses.mvwaddstr(helpwin, 0, (@maxx-border)/2 - (title.length + 2)/2, " #{title} ")
265
+ helpwin.attroff(Ncurses::A_REVERSE)
266
+
267
+ linenbr = 1
268
+ helpmsg.each { |line|
269
+ Ncurses.mvwaddstr(helpwin, linenbr, 2, line.chomp)
270
+ linenbr += 1
271
+ }
272
+ helpwin.refresh
273
+ end
274
+ end
275
+
276
+ class PlaylistWin < TabWins
277
+ def initialize(pscreen, xbmc)
278
+ super
279
+ @title = "Playlist"
280
+
281
+ @forcesel = 0
282
+ @fastRefresh = false
283
+ end
284
+
285
+ def drawHeader
286
+ super
287
+
288
+ volume = @xbmc.GetVolume.to_i
289
+ if(volume > 0)
290
+ volume_label = "Volume %d%%" % volume
291
+ else
292
+ volume_label = "Volume Muted"
293
+ end
294
+ Ncurses.mvwaddstr(@header, 0, @maxx - volume_label.length - 1, volume_label)
295
+ end
296
+
297
+ def drawMain
298
+ super
299
+
300
+ if(not @fastRefresh)
301
+ getCurrentPlaylist
302
+ getCurrentSong
303
+ else
304
+ @fastRefresh = false
305
+ end
306
+
307
+ return if @playlist.length == 1 and @playlist[0]["artist"].nil?
308
+
309
+ maxwy = self.handleScroll
310
+
311
+ idx = 0
312
+ @playlist.each { |song|
313
+ line = idx - @scroll
314
+ if(line >= 0)
315
+ break if(line > maxwy)
316
+
317
+ current = (@playing and @current_song["URL"] == song["path"])
318
+ if(current)
319
+ @main.attron(Ncurses.COLOR_PAIR(2)) if Ncurses.has_colors?
320
+ @main.attron(Ncurses::A_BOLD)
321
+ end
322
+ @main.attron(Ncurses::A_REVERSE) if(idx == @selected)
323
+ Ncurses.mvwaddstr(@main, line, 1, "#{song["artist"]} - #{song["title"]}")
324
+ if(current)
325
+ @main.attron(Ncurses.COLOR_PAIR(1)) if Ncurses.has_colors?
326
+ @main.attroff(Ncurses::A_BOLD)
327
+ end
328
+ @main.attroff(Ncurses::A_REVERSE) if(idx == @selected)
329
+ end
330
+ idx += 1
331
+ }
332
+
333
+ end
334
+
335
+ def drawFooter
336
+ super
337
+
338
+ getCurrentSong
339
+
340
+ if(@playing)
341
+ speed = @xbmc.GetPlaySpeed.to_i
342
+ @footer.mvwaddstr(0, 0, '=' * (@maxx * @current_song["Percentage"].to_i / 100) + (speed == 1 ? '0': (speed > 1 ? '>>':'<<')))
343
+ paused = (@current_song["PlayStatus"] == "Paused")
344
+ Ncurses.mvwaddstr(@footer, 1, 1, "Playing: #{@current_song["Artist"]} - #{@current_song["Title"]} #{(paused ? "(Pause)":"")}")
345
+ time_label = "["+@current_song["Time"]+"/"+@current_song["Duration"]+"]"
346
+ Ncurses.mvwaddstr(@footer, 1, @maxx - time_label.length - 1, time_label)
347
+ speed_label = "(%dX)" % speed
348
+ @footer.mvwaddstr(0, @maxx - speed_label.length - 1, speed_label) if(speed != 1)
349
+ end
350
+ end
351
+
352
+ def update(rheader=false, rmain=false, rfooter=true)
353
+ super
354
+
355
+ if(@playing) # new song playing, refresh playlist
356
+ if(not rmain and (@current_song["Changed"] == "True"))
357
+ self.drawMain
358
+ @main.refresh
359
+ end
360
+ end
361
+ end
362
+
363
+ def handleKey(key)
364
+ super
365
+
366
+ refresh_header = false
367
+ refresh_main = false
368
+ refresh_footer = false
369
+
370
+ case key
371
+ when KEY_LEFT then
372
+ @xbmc.SetVolume(@xbmc.GetVolume.to_i - 1)
373
+ refresh_header = true
374
+ when KEY_RIGHT then
375
+ @xbmc.SetVolume(@xbmc.GetVolume.to_i + 1)
376
+ refresh_header = true
377
+ when KEY_DOWN then
378
+ @selected += 1 if @selected < (@playlist.length - 1)
379
+ @fastRefresh = true
380
+ refresh_main = true
381
+ when KEY_UP then
382
+ @selected -= 1 if @selected > 0
383
+ @fastRefresh = true
384
+ refresh_main = true
385
+ when KEY_ENTER then
386
+ if(@xbmc.GetPlaySpeed.to_i != 1)
387
+ @xbmc.SetPlaySpeed(1)
388
+ else
389
+ @xbmc.SetPlaylistSong(@selected)
390
+ end
391
+ refresh_main = true
392
+ when KEY_d then
393
+ @xbmc.RemoveFromPlaylist(@selected)
394
+ @selected -= 1 if @selected >= (@playlist.length - 1)
395
+ @forcesel = @selected
396
+ refresh_main = true
397
+ when KEY_c then
398
+ @xbmc.Stop
399
+ @xbmc.ClearPlayList(XBMC::MUSIC_PLAYLIST)
400
+ @selected = 0
401
+ refresh_main = true
402
+ when KEY_f then
403
+ speed = @xbmc.GetPlaySpeed.to_i
404
+ newspeed = speed < 0 ? speed / 2 : speed * 2
405
+ newspeed = 1 if(newspeed == -1)
406
+ @xbmc.SetPlaySpeed(newspeed) if(speed < 32)
407
+ when KEY_b then
408
+ speed = @xbmc.GetPlaySpeed.to_i
409
+ newspeed = speed > 0 ? speed / 2 : speed * 2
410
+ newspeed = -2 if(newspeed == 0)
411
+ @xbmc.SetPlaySpeed(newspeed) if(speed > -32)
412
+ when KEY_m then
413
+ @xbmc.Mute
414
+ refresh_header = true
415
+ when KEY_p then
416
+ @xbmc.Pause
417
+ refresh_footer = true
418
+ end
419
+
420
+ self.update(refresh_header, refresh_main, refresh_footer)
421
+ end
422
+
423
+ def showHelp
424
+ @helpMSG = "UP select previous entry\n"
425
+ @helpMSG += "DOWN select next entry\n"
426
+ @helpMSG += "ENTER play selected entry\n"
427
+ @helpMSG += "d remove selected entry\n"
428
+ @helpMSG += "c clear current playlist\n\n"
429
+ @helpMSG += "RIGHT Volume +\n"
430
+ @helpMSG += "LEFT Volume -\n"
431
+ @helpMSG += "m mute volume\n\n"
432
+ @helpMSG += "f fast forward\n"
433
+ @helpMSG += "b fast rewind\n"
434
+ @helpMSG += "p pause playback"
435
+ super
436
+ end
437
+
438
+ def getCurrentSong
439
+ @current_song = @xbmc.GetCurrentlyPlaying
440
+ @playing = (@current_song != nil)
441
+ end
442
+
443
+ def getCurrentPlaylist
444
+ playlist = @xbmc.GetPlaylistContents
445
+ return if(playlist == @last_playlist)
446
+ @last_playlist = playlist
447
+
448
+ @selected = @forcesel
449
+ @forcesel = 0
450
+
451
+ @playlist = [] # cache tags infos for playlist
452
+ playlist.each { |file|
453
+ song = @xbmc.GetTagFromFilename(file)
454
+ @playlist.push({"path"=>file, "artist"=>song["Artist"], "title"=>song["Title"]})
455
+ }
456
+ end
457
+
458
+ end
459
+
460
+ class LibraryWin < TabWins
461
+
462
+ def initialize(pscreen, xbmc)
463
+ super
464
+ @title = "Library"
465
+ @deepth = 0
466
+ @lastdeepth = -1
467
+ @currentdir = nil
468
+ @history = []
469
+
470
+ end
471
+
472
+ def drawMain
473
+ super
474
+
475
+ if(@lastdeepth != @deepth) # refresh library entries list if needed
476
+ @list = @xbmc.GetMediaLocation("music", @currentdir)
477
+ @lastdeepth = @deepth
478
+ end
479
+
480
+ maxwy = self.handleScroll
481
+
482
+ idx = 0
483
+ @list.each { |entry|
484
+ line = idx - @scroll
485
+ if(line >= 0)
486
+ break if(line > maxwy)
487
+
488
+ @main.attron(Ncurses::A_REVERSE) if(idx == @selected)
489
+ Ncurses.mvwaddstr(@main, line, 1, "#{entry["name"]}")
490
+ @main.attroff(Ncurses::A_REVERSE) if(idx == @selected)
491
+ end
492
+ idx += 1
493
+ }
494
+ end
495
+
496
+ def drawFooter
497
+ super
498
+
499
+ histline = ""
500
+ @history.reverse.each {|old|
501
+ histline += " > " + old[:currentname]
502
+ }
503
+
504
+ Ncurses.mvwaddstr(@footer, 1, 1, histline)
505
+
506
+ nitems_label = @list.length.to_s + " items"
507
+ @footer.mvwaddstr(1, @maxx - nitems_label.length - 1, nitems_label)
508
+ end
509
+
510
+ def handleKey(key)
511
+ super
512
+
513
+ refresh_header = false
514
+ refresh_main = false
515
+ refresh_footer = false
516
+
517
+ case key
518
+ when KEY_DOWN then
519
+ @selected += 1 if @selected < (@list.length - 1)
520
+ refresh_main=true
521
+ when KEY_UP then
522
+ @selected -= 1 if @selected > 0
523
+ refresh_main=true
524
+ when KEY_ENTER then
525
+ if @list[@selected]["type"].to_i == XBMC::TYPE_DIRECTORY
526
+ @history.insert(0, {:currentdir=>@currentdir, :selected=>@selected, :scroll=>@scroll, :currentname=>@list[@selected]["name"]})
527
+ @currentdir = @list[@selected]["path"]
528
+ @selected = 0
529
+ @scroll = 0
530
+ @deepth += 1
531
+ end
532
+ refresh_main = true
533
+ refresh_footer = true
534
+ when KEY_RETURN then
535
+ return if(@history.length < 1)
536
+ oldentry = @history.shift
537
+ @currentdir = oldentry[:currentdir]
538
+ @selected = oldentry[:selected]
539
+ @scroll = oldentry[:scroll]
540
+ @deepth -= 1
541
+ refresh_main = true
542
+ refresh_footer = true
543
+ when KEY_SPACE then
544
+ @xbmc.AddToPlayList(@list[@selected]["path"], XBMC::MUSIC_PLAYLIST, "[music]")
545
+ @xbmc.SetCurrentPlaylist(XBMC::MUSIC_PLAYLIST)
546
+ #@xbmc.SetPlaylistSong(0)
547
+ when KEY_c then
548
+ @xbmc.Stop
549
+ @xbmc.ClearPlayList(XBMC::MUSIC_PLAYLIST)
550
+ end
551
+
552
+ self.update(refresh_header, refresh_main, refresh_footer)
553
+ end
554
+
555
+ def showHelp
556
+ @helpMSG = "UP select previous entry\n"
557
+ @helpMSG += "DOWN select next entry\n"
558
+ @helpMSG += "ENTER browse selected directory\n"
559
+ @helpMSG += "RETURN return into previous directory\n"
560
+ @helpMSG += "SPACE add selected directory/file to playlist\n\n"
561
+ @helpMSG += "c clear current playlist"
562
+ super
563
+ end
564
+ end
565
+ end
566
+
567
+ ##### MAIN ######
568
+
569
+ options = {}
570
+ inputopts = OptionParser.new do |opts|
571
+ options[:port] = NCXMBC_DEFAULTPORT
572
+
573
+ opts.banner = "Usage: %s [options] hostname" % File.basename($0)
574
+ opts.separator " "
575
+
576
+ opts.on("-v", "--version", "Print version") do |v|
577
+ options[:version] = v
578
+ end
579
+ opts.on("-p", "--port [NUMBER]", Integer,"Port used (default %d)" % NCXMBC_DEFAULTPORT) do |port|
580
+ options[:port] = port
581
+ end
582
+ opts.on("-U", "--user [NAME]", String, "Username used for authentication") do |name|
583
+ options[:user] = name
584
+ end
585
+ opts.on("-P", "--password [PASS]", String, "Password used for authentication") do |pass|
586
+ options[:pass] = pass
587
+ end
588
+ end
589
+
590
+ begin
591
+ inputopts.parse!
592
+ rescue OptionParser::InvalidOption => e
593
+ puts "Error: " + e
594
+ puts "\n" + inputopts.banner
595
+ exit 1
596
+ end
597
+
598
+ if(options[:version])
599
+ puts "ncXBMC version " + NCXBMC_VERSION.to_s
600
+ puts <<EOF
601
+
602
+ Copyright (C) 2009 Cedric TESSIER
603
+
604
+ This program may be redistributed under
605
+ the terms of the GPL v2 License
606
+ EOF
607
+ exit 0
608
+ end
609
+
610
+ if(ARGV.length != 1)
611
+ puts inputopts
612
+ exit 1
613
+ end
614
+
615
+ hostname = ARGV.first
616
+
617
+ xbmc = XBMC::XBMC.new(hostname, options[:port], options[:user], options[:pass])
618
+
619
+ begin
620
+ puts "Connected to " + xbmc.host + " (xbmc " + xbmc.GetSystemInfo(120).first + ")"
621
+ rescue XBMC::UnauthenticatedError => e
622
+ puts "ERROR: " + e
623
+ exit 1
624
+ rescue SocketError => e
625
+ puts "ERROR: Connection error"
626
+ puts "Please check given hostname (and/or port)"
627
+ exit 1
628
+ end
629
+
630
+ interface = Interface::NcurseInterface.new(xbmc)
631
+ interface.run
632
+
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nezetic-ncXBMC
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Cedric TESSIER
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-16 00:00:00 -07:00
13
+ default_executable: ncxbmc.rb
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ncurses
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.9"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: ruby-xbmc
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0.1"
34
+ version:
35
+ description: ncXBMC is a remote XBMC client, with an ncurses interface, which aims to provide a full control of the music player over a local network. It can be used to browse library, manage playlist, and control playback.
36
+ email: nezetic at gmail d o t com
37
+ executables:
38
+ - ncxbmc.rb
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - README
45
+ - bin/ncxbmc.rb
46
+ has_rdoc: false
47
+ homepage: http://github.com/nezetic/ncXBMC
48
+ post_install_message:
49
+ rdoc_options: []
50
+
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.2.0
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: ncXBMC is a remote XBMC client, with an ncurses interface
72
+ test_files: []
73
+