mumbletune 0.1.3 → 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.
data/.gitignore CHANGED
@@ -18,3 +18,6 @@ tmp
18
18
  .DS_Store
19
19
  conf.yml
20
20
  conf.yaml
21
+
22
+ spotify_appkey.key
23
+ tmp/
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in mumbletune.gemspec
4
4
  gemspec
5
+
6
+ gem "mumble-ruby", :github => "elliottwilliams/mumble-ruby", :branch => "master"
7
+ gem "hallon-queue-output", :github => "elliottwilliams/hallon-queue-output", :branch => "master"
@@ -1,74 +1,67 @@
1
+ GIT
2
+ remote: git://github.com/elliottwilliams/hallon-queue-output.git
3
+ revision: 19883c7b06a8b2bb7256bbef2579e715f359ab9e
4
+ branch: master
5
+ specs:
6
+ hallon-queue-output (0.0.2)
7
+
8
+ GIT
9
+ remote: git://github.com/elliottwilliams/mumble-ruby.git
10
+ revision: 8a557f55d2bace410e9ca046469e7ab7344a51de
11
+ branch: master
12
+ specs:
13
+ mumble-ruby (0.0.3)
14
+ activesupport
15
+ celt-ruby
16
+ ruby-libsamplerate
17
+ ruby_protobuf
18
+
1
19
  PATH
2
20
  remote: .
3
21
  specs:
4
- mumbletune (0.1.2)
5
- eventmachine
6
- meta-spotify
22
+ mumbletune (0.2.0)
23
+ daemons
24
+ ffi (~> 1.3.0)
25
+ hallon
7
26
  mumble-ruby
8
27
  mustache
9
- ruby-mpd
10
- rubypython
11
- sinatra
12
28
  text
13
- thin
14
29
 
15
30
  GEM
16
31
  remote: https://rubygems.org/
17
32
  specs:
18
- activesupport (3.2.12)
19
- i18n (~> 0.6)
33
+ activesupport (3.2.13)
34
+ i18n (= 0.6.1)
20
35
  multi_json (~> 1.0)
21
- blankslate (3.1.2)
22
36
  celt-ruby (0.0.1)
23
37
  ffi
24
- columnize (0.3.6)
25
38
  daemons (1.1.9)
26
- debugger (1.5.0)
27
- columnize (>= 0.3.1)
28
- debugger-linecache (~> 1.2.0)
29
- debugger-ruby_core_source (~> 1.2.0)
30
- debugger-linecache (1.2.0)
31
- debugger-ruby_core_source (1.2.0)
32
- eventmachine (1.0.3)
33
- ffi (1.4.0)
34
- httparty (0.10.2)
35
- multi_json (~> 1.0)
36
- multi_xml (>= 0.5.2)
37
- i18n (0.6.4)
38
- meta-spotify (0.3.0)
39
- httparty (> 0.8)
40
- multi_json (1.6.1)
41
- multi_xml (0.5.3)
42
- mumble-ruby (0.0.3)
43
- activesupport
44
- celt-ruby
45
- ruby_protobuf
39
+ ffi (1.3.1)
40
+ hallon (0.18.1)
41
+ spotify (~> 12.3.0)
42
+ weak_observable (~> 1.0)
43
+ i18n (0.6.1)
44
+ libspotify (12.1.51.3)
45
+ multi_json (1.7.2)
46
46
  mustache (0.99.4)
47
- rack (1.5.2)
48
- rack-protection (1.5.0)
49
- rack
50
- rake (10.0.3)
51
- ruby-mpd (0.2.1)
47
+ rake (10.0.4)
48
+ ref (1.0.4)
49
+ ruby-libsamplerate (0.0.1)
50
+ ffi
52
51
  ruby_protobuf (0.4.11)
53
- rubypython (0.6.3)
54
- blankslate (>= 2.1.2.3)
55
- ffi (>= 1.0.7)
56
- sinatra (1.4.1)
57
- rack (~> 1.5, >= 1.5.2)
58
- rack-protection (~> 1.4)
59
- tilt (~> 1.3, >= 1.3.4)
52
+ spotify (12.3.0)
53
+ ffi (~> 1.0, >= 1.0.11)
54
+ libspotify (~> 12.1.51)
60
55
  text (1.2.1)
61
- thin (1.5.0)
62
- daemons (>= 1.0.9)
63
- eventmachine (>= 0.12.6)
64
- rack (>= 1.0.0)
65
- tilt (1.3.5)
56
+ weak_observable (1.0.1)
57
+ ref (~> 1.0.0)
66
58
 
67
59
  PLATFORMS
68
60
  ruby
