Index: themes/G.A.N.T./ui.xml
===================================================================
--- themes/G.A.N.T./ui.xml	(revision 11073)
+++ themes/G.A.N.T./ui.xml	(working copy)
@@ -1315,8 +1315,12 @@
       </image>
       <image name="hdtv" draworder="6">
         <filename>type/hd.png</filename>
-        <position>275,195</position>
+        <position>265,195</position>
       </image>
+      <image name="watched" draworder="6">
+          <filename>type/watched.png</filename>
+          <position>300,195</position>
+      </image>
 
 
     </container>
Index: themes/default/watched.png
===================================================================
--- themes/default/watched.png	(revision 0)
+++ themes/default/watched.png	(revision 0)
@@ -0,0 +1,3 @@
+PNG
+
+
IHDR  szzôbKGDÞg:è	pHYstIMEÖ	0îtEXtCommentCreated with GIMPWºIDATXÃíQÃ0Cí(7ØýÏ}UZ5BÁéZ€ßð\jB:gC`Ò"§sôDÆØëôX©Ü®CDÐ²"³kÁáùE/"aåªÙ«£­Úÿê}©
M±q¹ùß*Ñ§§Ú^>gp-z{eÈpL,ÓsÛà#ÀZCèYçÙšrÐ­YPw$g¯di·rd®ÒV\â]PâetéÛðöx
QqLoà¥IEND®B`
\ No newline at end of file

Property changes on: themes/default/watched.png
___________________________________________________________________
Name: svn:mime-type
   + image/png

Index: themes/blue/ui.xml
===================================================================
--- themes/blue/ui.xml	(revision 11073)
+++ themes/blue/ui.xml	(working copy)
@@ -883,6 +883,10 @@
         <filename>type/hd.png</filename>
         <position>257,106</position>
       </image>
+      <image name="watched" draworder="6">
+          <filename>type/watched.png</filename>
+          <position>302,106</position>
+      </image>
 
       <image name="processing" draworder="6">
         <filename>processing.png</filename>
Index: themes/defaultosd/osd.xml
===================================================================
--- themes/defaultosd/osd.xml	(revision 11073)
+++ themes/defaultosd/osd.xml	(working copy)
@@ -273,6 +273,11 @@
       <font>infofont</font>
       <altfont>infofontgray</altfont>
     </textarea>
+    <textarea name="option5">
+      <area>85,420,470,30</area>
+      <font>infofont</font>
+      <altfont>infofontgray</altfont>
+    </textarea>
     <positionrects name="selector"> <!-- draws a white rectangle at these 
                                          coords indicating the current 
                                          selection -->
Index: themes/blueosd/osd.xml
===================================================================
--- themes/blueosd/osd.xml	(revision 11073)
+++ themes/blueosd/osd.xml	(working copy)
@@ -310,6 +310,11 @@
       <font>infofont</font>
       <altfont>infofontgray</altfont>
     </textarea>
+    <textarea name="option5">
+      <area>85,420,470,30</area>
+      <font>infofont</font>
+      <altfont>infofontgray</altfont>
+    </textarea>
     <positionrects name="selector">
       <area>80,296,480,30</area>
       <area>80,326,480,30</area>
Index: libs/libmythtv/NuppelVideoPlayer.cpp
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.cpp	(revision 11073)
+++ libs/libmythtv/NuppelVideoPlayer.cpp	(working copy)
@@ -3631,6 +3631,36 @@
                 "Audio buffer overflow, audio data lost!");
 }
 
