Big restructuring
This commit is contained in:
parent
d17ae08f03
commit
9bec6c7675
2 changed files with 1272 additions and 658 deletions
654
mcg.py
654
mcg.py
|
|
@ -8,10 +8,46 @@
|
||||||
|
|
||||||
import mpd
|
import mpd
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from threading import Thread
|
import urllib.request
|
||||||
|
import configparser
|
||||||
|
|
||||||
class MCGClient:
|
|
||||||
|
|
||||||
|
class MCGBase():
|
||||||
|
def __init__(self):
|
||||||
|
self._callbacks = {}
|
||||||
|
|
||||||
|
|
||||||
|
def connect_signal(self, signal, callback):
|
||||||
|
"""Connects a callback function to a signal (event).
|
||||||
|
"""
|
||||||
|
self._callbacks[signal] = callback
|
||||||
|
|
||||||
|
|
||||||
|
def disconnect_signal(self, signal):
|
||||||
|
if self._has_callback(signal):
|
||||||
|
del self._callbacks[signal]
|
||||||
|
|
||||||
|
|
||||||
|
def _has_callback(self, signal):
|
||||||
|
"""Checks if there is a registered callback function for a
|
||||||
|
signal.
|
||||||
|
"""
|
||||||
|
return signal in self._callbacks
|
||||||
|
|
||||||
|
|
||||||
|
def _callback(self, signal, *data):
|
||||||
|
if signal in self._callbacks:
|
||||||
|
callback = self._callbacks[signal]
|
||||||
|
callback(*data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MCGClient(MCGBase, mpd.MPDClient):
|
||||||
"""Client library for handling the connection to the Music Player Daemon.
|
"""Client library for handling the connection to the Music Player Daemon.
|
||||||
|
|
||||||
This class implements an album-based MPD client.
|
This class implements an album-based MPD client.
|
||||||
|
|
@ -20,30 +56,37 @@ class MCGClient:
|
||||||
"""
|
"""
|
||||||
# Signal: connect/disconnect event
|
# Signal: connect/disconnect event
|
||||||
SIGNAL_CONNECT = 'connect'
|
SIGNAL_CONNECT = 'connect'
|
||||||
# Signal: general idle event
|
|
||||||
SIGNAL_IDLE = 'idle'
|
|
||||||
# Signal: status event
|
# Signal: status event
|
||||||
SIGNAL_STATUS = 'status'
|
SIGNAL_STATUS = 'status'
|
||||||
# Signal: update event
|
# Signal: load albums
|
||||||
SIGNAL_UPDATE = 'update'
|
SIGNAL_LOAD_ALBUMS = 'load-albums'
|
||||||
|
# Signal: load playlist
|
||||||
|
SIGNAL_LOAD_PLAYLIST = 'load-playlist'
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Sets class variables and instantiates the MPDClient.
|
"""Sets class variables and instantiates the MPDClient.
|
||||||
"""
|
"""
|
||||||
|
MCGBase.__init__(self)
|
||||||
|
mpd.MPDClient.__init__(self)
|
||||||
self._connected = False
|
self._connected = False
|
||||||
self._albums = {}
|
self._client_lock = threading.Lock()
|
||||||
self._callbacks = {}
|
self._client_stop = threading.Event()
|
||||||
self._actions = []
|
self._actions = queue.Queue()
|
||||||
self._worker = None
|
self._worker = None
|
||||||
self._client = mpd.MPDClient()
|
self._albums = {}
|
||||||
self._go = True
|
self._host = None
|
||||||
|
self._image_dir = ""
|
||||||
|
|
||||||
|
|
||||||
def connect(self, host="localhost", port="6600", password=None):
|
# Connection commands
|
||||||
|
|
||||||
|
def connect(self, host="localhost", port="6600", password=None, image_dir=""):
|
||||||
"""Connects to MPD with the given host, port and password or
|
"""Connects to MPD with the given host, port and password or
|
||||||
with standard values.
|
with standard values.
|
||||||
"""
|
"""
|
||||||
|
self._host = host
|
||||||
|
self._image_dir = image_dir
|
||||||
self._add_action(self._connect, host, port, password)
|
self._add_action(self._connect, host, port, password)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -56,28 +99,15 @@ class MCGClient:
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
"""Disconnects from the connected MPD.
|
"""Disconnects from the connected MPD.
|
||||||
"""
|
"""
|
||||||
|
self._client_stop.set()
|
||||||
self._add_action(self._disconnect)
|
self._add_action(self._disconnect)
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def join(self):
|
||||||
"""Closes the connection and stops properly the worker thread.
|
self._actions.join()
|
||||||
This method is to stop the whole appliction.
|
|
||||||
"""
|
|
||||||
if not self.is_connected():
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self._go = False
|
|
||||||
self._client.noidle()
|
|
||||||
self._client.disconnect()
|
|
||||||
except TypeError as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def update(self):
|
# Status commands
|
||||||
"""Updates the album list.
|
|
||||||
"""
|
|
||||||
self._add_action(self._update)
|
|
||||||
|
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
"""Determines the current status.
|
"""Determines the current status.
|
||||||
|
|
@ -85,50 +115,50 @@ class MCGClient:
|
||||||
self._add_action(self._get_status)
|
self._add_action(self._get_status)
|
||||||
|
|
||||||
|
|
||||||
def play_album(self, album):
|
# Playback option commands
|
||||||
"""Plays the given album.
|
|
||||||
"""
|
|
||||||
self._add_action(self._play, album)
|
|
||||||
|
|
||||||
|
|
||||||
|
# Playback control commands
|
||||||
|
|
||||||
def playpause(self):
|
def playpause(self):
|
||||||
"""Plays or pauses the current state.
|
"""Plays or pauses the current state.
|
||||||
"""
|
"""
|
||||||
self._add_action(self._playpause)
|
self._add_action(self._playpause)
|
||||||
|
|
||||||
|
|
||||||
def next_song(self):
|
def play_album(self, album):
|
||||||
"""Plays the next album in the current order
|
"""Plays the given album.
|
||||||
"""
|
"""
|
||||||
self._add_action(self._next_song)
|
self._add_action(self._play_album, album)
|
||||||
|
|
||||||
|
|
||||||
def connect_signal(self, signal, callback):
|
def stop(self):
|
||||||
"""Connects a callback function to a signal (event).
|
self._add_action(self._stop)
|
||||||
"""
|
|
||||||
self._callbacks[signal] = callback
|
|
||||||
|
|
||||||
|
|
||||||
def _has_callback(self, signal):
|
# Playlist commands
|
||||||
"""Checks if there is a registered callback function for a
|
|
||||||
signal.
|
def load_playlist(self):
|
||||||
"""
|
self._add_action(self._load_playlist)
|
||||||
return signal in self._callbacks
|
|
||||||
|
|
||||||
|
|
||||||
def _callback(self, signal, *args):
|
# Database commands
|
||||||
"""Calls the callback function for a signal.
|
|
||||||
"""
|
|
||||||
if self._has_callback(signal):
|
|
||||||
callback = self._callbacks[signal]
|
|
||||||
callback(*args)
|
|
||||||
|
|
||||||
|
def load_albums(self):
|
||||||
|
self._add_action(self._load_albums)
|
||||||
|
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self._add_action(self._update)
|
||||||
|
|
||||||
|
|
||||||
|
# Private methods
|
||||||
|
|
||||||
def _add_action(self, method, *args):
|
def _add_action(self, method, *args):
|
||||||
"""Adds an action to the action list.
|
"""Adds an action to the action list.
|
||||||
"""
|
"""
|
||||||
action = [method, args]
|
action = [method, args]
|
||||||
self._actions.append(action)
|
self._actions.put(action)
|
||||||
self._start_worker()
|
self._start_worker()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -137,173 +167,251 @@ class MCGClient:
|
||||||
performed.
|
performed.
|
||||||
"""
|
"""
|
||||||
if self._worker is None or not self._worker.is_alive():
|
if self._worker is None or not self._worker.is_alive():
|
||||||
self._worker = Thread(target=self._work, name='worker', args=())
|
self._worker = threading.Thread(target=self._run, name='mcg-worker', args=())
|
||||||
|
self._worker.setDaemon(True)
|
||||||
self._worker.start()
|
self._worker.start()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self._client.noidle()
|
self.noidle()
|
||||||
except TypeError as e:
|
except mpd.ConnectionError as e:
|
||||||
pass
|
self._callback(MCGClient.SIGNAL_CONNECT, False, e)
|
||||||
|
|
||||||
|
|
||||||
def _work(self):
|
def _work(self, action):
|
||||||
"""Performs the next action or waits for an idle event.
|
method = action[0]
|
||||||
"""
|
params = action[1]
|
||||||
while True:
|
method(*params)
|
||||||
if self._actions:
|
|
||||||
action = self._actions.pop(0)
|
|
||||||
method = action[0]
|
|
||||||
params = action[1]
|
|
||||||
method(*params)
|
|
||||||
else:
|
|
||||||
if not self.is_connected():
|
|
||||||
break
|
|
||||||
modules = self._client.idle()
|
|
||||||
if not self._go:
|
|
||||||
break
|
|
||||||
self._idle(modules)
|
|
||||||
|
|
||||||
|
|
||||||
|
def _call(self, command, *args):
|
||||||
|
return getattr(super(), command)(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def _run(self):
|
||||||
|
while not self._client_stop.is_set() or not self._actions.empty():
|
||||||
|
if self._actions.empty():
|
||||||
|
self._actions.put([self._idle, ()])
|
||||||
|
action = self._actions.get()
|
||||||
|
|
||||||
|
self._client_lock.acquire()
|
||||||
|
self._work(action)
|
||||||
|
self._client_lock.release()
|
||||||
|
self._actions.task_done()
|
||||||
|
|
||||||
|
|
||||||
|
# Connection commands
|
||||||
|
|
||||||
def _connect(self, host, port, password):
|
def _connect(self, host, port, password):
|
||||||
"""Action: Performs the real connect to MPD.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
self._client.connect(host, port)
|
self._call('connect', host, port)
|
||||||
if password:
|
if password:
|
||||||
self._client.password(password)
|
self._call('password', password)
|
||||||
# TODO Verbindung testen
|
self._set_connction_status(True, None)
|
||||||
self._connected = True
|
except mpd.ConnectionError as e:
|
||||||
self._callback(self.SIGNAL_CONNECT, self._connected, None)
|
self._set_connction_status(False, e)
|
||||||
except IOError as e:
|
except OSError as e:
|
||||||
self._connected = False
|
self._set_connction_status(False, e)
|
||||||
self._callback(self.SIGNAL_CONNECT, self._connected, e)
|
|
||||||
|
|
||||||
|
|
||||||
def _disconnect(self):
|
def _disconnect(self):
|
||||||
"""Action: Performs the real disconnect from MPD.
|
|
||||||
"""
|
|
||||||
if not self.is_connected():
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
#self._client.close()
|
self._call('noidle')
|
||||||
self._client.disconnect()
|
self._call('disconnect')
|
||||||
except:
|
self._set_connction_status(False, None)
|
||||||
self._client = mpd.MPDClient()
|
except mpd.ConnectionError as e:
|
||||||
self._connected = False
|
self._set_connction_status(False, e)
|
||||||
self._callback(self.SIGNAL_CONNECT, self._connected, None)
|
|
||||||
|
|
||||||
|
|
||||||
def _update(self):
|
# Status commands
|
||||||
"""Action: Performs the real update.
|
|
||||||
"""
|
|
||||||
for song in self._client.listallinfo():
|
|
||||||
try:
|
|
||||||
hash = MCGAlbum.hash(song['artist'], song['album'])
|
|
||||||
if hash in self._albums.keys():
|
|
||||||
album = self._albums[hash]
|
|
||||||
else:
|
|
||||||
album = MCGAlbum(song['artist'], song['album'], song['date'], os.path.dirname(song['file']))
|
|
||||||
self._albums[album.get_hash()] = album
|
|
||||||
track = MCGTrack(song['title'], song['track'], song['time'], song['file'])
|
|
||||||
album.add_track(track)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
self._callback(self.SIGNAL_UPDATE, self._albums)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_status(self):
|
def _get_status(self):
|
||||||
"""Action: Performs the real status determination
|
"""Action: Performs the real status determination
|
||||||
"""
|
"""
|
||||||
if not self._has_callback(self.SIGNAL_STATUS):
|
try:
|
||||||
return
|
self._call('noidle')
|
||||||
status = self._client.status()
|
status = self._call('status')
|
||||||
state = status['state']
|
state = status['state']
|
||||||
song = self._client.currentsong()
|
self._call('noidle')
|
||||||
album = None
|
song = self._call('currentsong')
|
||||||
pos = None
|
album = None
|
||||||
if song:
|
pos = None
|
||||||
album = self._albums[MCGAlbum(song['artist'], song['album'], song['date'], os.path.dirname(song['file'])).get_hash()]
|
if song:
|
||||||
pos = int(song['pos'])
|
hash = MCGAlbum.hash(song['artist'], song['album'])
|
||||||
self._callback(self.SIGNAL_STATUS, state, album, pos)
|
if hash in self._albums:
|
||||||
|
album = self._albums[hash]
|
||||||
|
pos = int(song['pos'])
|
||||||
|
|
||||||
|
self._callback(MCGClient.SIGNAL_STATUS, state, album, pos, None)
|
||||||
|
except mpd.ConnectionError as e:
|
||||||
|
self._set_connction_status(False, e)
|
||||||
|
|
||||||
|
|
||||||
def _play(self, album):
|
# Playback option commants
|
||||||
"""Action: Performs the real play command.
|
|
||||||
"""
|
|
||||||
self._client.clear()
|
|
||||||
track_ids = []
|
|
||||||
for track in self._albums[album].get_tracks():
|
|
||||||
track_id = self._client.addid(track.get_file())
|
|
||||||
track_ids.append(track_id)
|
|
||||||
self._client.moveid(track_id, len(track_ids)-1)
|
|
||||||
self._client.playid(track_ids[0])
|
|
||||||
|
|
||||||
|
|
||||||
|
# Playback control commands
|
||||||
|
|
||||||
def _playpause(self):
|
def _playpause(self):
|
||||||
"""Action: Performs the real play/pause command.
|
"""Action: Performs the real play/pause command.
|
||||||
"""
|
"""
|
||||||
status = self._client.status()
|
status = self._call('status')
|
||||||
state = status['state']
|
state = status['state']
|
||||||
|
|
||||||
if state == 'play':
|
if state == 'play':
|
||||||
self._client.pause()
|
self._call('pause')
|
||||||
else:
|
else:
|
||||||
self._client.play()
|
self._call('play')
|
||||||
|
|
||||||
|
|
||||||
def _next_song(self):
|
def _play_album(self, album):
|
||||||
"""Action: Performs the real next command.
|
"""Action: Performs the real play command.
|
||||||
"""
|
"""
|
||||||
self._client.next()
|
if not album in self._albums:
|
||||||
|
# TODO print
|
||||||
|
print("album not found")
|
||||||
def _idle(self, modules):
|
|
||||||
"""Reacts to idle events from MPD.
|
|
||||||
"""
|
|
||||||
if not modules:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'player' in modules:
|
try:
|
||||||
self._get_status()
|
self._call('clear')
|
||||||
if 'database' in modules:
|
track_ids = []
|
||||||
# TODO update DB
|
for track in self._albums[album].get_tracks():
|
||||||
pass
|
track_id = self._call('addid', track.get_file())
|
||||||
if 'update' in modules:
|
track_ids.append(track_id)
|
||||||
# TODO update
|
self._call('moveid', track_id, len(track_ids)-1)
|
||||||
pass
|
self._call('playid', track_ids[0])
|
||||||
if 'mixer' in modules:
|
except mpd.ConnectionError as e:
|
||||||
# TODO mixer
|
self._set_connction_status(False, e)
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _idle_playlist(self):
|
def _stop(self):
|
||||||
""" Reacts on the playlist idle event.
|
try:
|
||||||
|
self._call('stop')
|
||||||
|
except mpd.ConnectionError as e:
|
||||||
|
self._set_connction_status(False, e)
|
||||||
|
|
||||||
|
|
||||||
|
# Playlist commands
|
||||||
|
|
||||||
|
def _load_playlist(self):
|
||||||
|
try:
|
||||||
|
playlist = []
|
||||||
|
for song in self._call('playlistinfo'):
|
||||||
|
try:
|
||||||
|
hash = MCGAlbum.hash(song['artist'], song['album'])
|
||||||
|
if len(playlist) == 0 or playlist[len(playlist)-1].get_hash() != hash:
|
||||||
|
date = ""
|
||||||
|
if 'date' in song:
|
||||||
|
date = song['date']
|
||||||
|
path = ""
|
||||||
|
if 'file' in song:
|
||||||
|
path = os.path.dirname(song['file'])
|
||||||
|
album = MCGAlbum(song['artist'], song['album'], date, path, self._host, self._image_dir)
|
||||||
|
playlist.append(album)
|
||||||
|
else:
|
||||||
|
album = playlist[len(playlist)-1]
|
||||||
|
track = MCGTrack(song['title'], song['track'], song['time'], song['file'])
|
||||||
|
album.add_track(track)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self._callback(MCGClient.SIGNAL_LOAD_PLAYLIST, playlist, None)
|
||||||
|
except mpd.ConnectionError as e:
|
||||||
|
self._set_connction_status(False, e)
|
||||||
|
|
||||||
|
|
||||||
|
# Database commands
|
||||||
|
|
||||||
|
def _load_albums(self):
|
||||||
|
"""Action: Performs the real update.
|
||||||
"""
|
"""
|
||||||
pass
|
try:
|
||||||
|
for song in self._call('listallinfo'):
|
||||||
|
try:
|
||||||
|
hash = MCGAlbum.hash(song['artist'], song['album'])
|
||||||
|
if hash in self._albums.keys():
|
||||||
|
album = self._albums[hash]
|
||||||
|
else:
|
||||||
|
date = ""
|
||||||
|
if 'date' in song:
|
||||||
|
date = song['date']
|
||||||
|
path = ""
|
||||||
|
if 'file' in song:
|
||||||
|
path = os.path.dirname(song['file'])
|
||||||
|
album = MCGAlbum(song['artist'], song['album'], date, path, self._host, self._image_dir)
|
||||||
|
self._albums[album.get_hash()] = album
|
||||||
|
track = MCGTrack(song['title'], song['track'], song['time'], song['file'])
|
||||||
|
album.add_track(track)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self._callback(MCGClient.SIGNAL_LOAD_ALBUMS, self._albums, None)
|
||||||
|
except mpd.ConnectionError as e:
|
||||||
|
self._set_connction_status(False, e)
|
||||||
|
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
try:
|
||||||
|
self._call('update')
|
||||||
|
except mpd.ConnectionError as e:
|
||||||
|
self._set_connction_status(False, e)
|
||||||
|
|
||||||
|
def _set_connction_status(self, status, error):
|
||||||
|
self._connected = status
|
||||||
|
self._callback(MCGClient.SIGNAL_CONNECT, status, error)
|
||||||
|
if not status:
|
||||||
|
self._client_stop.set()
|
||||||
|
|
||||||
|
|
||||||
|
def _idle(self):
|
||||||
|
"""Reacts to idle events from MPD.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
modules = self._call('idle')
|
||||||
|
if not modules:
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'player' in modules:
|
||||||
|
self.get_status()
|
||||||
|
if 'mixer' in modules:
|
||||||
|
# TODO mixer
|
||||||
|
print("not implemented: idle mixer")
|
||||||
|
if 'playlist' in modules:
|
||||||
|
self.load_playlist()
|
||||||
|
if 'database' in modules:
|
||||||
|
self.load_albums()
|
||||||
|
self.load_playlist()
|
||||||
|
self.get_status()
|
||||||
|
if 'update' in modules:
|
||||||
|
self.load_albums()
|
||||||
|
self.load_playlist()
|
||||||
|
self.get_status()
|
||||||
|
except ConnectionResetError as e:
|
||||||
|
self._set_connction_status(False, e)
|
||||||
|
except mpd.ConnectionError as e:
|
||||||
|
self._set_connction_status(False, e)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MCGAlbum:
|
class MCGAlbum:
|
||||||
_file_names = ['folder', 'cover']
|
|
||||||
_file_exts = ['jpg', 'jpeg', 'png']
|
|
||||||
SORT_BY_ARTIST = 'artist'
|
SORT_BY_ARTIST = 'artist'
|
||||||
SORT_BY_TITLE = 'title'
|
SORT_BY_TITLE = 'title'
|
||||||
SORT_BY_YEAR = 'year'
|
SORT_BY_YEAR = 'year'
|
||||||
|
_file_names = ['folder', 'cover']
|
||||||
|
_file_exts = ['jpg', 'jpeg', 'png']
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, artist, title, date, path):
|
def __init__(self, artist, title, date, path, host, image_dir):
|
||||||
self._artist = artist
|
self._artist = artist
|
||||||
if type(self._artist) is list:
|
if type(self._artist) is list:
|
||||||
self._artist = self._artist[0]
|
self._artist = self._artist[0]
|
||||||
self._title = title
|
self._title = title
|
||||||
self._date = date
|
self._date = date
|
||||||
self._path = path
|
self._path = path
|
||||||
|
self._host = host
|
||||||
|
self._image_dir = image_dir
|
||||||
self._tracks = []
|
self._tracks = []
|
||||||
self._cover = None
|
self._cover = None
|
||||||
|
self._cover_searched = False
|
||||||
self._set_hash()
|
self._set_hash()
|
||||||
self._find_cover()
|
|
||||||
|
|
||||||
|
|
||||||
def get_artist(self):
|
def get_artist(self):
|
||||||
|
|
@ -332,35 +440,17 @@ class MCGAlbum:
|
||||||
|
|
||||||
|
|
||||||
def get_cover(self):
|
def get_cover(self):
|
||||||
|
if self._cover is None and not self._cover_searched:
|
||||||
|
self._find_cover()
|
||||||
return self._cover
|
return self._cover
|
||||||
|
|
||||||
|
|
||||||
def _find_cover(self):
|
|
||||||
names = list(self._file_names)
|
|
||||||
names.append(self._title)
|
|
||||||
names.append(' - '.join((self._artist, self._title)))
|
|
||||||
|
|
||||||
|
|
||||||
for name in names:
|
|
||||||
for ext in self._file_exts:
|
|
||||||
filename = os.path.join('/home/oliver/Musik/', self._path, '.'.join([name, ext]))
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
self._cover = filename
|
|
||||||
break
|
|
||||||
if self._cover is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def hash(artist, title):
|
def hash(artist, title):
|
||||||
if type(artist) is list:
|
if type(artist) is list:
|
||||||
artist = artist[0]
|
artist = artist[0]
|
||||||
return md5(artist.encode('utf-8')+title.encode('utf-8')).hexdigest()
|
return md5(artist.encode('utf-8')+title.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def _set_hash(self):
|
|
||||||
self._hash = MCGAlbum.hash(self._artist, self._title)
|
|
||||||
|
|
||||||
|
|
||||||
def get_hash(self):
|
def get_hash(self):
|
||||||
return self._hash
|
return self._hash
|
||||||
|
|
||||||
|
|
@ -393,6 +483,47 @@ class MCGAlbum:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def _set_hash(self):
|
||||||
|
self._hash = MCGAlbum.hash(self._artist, self._title)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_cover(self):
|
||||||
|
names = list(self._file_names)
|
||||||
|
names.append(self._title)
|
||||||
|
names.append(' - '.join([self._artist, self._title]))
|
||||||
|
|
||||||
|
if self._host == "localhost" or self._host == "127.0.0.1":
|
||||||
|
self._cover = self._find_cover_local(names)
|
||||||
|
else:
|
||||||
|
self._cover = self._find_cover_web(names)
|
||||||
|
self._cover_searched = True
|
||||||
|
|
||||||
|
|
||||||
|
def _find_cover_web(self, names):
|
||||||
|
for name in names:
|
||||||
|
for ext in self._file_exts:
|
||||||
|
url = '/'.join([
|
||||||
|
'http:/',
|
||||||
|
self._host,
|
||||||
|
urllib.request.quote(self._path),
|
||||||
|
urllib.request.quote('.'.join([name, ext]))
|
||||||
|
])
|
||||||
|
request = urllib.request.Request(url)
|
||||||
|
try:
|
||||||
|
response = urllib.request.urlopen(request)
|
||||||
|
return url
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _find_cover_local(self, names):
|
||||||
|
for name in names:
|
||||||
|
for ext in self._file_exts:
|
||||||
|
filename = os.path.join(self._image_dir, self._path, '.'.join([name, ext]))
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MCGTrack:
|
class MCGTrack:
|
||||||
|
|
@ -418,3 +549,164 @@ class MCGTrack:
|
||||||
def get_file(self):
|
def get_file(self):
|
||||||
return self._file
|
return self._file
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MCGConfig(configparser.ConfigParser):
|
||||||
|
CONFIG_DIR = '~/.config/mcg/'
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, filename):
|
||||||
|
configparser.ConfigParser.__init__(self)
|
||||||
|
self._filename = os.path.expanduser(os.path.join(MCGConfig.CONFIG_DIR, filename))
|
||||||
|
self._create_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
if os.path.isfile(self._filename):
|
||||||
|
self.read(self._filename)
|
||||||
|
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with open(self._filename, 'w') as configfile:
|
||||||
|
self.write(configfile)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_dir(self):
|
||||||
|
dirname = os.path.dirname(self._filename)
|
||||||
|
if not os.path.exists(dirname):
|
||||||
|
os.makedirs(dirname)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MCGProfileConfig(MCGConfig):
|
||||||
|
CONFIG_FILE = 'profiles.conf'
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
MCGConfig.__init__(self, MCGProfileConfig.CONFIG_FILE)
|
||||||
|
self._profiles = []
|
||||||
|
|
||||||
|
|
||||||
|
def add_profile(self, profile):
|
||||||
|
self._profiles.append(profile)
|
||||||
|
|
||||||
|
|
||||||
|
def get_profiles(self):
|
||||||
|
return self._profiles
|
||||||
|
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
super().load()
|
||||||
|
count = 0
|
||||||
|
if self.has_section('profiles'):
|
||||||
|
if self.has_option('profiles', 'count'):
|
||||||
|
count = self.getint('profiles', 'count')
|
||||||
|
for index in range(count):
|
||||||
|
section = 'profile'+str(index+1)
|
||||||
|
if self.has_section(section):
|
||||||
|
profile = MCGProfile()
|
||||||
|
for attribute in profile.get_attributes():
|
||||||
|
if self.has_option(section, attribute):
|
||||||
|
profile.set(attribute, self.get(section, attribute))
|
||||||
|
self._profiles.append(profile)
|
||||||
|
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
if not self.has_section('profiles'):
|
||||||
|
self.add_section('profiles')
|
||||||
|
self.set('profiles', 'count', str(len(self._profiles)))
|
||||||
|
|
||||||
|
for index in range(len(self._profiles)):
|
||||||
|
profile = self._profiles[index]
|
||||||
|
section = 'profile'+str(index+1)
|
||||||
|
if not self.has_section(section):
|
||||||
|
self.add_section(section)
|
||||||
|
for attribute in profile.get_attributes():
|
||||||
|
self.set(section, attribute, str(profile.get(attribute)))
|
||||||
|
super().save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MCGConfigurable:
|
||||||
|
def __init__(self):
|
||||||
|
self._attributes = []
|
||||||
|
|
||||||
|
|
||||||
|
def get(self, attribute):
|
||||||
|
return getattr(self, attribute)
|
||||||
|
|
||||||
|
|
||||||
|
def set(self, attribute, value):
|
||||||
|
setattr(self, attribute, value)
|
||||||
|
if attribute not in self._attributes:
|
||||||
|
self._attributes.append(attribute)
|
||||||
|
|
||||||
|
|
||||||
|
def get_attributes(self):
|
||||||
|
return self._attributes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MCGProfile(MCGConfigurable):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
MCGConfigurable.__init__(self)
|
||||||
|
self.set('host', "localhost")
|
||||||
|
self.set('port', 6600)
|
||||||
|
self.set('password', "")
|
||||||
|
self.set('image_dir', "")
|
||||||
|
self.set('tags', "")
|
||||||
|
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
return self.get('tags').split(',')
|
||||||
|
|
||||||
|
|
||||||
|
def set_tags(self, tags):
|
||||||
|
self.set('tags', ','.join(tags))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MCGCache():
|
||||||
|
DIRNAME = '~/.cache/mcg/'
|
||||||
|
SIZE_FILENAME = 'size'
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, host, size):
|
||||||
|
self._dirname = os.path.expanduser(os.path.join(MCGCache.DIRNAME, host))
|
||||||
|
if not os.path.exists(self._dirname):
|
||||||
|
os.makedirs(self._dirname)
|
||||||
|
self._size = size
|
||||||
|
self._read_size()
|
||||||
|
|
||||||
|
|
||||||
|
def create_filename(self, album):
|
||||||
|
return os.path.join(self._dirname, '-'.join([album.get_hash()]))
|
||||||
|
|
||||||
|
|
||||||
|
def _read_size(self):
|
||||||
|
size = 100
|
||||||
|
filename = os.path.join(self._dirname, MCGCache.SIZE_FILENAME)
|
||||||
|
if os.path.exists(filename):
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
size = int(f.readline())
|
||||||
|
if size != self._size:
|
||||||
|
self._clear()
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(str(self._size))
|
||||||
|
|
||||||
|
|
||||||
|
def _clear(self):
|
||||||
|
for filename in os.listdir(self._dirname):
|
||||||
|
path = os.path.join(self._dirname, filename)
|
||||||
|
try:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.unlink(path)
|
||||||
|
except Exception as e:
|
||||||
|
print("clear:", e)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue