2 # Copyright (C) 2008 jerous <jerous@gmail.com>
3 # Copyright (C) 2009 Anton Khirnov <wyskas@gmail.com>
5 # Nephilim is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # Nephilim is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Nephilim. If not, see <http://www.gnu.org/licenses/>.
19 from PyQt4
import QtCore
26 class MPClient(QtCore
.QObject
):
27 """This class offers another layer above pympd, with usefull events."""
32 _logger
= logging
.getLogger('mpclient')
33 _status
= {'volume' : 0, 'repeat' : 0, 'random' : 0,
34 'songid' : 0, 'playlist' : 0, 'playlistlength' : 0,
35 'time' : 0, 'length' : 0, 'xfade' : 0,
36 'updatings_db' : 0,'state' : 'stop', 'single' : 0,
42 _retr_mutex
= QtCore
.QMutex()
45 QtCore
.QObject
.__init
__(self
)
47 self
._cur
_playlist
= []
49 self
._status
= dict(MPClient
._status
)
51 def connect_mpd(self
, host
, port
, password
= None):
52 """Connect to MPD@host:port, optionally using password.
53 Returns True at success, False otherwise."""
55 self
._logger
.info('Connecting to MPD...')
57 self
._logger
.warning('Attempted to connect when already connected.')
61 self
._client
= mpd
.MPDClient()
62 self
._client
.connect(host
, port
)
63 except socket
.error
, e
:
64 self
._logger
.error('Socket error: %s.'%e)
69 self
.password(password
)
71 self
._commands
= self
._retrieve
(self
._client
.commands
)
73 if not self
._check
_command
_ok
('listallinfo'):
74 self
._logger
.error('Don\'t have MPD read permission, diconnecting.')
75 return self
.disconnect_mpd()
78 self
._update
_playlist
()
79 self
._update
_current
_song
()
81 self
.emit(QtCore
.SIGNAL('connected'))
83 self
._timer
_id
= self
.startTimer(500)
84 self
._logger
.info('Successfully connected to MPD.')
86 def disconnect_mpd(self
):
87 """Disconnect from MPD."""
88 self
._logger
.info('Disconnecting from MPD...')
92 self
._client
.disconnect()
93 except (mpd
.ConnectionError
, socket
.error
):
97 self
._logger
.warning('Attempted to disconnect when not connected.')
100 self
.killTimer(self
._timer
_id
)
101 self
._timer
_id
= None
102 self
._status
= dict(MPClient
._status
)
103 self
._cur
_song
= None
105 self
._cur
_playlist
= []
107 self
.emit(QtCore
.SIGNAL('disconnected'))
108 def password(self
, password
):
109 """Use the password to authenticate with MPD."""
110 if not self
._check
_command
_ok
('password'):
113 self
._client
.password(password
)
114 self
._logger
.info('Successfully authenticated')
115 self
._commands
= self
._retrieve
(self
._client
.commands
)
116 except mpd
.CommandError
:
117 self
._logger
.error('Incorrect MPD password.')
118 def is_connected(self
):
119 """Returns True if connected to MPD, False otherwise."""
120 return self
._client
!= None
123 """Get current MPD status."""
126 """Returns the current playlist."""
127 return self
._cur
_playlist
129 """Returns current library."""
131 def current_song(self
):
132 """Returns the current playing song."""
133 return self
._cur
_song
134 def is_playing(self
):
135 """Returns True if MPD is playing, False otherwise."""
136 return self
._status
['state'] == 'play'
138 def update_db(self
, paths
= None):
139 """Starts MPD database update."""
140 if not self
._check
_command
_ok
('update'):
143 return self
._client
.update()
144 self
._client
.command_list_ok_begin()
146 self
._client
.update(path
)
147 self
._client
.command_list_end()
150 """Returns an array of configured MPD audio outputs."""
152 return self
._retrieve
(self
._client
.outputs
)
155 def set_output(self
, output_id
, state
):
156 """Set audio output output_id to state (0/1)."""
157 if not self
._check
_command
_ok
('enableoutput'):
160 self
._client
.enableoutput(output_id
)
162 self
._client
.disableoutput(output_id
)
165 """Get current volume."""
166 return int(self
._status
['volume'])
167 def set_volume(self
, volume
):
168 """Set volume to volume."""
170 return self
._logger
.error('Not connected.')
172 volume
= min(100, max(0, volume
))
173 self
._client
.setvol(volume
)
174 except mpd
.CommandError
, e
:
175 self
._logger
.error('Can\'t set volume: %s.' %('don\'t have control permissions' if 'permission' in str(e
)
176 else 'unknown error'))
178 def urlhandlers(self
):
179 """Returns an array of available url handlers."""
183 return self
._client
.urlhandlers()
185 """Returns a list of supported tags."""
186 if not self
._check
_command
_ok
('tagtypes'):
189 return self
._retrieve
(self
._client
.tagtypes
)
191 """List all currently available MPD commands."""
192 return self
._commands
194 def repeat(self
, val
):
195 """Set repeat playlist to val (True/False)."""
196 if not self
._check
_command
_ok
('repeat'):
198 if isinstance(val
, bool):
199 val
= 1 if val
else 0
200 self
._client
.repeat(val
)
201 def random(self
, val
):
202 """Set random playback to val (True, False)."""
203 if not self
._check
_command
_ok
('random'):
205 if isinstance(val
, bool):
206 val
= 1 if val
else 0
207 self
._client
.random(val
)
208 def crossfade(self
, time
):
209 """Set crossfading between songs."""
210 if not self
._check
_command
_ok
('crossfade'):
212 self
._client
.crossfade(time
)
213 def single(self
, val
):
214 """Set single playback to val (True, False)"""
215 if not self
._check
_command
_ok
('single'):
217 if isinstance(val
, bool):
218 val
= 1 if val
else 0
219 self
._client
.single(val
)
220 def consume(self
, val
):
221 """Set consume mode to val (True, False)"""
222 if not self
._check
_command
_ok
('consume'):
224 if isinstance(val
, bool):
225 val
= 1 if val
else 0
226 self
._client
.consume(val
)
228 def play(self
, id = None):
229 """Play song with ID id or next song if id is None."""
230 if not self
._check
_command
_ok
('play'):
233 self
._client
.playid(id)
235 self
._client
.playid()
238 if not self
._check
_command
_ok
('pause'):
240 self
._client
.pause(1)
242 """Resume playing."""
243 if not self
._check
_command
_ok
('pause'):
245 self
._client
.pause(0)
247 """Move on to the next song in the playlist."""
248 if not self
._check
_command
_ok
('next'):
252 """Move back to the previous song in the playlist."""
253 if not self
._check
_command
_ok
('previous'):
255 self
._client
.previous()
258 if not self
._check
_command
_ok
('stop'):
261 def seek(self
, time
):
262 """Seek to time (in seconds)."""
263 if not self
._check
_command
_ok
('seekid'):
265 if self
._status
['songid'] > 0:
266 self
._client
.seekid(self
._status
['songid'], time
)
268 def delete(self
, list):
269 """Remove all song IDs in list from the playlist."""
270 if not self
._check
_command
_ok
('deleteid'):
272 self
._client
.command_list_ok_begin()
274 self
._client
.deleteid(id)
275 self
._client
.command_list_end()
277 """Clear current playlist."""
278 if not self
._check
_command
_ok
('clear'):
281 def add(self
, paths
):
282 """Add all files in paths to the current playlist."""
283 if not self
._check
_command
_ok
('addid'):
285 self
._client
.command_list_ok_begin()
287 self
._client
.addid(path
.encode('utf-8'))
288 ret
= self
._client
.command_list_end()
289 self
._update
_playlist
()
290 if self
._status
['state'] == 'stop':
292 def move(self
, source
, target
):
293 """Move the songs in playlist. Takes a list of source ids and one target position."""
294 if not self
._check
_command
_ok
('moveid'):
296 self
._client
.command_list_ok_begin()
299 self
._client
.moveid(id, target
+ i
)
301 self
._client
.command_list_end()
303 def _retrieve(self
, method
):
304 """Makes sure only one call is made at a time to MPD."""
305 self
._retr
_mutex
.lock()
309 self
._logger
.error('Connection to MPD broken.')
310 self
._retr
_mutex
.unlock()
311 self
.disconnect_mpd()
314 self
._retr
_mutex
.unlock()
316 def _update_lib(self
):
317 """Update the cached library."""
318 self
._cur
_lib
= self
._array
_to
_song
_array
(self
._retrieve
(self
._client
.listallinfo
))
320 for song
in self
._cur
_lib
:
321 song
._data
['id'] = id
323 def _update_playlist(self
):
324 """Update the cached playlist."""
325 self
._cur
_playlist
= self
._array
_to
_song
_array
(self
._retrieve
(self
._client
.playlistinfo
))
326 def _array_to_song_array(self
, array
):
327 """Convert an array to an array of Songs."""
328 return map(lambda entry
: Song(entry
)
329 , filter(lambda entry
: not('directory' in entry
), array
)
331 def _update_current_song(self
):
332 """Update the current song."""
333 song
= self
._retrieve
(self
._client
.currentsong
)
335 self
._cur
_song
= None
337 self
._cur
_song
= Song(song
)
338 def _update_status(self
):
339 """Get current status"""
342 ret
= self
._retrieve
(self
._client
.status
)
346 ret
['repeat'] = int(ret
['repeat'])
347 ret
['random'] = int(ret
['random'])
348 ret
['single'] = int(ret
['single'])
349 ret
['consume'] = int(ret
['consume'])
351 cur
, len = ret
['time'].split(':')
352 ret
['length'] = int(len)
353 ret
['time'] = int(cur
)
358 if not 'updatings_db' in ret
:
359 ret
['updatings_db'] = 0
360 if not 'songid' in ret
:
364 def _check_command_ok(self
, cmd
):
366 return self
._logger
.error('Not connected.')
367 if not cmd
in self
._commands
:
368 return self
._logger
.error('Command %s not accessible'%cmd
)
371 def timerEvent(self
, event
):
372 """Check for changes since last check."""
373 old_status
= self
._status
374 self
._status
= self
._update
_status
()
377 return self
.disconnect_mpd()
379 self
._update
_current
_song
()
381 if self
._status
['songid'] != old_status
['songid']:
382 self
.emit(QtCore
.SIGNAL('song_changed'), self
._status
['songid'])
384 if self
._status
['time'] != old_status
['time']:
385 self
.emit(QtCore
.SIGNAL('time_changed'), self
._status
['time'])
387 if self
._status
['state'] != old_status
['state']:
388 self
.emit(QtCore
.SIGNAL('state_changed'), self
._status
['state'])
390 if self
._status
['volume'] != old_status
['volume']:
391 self
.emit(QtCore
.SIGNAL('volume_changed'), int(self
._status
['volume']))
393 if self
._status
['repeat'] != old_status
['repeat']:
394 self
.emit(QtCore
.SIGNAL('repeat_changed'), bool(self
._status
['repeat']))
396 if self
._status
['random'] != old_status
['random']:
397 self
.emit(QtCore
.SIGNAL('random_changed'), bool(self
._status
['random']))
399 if self
._status
['single'] != old_status
['single']:
400 self
.emit(QtCore
.SIGNAL('single_changed'), bool(self
._status
['single']))
402 if self
._status
['consume'] != old_status
['consume']:
403 self
.emit(QtCore
.SIGNAL('consume_changed'), bool(self
._status
['consume']))
405 if self
._status
['playlist'] != old_status
['playlist']:
406 self
._update
_playlist
()
407 self
.emit(QtCore
.SIGNAL('playlist_changed'))
409 if self
._status
['updatings_db'] and not old_status
['updatings_db']:
410 self
.emit(QtCore
.SIGNAL('update_started'))
411 if not self
._status
['updatings_db'] and old_status
['updatings_db']:
413 self
.emit(QtCore
.SIGNAL('update_finished'))