dvd-plugin: resume already whatched dvds

Message ID 423C3B5B.30506@errror.org
State New
Headers

Commit Message

Patrick Cernko March 19, 2005, 2:46 p.m. UTC
  hi list, hi dvd maintainers,

I created a patch to resume a dvd where it was interupted by the user.
It is not yet fully finalized:

The computation of the disc-id maybe still needs some work to avoid
resuming on the wrong resuming data, but I think it can be used already.
I currently takes the disc title, the number of titles on the disc and
the overall total number of chapters to form a uniq id.

Also what kind of data is stored and resumed might be worth a look.
Currently it resumed to the former title and time but the choosen audio
and subtitle stream (has anyone a dvd with differnent angles?) might be
useful, especially on multi-lingual dvds.

The patch is created against todays CVS. I don't know if it also applies
cleanly to the last release. Just give it a try!

Maybe the maintainers of the dvd plugin can integrate this functionality
into the CVS and so in further releases.

CU/all
--
Patrick Cernko | mailto:errror@errror.org | http://www.errror.org
dpkg: Warnung - altes pre-removal-Skript wurde beendet mit Fehler-Status 1
dpkg - probiere stattdessen Skript aus dem neuen Paket ...
dpkg: ... sieht so aus, als hätte das geklappt.
  

Patch

diff -Naur --exclude CVS dvd.orig/Makefile dvd/Makefile
--- dvd.orig/Makefile	2005-03-19 15:35:05.000000000 +0100
+++ dvd/Makefile	2005-03-18 17:53:03.000000000 +0100
@@ -65,6 +65,10 @@ 
 LDFLAGS  += -O3 -Wl,--retain-symbols-file,retain-sym
 endif

+ifdef RESUMEDIR
+  DEFINES += -DRESUMEDIR=\"$(RESUMEDIR)\"
+endif
+
 ### The object files (add further files here):

 OBJS = $(PLUGIN).o dvddev.o player-dvd.o control-dvd.o dvdspu.o     \
diff -Naur --exclude CVS dvd.orig/player-dvd.c dvd/player-dvd.c
--- dvd.orig/player-dvd.c	2005-03-19 15:35:05.000000000 +0100
+++ dvd/player-dvd.c	2005-03-19 15:32:27.000000000 +0100
@@ -21,6 +21,8 @@ 
 #include <vdr/thread.h>
 #include <vdr/device.h>
 #include <vdr/plugin.h>
+// for VideoDirectory variable
+#include <vdr/videodir.h>

 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -231,6 +233,216 @@ 
 }


