Giter VIP home page Giter VIP logo

ruby-mpd's Introduction

ruby-mpd

Build Status Code Climate

ruby-mpd is a powerful object-oriented client for the Music Player Daemon, forked from librmpd. librmpd is as of writing outdated by 6 years! This library tries to act as a successor, originally using librmpd as a base, however almost all of the codebase was rewritten. ruby-mpd supports all "modern" MPD features as well as callbacks.

MPD Protocol

The Music Player Daemon protocol is implemented inside the library. The implementation brings the entire set of features to ruby, with support of the newest protocol commands. However some commands were remapped, some were converted to objects, as I felt they fit this way much more into ruby and are more intuitive.

Usage

Add to your Gemfile:

gem 'ruby-mpd'

Then bunde install, require and make a new MPD instance:

require 'ruby-mpd'
mpd = MPD.new 'localhost', 6600

You can also omit the host and port, and it will use the defaults.

mpd = MPD.new 'localhost'
mpd = MPD.new

Once you have an instance of the MPD class, connect to the server.

mpd.connect

When you are done, disconnect by calling disconnect.

mpd.disconnect

Note: In the past, one had to tackle the issue of the server possibly disconnecting the client at any time due to inactivity. Since 0.3.0, this is handled automatically via a reconnect mechanism.

Once connected, you can issue commands to talk to the server.

mpd.connect
mpd.play if mpd.stopped?
song = mpd.current_song
puts "Current Song: #{song.artist} - #{song.title}"

Command documentation can be found here.

Commands

Some commands require URI paths. ruby-mpd allows you to use MPD::Song objects directly and it extracts the file paths behind the scenes.

song = mpd.songs_by_artist('Elvis Presley').first # => MPD::Song
mpd.add song

Options

Some commands accept "option hashes" besides their default values. For example, #move accepts an ID key instead of the position:

mpd.move(1, 10) # => move first song to position 10.
mpd.move({:id => 1}, 10) # => move the song with the ID of 1 to position 10.

Commands that accept ID's: #move, #delete, #play, #song_priority. #seek accepts both :pos and :id. Note: #swap and #swapid are still separate!

Ranges

Some commands also allow ranges instead of numbers, specifying a range of songs. ruby-mpd correctly handles inclusive and exclusive ranges (1..10 vs 1...10). Negative range end means that we want the range to span until the end of the list.

For example, #queue allows us to return only a subset of the queue:

mpd.queue.count # => 20
mpd.queue(1..10).count # => 10
mpd.queue(5..-1).count # => 15 (from 5 to the end of the range)
mpd.queue(5...-1).count # => 15 (does the same)

Move also allows specifying ranges to move a range of songs instead of just one.

mpd.move 1, 10 # => move song 1 to position 10.
mpd.move 1..3, 10 # => move songs 1, 2 and 3 to position 10 (and 11 and 12).

Commands that support ranges: #delete, #move, #queue, #song_priority, #shuffle, MPD::Playlist#load.

Searching

The MPD protocol supports two commands find and search, where find is strict and will be case sensitive, as well as return only full matches, while search is "loose" -- case insensitive and allow partial matches.

For ease of use, ruby-mpd encapsulates both find and search in one method, MPD#where.

Searching is case loose by default, meaning it is case insensitive, and will do partial matching. To enable strict matching, enable the strict option.

This does not work for Playlist#searchadd.

mpd.where({artist: 'MyArtiSt'}, {strict: true})

Multiple query parameters can also be used:

mpd.where(artist: 'Bonobo', album: 'Black Sands')

