# Source code for powerline.segments.common.players

    # vim:fileencoding=utf-8:noet
    from __future__ import (unicode_literals, division, absolute_import, print_function)

    import sys
    import re

    from powerline.lib.shell import asrun, run_cmd
    from powerline.lib.unicode import out_u
    from powerline.segments import Segment, with_docstring

    STATE_SYMBOLS = {
    	'fallback': '',
    	'play': '>',
    	'pause': '~',
    	'stop': 'X',
    }

    def _convert_state(state):
    	'''Guess player state'''
    	state = state.lower()
    	if 'play' in state:
    		return 'play'
    	if 'pause' in state:
    		return 'pause'
    	if 'stop' in state:
    		return 'stop'
    	return 'fallback'

    def _convert_seconds(seconds):
    	'''Convert seconds to minutes:seconds format'''
    	if isinstance(seconds, str):
    	        seconds = seconds.replace(",",".")
    	return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60))

    class PlayerSegment(Segment):
    	def __call__(self, format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs):
    		stats = {
    			'state': 'fallback',
    			'album': None,
    			'artist': None,
    			'title': None,
    			'elapsed': None,
    			'total': None,
    		}
    		func_stats = self.get_player_status(**kwargs)
    		if not func_stats:
    			return None
    		stats.update(func_stats)
    		stats['state_symbol'] = state_symbols.get(stats['state'])
    		return [{
    			'contents': format.format(**stats),
    			'highlight_groups': ['player_' + (stats['state'] or 'fallback'), 'player'],
    		}]

    	def get_player_status(self, pl):
    		pass

    	def argspecobjs(self):
    		for ret in super(PlayerSegment, self).argspecobjs():
    			yield ret
    		yield 'get_player_status', self.get_player_status

    	def omitted_args(self, name, method):
    		return ()

    _common_args = '''
    This player segment should be added like this:

    .. code-block:: json

    	{{
    		"function": "powerline.segments.common.players.{0}",
    		"name": "player"
    	}}

    (with additional ``"args": {{…}}`` if needed).

    Highlight groups used: ``player_fallback`` or ``player``, ``player_play`` or ``player``, ``player_pause`` or ``player``, ``player_stop`` or ``player``.

    :param str format:
    	Format used for displaying data from player. Should be a str.format-like
    	string with the following keyword parameters:

    	+------------+-------------------------------------------------------------+
    	|Parameter   |Description                                                  |
    	+============+=============================================================+
    	|state_symbol|Symbol displayed for play/pause/stop states. There is also   |
    	|            |“fallback” state used in case function failed to get player  |
    	|            |state. For this state symbol is by default empty. All        |
    	|            |symbols are defined in ``state_symbols`` argument.           |
    	+------------+-------------------------------------------------------------+
    	|album       |Album that is currently played.                              |
    	+------------+-------------------------------------------------------------+
    	|artist      |Artist whose song is currently played                        |
    	+------------+-------------------------------------------------------------+
    	|title       |Currently played composition.                                |
    	+------------+-------------------------------------------------------------+
    	|elapsed     |Composition duration in format M:SS (minutes:seconds).       |
    	+------------+-------------------------------------------------------------+
    	|total       |Composition length in format M:SS.                           |
    	+------------+-------------------------------------------------------------+
    :param dict state_symbols:
    	Symbols used for displaying state. Must contain all of the following keys:

    	========  ========================================================
    	Key       Description
    	========  ========================================================
    	play      Displayed when player is playing.
    	pause     Displayed when player is paused.
    	stop      Displayed when player is not playing anything.
    	fallback  Displayed if state is not one of the above or not known.
    	========  ========================================================
    '''

    _player = with_docstring(PlayerSegment(), _common_args.format('_player'))

    class CmusPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		'''Return cmus player information.

    		cmus-remote -Q returns data with multi-level information i.e.
    			status playing
    			file <file_name>
    			tag artist <artist_name>
    			tag title <track_title>
    			tag ..
    			tag n
    			set continue <true|false>
    			set repeat <true|false>
    			set ..
    			set n

    		For the information we are looking for we don’t really care if we’re on
    		the tag level or the set level. The dictionary comprehension in this
    		method takes anything in ignore_levels and brings the key inside that
    		to the first level of the dictionary.
    		'''
    		now_playing_str = run_cmd(pl, ['cmus-remote', '-Q'])
    		if not now_playing_str:
    			return
    		ignore_levels = ('tag', 'set',)
    		now_playing = dict(((token[0] if token[0] not in ignore_levels else token[1],
    			(' '.join(token[1:]) if token[0] not in ignore_levels else
    			' '.join(token[2:]))) for token in [line.split(' ') for line in now_playing_str.split('\n')[:-1]]))
    		state = _convert_state(now_playing.get('status'))
    		return {
    			'state': state,
    			'album': now_playing.get('album'),
    			'artist': now_playing.get('artist'),
    			'title': now_playing.get('title'),
    			'elapsed': _convert_seconds(now_playing.get('position', 0)),
    			'total': _convert_seconds(now_playing.get('duration', 0)),
    		}

    cmus = with_docstring(CmusPlayerSegment(),
    ('''Return CMUS player information

    Requires cmus-remote command be accessible from $PATH.

    {0}
    ''').format(_common_args.format('cmus')))

    class MpdPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl, host='localhost', password=None, port=6600):
    		try:
    			import mpd
    		except ImportError:
    			if password:
    				host = password + '@' + host
    			now_playing = run_cmd(pl, [
    				'mpc',
    				'-h', host,
    				'-p', str(port)
    			], strip=False)
    			album = run_cmd(pl, [
    				'mpc', 'current',
    				'-f', '%album%',
    				'-h', host,
    				'-p', str(port)
    			])
    			if not now_playing or now_playing.count("\n") != 3:
    				return
    			now_playing = re.match(
    				r"(.*) - (.*)\n\[([a-z]+)\] +[#0-9\/]+ +([0-9\:]+)\/([0-9\:]+)",
    				now_playing
    			)
    			return {
    				'state': _convert_state(now_playing[3]),
    				'album': album,
    				'artist': now_playing[1],
    				'title': now_playing[2],
    				'elapsed': now_playing[4],
    				'total': now_playing[5]
    			}
    		else:
    			try:
    				client = mpd.MPDClient(use_unicode=True)
    			except TypeError:
    				# python-mpd 1.x does not support use_unicode
    				client = mpd.MPDClient()
    			client.connect(host, port)
    			if password:
    				client.password(password)
    			now_playing = client.currentsong()
    			if not now_playing:
    				return
    			status = client.status()
    			client.close()
    			client.disconnect()
    			return {
    				'state': status.get('state'),
    				'album': now_playing.get('album'),
    				'artist': now_playing.get('artist'),
    				'title': now_playing.get('title'),
    				'elapsed': _convert_seconds(status.get('elapsed', 0)),
    				'total': _convert_seconds(now_playing.get('time', 0)),
    			}

    mpd = with_docstring(MpdPlayerSegment(),
    ('''Return Music Player Daemon information

    Requires ``mpd`` Python module (e.g. |python-mpd2|_ or |python-mpd|_ Python
    package) or alternatively the ``mpc`` command to be accessible from $PATH.

    .. |python-mpd| replace:: ``python-mpd``
    .. _python-mpd: https://pypi.python.org/pypi/python-mpd

    .. |python-mpd2| replace:: ``python-mpd2``
    .. _python-mpd2: https://pypi.python.org/pypi/python-mpd2

    {0}
    :param str host:
    	Host on which mpd runs.
    :param str password:
    	Password used for connecting to daemon.
    :param int port:
    	Port which should be connected to.
    ''').format(_common_args.format('mpd')))

    try:
    	import dbus
    except ImportError:
    	def _get_dbus_player_status(pl, player_name, **kwargs):
    		pl.error('Could not add {0} segment: requires dbus module', player_name)
    		return
    else:
    	def _get_dbus_player_status(pl,
    				bus_name=None,
    				iface_prop='org.freedesktop.DBus.Properties',
    				iface_player='org.mpris.MediaPlayer2.Player',
    				player_path='/org/mpris/MediaPlayer2',
    				player_name='player'):
    		bus = dbus.SessionBus()

    		if bus_name is None:
    			for service in bus.list_names():
    				if re.match('org.mpris.MediaPlayer2.', service):
    					bus_name = service
    					break

    		try:
    			player = bus.get_object(bus_name, player_path)
    			iface = dbus.Interface(player, iface_prop)
    			info = iface.Get(iface_player, 'Metadata')
    			status = iface.Get(iface_player, 'PlaybackStatus')
    		except dbus.exceptions.DBusException:
    			return
    		if not info:
    			return

    		try:
    			elapsed = iface.Get(iface_player, 'Position')
    		except dbus.exceptions.DBusException:
    			pl.warning('Missing player elapsed time')
    			elapsed = None
    		else:
    			elapsed = _convert_seconds(elapsed / 1e6)
    		album = info.get('xesam:album')
    		title = info.get('xesam:title')
    		artist = info.get('xesam:artist')
    		state = _convert_state(status)
    		if album:
    			album = out_u(album)
    		if title:
    			title = out_u(title)
    		if artist:
    			artist = out_u(artist[0])

    		length = info.get('mpris:length')
    		# avoid parsing `None` length values, that would
    		# raise an error otherwise
    		parsed_length = length and _convert_seconds(length / 1e6)

    		return {
    			'state': state,
    			'album': album,
    			'artist': artist,
    			'title': title,
    			'elapsed': elapsed,
    			'total': parsed_length,
    		}

    class DbusPlayerSegment(PlayerSegment):
    	get_player_status = staticmethod(_get_dbus_player_status)

    dbus_player = with_docstring(DbusPlayerSegment(),
    ('''Return generic dbus player state

    Requires ``dbus`` python module. Only for players that support specific protocol
     (e.g. like :py:func:`spotify` and :py:func:`clementine`).

    {0}
    :param str player_name:
    	Player name. Used in error messages only.
    :param str bus_name:
    	Dbus bus name.
    :param str player_path:
    	Path to the player on the given bus.
    :param str iface_prop:
    	Interface properties name for use with dbus.Interface.
    :param str iface_player:
    	Player name.
    ''').format(_common_args.format('dbus_player')))

    class SpotifyDbusPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		player_status = _get_dbus_player_status(
    			pl=pl,
    			player_name='Spotify',
    			bus_name='org.mpris.MediaPlayer2.spotify',
    			player_path='/org/mpris/MediaPlayer2',
    			iface_prop='org.freedesktop.DBus.Properties',
    			iface_player='org.mpris.MediaPlayer2.Player',
    		)
    		if player_status is not None:
    			return player_status
    		# Fallback for legacy spotify client with different DBus protocol
    		return _get_dbus_player_status(
    			pl=pl,
    			player_name='Spotify',
    			bus_name='com.spotify.qt',
    			player_path='/',
    			iface_prop='org.freedesktop.DBus.Properties',
    			iface_player='org.freedesktop.MediaPlayer2',
    		)

    spotify_dbus = with_docstring(SpotifyDbusPlayerSegment(),
    ('''Return spotify player information

    Requires ``dbus`` python module.

    {0}
    ''').format(_common_args.format('spotify_dbus')))

    class SpotifyAppleScriptPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		status_delimiter = '-~`/='
    		ascript = '''
    			tell application "System Events"
    				set process_list to (name of every process)
    			end tell

    			if process_list contains "Spotify" then
    				tell application "Spotify"
    					if player state is playing or player state is paused then
    						set track_name to name of current track
    						set artist_name to artist of current track
    						set album_name to album of current track
    						set track_length to duration of current track
    						set now_playing to "" & player state & "{0}" & album_name & "{0}" & artist_name & "{0}" & track_name & "{0}" & track_length & "{0}" & player position
    						return now_playing
    					else
    						return player state
    					end if

    				end tell
    			else
    				return "stopped"
    			end if
    		'''.format(status_delimiter)

    		spotify = asrun(pl, ascript)
    		if not asrun:
    			return None

    		spotify_status = spotify.split(status_delimiter)
    		state = _convert_state(spotify_status[0])
    		if state == 'stop':
    			return None
    		return {
    			'state': state,
    			'album': spotify_status[1],
    			'artist': spotify_status[2],
    			'title': spotify_status[3],
    			'total': _convert_seconds(int(spotify_status[4])/1000),
    			'elapsed': _convert_seconds(spotify_status[5]),
    		}

    spotify_apple_script = with_docstring(SpotifyAppleScriptPlayerSegment(),
    ('''Return spotify player information

    Requires ``osascript`` available in $PATH.

    {0}
    ''').format(_common_args.format('spotify_apple_script')))

    if not sys.platform.startswith('darwin'):
    	spotify = spotify_dbus
    	_old_name = 'spotify_dbus'
    else:
    	spotify = spotify_apple_script
    	_old_name = 'spotify_apple_script'

    spotify = with_docstring(spotify, spotify.__doc__.replace(_old_name, 'spotify'))

    class ClementinePlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		return _get_dbus_player_status(
    			pl=pl,
    			player_name='Clementine',
    			bus_name='org.mpris.MediaPlayer2.clementine',
    			player_path='/org/mpris/MediaPlayer2',
    			iface_prop='org.freedesktop.DBus.Properties',
    			iface_player='org.mpris.MediaPlayer2.Player',
    		)

    clementine = with_docstring(ClementinePlayerSegment(),
    ('''Return clementine player information

    Requires ``dbus`` python module.

    {0}
    ''').format(_common_args.format('clementine')))

    class RhythmboxPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		now_playing = run_cmd(pl, [
    			'rhythmbox-client',
    			'--no-start', '--no-present',
    			'--print-playing-format', '%at\n%aa\n%tt\n%te\n%td'
    		], strip=False)
    		if not now_playing:
    			return
    		now_playing = now_playing.split('\n')
    		return {
    			'album': now_playing[0],
    			'artist': now_playing[1],
    			'title': now_playing[2],
    			'elapsed': now_playing[3],
    			'total': now_playing[4],
    		}

    rhythmbox = with_docstring(RhythmboxPlayerSegment(),
    ('''Return rhythmbox player information

    Requires ``rhythmbox-client`` available in $PATH.

    {0}
    ''').format(_common_args.format('rhythmbox')))

    class RDIOPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		status_delimiter = '-~`/='
    		ascript = '''
    			tell application "System Events"
    				set rdio_active to the count(every process whose name is "Rdio")
    				if rdio_active is 0 then
    					return
    				end if
    			end tell
    			tell application "Rdio"
    				set rdio_name to the name of the current track
    				set rdio_artist to the artist of the current track
    				set rdio_album to the album of the current track
    				set rdio_duration to the duration of the current track
    				set rdio_state to the player state
    				set rdio_elapsed to the player position
    				return rdio_name & "{0}" & rdio_artist & "{0}" & rdio_album & "{0}" & rdio_elapsed & "{0}" & rdio_duration & "{0}" & rdio_state
    			end tell
    		'''.format(status_delimiter)
    		now_playing = asrun(pl, ascript)
    		if not now_playing:
    			return
    		now_playing = now_playing.split(status_delimiter)
    		if len(now_playing) != 6:
    			return
    		state = _convert_state(now_playing[5])
    		total = _convert_seconds(now_playing[4])
    		elapsed = _convert_seconds(float(now_playing[3]) * float(now_playing[4]) / 100)
    		return {
    			'title': now_playing[0],
    			'artist': now_playing[1],
    			'album': now_playing[2],
    			'elapsed': elapsed,
    			'total': total,
    			'state': state,
    		}

    rdio = with_docstring(RDIOPlayerSegment(),
    ('''Return rdio player information

    Requires ``osascript`` available in $PATH.

    {0}
    ''').format(_common_args.format('rdio')))

    class ITunesPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		status_delimiter = '-~`/='
    		ascript = '''
    			tell application "System Events"
    				set process_list to (name of every process)
    			end tell

    			if process_list contains "iTunes" then
    				tell application "iTunes"
    					if player state is playing then
    						set t_title to name of current track
    						set t_artist to artist of current track
    						set t_album to album of current track
    						set t_duration to duration of current track
    						set t_elapsed to player position
    						set t_state to player state
    						return t_title & "{0}" & t_artist & "{0}" & t_album & "{0}" & t_elapsed & "{0}" & t_duration & "{0}" & t_state
    					end if
    				end tell
    			end if
    		'''.format(status_delimiter)
    		now_playing = asrun(pl, ascript)
    		if not now_playing:
    			return
    		now_playing = now_playing.split(status_delimiter)
    		if len(now_playing) != 6:
    			return
    		title, artist, album = now_playing[0], now_playing[1], now_playing[2]
    		state = _convert_state(now_playing[5])
    		total = _convert_seconds(now_playing[4])
    		elapsed = _convert_seconds(now_playing[3])
    		return {
    			'title': title,
    			'artist': artist,
    			'album': album,
    			'total': total,
    			'elapsed': elapsed,
    			'state': state
    		}

    itunes = with_docstring(ITunesPlayerSegment(),
    ('''Return iTunes now playing information

    Requires ``osascript``.

    {0}
    ''').format(_common_args.format('itunes')))

    class MocPlayerSegment(PlayerSegment):
    	def get_player_status(self, pl):
    		'''Return Music On Console (mocp) player information.

    		``mocp -i`` returns current information i.e.

    		.. code-block::

    		   File: filename.format
    		   Title: full title
    		   Artist: artist name
    		   SongTitle: song title
    		   Album: album name
    		   TotalTime: 00:00
    		   TimeLeft: 00:00
    		   TotalSec: 000
    		   CurrentTime: 00:00
    		   CurrentSec: 000
    		   Bitrate: 000kbps
    		   AvgBitrate: 000kbps
    		   Rate: 00kHz

    		For the information we are looking for we don’t really care if we have
    		extra-timing information or bit rate level. The dictionary comprehension
    		in this method takes anything in ignore_info and brings the key inside
    		that to the right info of the dictionary.
    		'''
    		now_playing_str = run_cmd(pl, ['mocp', '-i'])
    		if not now_playing_str:
    			return

    		now_playing = dict((
    			line.split(': ', 1)
    			for line in now_playing_str.split('\n')[:-1]
    		))
    		state = _convert_state(now_playing.get('State', 'stop'))
    		return {
    			'state': state,
    			'album': now_playing.get('Album', ''),
    			'artist': now_playing.get('Artist', ''),
    			'title': now_playing.get('SongTitle', ''),
    			'elapsed': _convert_seconds(now_playing.get('CurrentSec', 0)),
    			'total': _convert_seconds(now_playing.get('TotalSec', 0)),
    		}

    mocp = with_docstring(MocPlayerSegment(),
    ('''Return MOC (Music On Console) player information

    Requires version >= 2.3.0 and ``mocp`` executable in ``$PATH``.

    {0}
    ''').format(_common_args.format('mocp')))