+// --- cResumeEntry ------------------------------------------------------------
+
+// borrowed from the mplayer plugin code and adapted to the dvd resume requirements
+class cResumeEntry : public cListObject {
+public:
+  char *key;
+  int title;
+  int chapter;
+  int64_t second;
+  //
+  cResumeEntry(void);
+  ~cResumeEntry();
+  };
+
+cResumeEntry::cResumeEntry(void)
+{
+  key=0;
+}
+
+cResumeEntry::~cResumeEntry()
+{
+  free(key);
+}
+
+// --- cDVDPlayerResume ----------------------------------------------------------
+
+// store resume database to this file ...
+#define RESUME_FILE "dvdplayer.resume"
+
+// ... in this directory (default: /video)
+#ifndef RESUMEDIR
+#define RESUMEDIR VideoDirectory
+#endif
+
+
+// borrowed from the mplayer plugin code and adapted to the dvd resume requirements
+class cDVDPlayerResume : public cList<cResumeEntry> {
+private:
+  char* resfile; // the full pathname of resume file
+  bool modified; // flag to indicate that memory database was modified and needs to be saved
+  /**
+   * LoadResume():
+   * reads in the resume database file from resfile.
+   */
+  void LoadResume();
+  /**
+   * SaveResume():
+   * saves the resume database to the file resfile.
+   * returns true on successful save.
+   */
+  bool SaveResume(void);
+  /**
+   * search the (loaded) resume database for the given key.
+   * returns the cResumeEntry* if the key was found
+   * or NULL if no resume entry was found for the given key.
+   */
+  cResumeEntry *FindResume(const char* key);
+public:
+  cDVDPlayerResume(void);
+  ~cDVDPlayerResume();
+  /**
+   * SetResume():
+   * set the given resume values for the given key into the resume database.
+   * the resume database is loaded from file if not yet loaded.
+   */
+  void SetResume(const char* key, int title, int chapter, int64_t second);
+  /**
+   * GetResume():
+   * tries looking up the given key into the resume database.
+   * the resume database is loaded from file if not yet loaded.
+   * returns true if resume data could be found. In this case
+   * the givven arguments are filled with the resume data. Otherwise
+   * the arguments are not modified!
+   */
+  bool GetResume(const char* key, int& title, int& chapter, int64_t& second);
+  };
+
+cDVDPlayerResume::cDVDPlayerResume(void)
+{
+  // initialize the resume filename string.
+  asprintf(&resfile, "%s/%s", RESUMEDIR, RESUME_FILE);
+}
+
+cDVDPlayerResume::~cDVDPlayerResume()
+{
+  // save resume data to disc before self-destruction.
+  SaveResume();
+  // free the resume filename string, allocated in C'tor by asprintf
+  free(resfile);
+}
+
+void cDVDPlayerResume::SetResume(const char* key, int title, int chapter, int64_t second)
+{
+  // (re)load resume data from file to be actual
+  LoadResume();
+  cResumeEntry* re = FindResume(key);
+  if (re) {
+    // found a resume entry, so we can update it.
+    DEBUGDVD("resume: setting resume %d:%d:%lld (update)", title, chapter, second);
+  } else {
+    // no resume entry found yet, creating a new one
+    re = new cResumeEntry;
+    re->key = strdup(key);
+    Add(re);
+    DEBUGDVD("resume: setting resume %d:%d:%lld (new)", title, chapter, second);
+  }
+  // set the new resume data for the found/created entry
+  re->title = title;
+  re->chapter = chapter;
+  re->second = second;
+  // and mark memory database as modified to be saved.
+  modified = true;
+  // save it now (sync!)
+  SaveResume();
+}
+
+bool cDVDPlayerResume::GetResume(const char* key, int& title, int& chapter, int64_t& second)
+{
+  // (re)load the resume file to have actual values
+  LoadResume();
+  cResumeEntry* re = FindResume(key);
+  if (re) {
+    // found a resume entry, copy values
+    title = re->title;
+    chapter = re->chapter;
+    second = re->second;
+    // indicate successful search
+    return true;
+  }
+  // no resume entry found in database
+  return false;
+}
+
+void cDVDPlayerResume::LoadResume()
+{
+  // we will load the file for sure and add all entries, clear all old entries.
+  Clear();
+  // no entries == no modifications
+  modified = false;
+  DEBUGDVD("resume: resume file is \"%s\"\n",resfile);
+  FILE *f = fopen(resfile,"r");
+  if (f) {
+    DEBUGDVD("resume: successfully opened resume file\n");
+    char line[768];
+    // read file line by line
+    while(fgets(line,sizeof(line),f)) {
+      char key[512];
+      int t, c;
+      int64_t s;
+      // parse line as "title:chapter:second:key"
+      if(sscanf(line,"%d:%d:%lld:%511[^\n]",&t,&c,&s,key) == 4) {
+        // successful parse, save in resume entry
+        cResumeEntry *re = new cResumeEntry;
+        re->key = strdup(key);
+        re->title = t;
+        re->chapter = c;
+        re->second = s;
+        // and add it to memory database
+        Add(re);
+      }
+    }
+    // don't forget to close what you have opened!
+    fclose(f);
+  }
+  // unsuccessful open leads to empty database as the file does not exists
+}
+
+bool cDVDPlayerResume::SaveResume(void)
+{
+  if(modified) {
+    // modification indicated, save the database to the resume file
+    DEBUGDVD("resume: saving resume file\n");
+    cSafeFile f(resfile);
+    if(f.Open()) {
+      // forall resume entries in the memory database
+      for (cResumeEntry *re=First(); re; re=Next(re)) {
+        // save the as one line in the format "title:chapter:second:key"
+        fprintf(f, "%d:%d:%lld:%s\n", re->title, re->chapter, re->second, re->key);
+      }
+      // don't forget to close what you have opened!
+      f.Close();
+      // signal successful save
+      return true;
+    } else {
+      DEBUGDVD("resume: failed to save resume file\n");
+      // saving did not succeed!!!!
+      return false;
+    }
+  } else {
+    // no modifications -> successful "save" :-)
+    return true;
+  }
+}
+
+cResumeEntry *cDVDPlayerResume::FindResume(const char* key)
+{
+  DEBUGDVD("resume: searching resume  position for \"%s\"\n", key);
+  // iterate over all entries in the memory database
+  for(cResumeEntry *re=First(); re; re=Next(re)) {
+    if (!strcasecmp(re->key, key)) {
+      // return the entry iff the keys match
+      DEBUGDVD("resume: found resume position %d:%d:%lld\n",re->title, re->chapter, re->second);
+      return re;
+    }
+  }
+  DEBUGDVD("resume: no resume position found\n");
+  return NULL;
+}
+
+
 // --- cDvdPlayer ------------------------------------------------------------

 //XXX+ also used in recorder.c - find a better place???
