]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
- better duplicate handling - better subtitle handling
authordero <de@ro>
Thu, 5 Mar 2015 20:13:51 +0000 (21:13 +0100)
committerJaroslav Kysela <perex@perex.cz>
Mon, 9 Mar 2015 15:18:35 +0000 (16:18 +0100)
src/dvr/dvr.h
src/dvr/dvr_autorec.c
src/dvr/dvr_db.c
src/dvr/dvr_timerec.c
src/epg.c
src/epg.h
src/htsp_server.c
src/main.c
src/webui/static/app/dvr.js

index 85b30c72c8eec0b41a0b8cb778d16798621f074b..dccf590d622a4ecc2944c259da3040d917006688 100644 (file)
@@ -163,6 +163,7 @@ typedef struct dvr_entry {
   char *de_directory; /* Can be set for autorec entries, will override any 
                          directory setting from the configuration */
   lang_str_t *de_title;      /* Title in UTF-8 (from EPG) */
+  lang_str_t *de_subtitle;   /* Subtitle in UTF-8 (from EPG) */
   lang_str_t *de_desc;       /* Description in UTF-8 (from EPG) */
   uint32_t de_content_type;  /* Content type (from EPG) (only code) */
 
@@ -244,6 +245,16 @@ typedef struct dvr_entry {
 
 #define DVR_CH_NAME(e) ((e)->de_channel == NULL ? (e)->de_channel_name : channel_get_name((e)->de_channel))
 
+typedef enum {
+  DVR_AUTOREC_RECORD_ALL = 0,
+  DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER = 1,
+  DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE = 2,
+  DVR_AUTOREC_RECORD_DIFFERENT_DESCRIPTION = 3,
+  DVR_AUTOREC_RECORD_ONCE_PER_WEEK = 4,
+  DVR_AUTOREC_RECORD_ONCE_PER_DAY = 5
+} dvr_autorec_dedup_t;
+
+
 /**
  * Autorec entry
  */
@@ -291,9 +302,12 @@ typedef struct dvr_autorec_entry {
   int dae_minduration;
   int dae_maxduration;
   int dae_retention;
-
+  
   time_t dae_start_extra;
   time_t dae_stop_extra;
+  
+  int dae_record;
+  
 } dvr_autorec_entry_t;
 
 TAILQ_HEAD(dvr_autorec_entry_queue, dvr_autorec_entry);
@@ -432,7 +446,7 @@ dvr_entry_t *
 dvr_entry_create_htsp( const char *dvr_config_uuid,
                        channel_t *ch, time_t start, time_t stop,
                        time_t start_extra, time_t stop_extra,
-                       const char *title, const char *description,
+                       const char *title, const char* subtitle, const char *description,
                        const char *lang, epg_genre_t *content_type,
                        const char *owner, const char *creator,
                        dvr_autorec_entry_t *dae,
@@ -441,7 +455,7 @@ dvr_entry_create_htsp( const char *dvr_config_uuid,
 
 dvr_entry_t *
 dvr_entry_update( dvr_entry_t *de,
-                  const char* de_title, const char *de_desc, const char *lang,
+                  const char* de_title, const char* de_subtitle, const char *de_desc, const char *lang,
                   time_t de_start, time_t de_stop,
                   time_t de_start_extra, time_t de_stop_extra,
                   dvr_prio_t pri, int retention );
@@ -506,7 +520,7 @@ dvr_entry_t *
 dvr_entry_create_(const char *config_uuid, epg_broadcast_t *e,
                   channel_t *ch, time_t start, time_t stop,
                   time_t start_extra, time_t stop_extra,
-                  const char *title, const char *description,
+                  const char *title, const char* subtitle, const char *description,
                   const char *lang, epg_genre_t *content_type,
                   const char *owner, const char *creator,
                   dvr_autorec_entry_t *dae, dvr_timerec_entry_t *tae,
index e9fddbf1b49cbb2340d428f12e6ca7731f9067ce..a5d1f4d5820494b67a5e29e8a4cc4204aa081ded 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  tvheadend, Automatic recordings
- *  Copyright (C) 2010 Andreas Öman
+ *  Copyright (C) 2010 Andreas man
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -867,6 +867,20 @@ dvr_autorec_entry_class_content_type_list(void *o)
   return m;
 }
 
+static htsmsg_t *
+dvr_autorec_entry_class_dedup_list ( void *o )
+{
+  static const struct strtab tab[] = {
+    { "Record all",            DVR_AUTOREC_RECORD_ALL },
+    { "Record if different episode number", DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER },
+    { "Record if different subtitle", DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE },
+    { "Record if different description", DVR_AUTOREC_RECORD_DIFFERENT_DESCRIPTION },
+    { "Record once per week", DVR_AUTOREC_RECORD_ONCE_PER_WEEK },
+    { "Record once per day", DVR_AUTOREC_RECORD_ONCE_PER_DAY },
+  };
+  return strtab2htsmsg(tab);
+}
+
 const idclass_t dvr_autorec_entry_class = {
   .ic_class      = "dvrautorec",
   .ic_caption    = "DVR Auto-Record Entry",
@@ -999,6 +1013,14 @@ const idclass_t dvr_autorec_entry_class = {
       .def.i    = DVR_PRIO_NORMAL,
       .off      = offsetof(dvr_autorec_entry_t, dae_pri),
     },
+    {
+      .type     = PT_U32,
+      .id       = "record",
+      .name     = "Duplicate Handling",
+      .def.i    = DVR_AUTOREC_RECORD_ALL,
+      .off      = offsetof(dvr_autorec_entry_t, dae_record),
+      .list     = dvr_autorec_entry_class_dedup_list,
+    },
     {
       .type     = PT_INT,
       .id       = "retention",
index e1f5a70e443d13d8c4dd6f854f8e8507c049fb02..07139a2d7c47f568e1aa3dab45c75ddae4bbe3da 100644 (file)
@@ -44,6 +44,7 @@ static void dvr_timer_expire(void *aux);
 static void dvr_timer_start_recording(void *aux);
 static void dvr_timer_stop_recording(void *aux);
 static int dvr_entry_class_disp_title_set(void *o, const void *v);
+static int dvr_entry_class_disp_subtitle_set(void *o, const void *v);
 
 /*
  * Start / stop time calculators
@@ -261,10 +262,9 @@ dvr_make_title(char *output, size_t outlen, dvr_entry_t *de)
                                 ".", "S%02d", NULL, "E%02d", NULL);
   }
 
-  if (cfg->dvr_subtitle_in_title) {
-    if(de->de_bcast && de->de_bcast->episode && de->de_bcast->episode->subtitle)
+  if (cfg->dvr_subtitle_in_title && de->de_subtitle) {
       snprintf(output + strlen(output), outlen - strlen(output),
-           ".%s", lang_str_get(de->de_bcast->episode->subtitle, NULL));
+           ".%s", lang_str_get(de->de_subtitle, NULL));
   }
 
   localtime_r(&de->de_start, &tm);
@@ -306,7 +306,7 @@ dvr_entry_set_timer(dvr_entry_t *de)
       de->de_sched_state = DVR_MISSED_TIME;
     else
       _dvr_entry_completed(de);
-    gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de, 
+    gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de,
                    de->de_stop + dvr_entry_get_retention(de) * 86400);
 
   } else if (de->de_sched_state == DVR_RECORDING)  {
@@ -429,6 +429,11 @@ dvr_entry_create(const char *uuid, htsmsg_t *conf)
       (s = htsmsg_get_str(conf, "disp_title")) != NULL)
     dvr_entry_class_disp_title_set(de, s);
 
+  /* special case, becaous PO_NOSAVE, load ignores it */
+  if (de->de_subtitle == NULL &&
+          (s = htsmsg_get_str(conf, "disp_subtitle")) != NULL)
+    dvr_entry_class_disp_subtitle_set(de, s);
+
   de->de_refcnt = 1;
 
   LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
@@ -456,7 +461,7 @@ dvr_entry_t *
 dvr_entry_create_(const char *config_uuid, epg_broadcast_t *e,
                   channel_t *ch, time_t start, time_t stop,
                   time_t start_extra, time_t stop_extra,
-                  const char *title, const char *description,
+                  const char *title, const char* subtitle, const char *description,
                   const char *lang, epg_genre_t *content_type,
                   const char *owner,
                   const char *creator, dvr_autorec_entry_t *dae,
@@ -487,6 +492,8 @@ dvr_entry_create_(const char *config_uuid, epg_broadcast_t *e,
     htsmsg_add_u32(conf, "dvb_eid", e->dvb_eid);
     if (e->episode && e->episode->title)
       lang_str_serialize(e->episode->title, conf, "title");
+    if (e->episode && e->episode->subtitle)
+      lang_str_serialize(e->episode->subtitle, conf, "subtitle");
     if (e->description)
       lang_str_serialize(e->description, conf, "description");
     else if (e->episode && e->episode->description)
@@ -508,6 +515,12 @@ dvr_entry_create_(const char *config_uuid, epg_broadcast_t *e,
       lang_str_serialize(l, conf, "description");
       lang_str_destroy(l);
     }
+    if (subtitle) {
+      l = lang_str_create();
+      lang_str_add(l, subtitle, lang, 0);
+      lang_str_serialize(l, conf, "subtitle");
+      lang_str_destroy(l);
+    }
   }
   if (content_type)
     htsmsg_add_u32(conf, "content_type", content_type->code / 16);
@@ -552,7 +565,7 @@ dvr_entry_t *
 dvr_entry_create_htsp(const char *config_uuid,
                       channel_t *ch, time_t start, time_t stop,
                       time_t start_extra, time_t stop_extra,
-                      const char *title,
+                      const char *title, const char* subtitle,
                       const char *description, const char *lang,
                       epg_genre_t *content_type,
                       const char *owner,
@@ -566,7 +579,7 @@ dvr_entry_create_htsp(const char *config_uuid,
   return dvr_entry_create_(cfg ? idnode_uuid_as_str(&cfg->dvr_id) : NULL,
                            NULL,
                            ch, start, stop, start_extra, stop_extra,
-                           title, description, lang, content_type,
+                           title, subtitle, description, lang, content_type,
                            owner, creator, dae, NULL, pri, retention,
                            comment);
 }
@@ -589,47 +602,109 @@ dvr_entry_create_by_event(const char *config_uuid,
   return dvr_entry_create_(config_uuid, e,
                            e->channel, e->start, e->stop,
                            start_extra, stop_extra,
-                           NULL, NULL, NULL,
+                           NULL, NULL, NULL, NULL,
                            LIST_FIRST(&e->episode->genre),
                            owner, creator, dae, NULL, pri, retention,
                            comment);
 }
 
+static inline int strempty(const char* c) {
+  return !c || c[0] == 0;
+}
+
+static inline int lang_str_empty(lang_str_t* str) {
+  return strempty(lang_str_get(str, NULL));
+}
+
+
 /**
  *
  */
-static int _dvr_duplicate_event ( epg_broadcast_t *e )
+static dvr_entry_t* _dvr_duplicate_event(dvr_entry_t* de)
 {
-  dvr_entry_t *de;
-  epg_episode_num_t empty_epnum;
-  int has_epnum = 1;
+  if (!de->de_autorec)
+    return NULL;
 
-  /* skip episode duplicate check below if no episode number */
-  memset(&empty_epnum, 0, sizeof(empty_epnum));
-  if (epg_episode_number_cmp(&empty_epnum, &e->episode->epnum) == 0)
-    has_epnum = 0;
+  int record = de->de_autorec->dae_record;
 
-  LIST_FOREACH(de, &dvrentries, de_global_link) {
-    if (de->de_bcast) {
-      if (de->de_bcast->episode == e->episode) return 1;
+  struct tm de_start;
+  localtime_r(&de->de_start, &de_start);
+
+  switch (record) {
+    case DVR_AUTOREC_RECORD_ALL:
+      return NULL;
+    case DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER:
+      if (strempty(de->de_episode))
+        return NULL;
+      break;
+    case DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE:
+      if (lang_str_empty(de->de_subtitle))
+        return NULL;
+      break;
+    case DVR_AUTOREC_RECORD_DIFFERENT_DESCRIPTION:
+      if (lang_str_empty(de->de_desc))
+        return NULL;
+      break;
+    case DVR_AUTOREC_RECORD_ONCE_PER_WEEK:
+      de_start.tm_mday -= (de_start.tm_wday + 6) % 7; // week = mon-sun
+      mktime(&de_start); // adjusts de_start
+      break;
+  }
 
-      if (has_epnum) {
-        int ep_dup_det = de->de_config->dvr_episode_duplicate;
+  // title not defined, can't be deduped
+  if (lang_str_empty(de->de_title))
+    return NULL;
 
-        if (ep_dup_det) {
-          const char* de_title = lang_str_get(de->de_bcast->episode->title, NULL);
-          const char* e_title = lang_str_get(e->episode->title, NULL);
+  dvr_entry_t *de2;
 
-          /* duplicate if title and episode match */
-          if (de_title && e_title && strcmp(de_title, e_title) == 0
-              && epg_episode_number_cmp(&de->de_bcast->episode->epnum, &e->episode->epnum) == 0) {
-            return 1;
-          }
-        }
+  LIST_FOREACH(de2, &dvrentries, de_global_link) {
+    if (de == de2)
+      continue;
+
+    // only earlier recordings qualify as master
+    if (de2->de_start > de->de_start)
+      continue;
+
+    // only successful earlier recordings qualify as master
+    if (de2->de_sched_state == DVR_MISSED_TIME || (de2->de_sched_state == DVR_COMPLETED && de2->de_last_error != SM_CODE_OK))
+      continue;
+
+    // if titles are not defined or do not match, don't dedup
+    if (lang_str_compare(de->de_title, de2->de_title))
+      continue;
+
+    switch (record) {
+      case DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER:
+        if (!strcmp(de->de_episode, de2->de_episode))
+          return de2;
+        break;
+      case DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE:
+        if (!lang_str_compare(de->de_subtitle, de2->de_subtitle))
+          return de2;
+        break;
+      case DVR_AUTOREC_RECORD_DIFFERENT_DESCRIPTION:
+        if (!lang_str_compare(de->de_desc, de2->de_desc))
+          return de2;
+        break;
+      case DVR_AUTOREC_RECORD_ONCE_PER_WEEK: {
+        struct tm de2_start;
+        localtime_r(&de2->de_start, &de2_start);
+        de2_start.tm_mday -= (de2_start.tm_wday + 6) % 7; // week = mon-sun
+        mktime(&de2_start); // adjusts de2_start
+        if (de_start.tm_year == de2_start.tm_year && de_start.tm_yday == de2_start.tm_yday)
+          return de2;
+        break;
+      }
+      case DVR_AUTOREC_RECORD_ONCE_PER_DAY: {
+        struct tm de2_start;
+        localtime_r(&de2->de_start, &de2_start);
+        if (de_start.tm_year == de2_start.tm_year && de_start.tm_yday == de2_start.tm_yday)
+          return de2;
+        break;
       }
     }
   }
-  return 0;
+  return NULL;
 }
 
 /**
@@ -641,7 +716,11 @@ dvr_entry_create_by_autorec(epg_broadcast_t *e, dvr_autorec_entry_t *dae)
   char buf[200];
 
   /* Dup detection */
-  if (_dvr_duplicate_event(e)) return;
+  dvr_entry_t* de;
+  LIST_FOREACH(de, &dvrentries, de_global_link) {
+    if (de->de_bcast == e || (de->de_bcast && de->de_bcast->episode == e->episode))
+      return;
+  }
 
   snprintf(buf, sizeof(buf), "Auto recording%s%s",
            dae->dae_creator ? " by: " : "",
@@ -651,6 +730,8 @@ dvr_entry_create_by_autorec(epg_broadcast_t *e, dvr_autorec_entry_t *dae)
                             dae->dae_start_extra, dae->dae_stop_extra,
                             dae->dae_owner, buf, dae, dae->dae_pri, dae->dae_retention,
                             dae->dae_comment);
+
+
 }
 
 /**
@@ -684,6 +765,7 @@ dvr_entry_dec_ref(dvr_entry_t *de)
   free(de->de_creator);
   free(de->de_comment);
   if (de->de_title) lang_str_destroy(de->de_title);
+  if (de->de_subtitle)  lang_str_destroy(de->de_subtitle);
   if (de->de_desc)  lang_str_destroy(de->de_desc);
   if (de->de_bcast) de->de_bcast->putref((epg_object_t*)de->de_bcast);
   free(de->de_channel_name);
@@ -769,7 +851,7 @@ dvr_timer_expire(void *aux)
 }
 
 static dvr_entry_t *_dvr_entry_update
-  ( dvr_entry_t *de, epg_broadcast_t *e, const char *title,
+  ( dvr_entry_t *de, epg_broadcast_t *e, const char *title, const char* subtitle,
     const char *desc, const char *lang, time_t start, time_t stop,
     time_t start_extra, time_t stop_extra,  dvr_prio_t pri, int retention )
 {
@@ -819,7 +901,13 @@ static dvr_entry_t *_dvr_entry_update
     if (!de->de_title) de->de_title = lang_str_create();
     save = lang_str_add(de->de_title, title, lang, 1);
   }
-  
+
+  /* Subtitle*/
+  if (subtitle) {
+    if (!de->de_subtitle) de->de_subtitle = lang_str_create();
+    save = lang_str_add(de->de_subtitle, subtitle, lang, 1);
+  }
+
   /* EID */
   if (e && e->dvb_eid != de->de_dvb_eid) {
     de->de_dvb_eid = e->dvb_eid;
@@ -873,12 +961,12 @@ static dvr_entry_t *_dvr_entry_update
 dvr_entry_t * 
 dvr_entry_update
   (dvr_entry_t *de,
-   const char* de_title, const char *de_desc, const char *lang,
+   const char* de_title, const char* de_subtitle, const char *de_desc, const char *lang,
    time_t de_start, time_t de_stop,
    time_t de_start_extra, time_t de_stop_extra,
    dvr_prio_t pri, int retention)
 {
-  return _dvr_entry_update(de, NULL, de_title, de_desc, lang, 
+  return _dvr_entry_update(de, NULL, de_title, de_subtitle, de_desc, lang,
                            de_start, de_stop, de_start_extra, de_stop_extra,
                            pri, retention);
 }
@@ -930,7 +1018,7 @@ dvr_event_replaced(epg_broadcast_t *e, epg_broadcast_t *new_e)
                    e->start, e->stop);
           e->getref(e);
           de->de_bcast = e;
-          _dvr_entry_update(de, e, NULL, NULL, NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0);
+          _dvr_entry_update(de, e, NULL, NULL, NULL, NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0);
           break;
         }
       }
@@ -943,7 +1031,7 @@ void dvr_event_updated ( epg_broadcast_t *e )
   dvr_entry_t *de;
   de = dvr_entry_find_by_event(e);
   if (de)
-    _dvr_entry_update(de, e, NULL, NULL, NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0);
+    _dvr_entry_update(de, e, NULL, NULL, NULL, NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0);
   else {
     LIST_FOREACH(de, &dvrentries, de_global_link) {
       if (de->de_sched_state != DVR_SCHEDULED) continue;
@@ -959,7 +1047,7 @@ void dvr_event_updated ( epg_broadcast_t *e )
                  e->start, e->stop);
         e->getref(e);
         de->de_bcast = e;
-        _dvr_entry_update(de, e, NULL, NULL, NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0);
+        _dvr_entry_update(de, e, NULL, NULL, NULL, NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0);
         break;
       }
     }
@@ -1020,6 +1108,12 @@ dvr_timer_start_recording(void *aux)
     return;
   }
 
+  // if duplicate, then delete it now, don't record!
+  if (_dvr_duplicate_event(de)) {
+    dvr_entry_cancel_delete(de);
+    return;
+  }
+
   de->de_sched_state = DVR_RECORDING;
   de->de_rec_state = DVR_RS_PENDING;
 
@@ -1539,13 +1633,46 @@ dvr_entry_class_disp_title_get(void *o)
   return &s;
 }
 
+
+static int
+dvr_entry_class_disp_subtitle_set(void *o, const void *v)
+{
+  dvr_entry_t *de = (dvr_entry_t *)o;
+  const char *s = "";
+  if (v == NULL || *((char *)v) == '\0')
+    v = "UnknownSubtitle";
+  if (de->de_subtitle)
+    s = lang_str_get(de->de_subtitle, NULL);
+  if (strcmp(s, v)) {
+    lang_str_destroy(de->de_subtitle);
+    de->de_subtitle = lang_str_create();
+    lang_str_add(de->de_subtitle, v, NULL, 0);
+    return 1;
+  }
+  return 0;
+}
+
+static const void *
+dvr_entry_class_disp_subtitle_get(void *o)
+{
+  dvr_entry_t *de = (dvr_entry_t *)o;
+  static const char *s;
+  s = "";
+  if (de->de_subtitle) {
+    s = lang_str_get(de->de_subtitle, NULL);
+    if (s == NULL)
+      s = "";
+  }
+  return &s;
+}
+
 static const void *
 dvr_entry_class_disp_description_get(void *o)
 {
   dvr_entry_t *de = (dvr_entry_t *)o;
   static const char *s;
   s = "";
-  if (de->de_title) {
+  if (de->de_desc) {
     s = lang_str_get(de->de_desc, NULL);
     if (s == NULL)
       s = "";
@@ -1654,6 +1781,16 @@ dvr_entry_class_channel_icon_url_get(void *o)
   return &s;
 }
 
+static const void *
+dvr_entry_class_duplicate_get(void *o)
+{
+  static time_t null = 0;
+  dvr_entry_t *de = (dvr_entry_t *)o;
+  de = _dvr_duplicate_event(de);
+  return de ? &de->de_start : &null;
+}
+
+
 htsmsg_t *
 dvr_entry_class_duration_list(void *o, const char *not_set, int max, int step)
 {
@@ -1803,6 +1940,21 @@ const idclass_t dvr_entry_class = {
       .set      = dvr_entry_class_disp_title_set,
       .opts     = PO_NOSAVE,
     },
+    {
+      .type     = PT_LANGSTR,
+      .id       = "subtitle",
+      .name     = "Subtitle",
+      .off      = offsetof(dvr_entry_t, de_subtitle),
+      .opts     = PO_RDONLY,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "disp_subtitle",
+      .name     = "Subtitle",
+      .get      = dvr_entry_class_disp_subtitle_get,
+      .set      = dvr_entry_class_disp_subtitle_set,
+      .opts     = PO_NOSAVE,
+    },
     {
       .type     = PT_LANGSTR,
       .id       = "description",
@@ -1984,6 +2136,13 @@ const idclass_t dvr_entry_class = {
       .get      = dvr_entry_class_sched_status_get,
       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN,
     },
+    {
+      .type     = PT_TIME,
+      .id       = "duplicate",
+      .name     = "Rerun of",
+      .get      = dvr_entry_class_duplicate_get,
+      .opts     = PO_RDONLY | PO_NOSAVE,
+    },
     {
       .type     = PT_STR,
       .id       = "comment",
@@ -2038,7 +2197,7 @@ static struct strtab priotab[] = {
   { "high",        DVR_PRIO_HIGH },
   { "normal",      DVR_PRIO_NORMAL },
   { "low",         DVR_PRIO_LOW },
-  { "unimportant", DVR_PRIO_UNIMPORTANT },
+  { "unimportant", DVR_PRIO_UNIMPORTANT }
 };
 
 dvr_prio_t
index 20c80e7f4895fde0c12d9d1f8be91f52a86117dd..8d54d8934a0c6ecdf8d54ec4bd9d7b6851af88eb 100644 (file)
@@ -155,7 +155,7 @@ dvr_timerec_check(dvr_timerec_entry_t *dte)
            dte->dte_creator ?: "");
   de = dvr_entry_create_(idnode_uuid_as_str(&dte->dte_config->dvr_id),
                          NULL, dte->dte_channel,
-                         start, stop, 0, 0, title,
+                         start, stop, 0, 0, title, NULL,
                          NULL, NULL, NULL, dte->dte_owner, buf,
                          NULL, dte, dte->dte_pri, dte->dte_retention,
                          dte->dte_comment);
index 95e596f73bd077044d51d7fdcdbfeea40537204c..a000ae09e1265a0059159bc1af60e2e7acfae081 100644 (file)
--- a/src/epg.c
+++ b/src/epg.c
@@ -1289,7 +1289,7 @@ const char *epg_episode_get_subtitle
   return lang_str_get(e->subtitle, lang);
 }
 
-const char *epg_episode_get_summary 
+const char *epg_episode_get_summary
   ( const epg_episode_t *e, const char *lang )
 {
   if (!e || !e->summary) return NULL;
index a93e64b6896f113543fc9b1c68670c140667cd3f..63b443f5276ea15490bc608ce28efe9db9a3a51b 100644 (file)
--- a/src/epg.h
+++ b/src/epg.h
@@ -290,6 +290,8 @@ const char *epg_episode_get_title
   ( const epg_episode_t *e, const char *lang );
 const char *epg_episode_get_subtitle
   ( const epg_episode_t *e, const char *lang );
+lang_str_t *epg_episode_get_subtitle2
+        ( const epg_episode_t *e );
 const char *epg_episode_get_summary
   ( const epg_episode_t *e, const char *lang );
 const char *epg_episode_get_description
@@ -511,6 +513,8 @@ const char *epg_broadcast_get_title
   ( epg_broadcast_t *b, const char *lang );
 const char *epg_broadcast_get_subtitle
   ( epg_broadcast_t *b, const char *lang );
+lang_str_t *epg_broadcast_get_subtitle2
+        ( epg_broadcast_t *b );
 const char *epg_broadcast_get_summary
   ( epg_broadcast_t *b, const char *lang );
 const char *epg_broadcast_get_description
index f61b92353a30cdefcd50c80d74923f99db975bbd..a4ce59fd3346f0018a7f190f3598706504a0af53 100644 (file)
@@ -1413,7 +1413,7 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   epg_broadcast_t *e = NULL;
   dvr_entry_t *de;
   dvr_entry_sched_state_t dvr_status;
-  const char *dvr_config_name, *title, *desc, *creator, *lang, *comment;
+  const char *dvr_config_name, *title, *desc, *subtitle, *creator, *lang, *comment;
   int64_t start, stop, start_extra, stop_extra;
   uint32_t u32, priority, retention;
   channel_t *ch = NULL;
@@ -1454,6 +1454,10 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
         !(title = htsmsg_get_str(in, "title")))
       return htsp_error("Invalid arguments");
 
+    /* Optional attributes */
+    if (!(subtitle = htsmsg_get_str(in, "subtitle")))
+      subtitle = "";
+
     /* Optional attributes */
     if (!(desc = htsmsg_get_str(in, "description")))
       desc = "";
@@ -1461,7 +1465,7 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
     // create the dvr entry
     de = dvr_entry_create_htsp(dvr_config_name, ch, start, stop,
                                start_extra, stop_extra,
-                               title, desc, lang, 0,
+                               title, subtitle, desc, lang, 0,
                                htsp->htsp_granted_access->aa_username,
                                creator, NULL,
                                priority, retention, comment);
@@ -1506,7 +1510,7 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   uint32_t dvrEntryId;
   dvr_entry_t *de;
   time_t start, stop, start_extra, stop_extra, priority, retention;
-  const char *title, *desc, *lang;
+  const char *title, *subtitle, *desc, *lang;
     
   if(htsmsg_get_u32(in, "id", &dvrEntryId))
     return htsp_error("Missing argument 'id'");
@@ -1528,11 +1532,12 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   retention   = htsmsg_get_u32_or_default(in, "retention",  0);
   priority    = htsmsg_get_u32_or_default(in, "priority",   DVR_PRIO_NORMAL);
   title       = htsmsg_get_str(in, "title");
+  subtitle    = htsmsg_get_str(in, "title");
   desc        = htsmsg_get_str(in, "description");
   lang        = htsmsg_get_str(in, "language");
   if (!lang) lang = htsp->htsp_language;
 
-  de = dvr_entry_update(de, title, desc, lang, start, stop,
+  de = dvr_entry_update(de, title, subtitle, desc, lang, start, stop,
                         start_extra, stop_extra, priority, retention);
 
   //create response
index ad066fabcb063eb78ebe5f3b378100d313c879ca..bb8d5625fa47770d0a708d4cf0217d3b764abc08 100644 (file)
@@ -799,7 +799,7 @@ main(int argc, char **argv)
   OPENSSL_config(NULL);
   SSL_load_error_strings();
   SSL_library_init();
-  
+
   /* Initialise configuration */
   uuid_init();
   idnode_init();
index 0a69f073b0d7f1b171d0b81d24a00893c1619618..0d19c447c768d474efdc9bda8cf7b7f783c9a677 100644 (file)
@@ -11,14 +11,15 @@ tvheadend.dvrDetails = function(uuid) {
         var params = d[0].params;
         var chicon = params[0].value;
         var title = params[1].value;
-        var episode = params[2].value;
-        var start_real = params[3].value;
-        var stop_real = params[4].value;
-        var duration = params[5].value;
-        var desc = params[6].value;
-        var status = params[7].value;
-        var filesize = params[8].value;
-        var comment = params[9].value;
+        var subtitle = params[2].value;
+        var episode = params[3].value;
+        var start_real = params[4].value;
+        var stop_real = params[5].value;
+        var duration = params[6].value;
+        var desc = params[7].value;
+        var status = params[8].value;
+        var filesize = params[9].value;
+        var comment = params[10].value;
         var content = '';
         var but;
 
@@ -27,6 +28,8 @@ tvheadend.dvrDetails = function(uuid) {
 
         if (title)
           content += '<div class="x-epg-title">' + title + '</div>';
+        if (subtitle)
+            content += '<div class="x-epg-title">' + subtitle + '</div>';
         if (episode)
           content += '<div class="x-epg-title">' + episode + '</div>';
         if (start_real)
@@ -64,7 +67,7 @@ tvheadend.dvrDetails = function(uuid) {
         url: 'api/idnode/load',
         params: {
             uuid: uuid,
-            list: 'channel_icon,disp_title,episode,start_real,stop_real,' +
+            list: 'channel_icon,disp_title,disp_subtitle,episode,start_real,stop_real,' +
                   'duration,disp_description,status,filesize,comment'
         },
         success: function(d) {
@@ -214,7 +217,7 @@ tvheadend.dvr_upcoming = function(panel, index) {
             }
         },
         del: true,
-        list: 'disp_title,episode,pri,start_real,stop_real,' +
+        list: 'duplicate,disp_title,disp_subtitle,episode,pri,start_real,stop_real,' +
               'duration,filesize,channel,owner,creator,config_name,' +
               'sched_status,errors,data_errors,comment',
         columns: {
@@ -282,7 +285,7 @@ tvheadend.dvr_finished = function(panel, index) {
         del: true,
         delquestion: 'Do you really want to delete the selected recordings?<br/><br/>' +
                      'The associated file will be removed from the storage.',
-        list: 'disp_title,episode,start_real,stop_real,' +
+        list: 'disp_title,disp_subtitle,episode,start_real,stop_real,' +
               'duration,filesize,channelname,owner,creator,' +
               'config_name,sched_status,errors,data_errors,url,comment',
         columns: {
@@ -362,7 +365,7 @@ tvheadend.dvr_failed = function(panel, index) {
         del: true,
         delquestion: 'Do you really want to delete the selected recordings?<br/><br/>' +
                      'The associated file will be removed from the storage.',
-        list: 'disp_title,episode,start_real,stop_real,' +
+        list: 'disp_title,disp_subtitle,episode,start_real,stop_real,' +
               'duration,filesize,channelname,owner,creator,config_name,' +
               'status,sched_status,errors,data_errors,url,comment',
         columns: {
@@ -457,6 +460,7 @@ tvheadend.autorec_editor = function(panel, index) {
                 renderer: function(st) { return tvheadend.weekdaysRenderer(st); }
             },
             pri:          { width: 80 },
+            dedup:        { width: 160 },
             retention:    { width: 80 },
             config_name:  { width: 120 },
             owner:        { width: 100 },
@@ -467,13 +471,13 @@ tvheadend.autorec_editor = function(panel, index) {
             url: 'api/dvr/autorec',
             params: {
                list: 'enabled,name,directory,title,fulltext,channel,tag,content_type,minduration,' +
-                     'maxduration,weekdays,start,start_window,pri,config_name,comment'
+                     'maxduration,weekdays,start,start_window,pri,dedup,config_name,comment'
             },
             create: { }
         },
         del: true,
         list: 'enabled,name,directory,title,fulltext,channel,tag,content_type,minduration,' +
-              'maxduration,weekdays,start,start_window,pri,config_name,owner,creator,comment',
+              'maxduration,weekdays,start,start_window,pri,dedup,config_name,owner,creator,comment',
         sort: {
           field: 'name',
           direction: 'ASC'