 .../mythnetvision/mythnetvision/netsearch.cpp      |   1 +
 .../mythnetvision/mythnetvision/nettree.cpp        |   6 +-
 mythtv/bindings/perl/MythTV.pm                     |   6 +-
 mythtv/bindings/php/MythBackend.php                |   6 +-
 mythtv/libs/libmyth/mythrssmanager.cpp             |   6 +-
 mythtv/libs/libmyth/netutils.cpp                   |  31 +-
 mythtv/libs/libmyth/programinfo.cpp                | 129 +++++++--
 mythtv/libs/libmyth/programinfo.h                  |  23 +-
 mythtv/libs/libmyth/rssparse.cpp                   |  13 +-
 mythtv/libs/libmyth/rssparse.h                     |   7 +-
 .../test/test_programinfo/test_programinfo.h       |  53 +++-
 mythtv/libs/libmythbase/libmythbase.pro            |   3 +
 mythtv/libs/libmythbase/mythsorthelper.cpp         |  92 ++++++
 mythtv/libs/libmythbase/mythsorthelper.h           |  30 ++
 mythtv/libs/libmythbase/mythversion.h              |   6 +-
 .../test/test_videometadata/test_videometadata.h   |  26 +-
 mythtv/libs/libmythmetadata/videometadata.cpp      | 320 +++++++++++----------
 mythtv/libs/libmythmetadata/videometadata.h        |  37 +--
 .../libmythmetadata/videometadatalistmanager.cpp   |  35 ++-
 .../libmythmetadata/videometadatalistmanager.h     |   3 +
 mythtv/libs/libmythmetadata/videoscan.cpp          |   8 +-
 mythtv/libs/libmythtv/datadirect.cpp               |  31 +-
 mythtv/libs/libmythtv/dbcheck.cpp                  | 146 +++++++++-
 mythtv/libs/libmythtv/eithelper.cpp                |   8 +-
 mythtv/libs/libmythtv/programdata.cpp              |  86 +++++-
 mythtv/libs/libmythtv/programdata.h                |  25 +-
 mythtv/libs/libmythtv/recordinginfo.cpp            |  37 ++-
 mythtv/libs/libmythtv/recordinginfo.h              |   4 +
 mythtv/libs/libmythtv/recordingrule.cpp            |  37 ++-
 mythtv/libs/libmythtv/recordingrule.h              |   2 +
 .../test/test_eitfixups/test_eitfixups.cpp         |   2 +
 mythtv/programs/mythbackend/main_helpers.cpp       |   5 +-
 mythtv/programs/mythbackend/scheduler.cpp          |  23 +-
 mythtv/programs/mythbackend/services/video.cpp     |   8 +-
 mythtv/programs/mythfrontend/globalsettings.cpp    |  38 +++
 mythtv/programs/mythfrontend/main.cpp              |   6 +-
 mythtv/programs/mythfrontend/playbackbox.cpp       |  11 +-
 mythtv/programs/mythfrontend/playbackbox.h         |   1 -
 mythtv/programs/mythfrontend/proglist.cpp          |  29 +-
 .../programs/mythfrontend/programrecpriority.cpp   |  77 +++--
 mythtv/programs/mythfrontend/videofilter.cpp       |  41 +--
 mythtv/programs/mythfrontend/videofilter.h         |   4 +-
 mythtv/programs/mythfrontend/videolist.cpp         |  53 +---
 mythtv/programs/mythpreviewgen/main.cpp            |   6 +-
 44 files changed, 1094 insertions(+), 427 deletions(-)
 create mode 100644 mythtv/libs/libmythbase/mythsorthelper.cpp
 create mode 100644 mythtv/libs/libmythbase/mythsorthelper.h

