Index: libs/libmythtv/deletemap.h
===================================================================
--- libs/libmythtv/deletemap.h	(revision 27220)
+++ libs/libmythtv/deletemap.h	(working copy)
@@ -4,12 +4,24 @@
 #include "programinfo.h"
 #include "playercontext.h"
 
+class DeleteMap;
+
+typedef struct DeleteMapUndoEntry
+{
+    frm_dir_map_t deleteMap;
+    QString message; // how we got from previous map to this map
+    DeleteMapUndoEntry(frm_dir_map_t dm, QString msg);
+    DeleteMapUndoEntry(void);
+} DeleteMapUndoEntry;
+
 class DeleteMap
 {
   public:
     DeleteMap(): m_editing(false),   m_nextCutStart(0), m_changed(true),
-                 m_seekamountpos(4), m_seekamount(30) { }
+                 m_seekamountpos(4), m_seekamount(30),
+                 m_ctx(0), m_undoStackPointer(-1) { Push(""); }
 
+    void SetPlayerContext(PlayerContext *ctx) { m_ctx = ctx; }
     bool HandleAction(QString &action, uint64_t frame, uint64_t played,
                       uint64_t total, double rate);
     int  GetSeekAmount(void) { return m_seekamount; }
@@ -28,15 +40,17 @@
 
     void SetMap(const frm_dir_map_t &map);
     void LoadCommBreakMap(uint64_t total, frm_dir_map_t &map);
-    void SaveMap(uint64_t total, PlayerContext *ctx);
-    void LoadMap(uint64_t total, PlayerContext *ctx);
+    void SaveMap(uint64_t total, PlayerContext *ctx, bool isAutoSave=false);
+    void LoadMap(uint64_t total, PlayerContext *ctx, QString undoMessage="");
+    bool LoadAutoSaveMap(uint64_t total, PlayerContext *ctx);
     void CleanMap(uint64_t total);
 
-    void Clear(void);
+    void Clear(QString undoMessage="");
     void ReverseAll(uint64_t total);
-    void Add(uint64_t frame, uint64_t total, MarkTypes type);
+    void Add(uint64_t frame, uint64_t total, MarkTypes type,
+             QString undoMessage);
     void NewCut(uint64_t frame, uint64_t total);
-    void Delete(uint64_t frame, uint64_t total);
+    void Delete(uint64_t frame, uint64_t total, QString undoMessage);
     void Reverse(uint64_t frame, uint64_t total);
     void MoveRelative(uint64_t frame, uint64_t total, bool right);
     void Move(uint64_t frame, uint64_t to, uint64_t total);
@@ -50,10 +64,19 @@
     void TrackerReset(uint64_t frame, uint64_t total);
     bool TrackerWantsToJump(uint64_t frame, uint64_t total, uint64_t &to);
 
+    bool Undo(void);
+    bool Redo(void);
+    bool HasUndo(void) { return m_undoStackPointer > 0; }
+    bool HasRedo(void) { return m_undoStackPointer < m_undoStack.size() - 1; }
+    QString GetUndoMessage(void);
+    QString GetRedoMessage(void);
+
   private:
     void Add(uint64_t frame, MarkTypes type);
     MarkTypes Delete(uint64_t frame);
 
+    void Push(QString undoMessage);
+
     bool          m_editing;
     uint64_t      m_nextCutStart;
     frm_dir_map_t m_deleteMap;
@@ -61,6 +84,11 @@
     bool          m_changed;
     int           m_seekamountpos;
     int           m_seekamount;
+    PlayerContext *m_ctx;
+
+    // Invariant: m_undoStack[m_undoStackPointer].deleteMap == m_deleteMap
+    QVector<DeleteMapUndoEntry> m_undoStack;
+    int m_undoStackPointer;
 };
 
 #endif // DELETEMAP_H
Index: libs/libmythtv/mythplayer.h
===================================================================
--- libs/libmythtv/mythplayer.h	(revision 27220)
+++ libs/libmythtv/mythplayer.h	(working copy)
@@ -408,6 +408,10 @@
     bool IsTemporaryMark(uint64_t frame);
     bool HasTemporaryMark(void);
     bool IsCutListSaved(PlayerContext *ctx) { return deleteMap.IsSaved(ctx); }
+    bool DeleteMapHasUndo(void) { return deleteMap.HasUndo(); }
+    bool DeleteMapHasRedo(void) { return deleteMap.HasRedo(); }
+    QString DeleteMapGetUndoMessage(void) { return deleteMap.GetUndoMessage(); }
+    QString DeleteMapGetRedoMessage(void) { return deleteMap.GetRedoMessage(); }
 
     // Reinit
     void ReinitOSD(void);