69
61
 
70
62
  DEPENDENCIES
71
63
  bundler (~> 1.3)
72
- debugger
64
+ hallon-queue-output!
65
+ mumble-ruby!
73
66
  mumbletune!
74
67
  rake
data/README.md CHANGED
@@ -1,23 +1,24 @@
1
1
  # Mumbletune
2
2
 
3
- MUMBLETUNE is an amiable bot that connects to a mumble server and allows users to interact with and play a queue of music. It currently plays music through Spotify.
3
+ MUMBLETUNE is a nice bot that connects to a mumble server and allows users to listen to Spotify together.
4
4
 
5
5
  ## Installation
6
6
 
7
- First, install Hexxeh's [spotify-websocket-api](https://github.com/Hexxeh/spotify-websocket-api).
7
+ What you need:
8
+ - A Spotify Premium account. I wish it didn't have to be this way, but for API access, Premium's required
9
+ - A Spotify App Key. Spotify will issue you one [here][1].
8
10
 
9
- git clone git://github.com/Hexxeh/spotify-websocket-api.git
10
- cd spotify-websocket-api
11
- python setup.py build
12
- python setup.py install
11
+ Clone this repo and install its dependencies
13
12
 
14
- Then, install the gem:
15
-
16
- $ gem install mumbletune
13
+ $ git clone git://github.com/elliottwilliams/mumbletune.git
14
+ cd mumbletune
15
+ bundle install
17
16
 
18
17
  ## Usage
19
18
 
20
19
  1. Create a configuration file. See `conf.example.yaml` for help.
21
20
  2. Start Mumbletune, passing your config with `-c`.
22
21
 
23
- $ mumbletune -c config_file.yaml
22
+ $ mumbletune -c config_file.yaml
23
+
24
+ [1]: https://developer.spotify.com/technologies/libspotify/keys/
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "daemons"
4
+ require "optparse"
5
+
6
+ dir = File.expand_path File.dirname(__FILE__)
7
+ log = File.expand_path File.join(dir, "../logs")
8
+
9
+ # change -c paramater to full path (survives daemon)
10
+ ARGV.each_index do |i|
11
+ if ARGV[i] == "-c"
12
+ ARGV[i+1] = File.expand_path File.join(Dir.pwd, ARGV[i+1])
13
+ end
14
+ end
15
+
16
+ options = {
17
+ app_name: "mumbletune",
18
+ dir_mode: :normal,
19
+ dir: log,
20
+ log_output: true
21
+ }
22
+
23
+ Daemons.run(File.join(dir, "mumbletune"), options)
@@ -1,4 +1,5 @@
1
1
  spotify:
2
+ appkey: "/path/to/spotify_appkey.key"
2
3
  username: "sp_username"
3
4
  password: "sp_password"
4
5
  region: "us"
@@ -8,8 +9,6 @@ mumble:
8
9
  username: "mumbletune"
9
10
  password: "server_password"
10
11
  channel: "Music Room"
11
- mpd:
12
- host: "localhost"
13
- port: 6600
14
- fifo_out: "/tmp/mumble.fifo"
12
+ player:
15
13
  default_volume: 60
14
+ tracks_for_artist: 5
@@ -16,9 +16,12 @@
16
16
  <br>
17
17
  For example:
18
18
 
19
- play coldplay
20
- play nicki minaj starships
19
+ `play coldplay` plays the top 5 tracks by Coldplay.
21
20
 
22
- Mumbletune uses Spotify to find music.
21
+ `play nicki minaj starships` will play the (excellent) song Starships.
22
+
23
+ Mumbletune uses Spotify to find and play music. You can `play` a Spotify URL directly instead of searching, like
24
+
25
+ `play spotify:album:1HjSyGjmLNjRAKgT9t1cna`
23
26
 
24
27
  <!-- Generate HTML with: $ pandoc -f markdown -->
@@ -1,81 +1,86 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'mumbletune/version'
1
4
  require 'mumbletune/mumble_client'
2
- require 'mumbletune/mpd_client'
5
+ require 'mumbletune/hallon_player'
3
6
  require 'mumbletune/messages'
4
- require 'mumbletune/track'
5
7
  require 'mumbletune/collection'
6
- require 'mumbletune/sp_uri_server'
7
- require 'mumbletune/spotify_track'
8
8
  require 'mumbletune/resolver'
9
- require 'mumbletune/handle_sp_error'
9
+ require 'mumbletune/spotify_resolver'
10
10
 
11
11
  require 'optparse'
12
12
  require 'yaml'
13
- require 'eventmachine'
14
- require 'rubypython'
15
13
 
