Added UI selector for including associated artists

This commit is contained in:
krateng 2023-10-18 10:58:46 +02:00
parent acf7402095
commit b9e3cd7624
7 changed files with 82 additions and 29 deletions

View File

@ -6,6 +6,8 @@ minor_release_name: "Nicole"
- "[Feature] Added basic support for albums" - "[Feature] Added basic support for albums"
- "[Feature] New start page" - "[Feature] New start page"
- "[Feature] Added UI for track-artist, track-album and album-artist association" - "[Feature] Added UI for track-artist, track-album and album-artist association"
- "[Feature] Added inline UI for association and merging in chart lists"
- "[Feature] Added UI selector for including associated artists"
- "[Performance] Improved image rendering" - "[Performance] Improved image rendering"
- "[Bugfix] Fixed configuration of time format" - "[Bugfix] Fixed configuration of time format"
- "[Bugfix] Fixed search on manual scrobble page" - "[Bugfix] Fixed search on manual scrobble page"

View File

@ -309,8 +309,9 @@ def associate_tracks_to_album(target_id,source_ids):
@waitfordb @waitfordb
def get_scrobbles(dbconn=None,**keys): def get_scrobbles(dbconn=None,**keys):
(since,to) = keys.get('timerange').timestamps() (since,to) = keys.get('timerange').timestamps()
associated = keys.get('associated',False)
if 'artist' in keys: if 'artist' in keys:
result = sqldb.get_scrobbles_of_artist(artist=keys['artist'],since=since,to=to,dbconn=dbconn) result = sqldb.get_scrobbles_of_artist(artist=keys['artist'],since=since,to=to,associated=associated,dbconn=dbconn)
elif 'track' in keys: elif 'track' in keys:
result = sqldb.get_scrobbles_of_track(track=keys['track'],since=since,to=to,dbconn=dbconn) result = sqldb.get_scrobbles_of_track(track=keys['track'],since=since,to=to,dbconn=dbconn)
elif 'album' in keys: elif 'album' in keys:
@ -324,8 +325,9 @@ def get_scrobbles(dbconn=None,**keys):
@waitfordb @waitfordb
def get_scrobbles_num(dbconn=None,**keys): def get_scrobbles_num(dbconn=None,**keys):
(since,to) = keys.get('timerange').timestamps() (since,to) = keys.get('timerange').timestamps()
associated = keys.get('associated',False)
if 'artist' in keys: if 'artist' in keys:
result = len(sqldb.get_scrobbles_of_artist(artist=keys['artist'],since=since,to=to,resolve_references=False,dbconn=dbconn)) result = len(sqldb.get_scrobbles_of_artist(artist=keys['artist'],since=since,to=to,associated=associated,resolve_references=False,dbconn=dbconn))
elif 'track' in keys: elif 'track' in keys:
result = len(sqldb.get_scrobbles_of_track(track=keys['track'],since=since,to=to,resolve_references=False,dbconn=dbconn)) result = len(sqldb.get_scrobbles_of_track(track=keys['track'],since=since,to=to,resolve_references=False,dbconn=dbconn))
elif 'album' in keys: elif 'album' in keys:
@ -370,14 +372,15 @@ def get_tracks_without_album(dbconn=None,resolve_ids=True):
@waitfordb @waitfordb
def get_charts_artists(dbconn=None,resolve_ids=True,**keys): def get_charts_artists(dbconn=None,resolve_ids=True,**keys):
(since,to) = keys.get('timerange').timestamps() (since,to) = keys.get('timerange').timestamps()
result = sqldb.count_scrobbles_by_artist(since=since,to=to,resolve_ids=resolve_ids,dbconn=dbconn) associated = keys.get('associated',True)
result = sqldb.count_scrobbles_by_artist(since=since,to=to,resolve_ids=resolve_ids,associated=associated,dbconn=dbconn)
return result return result
@waitfordb @waitfordb
def get_charts_tracks(dbconn=None,resolve_ids=True,**keys): def get_charts_tracks(dbconn=None,resolve_ids=True,**keys):
(since,to) = keys.get('timerange').timestamps() (since,to) = keys.get('timerange').timestamps()
if 'artist' in keys: if 'artist' in keys:
result = sqldb.count_scrobbles_by_track_of_artist(since=since,to=to,artist=keys['artist'],resolve_ids=resolve_ids,dbconn=dbconn) result = sqldb.count_scrobbles_by_track_of_artist(since=since,to=to,artist=keys['artist'],associated=keys.get('associated',False),resolve_ids=resolve_ids,dbconn=dbconn)
elif 'album' in keys: elif 'album' in keys:
result = sqldb.count_scrobbles_by_track_of_album(since=since,to=to,album=keys['album'],resolve_ids=resolve_ids,dbconn=dbconn) result = sqldb.count_scrobbles_by_track_of_album(since=since,to=to,album=keys['album'],resolve_ids=resolve_ids,dbconn=dbconn)
else: else:
@ -388,7 +391,7 @@ def get_charts_tracks(dbconn=None,resolve_ids=True,**keys):
def get_charts_albums(dbconn=None,resolve_ids=True,**keys): def get_charts_albums(dbconn=None,resolve_ids=True,**keys):
(since,to) = keys.get('timerange').timestamps() (since,to) = keys.get('timerange').timestamps()
if 'artist' in keys: if 'artist' in keys:
result = sqldb.count_scrobbles_by_album_of_artist(since=since,to=to,artist=keys['artist'],resolve_ids=resolve_ids,dbconn=dbconn) result = sqldb.count_scrobbles_by_album_of_artist(since=since,to=to,artist=keys['artist'],associated=keys.get('associated',False),resolve_ids=resolve_ids,dbconn=dbconn)
else: else:
result = sqldb.count_scrobbles_by_album(since=since,to=to,resolve_ids=resolve_ids,dbconn=dbconn) result = sqldb.count_scrobbles_by_album(since=since,to=to,resolve_ids=resolve_ids,dbconn=dbconn)
return result return result
@ -629,15 +632,16 @@ def get_featured(dbconn=None):
alltime() alltime()
] ]
funcs = { funcs = {
"artist": get_charts_artists, "artist": (get_charts_artists,{'associated':False}),
"album": get_charts_albums, "album": (get_charts_albums,{}),
"track": get_charts_tracks "track": (get_charts_tracks,{})
} }
result = {t:None for t in funcs} result = {t:None for t in funcs}
for entity_type in funcs: for entity_type in funcs:
for r in ranges: for r in ranges:
chart = funcs[entity_type](timerange=r) func,kwargs = funcs[entity_type]
chart = func(timerange=r,**kwargs)
if chart: if chart:
result[entity_type] = chart[0][entity_type] result[entity_type] = chart[0][entity_type]
break break