Index: libs/libmythtv/tv_play.cpp
===================================================================
--- libs/libmythtv/tv_play.cpp	(revision 27220)
+++ libs/libmythtv/tv_play.cpp	(working copy)
@@ -774,6 +774,10 @@
             "Jump back 10x the normal amount"), ",,<");
     REG_KEY("TV Editing", "BIGJUMPFWD",  QT_TRANSLATE_NOOP("MythControls",
             "Jump forward 10x the normal amount"), ">,.");
+    REG_KEY("TV Editing", "UNDO",        QT_TRANSLATE_NOOP("MythControls",
+            "Undo"), "Ctrl+Z");
+    REG_KEY("TV Editing", "REDO",        QT_TRANSLATE_NOOP("MythControls",
+            "Redo"), "Ctrl+Y");
 
     /* Teletext keys */
     REG_KEY("Teletext Menu", "NEXTPAGE",    QT_TRANSLATE_NOOP("MythControls",
@@ -9124,6 +9128,14 @@
         if ("EDIT_CUT_POINTS" == type)
             osd->DialogAddButton(QObject::tr("Cut List Options"),
                                  "DIALOG_CUTPOINT_CUTLISTOPTIONS_0", true);
+        if (ctx->player->DeleteMapHasUndo())
+            osd->DialogAddButton(QObject::tr("Undo") + " - " +
+                                 ctx->player->DeleteMapGetUndoMessage(),
+                                 QString("DIALOG_CUTPOINT_UNDO_0"));
+        if (ctx->player->DeleteMapHasRedo())
+            osd->DialogAddButton(QObject::tr("Redo") + " - " +
+                                 ctx->player->DeleteMapGetRedoMessage(),
+                                 QString("DIALOG_CUTPOINT_REDO_0"));
     }
     else if ("CUT_LIST_OPTIONS" == type)
     {
Index: libs/libmythtv/deletemap.cpp
===================================================================
--- libs/libmythtv/deletemap.cpp	(revision 27220)
+++ libs/libmythtv/deletemap.cpp	(working copy)
@@ -10,6 +10,61 @@
 #define EDIT_CHECK if(!m_editing) \
   { VERBOSE(VB_IMPORTANT, LOC_ERR + "Cannot edit outside editmode."); return; }
 
+DeleteMapUndoEntry::DeleteMapUndoEntry(frm_dir_map_t dm, QString msg) :
+    deleteMap(dm), message(msg) { }
+
+DeleteMapUndoEntry::DeleteMapUndoEntry(void)
+{
+    frm_dir_map_t dm;
+    deleteMap = dm;
+    message = "";
+}
+
+void DeleteMap::Push(QString undoMessage)
+{
+    DeleteMapUndoEntry entry(m_deleteMap, undoMessage);
+    // Remove all "redo" entries
+    while (m_undoStack.size() > m_undoStackPointer + 1)
+        m_undoStack.pop_back();
+    m_undoStack.append(entry);
+    m_undoStackPointer ++;
+    SaveMap(0, m_ctx, true);
+}
+
+bool DeleteMap::Undo(void)
+{
+    if (!HasUndo())
+        return false;
+    m_undoStackPointer --;
+    m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
+    m_changed = true;
+    SaveMap(0, m_ctx, true);
+    return true;
+}
+
+bool DeleteMap::Redo(void)
+{
+    if (!HasRedo())
+        return false;
+    m_undoStackPointer ++;
+    m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
+    m_changed = true;
+    SaveMap(0, m_ctx, true);
+    return true;
+}
+
+QString DeleteMap::GetUndoMessage(void)
+{
+    return (HasUndo() ? m_undoStack[m_undoStackPointer].message :
+            QObject::tr("(No more undo operations)"));
+}
+
+QString DeleteMap::GetRedoMessage(void)
+{
+    return (HasRedo() ? m_undoStack[m_undoStackPointer + 1].message :
+            QObject::tr("(No more redo operations)"));
+}
+
 bool DeleteMap::HandleAction(QString &action, uint64_t frame,
                              uint64_t played, uint64_t total, double rate)
 {
@@ -21,7 +76,7 @@
     else if (action == "DOWN")
         UpdateSeekAmount(-1, rate);
     else if (action == "CLEARMAP")
-        Clear();
+        Clear(QObject::tr("Clear Cut List"));
     else if (action == "INVERTMAP")
         ReverseAll(total);
     else if (action == "MOVEPREV")
@@ -29,13 +84,17 @@
     else if (action == "MOVENEXT")
         MoveRelative(frame, total, true);
     else if (action == "CUTTOBEGINNING")
-        Add(frame, total, MARK_CUT_END);
+        Add(frame, total, MARK_CUT_END, QObject::tr("Cut To Beginning"));
     else if (action == "CUTTOEND")
-        Add(frame, total, MARK_CUT_START);
+        Add(frame, total, MARK_CUT_START, QObject::tr("Cut To End"));
     else if (action == "NEWCUT")
         NewCut(frame, total);
     else if (action == "DELETE")
-        Delete(frame, total);
+        Delete(frame, total, QObject::tr("Delete Cut Area"));
+    else if (action == "UNDO")
+        Undo();
+    else if (action == "REDO")
+        Redo();
     else
         handled = false;
     return handled;
@@ -150,10 +209,12 @@
 }
 
 /// Clears the deleteMap.
-void DeleteMap::Clear(void)
+void DeleteMap::Clear(QString undoMessage)
 {
     m_deleteMap.clear();
     m_changed = true;
+    if (!undoMessage.isEmpty())
+        Push(undoMessage);
 }
 
 /// Reverses the direction of each mark in the map.
@@ -165,6 +226,7 @@
         Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START :
                                                    MARK_CUT_END);
     CleanMap(total);
+    Push(QObject::tr("Invert Cut List"));
 }
 
 /**
@@ -172,7 +234,8 @@
  *        existing redundant mark of that type is removed. This simplifies
  *        the cleanup code.
  */
