Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # -*- coding: utf-8 -*-
- #!/usr/bin/env python
- import mpd
- import socket
- import logging
- import sys
- import time
- import os
- import re
- APPNAME = 'MPD-Last.FM'
- SLEEP_LARGE = 2
- SLEEP_SMALL = .5
- NOSCROBBLE = re.compile('^(https?|mms|rtsp)://.+', re.I)
- LFMCLIENT_PORT = 33367
- # Где искать конфиги
- CONFIGFILES = [
- '/etc/mpdlastfm.conf',
- os.path.join( os.environ.get('HOME', ''), '.mpdlastfm.conf' )
- ]
- # Конфиг по умолчанию
- config = {
- # параметры подключения к mpd
- 'mpd_host' : 'localhost',
- 'mpd_port' : 6600,
- 'mpd_pass' : '',
- # путь до корня с музыкой. Клиент Last.FM не прочь получить полный путь
- 'mpd_root' : os.path.join( os.environ['HOME'], '.music' ),
- # файл с логом. ротации нет
- 'log_file' : '/var/log/mpdlfm/mdlfm.log',
- }
- # Простая читалка конфигов
- for cfile in CONFIGFILES:
- try:
- for line in file(cfile).xreadlines():
- line.strip()
- if line[0] == '#': continue
- try:
- key, value = [x.strip() for x in line.split('=')]
- if key in config:
- config[key] = value
- except ValueError:
- continue
- except:
- pass
- config['mpd_port'] = int( config['mpd_port'] )
- # Настраиваем лог
- log_dir = os.path.dirname( config['log_file'] )
- if not os.path.exists(log_dir):
- os.makedirs(log_dir)
- logging.basicConfig(level=logging.DEBUG,
- format='%(asctime)s %(levelname)-8s %(message)s',
- datefmt='%d/%m/%Y %H:%M:%S',
- filename=config['log_file'],
- filemode='w')
- console = logging.StreamHandler()
- logging.getLogger('').addHandler(console)
- formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
- console.setFormatter(formatter)
- logger = logging.getLogger(APPNAME)
- logger.info(u'Начинаем')
- class LFMClient():
- def __init__(self, host = 'localhost',
- port = LFMCLIENT_PORT, timeout = 5.0):
- self.id = "mdc" # Это ID от mpdscribble
- self.host = host
- self.port = port
- self.timeout = timeout
- def _build_command(self, command, **kwargs):
- kwargs['c'] = self.id
- # ключ=значение
- # объединяем при помощи &
- # & в значениях экранируем им же: &&
- command += ' '
- for key, value in kwargs.items():
- command += '%s=%s&' % (key, value.replace('&', '&&'))
- # не забываем про перевод строки
- command = command[:-1] + '\n'
- return command
- def send_command(self, command, **kwargs):
- command = self._build_command(command, **kwargs)
- try:
- # Клиент закрывает соединение после каждой (?) комманды
- # Так что не надо заморачиваться с постоянным соединением
- _socket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
- _socket.connect(( self.host, self.port ))
- _socket.settimeout( self.timeout )
- command_bytes = command.encode('utf-8')
- sent = _socket.send( command_bytes )
- if sent == len( command_bytes ):
- logger.debug(u"Отправлено: %s" % command.strip())
- else:
- logger.error( u"Не удалось отправить комманду клиенту" )
- _socket.shutdown(socket.SHUT_RDWR)
- _socket.close()
- except socket.error, e:
- logger.error('Нет соединения с Last.FM клиентом: %s' % e[1] )
- def track_changed(self, trackinfo):
- logger.info(u'Начинается воспроизведение нового трека)')
- try:
- # Надеюсь, что MPD всегда отдаёт инфу в UTF-8
- artist = trackinfo.get('artist', '').decode('utf-8')
- title = trackinfo.get('title', '').decode('utf-8')
- filename = trackinfo.get('file', '').decode('utf-8')
- album = trackinfo.get('album', '').decode('utf-8')
- except UnicodeDecodeError, e:
- logger.error(u'Неверная последовательность символов в значении тега: %s'
- % e)
- self.send_command("STOP")
- return
- length = int( trackinfo.get('time', 0) )
- if artist and title and length:
- logger.info(u'Трек: %s - %s (%s)' % (artist, title, filename))
- else:
- # Пишем предупреждение в лог, если надо
- # Но клиенту всё равно отправим потом
- # Пусть сам решает, что делать с коротким или с неправильными
- # тегами файлом
- logger.warn(u'В файле отсутствуют теги или он слишком короткий. Скорее всего он не будет заскробблен')
- mbId = ''
- self.send_command("START", a = artist, t = title, b = album, m = mbId,
- l = str(length), p = filename)
- def state_changed(self, oldstate, newstate):
- if newstate == 'play':
- logger.info(u'Воспроизведение возобновлено')
- self.send_command('RESUME')
- pass
- elif newstate == 'stop':
- logger.info(u'Воспроизведение остановлено')
- self.send_command('STOP')
- pass
- elif newstate == 'pause':
- logger.info(u'Воспроизведение поставлено на паузу')
- self.send_command('PAUSE')
- pass
- else:
- logger.warn(u'MPD находится в неизвестном состоянии: %s'
- % newstate)
- class MPDHelper():
- def __init__(self, host, port, password,
- root, state_changed, track_changed):
- self._mpd = mpd.MPDClient()
- self.host = host
- self.port = port
- self.password = password
- self.root = root
- self.state_changed = state_changed
- self.track_changed = track_changed
- self._servername = '%s:%d' % (host, port)
- if password:
- self._servername = '******@' + self._servername
- self.connection_status = None
- self.last_file_played = None
- self.last_state = None
- def _connect(self):
- # Пингуем, коннектимся, реконнектимся
- # «Умное» постоянное соединение
- try:
- self._mpd.ping()
- except socket.error:
- pass
- except mpd.ConnectionError:
- try:
- self._mpd.disconnect()
- except socket.error:
- pass
- except mpd.ConnectionError:
- pass
- try:
- self._mpd.connect(self.host, self.port)
- self._mpd.ping()
- if self.password:
- self._mpd.password(self.password)
- if self.connection_status is None:
- logger.info(u"Установленно соединение с MPD (%s)"
- % (self._servername,))
- else:
- logger.warn(u"Переподключение к MPD (%s)"
- % (self._servername,))
- self.connection_status = True
- except (mpd.ConnectionError, mpd.CommandError, socket.error), e:
- try:
- # На всякий случай отключаемся
- self._mpd.disconnect()
- except socket.error:
- pass
- except mpd.ConnectionError:
- pass
- error_message = e[1] if isinstance(e, socket.error) else e
- if self.connection_status:
- logger.error(u"Потеряно подключение к MPD (%s): %s"
- % (self._servername, error_message))
- else:
- logger.error(u"Не удается подключиться к MPD (%s): %s. Проверьте адрес сервера и пароль в настройках"
- % (self._servername, error_message))
- self.connection_status = False
- return self.connection_status
- def poll(self):
- try:
- currentsong, status = self._mpd.currentsong(), self._mpd.status()
- # Пытаемся получить полное имя файла на диске, если возможно
- file = currentsong.get('file')
- if self.root and file:
- if not NOSCROBBLE.match(file):
- if file.startswith('file://'):
- file = file[7:]
- fullpath = os.path.join(self.root, file)
- if os.path.exists(fullpath):
- currentsong['file'] = os.path.join(self.root, file)
- except (mpd.ConnectionError, mpd.CommandError, socket.error), e:
- error_message = e[1] if isinstance(e, socket.error) else e
- logger.warn(u'Невозможно получить информацию о текущей композиции: %s'
- % error_message)
- return False
- new_state = status.get('state')
- # Если изменилось состояние, то сообщаем клиенту об этом
- # Надо учесть, что при запуске скрипта MPD уже может работать, но при
- # этом стоять на паузе и пр. Об этом сообщать не будем
- if new_state != self.last_state:
- if self.state_changed and self.last_state is not None:
- self.state_changed(self.last_state, new_state)
- # Если изменилось имя файла
- # Если мы вышли из состояния ОСТАНОВЛЕННО
- # ... и при этом сейчас находимся в состоянии ВОСПРОИЗВЕДЕНИЕ
- # То сообщаем о новом треке
- if (
- (
- currentsong.get('file') != self.last_file_played or
- self.last_state == 'stop'
- ) and
- new_state == 'play'
- ):
- self.track_changed( currentsong )
- # Сохраняем состояние и имя проигрываемого файла
- self.last_file_played = currentsong.get('file')
- self.last_state = new_state
- return True
- lfm = LFMClient()
- mpdpoll = MPDHelper(config['mpd_host'], config['mpd_port'], config['mpd_pass'], config['mpd_root'], lfm.state_changed, lfm.track_changed)
- # Пробуем запустить Last.FM клиента сами
- # Если он уже работает, то вторая копия не запустится
- # Так что дополнительно проверять не нужно
- from subprocess import Popen
- for procname in ['last.fm', 'lastfm']:
- try:
- logger.debug(u'Попытка запустить процесс %s' % procname)
- stdin = open(os.devnull, 'r')
- stdout = open(os.devnull, 'a+')
- stderr = open(os.devnull, 'a+')
- Popen([procname, "-tray"], stdin = stdin, stdout = stdout, stderr = stderr).pid
- logger.debug(u'Процесс запущен. Немного подождем')
- time.sleep(3)
- break
- except:
- logger.debug(u'Не удалось запустить процесс: %s' % sys.exc_info()[1])
- # Пока живы опрашиваем MPD
- while True:
- # «Умный» connect
- mpdpoll._connect()
- # Если нет соединения с MPD, то выдержим паузу побольше
- if not mpdpoll.connection_status:
- time.sleep(SLEEP_LARGE)
- continue
- # Если клиент отвечает, то опрашиваем
- mpdpoll.poll()
- # и выдерживаем короткую паузу, перед следущей попыткой
- time.sleep(SLEEP_SMALL)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement