Index: SQL/mysql/schema_clear.sql
===================================================================
--- SQL/mysql/schema_clear.sql (revision 20488)
+++ SQL/mysql/schema_clear.sql (working copy)
@@ -3,6 +3,10 @@
-- Use DELETE instead of TRUNCATE, as TRUNCATE seems to need unlocked tables.
DELETE FROM tracks;
+DELETE FROM tags;
+
+DELETE FROM tag_track;
+
DELETE FROM playlist_track;
DELETE FROM albums;
Index: SQL/mysql/schema_6_up.sql
===================================================================
--- SQL/mysql/schema_6_up.sql (revision 0)
+++ SQL/mysql/schema_6_up.sql (revision 0)
@@ -0,0 +1,36 @@
+--
+-- Table: tags
+--
+DROP TABLE IF EXISTS tags;
+CREATE TABLE tags (
+ id int(10) unsigned NOT NULL auto_increment,
+ name varchar(40) NOT NULL,
+ value blob NOT NULL,
+ valuesort text,
+ valuesearch text,
+ customsearch text,
+ referencetype smallint(10) unsigned,
+ reference int(10) unsigned,
+ INDEX tagValueIndex (value(255)),
+ INDEX tagSortIndex (valuesort(255)),
+ INDEX tagSearchIndex (valuesearch(255)),
+ INDEX tagCustomSearchIndex (customsearch(255)),
+ INDEX tagReferenceIndex (reference),
+ INDEX tagNameIndex (name),
+ PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+--
+-- Table: genre_track
+--
+DROP TABLE IF EXISTS tag_track;
+CREATE TABLE tag_track (
+ tag int(10) unsigned,
+ track int(10) unsigned,
+ INDEX tag_trackTagIndex (tag),
+ INDEX tag_trackTrackIndex (track),
+ PRIMARY KEY (tag,track),
+ FOREIGN KEY (`track`) REFERENCES `tracks` (`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`tag`) REFERENCES `tags` (`id`) ON DELETE CASCADE
+) TYPE=InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci;
+
Index: SQL/mysql/schema_6_down.sql
===================================================================
--- SQL/mysql/schema_6_down.sql (revision 0)
+++ SQL/mysql/schema_6_down.sql (revision 0)
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS tag_track;
+DROP TABLE IF EXISTS tags;
Index: Slim/Schema/Track.pm
===================================================================
--- Slim/Schema/Track.pm (revision 20488)
+++ Slim/Schema/Track.pm (working copy)
@@ -37,6 +37,7 @@
$class->belongs_to('album' => 'Slim::Schema::Album');
$class->has_many('genreTracks' => 'Slim::Schema::GenreTrack' => 'track');
+ $class->has_many('tagTracks' => 'Slim::Schema::TagTrack' => 'track');
$class->has_many('comments' => 'Slim::Schema::Comment' => 'track');
$class->has_many('contributorTracks' => 'Slim::Schema::ContributorTrack');
@@ -79,6 +80,12 @@
return $self->genreTracks->search_related('genre', @_);
}
+sub tags {
+ my $self = shift;
+
+ return $self->tagTracks->search_related('tag', @_);
+}
+
sub attributes {
my $class = shift;
Index: Slim/Schema/Tag.pm
===================================================================
--- Slim/Schema/Tag.pm (revision 0)
+++ Slim/Schema/Tag.pm (revision 0)
@@ -0,0 +1,179 @@
+package Slim::Schema::Tag;
+
+# $Id:$
+
+use strict;
+use base 'Slim::Schema::DBI';
+use Scalar::Util qw(blessed);
+
+use Slim::Utils::Misc;
+
+our %typeOfReference = (
+ 'CONTRIBUTOR' => 1,
+ 'ALBUM' => 2,
+ 'GENRE' => 3,
+ 'YEAR' => 4,
+ 'TAG' => 5,
+);
+
+{
+ my $class = __PACKAGE__;
+
+ $class->table('tags');
+
+ $class->add_columns(qw(
+ id
+ name
+ value
+ valuesort
+ valuesearch
+ referencetype
+ reference
+ ));
+
+ $class->set_primary_key('id');
+ $class->add_unique_constraint('valuesearch' => [qw/name valuesearch/]);
+
+ $class->has_many('tagTracks' => 'Slim::Schema::TagTrack' => 'tag');
+
+ if ($] > 5.007) {
+ $class->utf8_columns(qw/value valuesort/);
+ }
+
+ $class->resultset_class('Slim::Schema::ResultSet::Tag');
+}
+
+sub url {
+ my $self = shift;
+
+ return sprintf('db:tag.valuesearch=%s', Slim::Utils::Misc::escape($self->valuesearch));
+}
+
+sub tracks {
+ my $self = shift;
+
+ return $self->tagTracks->search_related('track' => @_);
+}
+
+sub displayAsHTML {
+ my ($self, $form, $descend, $sort) = @_;
+
+ $form->{'text'} = $self->value;
+
+# my $Imports = Slim::Music::Import->importers;
+
+# for my $mixer (keys %{$Imports}) {
+
+# if (defined $Imports->{$mixer}->{'mixerlink'}) {
+# &{$Imports->{$mixer}->{'mixerlink'}}($self, $form, $descend);
+# }
+# }
+}
+
+sub add {
+ my $class = shift;
+ my $tag = shift;
+ my $track = shift;
+ my $value = shift;
+ my $sort = shift;
+ my $referencetype = shift || $typeOfReference{'TAG'};
+ my $reference = shift;
+
+ my @tags = ();
+
+ my @sortValues = ();
+ if(defined($sort)) {
+ @sortValues = Slim::Music::Info::splitTag($sort);
+ }
+ for my $tagSub (Slim::Music::Info::splitTag($value)) {
+
+ my $valuesort = undef;
+ unshift @sortValues,$valuesort;
+ $valuesort = Slim::Utils::Text::ignoreCaseArticles($valuesort || $tagSub);
+
+ # So that ucfirst() works properly.
+ use locale;
+ my $tagObj = Slim::Schema->resultset('Tag')->find_or_create({
+ 'name' => uc($tag),
+ 'valuesort' => $valuesort,
+ 'value' => ucfirst($tagSub),
+ 'valuesearch' => $valuesort,
+ 'referencetype' => $referencetype,
+ 'reference' => $reference,
+ }, { 'key' => 'valuesearch' });
+ if(!$reference && !$tagObj->reference) {
+ # Set reference to current item for custom tags
+ $tagObj->set_column('reference',$tagObj->id);
+ $tagObj->set_column('referencetype',$referencetype);
+ $tagObj->update();
+ }elsif($reference && $referencetype < $typeOfReference{'TAG'} && $tagObj->referencetype >= $typeOfReference{'TAG'}) {
+ # Standard tags should always override custom tags
+ $tagObj->set_column('reference',$reference);
+ $tagObj->set_column('referencetype',$referencetype);
+ $tagObj->update();
+ }
+
+ Slim::Schema->resultset('TagTrack')->find_or_create({
+ track => $track->id,
+ tag => $tagObj->id,
+ });
+
+ push @tags, $tagObj;
+ }
+
+ return wantarray ? @tags : $tags[0];
+}
+
+sub reference {
+ my $self = shift;
+
+ return undef if !$self->reference;
+
+ if($self->referencetype == $typeOfReference{'CONTRIBUTOR'}) {
+ return $self->contributor;
+ }elsif($self->referencetype == $typeOfReference{'ALBUM'}) {
+ return $self->album;
+ }elsif($self->referencetype == $typeOfReference{'GENRE'}) {
+ return $self->genre;
+ }elsif($self->referencetype == $typeOfReference{'YEAR'}) {
+ return $self->year;
+ }else {
+ return undef;
+ }
+}
+
+sub contributor {
+ my $self = shift;
+
+ return undef if !$self->reference || $self->referencetype != $typeOfReference{'CONTRIBUTOR'};
+
+ return Slim::Schema::resultset('Contributor')->find($self->reference);
+}
+
+sub album {
+ my $self = shift;
+
+ return undef if !$self->reference || $self->referencetype != $typeOfReference{'ALBUM'};
+
+ return Slim::Schema::resultset('Album')->find($self->reference);
+}
+
+sub genre {
+ my $self = shift;
+
+ return undef if !$self->reference || $self->referencetype != $typeOfReference{'GENRE'};
+
+ return Slim::Schema::resultset('Genre')->find($self->reference);
+}
+
+sub year {
+ my $self = shift;
+
+ return undef if !$self->reference || $self->referencetype != $typeOfReference{'YEAR'};
+
+ return Slim::Schema::resultset('Year')->find($self->reference);
+}
+
+1;
+
+__END__
Index: Slim/Schema/ResultSet/Tag.pm
===================================================================
--- Slim/Schema/ResultSet/Tag.pm (revision 0)
+++ Slim/Schema/ResultSet/Tag.pm (revision 0)
@@ -0,0 +1,98 @@
+package Slim::Schema::ResultSet::Tag;
+
+# $Id:$
+
+use strict;
+use base qw(Slim::Schema::ResultSet::Base);
+
+use Slim::Utils::Prefs;
+
+sub pageBarResults {
+ my $self = shift;
+
+ my $table = $self->{'attrs'}{'alias'};
+ my $value = "$table.valuesort";
+
+ $self->search(undef, {
+ 'select' => [ \"LEFT($value, 1)", { count => \"DISTINCT($table.id)" } ],
+ as => [ 'letter', 'count' ],
+ group_by => \"LEFT($value, 1)",
+ result_class => 'Slim::Schema::PageBar',
+ });
+}
+
+sub title {
+ my $self = shift;
+
+ return 'BROWSE_BY_TAG';
+}
+
+sub allTitle {
+ my $self = shift;
+
+ return 'ALL_TAGS';
+}
+
+sub alphaPageBar { 1 }
+
+sub searchColumn {
+ my $self = shift;
+
+ return 'valuesearch';
+}
+
+sub searchNames {
+ my $self = shift;
+ my $terms = shift;
+ my $attrs = shift || {};
+
+ $attrs->{'order_by'} ||= 'me.valuesort';
+ $attrs->{'distinct'} ||= 'me.id';
+
+ return $self->search({ 'me.valuesearch' => { 'like' => $terms } }, $attrs);
+}
+
+sub browse {
+ my $self = shift;
+ my $find = shift;
+ my $cond = shift;
+ my $sort = shift;
+
+ return $self->search($cond, { 'order_by' => 'me.valuesort' });
+}
+
+sub descendContributor {
+ my $self = shift;
+ my $find = shift;
+ my $cond = shift;
+ my $sort = shift;
+
+ # Get our own RS first - then search for related, which builds up a LEFT JOIN query.
+ my $rs = $self->search($cond)->search_related('tagTracks');
+
+ # If we are automatically identifiying VA albums, constrain the query.
+ if (preferences('server')->get('variousArtistAutoIdentification')) {
+
+ $rs = $rs->search_related('track', {
+ 'album.compilation' => [ { 'is' => undef }, { '=' => 0 } ]
+ }, { 'join' => 'album' });
+
+ } else {
+
+ $rs = $rs->search_related('track');
+ }
+
+ # The user may not want to include all the composers / conductors
+ if (my $roles = Slim::Schema->artistOnlyRoles) {
+
+ $rs = $rs->search_related('contributorTracks', { 'contributorTracks.role' => { 'in' => $roles } });
+
+ } else {
+
+ $rs = $rs->search_related('contributorTracks');
+ }
+
+ return $rs->search_related('contributor', {}, { 'order_by' => $sort || 'contributor.namesort' });
+}
+
+1;
Index: Slim/Schema/TagTrack.pm
===================================================================
--- Slim/Schema/TagTrack.pm (revision 0)
+++ Slim/Schema/TagTrack.pm (revision 0)
@@ -0,0 +1,27 @@
+package Slim::Schema::TagTrack;
+
+# $Id:$
+#
+# Tag to track mapping class
+
+use strict;
+use base 'Slim::Schema::DBI';
+
+{
+ my $class = __PACKAGE__;
+
+ $class->table('tag_track');
+
+ $class->add_columns(qw/tag track/);
+
+ $class->set_primary_key(qw/tag track/);
+ $class->add_unique_constraint('tag_track' => [qw/tag track/]);
+
+ $class->belongs_to('tag' => 'Slim::Schema::Tag');
+ $class->belongs_to('track' => 'Slim::Schema::Track');
+}
+
+1;
+
+__END__
+
Index: Slim/Utils/Prefs.pm
===================================================================
--- Slim/Utils/Prefs.pm (revision 20488)
+++ Slim/Utils/Prefs.pm (working copy)
@@ -188,6 +188,23 @@
'variousArtistAutoIdentification' => 0,
'useBandAsAlbumArtist' => 0,
'variousArtistsString' => undef,
+ 'scannedStandardTags' => [
+ 'ARTIST',
+ 'COMPOSER',
+ 'CONDUCTOR',
+ 'BAND',
+ 'ALBUMARTIST',
+ 'TRACKARTIST',
+ 'ALBUM',
+ 'YEAR',
+ 'GENRE',
+ ],
+ 'scannedCustomTags' => {
+ 'PERFORMER' => 'PERFORMERSORT',
+ 'WORK' => 'WORKSORT',
+ 'ENSEMBLE' => 'ENSEMBLESORT',
+ 'OPUS' => 'OPUSSORT',
+ },
# Server Settings - FileTypes
'disabledextensionsaudio' => '',
'disabledextensionsplaylist' => '',
@@ -406,7 +423,7 @@
$prefs->setChange( sub { Slim::Utils::Strings::setLanguage($_[1]) }, 'language' );
$prefs->setChange( \&main::checkVersion, 'checkVersion');
- $prefs->setChange( sub { Slim::Control::Request::executeRequest(undef, ['wipecache']) }, qw(splitList groupdiscs) );
+ $prefs->setChange( sub { Slim::Control::Request::executeRequest(undef, ['wipecache']) }, qw(splitList groupdiscs scannedCustomTags scannedStandardTags) );
$prefs->setChange( sub { Slim::Utils::Misc::setPriority($_[1]) }, 'serverPriority');
Index: Slim/Schema.pm
===================================================================
--- Slim/Schema.pm (revision 20488)
+++ Slim/Schema.pm (working copy)
@@ -173,6 +173,8 @@
Playlist
PlaylistTrack
Rescan
+ Tag
+ TagTrack
Track
Year
Progress
@@ -821,6 +823,7 @@
return;
}
+ my %allAttributes = %$attributeHash;
($attributeHash, $deferredAttributes) = $self->_preCheckAttributes({
'url' => $url,
'attributes' => $attributeHash,
@@ -884,6 +887,7 @@
$self->_postCheckAttributes({
'track' => $track,
'attributes' => $deferredAttributes,
+ 'allattributes' => \%allAttributes,
'create' => 1,
});
@@ -954,6 +958,7 @@
my $readTags = $args->{'readTags'} || 0;
my $checkMTime = $args->{'checkMTime'};
my $playlist = $args->{'playlist'};
+ my %allAttributes = %$attributeHash;
# XXX - exception should go here. Comming soon.
my ($track, $url, $blessed) = _validTrackOrURL($urlOrObj);
@@ -999,6 +1004,7 @@
}
my $deferredAttributes;
+ my %allAttributes = %$attributeHash;
($attributeHash, $deferredAttributes) = $self->_preCheckAttributes({
'url' => $url,
'attributes' => $attributeHash,
@@ -1022,6 +1028,7 @@
$self->_postCheckAttributes({
'track' => $track,
'attributes' => $deferredAttributes,
+ 'allattributes' => \%allAttributes,
});
}
@@ -1815,6 +1822,7 @@
my $track = $args->{'track'};
my $attributes = $args->{'attributes'};
+ my $allAttributes = $args->{'allattributes'};
my $create = $args->{'create'} || 0;
# Don't bother with directories / lnks. This makes sure "No Artist",
@@ -1910,6 +1918,39 @@
$log->debug("-- Track has genre '$genre'");
}
+
+ # Add custom tags
+ my $customTags = $prefs->get("scannedCustomTags");
+ while (my ($key, $val) = each %$customTags) {
+
+ if(exists $allAttributes->{$key}) {
+ if ($create && $isLocal) {
+ if(exists $allAttributes->{$val}) {
+ Slim::Schema::Tag->add($key, $track, $allAttributes->{$key}, $allAttributes->{$val});
+ }else {
+ Slim::Schema::Tag->add($key, $track, $allAttributes->{$key});
+ }
+
+ $log->debug(sprintf("-- Track has tag '$key'"));
+
+ } elsif (!$create && $isLocal && $allAttributes->{$key} ne $track->tags->single->name) {
+
+ # Bug 1143: The user has updated the genre tag, and is
+ # rescanning We need to remove the previous associations.
+ $track->tagTracks->delete_all;
+
+ if(exists $allAttributes->{$val}) {
+ Slim::Schema::Tag->add($key, $track, $allAttributes->{$key}, $allAttributes->{$val});
+ }else {
+ Slim::Schema::Tag->add($key, $track, $allAttributes->{$key});
+ }
+
+ $log->debug("-- Deleted all previous tags for this track");
+ $log->debug("-- Track has tag '$key'");
+ }
+ }
+ }
+
# Walk through the valid contributor roles, adding them to the database for each track.
my $contributors = $self->_mergeAndCreateContributors($track, $attributes, $isCompilation, $isLocal);
my $foundContributor = scalar keys %{$contributors};
@@ -2331,11 +2372,11 @@
# Years have their own lookup table.
# Bug: 3911 - don't add years for tracks without albums.
my $year = $track->year;
-
+ my $yearObj = undef;
if (defined $year && $year =~ /^\d+$/ &&
$blessedAlbum && $albumObj->title ne string('NO_ALBUM')) {
- Slim::Schema->rs('Year')->find_or_create({ 'id' => $year });
+ $yearObj = Slim::Schema->rs('Year')->find_or_create({ 'id' => $year });
}
# Add comments if we have them:
@@ -2349,6 +2390,32 @@
$log->debug("-- Track has comment '$comment'");
}
+ # Update standard tags
+ my %standardTags = map { $_ => 1 } @{ $prefs->get('scannedStandardTags') };
+ if($albumObj && $standardTags{'ALBUM'}) {
+ Slim::Schema::Tag->add('ALBUM', $track, $albumObj->title, $albumObj->titlesort,$Slim::Schema::Tag::typeOfReference{'ALBUM'},$albumObj->id);
+ }
+ while (my ($role, $contributorList) = each %{$contributors}) {
+ if($standardTags{$role}) {
+ for my $contributorObj (@$contributorList) {
+ Slim::Schema::Tag->add($role, $track, $contributorObj->name, $contributorObj->namesort,$Slim::Schema::Tag::typeOfReference{'CONTRIBUTOR'},$contributorObj->id);
+ }
+ }
+ }
+ if($standardTags{'GENRE'}) {
+ my @genres = $track->genres;
+ for my $genreObj (@genres) {
+ Slim::Schema::Tag->add('GENRE', $track, $genreObj->name, $genreObj->namesort,$Slim::Schema::Tag::typeOfReference{'GENRE'},$genreObj->id);
+ }
+ }
+ if($standardTags{'YEAR'}) {
+ if(defined($yearObj)) {
+ Slim::Schema::Tag->add('YEAR', $track, $yearObj->name, $yearObj->namesort,$Slim::Schema::Tag::typeOfReference{'YEAR'},$yearObj->id);
+ }else {
+ Slim::Schema::Tag->add('YEAR', $track, string('UNK'), string('UNK'),$Slim::Schema::Tag::typeOfReference{'YEAR'},0);
+ }
+ }
+
# refcount--
%{$contributors} = ();
}
Index: Slim/Web/Settings/Server/Behavior.pm
===================================================================
--- Slim/Web/Settings/Server/Behavior.pm (revision 20488)
+++ Slim/Web/Settings/Server/Behavior.pm (working copy)
@@ -12,6 +12,8 @@
use Slim::Utils::Prefs;
+my $prefs = preferences('server');
+
sub name {
return Slim::Web::HTTP::protectName('BEHAVIOR_SETTINGS');
}
@@ -28,6 +30,39 @@
);
}
+sub handler {
+ my ($class, $client, $paramRef, $pageSetup) = @_;
+
+ # If this is a settings update
+ if ($paramRef->{'saveSettings'}) {
+
+ my $scannedCustomTagsString = $paramRef->{'scannedCustomTags'};
+ my @scannedCustomTags = split(/\,/,$scannedCustomTagsString);
+ my %scannedCustomTagsHash = ();
+ for my $tagEntry (@scannedCustomTags) {
+ my ($tag,$sortTag) = split(/=/,$tagEntry);
+ if($tag) {
+ $sortTag = '' if !defined($sortTag);
+ $tag =~ s/^\s*//;
+ $sortTag =~ s/\s*$//;
+ $scannedCustomTagsHash{uc($tag)}=uc($sortTag);
+ }
+ }
+ $prefs->set('scannedCustomTags',\%scannedCustomTagsHash);
+ }
+ my $customTagsDisplayHash = $prefs->get('scannedCustomTags');
+ my $customTagsDisplay = '';
+ for my $tag (keys %$customTagsDisplayHash) {
+ if($customTagsDisplay ne '') {
+ $customTagsDisplay .= ', ';
+ }
+ $customTagsDisplay .= ($tag.($customTagsDisplayHash->{$tag}?('='.$customTagsDisplayHash->{$tag}):''));
+ }
+ $paramRef->{'scannedCustomTags'} = $customTagsDisplay;
+
+ return $class->SUPER::handler($client, $paramRef, $pageSetup);
+}
+
1;
__END__
Index: HTML/EN/settings/server/behavior.html
===================================================================
--- HTML/EN/settings/server/behavior.html (revision 20488)
+++ HTML/EN/settings/server/behavior.html (working copy)
@@ -101,5 +101,11 @@
[% END %]
+ [% WRAPPER settingSection %]
+ [% WRAPPER settingGroup title="SETUP_SCANNEDCUSTOMTAGS" desc="SETUP_SCANNEDCUSTOMTAGS_DESC"%]
+
+ [% END %]
+ [% END %]
+
[% PROCESS settings/footer.html %]
Index: strings.txt
===================================================================
--- strings.txt (revision 20488)
+++ strings.txt (working copy)
@@ -10105,6 +10105,9 @@
DA Genre
FR Genres
+BROWSE_BY_TAG
+ EN Tags
+
BROWSE_BY_ARTIST
CS Procházet podle interpretů
DA Gennemse kunstnere
@@ -15831,3 +15834,10 @@
ALBUMS_SORT_METHOD
EN Albums Sort Method
+
+SETUP_SCANNEDCUSTOMTAGS
+ EN Custom tags
+
+SETUP_SCANNEDCUSTOMTAGS_DESC
+ EN Additional custom tags to look for during scanning, entered as a comma separated list of tags as:
TAG1=SORTTAG1, TAG2=SORTTAG2
+