@@ -287,6 +499,9 @@ 
     skipPlayVideo=false;
     fastWindFactor=1;

+    // resume
+    resume = new cDVDPlayerResume;
+
     clearSeenSubpStream();
     clearSeenAudioTrack();

@@ -329,6 +544,7 @@ 
     if(titleinfo_str) { free(titleinfo_str); titleinfo_str=0; }
     if(title_str) { free(title_str); title_str=0; }
     if(aspect_str) { free(aspect_str); aspect_str=0; }
+    delete resume;
 }

 void cDvdPlayer::setController (cDvdPlayerControl *ctrl )
@@ -562,6 +778,100 @@ 
 #endif
 }

+char* cDvdPlayer::GetDVDResumeKey() const {
+  // first we fetch the total number of titles of the current dvd
+  int totalTitles;
+  if (dvdnav_get_number_of_titles(nav, &totalTitles)) {
+    // then we sum up the numbers of chapters for each title
+    int totalChapters = 0;
+    for (int t = 1; t <= totalTitles; t++) {
+      int curChapters;
+      dvdnav_get_number_of_parts(nav, t, &curChapters);
+      totalChapters += curChapters;
+      DEBUGDVD("resume: cDvdPlayer::Action() Title %d has %d chapters.\n", t, curChapters);
+    }
+    DEBUGDVD("resume: cDvdPlayer::Action() Titles: %d with %d chapters all together, Title: \"%s\"\n",
+             totalTitles, totalChapters, title_str);
+    // finally the key is build as "DVDName_TotalTitles_OverallChapters"
+    char* key;
+    asprintf(&key, "%s_%d_%d", title_str, totalTitles, totalChapters);
+    // note: this is not completly unique. Maybe some other informations are more suitable, like:
+    // - the "serial number" of the dvd as displayed in the libdvdnav debug output, but:
+    //   it is not available through the current libdvdnav api
+    // - the total bytes of the dvd (quiet unique!!!), but:
+    //   also not available through the libdvdnav api and no idea how to get it for a media not mounted.
+    // - any other ideas???
+    return key;
+  } else {
+    // if we cannot fetch the total number of titles of the current disc, there must be something wrong!
+    // Who needs a key for resuming then?
+    return NULL;
+  }
+}
+
+void cDvdPlayer::SaveResume() {
+  // make sure resume database is allocated (might be a possibility to completly disable resuming!)
+  if (resume) {
+    // fetch the current title and chapter number via libdvdnav api
+    int currentTitle, currentChapter;
+    if (dvdnav_current_title_info(nav, &currentTitle, &currentChapter) &&
+        (0 != currentTitle)) {
+      // fetch current time position through own class api
+      int64_t currentSec, totalSec;
+      GetPositionInSec(currentSec, totalSec);
+      // compute the resume key for the current dvd
+      char* key = GetDVDResumeKey();
+      if (key) {
+        // store computed/fetched resume data in database
+        DEBUGDVD("resume->SetResume(\"%s\", %d, %d, %lld)\n", key, currentTitle, currentChapter, currentSec);
+        resume->SetResume(key, currentTitle, currentChapter, currentSec);
+        // free the key string memory allocated by GetDVDResumeKey()
+        free(key);
+      } else {
+        DEBUGDVD("resume: ERROR computing resume key for this dvd!\n");
+      }
+    } else {
+      // in a menu title and chapter seams to be always 0 -> no way to resume there!
+      DEBUGDVD("resume: ERROR fetching current title and chapter (maybe in menus?).\n");
+    }
+  }
+}
+
+bool cDvdPlayer::LoadResume(int& title, int& chapter, int64_t& second) {
+  // helper variable for the return value
+  bool retval = false;
+  // make sure resume database is allocated (might be a possibility to completly disable resuming!)
+  if(resume) {
+    // compute the resume key for the current dvd
+    char* key = GetDVDResumeKey();
+    if (key) {
+      DEBUGDVD("resume->GetResume(\"%s\", ...): ", key);
+      // try loading the resume data for the computed key into the given arguments
+      if (resume->GetResume(key, title, chapter, second)) {
+        DEBUGDVD("%d:%d:%lld\n", title, chapter, second);
+        // continuing at the very same position might be inappropriate (vdr's recordings also rewind some seconds)
+        int ResumeRewind = 30; // rewind 30s if possible
+        // note: I used a variable here to show up, that this value might be made
+        //       possible to configure (in the setup dialog). Doing so myself was
+        //       not yet nesseccary and is so left to the plugin maintainers.
+        // make sure we do not rewind before the beginning
+        if (second > ResumeRewind) {
+          second -= ResumeRewind;
+        }
+        retval = true;
+      } else {
+        DEBUGDVD("<none>\n");
+        retval = false;
+      }
+      // free the key string memory allocated by GetDVDResumeKey()
+      free(key);
+    } else {
+      DEBUGDVD("resume: ERROR computing resume key for this dvd.\n");
+    }
+  }
+  return retval;
+}
+
 void cDvdPlayer::Action(void) {
     memset(event_buf, 0, sizeof(uint8_t)*4096);

@@ -631,6 +941,13 @@ 

     bool firstClear = true;

+    // we need to know the very first VTS change to hook inthe resume call
+    bool first_vts_change = true;
+    // we cannot directly resume to the exact time, so we hook on the next cell change when resuming
+    bool next_cell_change = false;
+    // and seek the the exact time stored here
+    int64_t resSecond = 0;
+
     while( running && nav ) {

         if (!pframe) {
@@ -1107,6 +1424,22 @@ 
 	      SetTitleInfoString();
 	      SetTitleString();
 	      SetAspectString();
+              if (first_vts_change) {
+                first_vts_change = false;
+
+                // now all data for computing the resume key is available, so trying to resume
+                int resTitle, resChapter;
+                if (LoadResume(resTitle, resChapter, resSecond)) {
+                  // if resume data could be found seek to the found title and chapter NOW
+                  GotoTitle(resTitle, resChapter);
+                  // and wait for the next cell change (= title and chapter reached)
+                  // to seek to the exact time
+                  next_cell_change = true;
+                  // note: seeking to the exact time HERE leads to an error on the libdvdnav console:
+                  //       "dvd error dvdnav_sector_search: New position not yet determined." and is
+                  //       slightly ignored :-( .
+                }
+              }
 	      break;
 	  case DVDNAV_CELL_CHANGE: {
 	      DEBUG_NAV("%s:%d:NAV CELL CHANGE\n", __FILE__, __LINE__);
@@ -1127,6 +1460,11 @@ 
 	      // cell change .. game over ..
 	      changeNavSubpStreamOnceInSameCell=false;
     	      SetTitleInfoString();
+              if (next_cell_change) {
+                next_cell_change = false;
+                // we are resuming the current dvd. NOW its time to seek to the correct second.
+                Goto(resSecond);
+              }
 	      break;
 	  }
 	  case DVDNAV_NAV_PACKET: {
@@ -1853,7 +2191,16 @@ 
     if(!DVDActiveAndRunning()) return;

     if( running && nav ) {
-	    dvdnav_stop(nav);
+      // we will stop replay now. Its time to save the current possition
+      // for later resuming.
+      SaveResume();
+
+      dvdnav_stop(nav);
+
+      // don't know why Stop() is called twice, but this prevents from
+      // twice save resume data and calling dvdnav_stop() twice.
+      // Comments from maintainers are welcome.
+      running = false;
     }
 }

@@ -2163,22 +2510,36 @@ 
       (void) GotoAngle(++cur_angle);
 }

-int cDvdPlayer::GotoTitle(int title)
+// GotoTitle now optionally takes a chapter to seek to in the given title.
+int cDvdPlayer::GotoTitle(int title, int chapter /*= 1*/)
 {
       int titleNumber;
+      int targetTitle = title;
+      int chapterNumber;
       if(!DVDActiveAndRunning()) return -1;
       LOCK_THREAD;
       DEBUG_NAV("DVD NAV SPU clear & empty %s:%d\n", __FILE__, __LINE__);
       Empty();

+      // check if the given title is in the title range of this dvd
       dvdnav_get_number_of_titles(nav, &titleNumber);
-
-      if(title>titleNumber) title=1;
-      if(title<=0) title=titleNumber;
+      if(title>titleNumber) targetTitle=1;
+      if(title<=0) targetTitle=titleNumber;
+
+      // if given title is in the bounds of this dvd's title range
+      if (title == targetTitle) {
+        // check if the chapter is in the title's chapter range
+        dvdnav_get_number_of_parts(nav, title, &chapterNumber);
+        if (chapter>chapterNumber) chapter=1;
+        if (chapter<=0) chapter=chapterNumber;
+      } else {
+        // otherwise reset it to the first chapter.
+        chapter = 1;
+      }

       if (stillTimer == 0)
       {
-	      dvdnav_part_play(nav, title, 1);
+	      dvdnav_part_play(nav, title, chapter);
 	      // dvdnav_title_play(nav, title);
       }

diff -Naur --exclude CVS dvd.orig/player-dvd.h dvd/player-dvd.h
--- dvd.orig/player-dvd.h	2005-03-19 15:35:05.000000000 +0100
+++ dvd/player-dvd.h	2005-03-19 15:15:50.000000000 +0100
@@ -52,6 +52,7 @@ 

 class cDvdPlayerControl ;
 class cIframeAssembler;
+class cDVDPlayerResume;

 class cDvdPlayer : public cPlayer, cThread {
  private:
@@ -183,6 +184,31 @@ 
     int  playPacket(unsigned char *&cache_buf, bool trickMode, bool noAudio);
     void playSPU(int spuId, unsigned char *data, int datalen);

+    //resuming
+    /**
+     * the resume database
+     */
+    cDVDPlayerResume* resume;
+    /**
+     * GetDVDResumeKey():
+     * computes a (hopefully) unique id for storing the resume data of the current disc.
+     *
+     * this get returns a new allocated memory area ..
+     * must be freed by callee ..
+     */
+    char* GetDVDResumeKey() const;
+    /**
+     * SaveResume():
+     * handles everything to save the current position on the disc for later resuming.
+     */
+    void SaveResume();
+    /**
+     * LoadResume():
+     * loads the resume data for the current disc and stores it in the given arguments.
+     * returns false if no resume data for the disc can be found or and error occured while loading.
+     */
+    bool LoadResume(int& title, int& chapter, int64_t& second);
+
 protected: //Player
     virtual void Activate(bool On);
     virtual void Action(void);
@@ -313,7 +339,8 @@ 
      *
      * return set title ..
      */
-    int GotoTitle(int title);
+    // GotoTitle now optionally takes a chapter to seek to in the given title.
+    int GotoTitle(int title, int chapter = 1);

     /**
      * jump to the previous Title (rotate)