+void NuppelVideoPlayer::SetWatched(bool forceWatched)
+{
+    if (!m_playbackinfo)
+        return;
+
+    int numFrames = totalFrames;
+
+    if (m_playbackinfo->GetTranscodedStatus() != TRANSCODING_COMPLETE)
+        numFrames = (m_playbackinfo->endts.toTime_t() -
+                    m_playbackinfo->recstartts.toTime_t()) * video_frame_rate;
+
+    int offset = (int) round(0.2 * (numFrames / video_frame_rate));
+
+    if (offset < 300)
+        offset = 300; // 5 Minutes Min
+    else if (offset > 720)
+        offset = 720; // 12 Minutes Max
+
+    if (forceWatched || framesPlayed > numFrames - (offset * video_frame_rate))
+    {
+        m_playbackinfo->SetWatchedFlag(true);
+        VERBOSE(VB_GENERAL, QString("Marking recording as watched using offset %1 minutes").arg(offset/60));
+    }
+    else
+    {
+        m_playbackinfo->SetWatchedFlag(false);
+        VERBOSE(VB_GENERAL, "Marking recording as unwatched");
+    }
+}
+
 void NuppelVideoPlayer::SetBookmark(void)
 {
     if (!m_playbackinfo || !osd)
@@ -6689,6 +6719,10 @@
 {
     osdtheme = QDeepCopy<QString>(themename);
 }
+
+NuppelVideoPlayer SetLiveTVChain( LiveTVChain * tvchain )
+{
+}
 // EIA-708 caption support -- end
 
 /* vim: set expandtab tabstop=4 shiftwidth=4: */
Index: libs/libmythtv/dbcheck.cpp
===================================================================
--- libs/libmythtv/dbcheck.cpp	(revision 11073)
+++ libs/libmythtv/dbcheck.cpp	(working copy)
@@ -10,7 +10,7 @@
 #include "mythdbcon.h"
 
 /// This is the DB schema version expected by the running MythTV instance.
-const QString currentDatabaseVersion = "1157";
+const QString currentDatabaseVersion = "1158";
 
 static bool UpdateDBVersionNumber(const QString &newnumber);
 static bool performActualUpdate(const QString updates[], QString version,
@@ -2497,6 +2497,17 @@
             return false;
     }
 
+    if (dbver == "1157")
+    {
+        const QString updates[] = {
+"ALTER TABLE recorded ADD COLUMN watched TINYINT NOT NULL DEFAULT '0';",
+""
+};
+
+        if (!performActualUpdate(updates, "1158", dbver))
+            return false;
+    }
+
 //"ALTER TABLE capturecard DROP COLUMN dvb_recordts;" in 0.21
 //"ALTER TABLE capturecard DROP COLUMN dvb_hw_decoder;" in 0.21
 //"ALTER TABLE cardinput DROP COLUMN preference;" in 0.22
Index: libs/libmythtv/NuppelVideoPlayer.h
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.h	(revision 11073)
+++ libs/libmythtv/NuppelVideoPlayer.h	(working copy)
@@ -145,6 +145,7 @@
 
     void SetTranscoding(bool value);
     void SetWatchingRecording(bool mode);
+    void SetWatched(bool forceWatched = false);
     void SetBookmark(void);
     void SetKeyframeDistance(int keyframedistance);
     void SetVideoParams(int w, int h, double fps, int keydist,
Index: libs/libmythtv/tv_play.cpp
===================================================================
--- libs/libmythtv/tv_play.cpp	(revision 11073)
+++ libs/libmythtv/tv_play.cpp	(working copy)
@@ -1216,7 +1216,7 @@
     {
         VERBOSE(VB_PLAYBACK,LOC + " StopStuff() -- get dvd player out of still frame or wait status");
         prbuffer->DVD()->IgnoreStillOrWait(true);
-    } 
+    }
 
     if (stopRingBuffers)
     {
@@ -2116,11 +2116,16 @@
                             wantsToQuit = true;
                             exitPlayer = true;
                             break;
-                        case 3: case 0:
+                        case 3:
+                            nvp->SetWatched(true);
+                            wantsToQuit = true;
+                            exitPlayer = true;
+                            break;
+                        case 4: case 0:
                             paused = !paused;
                             DoPause();
                             break;
-                        case 4:
+                        case 5:
                             wantsToQuit = true;
                             exitPlayer = true;
                             requestDelete = true;
@@ -2486,6 +2491,7 @@
                 QStringList options;
                 options += tr("Save this position and go to the menu");
                 options += tr("Do not save, just exit to the menu");
+                options += tr("Mark as watched and go to the menu");
                 options += tr("Keep watching");
                 options += tr("Delete this recording");
 
@@ -2520,6 +2526,8 @@
             {
                 if (nvp && gContext->GetNumSetting("PlaybackExitPrompt") == 2)
                     nvp->SetBookmark();
+                if (nvp && gContext->GetNumSetting("AutomaticSetWatched", 0))
+                    nvp->SetWatched();
                 exitPlayer = true;
                 wantsToQuit = true;
             }
Index: libs/libmythtv/programinfo.h
===================================================================
--- libs/libmythtv/programinfo.h	(revision 11073)
+++ libs/libmythtv/programinfo.h	(working copy)
@@ -55,7 +55,8 @@
     FL_STEREO         = 0x080,
     FL_CC             = 0x100,
     FL_HDTV           = 0x200,
-    FL_TRANSCODED     = 0x400
+    FL_TRANSCODED     = 0x400,
+    FL_WATCHED        = 0x800
 };
 
 enum RecStatusType {
@@ -183,6 +184,7 @@
     bool IsCommFlagged(void) const;
     bool IsInUse(QString &byWho) const;
     int GetAutoExpireFromRecorded(void) const;
+    int GetTranscodedStatus(void) const;
     bool GetPreserveEpisodeFromRecorded(void) const;
     bool UsesMaxEpisodes(void) const;
     int getProgramFlags(void) const;
@@ -195,6 +197,7 @@
     void SetBookmark(long long pos) const;
     void SetEditing(bool edit) const;
     void SetTranscoded(int transFlag) const;
+    void SetWatchedFlag(bool watchedFlag) const;
     void SetDeleteFlag(bool deleteFlag) const;
     void SetCommFlagged(int flag) const; // 1 = flagged, 2 = processing
     void SetAutoExpire(int autoExpire) const;
Index: libs/libmythtv/programinfo.cpp
===================================================================
--- libs/libmythtv/programinfo.cpp	(revision 11073)
+++ libs/libmythtv/programinfo.cpp	(working copy)
@@ -1808,6 +1808,30 @@
     return pos;
 }
 
+/** \fn ProgramInfo::SetWatchedFlag(bool) const
+ *  \brief Set "watched" field in "recorded" table to "watchedFlag".
+ *  \param watchedFlag value to set watched field to.
+ */
+void ProgramInfo::SetWatchedFlag(bool watchedFlag) const
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    query.prepare("UPDATE recorded"
+                  " SET watched = :WATCHEDFLAG"
+                  " WHERE chanid = :CHANID"
+                  " AND starttime = :STARTTIME ;");
+    query.bindValue(":CHANID", chanid);
+    query.bindValue(":STARTTIME", recstartts);
+
+    if (watchedFlag)
+        query.bindValue(":WATCHEDFLAG", 1);
+    else
+        query.bindValue(":WATCHEDFLAG", 0);
+
+    if (!query.exec() || !query.isActive())
+        MythContext::DBError("Set watched flag", query);
+}
+
 /** \fn ProgramInfo::IsEditing(void) const
  *  \brief Queries "recorded" table for its "editing" field
  *         and returns true if it is set to true.
@@ -1950,6 +1974,28 @@
     return false;
 }
 
+/** \fn ProgramInfo::GetTranscodedStatus(void) const
+ *  \brief Returns the "transcoded" field in "recorded" table.
+ */
+int ProgramInfo::GetTranscodedStatus(void) const
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    query.prepare("SELECT transcoded FROM recorded"
+                 " WHERE chanid = :CHANID"
+                 " AND starttime = :STARTTIME ;");
+    query.bindValue(":CHANID", chanid);
+    query.bindValue(":STARTTIME", recstartts);
+
+    if (query.exec() && query.isActive() && query.size() > 0)
+    {
+        query.next();
+        return query.value(0).toInt();
+    }
+
+    return false;
+}
+
 /** \fn ProgramInfo::SetTranscoded(int transFlag) const
  *  \brief Set "transcoded" field in "recorded" table to "transFlag".
  *  \param transFlag value to set transcoded field to.
@@ -3513,7 +3559,7 @@
     MSqlQuery query(MSqlQuery::InitCon());
 
     query.prepare("SELECT commflagged, cutlist, autoexpire, "
-                  "editing, bookmark, stereo, closecaptioned, hdtv "
+                  "editing, bookmark, stereo, closecaptioned, hdtv, watched "
                   "FROM recorded LEFT JOIN recordedprogram ON "
                   "(recorded.chanid = recordedprogram.chanid AND "
                   "recorded.starttime = recordedprogram.starttime) "
@@ -3535,6 +3581,7 @@
         flags |= (query.value(5).toInt() == 1) ? FL_STEREO : 0;
         flags |= (query.value(6).toInt() == 1) ? FL_CC : 0;
         flags |= (query.value(7).toInt() == 1) ? FL_HDTV : 0;
+        flags |= (query.value(8).toInt() == 1) ? FL_WATCHED : 0;
     }
 
     return flags;
@@ -4181,7 +4228,7 @@
         "recorded.basename, recorded.progstart, "
         "recorded.progend, recorded.stars, "
         "recordedprogram.stereo, recordedprogram.hdtv, "
-        "recordedprogram.closecaptioned "
+        "recordedprogram.closecaptioned, recorded.watched "
         "FROM recorded "
         "LEFT JOIN record ON recorded.recordid = record.recordid "
         "LEFT JOIN channel ON recorded.chanid = channel.chanid "
@@ -4274,6 +4321,7 @@
             flags |= (query.value(32).toInt() == 1)           ? FL_STEREO   : 0;
             flags |= (query.value(34).toInt() == 1)           ? FL_CC       : 0;
             flags |= (query.value(33).toInt() == 1)           ? FL_HDTV     : 0;
+            flags |= (query.value(35).toInt() == 1)           ? FL_WATCHED  : 0;
 
             inUseKey = query.value(0).toString() + " " +
                        query.value(1).toDateTime().toString(Qt::ISODate);
Index: programs/mythfrontend/playbackbox.cpp
===================================================================
--- programs/mythfrontend/playbackbox.cpp	(revision 11073)
+++ programs/mythfrontend/playbackbox.cpp	(working copy)
@@ -786,6 +786,7 @@
         iconMap["stereo"]      = FL_STEREO;
         iconMap["cc"]          = FL_CC;
         iconMap["hdtv"]        = FL_HDTV;
+        iconMap["watched"]     = FL_WATCHED;
 
         UIImageType *itype;
         for (it = iconMap.begin(); it != iconMap.end(); ++it)
@@ -2793,6 +2794,17 @@
             popup->addButton(tr("Enable Auto Expire"), this, SLOT(doAutoExpire()));
     }
 
+    if (curitem->programflags & FL_WATCHED)
+    {
+        popup->addButton(tr("Mark as Unwatched"), this,
+                                    SLOT(setUnwatched()));
+    }
+    else
+    {
+        popup->addButton(tr("Mark as Watched"), this,
+                                    SLOT(setWatched()));
+    }
+
     popup->addButton(tr("Recording Options"), this, SLOT(showRecordingPopup()));
     popup->addButton(tr("Job Options"), this, SLOT(showJobPopup()));
 
@@ -3304,6 +3316,48 @@
     return NULL;
 }
 
+void PlaybackBox::setUnwatched(void)
+{
+    if (!expectingPopup && delitem)
+        return;
+
+    cancelPopup();
+
+    delitem->SetWatchedFlag(0);
+
+    ProgramInfo *tmpItem = findMatchingProg(delitem);
+    if (tmpItem)
+        tmpItem->programflags &= ~FL_WATCHED;
+
+    delete delitem;
+    delitem = NULL;
+
+    previewVideoState = kChanging;
+
+    update(drawListBounds);
+}
+
+void PlaybackBox::setWatched(void)
+{
+    if (!expectingPopup)
+        return;
+
+    cancelPopup();
+
+    delitem->SetWatchedFlag(1);
+
+    ProgramInfo *tmpItem = findMatchingProg(delitem);
+    if (tmpItem)
+        tmpItem->programflags |= FL_WATCHED;
+
+    delete delitem;
+    delitem = NULL;
+
+    previewVideoState = kChanging;
+
+    update(drawListBounds);
+}
+
 void PlaybackBox::noAutoExpire(void)
 {
     if (!expectingPopup && delitem)
Index: programs/mythfrontend/globalsettings.cpp
===================================================================
--- programs/mythfrontend/globalsettings.cpp	(revision 11073)
+++ programs/mythfrontend/globalsettings.cpp	(working copy)
@@ -570,6 +570,16 @@
     return bc;
 }
 
+static GlobalCheckBox *AutoExpireWatchedPriority() 
+{
+    GlobalCheckBox *bc = new GlobalCheckBox("AutoExpireWatchedPriority"); 
+    bc->setLabel(QObject::tr("Auto Expire watched programs before unwatched")); 
+    bc->setValue(false); 
+    bc->setHelpText(QObject::tr("If set, programs that have been marked as "
+                    "watched will be expired first"));
+    return bc;
+}
+
 static GlobalSpinBox *AutoExpireDayPriority()
 {
     GlobalSpinBox *bs = new GlobalSpinBox("AutoExpireDayPriority", 1, 400, 1);
@@ -620,6 +630,17 @@
 }
 #endif
 
+static GlobalCheckBox *RerecordWatched() 
+{
+    GlobalCheckBox *bc = new GlobalCheckBox("RerecordWatched"); 
+    bc->setLabel(QObject::tr("Re-record watched programs")); 
+    bc->setValue(true); 
+    bc->setHelpText(QObject::tr("If set, programs that have been marked as "
+                    "watched and are auto-expired will be re-recorded if "
+                    "they are shown again."));
+    return bc;
+}
+
 static GlobalSpinBox *RecordPreRoll()
 {
     GlobalSpinBox *bs = new GlobalSpinBox("RecordPreRoll", 0, 600, 60, true);
@@ -1226,6 +1247,19 @@
     return gc;
 }
 
+static HostCheckBox *AutomaticSetWatched()
+{
+    HostCheckBox *gc = new HostCheckBox("AutomaticSetWatched");
+    gc->setLabel(QObject::tr("Automatically mark a record watched"));
+    gc->setValue(false);
+    gc->setHelpText(QObject::tr("If set, when you exit near the end of a "
+                    "recording it will be marked as watched. The automatic "
+                    "detection is not foolproof, so do not enable this "
+                    "setting if you don't want an unwatched recording marked "
+                    "as watched."));
+    return gc;
+}
+
 static HostCheckBox *GeneratePreviewPixmaps()
 {
     HostCheckBox *gc = new HostCheckBox("GeneratePreviewPixmaps");
@@ -3416,6 +3450,7 @@
     gen2->setLabel(QObject::tr("General playback (part 2)"));
     gen2->addChild(PlaybackExitPrompt());
     gen2->addChild(EndOfRecordingExitPrompt());
+    gen2->addChild(AutomaticSetWatched());
     gen2->addChild(ClearSavedPosition());
     gen2->addChild(AltClearSavedPosition());
 #ifdef USING_XV
@@ -3540,9 +3575,11 @@
     VerticalConfigurationGroup* autoexp = new VerticalConfigurationGroup(false);
     autoexp->setLabel(QObject::tr("General (AutoExpire)"));
     autoexp->addChild(AutoExpireMethod());
+    autoexp->addChild(AutoExpireWatchedPriority());
     autoexp->addChild(AutoExpireDayPriority());
     autoexp->addChild(AutoExpireDefault());
     autoexp->addChild(AutoExpireLiveTVMaxAge());
+    autoexp->addChild(RerecordWatched());
     autoexp->addChild(AutoExpireExtraSpace());
     addChild(autoexp);
 
Index: programs/mythfrontend/playbackbox.h
===================================================================
--- programs/mythfrontend/playbackbox.h	(revision 11073)
+++ programs/mythfrontend/playbackbox.h	(working copy)
@@ -161,6 +161,9 @@
     void doForceDelete();
     void noDelete();
 
+    void setWatched();
+    void setUnwatched();
+
     void doAutoExpire();
     void noAutoExpire();
     void doPreserveEpisode();
Index: programs/mythtranscode/main.cpp
===================================================================
--- programs/mythtranscode/main.cpp	(revision 11073)
+++ programs/mythtranscode/main.cpp	(working copy)
@@ -662,11 +662,12 @@
                 MythContext::DBError("Error in mythtranscode", query);
 
             query.prepare("UPDATE recorded "
-                          "SET cutlist = :CUTLIST, bookmark = :BOOKMARK "
-                          "WHERE chanid = :CHANID "
+                          "SET cutlist = :CUTLIST, bookmark = :BOOKMARK, "
+                          "watched = :WATCHED WHERE chanid = :CHANID "
                           "AND starttime = :STARTTIME ;");
             query.bindValue(":CUTLIST", "0");
             query.bindValue(":BOOKMARK", "0");
+            query.bindValue(":WATCHED", "0");
             query.bindValue(":CHANID", pginfo->chanid);
             query.bindValue(":STARTTIME", pginfo->recstartts);
             query.exec();
Index: programs/mythbackend/mainserver.cpp
===================================================================
--- programs/mythbackend/mainserver.cpp	(revision 11073)
+++ programs/mythbackend/mainserver.cpp	(working copy)
@@ -661,8 +661,11 @@
             if (pinfo)
             {
                 // allow re-record if auto expired but not expired live buffers
-                if (pinfo->recgroup != "LiveTV")
+                if (pinfo->recgroup != "LiveTV" &&
+                    (gContext->GetNumSetting("RerecordWatched", 0) ||
+                    (!pinfo->getProgramFlags() & FL_WATCHED)))
                     pinfo->ForgetHistory();
+
                 DoHandleDeleteRecording(pinfo, NULL, false);
             }
             else
@@ -1028,7 +1031,7 @@
         "recorded.progend, recorded.stars, "
         "recordedprogram.stereo, recordedprogram.hdtv, "
         "recordedprogram.closecaptioned, transcoded, "
-        "recorded.recpriority "
+        "recorded.recpriority, watched "
         "FROM recorded "
         "LEFT JOIN record ON recorded.recordid = record.recordid "
         "LEFT JOIN channel ON recorded.chanid = channel.chanid "
@@ -1132,6 +1135,7 @@
             flags |= (query.value(34).toInt() == 1) ? FL_CC : 0;
             flags |= (query.value(35).toInt() == TRANSCODING_COMPLETE) ?
                       FL_TRANSCODED : 0;
+            flags |= (query.value(37).toInt() == 1) ? FL_WATCHED : 0;
 
             inUseKey = query.value(0).toString() + " " +
                        query.value(1).toDateTime().toString(Qt::ISODate);
Index: programs/mythbackend/autoexpire.cpp
===================================================================
--- programs/mythbackend/autoexpire.cpp	(revision 11073)
+++ programs/mythbackend/autoexpire.cpp	(working copy)
@@ -762,15 +762,21 @@
         default:
         case emOldestFirst:
             where = "autoexpire > 0";
-            orderby = "starttime ASC";
+            if (gContext->GetNumSetting("AutoExpireWatchedPriority", 0))
+                orderby = "recorded.watched DESC, ";
+            orderby += "starttime ASC";
             break;
         case emLowestPriorityFirst:
             where = "autoexpire > 0";
-            orderby = "recorded.recpriority ASC, starttime ASC";
+            if (gContext->GetNumSetting("AutoExpireWatchedPriority", 0))
+                orderby = "recorded.watched DESC, ";
+            orderby += "recorded.recpriority ASC, starttime ASC";
             break;
         case emWeightedTimePriority:
             where = "autoexpire > 0";
-            orderby = QString("DATE_ADD(starttime, INTERVAL '%1' * "
+            if (gContext->GetNumSetting("AutoExpireWatchedPriority", 0))
+                orderby = "recorded.watched DESC, ";
+            orderby += QString("DATE_ADD(starttime, INTERVAL '%1' * "
                                         "recorded.recpriority DAY) ASC")
                       .arg(gContext->GetNumSetting("AutoExpireDayPriority", 3));
             break;
