mumbletune 0.1.3 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +42 -49
- data/README.md +11 -10
- data/bin/mumbletune-ctl +23 -0
- data/conf.example.yaml +3 -4
- data/docs/commands.md +6 -3
- data/lib/mumbletune.rb +40 -35
- data/lib/mumbletune/collection.rb +25 -5
- data/lib/mumbletune/hallon_player.rb +162 -0
- data/lib/mumbletune/messages.rb +24 -19
- data/lib/mumbletune/mumble_client.rb +28 -9
- data/lib/mumbletune/resolver.rb +19 -32
- data/lib/mumbletune/spotify_resolver.rb +51 -0
- data/lib/mumbletune/{templates → template}/commands.mustache +4 -3
- data/lib/mumbletune/{templates → template}/queue.mustache +13 -11
- data/lib/mumbletune/version.rb +1 -1
- data/logs/.gitignore +4 -0
- data/mumbletune.gemspec +5 -10
- metadata +18 -82
- data/lib/mumbletune/handle_sp_error.rb +0 -23
- data/lib/mumbletune/mpd_client.rb +0 -171
- data/lib/mumbletune/sp_uri_server.rb +0 -42
- data/lib/mumbletune/spotify_track.rb +0 -90
- data/lib/mumbletune/track.rb +0 -32
@@ -1,23 +0,0 @@
|
|
1
|
-
module Mumbletune
|
2
|
-
|
3
|
-
# The Spotify Metadata API seems to throw 502 (Bad Gateway) errors *a lot*.
|
4
|
-
# There's some complaint about this online, and I can reproduce it with
|
5
|
-
# plenty of REST clients, so I am sure it's Spotify's problem. But they
|
6
|
-
# don't seem too interested in fixing what appears to be a long-standing
|
7
|
-
# issue. Ho hum.
|
8
|
-
def self.handle_sp_error
|
9
|
-
begin
|
10
|
-
yield
|
11
|
-
rescue MetaSpotify::ServerError => err
|
12
|
-
puts "Caught ServerError: #{err}"
|
13
|
-
failed ||= 0
|
14
|
-
failed += 1
|
15
|
-
if failed < 4
|
16
|
-
sleep 1
|
17
|
-
retry
|
18
|
-
else
|
19
|
-
raise
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,171 +0,0 @@
|
|
1
|
-
require 'ruby-mpd'
|
2
|
-
|
3
|
-
module Mumbletune
|
4
|
-
|
5
|
-
class Player
|
6
|
-
|
7
|
-
attr_accessor :history
|
8
|
-
|
9
|
-
def initialize(host=Mumbletune.config['mpd']['host'], port=Mumbletune.config['mpd']['port'])
|
10
|
-
@mpd = MPD.new(host, port)
|
11
|
-
self.connect
|
12
|
-
self.clear_all
|
13
|
-
|
14
|
-
@history = Array.new
|
15
|
-
@prev_id = 0
|
16
|
-
|
17
|
-
self.defaults
|
18
|
-
self.establish_callbacks
|
19
|
-
end
|
20
|
-
|
21
|
-
def connect
|
22
|
-
@disconnecting = false
|
23
|
-
@mpd.connect true # 'true' enables callbacks
|
24
|
-
end
|
25
|
-
|
26
|
-
def disconnect
|
27
|
-
@disconnecting = true
|
28
|
-
@mpd.disconnect
|
29
|
-
puts ">> Disconnected from MPD"
|
30
|
-
end
|
31
|
-
|
32
|
-
# Setup
|
33
|
-
|
34
|
-
def defaults
|
35
|
-
@mpd.volume = Mumbletune.config["mpd"]["default_volume"] || 100
|
36
|
-
@mpd.consume = true
|
37
|
-
end
|
38
|
-
|
39
|
-
def establish_callbacks
|
40
|
-
@mpd.on :connection do |status|
|
41
|
-
if status == false && !@disconnecting
|
42
|
-
self.connect
|
43
|
-
else
|
44
|
-
puts ">> MPD happens to be connected."
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# Fires when currently playing song changes.
|
49
|
-
@mpd.on :songid do |id|
|
50
|
-
|
51
|
-
# Clear old tracks from the store.
|
52
|
-
Track.store.delete_if { |t| t.mpd_id == @prev_id }
|
53
|
-
|
54
|
-
@prev_id = id
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# Status methods
|
59
|
-
|
60
|
-
def playing?
|
61
|
-
state = @mpd.status[:state]
|
62
|
-
if state =~ /^(play|pause)$/i
|
63
|
-
true
|
64
|
-
else
|
65
|
-
false
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def paused?
|
70
|
-
state = @mpd.status[:state]
|
71
|
-
if state == :pause
|
72
|
-
true
|
73
|
-
else
|
74
|
-
false
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
# MPD Settings
|
80
|
-
|
81
|
-
def volume?
|
82
|
-
@mpd.volume
|
83
|
-
end
|
84
|
-
|
85
|
-
def volume(percent)
|
86
|
-
@mpd.volume = percent
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
# Queue
|
92
|
-
|
93
|
-
def add_collection(col, now=false)
|
94
|
-
col.tracks.each do |t|
|
95
|
-
id = @mpd.addid t.url,
|
96
|
-
(now) ? col.tracks.index(t)+1 : nil
|
97
|
-
t.mpd_id = id
|
98
|
-
end
|
99
|
-
|
100
|
-
@history.push col
|
101
|
-
|
102
|
-
@mpd.next if now
|
103
|
-
end
|
104
|
-
|
105
|
-
def queue
|
106
|
-
# Combine the future queue with the current track.
|
107
|
-
# MPD puts the current track in its queue only if
|
108
|
-
# other tracks are queued to play. Account for this.
|
109
|
-
queue = @mpd.queue
|
110
|
-
queue.unshift @mpd.current_song if @mpd.current_song
|
111
|
-
|
112
|
-
# Delete index 0 if first queue position and current song are duplicates.
|
113
|
-
queue.delete_at(0) if queue[1] && queue[0] == queue[1]
|
114
|
-
|
115
|
-
# Associate known Tracks with Queue items.
|
116
|
-
mapped_queue = queue.map do |mpd_song|
|
117
|
-
t = Track.retreive_from_mpd_id(mpd_song.id)
|
118
|
-
if t
|
119
|
-
t.queue_pos = mpd_song.pos
|
120
|
-
t
|
121
|
-
end
|
122
|
-
end
|
123
|
-
mapped_queue
|
124
|
-
end
|
125
|
-
|
126
|
-
def current_song
|
127
|
-
Track.retreive_from_mpd_id(@mpd.current_song.id) if @mpd.playing?
|
128
|
-
end
|
129
|
-
|
130
|
-
def undo
|
131
|
-
last_collection = @history.pop
|
132
|
-
|
133
|
-
last_collection.tracks.each do |t|
|
134
|
-
to_delete = @mpd.queue.select { |mpd_song| mpd_song.id == t.mpd_id }.first
|
135
|
-
@mpd.delete(to_delete.pos) if to_delete
|
136
|
-
end
|
137
|
-
last_collection
|
138
|
-
end
|
139
|
-
|
140
|
-
def clear_all
|
141
|
-
@mpd.clear
|
142
|
-
end
|
143
|
-
|
144
|
-
def clear_queue
|
145
|
-
current = @mpd.current_song
|
146
|
-
@mpd.queue.each do |t|
|
147
|
-
@mpd.delete :id => t.id unless t.id == current.id
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
# Playback commands
|
152
|
-
|
153
|
-
def play
|
154
|
-
@mpd.play
|
155
|
-
end
|
156
|
-
|
157
|
-
def pause
|
158
|
-
@mpd.pause = (@mpd.playing?) ? true : false
|
159
|
-
end
|
160
|
-
|
161
|
-
def next
|
162
|
-
@mpd.next
|
163
|
-
end
|
164
|
-
|
165
|
-
def stop
|
166
|
-
@mpd.stop
|
167
|
-
end
|
168
|
-
|
169
|
-
end
|
170
|
-
|
171
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'sinatra/base'
|
2
|
-
|
3
|
-
module Mumbletune
|
4
|
-
|
5
|
-
module SPURIServer
|
6
|
-
|
7
|
-
class Server < Sinatra::Base
|
8
|
-
set :run, true
|
9
|
-
set :server, :thin
|
10
|
-
set :logging, false
|
11
|
-
set :app_file, __FILE__
|
12
|
-
set :bind, 'localhost'
|
13
|
-
set :port, 8081
|
14
|
-
|
15
|
-
get '/play/:uri' do
|
16
|
-
cred = Mumbletune.config['spotify']
|
17
|
-
sp = Mumbletune::Spotify.new(cred['username'], cred['password'])
|
18
|
-
|
19
|
-
track = sp.objectFromURI(params[:uri])
|
20
|
-
halt 404, "Could not find a track with that URI." if track == nil
|
21
|
-
|
22
|
-
url = track.getFileURL().to_s
|
23
|
-
halt 404, "Could not find a track URL for that URI." if track == nil
|
24
|
-
|
25
|
-
sp.logout
|
26
|
-
redirect url, 303
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.url_for(uri)
|
31
|
-
bind = Server::bind
|
32
|
-
port = Server::port
|
33
|
-
"http://#{bind}:#{port}/play/#{uri}"
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.sp_uri_for(url)
|
37
|
-
regexp = /(.+)(<sp_uri>spotify:\w+:\w+)/i
|
38
|
-
matched = regexp.match(url)
|
39
|
-
matched[:sp_uri]
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,90 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
|
-
module Mumbletune
|
4
|
-
|
5
|
-
class SpotifyTrack < Track
|
6
|
-
def initialize(params)
|
7
|
-
super
|
8
|
-
|
9
|
-
@uri = params[:uri]
|
10
|
-
@url = SPURIServer.url_for(@uri)
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.track_from_uri(track)
|
14
|
-
track = MetaSpotify::Track.lookup(track) unless track.class == MetaSpotify::Track
|
15
|
-
|
16
|
-
# force track to be playable within region
|
17
|
-
unless track.album.available_territories.include? Mumbletune.config["spotify"]["region"]
|
18
|
-
raise "#{track.name}: Not available in this region."
|
19
|
-
end
|
20
|
-
|
21
|
-
song = SpotifyTrack.new({
|
22
|
-
:name => track.name,
|
23
|
-
:artist => track.artists.first.name,
|
24
|
-
:album => track.album.name,
|
25
|
-
:uri => track.uri
|
26
|
-
})
|
27
|
-
|
28
|
-
# Technically, a collection of one.
|
29
|
-
Collection.new(
|
30
|
-
:TRACK,
|
31
|
-
song,
|
32
|
-
"<b>#{song.name}</b> by <b>#{song.artist}</b>"
|
33
|
-
)
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.tracks_from_album(album_ref)
|
37
|
-
album_uri = album_ref.uri if album_ref.class == MetaSpotify::Album
|
38
|
-
album = MetaSpotify::Album.lookup(album_uri, {:extras => "track"})
|
39
|
-
|
40
|
-
# force album to be playable in region
|
41
|
-
unless album.available_territories.include? Mumbletune.config["spotify"]["region"]
|
42
|
-
raise "#{album.name}: Not available in this region."
|
43
|
-
end
|
44
|
-
|
45
|
-
tracks = []
|
46
|
-
album.tracks.each do |track|
|
47
|
-
tracks.push SpotifyTrack.new({
|
48
|
-
:name => track.name,
|
49
|
-
:artist => track.artists.first.name,
|
50
|
-
:album => album.name,
|
51
|
-
:uri => track.uri
|
52
|
-
})
|
53
|
-
end
|
54
|
-
|
55
|
-
Collection.new(
|
56
|
-
:ALBUM,
|
57
|
-
tracks,
|
58
|
-
"the album <b>#{album.name}</b> by <b>#{album.artists.first.name}</b>"
|
59
|
-
)
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.tracks_from_artist(artist)
|
63
|
-
artist = MetaSpotify::Artist.lookup(artist) unless artist.class == MetaSpotify::Artist
|
64
|
-
|
65
|
-
# spotify metadata api still error-prone
|
66
|
-
search_result = Mumbletune.handle_sp_error { MetaSpotify::Track.search("artist:\"#{artist.name}\"") }
|
67
|
-
|
68
|
-
# filter out tracks outside region
|
69
|
-
search_result[:tracks].select! do |track|
|
70
|
-
track.album.available_territories.include? Mumbletune.config["spotify"]["region"]
|
71
|
-
end
|
72
|
-
|
73
|
-
tracks = []
|
74
|
-
search_result[:tracks][0...10].each do |track|
|
75
|
-
tracks.push SpotifyTrack.new({
|
76
|
-
:name => track.name,
|
77
|
-
:artist => track.artists.first.name,
|
78
|
-
:album => track.album.name,
|
79
|
-
:uri => track.uri
|
80
|
-
})
|
81
|
-
end
|
82
|
-
|
83
|
-
Collection.new(
|
84
|
-
:ARTIST_TOP,
|
85
|
-
tracks,
|
86
|
-
"#{tracks.length} tracks by <b>#{artist.name}</b>"
|
87
|
-
)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
data/lib/mumbletune/track.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module Mumbletune
|
2
|
-
class Track
|
3
|
-
attr_accessor :name, :artist, :album, :url, :mpd_id, :queue_pos
|
4
|
-
|
5
|
-
class << self
|
6
|
-
attr_accessor :store
|
7
|
-
end
|
8
|
-
self.store = []
|
9
|
-
|
10
|
-
def initialize(params)
|
11
|
-
@name = params[:name]
|
12
|
-
@artist = params[:artist]
|
13
|
-
@album = params[:album]
|
14
|
-
@url = params[:url]
|
15
|
-
|
16
|
-
Track.store.push self
|
17
|
-
end
|
18
|
-
|
19
|
-
def playing?
|
20
|
-
if self == Mumbletune.player.current_song
|
21
|
-
true
|
22
|
-
else
|
23
|
-
false
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.retreive_from_mpd_id(id)
|
28
|
-
Track.store.select { |t| t.mpd_id == id }.first
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|