diff --git a/Changelog9.html b/Changelog9.html
index bb1d4b4da5a..9a9488c3529 100644
--- a/Changelog9.html
+++ b/Changelog9.html
@@ -10,6 +10,7 @@
Version 9.2.0
#1541 - Add per-player timezone support: players can now display date and time, and fire alarms, in their own timezone rather than the server's. (@boudekerk)
#1546 - Add "Default Adjustment for Local Tracks" player option. (@SamInPgh)
#1564 - Add Dismiss option to Jive alarm popup (@boudekerk)
+ #1555 - Store display_artist from ALBUMARTIST/ARTIST tags separately from individual contributors. Optional use of ALBUMARTISTS/ARTISTS plural tags for contributor creation. (@Rouzax, @darrell-k)
#1605 - MusicMagic: add configurable host for remote MusicIP instances (@jonstahl)
diff --git a/HTML/EN/settings/server/behavior.html b/HTML/EN/settings/server/behavior.html
index f06310cf956..f346c5aca3f 100644
--- a/HTML/EN/settings/server/behavior.html
+++ b/HTML/EN/settings/server/behavior.html
@@ -195,6 +195,15 @@
[% END %]
+ [% WRAPPER setting title="SETUP_USEPLURALARTISTTAGS" desc="SETUP_USEPLURALARTISTTAGS_DESC"%]
+
+
+ [% 'SETUP_USEPLURALARTISTTAGS_0' | string %]
+ [% 'SETUP_USEPLURALARTISTTAGS_1' | string %]
+
+
+ [% END %]
+
[% WRAPPER setting title="SETUP_YEARMENU" desc="SETUP_YEARMENU_DESC"%]
diff --git a/SQL/SQLite/schema_27_down.sql b/SQL/SQLite/schema_27_down.sql
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/SQL/SQLite/schema_27_up.sql b/SQL/SQLite/schema_27_up.sql
new file mode 100644
index 00000000000..4c6efeee9f3
--- /dev/null
+++ b/SQL/SQLite/schema_27_up.sql
@@ -0,0 +1,36 @@
+-- full relational solution for display_artist
+
+DROP TABLE IF EXISTS contributor_display;
+CREATE TABLE contributor_display (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name blob);
+ALTER TABLE albums ADD display_contributor integer;
+
+DROP TABLE IF EXISTS contributor_album_display;
+CREATE TABLE contributor_album_display (
+ contributor_display int(10),
+ contributor int(10),
+ album int(10),
+ PRIMARY KEY (contributor_display,contributor,album),
+ FOREIGN KEY (`album`) REFERENCES `albums` (`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`contributor`) REFERENCES `contributors` (`id`) ON DELETE CASCADE
+);
+CREATE INDEX contributor_album_displayContribIndex ON contributor_album_display (contributor);
+CREATE INDEX contributor_album_displayAlbumIndex ON contributor_album_display (album);
+CREATE INDEX contributor_album_displayContributorDisplayIndex ON contributor_album_display (contributor_display);
+
+ALTER TABLE tracks ADD display_contributor integer;
+
+DROP TABLE IF EXISTS contributor_track_display;
+CREATE TABLE contributor_track_display (
+ contributor_display int(10),
+ contributor int(10),
+ track int(10),
+ PRIMARY KEY (contributor_display,contributor,track),
+ FOREIGN KEY (`track`) REFERENCES `tracks` (`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`contributor`) REFERENCES `contributors` (`id`) ON DELETE CASCADE
+);
+CREATE INDEX contributor_track_displayContribIndex ON contributor_track_display (contributor);
+CREATE INDEX contributor_track_displayTrackIndex ON contributor_track_display (track);
+CREATE INDEX contributor_track_displayContributorDisplayIndex ON contributor_track_display (contributor_display);
+
diff --git a/SQL/SQLite/schema_clear.sql b/SQL/SQLite/schema_clear.sql
index f06c00f1f40..bd614e1a7c3 100644
--- a/SQL/SQLite/schema_clear.sql
+++ b/SQL/SQLite/schema_clear.sql
@@ -31,6 +31,12 @@ DELETE FROM library_album;
DELETE FROM library_contributor;
DELETE FROM library_genre;
+DELETE FROM contributor_display;
+
+DELETE FROM contributor_album_display;
+
+DELETE FROM contributor_track_display;
+
-- these table are created by the Fulltext Search plugin
DROP TABLE IF EXISTS fulltext;
DROP TABLE IF EXISTS fulltext_terms;
@@ -40,4 +46,4 @@ UPDATE metainformation SET value = 0 WHERE name = 'lastRescanTime';
-- start the contributor ID at the old offset to prevent ID overlap between scans
UPDATE SQLITE_SEQUENCE SET seq = (SELECT old.seq FROM old_autoincrement AS old WHERE old.name = 'contributors') WHERE name = 'contributors';
-DROP TABLE old_autoincrement;
\ No newline at end of file
+DROP TABLE old_autoincrement;
diff --git a/SQL/mysql/schema_27_down.sql b/SQL/mysql/schema_27_down.sql
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/SQL/mysql/schema_27_up.sql b/SQL/mysql/schema_27_up.sql
new file mode 100644
index 00000000000..3c18edcbcca
--- /dev/null
+++ b/SQL/mysql/schema_27_up.sql
@@ -0,0 +1,36 @@
+-- full relational solution for display_artist.
+
+DROP TABLE IF EXISTS contributor_display;
+CREATE TABLE contributor_display (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name blob);
+ALTER TABLE albums ADD display_contributor integer;
+
+DROP TABLE IF EXISTS contributor_album_display;
+CREATE TABLE contributor_album_display (
+ contributor_display int(10),
+ contributor int(10),
+ album int(10),
+ PRIMARY KEY (contributor_display,contributor,album),
+ FOREIGN KEY (`album`) REFERENCES `albums` (`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`contributor`) REFERENCES `contributors` (`id`) ON DELETE CASCADE
+);
+CREATE INDEX contributor_album_displayContribIndex ON contributor_album_display (contributor);
+CREATE INDEX contributor_album_displayAlbumIndex ON contributor_album_display (album);
+CREATE INDEX contributor_album_displayContributorDisplayIndex ON contributor_album_display (contributor_display);
+
+ALTER TABLE tracks ADD display_contributor integer;
+
+DROP TABLE IF EXISTS contributor_track_display;
+CREATE TABLE contributor_track_display (
+ contributor_display int(10),
+ contributor int(10),
+ track int(10),
+ PRIMARY KEY (contributor_display,contributor,track),
+ FOREIGN KEY (`track`) REFERENCES `tracks` (`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`contributor`) REFERENCES `contributors` (`id`) ON DELETE CASCADE
+);
+CREATE INDEX contributor_track_displayContribIndex ON contributor_track_display (contributor);
+CREATE INDEX contributor_track_displayTrackIndex ON contributor_track_display (track);
+CREATE INDEX contributor_track_displayContributorDisplayIndex ON contributor_track_display (contributor_display);
+
diff --git a/SQL/mysql/schema_clear.sql b/SQL/mysql/schema_clear.sql
index f0a7d886c0f..ce470cf1a1e 100644
--- a/SQL/mysql/schema_clear.sql
+++ b/SQL/mysql/schema_clear.sql
@@ -29,6 +29,12 @@ DELETE FROM scanned_files;
DELETE FROM works;
+DELETE FROM contributor_display;
+
+DELETE FROM contributor_album_display;
+
+DELETE FROM contributor_track_display;
+
UPDATE metainformation SET value = 0 WHERE name = 'lastRescanTime';
UPDATE tracks_persistent SET track = NULL;
diff --git a/Slim/Control/Queries.pm b/Slim/Control/Queries.pm
index 488830e9daa..efde99063fb 100644
--- a/Slim/Control/Queries.pm
+++ b/Slim/Control/Queries.pm
@@ -378,7 +378,7 @@ sub albumsQuery {
push @roles, 'ARTIST' if $roleID eq 'ALBUMARTIST' && !$prefs->get('useUnifiedArtistsList');
}
elsif ($prefs->get('useUnifiedArtistsList')) {
- @roles = Slim::Schema::Contributor->activeContributorRoles(1);
+ @roles = Slim::Utils::Misc::uniq(Slim::Schema::Contributor->activeContributorRoles(1), Slim::Schema::Contributor->allAlbumLinkRoles());
}
else {
@roles = Slim::Schema::Contributor->contributorRoles();
@@ -648,6 +648,12 @@ sub albumsQuery {
$c->{'contributors.name'} = 1;
}
+ if ( $tags =~ /3/ ) {
+ $sql .= 'LEFT JOIN contributor_display ON albums.display_contributor = contributor_display.id ';
+ $c->{'contributor_display.name'} = 1;
+ $c->{'contributor_display.id'} = 1;
+ }
+
if ( $tags =~ /s/ ) {
$c->{'albums.titlesort'} = 1;
}
@@ -789,7 +795,7 @@ sub albumsQuery {
);
};
- my ($contributorSql, $contributorSth, $contributorNameSth, $contributorRoleSth, @linkRoleIds);
+ my ($contributorSql, $contributorSth, $contributorRoleSth, @linkRoleIds, $displayArtistsSth);
if ( $tags =~ /(?:aa|SS)/ ) {
# Override $contributorSql if we're dealing with a Work: output Artist, Orchestra, Conductor in that order.
if ($work) {
@@ -808,6 +814,7 @@ sub albumsQuery {
join(',', @linkRoleIds));
} else {
my @linkRoles = Slim::Schema::Contributor->allAlbumLinkRoles();
+#Slim::Utils::Log::logError("DK \@linkRoles=" . Data::Dump::dump(@linkRoles));
# when filtering by role and not by contributor, put that role at the head of the list if it wasn't in there yet
if ($roleID && !$contributorID) {
unshift @linkRoles, map { Slim::Schema::Contributor->roleToType($_) || $_ } split(/,/, $roleID);
@@ -815,15 +822,26 @@ sub albumsQuery {
}
@linkRoleIds = map { Slim::Schema::Contributor->typeToRole($_) } @linkRoles;
-
+#ddd
+ my $excludeDisplayArtists = $tags =~ /3/
+ ? qq{
+ AND NOT EXISTS(
+ SELECT * FROM contributor_album_display
+ WHERE contributor_album_display.album = :album
+ AND contributor_album_display.contributor = contributor_album.contributor
+ AND contributor_album.role IN (1,5))
+ }
+ : "";
$contributorSql = sprintf( qq{
SELECT contributor_album.role AS role, contributors.name AS name, contributors.id AS id
FROM contributor_album
JOIN contributors ON contributors.id = contributor_album.contributor
WHERE contributor_album.album = :album
AND contributor_album.role IN (%s)
+ %s
ORDER BY contributor_album.role, contributors.namesort
- }, join(',', @linkRoleIds) );
+ }, join(',', @linkRoleIds), $excludeDisplayArtists );
+#Slim::Utils::Log::logError("DK \$contributorSql=" . Data::Dump::dump($contributorSql));
}
}
@@ -836,6 +854,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->{'contributor_display.name'} ) if exists $c->{'contributor_display.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'});
@@ -899,6 +918,8 @@ sub albumsQuery {
if ( !$work ) {
$tags =~ /S/ && $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'artist_id', $c->{'albums.contributor'});
$tags =~ /a/ && $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'artist', $c->{'contributors.name'});
+ $tags =~ /3/ && $c->{'contributor_display.name'} && $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'display_artist', $c->{'contributor_display.name'});
+ $tags =~ /3/ && $c->{'contributor_display.id'} && $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'display_artist_id', $c->{'contributor_display.id'});
$tags =~ /4/ && $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'portraitid', $c->{'contributors.portraitid'});
}
@@ -940,12 +961,33 @@ sub albumsQuery {
}
}
# if not dealing with a work, put the main album artist at the top of the list
- if (!$work) {
+ if (!$work && !$c->{'contributor_display.id'}) {
unshift @artists, $c->{'contributors.name'} if $c->{'contributors.name'};
unshift @artistIds, $c->{'albums.contributor'} if $c->{'albums.contributor'};
}
@artists = Slim::Utils::Misc::uniq(@artists);
@artistIds = Slim::Utils::Misc::uniq(@artistIds);
+#Slim::Utils::Log::logError("DK =============================================================================================");
+#Slim::Utils::Log::logError("DK \@artists=" . Data::Dump::dump(@artists));
+#Slim::Utils::Log::logError("DK \@artistIds=" . Data::Dump::dump(@artistIds));
+
+ if ( $c->{'contributor_display.id'} ) {
+ my $displayArtistsSql = qq{
+ SELECT contributors.id, contributors.name FROM contributor_album_display
+ JOIN contributors ON contributors.id = contributor_album_display.contributor
+ WHERE contributor_album_display.album = ?
+ AND contributor_album_display.contributor_display = ?
+ };
+ $displayArtistsSth ||= $dbh->prepare_cached($displayArtistsSql);
+ my $displayArtists = $dbh->selectall_arrayref($displayArtistsSth, { Slice => {} }, ( $c->{'albums.id'}, $c->{'contributor_display.id'} ) );
+ my $displayArtistsIds = join(',', map { $_->{id} } @$displayArtists);
+ my $displayArtistsNames = join(',', map { $_->{name} } @$displayArtists);
+ utf8::decode( $displayArtistsNames ) if $displayArtistsNames;
+#Slim::Utils::Log::logError("DK \$displayArtistsIds=" . Data::Dump::dump($displayArtistsIds));
+#Slim::Utils::Log::logError("DK \$displayArtistsNames=" . Data::Dump::dump($displayArtistsNames));
+ $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'display_artist_artist_ids', $displayArtistsIds);
+ $request->addResultLoopIfValueDefined($loopname, $chunkCount, 'display_artist_artists', $displayArtistsNames);
+ }
# XXX - what if the artist name itself contains ','?
if ( $tags =~ /aa/ && scalar @artists ) {
@@ -5705,10 +5747,12 @@ my %tagMap = (
'k' => ['comment', 'COMMENT', 'comment'], #->comment_object
'2' => [1], # to trigger addition of the input parameter
+ '3' => [1], # to trigger addition of the input parameter
# Tags handled in code only
#--------------------------------------------------------------------------------------------------
# '2': contiguity of tracks on album
+ # '3': display_contributor
# 'CC': counts of an entity
);
@@ -5795,6 +5839,11 @@ sub _songDataFromHash {
}
}
}
+ elsif ( $tag eq '3' ) {
+ $returnHash{display_trackartist} = $res->{'tacd.name'} if $res->{'tacd.name'} && $res->{'trackartist'};
+ $returnHash{display_albumartist} = $res->{'aacd.name'} if $res->{'aacd.name'} && $res->{'albumartist'};
+ $returnHash{display_artist} = $res->{'tacd.name'} || $res->{'aacd.name'} if ($res->{'tacd.name'} || $res->{'aacd.name'}) && $res->{'artist'};
+ }
elsif ( $tag eq 'S' ) {
for my $role ( @contributorRoles ) {
$role = lc $role;
@@ -6386,7 +6435,7 @@ sub _getTagDataForTracks {
my $join_contributors = sub {
$join_contributor_tracks->();
- if ( $sql !~ /JOIN contributors/ ) {
+ if ( $sql !~ /JOIN contributors ON/ ) {
$sql .= 'LEFT JOIN contributors ON contributors.id = tracks.primary_artist ';
}
};
@@ -6397,6 +6446,27 @@ sub _getTagDataForTracks {
}
};
+ my $join_album_contributor = sub {
+ $join_albums->();
+
+ if ( $sql !~ /JOIN contributors AS album_contributor/ ) {
+ $sql .= 'LEFT JOIN contributors AS album_contributor ON album_contributor.id = albums.contributor ';
+ }
+ };
+
+ my $join_contributor_display = sub {
+ if ( $sql !~ /JOIN contributor_display AS aacd/ ) {
+ $join_albums->();
+ $sql .= 'LEFT JOIN contributor_display AS aacd ON albums.display_contributor = aacd.id ';
+ }
+ };
+
+ my $join_track_contributor_display = sub {
+ if ( $sql !~ /JOIN contributor_display AS tacd/ ) {
+ $sql .= 'LEFT JOIN contributor_display AS tacd ON tracks.display_contributor = tacd.id ';
+ }
+ };
+
if ( my $year = $args->{year} ) {
push @{$w}, 'tracks.year = ?';
push @{$p}, $year;
@@ -6446,6 +6516,12 @@ sub _getTagDataForTracks {
# Filter out negative tracks (remote tracks)
push @{$w}, 'tracks.id IN (' . join( ',', grep { $_ > 0 } @{$trackIds} ) . ')';
}
+
+ if ($oneAlbum) {
+ $join_album_contributor->();
+ $c->{'album_contributor.id'} = 1;
+ $c->{'album_contributor.name'} = 1;
+ }
# Process tags and add columns/joins as needed
$tags =~ /e/ && do { $c->{'tracks.album'} = 1 };
@@ -6521,6 +6597,15 @@ sub _getTagDataForTracks {
push @{$w}, 'contributor_track.role IN (' . join(', ', map {'?'} @roles) . ')';
};
+ $tags =~ /3/ && do {
+ $join_contributor_display->();
+ $join_track_contributor_display->();
+ $c->{'aacd.id'} = 1;
+ $c->{'aacd.name'} = 1;
+ $c->{'tacd.id'} = 1;
+ $c->{'tacd.name'} = 1;
+ };
+
$tags =~ /s/ && do {
$join_contributors->();
$c->{'contributors.id'} = 1;
@@ -6661,10 +6746,13 @@ sub _getTagDataForTracks {
utf8::decode( $c->{'tracks.lyrics'} ) if exists $c->{'tracks.lyrics'};
utf8::decode( $c->{'albums.title'} ) if exists $c->{'albums.title'};
utf8::decode( $c->{'contributors.name'} ) if exists $c->{'contributors.name'};
+ utf8::decode( $c->{'aacd.name'} ) if exists $c->{'aacd.name'};
+ utf8::decode( $c->{'tacd.name'} ) if exists $c->{'tacd.name'};
utf8::decode( $c->{'genres.name'} ) if exists $c->{'genres.name'};
utf8::decode( $c->{'comments.value'} ) if exists $c->{'comments.value'};
utf8::decode( $c->{'tracks.discsubtitle'}) if exists $c->{'tracks.discsubtitle'};
utf8::decode( $c->{'tracks.grouping'}) if exists $c->{'tracks.grouping'};
+ utf8::decode( $c->{'album_contributor.name'} ) if exists $c->{'album_contributor.name'};
}
my $id = $c->{'tracks.id'};
@@ -6717,6 +6805,34 @@ sub _getTagDataForTracks {
my @albumTitleNames;
my @albumTitleIds;
+ if ( $oneAlbum ) {
+# push @albumTitleNames, $c->{'album_contributor.name'};
+# push @albumTitleIds, $c->{'album_contributor.id'};
+ if ($c->{'aacd.name'}) {
+ $albumHeader->{display_artist} = $c->{'aacd.name'};
+ my $displayArtistsSql = qq{
+ SELECT contributors.id, contributors.name FROM contributor_album_display
+ JOIN contributors ON contributors.id = contributor_album_display.contributor
+ WHERE contributor_album_display.album = ?
+ AND contributor_album_display.contributor_display = ?
+ };
+ my $displayAlbumArtistsSth = $dbh->prepare_cached($displayArtistsSql);
+ if ( main::DEBUGLOG && $sqllog->is_debug ) {
+ $sqllog->debug( "Tags A/S + 3 (display artists) query: $displayArtistsSql / [ $args->{albumId} $c->{'aacd.id'} ]" );
+ }
+ my $displayArtists = $dbh->selectall_arrayref($displayAlbumArtistsSth, { Slice => {} }, ( $args->{'albumId'}, $c->{'aacd.id'} ) );
+ @{$albumHeader->{display_artist_artists}} = map { $_->{name} } @$displayArtists;
+ @{$albumHeader->{display_artist_artist_ids}} = map { $_->{id} } @$displayArtists;
+ }
+ else {
+ push @albumTitleNames, $c->{'album_contributor.name'};
+ push @albumTitleIds, $c->{'album_contributor.id'};
+ }
+#Slim::Utils::Log::logError("DK \$albumHeader->{display_artist_artists}=" . Data::Dump::dump($albumHeader->{display_artist_artists}));
+#Slim::Utils::Log::logError("DK \$albumHeader->{display_artist_artist_ids}=" . Data::Dump::dump($albumHeader->{display_artist_artist_ids}));
+ }
+##ddd
+#Slim::Utils::Log::logError("DK \%values=" . Data::Dump::dump(%values)) if $oneAlbum;
while ( my ($id, $role) = each %values ) {
my $track = $results{$id};
@@ -6729,31 +6845,39 @@ sub _getTagDataForTracks {
}
if ( $oneAlbum ) {
+#Slim::Utils::Log::logError("DK \@albumTitleRoles=" . Data::Dump::dump(@albumTitleRoles));
+#Slim::Utils::Log::logError("DK \$role=" . Data::Dump::dump($role));
+#my @nonlinks = grep { my $f = $_; !grep $_ eq $f, @symbolic_files_found } @files_found;
foreach (@albumTitleRoles) {
- push @albumTitleNames, @{$role->{$_}->{names}} if $role->{$_}->{names};
- push @albumTitleIds, @{$role->{$_}->{ids}} if $role->{$_}->{ids};
+ if ( $_ !~ /1|5/ || !$c->{'aacd.name'} ) { #|| grep { my $v = $_; !grep $_ eq $v, @{$albumHeader->{display_artist_artist_ids}} } @{$role->{$_}->{ids}} ) {
+#Slim::Utils::Log::logError("DK \$role->{$_}=" . Data::Dump::dump($role->{$_}));
+ push @albumTitleNames, @{$role->{$_}->{names}} if $role->{$_}->{names};
+ push @albumTitleIds, @{$role->{$_}->{ids}} if $role->{$_}->{ids};
+ }
}
}
}
+#Slim::Utils::Log::logError("DK \@albumTitleNames=" . Data::Dump::dump(@albumTitleNames));
+#Slim::Utils::Log::logError("DK \@albumTitleIds=" . Data::Dump::dump(@albumTitleIds));
if ( scalar @albumTitleIds ) {
@{$albumHeader->{title_names}} = Slim::Utils::Misc::uniq(@albumTitleNames);
@{$albumHeader->{title_ids}} = Slim::Utils::Misc::uniq(@albumTitleIds);
}
- elsif ( $oneAlbum ) {
- # We've been asked for a specific album but for some reason we don't have any artists for the header: get from the album
- $sql = "SELECT albums.contributor, contributors.name FROM albums JOIN contributors ON albums.contributor = contributors.id WHERE albums.id = ?";
- my $album_contrib_sth = $dbh->prepare($sql);
-
- if ( main::DEBUGLOG && $sqllog->is_debug ) {
- $sqllog->debug( "Tag A/S (album contributor) query: $sql / $args->{albumId}" );
- }
-
- $album_contrib_sth->execute($args->{albumId});
- if ( my ($id, $name) = $album_contrib_sth->fetchrow_array ) {
- @{$albumHeader->{title_names}} = $name;
- @{$albumHeader->{title_ids}} = $id;
- }
- }
+# elsif ( $oneAlbum ) {
+# # We've been asked for a specific album but for some reason we don't have any artists for the header: get from the album
+# $sql = "SELECT albums.contributor, contributors.name FROM albums JOIN contributors ON albums.contributor = contributors.id WHERE albums.id = ?";
+# my $album_contrib_sth = $dbh->prepare($sql);
+
+# if ( main::DEBUGLOG && $sqllog->is_debug ) {
+# $sqllog->debug( "Tag A/S (album contributor) query: $sql / $args->{albumId}" );
+# }
+
+# $album_contrib_sth->execute($args->{albumId});
+# if ( my ($id, $name) = $album_contrib_sth->fetchrow_array ) {
+# @{$albumHeader->{title_names}} = $name;
+# @{$albumHeader->{title_ids}} = $id;
+# }
+# }
}
# Same thing for G/P, multiple genres requires another query
diff --git a/Slim/Plugin/FullTextSearch/Plugin.pm b/Slim/Plugin/FullTextSearch/Plugin.pm
index a43d2893901..3cfbe01c679 100644
--- a/Slim/Plugin/FullTextSearch/Plugin.pm
+++ b/Slim/Plugin/FullTextSearch/Plugin.pm
@@ -35,7 +35,8 @@ use constant SQL_CREATE_TRACK_ITEM => q{
UNIQUE_TOKENS(LOWER(IFNULL(tracks.title, '')) || ' ' || IFNULL(tracks.titlesearch, '') || ' ' || IFNULL(tracks.customsearch, '')),
-- weight 5
UNIQUE_TOKENS(IFNULL(tracks.year, '') || ' ' || GROUP_CONCAT(albums.title, ' ') || ' ' || GROUP_CONCAT(albums.titlesearch, ' ') || ' '
- || GROUP_CONCAT(genres.name, ' ') || ' ' || GROUP_CONCAT(genres.namesearch, ' ')),
+ || GROUP_CONCAT(genres.name, ' ') || ' ' || GROUP_CONCAT(genres.namesearch, ' ') || ' '
+ || IFNULL(tcd.name, '') || ' ' || IFNULL(acd.name, '')),
-- weight 3 - contributors create multiple hits, therefore only w3
UNIQUE_TOKENS(CONCAT_CONTRIBUTOR_ROLE(tracks.id, GROUP_CONCAT(contributor_track.contributor, ','), 'contributor_track') || ' '
|| IGNORE_CASE(comments.value) || ' ' || IGNORE_CASE(tracks.lyrics) || ' ' || IFNULL(tracks.content_type, '') || ' '
@@ -52,6 +53,8 @@ use constant SQL_CREATE_TRACK_ITEM => q{
LEFT JOIN genre_track ON genre_track.track = tracks.id
LEFT JOIN genres ON genres.id = genre_track.genre
LEFT JOIN comments ON comments.track = tracks.id
+ LEFT JOIN contributor_display AS acd ON albums.display_contributor = acd.id
+ LEFT JOIN contributor_display AS tcd ON tracks.display_contributor = tcd.id
%s
@@ -112,7 +115,7 @@ use constant SQL_CREATE_ALBUM_ITEM => qq{
UNIQUE_TOKENS(LOWER(IFNULL(albums.title, '')) || ' ' || IFNULL(albums.titlesearch, '') || ' ' || IFNULL(albums.customsearch, '') || ' '
|| IFNULL((SELECT GROUP_CONCAT(wt,' ') FROM (SELECT DISTINCT works.titlesearch wt FROM tracks JOIN works ON tracks.work = works.id WHERE tracks.album = albums.id) ), ' ') ),
-- weight 5
- IFNULL(albums.year, ''),
+ UNIQUE_TOKENS(IFNULL(albums.year, '') || ' ' || IFNULL(contributor_display.name, '')),
-- weight 3
UNIQUE_TOKENS(CONCAT_CONTRIBUTOR_ROLE(albums.id, GROUP_CONCAT(contributor_album.contributor, ','), 'contributor_album')),
-- weight 1
@@ -122,6 +125,7 @@ use constant SQL_CREATE_ALBUM_ITEM => qq{
FROM albums
LEFT JOIN contributor_album ON contributor_album.album = albums.id
LEFT JOIN contributors ON contributors.id = contributor_album.contributor
+ LEFT JOIN contributor_display ON albums.display_contributor = contributor_display.id
%s
diff --git a/Slim/Schema.pm b/Slim/Schema.pm
index e87ee728e9d..3f9186c7326 100644
--- a/Slim/Schema.pm
+++ b/Slim/Schema.pm
@@ -246,6 +246,21 @@ sub init {
Slim::Schema::Album->addReleaseTypeStrings;
}, [['rescan'], ['done']] );
}
+ else {
+ my $mbDuplicates = $dbh->selectall_arrayref("select tracks.url, tracks.title, tracks.musicbrainz_id from tracks where tracks.musicbrainz_id is not NULL and tracks.musicbrainz_id IN (select tracks.musicbrainz_id from tracks group by tracks.musicbrainz_id Having count(*) > 1) order by tracks.musicbrainz_id");
+ my $prevmb;
+ Slim::Utils::Log::logError("MUSICBRAINZ_TRACKID analysis");
+ Slim::Utils::Log::logError("----------------------------");
+ foreach (@$mbDuplicates) {
+ if ($prevmb ne $_->[2]) {
+ Slim::Utils::Log::logError("MUSICBRAINZ_TRACKID=$_->[2]");
+ $prevmb = $_->[2];
+ }
+ Slim::Utils::Log::logError(" Track Name=$_->[1]");
+ Slim::Utils::Log::logError(" URL=$_->[0]");
+ }
+#Slim::Utils::Log::logError("DK \$mbDuplicates=" . Data::Dump::dump($mbDuplicates));
+ }
$initialized = 1;
@@ -930,7 +945,7 @@ sub _objForDbUrl {
}
sub _createOrUpdateAlbum {
- my ($self, $attributes, $trackColumns, $isCompilation, $contributorId, $hasAlbumArtist, $create, $track, $basename) = @_;
+ my ($self, $attributes, $trackColumns, $isCompilation, $contributorId, $hasAlbumArtist, $create, $track, $basename, $aaDisplayID) = @_;
my $dbh = $self->dbh;
@@ -1296,6 +1311,7 @@ sub _createOrUpdateAlbum {
}
$albumHash->{musicbrainz_id} = $attributes->{MUSICBRAINZ_ALBUM_ID};
+ $albumHash->{display_contributor} = $aaDisplayID;
# Handle album gain tags.
for my $gainTag ( qw(REPLAYGAIN_ALBUM_GAIN REPLAYGAIN_ALBUM_PEAK) ) {
@@ -1794,10 +1810,14 @@ sub _newTrack {
# Walk through the valid contributor roles, adding them to the database.
my $contributors = $self->_mergeAndCreateContributors($deferredAttributes, $isCompilation, 1);
+ my $aaDisplayID = $self->_getOrCreateDisplayContributor($deferredAttributes->{ALBUMARTISTS}) if $deferredAttributes->{ALBUMARTISTS};
+ my $taDisplayID = $self->_getOrCreateDisplayContributor($deferredAttributes->{ARTISTS}) if $deferredAttributes->{ARTISTS};
+
# Set primary_artist for the track
if ( my $artist = $contributors->{ARTIST} || $contributors->{TRACKARTIST} ) {
$columnValueHash{primary_artist} = $artist->[0];
}
+ $columnValueHash{display_contributor} = $taDisplayID if $taDisplayID;
### Create Work rows
my $workID;
@@ -1834,6 +1854,7 @@ sub _newTrack {
1, # create
undef, # Track
$dirname,
+ $aaDisplayID,
);
### Create Track row
@@ -1842,7 +1863,7 @@ sub _newTrack {
$trackId = $self->_createTrack(\%columnValueHash, \%persistentColumnValueHash, $source);
### Create ContributorTrack & ContributorAlbum rows
- $self->_createContributorRoleRelationships($contributors, $trackId, $albumId);
+ $self->_createContributorRoleRelationships($contributors, $trackId, $albumId, $aaDisplayID, $taDisplayID);
### Create Genre rows
$self->_createGenre($deferredAttributes->{'GENRE'}, $trackId, 1);
@@ -2048,9 +2069,9 @@ sub updateOrCreateBase {
if (!$playlist) {
$self->_postCheckAttributes({
- 'track' => $track,
- 'attributes' => $deferredAttributes,
- 'integrateRemote' => $integrateRemote
+ 'track' => $track,
+ 'attributes' => $deferredAttributes,
+ 'integrateRemote' => $integrateRemote,
});
}
@@ -2717,6 +2738,23 @@ sub _preCheckAttributes {
}
}
+ for my $tag (Slim::Schema::Contributor->contributorRoles, qw(ALBUMARTISTS ARTISTS)) {
+ if ($attributes->{$tag}) {
+ my @tag = Slim::Music::Info::splitTag($attributes->{$tag});
+ $attributes->{$tag} = \@tag;
+ }
+ }
+ for my $tag (qw(ALBUMARTIST ARTIST)) {
+ if ( $prefs->get('usePluralArtistTags') && $attributes->{$tag} && scalar(@{$attributes->{$tag}}) == 1 && $attributes->{$tag . 'S'} ) {
+ my $displayArtist = $attributes->{$tag}->[0];
+ $attributes->{$tag} = $attributes->{$tag . 'S'};
+ $attributes->{$tag . 'S'} = $displayArtist;
+ }
+ else {
+ delete $attributes->{$tag . 'S'};
+ }
+ }
+
if ($attributes->{'TITLE'}) {
# Create a canonical title to search against.
$attributes->{'TITLESEARCH'} = Slim::Utils::Text::ignoreCase($attributes->{'TITLE'}, 1);
@@ -2844,6 +2882,7 @@ sub _preCheckAttributes {
MUSICBRAINZ_ARTIST_ID MUSICBRAINZ_ALBUMARTIST_ID MUSICBRAINZ_ALBUM_ID
MUSICBRAINZ_ALBUM_TYPE MUSICBRAINZ_ALBUM_STATUS RELEASETYPE
ALBUM_EXTID ARTIST_EXTID WORK WORKSORT
+ ARTISTS ALBUMARTISTS
))
{
@@ -3067,6 +3106,13 @@ sub _postCheckAttributes {
$cols{primary_artist} = $artist->[0];
}
+ my $aaDisplayID = $self->_getOrCreateDisplayContributor($attributes->{ALBUMARTISTS}) if $attributes->{ALBUMARTISTS};
+ my $taDisplayID = $self->_getOrCreateDisplayContributor($attributes->{ARTISTS}) if $attributes->{ARTISTS};
+
+ if ($taDisplayID) {
+ $track->display_contributor($taDisplayID);
+ }
+
#Work
if (defined $attributes->{'WORK'}) {
if ( _workRequired($attributes->{'GENRE'}) ) {
@@ -3086,10 +3132,12 @@ sub _postCheckAttributes {
my $albumId = $self->_createOrUpdateAlbum($attributes,
\%cols, # trackColumns
$isCompilation,
- $artist->[0], # primary contributor-id
+ $artist->[0], # primary contributor-id
defined $contributors->{'ALBUMARTIST'}->[0] ? 1 : 0, # hasAlbumArtist
$create, # create
$track, # Track
+ undef, # basename
+ $aaDisplayID,
);
# Don't add an album to container tracks - See bug 2337
@@ -3097,7 +3145,7 @@ sub _postCheckAttributes {
$track->album($albumId);
}
- $self->_createContributorRoleRelationships($contributors, $trackId, $albumId);
+ $self->_createContributorRoleRelationships($contributors, $trackId, $albumId, $aaDisplayID, $taDisplayID);
# Save any changes - such as album.
$track->update;
@@ -3133,6 +3181,9 @@ sub _mergeAndCreateContributors {
$attributes->{'TRACKARTIST'},
));
}
+ if ( !$attributes->{'ALBUMARTISTS'} && !$attributes->{'ALBUMARTIST'} && $attributes->{'ARTISTS'} ) {
+ $attributes->{'ALBUMARTISTS'} = delete $attributes->{'ARTISTS'}
+ }
}
my %contributors = ();
@@ -3192,9 +3243,31 @@ sub _mergeAndCreateContributors {
return \%contributors;
}
+sub _getOrCreateDisplayContributor {
+ my ($self, $displayName) = @_;
+ return undef if !$prefs->get('usePluralArtistTags');
+
+ my $sth = $self->dbh->prepare_cached(
+ 'SELECT id FROM contributor_display WHERE UPPER(name) = ?'
+ );
+ $sth->execute(uc($displayName));
+ my ($id) = $sth->fetchrow_array;
+ $sth->finish;
+
+ if (!$id) {
+ my $insert = $self->dbh->prepare_cached(
+ 'INSERT INTO contributor_display (name) VALUES (?)'
+ );
+ $insert->execute($displayName);
+ $id = $self->dbh->last_insert_id(undef, undef, undef, undef);
+ }
+
+ return $id;
+}
+
sub _createContributorRoleRelationships {
- my ($self, $contributors, $trackId, $albumId) = @_;
+ my ($self, $contributors, $trackId, $albumId, $aaDisplayID, $taDisplayID) = @_;
if (!keys %$contributors) {
main::DEBUGLOG && $log->debug('Attempt to set empty contributor set for trackid=', $trackId);
@@ -3210,6 +3283,13 @@ sub _createContributorRoleRelationships {
WHERE track = ?
} );
$sth_delete_tracks->execute($trackId);
+ # And for contributor_track_display
+ $sth_delete_tracks = $self->dbh->prepare_cached( qq{
+ DELETE
+ FROM contributor_track_display
+ WHERE track = ?
+ } );
+ $sth_delete_tracks->execute($trackId);
# Using native DBI here to improve performance during scanning
if ( my $artist = $contributors->{ARTIST} || $contributors->{TRACKARTIST} ) {
@@ -3234,6 +3314,19 @@ sub _createContributorRoleRelationships {
VALUES
(?, ?, ?)
} );
+ my $sth_album_display = $self->dbh->prepare_cached( qq{
+ REPLACE INTO contributor_album_display
+ (contributor_display, contributor, album)
+ VALUES
+ (?, ?, ?)
+ } ) if $aaDisplayID;
+
+ my $sth_track_display = $self->dbh->prepare_cached( qq{
+ REPLACE INTO contributor_track_display
+ (contributor_display, contributor, track)
+ VALUES
+ (?, ?, ?)
+ } ) if $taDisplayID;
while (my ($role, $contributorList) = each %{$contributors}) {
my $roleId = Slim::Schema::Contributor->typeToRole($role);
@@ -3245,6 +3338,13 @@ sub _createContributorRoleRelationships {
# The following is retained at present to add mappings for BMF, entries created will be deleted in the optimise phase
$sth_album->execute( $roleId, $contributor, $albumId );
+
+ if ( $aaDisplayID && ($role eq 'ALBUMARTIST' || $role eq 'ARTIST') ) {
+ $sth_album_display->execute( $aaDisplayID, $contributor, $albumId );
+ }
+ if ( $taDisplayID && ($role eq 'ARTIST' || $role eq 'TRACKARTIST') ) {
+ $sth_track_display->execute( $taDisplayID, $contributor, $trackId );
+ }
}
}
}
diff --git a/Slim/Schema/Track.pm b/Slim/Schema/Track.pm
index 7328f395ccb..78593a74341 100644
--- a/Slim/Schema/Track.pm
+++ b/Slim/Schema/Track.pm
@@ -34,7 +34,7 @@ our @allColumns = (qw(
cover cover_cached vbr_scale bitrate samplerate samplesize channels block_alignment endian
bpm tagversion drm musicmagic_mixable dlna_profile
musicbrainz_id lossless lyrics replay_gain replay_peak extid virtual work subtitle grouping performance
- discsubtitle
+ discsubtitle display_contributor
));
{
diff --git a/Slim/Utils/Prefs.pm b/Slim/Utils/Prefs.pm
index ff4f1f0b1cd..61631b7c718 100644
--- a/Slim/Utils/Prefs.pm
+++ b/Slim/Utils/Prefs.pm
@@ -173,6 +173,7 @@ sub init {
'autoDownloadUpdate' => sub { $os->canAutoUpdate() },
'noGenreFilter' => 0,
'noRoleFilter' => 0,
+ 'usePluralArtistTags' => 0,
'searchSubString' => 0,
'ignoredarticles' => "The El La Los Las Le Les",
'splitList' => ';',
@@ -410,7 +411,7 @@ sub init {
$prefs->setChange(
sub { Slim::Control::Request::executeRequest(undef, ['wipecache', $prefs->get('dontTriggerScanOnPrefChange') ? 'queue' : undef]) },
- qw(splitList groupdiscs useTPE2AsAlbumArtist useTIT1AsWork cleanupReleaseTypes worksScan)
+ qw(splitList groupdiscs useTPE2AsAlbumArtist useTIT1AsWork cleanupReleaseTypes worksScan usePluralArtistTags)
);
$prefs->setChange( sub {
diff --git a/Slim/Utils/Scanner/Local.pm b/Slim/Utils/Scanner/Local.pm
index 34b2267f3a6..7be30d3df04 100644
--- a/Slim/Utils/Scanner/Local.pm
+++ b/Slim/Utils/Scanner/Local.pm
@@ -353,6 +353,21 @@ sub rescan {
JOIN tracks ON tracks.id = contributor_track.track
JOIN changed_albums ON tracks.album = changed_albums.album
} );
+ # ... and contributor_album_display
+ $dbh->do( qq{
+ DELETE FROM contributor_album_display
+ WHERE contributor_album_display.album IN (SELECT album FROM changed_albums)
+ } );
+
+ $dbh->do( qq{
+ INSERT INTO contributor_album_display (contributor_display,contributor,album)
+ SELECT albums.display_contributor, contributor_album.contributor, albums.id
+ FROM albums
+ JOIN contributor_display ON contributor_display.id = albums.display_contributor
+ JOIN contributor_album on contributor_album.album = albums.id
+ JOIN changed_albums ON albums.id = changed_albums.album
+ WHERE contributor_album.role IN (1, 5)
+ } );
main::SCANNER && Slim::Schema->forceCommit;
}
diff --git a/Slim/Web/Settings/Server/Behavior.pm b/Slim/Web/Settings/Server/Behavior.pm
index 4e510301a14..3dee58e1091 100644
--- a/Slim/Web/Settings/Server/Behavior.pm
+++ b/Slim/Web/Settings/Server/Behavior.pm
@@ -31,7 +31,7 @@ sub prefs {
ignoreReleaseTypes cleanupReleaseTypes groupArtistAlbumsByReleaseType
useTPE2AsAlbumArtist variousArtistsString ratingImplementation useUnifiedArtistsList
skipsentinel showComposerReleasesbyAlbum myClassicalGenres onlyAlbumYears
- worksScan useTIT1AsWork)
+ worksScan useTIT1AsWork usePluralArtistTags)
);
}
diff --git a/strings.txt b/strings.txt
index d783f2870fc..badb4ed2eb7 100644
--- a/strings.txt
+++ b/strings.txt
@@ -8703,6 +8703,18 @@ SETUP_NOROLEFILTER_DESC
SV När du bläddrar genom "Artister" kan du filtrera så att bara album och låtar som matchar den valda rollen ("ALBUMARTIST", "COMPOSER", etc.) visas.
ZH_CN 在浏览艺术家时,可以过滤并只显示和所选角色匹配的专辑和音轨(例如,ALBUMARTIST,COMPOSER等)。
+SETUP_USEPLURALARTISTTAGS
+ EN Use Plural Artist Tags
+
+SETUP_USEPLURALARTISTTAGS_DESC
+ EN When your music files contain both a display name (e.g. ALBUMARTIST = "John Lennon & Yoko Ono") and a list of individual artists (ALBUMARTISTS/ARTISTS tags), LMS normally creates a single artist entry from the display name. Enable this setting to use the individual artist list instead, so each artist gets their own entry in the library. The display name is still shown in the UI. Requires a rescan after changing. Most taggers (Picard, SongKong, Mp3tag) can write these tags.
+
+SETUP_USEPLURALARTISTTAGS_0
+ EN Disabled
+
+SETUP_USEPLURALARTISTTAGS_1
+ EN Enabled
+
SETUP_ROLESINARTISTS
CS Interpret skladby, Skladatel, Skupina a Orchestr v Interpretech
DA Nummerets kunstner, Komponist, band og orkester i Kunstnere