-void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type)
+void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type,
+                    QString undoMessage)
 {
     EDIT_CHECK
     if ((MARK_CUT_START != type) && (MARK_CUT_END != type) &&
@@ -186,7 +249,7 @@
         {
             // Delete the temporary mark before putting a real mark at its
             // location
-            Delete(frame, total);
+            Delete(frame, total, "");
         }
         else // Don't add a mark on top of a mark
             return;
@@ -241,10 +304,12 @@
         Delete((uint64_t)remove);
     Add(frame, type);
     CleanMap(total);
+    if (!undoMessage.isEmpty())
+        Push(undoMessage);
 }
 
 /// Remove the mark at the given frame.
-void DeleteMap::Delete(uint64_t frame, uint64_t total)
+void DeleteMap::Delete(uint64_t frame, uint64_t total, QString undoMessage)
 {
     EDIT_CHECK
     if (m_deleteMap.isEmpty())
@@ -271,6 +336,8 @@
     if (prev != next)
         Delete(next);
     CleanMap(total);
+    if (!undoMessage.isEmpty())
+        Push(undoMessage);
 }
 
 /// Reverse the direction of the mark at the given frame.
@@ -278,7 +345,8 @@
 {
     EDIT_CHECK
     int type = Delete(frame);
-    Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END);
+    Add(frame, total, type == MARK_CUT_END ? MARK_CUT_START : MARK_CUT_END, "");
+    Push(QObject::tr("Reverse Mark Direction"));
 }
 
 /// Add a new cut marker (to start or end a cut region)
@@ -376,6 +444,7 @@
         Add(frame, MARK_PLACEHOLDER);
 
     CleanMap(total);
+    Push(QObject::tr("New Cut"));
 }
 
 /// Move the previous (!right) or next (right) cut to frame.
@@ -397,14 +466,14 @@
         {
             // If on a mark, don't collapse a cut region to 0;
             // instead, delete the region
-            Delete(frame, total);
+            Delete(frame, total, QObject::tr("Delete Cut Area"));
             return;
         }
         else if (MARK_PLACEHOLDER == type)
         {
             // Delete the temporary mark before putting a real mark at its
             // location
-            Delete(frame, total);
+            Delete(frame, total, "");
         }
     }
 
@@ -424,7 +493,7 @@
         else if (frame == total)
             type = MARK_CUT_END;
     }
-    Add(to, total, type);
+    Add(to, total, type, QObject::tr("Move Mark"));
 }
 
 /// Private addition to the deleteMap.
@@ -582,6 +651,7 @@
     Clear();
     m_deleteMap = map;
     m_deleteMap.detach();
+    Push(QObject::tr("Set New Cut List"));
 }
 
 /// Loads the given commercial break map into the deleteMap.
@@ -593,10 +663,11 @@
         Add(it.key(), it.value() == MARK_COMM_START ?
                 MARK_CUT_START : MARK_CUT_END);
     CleanMap(total);
+    Push(QObject::tr("Load Commskip List"));
 }
 
 /// Loads the delete map from the database.