View File

@ -763,19 +763,23 @@ def merge_albums(target_id,source_ids,dbconn=None):
@cached_wrapper @cached_wrapper
@connection_provider @connection_provider
def get_scrobbles_of_artist(artist,since=None,to=None,resolve_references=True,dbconn=None): def get_scrobbles_of_artist(artist,since=None,to=None,resolve_references=True,associated=False,dbconn=None):
if since is None: since=0 if since is None: since=0
if to is None: to=now() if to is None: to=now()
artist_id = get_artist_id(artist,dbconn=dbconn) if associated:
artist_ids = get_associated_artists(artist,resolve_ids=False,dbconn=dbconn) + [get_artist_id(artist,dbconn=dbconn)]
else:
artist_ids = [get_artist_id(artist,dbconn=dbconn)]
jointable = sql.join(DB['scrobbles'],DB['trackartists'],DB['scrobbles'].c.track_id == DB['trackartists'].c.track_id) jointable = sql.join(DB['scrobbles'],DB['trackartists'],DB['scrobbles'].c.track_id == DB['trackartists'].c.track_id)
op = jointable.select().where( op = jointable.select().where(
DB['scrobbles'].c.timestamp<=to, DB['scrobbles'].c.timestamp<=to,
DB['scrobbles'].c.timestamp>=since, DB['scrobbles'].c.timestamp>=since,
DB['trackartists'].c.artist_id==artist_id DB['trackartists'].c.artist_id.in_(artist_ids)
).order_by(sql.asc('timestamp')) ).order_by(sql.asc('timestamp'))
result = dbconn.execute(op).all() result = dbconn.execute(op).all()
@ -911,7 +915,7 @@ def get_tracks(dbconn=None):
@cached_wrapper @cached_wrapper
@connection_provider @connection_provider
def count_scrobbles_by_artist(since,to,resolve_ids=True,dbconn=None): def count_scrobbles_by_artist(since,to,associated=True,resolve_ids=True,dbconn=None):
jointable = sql.join( jointable = sql.join(
DB['scrobbles'], DB['scrobbles'],
DB['trackartists'], DB['trackartists'],
@ -924,18 +928,24 @@ def count_scrobbles_by_artist(since,to,resolve_ids=True,dbconn=None):
DB['trackartists'].c.artist_id == DB['associated_artists'].c.source_artist, DB['trackartists'].c.artist_id == DB['associated_artists'].c.source_artist,
isouter=True isouter=True
) )
if associated:
artistselect = sql.func.coalesce(DB['associated_artists'].c.target_artist,DB['trackartists'].c.artist_id)
else:
artistselect = DB['trackartists'].c.artist_id
op = sql.select( op = sql.select(
sql.func.count(sql.func.distinct(DB['scrobbles'].c.timestamp)).label('count'), sql.func.count(sql.func.distinct(DB['scrobbles'].c.timestamp)).label('count'),
# only count distinct scrobbles - because of artist replacement, we could end up # only count distinct scrobbles - because of artist replacement, we could end up
# with two artists of the same scrobble counting it twice for the same artist # with two artists of the same scrobble counting it twice for the same artist
# e.g. Irene and Seulgi adding two scrobbles to Red Velvet for one real scrobble # e.g. Irene and Seulgi adding two scrobbles to Red Velvet for one real scrobble
sql.func.coalesce(DB['associated_artists'].c.target_artist,DB['trackartists'].c.artist_id).label('artist_id') artistselect.label('artist_id')
# use the replaced artist as artist to count if it exists, otherwise original one # use the replaced artist as artist to count if it exists, otherwise original one
).select_from(jointable2).where( ).select_from(jointable2).where(
DB['scrobbles'].c.timestamp<=to, DB['scrobbles'].c.timestamp<=to,
DB['scrobbles'].c.timestamp>=since DB['scrobbles'].c.timestamp>=since
).group_by( ).group_by(
sql.func.coalesce(DB['associated_artists'].c.target_artist,DB['trackartists'].c.artist_id) artistselect
).order_by(sql.desc('count')) ).order_by(sql.desc('count'))
result = dbconn.execute(op).all() result = dbconn.execute(op).all()
@ -1001,9 +1011,12 @@ def count_scrobbles_by_album(since,to,resolve_ids=True,dbconn=None):
@connection_provider @connection_provider
# this ranks the albums of that artist, not albums the artist appears on - even scrobbles # this ranks the albums of that artist, not albums the artist appears on - even scrobbles
# of tracks the artist is not part of! # of tracks the artist is not part of!
def count_scrobbles_by_album_of_artist(since,to,artist,resolve_ids=True,dbconn=None): def count_scrobbles_by_album_of_artist(since,to,artist,associated=False,resolve_ids=True,dbconn=None):
artist_id = get_artist_id(artist,dbconn=dbconn) if associated:
artist_ids = get_associated_artists(artist,resolve_ids=False,dbconn=dbconn) + [get_artist_id(artist,dbconn=dbconn)]
else:
artist_ids = [get_artist_id(artist,dbconn=dbconn)]
jointable = sql.join( jointable = sql.join(
DB['scrobbles'], DB['scrobbles'],
@ -1022,7 +1035,7 @@ def count_scrobbles_by_album_of_artist(since,to,artist,resolve_ids=True,dbconn=N
).select_from(jointable2).where( ).select_from(jointable2).where(
DB['scrobbles'].c.timestamp<=to, DB['scrobbles'].c.timestamp<=to,
DB['scrobbles'].c.timestamp>=since, DB['scrobbles'].c.timestamp>=since,
DB['albumartists'].c.artist_id == artist_id DB['albumartists'].c.artist_id.in_(artist_ids)
).group_by(DB['tracks'].c.album_id).order_by(sql.desc('count')) ).group_by(DB['tracks'].c.album_id).order_by(sql.desc('count'))
result = dbconn.execute(op).all() result = dbconn.execute(op).all()
@ -1038,9 +1051,12 @@ def count_scrobbles_by_album_of_artist(since,to,artist,resolve_ids=True,dbconn=N
@connection_provider @connection_provider
# this ranks the tracks of that artist by the album they appear on - even when the album # this ranks the tracks of that artist by the album they appear on - even when the album
# is not the artist's # is not the artist's
def count_scrobbles_of_artist_by_album(since,to,artist,resolve_ids=True,dbconn=None): def count_scrobbles_of_artist_by_album(since,to,artist,associated=False,resolve_ids=True,dbconn=None):
artist_id = get_artist_id(artist,dbconn=dbconn) if associated:
artist_ids = get_associated_artists(artist,resolve_ids=False,dbconn=dbconn) + [get_artist_id(artist,dbconn=dbconn)]
else:
artist_ids = [get_artist_id(artist,dbconn=dbconn)]
jointable = sql.join( jointable = sql.join(
DB['scrobbles'], DB['scrobbles'],
@ -1059,7 +1075,7 @@ def count_scrobbles_of_artist_by_album(since,to,artist,resolve_ids=True,dbconn=N
).select_from(jointable2).where( ).select_from(jointable2).where(
DB['scrobbles'].c.timestamp<=to, DB['scrobbles'].c.timestamp<=to,
DB['scrobbles'].c.timestamp>=since, DB['scrobbles'].c.timestamp>=since,
DB['trackartists'].c.artist_id == artist_id DB['trackartists'].c.artist_id.in_(artist_ids)
).group_by(DB['tracks'].c.album_id).order_by(sql.desc('count')) ).group_by(DB['tracks'].c.album_id).order_by(sql.desc('count'))
result = dbconn.execute(op).all() result = dbconn.execute(op).all()
@ -1074,9 +1090,12 @@ def count_scrobbles_of_artist_by_album(since,to,artist,resolve_ids=True,dbconn=N
@cached_wrapper @cached_wrapper
@connection_provider @connection_provider
def count_scrobbles_by_track_of_artist(since,to,artist,resolve_ids=True,dbconn=None): def count_scrobbles_by_track_of_artist(since,to,artist,associated=False,resolve_ids=True,dbconn=None):
artist_id = get_artist_id(artist,dbconn=dbconn) if associated:
artist_ids = get_associated_artists(artist,resolve_ids=False,dbconn=dbconn) + [get_artist_id(artist,dbconn=dbconn)]
else:
artist_ids = [get_artist_id(artist,dbconn=dbconn)]
jointable = sql.join( jointable = sql.join(
DB['scrobbles'], DB['scrobbles'],
@ -1090,7 +1109,7 @@ def count_scrobbles_by_track_of_artist(since,to,artist,resolve_ids=True,dbconn=N
).select_from(jointable).filter( ).select_from(jointable).filter(
DB['scrobbles'].c.timestamp<=to, DB['scrobbles'].c.timestamp<=to,
DB['scrobbles'].c.timestamp>=since, DB['scrobbles'].c.timestamp>=since,
DB['trackartists'].c.artist_id==artist_id DB['trackartists'].c.artist_id.in_(artist_ids)
).group_by(DB['scrobbles'].c.track_id).order_by(sql.desc('count')) ).group_by(DB['scrobbles'].c.track_id).order_by(sql.desc('count'))
result = dbconn.execute(op).all() result = dbconn.execute(op).all()
@ -1301,7 +1320,7 @@ def get_albums_map(album_ids,dbconn=None):
@cached_wrapper @cached_wrapper
@connection_provider @connection_provider
def get_associated_artists(*artists,dbconn=None): def get_associated_artists(*artists,resolve_ids=True,dbconn=None):
artist_ids = [get_artist_id(a,dbconn=dbconn) for a in artists] artist_ids = [get_artist_id(a,dbconn=dbconn) for a in artists]
jointable = sql.join( jointable = sql.join(
@ -1319,8 +1338,11 @@ def get_associated_artists(*artists,dbconn=None):
) )
result = dbconn.execute(op).all() result = dbconn.execute(op).all()
artists = artists_db_to_dict(result,dbconn=dbconn) if resolve_ids:
return artists artists = artists_db_to_dict(result,dbconn=dbconn)
return artists
else:
return [a.id for a in result]
@cached_wrapper @cached_wrapper
@connection_provider @connection_provider

View File

@ -72,6 +72,10 @@ def update_jinja_environment():
{"identifier":"longtrailing","replacekeys":{"trail":3},"localisation":"Long Trailing"}, {"identifier":"longtrailing","replacekeys":{"trail":3},"localisation":"Long Trailing"},
{"identifier":"inert","replacekeys":{"trail":10},"localisation":"Inert","heavy":True}, {"identifier":"inert","replacekeys":{"trail":10},"localisation":"Inert","heavy":True},
{"identifier":"cumulative","replacekeys":{"trail":math.inf},"localisation":"Cumulative","heavy":True} {"identifier":"cumulative","replacekeys":{"trail":math.inf},"localisation":"Cumulative","heavy":True}
],
"xassociated": [
{"identifier":"include_associated","replacekeys":{"associated":True},"localisation":"Associated"},
{"identifier":"exclude_associated","replacekeys":{"associated":False},"localisation":"Exclusive"}
] ]
} }

View File

@ -31,12 +31,18 @@ def uri_to_internal(keys,forceTrack=False,forceArtist=False,forceAlbum=False,api
filterkeys = {"track":{"artists":keys.getall("artist"),"title":keys.get("title")}} filterkeys = {"track":{"artists":keys.getall("artist"),"title":keys.get("title")}}
elif type == "artist": elif type == "artist":
filterkeys = {"artist":keys.get("artist")} filterkeys = {"artist":keys.get("artist")}
if "associated" in keys: filterkeys["associated"] = True filterkeys["associated"] = (keys.get('associated','no').lower() == 'yes')
# associated is only used for filtering by artist, to indicate that we include associated artists
# for actual artist charts, to show that we want to count them, use 'unified'
elif type == "album": elif type == "album":
filterkeys = {"album":{"artists":keys.getall("artist"),"albumtitle":keys.get("albumtitle") or keys.get("title")}} filterkeys = {"album":{"artists":keys.getall("artist"),"albumtitle":keys.get("albumtitle") or keys.get("title")}}
else: else:
filterkeys = {} filterkeys = {}
# this can be the case regardless of actual entity filter
# e.g if i get scrobbles of an artist associated tells me i want all scrobbles by associated artists
# but seeing the artist charts (wich have no filterkeys) also is affected by this
# 2 # 2
limitkeys = {} limitkeys = {}
since,to,within = None,None,None since,to,within = None,None,None
@ -105,6 +111,7 @@ def internal_to_uri(keys):
urikeys.append("artist",a) urikeys.append("artist",a)
urikeys.append("albumtitle",keys["album"]["albumtitle"]) urikeys.append("albumtitle",keys["album"]["albumtitle"])
#time #time
if "timerange" in keys: if "timerange" in keys:
keydict = keys["timerange"].urikeys() keydict = keys["timerange"].urikeys()

View File

@ -3,7 +3,7 @@
{% macro desc(filterkeys,limitkeys,prefix="by") %} {% macro desc(filterkeys,limitkeys,prefix="by") %}
{% if filterkeys.get('artist') is not none %} {% if filterkeys.get('artist') is not none %}
{{ prefix }} {{ links.link(filterkeys.get('artist')) }} {{ prefix }} {{ links.link(filterkeys.get('artist')) }}{% if filterkeys.get('associated') %} (and associated artists){% endif %}
{% elif filterkeys.get('track') is not none %} {% elif filterkeys.get('track') is not none %}
of {{ links.link(filterkeys.get('track')) }} of {{ links.link(filterkeys.get('track')) }}
by {{ links.links(filterkeys["track"]["artists"]) }} by {{ links.links(filterkeys["track"]["artists"]) }}

View File

@ -61,3 +61,17 @@
</div> </div>
{% endif %} {% endif %}
{% if 'artist' in filterkeys %}
<div>
{% for o in xassociated %}
{% if o.replacekeys | map('compare_key_in_dicts',o.replacekeys,allkeys) | alltrue %}
<span style='opacity:0.5;'>{{ o.localisation }}</span>
{% else %}
<a href='{{ mlj_uri.create_uri("",allkeys,o.replacekeys) }}'><span>{{ o.localisation }}</span></a>
{% endif %}
{{ "|" if not loop.last }}
{% endfor %}
</div>
{% endif %}