diff --git a/config.sample b/config.sample index 1232585..d7788b9 100644 --- a/config.sample +++ b/config.sample @@ -1,5 +1,6 @@ [base] -; A database URI. See the 'schema' folder for schema creation scripts +; A database URI. See the 'schema' folder for schema creation scripts. Note that +; you don't have to run these scripts yourself. ; Default: sqlite:////tmp/supysonic/supysonic.db ;database_uri = sqlite:////var/supysonic/supysonic.db ;database_uri = mysql://supysonic:supysonic@localhost/supysonic @@ -35,6 +36,10 @@ log_level = WARNING ; Enable the administrative web interface. Default: on ;mount_webui = on +; Space separated list of prefixes that should be ignored on index endpoints +; Default: El La Le Las Les Los The +index_ignored_prefixes = El La Le Las Les Los The + [daemon] ; Socket file the daemon will listen on for incoming management commands ; Default: /tmp/supysonic/supysonic.sock diff --git a/docs/configuration.md b/docs/configuration.md index ea9308a..ecdbe60 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -106,6 +106,12 @@ purposes. Defaults to `on`. Note that setting this off will prevent users from defining a preferred transcoding format. Defaults to `on`. +`index_ignored_prefixes`: space separated list of prefixes that should be +ignored from artist names when returning their index. Example: if the word _The_ +is in this list, artist _The Rolling Stones_ will be listed under the letter _R_. +The match is case insensitive. +Defaults to `El La Le Las Les Los The`. + ```ini [webapp] ; Optional cache directory. Default: /tmp/supysonic @@ -130,6 +136,10 @@ log_level = WARNING ; Enable the administrative web interface. Default: on ;mount_webui = on + +; Space separated list of prefixes that should be ignored on index endpoints +; Default: El La Le Las Les Los The +index_ignored_prefixes = El La Le Las Les Los The ``` ## `[daemon]` section diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index f0d976b..bb47abf 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -5,10 +5,11 @@ # # Distributed under terms of the GNU AGPLv3 license. +import re import string import uuid -from flask import request +from flask import current_app, request from pony.orm import ObjectNotFound, select, count from ..db import Folder, Artist, Album, Track @@ -29,6 +30,26 @@ def list_folders(): ) +def build_ignored_articles_pattern(): + articles = current_app.config["WEBAPP"]["index_ignored_prefixes"] + if articles is None: + return None + + articles = articles.split() + if not articles: + return None + + return r"^(" + r" |".join(re.escape(a) for a in articles) + r" )" + + +def ignored_articles_str(): + articles = current_app.config["WEBAPP"]["index_ignored_prefixes"] + if articles is None: + return "" + + return " ".join(articles.split()) + + @api.route("/getIndexes.view", methods=["GET", "POST"]) def list_indexes(): musicFolderId = request.values.get("musicFolderId") @@ -49,7 +70,10 @@ def list_indexes(): last_modif = max(map(lambda f: f.last_scan, folders)) if ifModifiedSince is not None and last_modif < ifModifiedSince: return request.formatter( - "indexes", dict(lastModified=last_modif * 1000, ignoredArticles="") + "indexes", + dict( + lastModified=last_modif * 1000, ignoredArticles=ignored_articles_str() + ), ) # The XSD lies, we don't return artists but a directory structure @@ -60,8 +84,12 @@ def list_indexes(): children += f.tracks.select()[:] indexes = dict() + pattern = build_ignored_articles_pattern() for artist in artists: - index = artist.name[0].upper() + name = artist.name + if pattern: + name = re.sub(pattern, "", name, flags=re.I) + index = name[0].upper() if index in string.digits: index = "#" elif index not in string.ascii_letters: @@ -70,19 +98,19 @@ def list_indexes(): if index not in indexes: indexes[index] = [] - indexes[index].append(artist) + indexes[index].append((artist, name)) return request.formatter( "indexes", dict( lastModified=last_modif * 1000, - ignoredArticles="", + ignoredArticles=ignored_articles_str(), index=[ dict( name=k, artist=[ a.as_subsonic_artist(request.user) - for a in sorted(v, key=lambda a: a.name.lower()) + for a, _ in sorted(v, key=lambda t: t[1].lower()) ], ) for k, v in sorted(indexes.items()) @@ -122,8 +150,12 @@ def list_genres(): def list_artists(): # According to the API page, there are no parameters? indexes = dict() + pattern = build_ignored_articles_pattern() for artist in Artist.select(): - index = artist.name[0].upper() if artist.name else "?" + name = artist.name or "?" + if pattern: + name = re.sub(pattern, "", name, flags=re.I) + index = name[0].upper() if index in string.digits: index = "#" elif index not in string.ascii_letters: @@ -132,18 +164,18 @@ def list_artists(): if index not in indexes: indexes[index] = [] - indexes[index].append(artist) + indexes[index].append((artist, name)) return request.formatter( "artists", dict( - ignoredArticles="", + ignoredArticles=ignored_articles_str(), index=[ dict( name=k, artist=[ a.as_subsonic_artist(request.user) - for a in sorted(v, key=lambda a: a.name.lower()) + for a, _ in sorted(v, key=lambda t: t[1].lower()) ], ) for k, v in sorted(indexes.items()) diff --git a/supysonic/config.py b/supysonic/config.py index 56c9acb..d86d09d 100644 --- a/supysonic/config.py +++ b/supysonic/config.py @@ -38,6 +38,7 @@ class DefaultConfig(object): "log_level": "WARNING", "mount_webui": True, "mount_api": True, + "index_ignored_prefixes": "El La Le Las Les Los The", } DAEMON = { "socket": r"\\.\pipe\supysonic"