Index: Slim/DataStores/DBI/Contributor.pm
===================================================================
--- Slim/DataStores/DBI/Contributor.pm	(revision 4416)
+++ Slim/DataStores/DBI/Contributor.pm	(working copy)
@@ -90,7 +90,7 @@
 		my $sort   = Slim::Utils::Text::ignoreCaseArticles(($sortedList[$i] || $name));
 
 		my $artistObj = Slim::DataStores::DBI::Contributor->find_or_create({ 
-			namesearch => $search,
+			namesearch => Slim::DataStores::DBI::DataModel::preStoreSearchFilter(Slim::DataStores::DBI::DataModel::SEARCH_COLUMN_CONTRIBUTOR, $search),
 		});
 
 		$artistObj->name($name);
Index: Slim/DataStores/DBI/DataModel.pm
===================================================================
--- Slim/DataStores/DBI/DataModel.pm	(revision 4416)
+++ Slim/DataStores/DBI/DataModel.pm	(working copy)
@@ -27,6 +27,47 @@
 our $dirtyCount = 0;
 our $cleanupIds;
 
+# Hash of encode hooks that allow the server or plugins to preprocess the
+# search text before it is stored in the database. Each has entry has the
+# The hash has the following structure:
+#  '$columnType' => { '$key' => \$hookFunctionRef }
+#  Where:
+#  	$key is a string that is used to identify the owner of the hook. This is
+#  		not processed, but is used to identify the hook so that it can be
+#  		removed. This must be unique system-wide (recommendation is to
+#  		include the plugin name to help with this).
+#  	$columnType is a string identifying the search column type that is to
+#  		be hooked. This can be one of:
+#  			album
+#  			track
+#  			contributor
+#  			genre
+#  	$hookFunctionRef is a reference to a function that will process and encode
+#  		the search string. This function has the following form:
+#  			hookFunctionRef($type, $inString) { return $outString; }
+#  		$type is passed in so that a plugin may handle all columns in one
+#  		hook function if necessary, while still being able to encode column
+#  		search text differently.
+#  		$in_string is the original string that would normally undergo simple
+#  			encoding before storage in the database - this simple encoding does
+#  			not take place when a hook for the type is present.
+#  		$out_string is an encoded form of the original string. This will be
+#  			stored as-is within the database
+#
+# Note that it possible for multiple hooks to be present for the same column
+# type. In this case the input string is run through each in an arbitrary
+# order.
+my %preStoreSearchFilterHooks = ();
+
+# These contants identify names for the colums in the datastore that have
+# a 'searchable' version held. They are used inconjunction with the search
+# encoding hooks, and should also be used by plugins rather than hardcoding
+# the names.
+use constant SEARCH_COLUMN_ALBUM       => 'album';
+use constant SEARCH_COLUMN_TRACK       => 'track';
+use constant SEARCH_COLUMN_CONTRIBUTOR => 'contributor';
+use constant SEARCH_COLUMN_GENRE       => 'genre';
+
 {
 	my $class = __PACKAGE__;
 
@@ -913,6 +954,55 @@
         return $class;
 }
 
+# Add a new search text filter hook to be called before it is stored in the
+# database.
+# See description of preStoreSearchFilterHooks hash for further details.
+sub addPreStoreSearchFilterHook($$$) {
+	my $type            = shift;
+	my $key             = shift;
+	my $hookFunctionRef = shift;
+
+	$::d_sql && msg("Adding preStoreSearchFilterHook for $type=>$key\n");
+	$preStoreSearchFilterHooks{$type}{$key} = $hookFunctionRef;
+}
+
+# Remove a search text filter hook for a particular type.
+# See description of preStoreSearchFilterHooks hash for further details.
+sub removePreStoreSearchFilterHook($$) {
+	my $type = shift;
+	my $key  = shift;
+
+	$::d_sql && msg("Removing preStoreSearchFilterHook for $type=>$key\n");
+	delete $preStoreSearchFilterHooks{$type}{$key};
+}
+
+# Call all defined filter hooks for a particular search text type.
+# See description of preStoreSearchFilterHooks hash for further details.
+sub preStoreSearchFilter($$) {
+	my $type         = shift;
+	my $searchString = shift;
+
+	# Any hooks defined fore this type?
+	if ( defined( $preStoreSearchFilterHooks{$type} ) ) {
+
+		# Loop through the hooks for this type, allowing each to process the
+		# search string.
+		my $typeHooks = $preStoreSearchFilterHooks{$type};
+		for my $key ( keys %$typeHooks ) {
+			my $hookRef = $preStoreSearchFilterHooks{$type}{$key};
+			$searchString = &$hookRef( $type, $searchString );
+		}
+	}
+	else {
+
+		# Default behaviour if no specific hooks - make upper case and remove
+		# insignificant articles.
+		$searchString = Slim::Utils::Text::ignoreCaseArticles($searchString);
+	}
+
+	return $searchString;
+}
+
 1;
 
 __END__
