#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2013 Jack Kaliko <efrim@azylum.org>
+# Copyright (c) 2010-2015 Jack Kaliko <kaliko@azylum.org>
#
# This file is part of MPD_sima
#
#
#
-__version__ = '0.4.0'
+__version__ = '0.4.1'
# IMPORT#
-import re
-
-from argparse import (ArgumentParser, SUPPRESS, Action)
+from argparse import (ArgumentParser, SUPPRESS)
from difflib import get_close_matches
from locale import getpreferredencoding
from os import (environ, chmod, makedirs)
from sima.lib.track import Track
from sima.utils import utils
from sima.lib import simadb
-from musicpd import MPDClient, ConnectionError
+from musicpd import MPDClient, MPDError
DESCRIPTION = """
between artists."""
DB_NAME = 'sima.db'
-class FooAction(Action):
- def check(self, namespace):
- if namespace.similarity: return True
- if namespace.remove_art: return True
- if namespace.remove_sim: return True
-
- def __call__(self, parser, namespace, values, option_string=None):
- opt_required = '"--remove_artist", "--remove_similarity" or "--add_similarity"'
- if not self.check(namespace):
- parser.error(
- 'can\'t use {0} option before or without {1}'.format(
- option_string, opt_required))
- setattr(namespace, self.dest, True)
-
# Options list
# pop out 'sw' value before creating ArgumentParser object.
OPTS = list([
- {
- 'sw':['-a', '--add_similarity'],
- 'type': str,
- 'dest':'similarity',
- 'help': 'Similarity to add formated as follow: ' +
- ' "art_0,art_1:90,art_2:80..."'},
- {
- 'sw': ['-c', '--check_names'],
- 'action': 'store_true',
- 'default': False,
- 'help': 'Turn on controls of artists names in MPD library.'},
{
'sw':['-d', '--dbfile'],
'type': str,
'dest':'dbfile',
'action': utils.Wfile,
'help': 'File to read/write database from/to'},
- {
- 'sw': ['-r', '--reciprocal'],
- 'default': False,
- 'nargs': 0,
- 'action': FooAction,
- 'help': 'Turn on reciprocity for similarity relation when add/remove.'},
- {
- 'sw':['--remove_artist'],
- 'type': str,
- 'dest': 'remove_art',
- 'metavar': '"ARTIST TO REMOVE"',
- 'help': 'Remove an artist from DB (main artist entries).'},
- {
- 'sw':['--remove_similarity'],
- 'type': str,
- 'dest': 'remove_sim',
- 'metavar': '"MAIN ART,SIMI ART"',
- 'help': 'Remove an similarity relation from DB (main artist <=> similar artist).'},
- {
- 'sw':['-v', '--view_artist'],
- 'type': str,
- 'dest':'view',
- 'metavar': '"ARTIST NAME"',
- 'help': 'View an artist from DB.'},
- {
- 'sw':['--view_all'],
- 'action': 'store_true',
- 'help': 'View all similarity entries.'},
{
'sw': ['-S', '--host'],
'type': str,
{
'sw': ['--view_bl'],
'action': 'store_true',
+ 'dest': 'view_bl',
'help': 'View black list.'},
{
'sw': ['--remove_bl'],
self.options = dict({})
self.localencoding = 'UTF-8'
self._get_encoding()
- self._upgrade()
self.main()
def _get_encoding(self):
else:
self.options.mpdport = 6600
- def _upgrade(self):
- """Upgrades DB if necessary, create one if not existing."""
- if not isfile(self.dbfile): # No db file
- return
- db = simadb.SimaDB(db_path=self.dbfile)
- db.upgrade()
-
def _declare_opts(self):
"""
Declare options in ArgumentParser object.
def _get_mpd_client(self):
""""""
- # TODO: encode properly host name
host = self.options.mpdhost
port = self.options.mpdport
+ passwd = self.options.passwd
cli = MPDClient()
try:
- cli.connect(host=host, port=port)
- except ConnectionError as err:
- mess = 'ERROR: fail to connect MPD (host: %s:%s): %s' % (
+ cli.connect(host, port)
+ if passwd:
+ cli.password(passwd)
+ except MPDError as err:
+ mess = 'ERROR: fail to connect MPD on %s:%s %s' % (
host, port, err)
print(mess, file=stderr)
exit(1)
return None
return art_db
- def _control_similarity(self):
- """
- * Regex check of command line similarity
- * Controls artist presence in MPD library
- """
- usage = ('USAGE: "main artist,similar artist:<match score>,other' +
- 'similar artist:<match score>,..."')
- cli_sim = self.options.similarity
- pattern = '^([^,]+?),([^:,]+?:\d{1,2},?)+$'
- regexp = re.compile(pattern, re.U).match(cli_sim)
- if not regexp:
- mess = 'ERROR: similarity badly formated: "%s"' % cli_sim
- print(mess, file=stderr)
- print(usage, file=stderr)
- exit(1)
- if self.options.check_names:
- if not self._control_artist_names():
- mess = 'ERROR: some artist names not found in MPD library!'
- print(mess, file=stderr)
- exit(1)
-
- def _control_artist_names(self):
- """Controls artist names exist in MPD library"""
- mpd_cli = self._get_mpd_client()
- artists_list = mpd_cli.list('artist')
- sim_formated = self._parse_similarity()
- control = True
- if sim_formated[0] not in artists_list:
- mess = 'WARNING: Main artist not found in MPD: %s' % sim_formated[0]
- print(mess)
- control = False
- for sart in sim_formated[1]:
- art = sart.get('artist')
- if art not in artists_list:
- mess = str('WARNING: Similar artist not found in MPD: %s' % art)
- print(mess)
- control = False
- mpd_cli.disconnect()
- return control
-
- def _parse_similarity(self):
- """Parse command line option similarity"""
- cli_sim = self.options.similarity.strip(',').split(',')
- sim = list([])
- main = cli_sim[0]
- for art in cli_sim[1:]:
- artist = art.split(':')[0]
- score = int(art.split(':')[1])
- sim.append({'artist': artist, 'score': score})
- return (main, sim)
-
- def _print_main_art(self, art=None):
- """Print entries, art as main artist."""
- if not art:
- art = self.options.view
- db = simadb.SimaDB(db_path=self.dbfile)
- art_db = self._get_art_from_db(art)
- if not art_db: return
- sims = list([])
- [sims.append(a) for a in db._get_similar_artists_from_db(art_db[0])]
- if len(sims) == 0:
- return False
- print('"%s" similarities:' % art)
- for art in sims:
- mess = str(' - {score:0>2d} {artist}'.format(**art))
- print(mess)
- return True
-
- def _remove_sim(self, art1_db, art2_db):
- """Remove single similarity between two artists."""
- db = simadb.SimaDB(db_path=self.dbfile)
- similarity = db._get_artist_match(art1_db[0], art2_db[0])
- if similarity == 0:
- return False
- db._remove_relation_between_2_artist(art1_db[0], art2_db[0])
- mess = 'Remove: "{0}" "{1}:{2:0>2d}"'.format(art1_db[1], art2_db[1],
- similarity)
- print(mess)
- return True
-
- def _revert_similarity(self, sim_formated):
- """Revert similarity string (for reciprocal editing - add)."""
- main_art = sim_formated[0]
- similars = sim_formated[1]
- for similar in similars:
- yield (similar.get('artist'),
- [{'artist':main_art, 'score':similar.get('score')}])
-
def bl_artist(self):
"""Black list artist"""
mpd_cli = self._get_mpd_client()
- if not mpd_cli:
- return False
artists_list = mpd_cli.list('artist')
# Unicode cli given artist name
cli_artist_to_bl = self.options.bl_art
def bl_current_artist(self):
"""Black list current artist"""
mpd_cli = self._get_mpd_client()
- if not mpd_cli:
- return False
artist = mpd_cli.currentsong().get('artist', '')
if not artist:
print('No artist found.')
def bl_current_album(self):
"""Black list current artist"""
mpd_cli = self._get_mpd_client()
- if not mpd_cli:
- return False
track = Track(**mpd_cli.currentsong())
if not track.album:
print('No album set for this track: %s' % track)
def bl_current_track(self):
"""Black list current artist"""
mpd_cli = self._get_mpd_client()
- if not mpd_cli:
- return False
track = Track(**mpd_cli.currentsong())
print('Black listing track: %s' % track)
db = simadb.SimaDB(db_path=self.dbfile)
db.clean_database()
print('done.')
- def view(self):
- """Print out entries for an artist."""
- art = self.options.view
- db = simadb.SimaDB(db_path=self.dbfile)
- art_db = self._get_art_from_db(art)
- if not art_db: return
- if not self._print_main_art():
- mess = str('"%s" present in DB but not as a main artist' % art)
- print(mess)
- else: print('')
- art_rev = list([])
- [art_rev.append(a) for a in db._get_reverse_similar_artists_from_db(art_db[0])]
- if not art_rev: return
- mess = str('%s" appears as similar for the following artist(s): %s' %
- (art,', '.join(art_rev)))
- print(mess)
- [self._print_main_art(a) for a in art_rev]
-
- def view_all(self):
- """Print out all entries."""
- db = simadb.SimaDB(db_path=self.dbfile)
- for art in db.get_artists():
- if not art[0]: continue
- self._print_main_art(art=art[0])
-
def view_bl(self):
"""Print out black list."""
# TODO: enhance output formating
for bl_e in db.get_black_list():
print('\t# '.join([str(e) for e in bl_e]))
- def remove_similarity(self):
- """Remove entry"""
- cli_sim = self.options.remove_sim
- pattern = '^([^,]+?),([^,]+?,?)$'
- regexp = re.compile(pattern, re.U).match(cli_sim)
- if not regexp:
- print('ERROR: similarity badly formated: "%s"' % cli_sim, file=stderr)
- print('USAGE: A single relation between two artists is expected here.', file=stderr)
- print('USAGE: "main artist,similar artist"', file=stderr)
- exit(1)
- arts = cli_sim.split(',')
- if len(arts) != 2:
- print('ERROR: unknown error in similarity format', file=stderr)
- print('USAGE: "main artist,similar artist"', file=stderr)
- exit(1)
- art1_db = self._get_art_from_db(arts[0].strip())
- art2_db = self._get_art_from_db(arts[1].strip())
- if not art1_db or not art2_db: return
- self._remove_sim(art1_db, art2_db)
- if not self.options.reciprocal:
- return
- self._remove_sim(art2_db, art1_db)
-
- def remove_artist(self):
- """ Remove artist in the DB."""
- deep = False
- art = self.options.remove_art
- db = simadb.SimaDB(db_path=self.dbfile)
- art_db = self._get_art_from_db(art)
- if not art_db: return False
- print('Removing "%s" from database' % art)
- if self.options.reciprocal:
- print('reciprocal option used, performing deep remove!')
- deep = True
- db._remove_artist(art_db[0], deep=deep)
-
def remove_black_list_entry(self):
""""""
db = simadb.SimaDB(db_path=self.dbfile)
db._remove_bl(int(self.options.remove_bl))
- def write_simi(self):
- """Write similarity to DB.
- """
- self._create_db()
- sim_formated = self._parse_similarity()
- print('About to update DB with: "%s": %s' % sim_formated)
- db = simadb.SimaDB(db_path=self.dbfile)
- db._update_similar_artists(*sim_formated)
- if self.options.reciprocal:
- print('...and with reciprocal combinations as well.')
- for sim_formed_rec in self._revert_similarity(sim_formated):
- db._update_similar_artists(*sim_formed_rec)
-
def main(self):
"""
Parse command line and run actions.
if self.options.dbfile:
self.dbfile = self.options.dbfile
print('Using db file: %s' % self.dbfile)
- if self.options.reciprocal:
- print('Editing reciprocal similarity')
if self.options.bl_art:
self.bl_artist()
return
if self.options.remove_bl:
self.remove_black_list_entry()
return
- if self.options.similarity:
- self._control_similarity()
- self.write_simi()
- return
- if self.options.remove_art:
- self.remove_artist()
- return
- if self.options.remove_sim:
- self.remove_similarity()
- return
- if self.options.view:
- self.view()
- return
- if self.options.view_all:
- self.view_all()
if self.options.do_purge_hist:
self.purge_history()
exit(0)
-def main():
- SimaDB_CLI()
-
# Script starts here
if __name__ == '__main__':
- main()
+ try:
+ SimaDB_CLI()
+ except Exception as err:
+ print(err)
+ exit(1)
# VIM MODLINE
# vim: ai ts=4 sw=4 sts=4 expandtab