From ca1f5432fbc0dd41d482405f2bdeaba77bfaa6d7 Mon Sep 17 00:00:00 2001 From: darrell-k Date: Wed, 18 Dec 2024 17:40:16 +0000 Subject: [PATCH 1/9] Sorting by role, WIP --- Slim/Control/Queries.pm | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index 01c453d72ba..6497d46b9c6 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -292,14 +292,19 @@ sub albumsQuery { my $releaseType = $request->getParam('release_type'); my $libraryID = Slim::Music::VirtualLibraries->getRealId($request->getParam('library_id')); my $year = $request->getParam('year'); - my $sort = $request->getParam('sort') || ($roleID ? 'artistalbum' : 'album'); # a work_id of -1 would mean "all works" my $work = $request->getParam('work_id'); my $composerID = $request->getParam('composer_id'); my $fromSearch = $request->getParam('from_search'); + my $sort = $request->getParam('sort') || ($roleID && !$work ? 'artistalbum' : 'album'); my $ignoreNewAlbumsCache = $search || $compilation || $contributorID || $genreID || $trackID || $albumID || $year || Slim::Music::Import->stillScanning(); + my $rolesort; + if ( defined $sort && Slim::Schema::Contributor->roleToContributorMap()->{$sort} ) { + $rolesort = $sort; + $sort = 'album'; + } # FIXME: missing genrealbum, genreartistalbum if ($request->paramNotOneOfIfDefined($sort, ['new', 'changed', 'album', 'artflow', 'artistalbum', 'yearalbum', 'yearartistalbum', 'random' ])) { $request->setStatusBadParams(); @@ -579,6 +584,18 @@ sub albumsQuery { } } + if ( $rolesort ) { + $sql .= 'JOIN tracks ON tracks.album = albums.id ' unless $sql =~ /JOIN tracks/; + $sql .= "LEFT JOIN contributor_track AS rolesort_track ON rolesort_track.track = tracks.id AND rolesort_track.role = $rolesort "; + $sql .= 'LEFT JOIN contributors AS rolesort ON rolesort.id = rolesort_track.contributor '; + my $col = 'GROUP_CONCAT(DISTINCT rolesort.name)'; + $c->{$col} = 1; + $as->{$col} = 'rolesort.name'; + $col = 'GROUP_CONCAT(DISTINCT rolesort.id)'; + $c->{$col} = 1; + $as->{$col} = 'rolesort.id'; + $order_by = "CASE WHEN GROUP_CONCAT(DISTINCT rolesort.namesort) IS NULL THEN 1 ELSE 0 END, GROUP_CONCAT(DISTINCT rolesort.namesort) $collate, " . $order_by; + } if ( $tags =~ /l/ ) { # title/disc/discc is needed to construct (N of M) title @@ -802,6 +819,7 @@ sub albumsQuery { utf8::decode( $c->{'composer.name'} ) if exists $c->{'composer.name'}; utf8::decode( $c->{'tracks.performance'} ) if exists $c->{'tracks.performance'}; utf8::decode( $c->{'contributors.name'} ) if exists $c->{'contributors.name'}; + utf8::decode( $c->{'rolesort.name'} ) if exists $c->{'rolesort.name'}; $request->addResultLoop($loopname, $chunkCount, 'id', $c->{'albums.id'}); $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'work_id', $c->{'tracks.work'}); $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'work_name', $c->{'works.title'}); @@ -887,6 +905,12 @@ sub albumsQuery { unshift @artists, $c->{'contributors.name'} if $c->{'contributors.name'}; unshift @artistIds, $c->{'albums.contributor'} if $c->{'albums.contributor'}; } + # if role_sort specified, put the role artist at the top of the list + if ($c->{'rolesort.name'}) { + unshift @artists, $c->{'rolesort.name'} if $c->{'rolesort.name'}; + unshift @artistIds, $c->{'rolesort.id'} if $c->{'rolesort.id'}; + } + @artists = Slim::Utils::Misc::uniq(@artists); @artistIds = Slim::Utils::Misc::uniq(@artistIds); From 4d7dff879d9c5408cf316fa189293e5545aecf3e Mon Sep 17 00:00:00 2001 From: darrell-k Date: Thu, 19 Dec 2024 17:29:57 +0000 Subject: [PATCH 2/9] handle multiple albums in rolesQuery --- Slim/Control/Queries.pm | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index 6497d46b9c6..15587d07bd8 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -293,7 +293,7 @@ sub albumsQuery { my $libraryID = Slim::Music::VirtualLibraries->getRealId($request->getParam('library_id')); my $year = $request->getParam('year'); # a work_id of -1 would mean "all works" - my $work = $request->getParam('work_id'); + my $work = $request->getParam('work_id'); my $composerID = $request->getParam('composer_id'); my $fromSearch = $request->getParam('from_search'); my $sort = $request->getParam('sort') || ($roleID && !$work ? 'artistalbum' : 'album'); @@ -3201,8 +3201,11 @@ sub rolesQuery { } if (defined $albumID) { - push @{$w}, 'contributor_album.album = ?'; - push @{$p}, $albumID; + my @albums = split(',', $albumID); + if (scalar @albums) { + push @{$p}, @albums; + push @{$w}, 'contributor_album.album IN (' . join(', ', map {'?'} @albums) . ')'; + } } if (defined $year || defined $workID) { From 4bcde8d644ed7c20174035a9531f23b18e2a0032 Mon Sep 17 00:00:00 2001 From: darrell-k Date: Sat, 21 Dec 2024 12:44:02 +0000 Subject: [PATCH 3/9] add rolesort_name to result loop --- Slim/Control/Queries.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index 15587d07bd8..b0be3eeb0c6 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -825,6 +825,7 @@ sub albumsQuery { $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'work_name', $c->{'works.title'}); $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'composer', $c->{'composer.name'}); $request->addResultLoop($loopname, $chunkCount, 'performance', $c->{'tracks.performance'}||""); + $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'rolesort_name', $c->{'rolesort.name'}||""); my $favoritesUrl = $work ? sprintf('db:album.title=%s&contributor.name=%s&work.title=%s&composer.name=%s&track.performance=%s', From c9de8963862ab8fba5e926c9dd3755d8d2256666 Mon Sep 17 00:00:00 2001 From: darrell-k Date: Sat, 21 Dec 2024 23:26:51 +0000 Subject: [PATCH 4/9] oops! --- Slim/Control/Queries.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index b0be3eeb0c6..f4a485909f5 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -825,7 +825,7 @@ sub albumsQuery { $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'work_name', $c->{'works.title'}); $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'composer', $c->{'composer.name'}); $request->addResultLoop($loopname, $chunkCount, 'performance', $c->{'tracks.performance'}||""); - $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'rolesort_name', $c->{'rolesort.name'}||""); + $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'rolesort_name', $c->{'rolesort.name'}); my $favoritesUrl = $work ? sprintf('db:album.title=%s&contributor.name=%s&work.title=%s&composer.name=%s&track.performance=%s', From e9715a6dc1991d36c669b6d5ed6254fabbff93c8 Mon Sep 17 00:00:00 2001 From: darrell-k Date: Sun, 22 Dec 2024 15:26:23 +0000 Subject: [PATCH 5/9] limit rolesQuery results to the work, not whole album --- Slim/Control/Queries.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index f4a485909f5..fe9cd6ab31c 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -3237,7 +3237,7 @@ sub rolesQuery { my $dbh = Slim::Schema->dbh; - if (defined $trackID) { + if (defined $trackID || defined $workID) { $sql = sprintf($sql, 'DISTINCT contributor_track.role'); } else { $sql = sprintf($sql, 'DISTINCT contributor_album.role'); From e7ef2150fb312ebbb7b3d130959b85a6715f3215 Mon Sep 17 00:00:00 2001 From: darrell-k Date: Mon, 23 Dec 2024 16:26:21 +0000 Subject: [PATCH 6/9] More role query/sort enhancements --- Slim/Control/Queries.pm | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index fe9cd6ab31c..64424abdcf2 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -594,7 +594,11 @@ sub albumsQuery { $col = 'GROUP_CONCAT(DISTINCT rolesort.id)'; $c->{$col} = 1; $as->{$col} = 'rolesort.id'; + $col = 'GROUP_CONCAT(DISTINCT rolesort.namesort)'; + $c->{$col} = 1; + $as->{$col} = 'rolesort.namesort'; $order_by = "CASE WHEN GROUP_CONCAT(DISTINCT rolesort.namesort) IS NULL THEN 1 ELSE 0 END, GROUP_CONCAT(DISTINCT rolesort.namesort) $collate, " . $order_by; + $page_key = "COALESCE(SUBSTR(rolesort.namesort,1,1), '-')"; } if ( $tags =~ /l/ ) { @@ -867,7 +871,10 @@ sub albumsQuery { if ($tags =~ /s/) { #FIXME: see if multiple char textkey is doable for year/genre sort my $textKey; - if ($sort eq 'artflow' || $sort eq 'artistalbum') { + if ($rolesort) { + utf8::decode( $c->{'rolesort.namesort'} ) if exists $c->{'rolesort.namesort'}; + $textKey = $c->{'rolesort.namesort'} ? substr $c->{'rolesort.namesort'}, 0, 1 : '-'; + } elsif ($sort eq 'artflow' || $sort eq 'artistalbum') { utf8::decode( $c->{'contributors.namesort'} ) if exists $c->{'contributors.namesort'}; $textKey = substr $c->{'contributors.namesort'}, 0, 1; } elsif ( $sort eq 'album' ) { @@ -3164,6 +3171,7 @@ sub rolesQuery { my $albumID = $request->getParam('album_id'); my $trackID = $request->getParam('track_id'); my $workID = $request->getParam('work_id'); + my $genreID = $request->getParam('genre_id'); my $libraryID = Slim::Music::VirtualLibraries->getRealId($request->getParam('library_id')); my $tags = $request->getParam('tags') || ''; @@ -3209,7 +3217,7 @@ sub rolesQuery { } } - if (defined $year || defined $workID) { + if (defined $year || defined $workID || defined $genreID) { $sql .= 'JOIN contributor_track ON contributors.id = contributor_track.contributor '; $sql .= 'JOIN tracks ON tracks.id = contributor_track.track '; @@ -3225,6 +3233,12 @@ sub rolesQuery { push @{$p}, $workID; } } + if (defined $genreID) { + my @genreIDs = split(/,/, $genreID); + $sql .= 'JOIN genre_track ON genre_track.track = tracks.id '; + push @{$w}, 'genre_track.genre IN (' . join(', ', map {'?'} @genreIDs) . ')'; + push @{$p}, @genreIDs; + } } } @@ -3237,7 +3251,7 @@ sub rolesQuery { my $dbh = Slim::Schema->dbh; - if (defined $trackID || defined $workID) { + if (defined $trackID || defined $workID || defined $genreID) { $sql = sprintf($sql, 'DISTINCT contributor_track.role'); } else { $sql = sprintf($sql, 'DISTINCT contributor_album.role'); From 2753ab11a1e62758d790fe15579a7434948a7309 Mon Sep 17 00:00:00 2001 From: darrell-k Date: Wed, 5 Feb 2025 12:26:22 +0000 Subject: [PATCH 7/9] Default Skin role sort WIP Signed-off-by: darrell-k --- Slim/Control/Queries.pm | 11 +++++------ Slim/Menu/BrowseLibrary.pm | 35 ++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index 34ce6cf5639..eadcc725d80 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -276,6 +276,7 @@ sub albumsQuery { } my $sqllog = main::DEBUGLOG && logger('database.sql'); +Slim::Utils::Log::logError("DK \$request->getParam('sort')=" . Data::Dump::dump($request->getParam('sort'))); # get our parameters my $client = $request->client(); @@ -3242,12 +3243,10 @@ sub rolesQuery { push @{$p}, $libraryID; } - if (defined $albumID) { - my @albums = split(',', $albumID); - if (scalar @albums) { - push @{$p}, @albums; - push @{$w}, 'contributor_album.album IN (' . join(', ', map {'?'} @albums) . ')'; - } + if ( defined $albumID ) { + # remove anything nasty that might have crept into the parameter + $albumID = join(',', grep /^\d+$/, split(',', $albumID)); + push @{$w}, "contributor_album.album IN ($albumID)"; } if (defined $year || defined $workID || defined $genreID) { diff --git a/Slim/Menu/BrowseLibrary.pm b/Slim/Menu/BrowseLibrary.pm index 49ae0ab7a9b..4f8259e0674 100644 --- a/Slim/Menu/BrowseLibrary.pm +++ b/Slim/Menu/BrowseLibrary.pm @@ -1389,14 +1389,6 @@ sub _years { ); } -my %orderByList = ( - ALBUM => 'album', - SORT_YEARALBUM => 'yearalbum', - SORT_YEARARTISTALBUM => 'yearartistalbum', - SORT_ARTISTALBUM => 'artistalbum', - SORT_ARTISTYEARALBUM => 'artflow', -); - my %mapArtistOrders = ( album => 'album', yearalbum => 'yearalbum', @@ -1473,13 +1465,28 @@ sub _albums { $tags .= 'y' unless grep {/^year:/} @searchTags; + my %orderByList = ( + ALBUM => 'album', + SORT_YEARALBUM => 'yearalbum', + SORT_YEARARTISTALBUM => 'yearartistalbum', + SORT_ARTISTALBUM => 'artistalbum', + SORT_ARTISTYEARALBUM => 'artflow', + ); + + # Remove artist from sort order if selection includes artist if ($sort && $sort =~ /sort:(.*)/) { +Slim::Utils::Log::logError("DK \$1=" . $1); +Slim::Utils::Log::logError("DK \$&=" . $&); +Slim::Utils::Log::logError("DK \$sort=" . $sort); my $mapped; +Slim::Utils::Log::logError("DK \$artistId=" . $artistId); if ($artistId && ($mapped = $mapArtistOrders{$1})) { $sort = 'sort:' . $mapped; } - $sort = undef unless grep {$_ eq $1} ('new', 'changed', 'random', values %orderByList); +Slim::Utils::Log::logError("DK \%orderByList=" . Data::Dump::dump(%orderByList)); + $sort = undef unless grep {$_ eq $1} ('new', 'changed', 'random', values %orderByList, Slim::Schema::Contributor::contributorRoleIds()); +Slim::Utils::Log::logError("DK \$sort=" . Data::Dump::dump($sort)); } # Under certain circumstances (random albums in web UI or with remote streams) we are only @@ -1504,6 +1511,7 @@ sub _albums { my $results = shift; my $items = $results->{'albums_loop'}; $remote_library ||= $args->{'remote_library'}; + my @albumList; foreach (@$items) { $_->{'name'} = $_->{'composer'} ? $_->{'composer'} . cstring($client, 'COLON') . ' ' : ''; @@ -1547,6 +1555,7 @@ sub _albums { $_->{'image'} = _proxiedImageUrl($_, $remote_library); delete $_->{'artwork_track_id'}; } + push @albumList, $_->{'id'}; } my $extra; @@ -1749,6 +1758,14 @@ sub _albums { $actions{'playall'} = $actions{'play'}; $actions{'addall'} = $actions{'add'}; + my $rolesRequest = Slim::Control::Request->new( undef, [ 'roles', 0, 1000, "tags:t", "album_id:" . join(',', @albumList) ] ); + $rolesRequest->execute(); + foreach my $role (@{ $rolesRequest->getResult('roles_loop') || [] }) { +Slim::Utils::Log::logError("DK \$role=" . Data::Dump::dump("$role->{'role_id'} $role->{'role_name'}")); + $orderByList{$role->{'role_name'}} = $role->{'role_id'} unless $role->{'role_name'} =~ /^ARTIST$|^ALBUMARTIST$/; + } +Slim::Utils::Log::logError("DK \%orderByList=" . Data::Dump::dump(%orderByList)); + my $result = { items => $items, actions => \%actions, From 455cbfe7ec9d6894bd6cb6b3edc1ae7482c8a76c Mon Sep 17 00:00:00 2001 From: darrell-k Date: Wed, 5 Feb 2025 23:51:39 +0000 Subject: [PATCH 8/9] Albums sorting by role (Default Skin) Signed-off-by: darrell-k --- Slim/Control/Queries.pm | 14 ++++---------- Slim/Menu/BrowseLibrary.pm | 8 -------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm index eadcc725d80..2dfe425ba31 100644 --- a/Slim/Control/Queries.pm +++ b/Slim/Control/Queries.pm @@ -276,7 +276,6 @@ sub albumsQuery { } my $sqllog = main::DEBUGLOG && logger('database.sql'); -Slim::Utils::Log::logError("DK \$request->getParam('sort')=" . Data::Dump::dump($request->getParam('sort'))); # get our parameters my $client = $request->client(); @@ -301,13 +300,8 @@ Slim::Utils::Log::logError("DK \$request->getParam('sort')=" . Data::Dump::dump( my $ignoreNewAlbumsCache = $search || $compilation || $contributorID || $genreID || $trackID || $albumID || $year || Slim::Music::Import->stillScanning(); - my $rolesort; - if ( defined $sort && Slim::Schema::Contributor->roleToContributorMap()->{$sort} ) { - $rolesort = $sort; - $sort = 'album'; - } # FIXME: missing genrealbum, genreartistalbum - if ($request->paramNotOneOfIfDefined($sort, ['new', 'changed', 'album', 'artflow', 'artistalbum', 'yearalbum', 'yearartistalbum', 'random' ])) { + if ($request->paramNotOneOfIfDefined($sort, ['new', 'changed', 'album', 'artflow', 'artistalbum', 'yearalbum', 'yearartistalbum', 'random', Slim::Schema::Contributor->contributorRoleIds() ])) { $request->setStatusBadParams(); return; } @@ -585,9 +579,9 @@ Slim::Utils::Log::logError("DK \$request->getParam('sort')=" . Data::Dump::dump( } } - if ( $rolesort ) { + if ( Slim::Schema::Contributor->roleToType($sort) ) { $sql .= 'JOIN tracks ON tracks.album = albums.id ' unless $sql =~ /JOIN tracks/; - $sql .= "LEFT JOIN contributor_track AS rolesort_track ON rolesort_track.track = tracks.id AND rolesort_track.role = $rolesort "; + $sql .= "LEFT JOIN contributor_track AS rolesort_track ON rolesort_track.track = tracks.id AND rolesort_track.role = $sort "; $sql .= 'LEFT JOIN contributors AS rolesort ON rolesort.id = rolesort_track.contributor '; my $col = 'GROUP_CONCAT(DISTINCT rolesort.name)'; $c->{$col} = 1; @@ -896,7 +890,7 @@ Slim::Utils::Log::logError("DK \$request->getParam('sort')=" . Data::Dump::dump( if ($tags =~ /s/) { #FIXME: see if multiple char textkey is doable for year/genre sort my $textKey; - if ($rolesort) { + if (Slim::Schema::Contributor->roleToType($sort)) { utf8::decode( $c->{'rolesort.namesort'} ) if exists $c->{'rolesort.namesort'}; $textKey = $c->{'rolesort.namesort'} ? substr $c->{'rolesort.namesort'}, 0, 1 : '-'; } elsif ($sort eq 'artflow' || $sort eq 'artistalbum') { diff --git a/Slim/Menu/BrowseLibrary.pm b/Slim/Menu/BrowseLibrary.pm index 4f8259e0674..2086e492807 100644 --- a/Slim/Menu/BrowseLibrary.pm +++ b/Slim/Menu/BrowseLibrary.pm @@ -1476,17 +1476,11 @@ sub _albums { # Remove artist from sort order if selection includes artist if ($sort && $sort =~ /sort:(.*)/) { -Slim::Utils::Log::logError("DK \$1=" . $1); -Slim::Utils::Log::logError("DK \$&=" . $&); -Slim::Utils::Log::logError("DK \$sort=" . $sort); my $mapped; -Slim::Utils::Log::logError("DK \$artistId=" . $artistId); if ($artistId && ($mapped = $mapArtistOrders{$1})) { $sort = 'sort:' . $mapped; } -Slim::Utils::Log::logError("DK \%orderByList=" . Data::Dump::dump(%orderByList)); $sort = undef unless grep {$_ eq $1} ('new', 'changed', 'random', values %orderByList, Slim::Schema::Contributor::contributorRoleIds()); -Slim::Utils::Log::logError("DK \$sort=" . Data::Dump::dump($sort)); } # Under certain circumstances (random albums in web UI or with remote streams) we are only @@ -1761,10 +1755,8 @@ Slim::Utils::Log::logError("DK \$sort=" . Data::Dump::dump($sort)); my $rolesRequest = Slim::Control::Request->new( undef, [ 'roles', 0, 1000, "tags:t", "album_id:" . join(',', @albumList) ] ); $rolesRequest->execute(); foreach my $role (@{ $rolesRequest->getResult('roles_loop') || [] }) { -Slim::Utils::Log::logError("DK \$role=" . Data::Dump::dump("$role->{'role_id'} $role->{'role_name'}")); $orderByList{$role->{'role_name'}} = $role->{'role_id'} unless $role->{'role_name'} =~ /^ARTIST$|^ALBUMARTIST$/; } -Slim::Utils::Log::logError("DK \%orderByList=" . Data::Dump::dump(%orderByList)); my $result = { items => $items, From a5d4cf32aa26dcbdb472b1b7ba2cd2553d4ffecf Mon Sep 17 00:00:00 2001 From: darrell-k Date: Wed, 5 Feb 2025 23:57:36 +0000 Subject: [PATCH 9/9] remove blank line Signed-off-by: darrell-k --- Slim/Menu/BrowseLibrary.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/Slim/Menu/BrowseLibrary.pm b/Slim/Menu/BrowseLibrary.pm index 2086e492807..ab7f39701ce 100644 --- a/Slim/Menu/BrowseLibrary.pm +++ b/Slim/Menu/BrowseLibrary.pm @@ -1473,7 +1473,6 @@ sub _albums { SORT_ARTISTYEARALBUM => 'artflow', ); - # Remove artist from sort order if selection includes artist if ($sort && $sort =~ /sort:(.*)/) { my $mapped;