]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
DVR: add possibility to re-record broken recordings
authorJaroslav Kysela <perex@perex.cz>
Wed, 21 Oct 2015 14:06:10 +0000 (16:06 +0200)
committerJaroslav Kysela <perex@perex.cz>
Wed, 21 Oct 2015 14:17:52 +0000 (16:17 +0200)
src/dvr/dvr.h
src/dvr/dvr_config.c
src/dvr/dvr_db.c
src/htsp_server.c
src/streaming.c
src/tvheadend.h

index 966b0be1bdd03e826ae72cd0be36dfe74d08f90c..af047b6b3cb6a3522f9441cca0f6926510a6c6d6 100644 (file)
@@ -40,6 +40,7 @@ typedef struct dvr_config {
   profile_t *dvr_profile;
   char *dvr_storage;
   int dvr_clone;
+  uint32_t dvr_rerecord_errors;
   uint32_t dvr_retention_days;
   uint32_t dvr_removal_days;
   char *dvr_charset;
@@ -211,6 +212,12 @@ typedef struct dvr_entry {
    */
   struct dvr_timerec_entry *de_timerec;
 
+  /**
+   * Parent/slave
+   */
+  struct dvr_entry *de_parent;
+  struct dvr_entry *de_slave;
+
   /**
    * Fields for recording
    */
@@ -410,6 +417,8 @@ uint32_t dvr_entry_get_retention_days( dvr_entry_t *de );
 
 uint32_t dvr_entry_get_removal_days( dvr_entry_t *de );
 
+uint32_t dvr_entry_get_rerecord_errors( dvr_entry_t *de );
+
 int dvr_entry_get_start_time( dvr_entry_t *de );
 
 int dvr_entry_get_stop_time( dvr_entry_t *de );
index d74c5a7f57883dec8305dbd4ccfabfc4b8ece9aa..9e5222b65173042a11edddab46fecbe652af54b7 100644 (file)
@@ -830,6 +830,13 @@ const idclass_t dvr_config_class = {
       .def.u32  = 1,
       .group    = 1,
     },
+    {
+      .type     = PT_U32,
+      .id       = "rerecord-errors",
+      .name     = N_("Re-record When Errors (0=off)"),
+      .off      = offsetof(dvr_config_t, dvr_rerecord_errors),
+      .group    = 1,
+    },
     {
       .type     = PT_U32,
       .id       = "pre-extra-time",
index a3647bd97e6d33f9d6beda1114a1000b8c6f8985..64e1c76b5f5e7f5155c3a14f12e2425c8303f170 100644 (file)
@@ -41,11 +41,13 @@ static gtimer_t dvr_dbus_timer;
 #endif
 
 static void dvr_entry_destroy(dvr_entry_t *de, int delconf);
+static void dvr_timer_rerecord(void *aux);
 static void dvr_timer_expire(void *aux);
 static void dvr_timer_remove_files(void *aux);
 static void dvr_entry_start_recording(dvr_entry_t *de, int clone);
 static void dvr_timer_start_recording(void *aux);
 static void dvr_timer_stop_recording(void *aux);
+static void dvr_entry_rerecord(dvr_entry_t *de);
 
 /*
  *
@@ -196,6 +198,12 @@ dvr_entry_get_removal_days ( dvr_entry_t *de )
   return dvr_retention_cleanup(de->de_config->dvr_removal_days);
 }
 
+uint32_t
+dvr_entry_get_rerecord_errors( dvr_entry_t *de )
+{
+  return de->de_config->dvr_rerecord_errors;
+}
+
 /*
  * DBUS next dvr start notifications
  */
@@ -234,6 +242,20 @@ dvr_dbus_timer_cb( void *aux )
 }
 #endif
 
+/*
+ *
+ */
+static void
+dvr_entry_retention_arm(dvr_entry_t *de, gti_callback_t *cb, time_t when)
+{
+  uint32_t rerecord = dvr_entry_get_rerecord_errors(de);
+  if (rerecord && (when - dispatch_clock) > 3600) {
+    when = dispatch_clock + 3600;
+    cb = dvr_timer_rerecord;
+  }
+  gtimer_arm_abs(&de->de_timer, cb, de, when);
+}
+
 /*
  *
  */
@@ -248,14 +270,14 @@ dvr_entry_retention_timer(dvr_entry_t *de)
   stop = de->de_stop + removal * (time_t)86400;
   if (removal > 0 || retention == 0) {
     if (stop > dispatch_clock) {
-      gtimer_arm_abs(&de->de_timer, dvr_timer_remove_files, de, stop);
+      dvr_entry_retention_arm(de, dvr_timer_remove_files, stop);
       return;
     }
     if (dvr_get_filename(de))
       dvr_entry_delete(de, 1);
   }
   stop = de->de_stop + retention * (time_t)86400;
-  gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de, stop);
+  dvr_entry_retention_arm(de, dvr_timer_expire, stop);
 }
 
 /*
@@ -413,6 +435,8 @@ dvr_entry_set_timer(dvr_entry_t *de)
     else
       dvr_entry_completed(de, de->de_last_error);
 
+    dvr_entry_rerecord(de);
+
   } else if (de->de_sched_state == DVR_RECORDING)  {
 
     gtimer_arm_abs(&de->de_timer, dvr_timer_stop_recording, de, stop);
@@ -755,6 +779,84 @@ dvr_entry_clone(dvr_entry_t *de)
   return n == NULL ? de : n;
 }
 
+/**
+ *
+ */
+static void
+dvr_entry_rerecord(dvr_entry_t *de)
+{
+  uint32_t rerecord = dvr_entry_get_rerecord_errors(de);
+  epg_broadcast_t *e, *ev;
+  dvr_entry_t *de2;
+  char cfg_uuid[UUID_HEX_SIZE];
+  int64_t fsize1, fsize2;
+
+  if (rerecord == 0)
+    return;
+  if (de->de_parent) {
+    if (de->de_sched_state == DVR_COMPLETED &&
+        de->de_errors == 0 &&
+        de->de_data_errors < de->de_parent->de_data_errors) {
+      fsize1 = dvr_get_filesize(de);
+      fsize2 = dvr_get_filesize(de->de_parent);
+      if (fsize1 / 5 < fsize2 / 6) {
+        goto not_so_good;
+      } else {
+        dvr_entry_cancel_delete(de->de_parent);
+      }
+    } else if (de->de_sched_state == DVR_COMPLETED) {
+not_so_good:
+      de->de_retention = 1;
+      de->de_parent->de_slave = NULL;
+      de->de_parent = NULL;
+      dvr_entry_completed(de, SM_CODE_WEAK_STREAM);
+      return;
+    }
+  }
+  if (de->de_slave)
+    return;
+  if (de->de_enabled == 0)
+    return;
+  if (de->de_channel == NULL)
+    return;
+  if (de->de_sched_state == DVR_SCHEDULED ||
+      de->de_sched_state == DVR_RECORDING)
+    return;
+  /* rerecord if - DVR_NOSTATE, DVR_MISSED_TIME */
+  if (de->de_sched_state == DVR_COMPLETED &&
+      rerecord < de->de_data_errors && de->de_errors <= 0)
+    return;
+
+  e = NULL;
+  RB_FOREACH(ev, &de->de_channel->ch_epg_schedule, sched_link) {
+    if (dvr_entry_fuzzy_match(de, ev))
+      if (!e || e->start > ev->start)
+        e = ev;
+  }
+
+  if (e == NULL)
+    return;
+
+  tvhtrace("dvr", "  rerecord event %s on %s @ %"PRItime_t
+                   " to %"PRItime_t,
+                   epg_broadcast_get_title(e, NULL),
+                   channel_get_name(e->channel),
+                   e->start, e->stop);
+
+  idnode_uuid_as_str(&de->de_config->dvr_id, cfg_uuid);
+  de2 = dvr_entry_create_by_event(1, cfg_uuid, e,
+                                  de->de_start_extra, de->de_stop_extra,
+                                  de->de_owner, de->de_creator, NULL,
+                                  de->de_pri, de->de_retention, de->de_removal,
+                                  de->de_comment);
+  if (de2) {
+    de->de_slave = de2;
+    de2->de_parent = de;
+    dvr_entry_save(de);
+    dvr_entry_save(de2);
+  }
+}
+
 /**
  *
  */