Index: Slim/DataStores/DBI/DBIStore.pm
===================================================================
--- Slim/DataStores/DBI/DBIStore.pm	(revision 4416)
+++ Slim/DataStores/DBI/DBIStore.pm	(working copy)
@@ -1341,7 +1341,7 @@
 	}
 
 	# Create a canonical title to search against.
-	$attributes->{'TITLESEARCH'} = Slim::Utils::Text::ignoreCaseArticles($attributes->{'TITLE'});
+	$attributes->{'TITLESEARCH'} = Slim::DataStores::DBI::DataModel::preStoreSearchFilter(Slim::DataStores::DBI::DataModel::SEARCH_COLUMN_TRACK, $attributes->{'TITLE'});
 
 	# Remote index.
 	$attributes->{'REMOTE'} = Slim::Music::Info::isRemoteURL($url) ? 1 : 0;
@@ -1557,7 +1557,7 @@
 		$albumObj->titlesort($sortable_title) if $sortable_title;
 
 		# And our searchable version.
-		$albumObj->titlesearch(Slim::Utils::Text::ignoreCaseArticles($album));
+		$albumObj->titlesearch(Slim::DataStores::DBI::DataModel::preStoreSearchFilter(Slim::DataStores::DBI::DataModel::SEARCH_COLUMN_ALBUM, $album));
 
 		$albumObj->compilation( $attributes->{'COMPILATION'} ? 1 : 0 );
 
Index: Slim/Buttons/Search.pm
===================================================================
--- Slim/Buttons/Search.pm	(revision 4416)
+++ Slim/Buttons/Search.pm	(working copy)
@@ -12,6 +12,7 @@
 use File::Spec::Functions qw(updir);
 use Slim::Buttons::Common;
 use Slim::Display::Display;
+use Slim::Utils::Misc;
 
 # button functions for search directory
 my @defaultSearchChoices = qw(ARTISTS ALBUMS SONGS);
@@ -21,6 +22,26 @@
 our %context    = ();
 our %menuParams = ();
 
+# Hash of filter hooks that allow the server or plugins to preprocess the
+# search term before it is used in a database query.
+# following form:
+#  { '$key' => \$hookFunctionRef }
+#  Where:
+#  	$key is a string that is used to identify the owner of the hook. This is
+#  		not processed, but is used to identify the hook so that it can be
+#  		removed. This must be unique system-wide (recommendation is to
+#  		include the plugin name to help with this).
+#  	$hookFunctionRef is a reference to a function that will process and filter
+#  		the search term. This function has the following form:
+#  			hookFunctionRef($client, $searchTerm) { modify @$searchTerm }
+#		$client is the client (player) performing the search
+#		$searchTerm is a reference to an array containing the search terms.
+#		This is a reference and so the array is modified 'by reference'.
+#
+# Note that it possible for multiple hooks to be present. In this case the
+# search term is run through each in an arbitrary order.
+my %searchTermFilterHooks = ();
+
 sub init {
 	my %subs = (
 
@@ -204,13 +225,57 @@
 	# If we're searching in substrings, return - otherwise append another
 	# search which is effectively \b for the query. We might (should?)
 	# deal with alternate separator characters other than space.
+	my @searchTerm;
 	if (Slim::Utils::Prefs::get('searchSubString')) {
-		return [ $term ];
+		@searchTerm = ( $term );
+	} else {
+		@searchTerm = ( $term, "* $term" );
 	}
 
-	return [ $term, "* $term" ];
+	# Allow the search term to be filtered if any hooks have been defined.
+	searchTermFilter($client, \@searchTerm);
+	return \@searchTerm;
 }
 
+# Add a new filter hook to be called to allow the search term to be
+# (potentially) modified.
+# See description of searchTermFilterHooks hash for further details.
+sub addSearchTermFilterHook($$) {
+	my $key             = shift;
+	my $hookFunctionRef = shift;
+
+	$::d_sql
+	  && Slim::Utils::Misc::msg("Adding searchTermFilterHook for $key\n");
+	$searchTermFilterHooks{$key} = $hookFunctionRef;
+}
+
+# Remove a defined search term filter hook.
+# See description of searchTermFilterHooks hash for further details.
+sub removeSearchTermFilterHook($) {
+	my $key = shift;
+
+	$::d_sql
+	  && Slim::Utils::Misc::msg("Removing searchTermFiterHook for $key\n");
+	delete $searchTermFilterHooks{$key};
+}
+
+# Call all defined filter hooks.
+# See description of searchTermFilterHooks hash for further details.
+sub searchTermFilter($$) {
+	my $client     = shift;
+	my $searchTerm = shift;
+
+	# Give each defined filter a chance to modify the search term. No filters
+	# mean that no modification happens.
+	for my $key ( keys %searchTermFilterHooks ) {
+		my $hookRef = $searchTermFilterHooks{$key};
+
+		$::d_sql
+		  && Slim::Utils::Misc::msg("Calling searchTermFilterHook for $key\n");
+		&$hookRef( $client, $searchTerm );
+	}
+}
+
 1;
 
 __END__