-void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx)
+void DeleteMap::LoadMap(uint64_t total, PlayerContext *ctx, QString undoMessage)
 {
     if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
         return;
@@ -606,14 +677,39 @@
     ctx->playingInfo->QueryCutList(m_deleteMap);
     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
     CleanMap(total);
+    if (!undoMessage.isEmpty())
+        Push(undoMessage);
 }
 
+/// Returns true if an auto-save map was loaded.
+/// Does nothing and returns false if not.
+bool DeleteMap::LoadAutoSaveMap(uint64_t total, PlayerContext *ctx)
+{
+    if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
+        return false;
+
+    frm_dir_map_t tmpDeleteMap = m_deleteMap;
+    Clear();
+    ctx->LockPlayingInfo(__FILE__, __LINE__);
+    bool result = ctx->playingInfo->QueryCutList(m_deleteMap, true);
+    ctx->UnlockPlayingInfo(__FILE__, __LINE__);
+    CleanMap(total);
+    if (result)
+        Push(QObject::tr("Load Auto-Save Cut List"));
+    else
+        m_deleteMap = tmpDeleteMap;
+
+    return result;
+}
+
 /// Saves the delete map to the database.
-void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx)
+void DeleteMap::SaveMap(uint64_t total, PlayerContext *ctx, bool isAutoSave)
 {
     if (!ctx || !ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
         return;
 
+    if (!isAutoSave)
+    {
     // Remove temporary placeholder marks
     QMutableMapIterator<uint64_t, MarkTypes> it(m_deleteMap);
     while (it.hasNext())
@@ -627,9 +723,10 @@
     }
 
     CleanMap(total);
+    }
     ctx->LockPlayingInfo(__FILE__, __LINE__);
     ctx->playingInfo->SaveMarkupFlag(MARK_UPDATED_CUT);
-    ctx->playingInfo->SaveCutList(m_deleteMap);
+    ctx->playingInfo->SaveCutList(m_deleteMap, isAutoSave);
     ctx->UnlockPlayingInfo(__FILE__, __LINE__);
 }
 