@@ -940,6 +1042,11 @@ dvr_entry_destroy(dvr_entry_t *de, int delconf)
   LIST_REMOVE(de, de_global_link);
   de->de_channel = NULL;
 
+  if (de->de_parent)
+    de->de_parent->de_slave = NULL;
+  if (de->de_slave)
+    de->de_slave->de_parent = NULL;
+
   dvr_entry_dec_ref(de);
 }
 
@@ -982,6 +1089,18 @@ dvr_entry_save(dvr_entry_t *de)
 }
 
 
+/**
+ *
+ */
+static void
+dvr_timer_rerecord(void *aux)
+{
+  dvr_entry_t *de = aux;
+  dvr_entry_rerecord(de);
+  dvr_entry_retention_timer(de);
+}
+
+
 /**
  *
  */
@@ -1869,6 +1988,72 @@ dvr_entry_class_timerec_caption_get(void *o)
   return &ret;
 }
 
+static int
+dvr_entry_class_parent_set(void *o, const void *v)
+{
+  dvr_entry_t *de = (dvr_entry_t *)o, *de2;
+  if (!dvr_entry_is_editable(de))
+    return 0;
+  de2 = v ? dvr_entry_find_by_uuid(v) : NULL;
+  if (de2 == NULL) {
+    if (de->de_parent) {
+      de->de_parent->de_slave = NULL;
+      de->de_parent = NULL;
+      return 1;
+    }
+  } else if (de->de_parent != de2) {
+    de->de_parent = de2;
+    de2->de_slave = de;
+    return 1;
+  }
+  return 0;
+}
+
+static const void *
+dvr_entry_class_parent_get(void *o)
+{
+  static const char *ret;
+  dvr_entry_t *de = (dvr_entry_t *)o;
+  if (de->de_parent)
+    ret = idnode_uuid_as_sstr(&de->de_parent->de_id);
+  else
+    ret = "";
+  return &ret;
+}
+
+static int
+dvr_entry_class_slave_set(void *o, const void *v)
+{
+  dvr_entry_t *de = (dvr_entry_t *)o, *de2;
+  if (!dvr_entry_is_editable(de))
+    return 0;
+  de2 = v ? dvr_entry_find_by_uuid(v) : NULL;
+  if (de2 == NULL) {
+    if (de->de_slave) {
+      de->de_slave->de_parent = NULL;
+      de->de_slave = NULL;
+      return 1;
+    }
+  } else if (de->de_slave != de2) {
+    de->de_slave = de2;
+    de2->de_parent = de;
+    return 1;
+  }
+  return 0;
+}
+
+static const void *
+dvr_entry_class_slave_get(void *o)
+{
+  static const char *ret;
+  dvr_entry_t *de = (dvr_entry_t *)o;
+  if (de->de_parent)
+    ret = idnode_uuid_as_sstr(&de->de_slave->de_id);
+  else
+    ret = "";
+  return &ret;
+}
+
 static int
 dvr_entry_class_broadcast_set(void *o, const void *v)
 {
@@ -2410,6 +2595,22 @@ const idclass_t dvr_entry_class = {
       .get      = dvr_entry_class_timerec_caption_get,
       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN,
     },
+    {
+      .type     = PT_STR,
+      .id       = "parent",
+      .name     = N_("Parent Entry"),
+      .set      = dvr_entry_class_parent_set,
+      .get      = dvr_entry_class_parent_get,
+      .opts     = PO_RDONLY,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "slave",
+      .name     = N_("Slave Entry"),
+      .set      = dvr_entry_class_slave_set,
+      .get      = dvr_entry_class_slave_get,
+      .opts     = PO_RDONLY,
+    },
     {
       .type     = PT_U32,
       .id       = "content_type",
@@ -2555,7 +2756,6 @@ dvr_val2pri(dvr_prio_t v)
   return val2str(v, priotab) ?: "invalid";
 }
 
-
 /**
  *
  */
index 99cbd9cc9a33e69f2cac697af0088d810e7352b1..efe3bfb0a6b3817434b7bc17e2df3c41f599d666 100644 (file)
@@ -3692,6 +3692,8 @@ _htsp_get_subscription_status(int smcode)
     return "userAccess";
   case SM_CODE_USER_LIMIT:
     return "userLimit";
+  case SM_CODE_WEAK_STREAM:
+    return "weakStream";
   default:
     return streaming_code2txt(smcode);
   }
index b0b53f053a99fc7e229ba348d7b80b464687178b..6b86640adfb79cf44bc878b181310c1ea1ff2aa8 100644 (file)
@@ -422,6 +422,8 @@ streaming_code2txt(int code)
     return N_("User access error");
   case SM_CODE_USER_LIMIT:
     return N_("User limit reached");
+  case SM_CODE_WEAK_STREAM:
+    return N_("Weak stream");
 
   case SM_CODE_NO_FREE_ADAPTER:
     return N_("No free adapter");
index c1bbf26b01346e4686ac303b3ea941fe93ff9bff..5f2dbee94616ce772714c09c0be87352b1ef12fd 100644 (file)
@@ -442,6 +442,7 @@ typedef enum {
 #define SM_CODE_INVALID_TARGET            104
 #define SM_CODE_USER_ACCESS               105
 #define SM_CODE_USER_LIMIT                106
+#define SM_CODE_WEAK_STREAM               107
 
 #define SM_CODE_NO_FREE_ADAPTER           200
 #define SM_CODE_MUX_NOT_ENABLED           201