Class: SubsonicAPI
- Inherits:
-
Object
- Object
- SubsonicAPI
- Defined in:
- lib/subcl/subsonic_api.rb
Constant Summary collapse
- REQUIRED_SETTINGS =
%i{server username password}
Instance Method Summary collapse
-
#add_basic_auth(uri) ⇒ Object
adds the basic auth parameters from the config to the URI.
-
#album_songs(id) ⇒ Object
returns an array of songs for the given album id.
-
#albumart_url(streamUrl, size = nil) ⇒ Object
returns the albumart URL for the song.
-
#albumlist(type = :random) ⇒ Object
returns a list of albums from the specified type www.subsonic.org/pages/api.jsp#getAlbumList2.
-
#all_playlists ⇒ Object
returns all playlists.
-
#artist_songs(id) ⇒ Object
returns an array of songs for the given artist id.
- #build_url(method, params) ⇒ Object
-
#decorate_song(attributes) ⇒ Object
takes the attributes of a song tag from the xml and applies the :type and :stream_url attribute.
-
#get_playlists(name) ⇒ Object
returns all playlists matching name subsonic features no mechanism to search by playlist name, so this method retrieves all playlists and and filters them locally.
-
#get_songs(entities) ⇒ Object
takes a list of albums or artists and returns a list of their songs.
-
#initialize(configs) ⇒ SubsonicAPI
constructor
A new instance of SubsonicAPI.
-
#playlist_songs(id) ⇒ Object
returns all songs from playlist(s) matching the name.
- #query(method, params = {}) ⇒ Object
- #random_songs(count = ) ⇒ Object
- #search(query, type) ⇒ Object
-
#stream_url(songid) ⇒ Object
returns the streaming URL for the song, including basic auth.
Constructor Details
#initialize(configs) ⇒ SubsonicAPI
Returns a new instance of SubsonicAPI.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/subcl/subsonic_api.rb', line 16 def initialize(configs) @configs = { :appname => 'subcl', :app_version => '0.0.4', :proto_version => '1.9.0', #subsonic API protocol version :max_search_results => 20, :random_song_count => 10 }.merge! configs.to_hash REQUIRED_SETTINGS.each do |setting| unless @configs.key? setting raise "Missing setting '#{setting}'" end end end |
Instance Method Details
#add_basic_auth(uri) ⇒ Object
adds the basic auth parameters from the config to the URI
222 223 224 225 226 |
# File 'lib/subcl/subsonic_api.rb', line 222 def add_basic_auth(uri) uri.user = @configs[:username] uri.password = @configs[:password] return uri end |
#album_songs(id) ⇒ Object
returns an array of songs for the given album id
51 52 53 54 55 56 |
# File 'lib/subcl/subsonic_api.rb', line 51 def album_songs(id) doc = query('getAlbum.view', {:id => id}) doc.elements.collect('subsonic-response/album/song') do |song| decorate_song(song.attributes) end end |
#albumart_url(streamUrl, size = nil) ⇒ Object
returns the albumart URL for the song
236 237 238 239 240 241 242 243 244 |
# File 'lib/subcl/subsonic_api.rb', line 236 def albumart_url(streamUrl, size = nil) raise ArgumentError if streamUrl.empty? id = CGI.parse(URI.parse(streamUrl).query)['id'][0] params = {:id => id}; params[:size] = size unless size.nil? add_basic_auth( build_url('getCoverArt.view', params) ) end |
#albumlist(type = :random) ⇒ Object
returns a list of albums from the specified type www.subsonic.org/pages/api.jsp#getAlbumList2
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/subcl/subsonic_api.rb', line 101 def albumlist(type = :random) #TODO might want to add validation for the type here doc = query('getAlbumList2.view', {:type => type}) doc.elements.collect('subsonic-response/albumList2/album') do |album| album = album.attributes album = Hash[album.collect { |key,val| [key.to_sym, val] }] album[:type] = :album album end end |
#all_playlists ⇒ Object
returns all playlists
75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/subcl/subsonic_api.rb', line 75 def all_playlists doc = query('getPlaylists.view') doc.elements.collect('subsonic-response/playlists/playlist') do |playlist| { :id => playlist.attributes['id'], :name => playlist.attributes['name'], :owner => playlist.attributes['owner'], :type => :playlist } end end |
#artist_songs(id) ⇒ Object
returns an array of songs for the given artist id
59 60 61 62 63 64 |
# File 'lib/subcl/subsonic_api.rb', line 59 def artist_songs(id) doc = query('getArtist.view', {:id => id}) doc.elements.inject('subsonic-response/artist/album', []) do |memo, album| memo += album_songs(album.attributes['id']) end end |
#build_url(method, params) ⇒ Object
213 214 215 216 217 218 219 |
# File 'lib/subcl/subsonic_api.rb', line 213 def build_url(method, params) params[:v] = @configs[:proto_version] params[:c] = @configs[:appname] query = params.collect {|k,v| "#{k}=#{URI.escape(v.to_s)}"}.join('&') URI("#{@configs[:server]}/rest/#{method}?#{query}") end |
#decorate_song(attributes) ⇒ Object
takes the attributes of a song tag from the xml and applies the :type and :stream_url attribute
124 125 126 127 128 129 |
# File 'lib/subcl/subsonic_api.rb', line 124 def decorate_song(attributes) attributes = Hash[attributes.collect {|key, val| [key.to_sym, val]}] attributes[:type] = :song attributes[:stream_url] = stream_url(attributes[:id]) attributes end |
#get_playlists(name) ⇒ Object
returns all playlists matching name subsonic features no mechanism to search by playlist name, so this method retrieves all playlists and and filters them locally. This might become problematic when the server has a huge amount of playlists
92 93 94 95 96 97 |
# File 'lib/subcl/subsonic_api.rb', line 92 def get_playlists(name) name.downcase! all_playlists().select do |playlist| playlist[:name].downcase.include? name end end |
#get_songs(entities) ⇒ Object
takes a list of albums or artists and returns a list of their songs
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/subcl/subsonic_api.rb', line 33 def get_songs(entities) entities.collect_concat do |entity| case entity[:type] when :song entity when :album album_songs(entity[:id]) when :artist artist_songs(entity[:id]) when :playlist playlist_songs(entity[:id]) else raise "Cannot get songs for '#{entity[:type]}'" end end end |
#playlist_songs(id) ⇒ Object
returns all songs from playlist(s) matching the name
67 68 69 70 71 72 |
# File 'lib/subcl/subsonic_api.rb', line 67 def playlist_songs(id) doc = query('getPlaylist.view', {:id => id}) doc.elements.collect('subsonic-response/playlist/entry') do |entry| decorate_song(entry.attributes) end end |
#query(method, params = {}) ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/subcl/subsonic_api.rb', line 179 def query(method, params = {}) uri = build_url(method, params) LOGGER.debug { "query: #{uri} (basic auth sent per HTTP header)" } req = Net::HTTP::Get.new(uri.request_uri) req.basic_auth(@configs[:username], @configs[:password]) res = Net::HTTP.start(uri.hostname, uri.port) do |http| http.request(req) end doc = Document.new(res.body) LOGGER.debug { "response: " + doc.to_s } #handle error response doc.elements.each('subsonic-response/error') do |error| raise SubclError, "#{error.attributes["message"]} (#{error.attributes["code"]})" end #handle http error case res.code when '200' return doc else msg = case res.code when '401' "HTTP 401. Might be an incorrect username/password" else "HTTP #{res.code}" end raise SubclError, msg end end |
#random_songs(count = ) ⇒ Object
112 113 114 115 116 117 118 119 120 |
# File 'lib/subcl/subsonic_api.rb', line 112 def random_songs(count = @configs[:random_song_count]) #throws an exception if its not parseable to an int count = Integer(count) doc = query('getRandomSongs.view', {:size => count}) doc.elements.collect('subsonic-response/randomSongs/song') do |song| decorate_song(song.attributes) end end |
#search(query, type) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/subcl/subsonic_api.rb', line 131 def search(query, type) params = { :query => query, :songCount => 0, :albumCount => 0, :artistCount => 0, } max = @configs[:max_search_results] case type when :artist params[:artistCount] = max when :album params[:albumCount] = max when :song params[:songCount] = max when :playlist return get_playlists(query) when :any #XXX or do we now use max/3 for each? params[:songCount] = max params[:albumCount] = max params[:artistCount] = max #TODO need to search for playlists too! else raise "Cannot search for type '#{type}'" end doc = query('search3.view', params) results = %i{artist album song}.collect_concat do |entity_type| doc.elements.collect("subsonic-response/searchResult3/#{entity_type}") do |entity| entity = Hash[entity.attributes.collect{ |key, val| [key.to_sym, val]}] entity[:type] = entity_type if entity_type == :song entity[:stream_url] = stream_url(entity[:id]) entity[:name] = entity[:title] end entity end end if type == :any results += get_playlists(query) end return results end |
#stream_url(songid) ⇒ Object
returns the streaming URL for the song, including basic auth
229 230 231 232 233 |
# File 'lib/subcl/subsonic_api.rb', line 229 def stream_url(songid) raise ArgumentError, "no songid!" unless songid uri = build_url('stream.view', {:id => songid}) add_basic_auth(uri) end |