Index: libs/libmythtv/mythplayer.cpp
===================================================================
--- libs/libmythtv/mythplayer.cpp	(revision 27220)
+++ libs/libmythtv/mythplayer.cpp	(working copy)
@@ -3496,6 +3496,7 @@
 void MythPlayer::SetPlayerInfo(TV *tv, QWidget *widget,
                                bool frame_exact_seek, PlayerContext *ctx)
 {
+    deleteMap.SetPlayerContext(ctx);
     m_tv = tv;
     parentWidget = widget;
     exactseeks   = frame_exact_seek;
@@ -3523,6 +3524,10 @@
     deleteMap.SetEditing(true);
     osd->DialogQuit();
     osd->HideAll();
+
+    bool loadedAutoSave = deleteMap.LoadAutoSaveMap(totalFrames, player_ctx);
+    (void)loadedAutoSave; // XXX - should we display an OSD message?
+
     deleteMap.UpdateSeekAmount(0, video_frame_rate);
     deleteMap.UpdateOSD(framesPlayed, totalFrames, video_frame_rate,
                         player_ctx, osd);
@@ -3540,10 +3545,10 @@
 {
     osdLock.lock();
     deleteMap.SetEditing(false, osd);
-    if (save)
-        deleteMap.SaveMap(totalFrames, player_ctx);
-    else
+    if (!save)
         deleteMap.LoadMap(totalFrames, player_ctx);
+    // Unconditionally save to remove temporary marks from the DB.
+    deleteMap.SaveMap(totalFrames, player_ctx);
     deleteMap.TrackerReset(framesPlayed, totalFrames);
     deleteMap.SetFileEditing(player_ctx, false);
     player_ctx->LockPlayingInfo(__FILE__, __LINE__);
@@ -3640,13 +3645,15 @@
         {
             if (IsInDelete(frame))
             {
-                deleteMap.Delete(frame, totalFrames);
+                deleteMap.Delete(frame, totalFrames,
+                                 QObject::tr("Delete cut area"));
                 refresh = true;
             }
         }
         else if (action == "REVERT")
         {
-            deleteMap.LoadMap(totalFrames, player_ctx);
+            deleteMap.LoadMap(totalFrames, player_ctx,
+                              QObject::tr("Load Cut List"));
             refresh = true;
         }
         else if (action == "REVERTEXIT")
Index: libs/libmyth/programtypes.cpp
===================================================================
--- libs/libmyth/programtypes.cpp	(revision 27220)
+++ libs/libmyth/programtypes.cpp	(working copy)
@@ -22,6 +22,8 @@
     switch (type)
     {
         case MARK_UNSET:        return "UNSET";
+        case MARK_TMP_CUT_END:  return "TMP_CUT_END";
+        case MARK_TMP_CUT_START:return "TMP_CUT_START";
         case MARK_UPDATED_CUT:  return "UPDATED_CUT";
         case MARK_PLACEHOLDER:  return "PLACEHOLDER";
         case MARK_CUT_END:      return "CUT_END";
Index: libs/libmyth/programinfo.h
===================================================================
--- libs/libmyth/programinfo.h	(revision 27220)
+++ libs/libmyth/programinfo.h	(working copy)
@@ -534,8 +534,8 @@
                            bool forceCheckLocal = false) const;
 
     // Edit flagging map
-    void QueryCutList(frm_dir_map_t &) const;
-    void SaveCutList(frm_dir_map_t &) const;
+    bool QueryCutList(frm_dir_map_t &, bool loadAutosave=false) const;
+    void SaveCutList(frm_dir_map_t &, bool isAutoSave=false) const;
 
     // Commercial flagging map
     void QueryCommBreakList(frm_dir_map_t &) const;
Index: libs/libmyth/programinfo.cpp
===================================================================
--- libs/libmyth/programinfo.cpp	(revision 27220)
+++ libs/libmyth/programinfo.cpp	(working copy)
@@ -2687,20 +2687,68 @@
     return kDisableAutoExpire;
 }
 
-void ProgramInfo::QueryCutList(frm_dir_map_t &delMap) const
+bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
 {
-    QueryMarkupMap(delMap, MARK_CUT_START);
-    QueryMarkupMap(delMap, MARK_CUT_END, true);
-    QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
+    frm_dir_map_t autosaveMap;
+    QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
+    QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
+    QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
+    bool result = !autosaveMap.isEmpty();
+
+    if (loadAutoSave)
+    {
+        // Convert the temporary marks into regular marks.
+        delMap.clear();
+        frm_dir_map_t::const_iterator i = autosaveMap.constBegin();
+        for (; i != autosaveMap.constEnd(); ++i)
+        {
+            uint64_t frame = i.key();
+            MarkTypes mark = i.value();
+            if (mark == MARK_TMP_CUT_START)
+                mark = MARK_CUT_START;
+            else if (mark == MARK_TMP_CUT_END)
+                mark = MARK_CUT_END;
+            delMap[frame] = mark;
+        }
+    }
+    else
+    {
+        QueryMarkupMap(delMap, MARK_CUT_START);
+        QueryMarkupMap(delMap, MARK_CUT_END, true);
+        QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
+    }
+
+    return result;
 }
 
-void ProgramInfo::SaveCutList(frm_dir_map_t &delMap) const
+void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
 {
-    ClearMarkupMap(MARK_CUT_START);
-    ClearMarkupMap(MARK_CUT_END);
+    if (!isAutoSave)
+    {
+        ClearMarkupMap(MARK_CUT_START);
+        ClearMarkupMap(MARK_CUT_END);
+    }
     ClearMarkupMap(MARK_PLACEHOLDER);
-    SaveMarkupMap(delMap);
+    ClearMarkupMap(MARK_TMP_CUT_START);
+    ClearMarkupMap(MARK_TMP_CUT_END);
 
+    frm_dir_map_t tmpDelMap;
+    frm_dir_map_t::const_iterator i = delMap.constBegin();
+    for (; i != delMap.constEnd(); ++i)
+    {
+        uint64_t frame = i.key();
+        MarkTypes mark = i.value();
+        if (isAutoSave)
+        {
+            if (mark == MARK_CUT_START)
+                mark = MARK_TMP_CUT_START;
+            else if (mark == MARK_CUT_END)
+                mark = MARK_TMP_CUT_END;
+        }
+        tmpDelMap[frame] = mark;
+    }
+    SaveMarkupMap(tmpDelMap);
+
     if (IsRecording())
     {
         MSqlQuery query(MSqlQuery::InitCon());
Index: libs/libmyth/programtypes.h
===================================================================
--- libs/libmyth/programtypes.h	(revision 27220)
+++ libs/libmyth/programtypes.h	(working copy)
@@ -40,6 +40,8 @@
 typedef enum {
     MARK_ALL           = -100,
     MARK_UNSET         = -10,
+    MARK_TMP_CUT_END   = -5,
+    MARK_TMP_CUT_START = -4,
     MARK_UPDATED_CUT   = -3,
     MARK_PLACEHOLDER   = -2,
     MARK_CUT_END       = 0,