Query keys can be any of of the tags supported by MPD (a list can be fetched via MPD#tags), or one of the two special parameters: :file to search by full path (relative to database root), and :any to match against all available tags.

While searching, one can also enable the add option, which will automatically add the songs the query returned to the queue. In that case, the response will only return true, stating that the operation was successful (instead of returning an array).

mpd.where({artist: 'MyArtiSt'}, {strict: true, add: true})

Queue searching

Queue searching works the same way (except by using MPD#queue_where), and it also accepts multiple search parameters (which seems to be undocumented in the MPD protocol specification).

Same as #where, it is "loose" by default, and it supports a :strict option.

mpd.queue_where(artist: 'James Brown', genre: 'Funk')
mpd.queue_where({artist: 'James Brown', genre: 'Funk'}, {strict: true})

Playlists

Playlists are one of the objects that map the MPD commands onto a simple to use object. Instead of going trough all those function calls, passing data along to get your results, you simply use the object in an object-oriented way:

mpd.playlists # => [MPD::Playlist, MPD::Playlist...]
playlist = mpd.playlists.first
p playlist.name # => "My playlist"
playlist.songs # => [MPD::Song, MPD::Song...]
playlist.rename('Awesomelist')
p playlist.name # => "Awesomelist"
playlist.add('awesome_track.mp3')

To create a new playlist, simply create a new object. The playlist will be created in the daemon's library automatically as soon as you use #add or #searchadd. There is also no save method, as playlists get 'saved' by the daemon any time you do an action on them (add, delete, rename).

MPD::Playlist.new(mpd, 'name')

Currently, one also has to pass in the MPD instance, as playlists are tied to a certain connection.

Callbacks

Callbacks are a simple way to make your client respond to events, rather that have to continuously ask the server for updates. This allows you to focus on displaying the data, rather that working overly hard to get it. This is done by having a background thread continuously check the server for changes.

To make use of callbacks, we need to:

  1. Setup a callback to be called when something happens.
  2. Create a MPD client instance with callbacks enabled.

Firstly, we need to create a callback block and subscribe it, so that will get triggered whenever a specific event happens. When the callback is triggered, it will also recieve the new values of the event that happened.

So how do we do this? We use the MPD#on method, which sets it all up for us. The argument takes a symbol with the name of the event. The function also requires a block, which is our actual callback that will get called.

mpd.on :volume do |volume|
  puts "Volume was set to #{volume}!"
end

One can also use separate methods or Procs and whatnot, just pass them in as a parameter.

# Using a Proc
proc = Proc.new { |volume| puts "Volume was set to #{volume}." }
mpd.on :volume, &proc

# Using a method
def volume_change(value)
  puts "Volume changed to #{value}."
end

handler = method(:volume_change)
mpd.on :volume, &handler

ruby-mpd supports callbacks for any of the keys returned by MPD#status, as well as :connection. Here's the full list of events, along with the variables it will return:

  • volume: The volume level as an Integer between 0-100.

  • repeat: true or false

  • random: true or false

  • single: true or false

  • consume: true or false

  • playlist: 31-bit unsigned Integer, the playlist version number.

  • playlistlength: Integer, the length of the playlist

  • state: :play, :stop, or :pause, state of the playback.

  • song: An MPD::Song object, representing the current song.

  • songid: playlist songid of the current song stopped on or playing.

  • nextsong: playlist song number of the next song to be played.

  • nextsongid: playlist songid of the next song to be played.

  • time: Returns two integers, elapsed and total, Integers representing seconds.

  • elapsed: Float, representing total time elapsed within the current song, but with higher accuracy.

  • bitrate: instantaneous bitrate in kbps.

  • xfade: crossfade in seconds

  • mixrampdb: mixramp threshold in dB (Float)

  • mixrampdelay: mixrampdelay in seconds

  • audio: Returns three variables: sampleRate, bits and channels.

  • updating_db: job id

  • error: if there is an error, returns message here

  • connection: Are we connected to the daemon? true or false

Note that if the callback returns more than one value, the callback needs more arguments in order to recieve those values:

mpd.on :audio do |sampleRate, bits, channels|
  puts bits
end

# or
mpd.on :audio do |*args|
  puts args.join(',')
end

Finally, the easiest step. In order for callbacks to work, create a MPD instance with callbacks enabled:

MPD.new 'localhost', 6600, { callbacks: true }

Easy as pie. The above will connect to the server like normal, but this time it will create a new thread that loops until you issue a disconnect. This loop checks the server, then sleeps for two tenths of a second, then loops.

Not yet implemented

This section documents the features that are missing in this library at the moment.

Command lists

Command lists are not implemented yet. The proposed API would look like:

mpd.command_list do
  volume 80
  repeat true
  status
end

What makes me not so eager to implement this is that MPD returns all values one after another. This gets fixed with command_list_ok_begin, which returns list_OK for every command used, however then we still get more than one response, and I can't think of a reasonable way to retun all of them back to the user. Maybe just ignore the return values?

Idle

To implement idle, what is needed is a lock that prevents sending commands to the daemon while waiting for the response (except noidle). An intermediate solution would be to queue the commands to send them later, when idle has returned the response.

Idle seems like a possible way to reimplement callbacks; make a separate connection and just use idle and when it returns, simply use idle again and again.

Tests

There is the beginings of a test suite, that can be ran with:

$ bin/rspec spec/

The entire MPD server mock class either needs to be rewritten, or a mpd.conf along with a sample database and instructions for a controlled environment needs to be written.

TODO list

  • MPD::Song, MPD::Directory.
  • Make stickers a mixin for Playlist, Song, Directory
  • Namespace queue
  • Merge where and queue_where, by doing where(..., { in_queue: true})?

ruby-mpd's People

Contributors

alexanderk23 avatar attilagyorffy avatar denialadams avatar evol avatar federicobond avatar ingobecker avatar janpieper avatar jasperla avatar mikerodrigues avatar nex3 avatar phrogz avatar s-mage avatar sokkalf avatar wbrbr avatar whomwah avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ruby-mpd's Issues

Song callback event doesn't happen correctly when using radio streams.

When using a song callback as such:

@mpd.on :song do |current|
  puts current.inspect
end

it only works when a new file starts to play, which is a problem with radio streams, for two reasons.

  • It takes a second or two to get the metadata from the stream, and so when you first add the stream the callback happens, but it generates something like this:
#<MPD::Song:0x00000002a30c58 @data={:pos=>0, :id=>703}, @time=nil, @file="http://streamer.strobe.fm/", @title=nil, @artist=nil, @album=nil, @albumartist=nil>

But, it would generate something like this if the callback detected the metadata changing:

#<MPD::Song:0x000000019a9628 @data={:name=>"Strobe.FM", :pos=>0, :id=>703}, @time=nil, @file="http://streamer.strobe.fm/", @title="Goo Goo Dolls - Caught In The Storm", @artist=nil, @album=nil, @albumartist=nil>
  • Whenever the song changes in the stream, it doesn't go through the code again, and so the metadata that was generated when the stream was first added stays that way until it's changed to a different file.

The only thing I can think of is that the callback checks if the id changed, (notice the id is 703 in both of those arrays, because it's still the same stream) but it doesn't change when using radio streams. I guess the best way to check this would be to check if anything in the array changed.

Obviously my diagnosis could be entirely wrong, I'm not that good with ruby, but that's my theory.

mpd.playlists[index].songs breaks when playlist contains non-existent file

If a playlist has an internet radio or an erroneous file the .songs method throws the exception:

TypeError: no implicit conversion of Symbol into String

I would expect the the method to return a list of song objects as stated in the playlist. Perhaps some checks could be made for interet radio. Or perhaps return nil for those songs which do not exist. Or worst case provide an error indicating which playlist and file produce the error. In my case, the playlist has two good files and just one bad, and it took me a while to track down the problem.

To confirm just echo some erronous fileames or an internet radio url into an .m3u file in mpd's playlist directory and parse it with ruby-mpd. Heres the trace:

from ...gem/ruby/2.0.0/gems/ruby-mpd-0.2.2/lib/ruby-mpd/song.rb:12:in `initialize'
from ...gem/ruby/2.0.0/gems/ruby-mpd-0.2.2/lib/ruby-mpd/playlist.rb:25:in `new'
from ...gem/ruby/2.0.0/gems/ruby-mpd-0.2.2/lib/ruby-mpd/playlist.rb:25:in `block in songs'
from ...gem/ruby/2.0.0/gems/ruby-mpd-0.2.2/lib/ruby-mpd/playlist.rb:25:in `map'
from ...gem/ruby/2.0.0/gems/ruby-mpd-0.2.2/lib/ruby-mpd/playlist.rb:25:in `songs'

Correct way of handling timeouts?

As the README.md states, version 0.3.0 should introduce some kind of 'reconnect mechanism'. As far as is can see, there is no such mechanism. If the server decides to time out the connection of a ruby-mpd client object, #connected? doesn't report the timed out connection as well as #ping and any call that requires a working connection returns true or throws different exceptions as calls to the sockets #gets function return nil. The only way to detect, that the server closed the connection due to a timeout would be to check #eof? on the socket.
At the moment i'm working on an implementation of the #callback_thread that uses a blocking #gets version of the mpd idle command and as a result of my implementation, not even having callbacks enabled prevents the connection from timing out as there are no frequent polling any more. That is why i stumbled about it.
Am i missing something or is this the intended way the ruby-mpd gem should work?

song_with_id( nil ) causes error

A silly edge case, but:

begin; @mpd.song_with_id(1); rescue MPD::Error=>e; puts e; end
#=> [playlistid] No such song

begin; @mpd.song_with_id(-1); rescue MPD::Error=>e; puts e; end
#=> [playlistid] Number too large: -1

begin; @mpd.song_with_id('cat'); rescue MPD::Error=>e; puts e; end
#=> [playlistid] Integer expected: cat

begin; @mpd.song_with_id(nil); rescue MPD::Error=>e; puts e; end
NoMethodError: undefined method `delete' for true:TrueClass
        from /var/lib/gems/2.1.0/gems/ruby-mpd-0.3.3/lib/ruby-mpd/song.rb:13:in `initialize'
        from /var/lib/gems/2.1.0/gems/ruby-mpd-0.3.3/lib/ruby-mpd/plugins/queue.rb:81:in `new'
        from /var/lib/gems/2.1.0/gems/ruby-mpd-0.3.3/lib/ruby-mpd/plugins/queue.rb:81:in `song_with_id'
        ...

track_length errors when a song has no time

A Song may have @time==nil if the song is not valid. This may occur if a playlist has songs added to it that have then been removed from the queue (or if a playlist file is hand-edited to add broken entries).

When this is the case, asking for song.track_length errors, because the implementation is just @time.last.

No event generated when 'updating_db' disappears from 'status'

Ideally, if I

@mpd.on :updating_db do |job_id|
  puts "Updating DB #{job_id.inspect}"
end
@mpd.update

I should get something like

Updating DB 1
Updating DB nil

so that I can tell when the update has ended, but that doesn't happen at all. Instead the callback is only executed when updating_db appears.

Here's the relevant status change:

{:volume=>96, :repeat=>false, :random=>false, :single=>false, :consume=>false, :playlist=>5002, :playlistlength=>4999, :mixrampdb=>0.0, :state=>:play, :song=>0, :songid=>5000, :time=>[5, 244], :elapsed=>4.574, :bitrate=>1202, :audio=>[44100, 16, 2], :updating_db=>1, :nextsong=>1, :nextsongid=>5001}
{:volume=>96, :repeat=>false, :random=>false, :single=>false, :consume=>false, :playlist=>5002, :playlistlength=>4999, :mixrampdb=>0.0, :state=>:play, :song=>0, :songid=>5000, :time=>[6, 244], :elapsed=>5.619, :bitrate=>1224, :audio=>[44100, 16, 2], :nextsong=>1, :nextsongid=>5001}

As you can see, updating_db simply disappears when the update has completed, which is fine, but I should also get an event.

Error when attempting to play stream

When using the add command (in a queue) with a valid stream URL (both m3u or mp3), this error is shown : [] No database .
How can I fix that ?

extraneous data leading to inconsistencies in MPD#songs

I'm working on Dockerizing an application that uses ruby-mpd. I include a small set of Creative Commons mp3s as a kind of "database seed". I noticed that @mpd.songs.count was always 1 greater than the actual count of my library.

using some Dirty Ruby Tricks(tm), I've traced it as follows:

[1] pry(main)> @mpd = MPD.new ENV['MPD_HOST'], ENV['MPD_PORT']    
=> #<MPD:0x0000000584e6d0
 @callbacks={},
 @hostname="127.0.0.1",
 @mutex=#<Mutex:0x0000000584e590>,
 @options={:callbacks=>false},
 @password=nil,
 @port="6600",
 @socket=nil,
 @tags=nil,
 @version=nil>
[2] pry(main)> @mpd.connect    
=> true
[3] pry(main)> unless ENV['MPD_PASS'].nil?    
[3] pry(main)*   @mpd.password ENV['MPD_PASS']      
[3] pry(main)* end      
=> nil
[4] pry(main)> 
[5] pry(main)> 
[6] pry(main)> 
[7] pry(main)> 
[8] pry(main)> @mpd.send('socket').puts 'listallinfo'
=> nil
[9] pry(main)> response = @mpd.send('handle_server_response')
=> "directory: test_library\nLast-Modified: 2015-08-04T20:01:23Z\nfile: test_library/Indian_Summer-212174.mp3\nLast-Modified: 2009-04-04T13:04:47Z\nTime: 286\nArtist: canton\nTitle: Indian Summer\nAlbum: http://www.cantonbecker.com\nfile: test_library/Map_of_the_Cosmos-184608.mp3\nLast-Modified: 2009-03-16T01:37:52Z\nTime: 395\nArtist: canton\nTitle: Map of the Cosmos\nAlbum: http://www.cantonbecker.com\n"
[10] pry(main)> 
[11] pry(main)> response.lines
=> ["directory: test_library\n",
 "Last-Modified: 2015-08-04T20:01:23Z\n",
 "file: test_library/Indian_Summer-212174.mp3\n",
 "Last-Modified: 2009-04-04T13:04:47Z\n",
 "Time: 286\n",
 "Artist: canton\n",
 "Title: Indian Summer\n",
 "Album: http://www.cantonbecker.com\n",
 "file: test_library/Map_of_the_Cosmos-184608.mp3\n",
 "Last-Modified: 2009-03-16T01:37:52Z\n",
 "Time: 395\n",
 "Artist: canton\n",
 "Title: Map of the Cosmos\n",
 "Album: http://www.cantonbecker.com\n"]
[12] pry(main)> response.lines.reject {|line| line =~ /(#{[:directory, :playlist].join('|')}):/i}
=> ["Last-Modified: 2015-08-04T20:01:23Z\n",
 "file: test_library/Indian_Summer-212174.mp3\n",
 "Last-Modified: 2009-04-04T13:04:47Z\n",
 "Time: 286\n",
 "Artist: canton\n",
 "Title: Indian Summer\n",
 "Album: http://www.cantonbecker.com\n",
 "file: test_library/Map_of_the_Cosmos-184608.mp3\n",
 "Last-Modified: 2009-03-16T01:37:52Z\n",
 "Time: 395\n",
 "Artist: canton\n",
 "Title: Map of the Cosmos\n",
 "Album: http://www.cantonbecker.com\n"]

note statements 11 and 12. MPD::Parser#filter_lines removes the "directory" line, but leaves behind the "Last-Modified" line associated with the directory. this later on manifests as a bizarre, inconsistent song list:

[13] pry(main)> @mpd.songs
=> [#<MPD::Song:0x000000049a5930
  @album=nil,
  @albumartist=nil,
  @artist=nil,
  @data={:"last-modified"=>2015-08-04 20:01:23 UTC},
  @file="test_library/Indian_Summer-212174.mp3",
  @mpd=
   #<MPD:0x0000000584e6d0
    @callbacks={},
    @hostname="127.0.0.1",
    @mutex=#<Mutex:0x0000000584e590>,
    @options={:callbacks=>false},
    @password=nil,
    @port="6600",
    @socket=#<TCPSocket:fd 10>,
    @tags=nil,
    @version="0.19.0">,
  @time=nil,
  @title=nil>,
 #<MPD::Song:0x000000049a58b8
  @album="http://www.cantonbecker.com",
  @albumartist=nil,
  @artist="canton",
  @data={:"last-modified"=>2009-04-04 13:04:47 UTC},
  @file="test_library/Map_of_the_Cosmos-184608.mp3",
  @mpd=
   #<MPD:0x0000000584e6d0
    @callbacks={},
    @hostname="127.0.0.1",
    @mutex=#<Mutex:0x0000000584e590>,
    @options={:callbacks=>false},
    @password=nil,
    @port="6600",
    @socket=#<TCPSocket:fd 10>,
    @tags=nil,
    @version="0.19.0">,
  @time=[nil, 286],
  @title="Indian Summer">,
 #<MPD::Song:0x000000049a5868
  @album="http://www.cantonbecker.com",
  @albumartist=nil,
  @artist="canton",
  @data={:"last-modified"=>2009-03-16 01:37:52 UTC},
  @file=nil,
  @mpd=
   #<MPD:0x0000000584e6d0
    @callbacks={},
    @hostname="127.0.0.1",
    @mutex=#<Mutex:0x0000000584e590>,
    @options={:callbacks=>false},
    @password=nil,
    @port="6600",
    @socket=#<TCPSocket:fd 10>,
    @tags=nil,
    @version="0.19.0">,
  @time=[nil, 395],
  @title="Map of the Cosmos">]

3 songs where there should be 2, one with a nil @file variable, another with a nil @time variable.

I have no idea what a fix would/should look like. I think I've spent all of today's brain juices tracing the issue this far.

as an additional exhibit, here's a data structure that has obviously weird behavior in :"last-modified" and :time. I didn't trace it fully, but I assume this data structure will eventually become an array of MPD::Song objects. the mis-matched columns likely causes malformed objects to be created.

[15] pry(main)> @mpd.send('parse_response', 'listallinfo', response)
=> {:directory=>"test_library",
 :"last-modified"=>[23, 1, 20, 4, 8, 2015, 2, 216, false, "UTC", 2009-04-04 13:04:47 UTC, 2009-03-16 01:37:52 UTC],
 :file=>["test_library/Indian_Summer-212174.mp3", "test_library/Map_of_the_Cosmos-184608.mp3"],
 :time=>[nil, 286, [nil, 395]],
 :artist=>["canton", "canton"],
 :title=>["Indian Summer", "Map of the Cosmos"],
 :album=>["http://www.cantonbecker.com", "http://www.cantonbecker.com"]}

Time not correctly updating

I recently into strange behavior regarding the time field and streams. If I add a youtube stream to mpd, mpd seems to first have no idea of the time (mpc reports it as 0:00/0:00) and then as it parses it mpd starts correctly reporting the time. ruby-mpd always reports the time as nil, even when mpc is correctly reporting the time. I am thinking the ruby-mpd might not be getting the latest information? But I'm a bit lost as to where the issue lies. Here is an example of what I am talking about:

Youtube video I have been using for testing (happens to all videos): https://www.youtube.com/watch?v=Q1JxHz_9LBI

I have been obtaining the stream using youtube-dl --prefer-insecure -i -f140 -q --no-warnings -ge https://www.youtube.com/watch?v=Q1JxHz_9LBI via my mumble bot.

Immediately after adding the stream, nothing is playing yet

brick@treef ~/mumblecop (hg)-[default] % mpc
Stanchinsky - Piano Sonata in E-flat minor 
[playing] #1/1   0:00/0:00 (0%)
volume: n/a   repeat: off   random: off   single: off   consume: on \

Stream starts playing through mpd

brick@treef ~/mumblecop (hg)-[default] % mpc
Stanchinsky - Piano Sonata in E-flat minor 
[playing] #1/1   0:17/9:46 (2%)
volume: n/a   repeat: off   random: off   single: off   consume: on 
brick@treef ~/mumblecop (hg)-[default] % irb
irb(main):001:0> require 'ruby-mpd'
=> true
irb(main):002:0> a = MPD.new
=> #<MPD:0x00000002593268 @hostname="localhost", @port=6600, @options={:callbacks=>false}, @password=nil, @socket=nil, @version=nil, @tags=nil, @mutex=#<Mutex:0x00000002593150>, @callbacks={}>
irb(main):003:0> a.connect
=> true
irb(main):004:0> a.current_song
=> #<MPD::Song:0x000000025767f8 @mpd=#<MPD:0x00000002593268 @hostname="localhost", @port=6600, @options={:callbacks=>false}, @password=nil, @socket=#<TCPSocket:fd 9>, @version="0.19.0", @tags=nil, @mutex=#<Mutex:0x00000002593150>, @callbacks={}>, @data={:pos=>0, :id=>35}, @time=nil, @file="http://r9---sn-ab5e6m7e.googlevideo.com/videoplayback?id=4352711f3ffd2c12&itag=140&source=youtube&pl=17&mm=31&mn=sn-ab5e6m7e&mv=m&ms=au&nh=EAI&ratebypass=yes&mime=audio/mp4&gir=yes&clen=9300109&lmt=1390341494585760&dur=585.514&mt=1438116453&sver=3&signature=81FD1414A77EFE2FDCE2CDB9F2E7B6215AE56704.19EB9387CC5F4D701538CEA4779088190B7E8797&key=dg_yt0&upn=5RvJtqAhGo0&fexp=901816,9407150,9407813,9408710,9415365,9415387,9415430,9415485,9416126,9416217,9416324,9417279,9417878&ip=96.252.105.91&ipbits=0&expire=1438138113&sparams=ip,ipbits,expire,id,itag,source,pl,mm,mn,mv,ms,nh,ratebypass,mime,gir,clen,lmt,dur", @title="Stanchinsky - Piano Sonata in E-flat minor ", @artist=nil, @album=nil, @albumartist=nil>

ruby-mpd returns time as nil.

Any thoughts? I will try digging more into the source. I'm guessing that when the song object is initialized the time is nil but when mpd updates it ruby-mpd doesn't read the updated value, but I'm not sure how it works behind the scenes. But on second thought I guess I must be wrong because I have been adding the title after adding the stream and ruby-mpd is correctly reading the song. So... I'll keep looking :)

callback stops firing after a few hours

not sure if this is ruby-mpd related, but i didnt find a clue yet, maybe you can enlighten me

basically i have this code for handling the connection

def self.connect_mpd
    @mpd = MPD.new 'localhost', 6600, {callbacks: true} if @mpd == nil
    unless @mpd.connected?
      Rails.logger.info "Connecting to MPD . . ."
      @mpd.connect
      sleep 0.3
      @mpd.on :song do |song|
        Rails.logger.info "!!!!!!!!!!!!!! #{Time.now.to_s} MPD_SONG change: #{song.title} - #{song.artist} !!!"
      end
    end

  end

this block gets fired about every minute(or even more) and works fine basically

After a few hours(about 6-8) the callback just stops firing, but the connection to mpd is still there, application continuing to work normally. No clues about dropped connection in mpd.log

connection_timeout is set to 3600 in mpd.conf

using ruby-mpd 0.31, MPD 0.18, local only

any tips what could be wrong?

Songs with nil file

Just imported my song library. There are many Songs returned with no file.

[ M.stats[:songs], M.songs.length, M.songs.reject(&:file).length ]
#=> [17103, 22892, 5789]

Some of them are real zombies:

M.songs.reject(&:file).first
#=> #<MPD::Song:0x000000028e18e8 @mpd=…, @data={:"last-modified"=>2015-11-08 07:57:01 UTC}, @time=nil, @file=nil, @title=nil, @artist=nil, @album=nil, @albumartist=nil>

Some of them are files that exist on disk (perhaps the Unicode characters in the path are tricky?):

M.songs.reject(&:file).last
#=> #<MPD::Song:0x00000003704178 @mpd=…, @data={:"last-modified"=>2008-06-01 03:16:00 UTC, :track=>1, :date=>2003, :genre=>"Soundtrack"}, @time=[nil, 160], @file=nil, @title="Bang Bang (My Baby Shot Me Down)", @artist="Nancy Sinatra", @album="Kill Bill: Vol.1", @albumartist="群星">

Dir['**/*'].grep /Bang Bang/
#=> ["群星/Kill Bill_ Vol.1/01 Bang Bang (My Baby Shot Me Down).mp3"]

puts `ls -l "群星/Kill Bill_ Vol.1/"`
#=> total 3744
#=> -rw-r--r-- 1 gkistner gkistner 3833856 May 31  2008 01 Bang Bang (My Baby Shot Me Down).mp3

How do I fix it so that all songs have a file?

Some genres have cryptic ids instead of text

s = M.where(genre:'(123)').first
s.genre         #=> "(123)"
s.file          #=> "Beelzebubs/Foster Street/08 Comfortably Numb.mp3"
phrogz$ eyeD3 "Beelzebubs/Foster Street/08 Comfortably Numb.mp3" | grep genre
track: 8        genre: A Cappella (id 123)

I'd like to get the genre name, not the id, in my Song instances. Is this possible?

License missing from gemspec

RubyGems.org doesn't report a license for your gem. This is because it is not specified in the gemspec of your last release.

via e.g.

spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']

Including a license in your gemspec is an easy way for rubygems.org and other tools to check how your gem is licensed. As you can imagine, scanning your repository for a LICENSE file or parsing the README, and then attempting to identify the license or licenses is much more difficult and more error prone. So, even for projects that already specify a license, including a license in your gemspec is a good practice. See, for example, how rubygems.org uses the gemspec to display the rails gem license.

There is even a License Finder gem to help companies/individuals ensure all gems they use meet their licensing needs. This tool depends on license information being available in the gemspec. This is an important enough issue that even Bundler now generates gems with a default 'MIT' license.

I hope you'll consider specifying a license in your gemspec. If not, please just close the issue with a nice message. In either case, I'll follow up. Thanks for your time!

Appendix:

If you need help choosing a license (sorry, I haven't checked your readme or looked for a license file), GitHub has created a license picker tool. Code without a license specified defaults to 'All rights reserved'-- denying others all rights to use of the code.
Here's a list of the license names I've found and their frequencies

p.s. In case you're wondering how I found you and why I made this issue, it's because I'm collecting stats on gems (I was originally looking for download data) and decided to collect license metadata,too, and make issues for gemspecs not specifying a license as a public service :). See the previous link or my blog post about this project for more information.

Problem adding 1st song to the queue.

Hey,

I wasn't sure if this was a bug or I'm not using the library correctly. I noticed that the first song I add doesn't show up in the queue until I add a 2nd song. I took a screenshot of the irb session so you could see what I'm talking about. Let me know if it's a bug, I wouldn't mind putting in a fix for it if you wanted some help. Also thank you very much for putting this gem together. It's been great fun working with it.

ruby-mpd-irb

Error in make_chunks in parser.rb

I tried using your gem today, and I get the following error when I try to run any commands. Seems that the regex in the make_chunks command returns Nil, and that's causing an error in the match command that exits the entire program.

/home/pi/.rvm/gems/ruby-2.2.1/gems/ruby-mpd-0.3.3/lib/ruby-mpd/parser.rb:129:in `make_chunks': undefined method `[]' for nil:NilClass (NoMethodError)
    from /home/pi/.rvm/gems/ruby-2.2.1/gems/ruby-mpd-0.3.3/lib/ruby-mpd/parser.rb:155:in `build_response'
    from /home/pi/.rvm/gems/ruby-2.2.1/gems/ruby-mpd-0.3.3/lib/ruby-mpd/parser.rb:147:in `parse_response'
    from /home/pi/.rvm/gems/ruby-2.2.1/gems/ruby-mpd-0.3.3/lib/ruby-mpd.rb:195:in `block in send_command'
    from /home/pi/.rvm/gems/ruby-2.2.1/gems/ruby-mpd-0.3.3/lib/ruby-mpd.rb:191:in `synchronize'
    from /home/pi/.rvm/gems/ruby-2.2.1/gems/ruby-mpd-0.3.3/lib/ruby-mpd.rb:191:in `send_command'
    from /home/pi/.rvm/gems/ruby-2.2.1/gems/ruby-mpd-0.3.3/lib/ruby-mpd/plugins/controls.rb:29:in `play'
    from radio.rb:10:in `block in <main>'
    from radio.rb:5:in `loop'
    from radio.rb:5:in `<main>'

Support `idle`

The information.rb plugin has an idle command, but it is currently commented out.

I'd like to be able to watch for changes. As it is I'll have to use background threads shelling out to mpc idle to implement the same functionality.

Song filename with double-quotes causes errors

Though rarely a good idea, it's possible for filenames to have double-quotes in them. In this case, the MPD protocol expects the name to be double-quoted, with double-quotes inside escaped with backslashes. For example, given a song test/"Suddenly".mp3:

# telnet to MPD
add test/"Suddenly".mp3
ACK [2@0] {add} Invalid unquoted character
add "test/"Suddenly".mp3"
ACK [2@0] {add} Space expected after closing '"'
add "test/\"Suddenly\".mp3"
OK

Several spots in the codebase just wrap a uri in quotes without escaping embedded quotes.

Limit search results

I have a DB that's currently at 17k songs (and growing). Some search terms can produce a lot of results, like:

@mpd.where file:'.'
@mpd.where any:'e'

Calls like this take 1.3 seconds on the server before they return. In my interface I then go on to truncate the results to the first 500. I'd like to be able to:

@mpd.where {file:'.'}, {limit:500}

and have the results limited for me.

I realize that this requires MPD support. I've posted a feature request on the MPD forum.

(FWIW, I also tried modifying build_songs_list in parser.rb to accept an optional limit, to reduce the number of songs created in the map. Reducing the map set from 16883 songs to 500 songs only reduced the time from 1.27s to 1.23s, a negligible savings.)

Detect invalid songs

  1. Create a playlist and add a song to it.
  2. Remove that song from the MPD library.
  3. Load the playlist.

The playlist now has a song with a file but no other data. How can I detect if this is a valid song with no metadata, or an invalid song?

I'm currently using the heuristic of checking to see if song.time.nil?. It would be nice to have something like MPD::Song#valid? to see if the song instance is grounded in reality and able to be used.

A bug in SoundCloud playing?

mpd 0.17.0 added some new-ish support for SoundCloud URLs.

In a Ruby script, I can execute

`mpc load soundcloud://track/121108619`

To queue a new URL. With this library, the following does not work:

mpd.load "soundcloud://track/121108619"

The error is:

MPD::ConnectionError (Broken pipe (got disconnected)):

Note that this should be load, not add, because MPD considers these to be playlists.

Add toggle command (mpc like)

mpc like toggle command seems to be unsupported, would be a good addition.
"mpc toggle Toggles Play/Pause, plays if stopped"

Insert at position

mpc has a feature to insert a song at a specific position, is this available here as well?

error in playlist.rb method move

hi, thank you for this gem
i got error when try to move song in playlist
there are not enough params (songid and songpos) in @mpd.send_command

def move(songid, songpos)
- @mpd.send_command :playlistmove, @name
+ @mpd.send_command :playlistmove, @name, songid, songpos
end

Instantiate a playlist by name

My UI uses the name of a playlist as the unique identifier. When I want to fetch a playlist with this name I must currently use one of the following:

playlist = @mpd.playlists.find{ |pl| pl.name==playlistname }
playlist = MPD::Playlist.new( @mpd, playlist:playlistname )

Using find feels gross because it instantiates all the playlists (maybe a great many) when I only need one. Using new feels gross because if I mistakenly ask for a playlist that does not exist it will be created.

I'd prefer to be able to do something like:

playlist = @mpd.playlist( playlistname )

Does that seem reasonable?

.songs returns empty array despite reporting 200k songs

Any idea why I would always get an empty string when using mpd.songs. When using ympd it shows a database, and mpd stats even report back that it has songs. I can do mpd.albums and mpd.artists and it gives the exepcted output but songs is empty. Is it that I have too many songs?

mpd.stats
 => {:uptime=>2930, :playtime=>0, :artists=>2169, :albums=>1772, :songs=>201361, :db_playtime=>29261847, :db_update=>2016-02-01 23:44:27 +0100}

New release?

First of all, thanks for this library. It's been terrific for scripting with.

I just came across the bug in #62, but the last release was back in 2015 (and the fix was in 2016). Is it possible to get a new release pushed to RubyGems so that I (and others) don't have to vendor/monkeypatch this in?

Thanks!

Error adding song with backslash in name

Given the (ugly) situation of a file with a backslash in the name, MPD requires the backslash to be escaped. Right now only double-quotes are backslash-escaped inside the double quoted param.

Protocol updates in mpd v.18

There are a few new commands and features in .18 that aren't implemented:

  • protocol:
    • new command "readcomments" lists arbitrary file tags
    • new command "toggleoutput"
    • "find"/"search" with "any" does not match file name
    • "search" and "find" with base URI (keyword "base")
    • search for album artist falls back to the artist tag
    • re-add the "volume" command

http://git.musicpd.org/cgit/master/mpd.git/plain/NEWS?h=release-0.18

:toggleoutput is pretty straightforward but :readcomments is not. :readcomments returns a hash of additional arbitrary tags on the song file. I'd like to implement it but I wanted to see if you had any comment on how it should be done.

The last 4 items already work.

Adding songs to the queue is slow

On my live music server:

@mpd.add( song )         # takes about 40ms
`mpc add "#{song.file}"` # takes about  5ms

This may seem trivial, but when dynamically generating the queue with 100 songs this is the difference between waiting 4 seconds versus half a second.

I wonder if this is the @mutex.synchronize slowing me down; I'll be looking at ways to speed up the batch adding of many songs.

Crash everytime #where is called with add: true

Whenever I call #where with the add flag true, it crashes with the following error:

/usr/lib/ruby/gems/1.9.1/gems/ruby-mpd-0.3.1/lib/ruby-mpd/parser.rb:112:in `build_songs_list': undefined method `map' for true:TrueClass (NoMethodError)
        from /usr/lib/ruby/gems/1.9.1/gems/ruby-mpd-0.3.1/lib/ruby-mpd/plugins/database.rb:95:in `where'
        from MusicBot.rb:117:in `search'
        from MusicBot.rb:191:in `parse_input'
        from MusicBot.rb:55:in `block in <main>'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:145:in `call'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:145:in `block in run_callbacks'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:145:in `each'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:145:in `run_callbacks'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:136:in `read'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:130:in `block (2 levels) in spawn_thread'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:130:in `loop'
        from /usr/lib/ruby/gems/1.9.1/gems/mumble-ruby-1.0.2/lib/mumble-ruby/client.rb:130:in `block in spawn_thread'

This also happens when I am calling #where from irb, and regardless of the value of the strict option. Also, it should be noted that mpd does indeed add the query into the queue, but ruby-mpd crashes.

By looking at the source, you say that #where should return true whenever the add option is true and it seems like #build_song_list can't handle the return from #send_command.

I think that this is unintentional behavior, as half of the examples that are given on how to use #where use the add option.

I have tested and reproduced the bug on debian squeeze with mpd 0.17.6 and ruby 1.9.3 and on arch with mpd 0.18.9 with ruby 2.1.1

Getting stickers does not parse the values

mpd.set_sticker 'song', song, 'added-by', 'phrogz'
#=> true

mpd.list_stickers 'song', song
#=> "added-by=phrogz"

mpd.get_sticker 'song', song, 'added-by'
#=> "added-by=phrogz"

I would expect list_stickers to return a Hash, e.g. { 'added-by' => 'phrogz' }
I would expect get_sticker to return a String, e.g. "phrogz"

First command causes error

Using MPD 0.19.1 on Debian 8, Ruby 2.1.5p273

require 'ruby-mpd'
mpd = MPD.new
mpd.status # NoMethodError: undefined method `[]' for nil:NilClass from /var/lib/gems/2.1.0/gems/ruby-mpd-0.3.3/lib/ruby-mpd/parser.rb:129:in `make_chunks'
mpd.status # works

Discussion: how to best test complex interactions?

I've put a question on Stack Exchange Code Review:
http://codereview.stackexchange.com/q/120345/3060

In short, I am writing tests for the command_lists functionality and expected results for ugly edge cases. I welcome any opinions on a better way to simulate MPD getting and sending information.

The two changes I plan to make beyond what I put there are:

  1. Instead of one monolithic recording of requests and responses, I'd prefer a more modular approach of smaller recordings that are easier to handle. Maybe one recording file per 'feature', or maybe even (!) one recording file per streak of commands sent. (Where the filename is the SHA-1 hash of the commands sent?)
  2. Instead of impenetrable Marshal files for the recordings, which can't be hand-edited, I want some sort of plain-text syntax. If I have one file per response, the contents of the file can BE the response, one on each line. I like that.

Cannot fetch songs for playlist (TypeError)

Code

require "pp"
require "ruby-mpd"

mpd = MPD.new
mpd.connect

begin
  playlist = MPD::Playlist.new(mpd, "Example")
  playlist.add("http://uk1.internet-radio.com:8106/listen.pls")
  pp playlist.songs
ensure
  mpd.disconnect
end

Output

Files inside Playlist 'Example' do not exist!
[]

I've checked the code and found this implementation of MPD::Playlist#songs:

    # Lists the songs in the playlist. Playlist plugins are supported.
    # @return [Array<MPD::Song>] songs in the playlist.
    def songs
      result = @mpd.send_command(:listplaylistinfo, @name)
      result.map do |hash|
        if hash[:file] && !hash[:file].match(/^(https?:\/\/)?/)[0].empty?
          Song.new(@mpd, {:file => hash[:file], :time => [0]})
        else
          Song.new(@mpd, hash)
        end
      end
    rescue TypeError
      puts "Files inside Playlist '#{@name}' do not exist!"
      return []
    rescue NotFound
      return [] # we rescue in the case the playlist doesn't exist.
    end

The problem is, that result looks like this:

[ "http://uk1.internet-radio.com:8106/listen.pls" ]

But the code requires result to look like this:

[ { :file => "http://uk1.internet-radio.com:8106/listen.pls" } ]

Edit: I am using MPD 0.18.0

Tag releases

Hey Blaz @archseer,

I’ve seen that you’ve incremented the version number in ruby-mpd but and released on Rubygems but in fact the code has not been tagged. Given that I’ve migrated the codebase onto Bundler, I thought I’d share with you what I do, it may be useful:

I use rake release after changing version.rb, this rake task comes from Bundler and it properly tags the codebase, pushes it up and then releases the gem onto Rubygems. So all release stuff can be done using a single command in the terminal.

Hopefully this will help.

— Attila

Add a function to temporarily disable callbacks

When I'm adding a lot of songs one by one (which as far as I can see can't be done any other way), the :playlist callback gets fired multiple times, which is correct. But it would be nice to be able to temporarily disable callbacks for potentially long operations, like this:

class MPD
  def no_callbacks(&block)
    # pause callback thread (or stop it)
    yield
    # resume callback thread (or restart it)
  end
end

mpd.no_callbacks do
  # add many songs here
end
# :playlist callback gets fired here

This way, my client would get the updated playlist when it's finished and wouldn't have to process multiple incomplete updates. At the end of the provided block, the callback thread would resume as usual and emit all changed keys. This requires that the callback cannot only be stopped, but also resumed (or stopped and then restarted).

Any feedback would be appreciated!

Song with multiple genres causes runtime error

Some of my songs come back from the server with multiple genres applied:

$ telnet 0 6600
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
OK MPD 0.19.0
search file "pull me under"
file: doza/Dream Theater/Images and Words/01 Pull Me Under.mp3
Last-Modified: 2012-11-11T16:44:44Z
Time: 493
Artist: Dream Theater
AlbumArtist: Dream Theater
Title: Pull Me Under
Album: Images and Words
Track: 1
Date: 1992
Genre: Rock
Genre: Rock
Composer: Dream Theater; Kevin Moore

This results in an array in the genre metadata:

song = @mpd.where(file:'pull me under').first
p song.instance_variable_get(:@data)[:genre]
#=> ["Rock", "Rock"]

This causes some code in the new genre resolver to fail when asking for song.genre.

/var/lib/gems/2.1.0/gems/ruby-mpd-0.3.3/lib/ruby-mpd/song.rb:264:in `[]': no implicit conversion of Regexp into Integer (TypeError)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.