16
14
  module Mumbletune
17
15
  class << self
18
- attr_reader :player, :mumble, :uri_server, :config
16
+ attr_reader :player, :mumble, :uri_server, :config, :verbose
19
17
  end
20
18
 
19
+ # defaults
20
+ @verbose = false
21
+ config_file = nil
22
+
21
23
  # parse command line options
22
- config_file = nil
23
- OptionParser.new do |opts|
24
+ opts = OptionParser.new do |opts|
24
25
  opts.banner = "Usage: mumbletune.rb [options]"
25
26
  opts.on("-c", "--config FILE", "=MANDATORY", "Path to configuration file") do |file|
26
27
  config_file = file
27
28
  end
29
+ opts.on("-v", "--verbose", "Verbose output") do
30
+ @verbose = true
31
+ end
28
32
  opts.on("-h", "--help", "This help message") do
29
- puts opts.help()
33
+ puts opts.help
30
34
  exit
31
35
  end
32
- end.parse!
33
- raise OptionParser::MissingArgument unless config_file
34
-
35
- # load configuration file
36
- @config = YAML.load_file(config_file)
36
+ end
37
37
 
38
- # load spotify-websocket-api
39
- puts ">> Loading Spotify APIs..."
40
- RubyPython.start(:python_exe => 'python2.7')
41
- Spotify = RubyPython.import('spotify_web.friendly').Spotify
38
+ opts.parse!
42
39
 
43
- # open URI server
44
- uri_thread = Thread.new do
45
- SPURIServer::Server.run!
40
+ unless config_file
41
+ puts opts.help
42
+ exit
46
43
  end
47
44
 
45
+ # load configuration file
46
+ @config = YAML.load_file(config_file)
47
+
48
48
  # initialize player
49
49
  play_thread = Thread.new do
50
- @player = Player.new
50
+ @player = HallonPlayer.new
51
+ @player.connect
52
+ puts ">> Connected to Spotify."
51
53
  end
52
54
 
53
55
  # connect to mumble & start streaming
56
+ sleep 0.1 until @player && @player.ready
54
57
  mumble_thread = Thread.new do
55
58
  @mumble = MumbleClient.new
56
59
  @mumble.connect
60
+ puts ">> Connected to Mumble server at #{self.config['mumble']['host']}."
57
61
  @mumble.stream
58
62
  end
59
63
 
60
64
  # shutdown code
61
65
  def self.shutdown
66
+ Thread.new do
67
+ sleep 5 # timeout
68
+ puts "Timeout. Forcing exit."
69
+ exit!
70
+ end
71
+ print "\n>> Exiting... "
62
72
  self.mumble.disconnect
73
+ print "Disconnected from Mumble... "
63
74
  self.player.disconnect
64
- puts "\nGoodbye forever. Exiting..."
75
+ print "Disconnected from Spotify... "
76
+ puts "\nGoodbye forever."
65
77
  exit
66
78
  end
67
79
 
68
80
  # exit when Ctrl-C pressed
69
- EventMachine.schedule do
70
- trap("INT") { Mumbletune.shutdown }
81
+ Signal.trap("INT") do
82
+ Mumbletune.shutdown
71
83
  end
72
84
 
73
- # testing
74
- # sleep 3
75
- # @player.command_test_load
76
- # @player.command_play
77
-
78
- Thread.stop # wake up to shut down
79
- self.shutdown
80
-
85
+ Thread.stop # we're done here
81
86
  end
@@ -1,17 +1,37 @@
1
+ require 'forwardable'
2
+
1
3
  module Mumbletune
2
4
  class Collection
3
- attr_accessor :type, :tracks, :description, :user
5
+ include Enumerable
6
+ extend Forwardable
7
+
8
+ attr_accessor :type, :description, :user, :tracks, :history, :current_track
9
+
10
+ def_delegators :@tracks, :length, :first, :last, :each, :any?, :empty?
4
11
 
5
12
  def initialize(type, tracks, description)
6
13
  @type = type
7
14
  @description = description
15
+ @tracks = [tracks].flatten
16
+ @history = Array.new
17
+ end
8
18
 
9
- if tracks.class == Array
10
- @tracks = tracks
11
- else
12
- @tracks = [tracks]
19
+ def next
20
+ if @current_track
21
+ @history << @current_track
22
+ @tracks.delete @current_track
13
23
  end
24
+ @current_track = @tracks.first
14
25
  end
15
26
 
27
+ def empty?
28
+ without_current = @tracks.dup
29
+ without_current.delete_if { |t| t == @current_track }
30
+ without_current.empty?
31
+ end
32
+
33
+ def any?
34
+ !empty?
35
+ end
16
36
  end
