|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""Song management."""
|
|
|
|
|
|
|
|
from unidecode import unidecode
|
|
|
|
import glob
|
|
|
|
import locale
|
|
|
|
import os.path
|
|
|
|
import re
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from songbook_core.authors import processauthors
|
|
|
|
from songbook_core.plastex import parsetex
|
|
|
|
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
|
|
class Song(object):
|
|
|
|
"""Song management"""
|
|
|
|
|
|
|
|
#: Ordre de tri
|
|
|
|
sort = []
|
|
|
|
#: Préfixes à ignorer pour le tri par titres
|
|
|
|
prefixes = []
|
|
|
|
#: Dictionnaire des options pour le traitement des auteurs
|
|
|
|
authwords = {"after": [], "ignore": [], "sep": []}
|
|
|
|
|
|
|
|
def __init__(self, path, languages, titles, args):
|
|
|
|
self.titles = titles
|
|
|
|
self.normalized_titles = [
|
|
|
|
locale.strxfrm(
|
|
|
|
unprefixed_title(
|
|
|
|
unidecode(unicode(title, "utf-8")),
|
|
|
|
self.prefixes
|
|
|
|
)
|
|
|
|
)
|
|
|
|
for title
|
|
|
|
in titles
|
|
|
|
]
|
|
|
|
self.args = args
|
|
|
|
self.path = path
|
|
|
|
self.languages = languages
|
|
|
|
if "by" in self.args.keys():
|
|
|
|
self.normalized_authors = [
|
|
|
|
locale.strxfrm(author)
|
|
|
|
for author
|
|
|
|
in processauthors(self.args["by"], **self.authwords)
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
self.normalized_authors = []
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return repr((self.titles, self.args, self.path))
|
|
|
|
|
|
|
|
def __cmp__(self, other):
|
|
|
|
if not isinstance(other, Song):
|
|
|
|
return NotImplemented
|
|
|
|
for key in self.sort:
|
|
|
|
if key == "@title":
|
|
|
|
self_key = self.normalized_titles
|
|
|
|
other_key = other.normalized_titles
|
|
|
|
elif key == "@path":
|
|
|
|
self_key = locale.strxfrm(self.path)
|
|
|
|
other_key = locale.strxfrm(other.path)
|
|
|
|
elif key == "by":
|
|
|
|
self_key = self.normalized_authors
|
|
|
|
other_key = other.normalized_authors
|
|
|
|
else:
|
|
|
|
self_key = locale.strxfrm(self.args.get(key, ""))
|
|
|
|
other_key = locale.strxfrm(other.args.get(key, ""))
|
|
|
|
|
|
|
|
if self_key < other_key:
|
|
|
|
return -1
|
|
|
|
elif self_key > other_key:
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
def unprefixed_title(title, prefixes):
|
|
|
|
"""Remove the first prefix of the list in the beginning of title (if any).
|
|
|
|
"""
|
|
|
|
for prefix in prefixes:
|
|
|
|
match = re.compile(r"^(%s)\b\s*(.*)$" % prefix).match(title)
|
|
|
|
if match:
|
|
|
|
return match.group(2)
|
|
|
|
return title
|
|
|
|
|
|
|
|
|
|
|
|
class SongbookContent(object):
|
|
|
|
"""Manipulation et traitement de liste de chansons"""
|
|
|
|
|
|
|
|
def __init__(self, datadirs):
|
|
|
|
self.songdirs = [os.path.join(d, 'songs')
|
|
|
|
for d in datadirs]
|
|
|
|
self.content = [] # Sorted list of the content
|
|
|
|
|
|
|
|
def append_song(self, filename):
|
|
|
|
"""Ajout d'une chanson à la liste
|
|
|
|
|
|
|
|
Effets de bord : analyse syntaxique plus ou moins sommaire du fichier
|
|
|
|
pour en extraire et traiter certaines information (titre, langue,
|
|
|
|
album, etc.).
|
|
|
|
"""
|
|
|
|
LOGGER.debug('Parsing file "{}"…'.format(filename))
|
|
|
|
# Data extraction from the song with plastex
|
|
|
|
data = parsetex(filename)
|
|
|
|
song = Song(filename, data['languages'], data['titles'], data['args'])
|
|
|
|
self.content.append(("song", song))
|
|
|
|
|
|
|
|
def append(self, type, value):
|
|
|
|
""" Append a generic element to the content list"""
|
|
|
|
self.content.append((type, value))
|
|
|
|
|
|
|
|
def append_list(self, contentlist):
|
|
|
|
"""Ajoute une liste de chansons à la liste
|
|
|
|
|
|
|
|
L'argument est une liste de chaînes, représentant des noms de fichiers
|
|
|
|
sous la forme d'expressions régulières destinées à être analysées avec
|
|
|
|
le module glob.
|
|
|
|
"""
|
|
|
|
for type, elem in contentlist:
|
|
|
|
if type == "song":
|
|
|
|
# Add all the songs matching the regex
|
|
|
|
before = len(self.content)
|
|
|
|
for songdir in self.songdirs:
|
|
|
|
for filename in glob.iglob(os.path.join(songdir, elem)):
|
|
|
|
self.append_song(filename)
|
|
|
|
if len(self.content) > before:
|
|
|
|
break
|
|
|
|
if len(self.content) == before:
|
|
|
|
# No songs were added
|
|
|
|
LOGGER.warning(
|
|
|
|
"Expression '{}' did not match any file".format(elem)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
self.append(type, elem)
|
|
|
|
|
|
|
|
def languages(self):
|
|
|
|
"""Renvoie la liste des langues utilisées par les chansons"""
|
|
|
|
return set().union(*[set(song.languages) for type, song in self.content if type=="song"])
|