diff --git a/mythplugins/mythnetvision/mythnetvision/netsearch.cpp b/mythplugins/mythnetvision/mythnetvision/netsearch.cpp
index 5aa8ad5..e9e0014 100644
--- a/mythplugins/mythnetvision/mythnetvision/netsearch.cpp
+++ b/mythplugins/mythnetvision/mythnetvision/netsearch.cpp
@@ -509,6 +509,7 @@ void NetSearch::SlotItemChanged()
         MythUIButtonListItem *btn = m_siteList->GetItemCurrent();
 
         ResultItem res(btn->GetText(), QString(), QString(),
+                       QString(), QString(),
                        QString(), QString(), QString(), QString(),
                        QDateTime(), 0, 0, -1, QString(), QStringList(),
                        QString(), QStringList(), 0, 0, QString(),
diff --git a/mythplugins/mythnetvision/mythnetvision/nettree.cpp b/mythplugins/mythnetvision/mythnetvision/nettree.cpp
index b140e8a..9027218 100644
--- a/mythplugins/mythnetvision/mythnetvision/nettree.cpp
+++ b/mythplugins/mythnetvision/mythnetvision/nettree.cpp
@@ -714,7 +714,8 @@ void NetTree::UpdateResultItem(ResultItem *item)
 void NetTree::UpdateSiteItem(RSSSite *site)
 {
     ResultItem res =
-        ResultItem(site->GetTitle(), QString(), site->GetDescription(),
+        ResultItem(site->GetTitle(), QString(), QString(), QString(),
+                   site->GetDescription(),
                    site->GetURL(), site->GetImage(), QString(),
                    site->GetAuthor(), QDateTime(), 0, 0, -1, QString(),
                    QStringList(), QString(), QStringList(), 0, 0, QString(),
@@ -758,7 +759,8 @@ void NetTree::UpdateCurrentItem(void)
     }
 
     ResultItem res =
-        ResultItem(title, QString(), QString(), QString(), thumb, QString(),
+        ResultItem(title, QString(), QString(), QString(), QString(),
+                   QString(), thumb, QString(),
                    QString(), QDateTime(), 0, 0, -1, QString(),
                    QStringList(), QString(), QStringList(), 0, 0, QString(),
                    0, QStringList(), 0, 0, 0);
diff --git a/mythtv/bindings/perl/MythTV.pm b/mythtv/bindings/perl/MythTV.pm
index 80ebd8f..eca6311 100644
--- a/mythtv/bindings/perl/MythTV.pm
+++ b/mythtv/bindings/perl/MythTV.pm
@@ -107,8 +107,8 @@ package MythTV;
 # Note: as of July 21, 2010, this is actually a string, to account for proto
 # versions of the form "58a".  This will get used if protocol versions are 
 # changed on a fixes branch ongoing.
-    our $PROTO_VERSION = "91";
-    our $PROTO_TOKEN = "BuzzOff";
+    our $PROTO_VERSION = "92";
+    our $PROTO_TOKEN = "LightSpeed";
 
 # currentDatabaseVersion is defined in libmythtv in
 # mythtv/libs/libmythtv/dbcheck.cpp and should be the current MythTV core
@@ -120,7 +120,7 @@ package MythTV;
 # NUMPROGRAMLINES is defined in mythtv/libs/libmythtv/programinfo.h and is
 # the number of items in a ProgramInfo QStringList group used by
 # ProgramInfo::ToSringList and ProgramInfo::FromStringList.
-    our $NUMPROGRAMLINES = 52;
+    our $NUMPROGRAMLINES = 54;
 
 # Reasons a recording wouldn't be happening (from libs/libmythtv/programinfo.h)
     our %RecStatus_Types = (
diff --git a/mythtv/bindings/php/MythBackend.php b/mythtv/bindings/php/MythBackend.php
index aba7bdf..4e43ebb 100644
--- a/mythtv/bindings/php/MythBackend.php
+++ b/mythtv/bindings/php/MythBackend.php
@@ -11,8 +11,8 @@ class MythBackend {
 
 // MYTH_PROTO_VERSION is defined in libmyth in mythtv/libs/libmyth/mythcontext.h
 // and should be the current MythTV protocol version.
-    static $protocol_version        = '91';
-    static $protocol_token          = 'BuzzOff';
+    static $protocol_version        = '92';
+    static $protocol_token          = 'LightSpeed';
 
 // The character string used by the backend to separate records
     static $backend_separator       = '[]:[]';
@@ -20,7 +20,7 @@ class MythBackend {
 // NUMPROGRAMLINES is defined in mythtv/libs/libmythtv/programinfo.h and is
 // the number of items in a ProgramInfo QStringList group used by
 // ProgramInfo::ToSringList and ProgramInfo::FromStringList.
-    static $program_line_number     = 52;
+    static $program_line_number     = 54;
 
     private $fp                     = null;
     private $connected              = false;
diff --git a/mythtv/libs/libmyth/mythrssmanager.cpp b/mythtv/libs/libmyth/mythrssmanager.cpp
index 05a1248..cbbe0b8 100644
--- a/mythtv/libs/libmyth/mythrssmanager.cpp
+++ b/mythtv/libs/libmyth/mythrssmanager.cpp
@@ -233,8 +233,10 @@ void RSSSite::process(void)
         for (ResultItem::resultList::iterator i = items.begin();
                 i != items.end(); ++i)
         {
-            insertRSSArticle(new ResultItem((*i)->GetTitle(),
-               QString(), (*i)->GetDescription(), (*i)->GetURL(),
+            insertRSSArticle(new ResultItem(
+               (*i)->GetTitle(), (*i)->GetSortTitle(),
+               (*i)->GetSubtitle(), (*i)->GetSortSubtitle(),
+               (*i)->GetDescription(), (*i)->GetURL(),
                (*i)->GetThumbnail(), (*i)->GetMediaURL(),
                (*i)->GetAuthor(), (*i)->GetDate(),
                (*i)->GetTime(), (*i)->GetRating(),
diff --git a/mythtv/libs/libmyth/netutils.cpp b/mythtv/libs/libmyth/netutils.cpp
index 7582171..5cc1b10 100644
--- a/mythtv/libs/libmyth/netutils.cpp
+++ b/mythtv/libs/libmyth/netutils.cpp
@@ -404,17 +404,19 @@ bool insertTreeArticleInDB(const QString &feedtitle, const QString &path,
                   " title, subtitle, description, url, type, thumbnail, mediaURL, author, "
                   "date, time, rating, filesize, player, playerargs, download, "
                   "downloadargs, width, height, language, podcast, downloadable, "
-                  "customhtml, countries, season, episode) "
+                  "customhtml, countries, season, episode, sorttitle, sortsubtitle) "
             "VALUES( :FEEDTITLE, :PATH, :PATHTHUMB, :TITLE, :SUBTITLE, :DESCRIPTION, "
             ":URL, :TYPE, :THUMBNAIL, :MEDIAURL, :AUTHOR, :DATE, :TIME, :RATING, "
             ":FILESIZE, :PLAYER, :PLAYERARGS, :DOWNLOAD, :DOWNLOADARGS, :WIDTH, :HEIGHT, "
             ":LANGUAGE, :PODCAST, :DOWNLOADABLE, :CUSTOMHTML, :COUNTRIES, :SEASON, "
-            ":EPISODE);");
+            ":EPISODE, :SORTTITLE, :SORTSUBTITLE);");
     query.bindValue(":FEEDTITLE", feedtitle);
     query.bindValue(":PATH", path);
     query.bindValue(":PATHTHUMB", paththumb);
     query.bindValue(":TITLE", item->GetTitle());
+    query.bindValue(":SORTTITLE", item->GetSortTitle());
     query.bindValue(":SUBTITLE", item->GetSubtitle().isNull() ? "" : item->GetSubtitle());
+    query.bindValue(":SORTSUBTITLE", item->GetSortSubtitle().isNull() ? "" : item->GetSortSubtitle());
     query.bindValue(":DESCRIPTION", item->GetDescription());
     query.bindValue(":URL", item->GetURL());
     query.bindValue(":TYPE", type);
@@ -468,7 +470,8 @@ QMultiMap<QPair<QString,QString>, ResultItem*> getTreeArticles(const QString &fe
                   "rating, filesize, player, playerargs, download, "
                   "downloadargs, width, height, language, "
                   "downloadable, customhtml, countries, season, episode, "
-                  "path, paththumb FROM internetcontentarticles "
+                  "path, paththumb, sorttitle, sortsubtitle "
+                  "FROM internetcontentarticles "
                   "WHERE feedtitle = :FEEDTITLE AND podcast = 0 "
                   "AND type = :TYPE ORDER BY title DESC;");
     query.bindValue(":FEEDTITLE", feedtitle);
@@ -482,7 +485,9 @@ QMultiMap<QPair<QString,QString>, ResultItem*> getTreeArticles(const QString &fe
     while (query.next())
     {
         QString     title = query.value(0).toString();
+        QString     sortTitle = query.value(26).toString();
         QString     subtitle = query.value(1).toString();
+        QString     sortSubtitle = query.value(27).toString();
         QString     desc = query.value(2).toString();
         QString     URL = query.value(3).toString();
         QString     type = query.value(4).toString();
@@ -510,7 +515,8 @@ QMultiMap<QPair<QString,QString>, ResultItem*> getTreeArticles(const QString &fe
         QString     paththumb = query.value(25).toString();
 
         QPair<QString,QString> pair(path,paththumb);
-        ret.insert(pair, new ResultItem(title, subtitle, desc, URL,
+        ret.insert(pair, new ResultItem(title, sortTitle, subtitle,
+                   sortSubtitle, desc, URL,
                    thumbnail, mediaURL, author, date, time, rating, filesize,
                    player, playerargs, download, downloadargs,
                    width, height, language, downloadable, countries,
@@ -741,13 +747,17 @@ bool insertRSSArticleInDB(const QString &feedtitle, ResultItem *item,
                   "description, url, type, thumbnail, mediaURL, author, date, time, "
                   "rating, filesize, player, playerargs, download, "
                   "downloadargs, width, height, language, downloadable, countries, "
-                  "podcast) "
+                  "podcast, subtitle, sorttitle, sortsubtitle) "
             "VALUES( :FEEDTITLE, :TITLE, :DESCRIPTION, :URL, :TYPE, :THUMBNAIL, "
             ":MEDIAURL, :AUTHOR, :DATE, :TIME, :RATING, :FILESIZE, :PLAYER, "
             ":PLAYERARGS, :DOWNLOAD, :DOWNLOADARGS, :WIDTH, :HEIGHT, "
-            ":LANGUAGE, :DOWNLOADABLE, :COUNTRIES, :PODCAST);");
+            ":LANGUAGE, :DOWNLOADABLE, :COUNTRIES, :PODCAST, "
+            ":SUBTITLE, :SORTTITLE, :SORTSUBTITLE);");
     query.bindValue(":FEEDTITLE", feedtitle);
     query.bindValue(":TITLE", item->GetTitle());
+    query.bindValue(":SORTTITLE", item->GetSortTitle());
+    query.bindValue(":SUBTITLE", item->GetSubtitle());
+    query.bindValue(":SORTSUBTITLE", item->GetSortSubtitle());
     query.bindValue(":DESCRIPTION", item->GetDescription());
     query.bindValue(":URL", item->GetURL());
     query.bindValue(":TYPE", type);
@@ -795,7 +805,8 @@ ResultItem::resultList getRSSArticles(const QString &feedtitle,
                   "thumbnail, mediaURL, author, date, time, "
                   "rating, filesize, player, playerargs, download, "
                   "downloadargs, width, height, language, "
-                  "downloadable, countries, season, episode "
+                  "downloadable, countries, season, episode, "
+                  "sorttitle, subtitle, sortsubtitle "
                   "FROM internetcontentarticles "
                   "WHERE feedtitle = :FEEDTITLE AND podcast = 1 "
                   "AND type = :TYPE ORDER BY date DESC;");
@@ -809,6 +820,9 @@ ResultItem::resultList getRSSArticles(const QString &feedtitle,
     while (query.next())
     {
         QString     title = query.value(0).toString();
+        QString     sortTitle = query.value(21).toString();
+        QString     subtitle = query.value(22).toString();
+        QString     sortSubtitle = query.value(23).toString();
         QString     desc = query.value(1).toString();
         QString     URL = query.value(2).toString();
         QString     thumbnail = query.value(3).toString();
@@ -830,7 +844,8 @@ ResultItem::resultList getRSSArticles(const QString &feedtitle,
         uint        season = query.value(19).toUInt();
         uint        episode = query.value(20).toUInt();
 
-        ret.append(new ResultItem(title, QString(), desc, URL, thumbnail,
+        ret.append(new ResultItem(title, sortTitle, subtitle,
+                   sortSubtitle, desc, URL, thumbnail,
                    mediaURL, author, date, time, rating, filesize,
                    player, playerargs, download, downloadargs,
                    width, height, language, downloadable, countries,
diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp
index d1f633b..ca861fd 100644
--- a/mythtv/libs/libmyth/programinfo.cpp
+++ b/mythtv/libs/libmyth/programinfo.cpp
@@ -31,6 +31,7 @@ using std::min;
 #include "mythdb.h"
 #include "compat.h"
 #include "mythcdrom.h"
+#include "mythsorthelper.h"
 
 #include <unistd.h> // for getpid()
 
@@ -80,7 +81,7 @@ const QString ProgramInfo::kFromRecordedQuery =
     "       p.syndicatedepisodenumber, p.partnumber, p.parttotal,  "//48-50
     "       p.season,           p.episode,      p.totalepisodes,   "//51-53
     "       p.category_type,    r.recordedid,   r.inputname,       "//54-56
-    "       r.bookmarkupdate                                       "//57-57
+    "       r.bookmarkupdate,   r.sorttitle,    r.sortsubtitle     "//57-59
     "FROM recorded AS r "
     "LEFT JOIN channel AS c "
     "ON (r.chanid    = c.chanid) "
@@ -159,7 +160,9 @@ ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_t
  */
 ProgramInfo::ProgramInfo(void) :
     title(),
+    sortTitle(),
     subtitle(),
+    sortSubtitle(),
     description(),
     season(0),
     episode(0),
@@ -232,12 +235,12 @@ ProgramInfo::ProgramInfo(void) :
     availableStatus(asAvailable),
     spread(-1),
     startCol(-1),
-    sortTitle(),
 
     // Private
     inUseForWhat(),
     positionMapDBReplacement(NULL)
 {
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo(const ProgramInfo &other)
@@ -245,7 +248,9 @@ ProgramInfo::ProgramInfo(void) :
  */
 ProgramInfo::ProgramInfo(const ProgramInfo &other) :
     title(other.title),
+    sortTitle(other.sortTitle),
     subtitle(other.subtitle),
+    sortSubtitle(other.sortSubtitle),
     description(other.description),
     season(other.season),
     episode(other.episode),
@@ -316,12 +321,12 @@ ProgramInfo::ProgramInfo(const ProgramInfo &other) :
     availableStatus(other.availableStatus),
     spread(other.spread),
     startCol(other.startCol),
-    sortTitle(other.sortTitle),
 
     // Private
     inUseForWhat(),
     positionMapDBReplacement(other.positionMapDBReplacement)
 {
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo(uint _recordedid)
@@ -351,6 +356,8 @@ ProgramInfo::ProgramInfo(uint _recordedid)
             .arg(_recordedid));
         clear();
     }
+
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo(uint _chanid, const QDateTime &_recstartts)
@@ -362,6 +369,7 @@ ProgramInfo::ProgramInfo(uint _chanid, const QDateTime &_recstartts) :
     clear();
 
     LoadProgramFromRecorded(_chanid, _recstartts);
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo()
@@ -370,7 +378,9 @@ ProgramInfo::ProgramInfo(uint _chanid, const QDateTime &_recstartts) :
 ProgramInfo::ProgramInfo(
     uint  _recordedid,
     const QString &_title,
+    const QString &_sortTitle,
     const QString &_subtitle,
+    const QString &_sortSubtitle,
     const QString &_description,
     uint  _season,
     uint  _episode,
@@ -430,7 +440,9 @@ ProgramInfo::ProgramInfo(
     const QString &_inputname,
     const QDateTime &_bookmarkupdate) :
     title(_title),
+    sortTitle(_sortTitle),
     subtitle(_subtitle),
+    sortSubtitle(_sortSubtitle),
     description(_description),
     season(_season),
     episode(_episode),
@@ -504,7 +516,6 @@ ProgramInfo::ProgramInfo(
     availableStatus(asAvailable),
     spread(-1),
     startCol(-1),
-    sortTitle(),
 
     // Private
     inUseForWhat(),
@@ -514,6 +525,7 @@ ProgramInfo::ProgramInfo(
         originalAirDate = QDate();
 
     SetPathname(_pathname);
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo()
@@ -521,7 +533,9 @@ ProgramInfo::ProgramInfo(
  */
 ProgramInfo::ProgramInfo(
     const QString &_title,
+    const QString &_sortTitle,
     const QString &_subtitle,
+    const QString &_sortSubtitle,
     const QString &_description,
     uint  _season,
     uint  _episode,
@@ -551,7 +565,9 @@ ProgramInfo::ProgramInfo(
 
     bool duplicate) :
     title(_title),
+    sortTitle(_sortTitle),
     subtitle(_subtitle),
+    sortSubtitle(_sortSubtitle),
     description(_description),
     season(_season),
     episode(_episode),
@@ -622,12 +638,12 @@ ProgramInfo::ProgramInfo(
     availableStatus(asAvailable),
     spread(-1),
     startCol(-1),
-    sortTitle(),
 
     // Private
     inUseForWhat(),
     positionMapDBReplacement(NULL)
 {
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo()
@@ -635,7 +651,9 @@ ProgramInfo::ProgramInfo(
  */
 ProgramInfo::ProgramInfo(
     const QString &_title,
+    const QString &_sortTitle,
     const QString &_subtitle,
+    const QString &_sortSubtitle,
     const QString &_description,
     const QString &_syndicatedepisode,
     const QString &_category,
@@ -679,7 +697,9 @@ ProgramInfo::ProgramInfo(
 
     const ProgramList &schedList) :
     title(_title),
+    sortTitle(_sortTitle),
     subtitle(_subtitle),
+    sortSubtitle(_sortSubtitle),
     description(_description),
     season(_season),
     episode(_episode),
@@ -753,7 +773,6 @@ ProgramInfo::ProgramInfo(
     availableStatus(asAvailable),
     spread(-1),
     startCol(-1),
-    sortTitle(),
 
     // Private
     inUseForWhat(),
@@ -805,6 +824,7 @@ ProgramInfo::ProgramInfo(
             s.recstatus == RecStatus::Failing)
         recstatus = s.recstatus;
     }
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo()
@@ -812,7 +832,9 @@ ProgramInfo::ProgramInfo(
  */
 ProgramInfo::ProgramInfo(
     const QString &_title,
+    const QString &_sortTitle,
     const QString &_subtitle,
+    const QString &_sortSubtitle,
     const QString &_description,
     uint  _season,
     uint  _episode,
@@ -837,7 +859,9 @@ ProgramInfo::ProgramInfo(
     const QString &_programid,
     const QString &_inetref) :
     title(_title),
+    sortTitle(_sortTitle),
     subtitle(_subtitle),
+    sortSubtitle(_sortSubtitle),
     description(_description),
     season(_season),
     episode(_episode),
@@ -908,12 +932,12 @@ ProgramInfo::ProgramInfo(
     availableStatus(asAvailable),
     spread(-1),
     startCol(-1),
-    sortTitle(),
 
     // Private
     inUseForWhat(),
     positionMapDBReplacement(NULL)
 {
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo(const QString &_pathname)
@@ -950,6 +974,7 @@ ProgramInfo::ProgramInfo(const QString &_pathname) :
         SetPathname(QFileInfo(_pathname).absoluteFilePath());
     else
         SetPathname(_pathname);
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo()
@@ -958,7 +983,9 @@ ProgramInfo::ProgramInfo(const QString &_pathname) :
 ProgramInfo::ProgramInfo(const QString &_pathname,
                          const QString &_plot,
                          const QString &_title,
+                         const QString &_sortTitle,
                          const QString &_subtitle,
+                         const QString &_sortSubtitle,
                          const QString &_director,
                          int _season, int _episode,
                          const QString &_inetref,
@@ -970,7 +997,9 @@ ProgramInfo::ProgramInfo(const QString &_pathname,
     clear();
 
     title = _title;
+    sortTitle = _sortTitle;
     subtitle = _subtitle;
+    sortSubtitle = _sortSubtitle;
     description = _plot;
     season = _season;
     episode = _episode;
@@ -990,6 +1019,7 @@ ProgramInfo::ProgramInfo(const QString &_pathname,
         pn = determineURLType(_pathname);
 
     SetPathname(pn);
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo()
@@ -1034,6 +1064,7 @@ ProgramInfo::ProgramInfo(const QString &_title, uint _chanid,
 
     description = title =
         QString("%1 (%2)").arg(title).arg(QObject::tr("Manual Record"));
+    ensureSortFields();
 }
 
 /** \fn ProgramInfo::ProgramInfo()
@@ -1049,6 +1080,7 @@ ProgramInfo::ProgramInfo(
     category = _category;
     startts  = _startts;
     endts    = _endts;
+    ensureSortFields();
 }
 
 
@@ -1071,7 +1103,9 @@ void ProgramInfo::clone(const ProgramInfo &other,
          startts == other.startts);
 
     title = other.title;
+    sortTitle = other.sortTitle;
     subtitle = other.subtitle;
+    sortSubtitle = other.sortSubtitle;
     description = other.description;
     season = other.season;
     episode = other.episode;
@@ -1148,7 +1182,6 @@ void ProgramInfo::clone(const ProgramInfo &other,
     {
         spread = other.spread;
         startCol = other.startCol;
-        sortTitle = other.sortTitle;
         availableStatus = other.availableStatus;
 
         inUseForWhat = other.inUseForWhat;
@@ -1156,7 +1189,9 @@ void ProgramInfo::clone(const ProgramInfo &other,
     }
 
     title.detach();
+    sortTitle.detach();
     subtitle.detach();
+    sortSubtitle.detach();
     description.detach();
     category.detach();
 
@@ -1176,7 +1211,6 @@ void ProgramInfo::clone(const ProgramInfo &other,
     programid.detach();
     inetref.detach();
 
-    sortTitle.detach();
     inUseForWhat.detach();
     inputname.detach();
 }
@@ -1184,7 +1218,9 @@ void ProgramInfo::clone(const ProgramInfo &other,
 void ProgramInfo::clear(void)
 {
     title.clear();
+    sortTitle.clear();
     subtitle.clear();
+    sortSubtitle.clear();
     description.clear();
     season = 0;
     episode = 0;
@@ -1216,8 +1252,6 @@ void ProgramInfo::clear(void)
     inetref.clear();
     catType = kCategoryNone;
 
-    sortTitle.clear();
-
     recpriority = 0;
 
     filesize = 0ULL;
@@ -1266,6 +1300,19 @@ void ProgramInfo::clear(void)
     positionMapDBReplacement = NULL;
 }
 
+/** \fn ProgramInfo::ensureSortFields
+ *  \brief Ensure that the sortTitle and sortSubtitle fields are set.
+ */
+void ProgramInfo::ensureSortFields(void)
+{
+    SortHelper *sh = new SortHelper;
+
+    if (sortTitle.isEmpty() and !title.isEmpty())
+        sortTitle = sh->doTitle(title);
+    if (sortSubtitle.isEmpty() and !subtitle.isEmpty())
+        sortSubtitle = sh->doTitle(subtitle);
+}
+
 /** \fn ProgramInfo::~ProgramInfo()
  *  \brief Destructor deletes "record" if it exists.
  */
@@ -1273,6 +1320,34 @@ ProgramInfo::~ProgramInfo()
 {
 }
 
+void ProgramInfo::SetTitle(const QString &t, const QString &st)
+{
+    title = t;
+    title.detach();
+    sortTitle = st;
+    sortTitle.detach();
+}
+
+void ProgramInfo::SetTitle(const QString &t)
+{
+    SortHelper *sh = new SortHelper;
+    SetTitle(t, sh->doTitle(t));
+}
+
+void ProgramInfo::SetSubtitle(const QString &st, const QString &sst)
+{
+    subtitle = st;
+    subtitle.detach();
+    sortSubtitle = sst;
+    sortSubtitle.detach();
+}
+
+void ProgramInfo::SetSubtitle(const QString &st)
+{
+    SortHelper *sh = new SortHelper;
+    SetSubtitle(st, sh->doTitle(st));
+}
+
 /// \brief Creates a unique string that can be used to identify a recording.
 QString ProgramInfo::MakeUniqueKey(
     uint chanid, const QDateTime &recstartts)
@@ -1437,6 +1512,9 @@ void ProgramInfo::ToStringList(QStringList &list) const
     INT_TO_LIST(recordedid);          // 49
     STR_TO_LIST(inputname);           // 50
     DATETIME_TO_LIST(bookmarkupdate); // 51
+
+    STR_TO_LIST(sortTitle);        // 52
+    STR_TO_LIST(sortSubtitle);     // 53
 /* do not forget to update the NUMPROGRAMLINES defines! */
 }
 
@@ -1549,17 +1627,21 @@ bool ProgramInfo::FromStringList(QStringList::const_iterator &it,
     STR_FROM_LIST(inputname);           // 50
     DATETIME_FROM_LIST(bookmarkupdate); // 51
 
+    STR_FROM_LIST(sortTitle);           // 52
+    STR_FROM_LIST(sortSubtitle);        // 53
+
     if (!origChanid || !origRecstartts.isValid() ||
         (origChanid != chanid) || (origRecstartts != recstartts))
     {
         availableStatus = asAvailable;
         spread = -1;
         startCol = -1;
-        sortTitle = QString();
         inUseForWhat = QString();
         positionMapDBReplacement = NULL;
     }
 
+    ensureSortFields();
+
     return true;
 }
 
@@ -1584,15 +1666,21 @@ void ProgramInfo::ToMap(InfoMap &progMap,
 
     progMap["title"] = title;
     progMap["subtitle"] = subtitle;
+    progMap["sorttitle"] = sortTitle;
+    progMap["sortsubtitle"] = sortSubtitle;
 
     QString tempSubTitle = title;
+    QString tempSortSubtitle = sortTitle;
     if (!subtitle.trimmed().isEmpty())
     {
         tempSubTitle = QString("%1 - \"%2\"")
             .arg(tempSubTitle).arg(subtitle);
+        tempSortSubtitle = QString("%1 - \"%2\"")
+            .arg(tempSortSubtitle).arg(sortSubtitle);
     }
 
     progMap["titlesubtitle"] = tempSubTitle;
+    progMap["sorttitlesubtitle"] = tempSortSubtitle;
 
     progMap["description"] = description;
 
@@ -2020,14 +2108,15 @@ bool ProgramInfo::LoadProgramFromRecorded(
 
         // everything below this line (in context) is not serialized
         spread = startCol = -1;
-        sortTitle.clear();
         availableStatus = asAvailable;
         inUseForWhat.clear();
         positionMapDBReplacement = NULL;
     }
 
     title        = query.value(0).toString();
+    sortTitle    = query.value(58).toString();
     subtitle     = query.value(1).toString();
+    sortSubtitle = query.value(59).toString();
     description  = query.value(2).toString();
     season       = query.value(3).toUInt();
     if (season == 0)
@@ -2151,7 +2240,6 @@ bool ProgramInfo::LoadProgramFromRecorded(
     // Extra stuff which is not serialized and may get lost.
     /**/// spread
     /**/// startCol
-    /**/// sortTitle
     /**/// availableStatus
     /**/// inUseForWhat
     /**/// postitionMapDBReplacement
@@ -5348,7 +5436,9 @@ void ProgramInfo::SubstituteMatches(QString &str)
 
     str.replace(QString("%FILE%"), GetBasename());
     str.replace(QString("%TITLE%"), title);
+    str.replace(QString("%SORTTITLE%"), sortTitle);
     str.replace(QString("%SUBTITLE%"), subtitle);
+    str.replace(QString("%SORTSUBTITLE%"), sortSubtitle);
     str.replace(QString("%SEASON%"), QString::number(season));
     str.replace(QString("%EPISODE%"), QString::number(episode));
     str.replace(QString("%TOTALEPISODES%"), QString::number(totalepisodes));
@@ -5503,7 +5593,8 @@ static bool FromProgramQuery(const QString &sql, const MSqlBindings &bindings,
         "oldrecstatus.findid, program.videoprop+0, program.audioprop+0, "
         "program.subtitletypes+0, program.syndicatedepisodenumber, "
         "program.partnumber, program.parttotal, "
-        "program.season, program.episode, program.totalepisodes ");
+        "program.season, program.episode, program.totalepisodes, "
+        "program.sorttitle, program.sortsubtitle ");
 
     QString querystr = QString(
         "SELECT %1 "
@@ -5681,7 +5772,9 @@ bool LoadFromProgram( ProgramList &destination,
         destination.push_back(
             new ProgramInfo(
                 query.value(3).toString(), // title
+                query.value(32).toString(), // sortTitle
                 query.value(4).toString(), // subtitle
+                query.value(33).toString(), // sortSubtitle
                 query.value(5).toString(), // description
                 query.value(26).toString(), // syndicatedepisodenumber
                 query.value(6).toString(), // category
@@ -5774,7 +5867,7 @@ bool LoadFromOldRecorded(
         "       title, subtitle, description, season, episode, category, seriesid, "
         "       programid, inetref, channel.channum, channel.callsign, "
         "       channel.name, findid, rectype, recstatus, recordid, "
-        "       duplicate "
+        "       duplicate, sorttitle, sortsubtitle "
         " FROM oldrecorded "
         " LEFT JOIN channel ON oldrecorded.chanid = channel.chanid "
         " WHERE oldrecorded.future = 0 "
@@ -5809,7 +5902,9 @@ bool LoadFromOldRecorded(
 
         destination.push_back(new ProgramInfo(
             query.value(3).toString(),
+            query.value(20).toString(),
             query.value(4).toString(),
+            query.value(21).toString(),
             query.value(5).toString(),
             query.value(6).toUInt(),
             query.value(7).toUInt(),
@@ -5965,7 +6060,9 @@ bool LoadFromRecorded(
             new ProgramInfo(
                 query.value(55).toUInt(),
                 query.value(0).toString(),
+                query.value(58).toString(),
                 query.value(1).toString(),
+                query.value(59).toString(),
                 query.value(2).toString(),
                 season,
                 episode,
diff --git a/mythtv/libs/libmyth/programinfo.h b/mythtv/libs/libmyth/programinfo.h
index 63eac7a..279e24d 100644
--- a/mythtv/libs/libmyth/programinfo.h
+++ b/mythtv/libs/libmyth/programinfo.h
@@ -24,7 +24,7 @@
    in mythversion.h need to be bumped, and also follow the instructions in
    myth_version.h about other changes needed when the network protocol changes.
 */
-#define NUMPROGRAMLINES 52
+#define NUMPROGRAMLINES 54
 
 class ProgramInfo;
 typedef AutoDeleteDeque<ProgramInfo*> ProgramList;
@@ -81,7 +81,9 @@ class MPUBLIC ProgramInfo
     /// Constructs a ProgramInfo from data in 'recorded' table
     ProgramInfo(uint recordedid,
                 const QString &title,
+                const QString &sortTitle,
                 const QString &subtitle,
+                const QString &sortSubtitle,
                 const QString &description,
                 uint season,
                 uint episode,
@@ -144,7 +146,9 @@ class MPUBLIC ProgramInfo
 
     /// Constructs a ProgramInfo from data in 'oldrecorded' table
     ProgramInfo(const QString &title,
+                const QString &sortTitle,
                 const QString &subtitle,
+                const QString &sortSubtitle,
                 const QString &description,
                 uint season,
                 uint episode,
@@ -176,7 +180,9 @@ class MPUBLIC ProgramInfo
 
     /// Constructs a ProgramInfo from listings data in 'program' table
     ProgramInfo(const QString &title,
+                const QString &sortTitle,
                 const QString &subtitle,
+                const QString &sortSubtitle,
                 const QString &description,
                 const QString &syndicatedepisode,
                 const QString &category,
@@ -220,7 +226,9 @@ class MPUBLIC ProgramInfo
                 const ProgramList &schedList);
     /// Constructs a basic ProgramInfo (used by RecordingInfo)
     ProgramInfo(const QString &title,
+                const QString &sortTitle,
                 const QString &subtitle,
+                const QString &sortSubtitle,
                 const QString &description,
                 uint season,
                 uint episode,
@@ -250,7 +258,9 @@ class MPUBLIC ProgramInfo
     ProgramInfo(const QString &pathname,
                 const QString &plot,
                 const QString &title,
+                const QString &sortTitle,
                 const QString &subtitle,
+                const QString &sortSubtitle,
                 const QString &director,
                 int season, int episode,
                 const QString &inetref,
@@ -283,6 +293,7 @@ class MPUBLIC ProgramInfo
     ProgramInfo &operator=(const ProgramInfo &other);
     virtual void clone(const ProgramInfo &other,
                        bool ignore_non_serialized_data = false);
+    virtual void ensureSortFields(void);
 
     virtual void clear(void);
 
@@ -340,7 +351,9 @@ class MPUBLIC ProgramInfo
     bool IsFileReadable(void) const;
 
     QString GetTitle(void)        const { return title; }
+    QString GetSortTitle(void)    const { return sortTitle; }
     QString GetSubtitle(void)     const { return subtitle; }
+    QString GetSortSubtitle(void) const { return sortSubtitle; }
     QString GetDescription(void)  const { return description; }
     uint    GetSeason(void)       const { return season; }
     uint    GetEpisode(void)      const { return episode; }
@@ -482,7 +495,10 @@ class MPUBLIC ProgramInfo
                      QString grp = "\"") const;
 
     // Quick sets
-    void SetTitle(const QString &t) { title = t; title.detach(); }
+    void SetTitle(const QString &t, const QString &st);
+    void SetTitle(const QString &t);
+    void SetSubtitle(const QString &st, const QString &sst);
+    void SetSubtitle(const QString &st);
     void SetProgramInfoType(ProgramInfoType t)
         { programflags &= ~FL_TYPEMASK; programflags |= ((uint32_t)t<<20); }
     void SetPathname(const QString&) const;
@@ -715,7 +731,9 @@ class MPUBLIC ProgramInfo
 
   protected:
     QString title;
+    QString sortTitle;
     QString subtitle;
+    QString sortSubtitle;
     QString description;
     uint    season;
     uint    episode;
@@ -790,7 +808,6 @@ class MPUBLIC ProgramInfo
         { return (AvailableStatusType) availableStatus; }
     int8_t spread;   // only used in guidegrid.cpp
     int8_t startCol; // only used in guidegrid.cpp
-    QString sortTitle; // only use for sorting in frontend
 
     static const QString kFromRecordedQuery;
 
diff --git a/mythtv/libs/libmyth/rssparse.cpp b/mythtv/libs/libmyth/rssparse.cpp
index 1db242a..4b16a61 100644
--- a/mythtv/libs/libmyth/rssparse.cpp
+++ b/mythtv/libs/libmyth/rssparse.cpp
@@ -13,10 +13,12 @@
 #include "mythdirs.h"
 #include "mythdate.h"
 #include "programinfo.h" // for format_season_and_episode()
+#include "mythsorthelper.h"
 
 using namespace std;
 
-ResultItem::ResultItem(const QString& title, const QString& subtitle,
+ResultItem::ResultItem(const QString& title, const QString& sortTitle,
+              const QString& subtitle, const QString& sortSubtitle,
               const QString& desc, const QString& URL,
               const QString& thumbnail, const QString& mediaURL,
               const QString& author, const QDateTime& date,
@@ -29,8 +31,13 @@ ResultItem::ResultItem(const QString& title, const QString& subtitle,
               const uint& season, const uint& episode,
               const bool& customhtml)
 {
+    SortHelper *sh = new SortHelper;
+
     m_title = title;
+    m_sorttitle = sortTitle.isEmpty() ? sh->doTitle(title) : sortTitle;
     m_subtitle = subtitle;
+    m_sortsubtitle =
+        sortSubtitle.isEmpty() ? sh->doTitle(subtitle) : sortSubtitle;
     m_desc = desc;
     m_URL = URL;
     m_thumbnail = thumbnail;
@@ -70,7 +77,9 @@ ResultItem::~ResultItem()
 void ResultItem::toMap(InfoMap &metadataMap)
 {
     metadataMap["title"] = m_title;
+    metadataMap["sorttitle"] = m_sorttitle;
     metadataMap["subtitle"] = m_subtitle;
+    metadataMap["sortsubtitle"] = m_sortsubtitle;
     metadataMap["description"] = m_desc;
     metadataMap["url"] = m_URL;
     metadataMap["thumbnail"] = m_thumbnail;
@@ -929,7 +938,7 @@ ResultItem* Parse::ParseItem(const QDomElement& item) const
     if (mediaURL.isNull() || mediaURL == url)
         downloadable = false;
 
-    return(new ResultItem(title, subtitle, description,
+    return(new ResultItem(title, QString(), subtitle, QString(), description,
               url, thumbnail, mediaURL, author, date, duration,
               rating, filesize, player, playerargs,
               download, downloadargs, width, height,
diff --git a/mythtv/libs/libmyth/rssparse.h b/mythtv/libs/libmyth/rssparse.h
index 0a35dcf..0065b68 100644
--- a/mythtv/libs/libmyth/rssparse.h
+++ b/mythtv/libs/libmyth/rssparse.h
@@ -114,7 +114,8 @@ class MPUBLIC ResultItem
     typedef QList<ResultItem *> resultList;
     typedef std::vector<ResultItem> List;
 
-    ResultItem(const QString& title, const QString& subtitle,
+    ResultItem(const QString& title, const QString& sortTitle,
+              const QString& subtitle, const QString& sortSubtitle,
               const QString& desc, const QString& URL,
               const QString& thumbnail, const QString& mediaURL,
               const QString& author, const QDateTime& date, const QString& time,
@@ -130,7 +131,9 @@ class MPUBLIC ResultItem
     void toMap(InfoMap &infoMap);
 
     const QString& GetTitle() const { return m_title; }
+    const QString& GetSortTitle() const { return m_sorttitle; }
     const QString& GetSubtitle() const { return m_subtitle; }
+    const QString& GetSortSubtitle() const { return m_sortsubtitle; }
     const QString& GetDescription() const { return m_desc; }
     const QString& GetURL() const { return m_URL; }
     const QString& GetThumbnail() const { return m_thumbnail; }
@@ -155,7 +158,9 @@ class MPUBLIC ResultItem
 
   private:
     QString      m_title;
+    QString      m_sorttitle;
     QString      m_subtitle;
+    QString      m_sortsubtitle;
     QString      m_desc;
     QString      m_URL;
     QString      m_thumbnail;
diff --git a/mythtv/libs/libmyth/test/test_programinfo/test_programinfo.h b/mythtv/libs/libmyth/test/test_programinfo/test_programinfo.h
index 0298e99..85a798b 100644
--- a/mythtv/libs/libmyth/test/test_programinfo/test_programinfo.h
+++ b/mythtv/libs/libmyth/test/test_programinfo/test_programinfo.h
@@ -37,7 +37,9 @@ class TestProgramInfo : public QObject
     {
         return ProgramInfo (
             title,          /* title */
+            "",              /* sortTitle */
             "",              /* subtitle */
+            "",              /* sortSubtitle */
             "Its a movie.", /* description */
             "", /* syndicated episode */
             "", /* category */
@@ -83,8 +85,8 @@ class TestProgramInfo : public QObject
         ProgramInfo program (
             "/pathname",
              "The plot",
-             "The Title",
-             "The Subtitle",
+             "The Title", "title, the",
+             "The Subtitle", "subtitle, the",
              "Director",
              1,
              2,
@@ -142,4 +144,51 @@ class TestProgramInfo : public QObject
         ProgramInfo programH (mockMovie ("", "", "Gone", 2012));
         QVERIFY (programG.IsSameProgram (programH));
     }
+
+    void programSorting_test(void)
+    {
+        ProgramInfo flash34
+            (715,
+             "The Flash (2014)", QString(),
+             "The New Rogues", QString(),
+             "Barry continues to train Jesse ...",
+             3, 4, 23, "syndicatedepisode", "Drama",
+             1514, "514", "WNUVDT", "WNUBDT (WNUV-DT)", "",
+             QString("Default"), QString("Default"),
+             "/recordings/1514_20161025235800.ts",
+             "localhost", "Default",
+             "EP01922936", "EP019229360055", "ttvdb.py_279121",
+             ProgramInfo::kCategoryTVShow, 0, 6056109800,
+             MythDate::fromString("2016-10-25 23:58:00"),
+             MythDate::fromString("2016-10-26 01:02:00"),
+             MythDate::fromString("2016-10-25 23:58:00"),
+             MythDate::fromString("2016-10-26 01:02:00"),
+             0.0, 2016, 0, 0, QDate(2016,10,25),
+             QDateTime(QDate(2016,10,26),QTime(1,12,34)),
+             RecStatus::Unknown, 0,
+             kDupsInAll, kDupCheckSubThenDesc,
+             0, 0, 0, 0, 0, "Prime A-1",
+             QDateTime(QDate(0,0,0),QTime(0,0,0)));
+        ProgramInfo supergirl23
+            ("Supergirl", QString(),
+             "Welcome to Earth", QString(),
+             "An attack is made on the President as hot-button...",
+             2, 3, 23, "Drama",
+             1514, "514", "WNUVDT", "WNUBDT (WNUV-DT)", "",
+             QString("Default"), QString("Default"),
+             MythDate::fromString("2016-10-24 23:58:00"),
+             MythDate::fromString("2016-10-25 01:02:00"),
+             MythDate::fromString("2016-10-24 23:58:00"),
+             MythDate::fromString("2016-10-25 01:02:00"),
+             "295759", "5772362", "");
+
+        QCOMPARE (flash34.GetSortTitle(), QString("flash (2014)"));
+        QCOMPARE (flash34.GetSortSubtitle(), QString("new rogues"));
+        QVERIFY (flash34.GetSortTitle() < supergirl23.GetSortTitle());
+        QString foo = "%TITLE%|%SORTTITLE%|%SUBTITLE%|%SORTSUBTITLE%";
+        // Fake out GetPlaybackURL() so it doesn't attempt a database query
+        flash34.SetProgramInfoType(kProgramInfoTypeVideoBD);
+        flash34.SubstituteMatches(foo);
+        QCOMPARE(foo, QString("The Flash (2014)|flash (2014)|The New Rogues|new rogues"));
+    }
 };
diff --git a/mythtv/libs/libmythbase/libmythbase.pro b/mythtv/libs/libmythbase/libmythbase.pro
index aa7ac15..efe32ea 100644
--- a/mythtv/libs/libmythbase/libmythbase.pro
+++ b/mythtv/libs/libmythbase/libmythbase.pro
@@ -34,6 +34,7 @@ HEADERS += threadedfilewriter.h mythsingledownload.h codecutil.h
 HEADERS += mythsession.h
 HEADERS += ../../external/qjsonwrapper/qjsonwrapper/Json.h
 HEADERS += cleanupguard.h portchecker.h
+HEADERS += mythsorthelper.h
 
 SOURCES += mthread.cpp mthreadpool.cpp
 SOURCES += mythsocket.cpp
@@ -55,6 +56,7 @@ SOURCES += threadedfilewriter.cpp mythsingledownload.cpp codecutil.cpp
 SOURCES += mythsession.cpp
 SOURCES += ../../external/qjsonwrapper/qjsonwrapper/Json.cpp
 SOURCES += cleanupguard.cpp portchecker.cpp
+SOURCES += mythsorthelper.cpp
 
 unix {
     SOURCES += mythsystemunix.cpp
@@ -84,6 +86,7 @@ inc.files += plist.h bswap.h signalhandling.h ffmpeg-mmx.h mythdate.h
 inc.files += mythplugin.h mythpluginapi.h mythqtcompat.h
 inc.files += remotefile.h mythsystemlegacy.h mythtypes.h
 inc.files += threadedfilewriter.h mythsingledownload.h mythsession.h
+inc.files += mythsorthelper.h
 
 # Allow both #include <blah.h> and #include <libmythbase/blah.h>
 inc2.path  = $${PREFIX}/include/mythtv/libmythbase
diff --git a/mythtv/libs/libmythbase/mythsorthelper.cpp b/mythtv/libs/libmythbase/mythsorthelper.cpp
new file mode 100644
index 0000000..4a81a9a
--- /dev/null
+++ b/mythtv/libs/libmythbase/mythsorthelper.cpp
@@ -0,0 +1,92 @@
+// -*- Mode: c++ -*-
+// vim: set expandtab tabstop=4 shiftwidth=4
+
+#include "mythsorthelper.h"
+#include "mythcorecontext.h"
+
+/** \fn SortHelper::SortHelper
+ *  \brief Create the object and read settings from the core context.
+ *
+ *  Create the object and read settings from the core context. Set of
+ *  the various regular expressions and exclusion lists for later
+ *  usage.
+ *
+ *  Handling the case where the gCoreContext object doesn't exists
+ *  (i.e. running under the Qt test harness) allows this code and
+ *  objects using it to be tested.
+ */
+SortHelper::SortHelper()
+{
+    QString excludes;
+    QString prefixes(tr("^(The |A |An )",
+			"Regular Expression for what to ignore when sorting"));
+    if (gCoreContext) {
+        // Running code
+        m_ignore_case = gCoreContext->GetNumSetting("SortIgnoreCase", true);
+        m_ignore_prefixes = gCoreContext->GetNumSetting("SortIgnorePrefixes", true);
+        excludes = gCoreContext->GetSetting("SortIgnorePrefixExceptions", "");
+    } else {
+        // Qt Test Harness
+        m_ignore_case = true;
+        m_ignore_prefixes = true;
+        excludes = "A to Z";
+    }
+    if (m_ignore_case)
+    {
+        prefixes = prefixes.toLower();
+        excludes = excludes.toLower();
+    }
+    m_ign_regex1 = QRegExp(prefixes);
+    m_ign_regex2 = QRegExp(prefixes.replace("^","/"));
+    m_excl_list = excludes.split(";", QString::SkipEmptyParts);
+    for (int i = 0; i < m_excl_list.size(); i++)
+      m_excl_list[i] = m_excl_list[i].trimmed();
+    m_creator = "unknown";
+}
+
+
+/** \fn SortHelper::doTitle(QString)
+ *  \brief Create the sortable form of an title string.
+ *
+ *  This function converts a title string to a version that can be
+ *  used for sorting. Depending upon user settings, it may ignore the
+ *  case of the string (by forcing all strings to lower case) and may
+ *  strip the common prefix words "A", "An" and "The" from the
+ *  beginning of the string.
+ *
+ * \param title The title of an item.
+ * \return The conversion of the title to use when sorting.
+ */
+QString SortHelper::doTitle(QString title) const
+{
+    if (m_ignore_case)
+        title = title.toLower();
+    if (not m_ignore_prefixes)
+	return title;
+    if (m_excl_list.contains(title))
+	return title;
+    return title.remove(m_ign_regex1);
+}
+
+/** \fn SortHelper::doPathname(QString)
+ *  \brief Create the sortable form of an item.
+ *
+ *  This function converts a pathname to a version that can be used
+ *  for sorting. Depending upon user settings, it may ignore the case
+ *  of the string (by forcing all strings to lower case) and may strip
+ *  the common prefix words "A", "An" and "The" from the beginning of
+ *  each component in the pathname.
+ *
+ * \param title The pathname of an item.
+ * \return The conversion of the pathname to use when sorting.
+ */
+QString SortHelper::doPathname(QString pathname) const
+{
+    if (m_ignore_case)
+        pathname = pathname.toLower();
+    if (not m_ignore_prefixes)
+	return pathname;
+    if (m_excl_list.contains(pathname))
+	return pathname;
+    return pathname.remove(m_ign_regex1).replace(m_ign_regex2, "/");
+}
diff --git a/mythtv/libs/libmythbase/mythsorthelper.h b/mythtv/libs/libmythbase/mythsorthelper.h
new file mode 100644
index 0000000..496b043
--- /dev/null
+++ b/mythtv/libs/libmythbase/mythsorthelper.h
@@ -0,0 +1,30 @@
+// -*- Mode: c++ -*-
+// vim: set expandtab tabstop=4 shiftwidth=4
+
+#ifndef MYTHSORTHELPER_H_
+#define MYTHSORTHELPER_H_
+
+#include <QCoreApplication>
+#include "mythbaseexp.h"
+
+class MBASE_PUBLIC SortHelper
+{
+    Q_DECLARE_TR_FUNCTIONS(SortHelper)
+
+  public:
+    SortHelper();
+    QString doTitle(QString title) const;
+    QString doPathname(QString filename) const;
+
+  private:
+    bool m_ignore_case;
+
+    bool m_ignore_prefixes;
+    QRegExp m_ign_regex1;	// Anchored at the start
+    QRegExp m_ign_regex2;	// Following a '/'
+
+    QStringList m_excl_list;
+    QString m_creator;
+};
+
+#endif // MYTHSORTHELPER_H_
diff --git a/mythtv/libs/libmythbase/mythversion.h b/mythtv/libs/libmythbase/mythversion.h
index 9f0efb7..6a1753d 100644
--- a/mythtv/libs/libmythbase/mythversion.h
+++ b/mythtv/libs/libmythbase/mythversion.h
@@ -44,8 +44,8 @@
  *       http://www.mythtv.org/wiki/Category:Myth_Protocol_Commands
  *       http://www.mythtv.org/wiki/Category:Myth_Protocol
  */
-#define MYTH_PROTO_VERSION "91"
-#define MYTH_PROTO_TOKEN "BuzzOff"
+#define MYTH_PROTO_VERSION "92"
+#define MYTH_PROTO_TOKEN "LightSpeed"
 
 /** \brief Increment this whenever the MythTV core database schema changes.
  *
@@ -64,7 +64,7 @@
  *      mythtv/bindings/php/MythBackend.php
  */
 
-#define MYTH_DATABASE_VERSION "1346"
+#define MYTH_DATABASE_VERSION "1347"
 
 
  MBASE_PUBLIC  const char *GetMythSourceVersion();
diff --git a/mythtv/libs/libmythmetadata/test/test_videometadata/test_videometadata.h b/mythtv/libs/libmythmetadata/test/test_videometadata/test_videometadata.h
index 8f4f493..ce36d9e 100644
--- a/mythtv/libs/libmythmetadata/test/test_videometadata/test_videometadata.h
+++ b/mythtv/libs/libmythmetadata/test/test_videometadata/test_videometadata.h
@@ -345,12 +345,14 @@ class Testvideometadata: public QObject
     void ProgramWithInetref ()
     {
         MSKIP ("Might connect to the database or call the installed metadata grabbers.");
-        ProgramInfo proginfo = ProgramInfo ("", "", "Test Movie", "", "", 0, 0, "tmdb3.py_1234", 0, 0, "");
+        ProgramInfo proginfo = ProgramInfo ("", "", "Test Movie", "", "", "",
+                                            "", 0, 0, "tmdb3.py_1234", 0, 0, "");
         RecordingInfo recinfo = proginfo;
         QCOMPARE (recinfo.GetInetRef(), QString("tmdb3.py_1234"));
         QCOMPARE (GuessLookupType (&recinfo), kProbableMovie);
 
-        proginfo = ProgramInfo ("", "", "Test Series", "Test Episode", "", 1, 15, "ttvdb.py_1234", 0, 0, "");
+        proginfo = ProgramInfo ("", "", "Test Series", "", "Test Episode", "",
+                                "", 1, 15, "ttvdb.py_1234", 0, 0, "");
         recinfo = proginfo;
         QCOMPARE (recinfo.GetInetRef(), QString("ttvdb.py_1234"));
         QCOMPARE (GuessLookupType (&recinfo), kProbableTelevision);
@@ -358,4 +360,24 @@ class Testvideometadata: public QObject
         //QCOMPARE (GuessLookupType (QString ("tmdb3.py_1234")), kProbableMovie);
         //QCOMPARE (GuessLookupType (QString ("ttvdb.py_1234")), kProbableTelevision);
     }
+
+    void testEmbeddedFilnameToMetadata ()
+    {
+        MSKIP ("Tries to connect to the database.");
+        VideoMetadata obj = VideoMetadata(QString ("Series Title/Season 1/02x03 Episode Title.mp4"));
+        QCOMPARE (obj.GetTitle(), QString ("Series Title"));
+        QCOMPARE (obj.GetSortTitle(), QString ("series title"));
+        QCOMPARE (obj.GetSubtitle(), QString ("Episode Title"));
+        QCOMPARE (obj.GetSortSubtitle(), QString ("episode title"));
+        QCOMPARE (obj.GetSeason(), 2);
+        QCOMPARE (obj.GetEpisode(), 3);
+
+        obj = VideoMetadata(QString ("The Series Title/Season 1/02x03 The Episode Title.mp4"));
+        QCOMPARE (obj.GetTitle(), QString ("The Series Title"));
+        QCOMPARE (obj.GetSortTitle(), QString ("series title"));
+        QCOMPARE (obj.GetSubtitle(), QString ("The Episode Title"));
+        QCOMPARE (obj.GetSortSubtitle(), QString ("episode title"));
+        QCOMPARE (obj.GetSeason(), 2);
+        QCOMPARE (obj.GetEpisode(), 3);
+    }
 };
diff --git a/mythtv/libs/libmythmetadata/videometadata.cpp b/mythtv/libs/libmythmetadata/videometadata.cpp
index 8f4c2b4..dbf41fe 100644
--- a/mythtv/libs/libmythmetadata/videometadata.cpp
+++ b/mythtv/libs/libmythmetadata/videometadata.cpp
@@ -19,76 +19,10 @@
 #include "videometadatalistmanager.h"
 #include "videoutils.h"
 #include "programinfo.h" // for format_season_and_episode
+#include "mythsorthelper.h"
 
 using namespace std;
 
-struct SortData
-{
-    SortData(const QString &title, const QString &filename, const QString &id) :
-        m_title(title), m_filename(filename), m_id(id)
-    {
-    }
-
-    QString m_title;
-    QString m_filename;
-    QString m_id;
-};
-
-static bool operator<(const SortData &lhs, const SortData &rhs)
-{
-    int ret = naturalCompare(lhs.m_title, rhs.m_title);
-
-    if (ret == 0)
-        ret = naturalCompare(lhs.m_filename, rhs.m_filename);
-
-    if (ret == 0)
-        ret = naturalCompare(lhs.m_id, rhs.m_id);
-
-    return ret < 0;
-}
-
-VideoMetadata::SortKey::SortKey() : m_sd(NULL)
-{
-}
-
-VideoMetadata::SortKey::SortKey(const SortData &data)
-{
-    m_sd = new SortData(data);
-}
-
-VideoMetadata::SortKey::SortKey(const SortKey &other) : m_sd(NULL)
-{
-    *this = other;
-}
-
-VideoMetadata::SortKey &VideoMetadata::SortKey::operator=(const SortKey &rhs)
-{
-    if (this != &rhs)
-    {
-        Clear();
-        if (rhs.m_sd)
-            m_sd = new SortData(*rhs.m_sd);
-    }
-
-    return *this;
-}
-
-VideoMetadata::SortKey::~SortKey()
-{
-    Clear();
-}
-
-bool VideoMetadata::SortKey::isSet() const
-{
-    return m_sd != 0;
-}
-
-void VideoMetadata::SortKey::Clear()
-{
-    delete m_sd;
-    m_sd = 0;
-}
-
 class VideoMetadataImp
 {
   public:
@@ -97,9 +31,11 @@ class VideoMetadataImp
     typedef VideoMetadata::cast_list cast_list;
 
   public:
-    VideoMetadataImp(const QString &filename, const QString &hash, const QString &trailer,
+    VideoMetadataImp(const QString &filename, const QString &sortFilename,
+             const QString &hash, const QString &trailer,
              const QString &coverfile, const QString &screenshot, const QString &banner,
-             const QString &fanart, const QString &title, const QString &subtitle,
+             const QString &fanart, const QString &title, const QString &sortTitle,
+             const QString &subtitle, const QString &sortSubtitle,
              const QString &tagline, int year, const QDate &releasedate,
              const QString &inetref, int collectionref, const QString &homepage,
              const QString &director, const QString &studio,
@@ -115,12 +51,14 @@ class VideoMetadataImp
              const QString &host = "",
              bool processed = false,
              VideoContentType contenttype = kContentUnknown) :
-        m_title(title), m_subtitle(subtitle), m_tagline(tagline),
+        m_title(title), m_sortTitle(sortTitle), m_subtitle(subtitle),
+        m_sortSubtitle(sortSubtitle), m_tagline(tagline),
         m_inetref(inetref), m_collectionref(collectionref), m_homepage(homepage),
         m_director(director), m_studio(studio),
         m_plot(plot), m_rating(rating), m_playcommand(playcommand), m_category(category),
         m_genres(genres), m_countries(countries), m_cast(cast),
-        m_filename(filename), m_hash(hash), m_trailer(trailer), m_coverfile(coverfile),
+        m_filename(filename), m_sortFilename(sortFilename), m_hash(hash),
+        m_trailer(trailer), m_coverfile(coverfile),
         m_screenshot(screenshot), m_banner(banner), m_fanart(fanart),
         m_host(host), m_categoryID(categoryID), m_childID(childID),
         m_year(year), m_releasedate(releasedate), m_length(length), m_playcount(playcount),
@@ -129,6 +67,24 @@ class VideoMetadataImp
         m_userrating(userrating), m_processed(processed),
         m_contenttype(contenttype)
     {
+        // Try to glean data if none provided.
+        if (title.isEmpty() and subtitle.isEmpty()
+            and season == 0 and episode == 0)
+        {
+            m_title = VideoMetadata::FilenameToMeta(m_filename, 1);
+            m_subtitle = VideoMetadata::FilenameToMeta(m_filename, 4);
+            m_season = VideoMetadata::FilenameToMeta(m_filename, 2).toInt();
+            m_episode = VideoMetadata::FilenameToMeta(m_filename, 3).toInt();
+        }
+
+        SortHelper *sh = new SortHelper;
+        if (m_sortFilename.isEmpty())
+            m_sortFilename = sh->doPathname(m_filename);
+        if (m_sortTitle.isEmpty())
+            m_sortTitle = sh->doTitle(m_title);
+        if (m_sortSubtitle.isEmpty())
+            m_sortSubtitle = sh->doTitle(m_subtitle);
+
         VideoCategory::GetCategory().get(m_categoryID, m_category);
     }
 
@@ -147,7 +103,9 @@ class VideoMetadataImp
         if (this != &rhs)
         {
             m_title = rhs.m_title;
+            m_sortTitle = rhs.m_sortTitle;
             m_subtitle = rhs.m_subtitle;
+            m_sortSubtitle = rhs.m_sortSubtitle;
             m_tagline = rhs.m_tagline;
             m_inetref = rhs.m_inetref;
             m_collectionref = rhs.m_collectionref;
@@ -162,6 +120,7 @@ class VideoMetadataImp
             m_countries = rhs.m_countries;
             m_cast = rhs.m_cast;
             m_filename = rhs.m_filename;
+            m_sortFilename = rhs.m_sortFilename;
             m_hash = rhs.m_hash;
             m_trailer = rhs.m_trailer;
             m_coverfile = rhs.m_coverfile;
@@ -188,7 +147,6 @@ class VideoMetadataImp
             m_contenttype = rhs.m_contenttype;
 
             // No DB vars
-            m_sort_key = rhs.m_sort_key;
             m_prefix = rhs.m_prefix;
         }
 
@@ -196,26 +154,32 @@ class VideoMetadataImp
     }
 
   public:
-    bool HasSortKey() const { return m_sort_key.isSet(); }
-    const VideoMetadata::SortKey &GetSortKey() const { return m_sort_key; }
-    void SetSortKey(const VideoMetadata::SortKey &sort_key)
-    {
-        m_sort_key = sort_key;
-    }
-
     const QString &GetPrefix() const { return m_prefix; }
     void SetPrefix(const QString &prefix) { m_prefix = prefix; }
 
     const QString &getTitle() const { return m_title; }
-    void SetTitle(const QString& title)
+    const QString &getSortTitle() const { return m_sortTitle; }
+    void SetTitle(const QString& title, const QString& sortTitle)
     {
-        if (gCoreContext->HasGUI())
-            m_sort_key.Clear();
         m_title = title;
+        m_sortTitle = sortTitle;
+    }
+    void SetTitle(const QString& title)
+    {
+        SortHelper *sh = new SortHelper;
+        SetTitle(title, sh->doTitle(title));
     }
 
     const QString &getSubtitle() const { return m_subtitle; }
-    void SetSubtitle(const QString &subtitle) { m_subtitle = subtitle; }
+    const QString &getSortSubtitle() const { return m_sortSubtitle; }
+    void SetSubtitle(const QString &subtitle, const QString &sortSubtitle) {
+        m_subtitle = subtitle;
+        m_sortSubtitle = sortSubtitle;
+    }
+    void SetSubtitle(const QString &subtitle) {
+        SortHelper *sh = new SortHelper;
+        SetSubtitle(subtitle, sh->doTitle(subtitle));
+    }
 
     const QString &GetTagline() const { return m_tagline; }
     void SetTagline(const QString &tagline) { m_tagline = tagline; }
@@ -266,7 +230,10 @@ class VideoMetadataImp
     void SetHost(const QString &host) { m_host = host; }
 
     const QString &getFilename() const { return m_filename; }
-    void SetFilename(const QString &filename) { m_filename = filename; }
+    const QString &getSortFilename() const { return m_sortFilename; }
+    void SetFilename(const QString &filename, const QString &sortFilename);
+    void SetFilename(const QString &filename);
+    bool sortBefore(const VideoMetadataImp *rhs) const;
 
     const QString &GetHash() const { return m_hash; }
     void SetHash(const QString &hash) { m_hash = hash; }
@@ -367,7 +334,9 @@ class VideoMetadataImp
 
   private:
     QString m_title;
+    QString m_sortTitle;
     QString m_subtitle;
+    QString m_sortSubtitle;
     QString m_tagline;
     QString m_inetref;
     int m_collectionref;
@@ -382,6 +351,7 @@ class VideoMetadataImp
     country_list m_countries;
     cast_list m_cast;
     QString m_filename;
+    QString m_sortFilename;
     QString m_hash;
     QString m_trailer;
     QString m_coverfile;
@@ -408,13 +378,36 @@ class VideoMetadataImp
     VideoContentType m_contenttype;
 
     // not in DB
-    VideoMetadata::SortKey m_sort_key;
     QString m_prefix;
 };
 
 /////////////////////////////
 /////////
 /////////////////////////////
+void VideoMetadataImp::SetFilename(const QString &filename, const QString &sortFilename) {
+    m_filename = filename;
+    m_sortFilename = sortFilename;
+}
+
+void VideoMetadataImp::SetFilename(const QString &filename) {
+    SortHelper *sh = new SortHelper;
+    SetFilename(filename, sh->doPathname(filename));
+}
+
+/** \fn VideoMetadataImp::sortBefore(const VideoMetadataImp *)
+ *  \brief Returns true if the object should appear before the argument.
+ */
+bool VideoMetadataImp::sortBefore(const VideoMetadataImp *rhs) const
+{
+    int ret = naturalCompare(m_sortTitle, rhs->m_sortTitle);
+    if (ret != 0)
+        return (ret == -1);
+    ret = naturalCompare(m_sortFilename, rhs->m_sortFilename);
+    if (ret != 0)
+        return (ret == -1);
+    return (m_id < rhs->m_id);
+}
+
 bool VideoMetadataImp::removeDir(const QString &dirName)
 {
     QDir d(dirName);
@@ -477,15 +470,15 @@ bool VideoMetadataImp::DeleteFile()
 
 void VideoMetadataImp::Reset()
 {
-    VideoMetadataImp tmp(m_filename, VideoMetadata::VideoFileHash(m_filename, m_host), VIDEO_TRAILER_DEFAULT,
+    VideoMetadataImp tmp(m_filename, QString(),
+                    VideoMetadata::VideoFileHash(m_filename, m_host), VIDEO_TRAILER_DEFAULT,
                     VIDEO_COVERFILE_DEFAULT, VIDEO_SCREENSHOT_DEFAULT, VIDEO_BANNER_DEFAULT,
-                    VIDEO_FANART_DEFAULT, VideoMetadata::FilenameToMeta(m_filename, 1), QString(),
-                    VideoMetadata::FilenameToMeta(m_filename, 4), VIDEO_YEAR_DEFAULT,
+                    VIDEO_FANART_DEFAULT, QString(), QString(), QString(), QString(),
+                    QString(), VIDEO_YEAR_DEFAULT,
                     QDate(), VIDEO_INETREF_DEFAULT, -1, QString(), VIDEO_DIRECTOR_DEFAULT,
                     QString(), VIDEO_PLOT_DEFAULT, 0.0,
                     VIDEO_RATING_DEFAULT, 0, 0,
-                    VideoMetadata::FilenameToMeta(m_filename, 2).toInt(),
-                    VideoMetadata::FilenameToMeta(m_filename, 3).toInt(), QDate(), m_id,
+                    0, 0, QDate(), m_id,
                     ParentalLevel::plLowest, 0, -1, true, false, "", "",
                     VideoMetadata::genre_list(), VideoMetadata::country_list(),
                     VideoMetadata::cast_list(), m_host, false);
@@ -557,6 +550,9 @@ void VideoMetadataImp::fillCast()
 }
 
 /// Sets metadata from a DB row
+///
+/// Query in VideoMetadataListManager::loadAllFromDatabase
+///
 void VideoMetadataImp::fromDBRow(MSqlQuery &query)
 {
     m_title = query.value(0).toString();
@@ -600,6 +596,10 @@ void VideoMetadataImp::fromDBRow(MSqlQuery &query)
 
     m_contenttype = ContentTypeFromString(query.value(34).toString());
 
+    m_sortTitle = query.value(35).toString();
+    m_sortSubtitle = query.value(36).toString();
+    m_sortFilename = query.value(37).toString();
+
     VideoCategory::GetCategory().get(m_categoryID, m_category);
 
     // Genres
@@ -614,12 +614,20 @@ void VideoMetadataImp::fromDBRow(MSqlQuery &query)
 
 void VideoMetadataImp::saveToDatabase()
 {
+    SortHelper *sh = new SortHelper;
+
     if (m_title.isEmpty())
+    {
         m_title = VideoMetadata::FilenameToMeta(m_filename, 1);
+        m_sortTitle = sh->doTitle(m_title);
+    }
     if (m_hash.isEmpty())
         m_hash = VideoMetadata::VideoFileHash(m_filename, m_host);
     if (m_subtitle.isEmpty())
+    {
         m_subtitle = VideoMetadata::FilenameToMeta(m_filename, 4);
+        m_sortSubtitle = sh->doTitle(m_subtitle);
+    }
     if (m_director.isEmpty())
         m_director = VIDEO_DIRECTOR_UNKNOWN;
     if (m_plot.isEmpty())
@@ -670,23 +678,27 @@ void VideoMetadataImp::saveToDatabase()
 
         m_watched = 0;
 
-        query.prepare("INSERT INTO videometadata (title,subtitle,tagline,director,studio,plot,"
-                      "rating,year,userrating,length,season,episode,filename,hash,"
+        query.prepare("INSERT INTO videometadata (title,sorttitle,subtitle,sortsubtitle,"
+                      "tagline,director,studio,plot,rating,year,userrating,length,"
+                      "season,episode,filename,sortfilename,hash,"
                       "showlevel,coverfile,inetref,homepage,browse,watched,trailer,"
-                      "screenshot,banner,fanart,host,processed,contenttype) VALUES (:TITLE, :SUBTITLE, "
+                      "screenshot,banner,fanart,host,processed,contenttype) "
+                      "VALUES (:TITLE, :TITLESORT, :SUBTITLE, :SUBTITLESORT, "
                       ":TAGLINE, :DIRECTOR, :STUDIO, :PLOT, :RATING, :YEAR, :USERRATING, "
-                      ":LENGTH, :SEASON, :EPISODE, :FILENAME, :HASH, :SHOWLEVEL, "
+                      ":LENGTH, :SEASON, :EPISODE, :FILENAME, :SORTFILENAME, :HASH, :SHOWLEVEL, "
                       ":COVERFILE, :INETREF, :HOMEPAGE, :BROWSE, :WATCHED, "
                       ":TRAILER, :SCREENSHOT, :BANNER, :FANART, :HOST, :PROCESSED, :CONTENTTYPE)");
     }
     else
     {
-        query.prepare("UPDATE videometadata SET title = :TITLE, subtitle = :SUBTITLE, "
+        query.prepare("UPDATE videometadata SET title = :TITLE, sorttitle = :SORTTITLE, "
+                      "subtitle = :SUBTITLE, sortsubtitle = :SORTSUBTITLE, "
                       "tagline = :TAGLINE, director = :DIRECTOR, studio = :STUDIO, "
                       "plot = :PLOT, rating= :RATING, year = :YEAR, "
                       "releasedate = :RELEASEDATE, userrating = :USERRATING, "
                       "length = :LENGTH, playcount = :PLAYCOUNT, season = :SEASON, "
-                      "episode = :EPISODE, filename = :FILENAME, hash = :HASH, trailer = :TRAILER, "
+                      "episode = :EPISODE, filename = :FILENAME, sortfilename = :SORTFILENAME, "
+                      "hash = :HASH, trailer = :TRAILER, "
                       "showlevel = :SHOWLEVEL, coverfile = :COVERFILE, "
                       "screenshot = :SCREENSHOT, banner = :BANNER, fanart = :FANART, "
                       "inetref = :INETREF, collectionref = :COLLECTION, homepage = :HOMEPAGE, "
@@ -701,7 +713,9 @@ void VideoMetadataImp::saveToDatabase()
     }
 
     query.bindValue(":TITLE", m_title.isNull() ? "" : m_title);
+    query.bindValue(":SORTTITLE", m_sortTitle.isNull() ? "" : m_sortTitle);
     query.bindValue(":SUBTITLE", m_subtitle.isNull() ? "" : m_subtitle);
+    query.bindValue(":SORTSUBTITLE", m_sortSubtitle.isNull() ? "" : m_sortSubtitle);
     query.bindValue(":TAGLINE", m_tagline);
     query.bindValue(":DIRECTOR", m_director.isNull() ? "" : m_director);
     query.bindValue(":STUDIO", m_studio);
@@ -715,6 +729,7 @@ void VideoMetadataImp::saveToDatabase()
     query.bindValue(":SEASON", m_season);
     query.bindValue(":EPISODE", m_episode);
     query.bindValue(":FILENAME", m_filename);
+    query.bindValue(":SORTFILENAME", m_sortFilename);
     query.bindValue(":HASH", m_hash);
     query.bindValue(":TRAILER", m_trailer.isNull() ? "" : m_trailer);
     query.bindValue(":SHOWLEVEL", m_showlevel);
@@ -960,16 +975,6 @@ void VideoMetadataImp::GetImageMap(InfoMap &imageMap) const
 ////////////////////////////////////////
 //// Metadata
 ////////////////////////////////////////
-VideoMetadata::SortKey VideoMetadata::GenerateDefaultSortKey(const VideoMetadata &m,
-                                                   bool ignore_case)
-{
-    QString title(ignore_case ? m.GetTitle().toLower() : m.GetTitle());
-    title = TrimTitle(title, ignore_case);
-
-    return SortKey(SortData(title, m.GetFilename(),
-                         QString().sprintf("%.7d", m.GetID())));
-}
-
 namespace
 {
     QString eatBraces(const QString &title, const QString &left_brace,
@@ -1022,6 +1027,7 @@ int VideoMetadata::UpdateHashedDBRecord(const QString &hash,
                                    const QString &host)
 {
     MSqlQuery query(MSqlQuery::InitCon());
+    SortHelper *sh = new SortHelper;
 
     query.prepare("SELECT intid,filename FROM videometadata WHERE "
                   "hash = :HASH");
@@ -1040,8 +1046,10 @@ int VideoMetadata::UpdateHashedDBRecord(const QString &hash,
     QString oldfilename = query.value(1).toString();
 
     query.prepare("UPDATE videometadata SET filename = :FILENAME, "
-                  "host = :HOST WHERE intid = :INTID");
+                  "sortfilename = :SORTFILENAME, host = :HOST "
+                  "WHERE intid = :INTID");
     query.bindValue(":FILENAME", file_name);
+    query.bindValue(":SORTFILENAME", sh->doPathname(file_name));
     query.bindValue(":HOST", host);
     query.bindValue(":INTID", intid);
 
@@ -1172,28 +1180,11 @@ QString VideoMetadata::FilenameToMeta(const QString &file_name, int position)
     return QString();
 }
 
-namespace
-{
-    const QRegExp &getTitleTrim(bool ignore_case)
-    {
-        static QString pattern(VideoMetadata::tr("^(The |A |An )"));
-        static QRegExp prefixes_case(pattern, Qt::CaseSensitive);
-        static QRegExp prefixes_nocase(pattern, Qt::CaseInsensitive);
-        return ignore_case ? prefixes_nocase : prefixes_case;
-    }
-}
-
-QString VideoMetadata::TrimTitle(const QString &title, bool ignore_case)
-{
-    QString ret(title);
-    ret.remove(getTitleTrim(ignore_case));
-    return ret;
-}
-
-VideoMetadata::VideoMetadata(const QString &filename, const QString &hash,
-             const QString &trailer, const QString &coverfile,
+VideoMetadata::VideoMetadata(const QString &filename, const QString &sortFilename,
+             const QString &hash, const QString &trailer, const QString &coverfile,
              const QString &screenshot, const QString &banner, const QString &fanart,
-             const QString &title, const QString &subtitle, const QString &tagline,
+             const QString &title, const QString &sortTitle,
+             const QString &subtitle, const QString &sortSubtitle, const QString &tagline,
              int year, const QDate &releasedate, const QString &inetref, int collectionref,
              const QString &homepage, const QString &director, const QString &studio,
              const QString &plot, float userrating, const QString &rating,
@@ -1207,8 +1198,9 @@ VideoMetadata::VideoMetadata(const QString &filename, const QString &hash,
              const QString &host, bool processed,
              VideoContentType contenttype)
 {
-    m_imp = new VideoMetadataImp(filename, hash, trailer, coverfile, screenshot, banner,
-                            fanart, title, subtitle, tagline, year, releasedate, inetref,
+    m_imp = new VideoMetadataImp(filename, sortFilename, hash, trailer, coverfile,
+                            screenshot, banner, fanart, title, sortTitle, subtitle,
+                            sortSubtitle, tagline, year, releasedate, inetref,
                             collectionref, homepage, director, studio, plot, userrating, rating,
                             length, playcount, season, episode, insertdate, id, showlevel, categoryID,
                             childID, browse, watched, playcommand, category, genres, countries,
@@ -1248,8 +1240,11 @@ void VideoMetadata::toMap(InfoMap &metadataMap)
     GetImageMap(metadataMap);
 
     metadataMap["filename"] = GetFilename();
+    metadataMap["sortfilename"] = GetSortFilename();
     metadataMap["title"] = GetTitle();
+    metadataMap["sorttitle"] = GetSortTitle();
     metadataMap["subtitle"] = GetSubtitle();
+    metadataMap["sortsubtitle"] = GetSortSubtitle();
     metadataMap["tagline"] = GetTagline();
     metadataMap["director"] = GetDirector();
     metadataMap["studio"] = GetStudio();
@@ -1320,8 +1315,11 @@ void ClearMap(InfoMap &metadataMap)
     metadataMap["bannerfile"] = "";
     metadataMap["fanartfile"] = "";
     metadataMap["filename"] = "";
+    metadataMap["sortfilename"] = "";
     metadataMap["title"] = "";
+    metadataMap["sorttitle"] = "";
     metadataMap["subtitle"] = "";
+    metadataMap["sortsubtitle"] = "";
     metadataMap["tagline"] = "";
     metadataMap["director"] = "";
     metadataMap["studio"] = "";
@@ -1353,21 +1351,6 @@ void ClearMap(InfoMap &metadataMap)
     metadataMap["processed"] = "";
 }
 
-bool VideoMetadata::HasSortKey() const
-{
-    return m_imp->HasSortKey();
-}
-
-const VideoMetadata::SortKey &VideoMetadata::GetSortKey() const
-{
-    return m_imp->GetSortKey();
-}
-
-void VideoMetadata::SetSortKey(const VideoMetadata::SortKey &sort_key)
-{
-    m_imp->SetSortKey(sort_key);
-}
-
 const QString &VideoMetadata::GetPrefix() const
 {
     return m_imp->GetPrefix();
@@ -1383,6 +1366,16 @@ const QString &VideoMetadata::GetTitle() const
     return m_imp->getTitle();
 }
 
+const QString &VideoMetadata::GetSortTitle() const
+{
+    return m_imp->getSortTitle();
+}
+
+void VideoMetadata::SetTitle(const QString &title, const QString &sortTitle)
+{
+    m_imp->SetTitle(title, sortTitle);
+}
+
 void VideoMetadata::SetTitle(const QString &title)
 {
     m_imp->SetTitle(title);
@@ -1393,6 +1386,16 @@ const QString &VideoMetadata::GetSubtitle() const
     return m_imp->getSubtitle();
 }
 
+const QString &VideoMetadata::GetSortSubtitle() const
+{
+    return m_imp->getSortSubtitle();
+}
+
+void VideoMetadata::SetSubtitle(const QString &subtitle, const QString &sortSubtitle)
+{
+    m_imp->SetSubtitle(subtitle, sortSubtitle);
+}
+
 void VideoMetadata::SetSubtitle(const QString &subtitle)
 {
     m_imp->SetSubtitle(subtitle);
@@ -1643,6 +1646,11 @@ const QString &VideoMetadata::GetFilename() const
     return m_imp->getFilename();
 }
 
+const QString &VideoMetadata::GetSortFilename() const
+{
+    return m_imp->getSortFilename();
+}
+
 const QString &VideoMetadata::GetHash() const
 {
     return m_imp->GetHash();
@@ -1663,6 +1671,11 @@ void VideoMetadata::SetHost(const QString &host)
         m_imp->SetHost(host);
 }
 
+void VideoMetadata::SetFilename(const QString &filename, const QString &sortFilename)
+{
+    m_imp->SetFilename(filename, sortFilename);
+}
+
 void VideoMetadata::SetFilename(const QString &filename)
 {
     m_imp->SetFilename(filename);
@@ -1831,6 +1844,11 @@ bool VideoMetadata::IsHostSet() const
     return m_imp->IsHostSet();
 }
 
+bool VideoMetadata::sortBefore(const VideoMetadata &rhs) const
+{
+    return m_imp->sortBefore(rhs.m_imp);
+}
+
 bool operator==(const VideoMetadata& a, const VideoMetadata& b)
 {
     if (a.GetFilename() == b.GetFilename())
@@ -1844,15 +1862,3 @@ bool operator!=(const VideoMetadata& a, const VideoMetadata& b)
         return true;
     return false;
 }
-
-bool operator<(const VideoMetadata::SortKey &lhs, const VideoMetadata::SortKey &rhs)
-{
-    if (lhs.m_sd && rhs.m_sd)
-        return *lhs.m_sd < *rhs.m_sd;
-    else
-    {
-        LOG(VB_GENERAL, LOG_ERR,
-            "Error: Bug, Metadata item with empty sort key compared");
-        return lhs.m_sd < rhs.m_sd;
-    }
-}
diff --git a/mythtv/libs/libmythmetadata/videometadata.h b/mythtv/libs/libmythmetadata/videometadata.h
index a02587c..5fb8289 100644
--- a/mythtv/libs/libmythmetadata/videometadata.h
+++ b/mythtv/libs/libmythmetadata/videometadata.h
@@ -19,8 +19,6 @@ enum { VIDEO_YEAR_DEFAULT = 1895 };
 
 const QString VIDEO_SUBTITLE_DEFAULT = "";
 
-struct SortData;
-
 typedef QHash<QString,QString> MetadataMap;
 
 class META_PUBLIC VideoMetadata
@@ -36,24 +34,6 @@ class META_PUBLIC VideoMetadata
     typedef std::vector<cast_entry> cast_list;
 
   public:
-    class META_PUBLIC SortKey
-    {
-      public:
-        SortKey();
-        explicit SortKey(const SortData &data);
-        SortKey(const SortKey &other);
-        SortKey &operator=(const SortKey &rhs);
-        ~SortKey();
-
-        bool isSet() const;
-        void Clear();
-
-      public:
-        SortData *m_sd;
-    };
-
-  public:
-    static SortKey GenerateDefaultSortKey(const VideoMetadata &m, bool ignore_case);
     static int UpdateHashedDBRecord(const QString &hash, const QString &file_name,
                                     const QString &host);
     static QString VideoFileHash(const QString &file_name, const QString &host);
@@ -62,6 +42,7 @@ class META_PUBLIC VideoMetadata
 
   public:
     VideoMetadata(const QString &filename = QString(),
+             const QString &sortFilename = QString(),
              const QString &hash = QString(),
              const QString &trailer = QString(),
              const QString &coverfile = QString(),
@@ -69,7 +50,9 @@ class META_PUBLIC VideoMetadata
              const QString &banner = QString(),
              const QString &fanart = QString(),
              const QString &title = QString(),
+             const QString &sortTitle = QString(),
              const QString &subtitle = QString(),
+             const QString &sortSubtitle = QString(),
              const QString &tagline = QString(),
              int year = VIDEO_YEAR_DEFAULT,
              const QDate &releasedate = QDate(),
@@ -104,23 +87,23 @@ class META_PUBLIC VideoMetadata
     explicit VideoMetadata(MSqlQuery &query);
     VideoMetadata(const VideoMetadata &rhs);
     VideoMetadata &operator=(const VideoMetadata &rhs);
+    bool sortBefore(const VideoMetadata &rhs) const;
 
     void toMap(InfoMap &metadataMap);
     void GetStateMap(InfoMap &stateMap);
     void GetImageMap(InfoMap &imageMap);
 
-    // returns a string to use when sorting
-    bool HasSortKey() const;
-    const SortKey &GetSortKey() const;
-    void SetSortKey(const SortKey &sort_key);
-
     const QString &GetPrefix() const;
     void SetPrefix(const QString &prefix);
 
     const QString &GetTitle() const;
+    const QString &GetSortTitle() const;
+    void SetTitle(const QString& title, const QString& sortTitle);
     void SetTitle(const QString& title);
 
     const QString &GetSubtitle() const;
+    const QString &GetSortSubtitle() const;
+    void SetSubtitle(const QString &subtitle, const QString &sortSubtitle);
     void SetSubtitle(const QString &subtitle);
 
     const QString &GetTagline() const;
@@ -199,6 +182,8 @@ class META_PUBLIC VideoMetadata
     void SetHost(const QString &host);
 
     const QString &GetFilename() const;
+    const QString &GetSortFilename() const;
+    void SetFilename(const QString &filename, const QString &sortFilename);
     void SetFilename(const QString &filename);
 
     const QString &GetHash() const;
@@ -258,8 +243,6 @@ META_PUBLIC void ClearMap(InfoMap &metadataMap);
 META_PUBLIC bool operator==(const VideoMetadata &a, const VideoMetadata &b);
 META_PUBLIC bool operator!=(const VideoMetadata &a, const VideoMetadata &b);
 
-META_PUBLIC bool operator<(const VideoMetadata::SortKey &lhs, const VideoMetadata::SortKey &rhs);
-
 Q_DECLARE_METATYPE(VideoMetadata*)
 
 #endif
diff --git a/mythtv/libs/libmythmetadata/videometadatalistmanager.cpp b/mythtv/libs/libmythmetadata/videometadatalistmanager.cpp
index e83198a..854ddb1 100644
--- a/mythtv/libs/libmythmetadata/videometadatalistmanager.cpp
+++ b/mythtv/libs/libmythmetadata/videometadatalistmanager.cpp
@@ -2,6 +2,7 @@
 
 #include "mythdb.h"
 #include "videometadatalistmanager.h"
+#include "mythsorthelper.h"
 
 class VideoMetadataListManagerImp
 {
@@ -120,18 +121,23 @@ VideoMetadataListManager::loadOneFromDatabase(uint id)
     return VideoMetadataPtr(new VideoMetadata());
 }
 
+/// Load videometadata database into memory
+///
+/// Query consumed in VideoMetadataImp::fromDBRow
+///
 void VideoMetadataListManager::loadAllFromDatabase(metadata_list &items,
                                                    const QString &sql)
 {
     MSqlQuery query(MSqlQuery::InitCon());
     query.setForwardOnly(true);
     QString BaseMetadataQuery(
-        "SELECT title, director, studio, plot, rating, year, releasedate,"
-        "userrating, length, playcount, filename, hash, showlevel, "
-        "coverfile, inetref, collectionref, homepage, childid, browse, watched, "
-        "playcommand, category, intid, trailer, screenshot, banner, fanart, "
-        "subtitle, tagline, season, episode, host, insertdate, processed, "
-        "contenttype FROM videometadata ");
+        "SELECT title, director, studio, plot, rating, year, releasedate," // 0-6
+        "userrating, length, playcount, filename, hash, showlevel, "       // 7-12
+        "coverfile, inetref, collectionref, homepage, childid, browse, watched, " // 13-19
+        "playcommand, category, intid, trailer, screenshot, banner, fanart, " // 20-26
+        "subtitle, tagline, season, episode, host, insertdate, processed, " // 27-33
+        "contenttype, sorttitle, sortsubtitle, sortfilename " // 34-37
+        "FROM videometadata ");
 
     if (!sql.isEmpty())
         BaseMetadataQuery.append(sql);
@@ -249,6 +255,9 @@ meta_dir_node::meta_dir_node(const QString &path, const QString &name,
   : meta_node(parent, is_path_root), m_path(path), m_name(name),
     m_host(host), m_prefix(prefix), m_data(data)
 {
+    SortHelper *sh = new SortHelper;
+    m_sortPath = sh->doPathname(m_path);
+
     if (!name.length())
         m_name = path;
 }
@@ -288,9 +297,21 @@ const QString &meta_dir_node::getPath() const
     return m_path;
 }
 
-void meta_dir_node::setPath(const QString &path)
+const QString &meta_dir_node::getSortPath() const
+{
+    return m_sortPath;
+}
+
+void meta_dir_node::setPath(const QString &path, const QString &sortPath)
 {
     m_path = path;
+    m_sortPath = sortPath;
+}
+
+void meta_dir_node::setPath(const QString &path)
+{
+    SortHelper *sh = new SortHelper;
+    setPath(path, sh->doPathname(path));
 }
 
 void meta_dir_node::SetData(const QVariant &data)
diff --git a/mythtv/libs/libmythmetadata/videometadatalistmanager.h b/mythtv/libs/libmythmetadata/videometadatalistmanager.h
index 002f88c..cfc4e1c 100644
--- a/mythtv/libs/libmythmetadata/videometadatalistmanager.h
+++ b/mythtv/libs/libmythmetadata/videometadatalistmanager.h
@@ -102,6 +102,8 @@ class META_PUBLIC meta_dir_node : public meta_node
     void SetPrefix(const QString &prefix);
     const QString &GetPrefix() const;
     const QString &getPath() const;
+    const QString &getSortPath() const;
+    void setPath(const QString &path, const QString &sortPath);
     void setPath(const QString &path);
     void SetData(const QVariant &data);
     const QVariant &GetData() const;
@@ -146,6 +148,7 @@ class META_PUBLIC meta_dir_node : public meta_node
 
   private:
     QString m_path;
+    QString m_sortPath;
     QString m_name;
     QString m_host;
     QString m_prefix;
diff --git a/mythtv/libs/libmythmetadata/videoscan.cpp b/mythtv/libs/libmythmetadata/videoscan.cpp
index e40c792..72cf2a0 100644
--- a/mythtv/libs/libmythmetadata/videoscan.cpp
+++ b/mythtv/libs/libmythmetadata/videoscan.cpp
@@ -340,22 +340,20 @@ bool VideoScannerThread::updateDB(const FileCheckList &add, const PurgeList &rem
             if (id == -1)
             {
                 VideoMetadata newFile(
-                    p->first, hash,
+                    p->first, QString(), hash,
                     VIDEO_TRAILER_DEFAULT,
                     VIDEO_COVERFILE_DEFAULT,
                     VIDEO_SCREENSHOT_DEFAULT,
                     VIDEO_BANNER_DEFAULT,
                     VIDEO_FANART_DEFAULT,
-                    VideoMetadata::FilenameToMeta(p->first, 1),
-                    VideoMetadata::FilenameToMeta(p->first, 4),
+                    QString(), QString(), QString(), QString(),
                     QString(),
                     VIDEO_YEAR_DEFAULT,
                     QDate::fromString("0000-00-00","YYYY-MM-DD"),
                     VIDEO_INETREF_DEFAULT, 0, QString(),
                     VIDEO_DIRECTOR_DEFAULT, QString(), VIDEO_PLOT_DEFAULT,
                     0.0, VIDEO_RATING_DEFAULT, 0, 0,
-                    VideoMetadata::FilenameToMeta(p->first, 2).toInt(),
-                    VideoMetadata::FilenameToMeta(p->first, 3).toInt(),
+                    0, 0,
                     MythDate::current().date(),
                     0, ParentalLevel::plLowest);
 
diff --git a/mythtv/libs/libmythtv/datadirect.cpp b/mythtv/libs/libmythtv/datadirect.cpp
index 7c62597..fc44eef 100644
--- a/mythtv/libs/libmythtv/datadirect.cpp
+++ b/mythtv/libs/libmythtv/datadirect.cpp
@@ -26,6 +26,7 @@
 #include "mythdownloadmanager.h"
 #include "mythtvexp.h"
 #include "mythdate.h"
+#include "mythsorthelper.h"
 
 #define LOC QString("DataDirect: ")
 
@@ -212,6 +213,7 @@ bool DDStructureParser::endElement(const QString &pnamespaceuri,
     (void)pnamespaceuri;
     (void)plocalname;
 
+    SortHelper *sh = new SortHelper;
     MSqlQuery query(MSqlQuery::DDCon());
 
     if (pqname == "station")
@@ -336,13 +338,15 @@ bool DDStructureParser::endElement(const QString &pnamespaceuri,
 
         query.prepare(
             "INSERT INTO dd_program "
-            "     ( programid,    title,       subtitle,       "
+            "     ( programid,    title,       sorttitle,      "
+            "       subtitle,     sortsubtitle,                "
             "       description,  showtype,    category_type,  "
             "       mpaarating,   starrating,  stars,          "
             "       runtime,      year,        seriesid,       "
             "       colorcode,    syndicatedepisodenumber, originalairdate) "
             "VALUES "
-            "     (:PROGRAMID,   :TITLE,      :SUBTITLE,       "
+            "     (:PROGRAMID,   :TITLE,      :SORTTITLE,      "
+            "      :SUBTITLE,    :SORTSUBTITLE,                "
             "      :DESCRIPTION, :SHOWTYPE,   :CATTYPE,        "
             "      :MPAARATING,  :STARRATING, :STARS,          "
             "      :RUNTIME,     :YEAR,       :SERIESID,       "
@@ -350,7 +354,9 @@ bool DDStructureParser::endElement(const QString &pnamespaceuri,
 
         query.bindValue(":PROGRAMID",   curr_program.programid);
         query.bindValue(":TITLE",       curr_program.title);
+        query.bindValue(":SORTTITLE",   sh->doTitle(curr_program.title));
         query.bindValue(":SUBTITLE",    curr_program.subtitle);
+        query.bindValue(":SORTSUBTITLE",sh->doTitle(curr_program.subtitle));
         query.bindValue(":DESCRIPTION", curr_program.description);
         query.bindValue(":SHOWTYPE",    curr_program.showtype);
         query.bindValue(":CATTYPE",     cat_type);
@@ -635,7 +641,8 @@ void DataDirectProcessor::UpdateProgramViewTable(uint sourceid)
     QString qstr =
         "INSERT INTO dd_v_program "
         "     ( chanid,                  starttime,       endtime,         "
-        "       title,                   subtitle,        description,     "
+        "       title,                   sorttitle,                        "
+        "       subtitle,                sortsubtitle,    description,     "
         "       airdate,                 stars,           previouslyshown, "
         "       stereo,                  dolby,           subtitled,       "
         "       hdtv,                    closecaptioned,  partnumber,      "
@@ -644,7 +651,8 @@ void DataDirectProcessor::UpdateProgramViewTable(uint sourceid)
         "       syndicatedepisodenumber, tvrating,        mpaarating,      "
         "       programid )      "
         "SELECT chanid,                  scheduletime,    endtime,         "
-        "       title,                   subtitle,        description,     "
+        "       title,                   sorttitle,                        "
+        "       subtitle,                sortsubtitle,    description,     "
         "       year,                    stars,           isrepeat,        "
         "       stereo,                  dolby,           subtitled,       "
         "       hdtv,                    closecaptioned,  partnumber,      "
@@ -840,8 +848,9 @@ void DataDirectProcessor::DataDirectProgramUpdate(void)
 #endif
     query.prepare(
         "INSERT IGNORE INTO program "
-        "  ( chanid,        starttime,   endtime,         title,           "
-        "    subtitle,      description, showtype,        category,        "
+        "  ( chanid,        starttime,   endtime,                          "
+        "    title,         sorttitle,   subtitle,        sortsubtitle,    "
+        "                   description, showtype,        category,        "
         "    category_type, airdate,     stars,           previouslyshown, "
         "    stereo,        subtitled,   subtitletypes,   videoprop,       "
         "    audioprop,     hdtv,        closecaptioned,  partnumber,      "
@@ -852,8 +861,8 @@ void DataDirectProcessor::DataDirectProgramUpdate(void)
         "    dd_v_program.chanid,                                          "
         "    DATE_ADD(starttime, INTERVAL channel.tmoffset MINUTE),        "
         "    DATE_ADD(endtime, INTERVAL channel.tmoffset MINUTE),          "
-        "                                                 title,           "
-        "    subtitle,      description, showtype,        dd_genre.class,  "
+        "    title,         sorttitle,   subtitle,        sortsubtitle,    "
+        "                   description, showtype,        dd_genre.class,  "
         "    category_type, airdate,     stars,           previouslyshown, "
         "    stereo,        subtitled,                                     "
         "    (subtitled << 1 ) | closecaptioned, hdtv,                     "
@@ -1306,7 +1315,8 @@ void DataDirectProcessor::CreateTempTables()
 
     dd_tables["dd_program"] =
         "( programid char(40) NOT NULL,  seriesid char(12),     "
-        "  title varchar(120),           subtitle varchar(150), "
+        "  title varchar(120),           sorttitle varchar(120), "
+        "  subtitle varchar(150),        sortsubtitle varchar(150), "
         "  description text,             mpaarating char(5),    "
         "  starrating char(5),           runtime time,          "
         "  year char(4),                 showtype char(30),     "
@@ -1318,7 +1328,8 @@ void DataDirectProcessor::CreateTempTables()
     dd_tables["dd_v_program"] =
         "( chanid int unsigned NOT NULL, starttime datetime NOT NULL, "
         "  endtime datetime,             title varchar(128),          "
-        "  subtitle varchar(128),        description text,            "
+        "  sorttitle varchar(128),       subtitle varchar(128),       "
+        "  sortsubtitle varchar(128),    description text,            "
         "  category varchar(64),         category_type varchar(64),   "
         "  airdate year,                 stars float unsigned,        "
         "  previouslyshown tinyint,      isrepeat bool,               "
diff --git a/mythtv/libs/libmythtv/dbcheck.cpp b/mythtv/libs/libmythtv/dbcheck.cpp
index a0892ce..b88c179 100644
--- a/mythtv/libs/libmythtv/dbcheck.cpp
+++ b/mythtv/libs/libmythtv/dbcheck.cpp
@@ -18,6 +18,7 @@ using namespace std;
 #include "recordingprofile.h"
 #include "recordinginfo.h"
 #include "cardutil.h"
+#include "mythsorthelper.h"
 
 // TODO convert all dates to UTC
 
@@ -542,6 +543,73 @@ bool UpgradeTVDatabaseSchema(const bool upgradeAllowed,
     return false;
 }
 
+
+/** \fn populateSortColumn(const char *, const char *)
+ *  \brief Called by doUpgradeTVDatabaseSchema() to populate a single
+ *         sort column.
+ *
+ *   This function populates the information ins a single sort
+ *   column. The sort column has the same name as the original column
+ *   with the name "sort" appended. I.E. The sort data for the column
+ *   "title" is put into column "sorttitle". This function attempts to
+ *   be efficient by asking the database to only return distinct
+ *   entries. The "ORDER" clause should eventually be dropped.
+ *
+ *  \param table The name of the table to update.
+ *
+ *  \param column The name of a column whose sort data should be
+ *                populated.
+ *
+ *  \return true on success, false on failure
+ */
+static bool populateSortColumn(const char *table, const char *column, int num = 0, int total = 0)
+{
+    MSqlQuery select(MSqlQuery::InitCon());
+    MSqlQuery update(MSqlQuery::InitCon());
+    SortHelper *sh = new SortHelper;
+
+    QString thequery = QString("SELECT DISTINCT(%1) AS %1 FROM %2 ORDER BY %1")
+                               .arg(column).arg(table);
+    QString theupdate = QString("UPDATE %1 SET sort%2=:SORT WHERE %2=:ORIG")
+                                .arg(table).arg(column);
+
+    if (!select.exec(thequery))
+    {
+        MythDB::DBError(QString("Unable to retrieve %1 %2 information.")
+                        .arg(table).arg(column), select);
+        return false;
+    }
+    while (select.next())
+    {
+        update.prepare(theupdate);
+        QString string = select.value(0).toString();
+        QString sortString = (strcmp(column,"filename") == 0)
+            ? sh->doPathname(string) : sh->doTitle(string);
+        update.bindValue(":ORIG", string);
+        update.bindValue(":SORT", sortString);
+        if (!update.exec())
+        {
+            MythDB::DBError(QString("Unable to update %1 %2 information.")
+                            .arg(table).arg(column), update);
+            return false;
+        }
+    }
+    if (num > 0)
+        LOG(VB_GENERAL, LOG_INFO, QString("Populated %1 table [%2/%3].")
+            .arg(table).arg(num).arg(total));
+    return true;
+}
+
+static bool populateSortColumns(const char *table, const char *column1, const char *column2, int num = 0, int total = 0)
+{
+    bool result = populateSortColumn(table, column1);
+    result &= populateSortColumn(table, column2);
+    if (num > 0)
+        LOG(VB_GENERAL, LOG_INFO, QString("Populated %1 table [%2/%3].")
+            .arg(table).arg(num).arg(total));
+    return result;
+}
+
 /** \fn doUpgradeTVDatabaseSchema(void)
  *  \brief This is called by UpgradeTVDatabaseSchema() to actually upgrade
  *         the schema to what MythTV expects.
@@ -3331,6 +3399,58 @@ NULL
             return false;
     }
 
+    if (dbver == "1346")
+    {
+        const char *updates[] = {
+            "ALTER TABLE internetcontentarticles "
+            "    ADD COLUMN sorttitle varchar(255) NOT NULL AFTER title,"
+            "    ADD COLUMN sortsubtitle varchar(255) NOT NULL AFTER subtitle",
+            "ALTER TABLE oldrecorded "
+            "    ADD COLUMN sorttitle varchar(128) NOT NULL AFTER title,"
+            "    ADD COLUMN sortsubtitle varchar(128) NOT NULL AFTER subtitle,"
+            "    ADD KEY sorttitle (sorttitle),"
+            "    ADD KEY sortsubtitle (sortsubtitle)",
+            "ALTER TABLE program "
+            "    ADD COLUMN sorttitle varchar(128) NOT NULL AFTER title,"
+            "    ADD COLUMN sortsubtitle varchar(128) NOT NULL AFTER subtitle,"
+            "    ADD KEY sorttitle (sorttitle),"
+            "    ADD KEY sortsubtitle (sortsubtitle)",
+            "ALTER TABLE record "
+            "    ADD COLUMN sorttitle varchar(128) NOT NULL AFTER title,"
+            "    ADD COLUMN sortsubtitle varchar(128) NOT NULL AFTER subtitle,"
+            "    ADD KEY sorttitle (sorttitle)",
+            "ALTER TABLE recorded "
+            "    ADD COLUMN sorttitle varchar(128) NOT NULL AFTER title,"
+            "    ADD COLUMN sortsubtitle varchar(128) NOT NULL AFTER subtitle,"
+            "    ADD KEY sorttitle (sorttitle)",
+            "ALTER TABLE recordedprogram "
+            "    ADD COLUMN sorttitle varchar(128) NOT NULL AFTER title,"
+            "    ADD COLUMN sortsubtitle varchar(128) NOT NULL AFTER subtitle,"
+            "    ADD KEY sorttitle (sorttitle)",
+            "ALTER TABLE videometadata "
+            "    ADD COLUMN sorttitle varchar(128) NOT NULL AFTER title,"
+            "    ADD COLUMN sortsubtitle varchar(128) NOT NULL AFTER subtitle,"
+            "    ADD COLUMN sortfilename text NOT NULL AFTER filename,"
+            "    ADD KEY sorttitle (sorttitle)",
+            NULL
+        };
+
+        if (!performUpdateSeries(updates))
+            return false;
+        LOG(VB_GENERAL, LOG_INFO, "Populating sort columns. This may take some time.");
+        if (!populateSortColumns("internetcontentarticles", "title", "subtitle", 1, 8)
+            or !populateSortColumns("oldrecorded", "title", "subtitle", 2, 8)
+            or !populateSortColumns("program", "title", "subtitle", 3, 8)
+            or !populateSortColumns("record", "title", "subtitle", 4, 8)
+            or !populateSortColumns("recorded", "title", "subtitle", 5, 8)
+            or !populateSortColumns("recordedprogram", "title", "subtitle", 6, 8)
+            or !populateSortColumn("videometadata", "filename", 7, 8)
+            or !populateSortColumns("videometadata", "title", "subtitle", 8, 8))
+            return false;
+        if (!UpdateDBVersionNumber("1347", dbver))
+            return false;
+    }
+
     /*
      * TODO the following settings are no more, clean them up with the next schema change
      * to avoid confusion by stale settings in the database
@@ -3736,7 +3856,9 @@ bool InitializeMythSchema(void)
 "  path text NOT NULL,"
 "  paththumb text NOT NULL,"
 "  title varchar(255) NOT NULL,"
+"  sorttitle varchar(255) NOT NULL,"
 "  subtitle varchar(255) NOT NULL,"
+"  sortsubtitle varchar(255) NOT NULL,"
 "  season smallint(5) NOT NULL DEFAULT '0',"
 "  episode smallint(5) NOT NULL DEFAULT '0',"
 "  description text NOT NULL,"
@@ -3890,7 +4012,9 @@ bool InitializeMythSchema(void)
 "  starttime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  endtime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  title varchar(128) NOT NULL DEFAULT '',"
+"  sorttitle varchar(128) NOT NULL DEFAULT '',"
 "  subtitle varchar(128) NOT NULL DEFAULT '',"
+"  sortsubtitle varchar(128) NOT NULL DEFAULT '',"
 "  description varchar(16000) NOT NULL DEFAULT '',"
 "  season smallint(5) NOT NULL,"
 "  episode smallint(5) NOT NULL,"
@@ -3910,6 +4034,7 @@ bool InitializeMythSchema(void)
 "  PRIMARY KEY (station,starttime,title),"
 "  KEY endtime (endtime),"
 "  KEY title (title),"
+"  KEY sorttitle (sorttitle),"
 "  KEY seriesid (seriesid),"
 "  KEY programid (programid),"
 "  KEY recordid (recordid),"
@@ -3918,6 +4043,7 @@ bool InitializeMythSchema(void)
 "  KEY future (future),"
 "  KEY chanid (chanid,starttime),"
 "  KEY subtitle (subtitle),"
+"  KEY sortsubtitle (sortsubtitle),"
 "  KEY description (description(255))"
 ") ENGINE=MyISAM DEFAULT CHARSET=utf8;",
 "CREATE TABLE people ("
@@ -3962,7 +4088,9 @@ bool InitializeMythSchema(void)
 "  starttime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  endtime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  title varchar(128) NOT NULL DEFAULT '',"
+"  sorttitle varchar(128) NOT NULL DEFAULT '',"
 "  subtitle varchar(128) NOT NULL DEFAULT '',"
+"  sortsubtitle varchar(128) NOT NULL DEFAULT '',"
 "  description varchar(16000) NOT NULL DEFAULT '',"
 "  category varchar(64) NOT NULL DEFAULT '',"
 "  category_type varchar(64) NOT NULL DEFAULT '',"
@@ -3993,6 +4121,7 @@ bool InitializeMythSchema(void)
 "  PRIMARY KEY (chanid,starttime,manualid),"
 "  KEY endtime (endtime),"
 "  KEY title (title),"
+"  KEY sorttitle (sorttitle),"
 "  KEY title_pronounce (title_pronounce),"
 "  KEY seriesid (seriesid),"
 "  KEY id_start_end (chanid,starttime,endtime),"
@@ -4001,6 +4130,7 @@ bool InitializeMythSchema(void)
 "  KEY programid (programid,starttime),"
 "  KEY starttime (starttime),"
 "  KEY subtitle (subtitle),"
+"  KEY sortsubtitle (sortsubtitle),"
 "  KEY description (description(255))"
 ") ENGINE=MyISAM DEFAULT CHARSET=utf8;",
 "CREATE TABLE programgenres ("
@@ -4033,7 +4163,9 @@ bool InitializeMythSchema(void)
 "  endtime time NOT NULL DEFAULT '00:00:00',"
 "  enddate date NOT NULL DEFAULT '0000-00-00',"
 "  title varchar(128) NOT NULL DEFAULT '',"
+"  sorttitle varchar(128) NOT NULL DEFAULT '',"
 "  subtitle varchar(128) NOT NULL DEFAULT '',"
+"  sortsubtitle varchar(128) NOT NULL DEFAULT '',"
 "  description varchar(16000) NOT NULL DEFAULT '',"
 "  season smallint(5) NOT NULL,"
 "  episode smallint(5) NOT NULL,"
@@ -4077,6 +4209,7 @@ bool InitializeMythSchema(void)
 "  PRIMARY KEY (recordid),"
 "  KEY chanid (chanid,starttime),"
 "  KEY title (title),"
+"  KEY sorttitle (sorttitle),"
 "  KEY seriesid (seriesid),"
 "  KEY programid (programid),"
 "  KEY maxepisodes (maxepisodes),"
@@ -4088,7 +4221,9 @@ bool InitializeMythSchema(void)
 "  starttime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  endtime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  title varchar(128) NOT NULL DEFAULT '',"
+"  sorttitle varchar(128) NOT NULL DEFAULT '',"
 "  subtitle varchar(128) NOT NULL DEFAULT '',"
+"  sortsubtitle varchar(128) NOT NULL DEFAULT '',"
 "  description varchar(16000) NOT NULL DEFAULT '',"
 "  season smallint(5) NOT NULL,"
 "  episode smallint(5) NOT NULL,"
@@ -4130,6 +4265,7 @@ bool InitializeMythSchema(void)
 "  KEY seriesid (seriesid),"
 "  KEY programid (programid),"
 "  KEY title (title),"
+"  KEY sorttitle (sorttitle),"
 "  KEY recordid (recordid),"
 "  KEY deletepending (deletepending,lastmodified),"
 "  KEY recgroup (recgroup,endtime)"
@@ -4185,7 +4321,9 @@ bool InitializeMythSchema(void)
 "  starttime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  endtime datetime NOT NULL DEFAULT '0000-00-00 00:00:00',"
 "  title varchar(128) NOT NULL DEFAULT '',"
+"  sorttitle varchar(128) NOT NULL DEFAULT '',"
 "  subtitle varchar(128) NOT NULL DEFAULT '',"
+"  sortsubtitle varchar(128) NOT NULL DEFAULT '',"
 "  description varchar(16000) NOT NULL DEFAULT '',"
 "  category varchar(64) NOT NULL DEFAULT '',"
 "  category_type varchar(64) NOT NULL DEFAULT '',"
@@ -4216,6 +4354,7 @@ bool InitializeMythSchema(void)
 "  PRIMARY KEY (chanid,starttime,manualid),"
 "  KEY endtime (endtime),"
 "  KEY title (title),"
+"  KEY sorttitle (sorttitle),"
 "  KEY title_pronounce (title_pronounce),"
 "  KEY seriesid (seriesid),"
 "  KEY programid (programid),"
@@ -4328,6 +4467,7 @@ bool InitializeMythSchema(void)
 "  itemproperties varchar(255) NOT NULL DEFAULT '',"
 "  filepath varchar(512) NOT NULL DEFAULT '',"
 "  title varchar(255) NOT NULL DEFAULT '',"
+"  sorttitle varchar(255) NOT NULL DEFAULT '',"
 "  filename varchar(512) NOT NULL DEFAULT '',"
 "  coverart varchar(512) NOT NULL DEFAULT '',"
 "  PRIMARY KEY (intid),"
@@ -4379,7 +4519,9 @@ bool InitializeMythSchema(void)
 "CREATE TABLE videometadata ("
 "  intid int(10) unsigned NOT NULL AUTO_INCREMENT,"
 "  title varchar(128) NOT NULL,"
+"  sorttitle varchar(128) NOT NULL,"
 "  subtitle text NOT NULL,"
+"  sortsubtitle text NOT NULL,"
 "  tagline varchar(255) DEFAULT NULL,"
 "  director varchar(128) NOT NULL,"
 "  studio varchar(128) DEFAULT NULL,"
@@ -4397,6 +4539,7 @@ bool InitializeMythSchema(void)
 "  episode smallint(5) unsigned NOT NULL DEFAULT '0',"
 "  showlevel int(10) unsigned NOT NULL,"
 "  filename text NOT NULL,"
+"  sortFilename text NOT NULL,"
 "  `hash` varchar(128) NOT NULL,"
 "  coverfile text NOT NULL,"
 "  childid int(11) NOT NULL DEFAULT '-1',"
@@ -4414,7 +4557,8 @@ bool InitializeMythSchema(void)
 "  contenttype set('MOVIE','TELEVISION','ADULT','MUSICVIDEO','HOMEVIDEO') NOT NULL DEFAULT '',"
 "  PRIMARY KEY (intid),"
 "  KEY director (director),"
-"  KEY title (title)"
+"  KEY title (title),"
+"  KEY titlesort (titlesort)"
 ") ENGINE=MyISAM DEFAULT CHARSET=utf8;",
 "CREATE TABLE videometadatacast ("
 "  idvideo int(10) unsigned NOT NULL,"
diff --git a/mythtv/libs/libmythtv/eithelper.cpp b/mythtv/libs/libmythtv/eithelper.cpp
index 5412770..0c57065 100644
--- a/mythtv/libs/libmythtv/eithelper.cpp
+++ b/mythtv/libs/libmythtv/eithelper.cpp
@@ -684,8 +684,8 @@ void EITHelper::AddEIT(const DVBEventInformationTable *eit)
         QDateTime endtime   = starttime.addSecs(eit->DurationInSeconds(i));
 
         DBEventEIT *event = new DBEventEIT(
-            chanid,
-            title,     subtitle,      description,
+            chanid,    title,         QString(),
+            subtitle,  QString(),  description,
             category,  category_type,
             starttime, endtime,       fix,
             subtitle_type,
@@ -795,8 +795,8 @@ void EITHelper::AddEIT(const PremiereContentInformationTable *cit)
             QDateTime endtime   = starttime.addSecs(cit->DurationInSeconds());
 
             DBEventEIT *event = new DBEventEIT(
-                chanid,
-                title,     subtitle,      description,
+                chanid,    title,         QString(),
+                subtitle,  QString(),  description,
                 category,  category_type,
                 starttime, endtime,       fix,
                 subtitle_type,
diff --git a/mythtv/libs/libmythtv/programdata.cpp b/mythtv/libs/libmythtv/programdata.cpp
index f99b0d8..3472b83 100644
--- a/mythtv/libs/libmythtv/programdata.cpp
+++ b/mythtv/libs/libmythtv/programdata.cpp
@@ -15,6 +15,7 @@ using namespace std;
 #include "mythdb.h"
 #include "mythlogging.h"
 #include "dvbdescriptors.h"
+#include "mythsorthelper.h"
 
 #define LOC      QString("ProgramData: ")
 
@@ -363,7 +364,7 @@ uint DBEvent::GetOverlappingPrograms(
         "       previouslyshown,listingsource, "
         "       stars+0, "
         "       season,         episode,       totalepisodes, "
-        "       inetref "
+        "       inetref,        sorttitle,     sortsubtitle "
         "FROM program "
         "WHERE chanid   = :CHANID AND "
         "      manualid = 0       AND "
@@ -391,7 +392,9 @@ uint DBEvent::GetOverlappingPrograms(
 
         DBEvent prog(
             query.value(0).toString(),
+            query.value(24).toString(),
             query.value(1).toString(),
+            query.value(25).toString(),
             query.value(2).toString(),
             query.value(3).toString(),
             category_type,
@@ -617,7 +620,9 @@ uint DBEvent::UpdateDB(
     MSqlQuery &query, uint chanid, const DBEvent &match)  const
 {
     QString  ltitle     = title;
+    QString  lsortTitle = sortTitle;
     QString  lsubtitle  = subtitle;
+    QString  lsortSubtitle  = sortSubtitle;
     QString  ldesc      = description;
     QString  lcategory  = category;
     uint16_t lairdate   = airdate;
@@ -627,10 +632,16 @@ uint DBEvent::UpdateDB(
     QDate loriginalairdate = originalairdate;
 
     if (match.title.length() >= ltitle.length())
+    {
         ltitle = match.title;
+        lsortTitle = match.sortTitle;
+    }
 
     if (match.subtitle.length() >= lsubtitle.length())
+    {
         lsubtitle = match.subtitle;
+        lsortSubtitle = match.sortSubtitle;
+    }
 
     if (match.description.length() >= ldesc.length())
         ldesc = match.description;
@@ -694,7 +705,8 @@ uint DBEvent::UpdateDB(
 
     query.prepare(
         "UPDATE program "
-        "SET title          = :TITLE,     subtitle      = :SUBTITLE, "
+        "SET title          = :TITLE,     sorttitle     = :SORTTITLE, "
+        "    subtitle       = :SUBTITLE,  sortsubtitle  = :SORTSUBTITLE, "
         "    description    = :DESC, "
         "    category       = :CATEGORY,  category_type = :CATTYPE, "
         "    starttime      = :STARTTIME, endtime       = :ENDTIME, "
@@ -716,7 +728,9 @@ uint DBEvent::UpdateDB(
     query.bindValue(":CHANID",      chanid);
     query.bindValue(":OLDSTART",    match.starttime);
     query.bindValue(":TITLE",       denullify(ltitle));
+    query.bindValue(":SORTTITLE",   denullify(lsortTitle));
     query.bindValue(":SUBTITLE",    denullify(lsubtitle));
+    query.bindValue(":SORTSUBTITLE", denullify(lsortSubtitle));
     query.bindValue(":DESC",        denullify(ldesc));
     query.bindValue(":CATEGORY",    denullify(lcategory));
     query.bindValue(":CATTYPE",     lcattype);
@@ -745,7 +759,7 @@ uint DBEvent::UpdateDB(
 
     if (!query.exec())
     {
-        MythDB::DBError("InsertDB", query);
+        MythDB::DBError("UpdateDB", query);
         return 0;
     }
 
@@ -989,11 +1003,15 @@ bool DBEvent::MoveOutOfTheWayDB(
     return true;
 }
 
+/**
+ *  \brief Insert Callback function when Allow Re-record is pressed in Watch Recordings
+ */
 uint DBEvent::InsertDB(MSqlQuery &query, uint chanid) const
 {
     query.prepare(
         "REPLACE INTO program ("
-        "  chanid,         title,          subtitle,        description, "
+        "  chanid,         title,          sorttitle, "
+        "  subtitle,       sortsubtitle,   description, "
         "  category,       category_type, "
         "  starttime,      endtime, "
         "  closecaptioned, stereo,         hdtv,            subtitled, "
@@ -1005,7 +1023,8 @@ uint DBEvent::InsertDB(MSqlQuery &query, uint chanid) const
         "  season,         episode,        totalepisodes, "
         "  inetref ) "
         "VALUES ("
-        " :CHANID,        :TITLE,         :SUBTITLE,       :DESCRIPTION, "
+        " :CHANID,        :TITLE,         :SORTTITLE, "
+        " :SUBTITLE,      :SORTSUBTITLE,  :DESCRIPTION, "
         " :CATEGORY,      :CATTYPE, "
         " :STARTTIME,     :ENDTIME, "
         " :CC,            :STEREO,        :HDTV,           :HASSUBTITLES, "
@@ -1021,7 +1040,9 @@ uint DBEvent::InsertDB(MSqlQuery &query, uint chanid) const
     QString empty("");
     query.bindValue(":CHANID",      chanid);
     query.bindValue(":TITLE",       denullify(title));
+    query.bindValue(":SORTTITLE",   denullify(sortTitle));
     query.bindValue(":SUBTITLE",    denullify(subtitle));
+    query.bindValue(":SORTSUBTITLE", denullify(sortSubtitle));
     query.bindValue(":DESCRIPTION", denullify(description));
     query.bindValue(":CATEGORY",    denullify(category));
     query.bindValue(":CATTYPE",     cattype);
@@ -1129,6 +1150,18 @@ void ProgInfo::Squeeze(void)
     clumpmax.squeeze();
 }
 
+/**
+ *  \brief Insert a single entry into the "program" database.
+ *
+ *  This function is called from HandlePrograms, which is called from
+ *  mythfilldatabase. It inserts a single entry into the "program"
+ *  database. The data for this structure is taken from the 'this'
+ *  ProgInfo object.
+ *
+ *  \param query  Any mysql query structure. The contents is ignored
+ *                and the structure is repurposed for local queries.
+ *  \param chanid The channel number for this program.
+ */
 uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
 {
     LOG(VB_XMLTV, LOG_INFO,
@@ -1140,7 +1173,8 @@ uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
 
     query.prepare(
         "REPLACE INTO program ("
-        "  chanid,         title,          subtitle,        description, "
+        "  chanid,         title,          sorttitle, "
+        "  subtitle,       sortsubtitle,   description, "
         "  category,       category_type,  "
         "  starttime,      endtime, "
         "  closecaptioned, stereo,         hdtv,            subtitled, "
@@ -1154,7 +1188,8 @@ uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
         "  inetref ) "
 
         "VALUES("
-        " :CHANID,        :TITLE,         :SUBTITLE,       :DESCRIPTION, "
+        " :CHANID,        :TITLE,         :SORTTITLE, "
+        " :SUBTITLE,      :SORTSUBTITLE,  :DESCRIPTION, "
         " :CATEGORY,      :CATTYPE,       "
         " :STARTTIME,     :ENDTIME, "
         " :CC,            :STEREO,        :HDTV,           :HASSUBTITLES, "
@@ -1169,9 +1204,23 @@ uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
 
     QString cattype = myth_category_type_to_string(categoryType);
 
+    // Someday mythfilldatabase might pass in sort strings. Until
+    // then, generate the sort strings here as needed.
+    SortHelper *sh = new SortHelper;
+    QString ltitle = denullify(title);
+    QString lsortTitle = denullify(sortTitle);
+    QString lsubtitle = denullify(subtitle);
+    QString lsortSubtitle = denullify(sortSubtitle);
+    if (lsortTitle.isEmpty())
+        lsortTitle = sh->doTitle(ltitle);
+    if (lsortSubtitle.isEmpty())
+        lsortSubtitle = sh->doTitle(lsubtitle);
+
     query.bindValue(":CHANID",      chanid);
-    query.bindValue(":TITLE",       denullify(title));
-    query.bindValue(":SUBTITLE",    denullify(subtitle));
+    query.bindValue(":TITLE",       ltitle);
+    query.bindValue(":SORTTITLE",   lsortTitle);
+    query.bindValue(":SUBTITLE",    lsubtitle);
+    query.bindValue(":SORTSUBTITLE", lsortSubtitle);
     query.bindValue(":DESCRIPTION", denullify(description));
     query.bindValue(":CATEGORY",    denullify(category));
     query.bindValue(":CATTYPE",     cattype);
@@ -1374,6 +1423,14 @@ void ProgramData::FixProgramList(QList<ProgInfo*> &fixlist)
     }
 }
 
+/**
+ *  \brief Called from mythfilldatabase to bulk insert data into the
+ *  program database.
+ *
+ *  \param sourceid The data source identifier
+ *  \param proglist A map of all program information keyed by channel
+ *                  identifier
+ */
 void ProgramData::HandlePrograms(
     uint sourceid, QMap<QString, QList<ProgInfo> > &proglist)
 {
@@ -1432,6 +1489,17 @@ void ProgramData::HandlePrograms(
                 .arg(updated) .arg(unchanged));
 }
 
+/**
+ *  \brief Called from HandlePrograms to bulk insert data into the
+ *  program database.
+ *
+ *  \param query A mysql query related to all channel ids for
+ *               a given source
+ *  \param chanid The specific channel id to process
+ *  \param sortlist A time sorted list of ProgInfo structures
+ *  \param unchanged Set to the number of unchanged programs
+ *  \param updated Set to the number of updated programs
+ */
 void ProgramData::HandlePrograms(MSqlQuery             &query,
                                  uint                   chanid,
                                  const QList<ProgInfo*> &sortlist,
diff --git a/mythtv/libs/libmythtv/programdata.h b/mythtv/libs/libmythtv/programdata.h
index 58db39b..9ee40a4 100644
--- a/mythtv/libs/libmythtv/programdata.h
+++ b/mythtv/libs/libmythtv/programdata.h
@@ -20,6 +20,7 @@ using namespace std;
 #include "listingsources.h"
 #include "programinfo.h"
 #include "eithelper.h" /* for FixupValue */
+#include "mythsorthelper.h"
 
 class MSqlQuery;
 
@@ -75,7 +76,9 @@ class MTV_PUBLIC DBEvent
   public:
     DBEvent(uint _listingsource) :
         title(),
+        sortTitle(),
         subtitle(),
+        sortSubtitle(),
         description(),
         category(),
         /*starttime, endtime,*/
@@ -98,7 +101,8 @@ class MTV_PUBLIC DBEvent
         episode(0),
         totalepisodes(0) {}
 
-    DBEvent(const QString   &_title,     const QString   &_subtitle,
+    DBEvent(const QString   &_title,     const QString   &_sortTitle,
+            const QString   &_subtitle,  const QString   &_sortSubtitle,
             const QString   &_desc,
             const QString   &_category,  ProgramInfo::CategoryType _category_type,
             const QDateTime &_start,     const QDateTime &_end,
@@ -110,7 +114,8 @@ class MTV_PUBLIC DBEvent
             uint32_t         _listingsource,
             uint _season,                uint _episode,
             uint _totalepisodes ) :
-        title(_title),           subtitle(_subtitle),
+        title(_title),           sortTitle(_sortTitle),
+        subtitle(_subtitle),     sortSubtitle(_sortSubtitle),
         description(_desc),
         category(_category),
         starttime(_start),       endtime(_end),
@@ -131,6 +136,11 @@ class MTV_PUBLIC DBEvent
         episode(_episode),
         totalepisodes(_totalepisodes)
     {
+        SortHelper *sh = new SortHelper;
+        if (sortTitle.isEmpty())
+            sortTitle = sh->doTitle(_title);
+        if (sortSubtitle.isEmpty())
+            sortSubtitle = sh->doTitle(_sortTitle);
     }
 
     virtual ~DBEvent() { delete credits; }
@@ -161,7 +171,9 @@ class MTV_PUBLIC DBEvent
 
   public:
     QString       title;
+    QString       sortTitle;
     QString       subtitle;
+    QString       sortSubtitle;
     QString       description;
     QString       category;
     QDateTime     starttime;
@@ -193,7 +205,8 @@ class MTV_PUBLIC DBEventEIT : public DBEvent
 {
   public:
     DBEventEIT(uint             _chanid,
-               const QString   &_title,     const QString   &_subtitle,
+               const QString   &_title,     const QString   &_sortTitle,
+               const QString   &_subtitle,  const QString   &_sortSubtitle,
                const QString   &_desc,
                const QString   &_category,  ProgramInfo::CategoryType _category_type,
                const QDateTime &_start,     const QDateTime &_end,
@@ -205,7 +218,8 @@ class MTV_PUBLIC DBEventEIT : public DBEvent
                const QString   &_seriesId,  const QString   &_programId,
                uint _season,                uint _episode,
                uint _totalepisodes ) :
-        DBEvent(_title, _subtitle, _desc, _category, _category_type,
+        DBEvent(_title, _sortTitle, _subtitle, _sortSubtitle,
+                _desc, _category, _category_type,
                 _start, _end, _subtitleType, _audioProps, _videoProps,
                 _stars, _seriesId, _programId, kListingSourceEIT,
                 _season, _episode, _totalepisodes),
@@ -220,7 +234,8 @@ class MTV_PUBLIC DBEventEIT : public DBEvent
                unsigned char    _subtitleType,
                unsigned char    _audioProps,
                unsigned char    _videoProps) :
-        DBEvent(_title, QString(), _desc, QString(), ProgramInfo::kCategoryNone,
+        DBEvent(_title, QString(), QString(), QString(),
+                _desc, QString(), ProgramInfo::kCategoryNone,
                 _start, _end, _subtitleType, _audioProps, _videoProps,
                 0.0, QString(), QString(), kListingSourceEIT, 0, 0, 0), // Season, Episode and Total Episodes are not set with this constructor!
         chanid(_chanid), fixup(_fixup)
diff --git a/mythtv/libs/libmythtv/recordinginfo.cpp b/mythtv/libs/libmythtv/recordinginfo.cpp
index 3a87da0..da1cec0 100644
--- a/mythtv/libs/libmythtv/recordinginfo.cpp
+++ b/mythtv/libs/libmythtv/recordinginfo.cpp
@@ -18,6 +18,7 @@
 #include "jobqueue.h"
 #include "mythdb.h"
 #include "mythlogging.h"
+#include "mythsorthelper.h"
 
 #define LOC      QString("RecordingInfo(%1): ").arg(GetBasename())
 
@@ -32,7 +33,9 @@ static const uint kUnknownProgramLength = 30;
 
 RecordingInfo::RecordingInfo(
     const QString &_title,
+    const QString &_sortTitle,
     const QString &_subtitle,
+    const QString &_sortSubtitle,
     const QString &_description,
     uint _season,
     uint _episode,
@@ -95,7 +98,8 @@ RecordingInfo::RecordingInfo(
     uint _mplexid,
     uint _sgroupid) :
     ProgramInfo(
-        _title, _subtitle, _description, _season, _episode, _totalepisodes,
+        _title, _sortTitle, _subtitle, _sortSubtitle,
+        _description, _season, _episode, _totalepisodes,
         _category, _chanid, _chanstr, _chansign, _channame,
         QString(), _recgroup, _playgroup,
         _startts, _endts, _recstartts, _recendts,
@@ -162,7 +166,9 @@ RecordingInfo::RecordingInfo(
 
 RecordingInfo::RecordingInfo(
     const QString &_title,
+    const QString &_sortTitle,
     const QString &_subtitle,
+    const QString &_sortSubtitle,
     const QString &_description,
     uint _season,
     uint _episode,
@@ -198,7 +204,8 @@ RecordingInfo::RecordingInfo(
 
     bool _commfree) :
     ProgramInfo(
-        _title, _subtitle, _description, _season, _episode, 0,
+        _title, _sortTitle, _subtitle, _sortSubtitle,
+        _description, _season, _episode, 0,
         _category, _chanid, _chanstr, _chansign, _channame,
         QString(), _recgroup, _playgroup,
         _startts, _endts, _recstartts, _recendts,
@@ -755,14 +762,19 @@ void RecordingInfo::ApplyRecordRecTitleChange(const QString &newTitle,
         const QString &newSubtitle, const QString &newDescription)
 {
     MSqlQuery query(MSqlQuery::InitCon());
-    QString sql = "UPDATE recorded SET title = :TITLE, subtitle = :SUBTITLE ";
+    QString sql = "UPDATE recorded SET "
+        "    title = :TITLE, sorttitle = :SORTTITLE, "
+        "    subtitle = :SUBTITLE, sortsubtitle = :SORTSUBTITLE ";
     if (!newDescription.isNull())
         sql += ", description = :DESCRIPTION ";
     sql += " WHERE chanid = :CHANID AND starttime = :START ;";
 
+    SortHelper *sh = new SortHelper;
     query.prepare(sql);
     query.bindValue(":TITLE", newTitle);
+    query.bindValue(":SORTTITLE", sh->doTitle(newTitle));
     query.bindValue(":SUBTITLE", null_to_empty(newSubtitle));
+    query.bindValue(":SORTSUBTITLE", sh->doTitle(null_to_empty(newSubtitle)));
     if (!newDescription.isNull())
         query.bindValue(":DESCRIPTION", newDescription);
     query.bindValue(":CHANID", chanid);
@@ -1045,8 +1057,9 @@ bool RecordingInfo::InsertProgram(RecordingInfo *pg,
 
     query.prepare(
         "INSERT INTO recorded "
-        "   (chanid,    starttime,   endtime,         title,            "
-        "    subtitle,  description, season,          episode,          "
+        "   (chanid,    starttime,   endtime,                           "
+        "    title,     sorttitle,   subtitle,        sortsubtitle,     "
+        "               description, season,          episode,          "
         "    hostname,  category,    recgroup,        autoexpire,       "
         "    recordid,  seriesid,    programid,       inetref,          "
         "    stars,     previouslyshown,              originalairdate,  "
@@ -1054,8 +1067,9 @@ bool RecordingInfo::InsertProgram(RecordingInfo *pg,
         "    basename,  progstart,   progend,         profile,          "
         "    duplicate, storagegroup, inputname,      recgroupid) "
         "VALUES"
-        "  (:CHANID,   :STARTS,     :ENDS,           :TITLE,            "
-        "   :SUBTITLE, :DESC,       :SEASON,         :EPISODE,          "
+        "  (:CHANID,   :STARTS,     :ENDS,                              "
+        "   :TITLE,    :SORTTITLE,  :SUBTITLE,       :SORTSUBTITLE,     "
+        "              :DESC,       :SEASON,         :EPISODE,          "
         "   :HOSTNAME, :CATEGORY,   :RECGROUP,       :AUTOEXP,          "
         "   :RECORDID, :SERIESID,   :PROGRAMID,      :INETREF,          "
         "   :STARS,    :REPEAT,                      :ORIGAIRDATE,      "
@@ -1078,7 +1092,9 @@ bool RecordingInfo::InsertProgram(RecordingInfo *pg,
     query.bindValue(":STARTS",      pg->recstartts);
     query.bindValue(":ENDS",        pg->recendts);
     query.bindValue(":TITLE",       pg->title);
+    query.bindValue(":SORTTITLE",   pg->sortTitle);
     query.bindValue(":SUBTITLE",    null_to_empty(pg->subtitle));
+    query.bindValue(":SORTSUBTITLE", null_to_empty(pg->sortSubtitle));
     query.bindValue(":DESC",        null_to_empty(pg->description));
     query.bindValue(":SEASON",      pg->season);
     query.bindValue(":EPISODE",     pg->episode);
@@ -1252,16 +1268,19 @@ void RecordingInfo::AddHistory(bool resched, bool forcedup, bool future)
     result.prepare("REPLACE INTO oldrecorded (chanid,starttime,"
                    "endtime,title,subtitle,description,season,episode,"
                    "category,seriesid,programid,inetref,findid,recordid,"
-                   "station,rectype,recstatus,duplicate,reactivate,future) "
+                   "station,rectype,recstatus,duplicate,reactivate,future,"
+                   "sorttitle,sortsubtitle) "
                    "VALUES(:CHANID,:START,:END,:TITLE,:SUBTITLE,:DESC,:SEASON,"
                    ":EPISODE,:CATEGORY,:SERIESID,:PROGRAMID,:INETREF,"
                    ":FINDID,:RECORDID,:STATION,:RECTYPE,:RECSTATUS,:DUPLICATE,"
-                   ":REACTIVATE,:FUTURE);");
+                   ":REACTIVATE,:FUTURE,:SORTTITLE,:SORTSUBTITLE);");
     result.bindValue(":CHANID", chanid);
     result.bindValue(":START", startts);
     result.bindValue(":END", endts);
     result.bindValue(":TITLE", title);
+    result.bindValue(":SORTTITLE", sortTitle);
     result.bindValue(":SUBTITLE", null_to_empty(subtitle));
+    result.bindValue(":SORTSUBTITLE", null_to_empty(sortSubtitle));
     result.bindValue(":DESC", null_to_empty(description));
     result.bindValue(":SEASON", season);
     result.bindValue(":EPISODE", episode);
diff --git a/mythtv/libs/libmythtv/recordinginfo.h b/mythtv/libs/libmythtv/recordinginfo.h
index 9502001..c2b4252 100644
--- a/mythtv/libs/libmythtv/recordinginfo.h
+++ b/mythtv/libs/libmythtv/recordinginfo.h
@@ -110,7 +110,9 @@ class MTV_PUBLIC RecordingInfo : public ProgramInfo
     /// used in scheduler.cpp @ ~ 3296
     RecordingInfo(
         const QString &title,
+        const QString &sortTitle,
         const QString &subtitle,
+        const QString &sortSubtitle,
         const QString &description,
         uint season,
         uint episode,
@@ -177,7 +179,9 @@ class MTV_PUBLIC RecordingInfo : public ProgramInfo
     /// user in scheduler.cpp  @ ~ 3566 & ~ 3643
     RecordingInfo(
         const QString &title,
+        const QString &sortTitle,
         const QString &subtitle,
+        const QString &sortSubtitle,
         const QString &description,
         uint season,
         uint episode,
diff --git a/mythtv/libs/libmythtv/recordingrule.cpp b/mythtv/libs/libmythtv/recordingrule.cpp
index a206500..d9508b1 100644
--- a/mythtv/libs/libmythtv/recordingrule.cpp
+++ b/mythtv/libs/libmythtv/recordingrule.cpp
@@ -12,6 +12,7 @@
 #include "playgroup.h" // For GetInitialName()
 #include "recordingprofile.h" // For constants
 #include "mythdate.h"
+#include "mythsorthelper.h"
 
 static inline QString null_to_empty(const QString &str)
 {
@@ -93,7 +94,8 @@ bool RecordingRule::Load(bool asTemplate)
     "autometadata, parentid, title, subtitle, description, season, episode, "
     "category, starttime, startdate, endtime, enddate, seriesid, programid, "
     "inetref, chanid, station, findday, findtime, findid, "
-    "next_record, last_record, last_delete, avg_delay, filter, recgroupid "
+    "next_record, last_record, last_delete, avg_delay, filter, recgroupid, "
+    "sorttitle,sortsubtitle "
     "FROM record WHERE recordid = :RECORDID ;");
 
     query.bindValue(":RECORDID", m_recordID);
@@ -150,7 +152,9 @@ bool RecordingRule::Load(bool asTemplate)
     if (!asTemplate)
     {
         m_title = query.value(25).toString();
+        m_sortTitle = query.value(49).toString();
         m_subtitle = query.value(26).toString();
+        m_sortSubtitle = query.value(50).toString();
         m_description = query.value(27).toString();
         m_season = query.value(28).toUInt();
         m_episode = query.value(29).toUInt();
@@ -258,9 +262,17 @@ bool RecordingRule::LoadBySearch(RecSearchType lsearch, QString textname,
         m_searchType = lsearch;
         searchType = SearchTypeToString(m_searchType);
 
+        SortHelper *sh = new SortHelper;
         QString ltitle = QString("%1 (%2)").arg(textname).arg(searchType);
         m_title = ltitle;
+        m_sortTitle = sh->doTitle(ltitle);
+        // The caller from customedit.cpp calls here with the subtitle
+        // in the 'from' argument. The other callers from
+        // proglist_helpers.cpp call with an SQL code fragment in the
+        // 'from' field. Impossible to know what's the right way to
+        // create the sortSubtitle field.
         m_subtitle = from;
+        m_sortSubtitle = from;
         m_description = forwhat;
 
         if (pginfo)
@@ -322,11 +334,13 @@ bool RecordingRule::MakeTemplate(QString category)
     {
         category = "Default";
         m_title = tr("Default (Template)");
+        m_sortTitle = m_title;
     }
     else
     {
         //: %1 is the category
         m_title = tr("%1 (Template)").arg(category);
+        m_sortTitle = m_title;
     }
 
     LoadTemplate(category);
@@ -351,8 +365,12 @@ bool RecordingRule::ModifyPowerSearchByID(int rid, QString textname,
 
     QString ltitle = QString("%1 (%2)").arg(textname)
                                        .arg(tr("Power Search"));
+    SortHelper *sh = new SortHelper;
     m_title = ltitle;
+    m_sortTitle = sh->doTitle(ltitle);
     m_subtitle = from;
+    // This doesn't appear to be a subtitle, so copy it verbatim.
+    m_sortSubtitle = from;
     m_description = forwhat;
 
     m_loaded = true;
@@ -401,8 +419,10 @@ bool RecordingRule::Save(bool sendSig)
                     "autouserjob3 = :AUTOUSERJOB3, "
                     "autouserjob4 = :AUTOUSERJOB4, "
                     "autometadata = :AUTOMETADATA, "
-                    "parentid = :PARENTID, title = :TITLE, "
-                    "subtitle = :SUBTITLE, season = :SEASON, episode = :EPISODE, "
+                    "parentid = :PARENTID, "
+                    "title = :TITLE, sorttitle = :SORTTITLE, "
+                    "subtitle = :SUBTITLE, sortsubtitle = :SORTSUBTITLE, "
+                    "season = :SEASON, episode = :EPISODE, "
                     "description = :DESCRIPTION, category = :CATEGORY, "
                     "starttime = :STARTTIME, startdate = :STARTDATE, "
                     "endtime = :ENDTIME, enddate = :ENDDATE, seriesid = :SERIESID, "
@@ -450,7 +470,9 @@ bool RecordingRule::Save(bool sendSig)
     query.bindValue(":AUTOMETADATA", m_autoMetadataLookup);
     query.bindValue(":PARENTID", m_parentRecID);
     query.bindValue(":TITLE", m_title);
+    query.bindValue(":SORTTITLE", m_sortTitle);
     query.bindValue(":SUBTITLE", null_to_empty(m_subtitle));
+    query.bindValue(":SORTSUBTITLE", null_to_empty(m_sortSubtitle));
     query.bindValue(":DESCRIPTION", null_to_empty(m_description));
     query.bindValue(":SEASON", m_season);
     query.bindValue(":EPISODE", m_episode);
@@ -542,10 +564,17 @@ bool RecordingRule::Delete(bool sendSig)
 void RecordingRule::ToMap(InfoMap &infoMap) const
 {
     if (m_title == "Default (Template)")
+    {
         infoMap["title"] = tr("Default (Template)");
+        infoMap["sorttitle"] = tr("Default (Template)");
+    }
     else
+    {
         infoMap["title"] = m_title;
+        infoMap["sorttitle"] = m_sortTitle;
+    }
     infoMap["subtitle"] = m_subtitle;
+    infoMap["sortsubtitle"] = m_sortSubtitle;
     infoMap["description"] = m_description;
     infoMap["season"] = QString::number(m_season);
     infoMap["episode"] = QString::number(m_episode);
@@ -736,7 +765,9 @@ void RecordingRule::AssignProgramInfo()
         return;
 
     m_title       = m_progInfo->GetTitle();
+    m_sortTitle   = m_progInfo->GetSortTitle();
     m_subtitle    = m_progInfo->GetSubtitle();
+    m_sortSubtitle= m_progInfo->GetSortSubtitle();
     m_description = m_progInfo->GetDescription();
     m_channelid   = m_progInfo->GetChanID();
     m_station     = m_progInfo->GetChannelSchedulingID();
diff --git a/mythtv/libs/libmythtv/recordingrule.h b/mythtv/libs/libmythtv/recordingrule.h
index 0ef9c5a..4e10ac0 100644
--- a/mythtv/libs/libmythtv/recordingrule.h
+++ b/mythtv/libs/libmythtv/recordingrule.h
@@ -73,7 +73,9 @@ class MTV_PUBLIC RecordingRule
 
     // Recording metadata
     QString m_title;
+    QString m_sortTitle;
     QString m_subtitle;
+    QString m_sortSubtitle;
     QString m_description;
     uint m_season;
     uint m_episode;
diff --git a/mythtv/libs/libmythtv/test/test_eitfixups/test_eitfixups.cpp b/mythtv/libs/libmythtv/test/test_eitfixups/test_eitfixups.cpp
index 946a5f7..e9f6f76 100644
--- a/mythtv/libs/libmythtv/test/test_eitfixups/test_eitfixups.cpp
+++ b/mythtv/libs/libmythtv/test/test_eitfixups/test_eitfixups.cpp
@@ -419,7 +419,9 @@ DBEventEIT *TestEITFixups::SimpleDBEventEIT (FixupValue fixup, QString title, QS
 {
     DBEventEIT *event = new DBEventEIT (1, // channel id
                                        title, // title
+                                       QString(), //sortTitle
                                        subtitle, // subtitle
+                                       QString(), //sortSubtitle
                                        description, // description
                                        "", // category
                                        ProgramInfo::kCategoryNone, // category_type
diff --git a/mythtv/programs/mythbackend/main_helpers.cpp b/mythtv/programs/mythbackend/main_helpers.cpp
index e4a4089..bb698af 100644
--- a/mythtv/programs/mythbackend/main_helpers.cpp
+++ b/mythtv/programs/mythbackend/main_helpers.cpp
@@ -568,7 +568,10 @@ int run_backend(MythBackendCommandLineParser &cmdline)
 
     if (!UpgradeTVDatabaseSchema(ismaster, ismaster))
     {
-        LOG(VB_GENERAL, LOG_ERR, "Couldn't upgrade database to new schema");
+        if (ismaster)
+            LOG(VB_GENERAL, LOG_ERR, "Couldn't upgrade database to new schema on master backend.");
+        else
+            LOG(VB_GENERAL, LOG_ERR, "Couldn't upgrade database to new schema on slave backend.");
         return GENERIC_EXIT_DB_OUTOFDATE;
     }
 
diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
index 49b2912..e39eb73 100644
--- a/mythtv/programs/mythbackend/scheduler.cpp
+++ b/mythtv/programs/mythbackend/scheduler.cpp
@@ -3666,7 +3666,7 @@ void Scheduler::UpdateManuals(uint recordid)
     MSqlQuery query(dbConn);
 
     query.prepare(QString("SELECT type,title,station,startdate,starttime, "
-                  " enddate,endtime "
+                  " enddate,endtime,sorttitle "
                   "FROM %1 WHERE recordid = :RECORDID").arg(recordTable));
     query.bindValue(":RECORDID", recordid);
     if (!query.exec() || query.size() != 1)
@@ -3680,6 +3680,7 @@ void Scheduler::UpdateManuals(uint recordid)
 
     RecordingType rectype = RecordingType(query.value(0).toInt());
     QString title = query.value(1).toString();
+    QString sortTitle = query.value(7).toString();
     QString station = query.value(2).toString() ;
     QDateTime startdt = QDateTime(query.value(3).toDate(),
                                   query.value(4).toTime(), Qt::UTC);
@@ -3749,13 +3750,14 @@ void Scheduler::UpdateManuals(uint recordid)
                 continue;
 
             query.prepare("REPLACE INTO program (chanid, starttime, endtime,"
-                          " title, subtitle, manualid, generic) "
+                          " title, sorttitle, subtitle, manualid, generic) "
                           "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
-                          " :SUBTITLE, :RECORDID, 1)");
+                          " :SORTTITLE, :SUBTITLE, :RECORDID, 1)");
             query.bindValue(":CHANID", chanidlist[i]);
             query.bindValue(":STARTTIME", startdt);
             query.bindValue(":ENDTIME", startdt.addSecs(duration));
             query.bindValue(":TITLE", title);
+            query.bindValue(":SORTTITLE", sortTitle);
             query.bindValue(":SUBTITLE", startdt.toLocalTime());
             query.bindValue(":RECORDID", recordid);
             if (!query.exec())
@@ -4439,7 +4441,7 @@ void Scheduler::AddNewRecords(void)
         "    capturecard.hostname, recordmatch.oldrecstatus, NULL, "//43-45
         "    oldrecstatus.future, capturecard.schedorder, " //46-47
         "    p.syndicatedepisodenumber, p.partnumber, p.parttotal, " //48-50
-        "    c.mplexid, ") +                                         //51
+        "    c.mplexid, p.sorttitle, p.sortsubtitle, ") +            //51-53
         pwrpri + QString(
         "FROM recordmatch "
         "INNER JOIN RECTABLE ON (recordmatch.recordid = RECTABLE.recordid) "
@@ -4506,7 +4508,9 @@ void Scheduler::AddNewRecords(void)
 
         RecordingInfo *p = new RecordingInfo(
             title,
+            result.value(52).toString(),//sorttitle
             result.value(5).toString(),//subtitle
+            result.value(53).toString(),//sortsubtitle
             result.value(6).toString(),//description
             0, // season
             0, // episode
@@ -4709,7 +4713,8 @@ void Scheduler::AddNotListed(void) {
         "       RECTABLE.dupin,       RECTABLE.dupmethod,   " // 22,23
         "       RECTABLE.findid,                            " // 24
         "       RECTABLE.startoffset, RECTABLE.endoffset,   " // 25,26
-        "       channel.commmethod                          " // 27
+        "       channel.commmethod,   RECTABLE.sorttitle,   " // 27,28
+        "       RECTABLE.sortsubtitle                       " // 29
         "FROM RECTABLE "
         "INNER JOIN channel ON (channel.chanid = RECTABLE.chanid) "
         "LEFT JOIN recordmatch on RECTABLE.recordid = recordmatch.recordid "
@@ -4768,7 +4773,9 @@ void Scheduler::AddNotListed(void) {
 
         RecordingInfo *p = new RecordingInfo(
             result.value(0).toString(), // Title
+            result.value(28).toString(), // Title Sort
             (sor) ? result.value(1).toString() : QString(), // Subtitle
+            (sor) ? result.value(29).toString() : QString(), // Subtitle Sort
             (sor) ? result.value(2).toString() : QString(), // Description
             result.value(3).toUInt(), // Season
             result.value(4).toUInt(), // Episode
@@ -4865,7 +4872,8 @@ void Scheduler::GetAllScheduled(RecList &proglist, SchedSortColumn sortBy,
         "       record.recordid,    record.type,        " // 20,21
         "       record.dupin,       record.dupmethod,   " // 22,23
         "       record.findid,                          " // 24
-        "       channel.commmethod                      " // 25
+        "       channel.commmethod,                     " // 25
+        "       record.sorttitle,   record.sortsubtitle " // 26,27
         "FROM record "
         "LEFT JOIN channel ON channel.callsign = record.station "
         "GROUP BY recordid "
@@ -4897,7 +4905,8 @@ void Scheduler::GetAllScheduled(RecList &proglist, SchedSortColumn sortBy,
             endts = startts;
 
         proglist.push_back(new RecordingInfo(
-            result.value(0).toString(),  result.value(1).toString(),
+            result.value(0).toString(),  result.value(26).toString(),
+            result.value(1).toString(),  result.value(27).toString(),
             result.value(2).toString(),  result.value(3).toUInt(),
             result.value(4).toUInt(),    result.value(5).toString(),
 
diff --git a/mythtv/programs/mythbackend/services/video.cpp b/mythtv/programs/mythbackend/services/video.cpp
index 7f977c2..00c4305 100644
--- a/mythtv/programs/mythbackend/services/video.cpp
+++ b/mythtv/programs/mythbackend/services/video.cpp
@@ -347,21 +347,19 @@ bool Video::AddVideo( const QString &sFileName,
         hash = "";
     }
 
-    VideoMetadata newFile(sFileName, hash,
+    VideoMetadata newFile(sFileName, QString(), hash,
                           VIDEO_TRAILER_DEFAULT,
                           VIDEO_COVERFILE_DEFAULT,
                           VIDEO_SCREENSHOT_DEFAULT,
                           VIDEO_BANNER_DEFAULT,
                           VIDEO_FANART_DEFAULT,
-                          VideoMetadata::FilenameToMeta(sFileName, 1),
-                          VideoMetadata::FilenameToMeta(sFileName, 4),
+                          QString(), QString(), QString(), QString(),
                           QString(), VIDEO_YEAR_DEFAULT,
                           QDate::fromString("0000-00-00","YYYY-MM-DD"),
                           VIDEO_INETREF_DEFAULT, 0, QString(),
                           VIDEO_DIRECTOR_DEFAULT, QString(), VIDEO_PLOT_DEFAULT,
                           0.0, VIDEO_RATING_DEFAULT, 0, 0,
-                          VideoMetadata::FilenameToMeta(sFileName, 2).toInt(),
-                          VideoMetadata::FilenameToMeta(sFileName, 3).toInt(),
+                          0, 0,
                           MythDate::current().date(), 0,
                           ParentalLevel::plLowest);
 
diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp b/mythtv/programs/mythfrontend/globalsettings.cpp
index 4e53990..78b7852 100644
--- a/mythtv/programs/mythfrontend/globalsettings.cpp
+++ b/mythtv/programs/mythfrontend/globalsettings.cpp
@@ -2756,6 +2756,41 @@ ChannelGroupSettings::ChannelGroupSettings() :
     addTarget("1", new VerticalConfigurationGroup(true,false));
 };
 
+static HostCheckBox *SortIgnoreCase()
+{
+    HostCheckBox *gc = new HostCheckBox("SortIgnoreCase");
+    gc->setLabel(GeneralSettings::tr("Ignore case when sorting"));
+    gc->setValue(true);
+    gc->setHelpText(GeneralSettings::tr("If enabled, all sorting will be case-insensitive."));
+    return gc;
+}
+
+static HostCheckBox *SortIgnorePrefixes()
+{
+    HostCheckBox *gc = new HostCheckBox("SortIgnorePrefixes");
+
+    gc->setLabel(GeneralSettings::tr("Ignore prefixes when sorting"));
+    gc->setValue(true);
+    gc->setHelpText(GeneralSettings::tr("If enabled, video listings will "
+                                        "ignore common prefixes (The, A, An) when "
+                                        "sorting show names."));
+    return gc;
+}
+
+static HostLineEdit *SortIgnorePrefixExceptions()
+{
+    HostLineEdit *gc = new HostLineEdit("SortIgnorePrefixExceptions");
+
+    gc->setLabel(MainGeneralSettings::tr("Names exempt from prefix removal"));
+    gc->setValue("");
+    gc->setHelpText(MainGeneralSettings::tr("When stripping common prefixes (The, A, An) "
+					    "from filenames, this list of names "
+					    "will be exempt from that process. "
+					    "Enter multiple names separated by ';'."));
+    return gc;
+}
+
+
 // General RecPriorities settings
 
 static GlobalComboBox *GRSchedOpenEnd()
@@ -3863,6 +3898,9 @@ MainGeneralSettings::MainGeneralSettings()
     general->setLabel(tr("General"));
     general->addChild(UseVirtualKeyboard());
     general->addChild(ScreenShotPath());
+    general->addChild(SortIgnoreCase());
+    general->addChild(SortIgnorePrefixes());
+    general->addChild(SortIgnorePrefixExceptions());
     addChild(general);
 
     VerticalConfigurationGroup *media =
diff --git a/mythtv/programs/mythfrontend/main.cpp b/mythtv/programs/mythfrontend/main.cpp
index f4ed3f2..ef12639 100644
--- a/mythtv/programs/mythfrontend/main.cpp
+++ b/mythtv/programs/mythfrontend/main.cpp
@@ -1168,9 +1168,9 @@ static int internal_play_media(const QString &mrl, const QString &plot,
     }
 
     ProgramInfo *pginfo = new ProgramInfo(
-        mrl, plot, title, subtitle, director, season, episode,
-        inetref, lenMins, (year.toUInt()) ? year.toUInt() : 1900,
-        id);
+        mrl, plot, title, QString(), subtitle, QString(),
+        director, season, episode, inetref, lenMins,
+        (year.toUInt()) ? year.toUInt() : 1900, id);
 
     pginfo->SetProgramInfoType(pginfo->DiscoverProgramInfoType());
 
diff --git a/mythtv/programs/mythfrontend/playbackbox.cpp b/mythtv/programs/mythfrontend/playbackbox.cpp
index fea8033..26427a4 100644
--- a/mythtv/programs/mythfrontend/playbackbox.cpp
+++ b/mythtv/programs/mythfrontend/playbackbox.cpp
@@ -222,17 +222,15 @@ static PlaybackBox::ViewMask m_viewMaskToggle(PlaybackBox::ViewMask mask,
 
 static QString construct_sort_title(
     QString title, PlaybackBox::ViewMask viewmask,
-    PlaybackBox::ViewTitleSort titleSort, int recpriority,
-    const QRegExp &prefixes)
+    PlaybackBox::ViewTitleSort sortType, int recpriority)
 {
     if (title.isEmpty())
         return title;
 
     QString sTitle = title;
 
-    sTitle.remove(prefixes);
     if (viewmask == PlaybackBox::VIEW_TITLES &&
-            titleSort == PlaybackBox::TitleSortRecPriority)
+            sortType == PlaybackBox::TitleSortRecPriority)
     {
         // Also incorporate recpriority (reverse numeric sort). In
         // case different episodes of a recording schedule somehow
@@ -393,7 +391,6 @@ void * PlaybackBox::RunPlaybackBox(void * player, bool showTV)
 PlaybackBox::PlaybackBox(MythScreenStack *parent, QString name,
                             TV *player, bool showTV)
     : ScheduleCommon(parent, name),
-      m_prefixes(QObject::tr("^(The |A |An )")),
       m_titleChaff(" \\(.*\\)$"),
       // UI variables
       m_recgroupList(NULL),
@@ -1757,8 +1754,8 @@ bool PlaybackBox::UpdateUILists(void)
                     (pRecgroup != "LiveTV" || m_recGroup == "LiveTV"))
             {
                 sTitle = construct_sort_title(
-                            p->GetTitle(), m_viewMask, titleSort,
-                            p->GetRecordingPriority(), m_prefixes);
+                            p->GetSortTitle(), m_viewMask, titleSort,
+                            p->GetRecordingPriority());
                 sTitle = sTitle.toLower();
 
                 if (!sortedList.contains(sTitle))
diff --git a/mythtv/programs/mythfrontend/playbackbox.h b/mythtv/programs/mythfrontend/playbackbox.h
index 6b7e144..07ee0e4 100644
--- a/mythtv/programs/mythfrontend/playbackbox.h
+++ b/mythtv/programs/mythfrontend/playbackbox.h
@@ -338,7 +338,6 @@ class PlaybackBox : public ScheduleCommon
     QString extract_commflag_state(const ProgramInfo &pginfo);
 
 
-    QRegExp m_prefixes;   ///< prefixes to be ignored when sorting
     QRegExp m_titleChaff; ///< stuff to remove for search rules
 
     MythUIButtonList *m_recgroupList;
diff --git a/mythtv/programs/mythfrontend/proglist.cpp b/mythtv/programs/mythfrontend/proglist.cpp
index ccdb95a..cb988c5 100644
--- a/mythtv/programs/mythfrontend/proglist.cpp
+++ b/mythtv/programs/mythfrontend/proglist.cpp
@@ -1057,8 +1057,10 @@ class plTitleSort : public plCompare
   public:
     bool operator()(const ProgramInfo *a, const ProgramInfo *b)
     {
-        if (a->sortTitle != b->sortTitle)
-            return (a->sortTitle < b->sortTitle);
+        if (a->GetSortTitle() != b->GetSortTitle())
+            return (a->GetSortTitle() < b->GetSortTitle());
+        if (a->GetSortSubtitle() != b->GetSortSubtitle())
+            return (a->GetSortSubtitle() < b->GetSortSubtitle());
 
         if (a->GetRecordingStatus() == b->GetRecordingStatus())
             return a->GetScheduledStartTime() < b->GetScheduledStartTime();
@@ -1090,8 +1092,10 @@ class plPrevTitleSort : public plCompare
 
     bool operator()(const ProgramInfo *a, const ProgramInfo *b)
     {
-        if (a->sortTitle != b->sortTitle)
-            return (a->sortTitle < b->sortTitle);
+        if (a->GetSortTitle() != b->GetSortTitle())
+            return (a->GetSortTitle() < b->GetSortTitle());
+        if (a->GetSortSubtitle() != b->GetSortSubtitle())
+            return (a->GetSortSubtitle() < b->GetSortSubtitle());
 
         if (a->GetProgramID() != b->GetProgramID())
             return a->GetProgramID() < b->GetProgramID();
@@ -1390,19 +1394,6 @@ void ProgLister::FillItemList(bool restorePosition, bool updateDisp)
         LoadFromProgram(m_itemList, where, bindings, m_schedList);
     }
 
-    const QRegExp prefixes(
-        tr("^(The |A |An )",
-           "Regular Expression for what to ignore when sorting"));
-    for (uint i = 0; i < m_itemList.size(); i++)
-    {
-        ProgramInfo *s = m_itemList[i];
-        if (s)
-        {
-            s->sortTitle = (m_type == plTitle) ? s->GetSubtitle() : s->GetTitle();
-            s->sortTitle.remove(prefixes);
-        }
-    }
-
     if (m_type == plNewListings || m_titleSort)
     {
         SortList(kTitleSort, m_reverseSort);
@@ -1413,9 +1404,9 @@ void ProgLister::FillItemList(bool restorePosition, bool updateDisp)
             ProgramList::iterator it = m_itemList.begin();
             while (it != m_itemList.end())
             {
-                if ((*it)->sortTitle != curtitle)
+                if ((*it)->GetSortTitle() != curtitle)
                 {
-                    curtitle = (*it)->sortTitle;
+                    curtitle = (*it)->GetSortTitle();
                     ++it;
                 }
                 else
diff --git a/mythtv/programs/mythfrontend/programrecpriority.cpp b/mythtv/programs/mythfrontend/programrecpriority.cpp
index dc8c5fd..923e658 100644
--- a/mythtv/programs/mythfrontend/programrecpriority.cpp
+++ b/mythtv/programs/mythfrontend/programrecpriority.cpp
@@ -133,12 +133,20 @@ class TitleSort
     bool operator()(const ProgramRecPriorityInfo *a,
                     const ProgramRecPriorityInfo *b) const
     {
-        if (a->sortTitle != b->sortTitle)
+        if (a->GetSortTitle() != b->GetSortTitle())
         {
             if (m_reverse)
-                return (a->sortTitle > b->sortTitle);
+                return (a->GetSortTitle() > b->GetSortTitle());
             else
-                return (a->sortTitle < b->sortTitle);
+                return (a->GetSortTitle() < b->GetSortTitle());
+        }
+
+        if (a->GetSortSubtitle() != b->GetSortSubtitle())
+        {
+            if (m_reverse)
+                return (a->GetSortSubtitle() > b->GetSortSubtitle());
+            else
+                return (a->GetSortSubtitle() < b->GetSortSubtitle());
         }
 
         int finalA = a->GetRecordingPriority();
@@ -288,10 +296,18 @@ class ProgramCountSort
                 return recCountA > recCountB;
         }
 
+        if (a->GetSortTitle() != b->GetSortTitle())
+        {
+            if (m_reverse)
+                return (a->GetSortTitle() > b->GetSortTitle());
+            else
+                return (a->GetSortTitle() < b->GetSortTitle());
+        }
+
         if (m_reverse)
-            return (a->sortTitle > b->sortTitle);
+            return (a->GetSortSubtitle() > b->GetSortSubtitle());
         else
-            return (a->sortTitle < b->sortTitle);
+            return (a->GetSortSubtitle() < b->GetSortSubtitle());
     }
 
   private:
@@ -327,10 +343,18 @@ class ProgramRecCountSort
                 return countA > countB;
         }
 
+        if (a->GetSortTitle() != b->GetSortTitle())
+        {
+            if (m_reverse)
+                return (a->GetSortTitle() > b->GetSortTitle());
+            else
+                return (a->GetSortTitle() < b->GetSortTitle());
+        }
+
         if (m_reverse)
-            return (a->sortTitle > b->sortTitle);
+            return (a->GetSortSubtitle() > b->GetSortSubtitle());
         else
-            return (a->sortTitle < b->sortTitle);
+            return (a->GetSortSubtitle() < b->GetSortSubtitle());
     }
 
   private:
@@ -356,10 +380,18 @@ class ProgramLastRecordSort
                 return lastRecA > lastRecB;
         }
 
+        if (a->GetSortTitle() != b->GetSortTitle())
+        {
+            if (m_reverse)
+                return (a->GetSortTitle() > b->GetSortTitle());
+            else
+                return (a->GetSortTitle() < b->GetSortTitle());
+        }
+
         if (m_reverse)
-            return (a->sortTitle > b->sortTitle);
+            return (a->GetSortSubtitle() > b->GetSortSubtitle());
         else
-            return (a->sortTitle < b->sortTitle);
+            return (a->GetSortSubtitle() < b->GetSortSubtitle());
     }
 
   private:
@@ -385,10 +417,18 @@ class ProgramAvgDelaySort
                 return avgA < avgB;
         }
 
+        if (a->GetSortTitle() != b->GetSortTitle())
+        {
+            if (m_reverse)
+                return (a->GetSortTitle() > b->GetSortTitle());
+            else
+                return (a->GetSortTitle() < b->GetSortTitle());
+        }
+
         if (m_reverse)
-            return (a->sortTitle > b->sortTitle);
+            return (a->GetSortSubtitle() > b->GetSortSubtitle());
         else
-            return (a->sortTitle < b->sortTitle);
+            return (a->GetSortSubtitle() < b->GetSortSubtitle());
     }
 
   private:
@@ -935,11 +975,11 @@ void ProgramRecPriority::scheduleChanged(int recid)
         ProgramRecPriorityInfo progInfo;
         progInfo.SetRecordingRuleID(record.m_recordID);
         progInfo.SetRecordingRuleType(record.m_type);
-        progInfo.SetTitle(record.m_title);
+        progInfo.SetTitle(record.m_title, record.m_sortTitle);
+        progInfo.SetSubtitle(record.m_subtitle, record.m_sortSubtitle);
         progInfo.SetCategory(record.m_category);
         progInfo.SetRecordingPriority(record.m_recPriority);
         progInfo.recType = record.m_type;
-        progInfo.sortTitle = record.m_title;
         progInfo.recstatus = record.m_isInactive ?
             RecStatus::Inactive : RecStatus::Unknown;
         progInfo.profile = record.m_recProfile;
@@ -1177,8 +1217,9 @@ void ProgramRecPriority::FillList(void)
     // it all at once than once per program)
 
     MSqlQuery result(MSqlQuery::InitCon());
-    result.prepare("SELECT recordid, title, chanid, starttime, startdate, "
-                   "type, inactive, last_record, avg_delay, profile "
+    result.prepare("SELECT recordid, title, chanid, starttime, startdate, " // 0-4
+                   "type, inactive, last_record, avg_delay, profile, " // 5-9
+                   "subtitle, sorttitle, sortsubtitle " // 10-12
                    "FROM record;");
 
     if (!result.exec())
@@ -1191,6 +1232,9 @@ void ProgramRecPriority::FillList(void)
         do {
             uint recordid = result.value(0).toUInt();
             QString title = result.value(1).toString();
+            QString sortTitle = result.value(10).toString();
+            QString subtitle = result.value(11).toString();
+            QString sortSubtitle = result.value(12).toString();
             QString chanid = result.value(2).toString();
             QString tempTime = result.value(3).toString();
             QString tempDate = result.value(4).toString();
@@ -1208,9 +1252,6 @@ void ProgramRecPriority::FillList(void)
             {
                 ProgramRecPriorityInfo *progInfo = &(*it);
 
-                progInfo->sortTitle = progInfo->title;
-                progInfo->sortTitle.remove(QRegExp(tr("^(The |A |An )")));
-
                 progInfo->recType = recType;
                 progInfo->matchCount =
                     m_listMatch[progInfo->GetRecordingRuleID()];
diff --git a/mythtv/programs/mythfrontend/videofilter.cpp b/mythtv/programs/mythfrontend/videofilter.cpp
index 2e82927..86b9e0b 100644
--- a/mythtv/programs/mythfrontend/videofilter.cpp
+++ b/mythtv/programs/mythfrontend/videofilter.cpp
@@ -389,29 +389,14 @@ bool VideoFilterSettings::matches_filter(const VideoMetadata &mdata) const
 
 /// Compares two VideoMetadata instances
 bool VideoFilterSettings::meta_less_than(const VideoMetadata &lhs,
-                                         const VideoMetadata &rhs,
-                                         bool sort_ignores_case) const
+                                         const VideoMetadata &rhs) const
 {
     bool ret = false;
     switch (orderby)
     {
         case kOrderByTitle:
         {
-            VideoMetadata::SortKey lhs_key;
-            VideoMetadata::SortKey rhs_key;
-            if (lhs.HasSortKey() && rhs.HasSortKey())
-            {
-                lhs_key = lhs.GetSortKey();
-                rhs_key = rhs.GetSortKey();
-            }
-            else
-            {
-                lhs_key = VideoMetadata::GenerateDefaultSortKey(lhs,
-                                                           sort_ignores_case);
-                rhs_key = VideoMetadata::GenerateDefaultSortKey(rhs,
-                                                           sort_ignores_case);
-            }
-            ret = lhs_key < rhs_key;
+            ret = lhs.sortBefore(rhs);
             break;
         }
         case kOrderBySeasonEp:
@@ -423,21 +408,7 @@ bool VideoFilterSettings::meta_less_than(const VideoMetadata &lhs,
                 && (lhs.GetEpisode() == 0)
                 && (rhs.GetEpisode() == 0))
             {
-                VideoMetadata::SortKey lhs_key;
-                VideoMetadata::SortKey rhs_key;
-                if (lhs.HasSortKey() && rhs.HasSortKey())
-                {
-                    lhs_key = lhs.GetSortKey();
-                    rhs_key = rhs.GetSortKey();
-                }
-                else
-                {
-                    lhs_key = VideoMetadata::GenerateDefaultSortKey(lhs,
-                                                               sort_ignores_case);
-                    rhs_key = VideoMetadata::GenerateDefaultSortKey(rhs,
-                                                               sort_ignores_case);
-                }
-                ret = lhs_key < rhs_key;
+                ret = lhs.sortBefore(rhs);
             }
             else if ((lhs.GetSeason() == rhs.GetSeason())
                      && (lhs.GetTitle() == rhs.GetTitle()))
@@ -463,10 +434,8 @@ bool VideoFilterSettings::meta_less_than(const VideoMetadata &lhs,
         }
         case kOrderByFilename:
         {
-            QString lhsfn(sort_ignores_case ?
-                          lhs.GetFilename().toLower() : lhs.GetFilename());
-            QString rhsfn(sort_ignores_case ?
-                          rhs.GetFilename().toLower() : rhs.GetFilename());
+            QString lhsfn = lhs.GetSortFilename();
+            QString rhsfn = rhs.GetSortFilename();
             ret = naturalCompare(lhsfn, rhsfn) < 0;
             break;
         }
diff --git a/mythtv/programs/mythfrontend/videofilter.h b/mythtv/programs/mythfrontend/videofilter.h
index d518abf..4a6a171 100644
--- a/mythtv/programs/mythfrontend/videofilter.h
+++ b/mythtv/programs/mythfrontend/videofilter.h
@@ -48,8 +48,7 @@ class VideoFilterSettings
     VideoFilterSettings &operator=(const VideoFilterSettings &rhs);
 
     bool matches_filter(const VideoMetadata &mdata) const;
-    bool meta_less_than(const VideoMetadata &lhs, const VideoMetadata &rhs,
-                        bool sort_ignores_case) const;
+    bool meta_less_than(const VideoMetadata &lhs, const VideoMetadata &rhs) const;
 
     void saveAsDefault();
 
@@ -190,7 +189,6 @@ class VideoFilterSettings
     const QRegExp re_season;
     const QRegExp re_date;
 
-
     unsigned int m_changed_state;
 };
 
diff --git a/mythtv/programs/mythfrontend/videolist.cpp b/mythtv/programs/mythfrontend/videolist.cpp
index 7bcbda0..a80b0dc 100644
--- a/mythtv/programs/mythfrontend/videolist.cpp
+++ b/mythtv/programs/mythfrontend/videolist.cpp
@@ -149,28 +149,25 @@ QString TreeNodeData::GetPrefix(void) const
 /// metadata sort function
 struct metadata_sort
 {
-    metadata_sort(const VideoFilterSettings &vfs, bool sort_ignores_case) :
-        m_vfs(vfs), m_sic(sort_ignores_case) {}
+    metadata_sort(const VideoFilterSettings &vfs) : m_vfs(vfs) {}
 
     bool operator()(const VideoMetadata *lhs, const VideoMetadata *rhs)
     {
-        return m_vfs.meta_less_than(*lhs, *rhs, m_sic);
+        return m_vfs.meta_less_than(*lhs, *rhs);
     }
 
     bool operator()(const smart_meta_node &lhs, const smart_meta_node &rhs)
     {
-        return m_vfs.meta_less_than(*(lhs->getData()), *(rhs->getData()),
-                                    m_sic);
+        return m_vfs.meta_less_than(*(lhs->getData()), *(rhs->getData()));
     }
 
   private:
     const VideoFilterSettings &m_vfs;
-    bool m_sic;
 };
 
 struct metadata_path_sort
 {
-    explicit metadata_path_sort(bool ignore_case) : m_ignore_case(ignore_case) {}
+    explicit metadata_path_sort(void) {}
 
     bool operator()(const VideoMetadata &lhs, const VideoMetadata &rhs)
     {
@@ -184,28 +181,19 @@ struct metadata_path_sort
 
     bool operator()(const smart_dir_node &lhs, const smart_dir_node &rhs)
     {
-        return sort(lhs->getPath(), rhs->getPath());
+        return sort(lhs->getSortPath(), rhs->getSortPath());
     }
 
   private:
     bool sort(const VideoMetadata *lhs, const VideoMetadata *rhs)
     {
-        return sort(lhs->GetFilename(), rhs->GetFilename());
+        return sort(lhs->GetSortFilename(), rhs->GetSortFilename());
     }
 
     bool sort(const QString &lhs, const QString &rhs)
     {
-        QString lhs_comp(lhs);
-        QString rhs_comp(rhs);
-        if (m_ignore_case)
-        {
-            lhs_comp = lhs_comp.toLower();
-            rhs_comp = rhs_comp.toLower();
-        }
-        return naturalCompare(lhs_comp, rhs_comp) < 0;
+        return naturalCompare(lhs, rhs) < 0;
     }
-
-    bool m_ignore_case;
 };
 
 static QString path_to_node_name(const QString &path)
@@ -751,13 +739,12 @@ void VideoListImp::sort_view_data(bool flat_list)
     if (flat_list)
     {
         sort(m_metadata_view_flat.begin(), m_metadata_view_flat.end(),
-             metadata_sort(m_video_filter, true));
+             metadata_sort(m_video_filter));
     }
     else
     {
-        m_metadata_view_tree.sort(metadata_path_sort(true),
-                                  metadata_sort(m_video_filter,
-                                                true));
+        m_metadata_view_tree.sort(metadata_path_sort(),
+                                  metadata_sort(m_video_filter));
     }
 }
 
@@ -811,7 +798,7 @@ void VideoListImp::buildGroupList(metadata_list_type whence)
     transform(m_metadata.getList().begin(), m_metadata.getList().end(),
               mli, to_metadata_ptr());
 
-    metadata_path_sort mps(true);
+    metadata_path_sort mps = metadata_path_sort();
     sort(mlist.begin(), mlist.end(), mps);
 
     typedef map<QString, meta_dir_node *> group_to_node_map;
@@ -944,7 +931,7 @@ void VideoListImp::buildTVList(void)
     transform(m_metadata.getList().begin(), m_metadata.getList().end(),
               mli, to_metadata_ptr());
 
-    metadata_path_sort mps(true);
+    metadata_path_sort mps = metadata_path_sort();
     sort(mlist.begin(), mlist.end(), mps);
 
     meta_dir_node *video_root = &m_metadata_tree;
@@ -990,7 +977,7 @@ void VideoListImp::buildDbList()
 
 //    print_meta_list(mlist);
 
-    metadata_path_sort mps(true);
+    metadata_path_sort mps = metadata_path_sort();
     sort(mlist.begin(), mlist.end(), mps);
 
     // TODO: break out the prefix in the DB so this isn't needed
@@ -1145,20 +1132,6 @@ void VideoListImp::update_meta_view(bool flat_list)
 
     m_metadata_view_tree.clear();
 
-    // a big punt on setting the sort key
-    // TODO: put this in the DB, half the time in this function is spent
-    // doing this.
-    for (metadata_list::const_iterator si = m_metadata.getList().begin();
-         si != m_metadata.getList().end(); ++si)
-    {
-        if (!(*si)->HasSortKey())
-        {
-            VideoMetadata::SortKey skey =
-                VideoMetadata::GenerateDefaultSortKey(*(*si), true);
-            (*si)->SetSortKey(skey);
-        }
-    }
-
     if (flat_list)
     {
         for (metadata_list::const_iterator p = m_metadata.getList().begin();
diff --git a/mythtv/programs/mythpreviewgen/main.cpp b/mythtv/programs/mythpreviewgen/main.cpp
index 87ea784..36fface 100644
--- a/mythtv/programs/mythpreviewgen/main.cpp
+++ b/mythtv/programs/mythpreviewgen/main.cpp
@@ -106,9 +106,9 @@ int preview_helper(uint chanid, QDateTime starttime,
             return GENERIC_EXIT_NOT_OK;
         }
         pginfo = new ProgramInfo(
-            infile, ""/*plot*/, ""/*title*/, ""/*subtitle*/, ""/*director*/,
-            0/*season*/, 0/*episode*/, ""/*inetref*/, 120/*length_in_minutes*/,
-            1895/*year*/, ""/*id*/);
+            infile, ""/*plot*/, ""/*title*/, ""/*sortTitle*/, ""/*subtitle*/,
+            ""/*sortSubtitle*/, ""/*director*/, 0/*season*/, 0/*episode*/,
+            ""/*inetref*/, 120/*length_in_minutes*/, 1895/*year*/, ""/*id*/);
     }
     else
     {
-- 
2.9.3