17
37
  end
@@ -0,0 +1,162 @@
1
+ require "hallon"
2
+ require "hallon-queue-output"
3
+ require "thread"
4
+
5
+ module Mumbletune
6
+
7
+ class HallonPlayer
8
+
9
+ attr_accessor :ready, :play_history, :add_history, :queue, :current_track, :audio_queue
10
+
11
+ def initialize
12
+ conf = Mumbletune.config
13
+
14
+ @play_history = Array.new
15
+ @add_history = Array.new
16
+ @queue = Array.new
17
+ @prev_id = 0
18
+ @ready = false
19
+ @audio_queue = Queue.new
20
+
21
+ @ready = true
22
+ end
23
+
24
+ def connect
25
+ conf = Mumbletune.config
26
+
27
+ @session = Hallon::Session.initialize(IO.read(conf["spotify"]["appkey"]))
28
+ @session.login!(conf["spotify"]["username"], conf["spotify"]["password"])
29
+
30
+ @player = Hallon::Player.new(Hallon::QueueOutput) do
31
+ # Set driver options
32
+ @driver.queue = Mumbletune.player.audio_queue
33
+ @driver.verbose = true if Mumbletune.verbose
34
+
35
+ # Define callbacks
36
+ on(:end_of_track) { Mumbletune.player.next }
37
+ on(:streaming_error) { |s, e| raise "Spotify connection error: #{e}" }
38
+ on(:play_token_lost) { Mumbletune.player.play_token_lost }
39
+ end
40
+
41
+ @ready = true
42
+
43
+ start_event_loop
44
+ end
45
+
46
+ def disconnect
47
+ @logging_out = true
48
+ @event_loop_thread.kill
49
+ @session.logout!
50
+ end
51
+
52
+ # Status methods
53
+ def playing?
54
+ @player.status == :playing
55
+ end
56
+
57
+ def paused?
58
+ @player.status == :paused
59
+ end
60
+
61
+ def stopped?
62
+ @player.status == :stopped
63
+ end
64
+
65
+ def any?
66
+ cols_with_more = @queue.map { |col| col.any? }
67
+ cols_with_more.delete_if { |m| m == false }
68
+ cols_with_more.any?
69
+ end
70
+
71
+ def empty?
72
+ !any?
73
+ end
74
+
75
+ # Queue Control
76
+ def add_collection(col, now=false)
77
+ only_track = empty?
78
+
79
+ # add to the queue
80
+ if now
81
+ @queue.unshift col
82
+ else
83
+ @queue.push col
84
+ end
85
+
86
+ # record in additions history
87
+ @add_history.push col
88
+
89
+ # play it, if this is the first track or if the user specified `now`
90
+ self.next if now || only_track
91
+ end
92
+
93
+ def undo
94
+ removed = @add_history.pop
95
+ @queue.delete_if { |col| col == removed }
96
+ self.next if removed.current_track
97
+ removed
98
+ end
99
+
100
+ def clear_queue
101
+ @queue.clear
102
+ self.stop
103
+ end
104
+
105
+ # Playback Commands
106
+ def play
107
+ if stopped?
108
+ self.next
109
+ elsif paused?
110
+ @player.play
111
+ end
112
+ end
113
+
114
+ def pause
115
+ if playing?
116
+ @player.pause
117
+ elsif paused?
118
+ @player.play
119
+ end
120
+ end
121
+
122
+ def next
123
+ # move the collection to play_history if it has played all its tracks
124
+ @play_history << @queue.shift if @queue.first && @queue.first.empty?
125
+
126
+ return stop unless any?
127
+
128
+ track = @queue.first.next
129
+
130
+ return stop unless track
131
+
132
+ # play that shit!
133
+ @current_track = track
134
+ @player.play track
135
+
136
+ puts "\u266B #{track.name} - #{track.artist.name}" unless Mumbletune.verbose
137
+ end
138
+
139
+ def stop
140
+ @player.stop
141
+ end
142
+
143
+ # Callback Handling
144
+ def play_token_lost
145
+ Mumbletune.mumble.broadcast %w{Mumbletune was just paused because this
146
+ Spotify account is being used elsewhere. Type <code>unpause
147
+ </code> to regain control and keep playing.} * " "
148
+ end
149
+
150
+ private
151
+
152
+ def start_event_loop
153
+ @event_loop_thread = Thread.new do
154
+ loop do
155
+ @session.process_events unless @session.disconnected?
156
+ sleep 1
157
+ end
158
+ end
159
+ end
160
+
161
+ end
162
+ end