]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
xmltv: Parse credits, category, keyword and more age ratings. (#4441)
authorE.Smith <31170571+azlm8t@users.noreply.github.com>
Wed, 20 Sep 2017 01:05:39 +0000 (02:05 +0100)
committerJaroslav Kysela <perex@perex.cz>
Mon, 9 Oct 2017 14:15:05 +0000 (16:15 +0200)
The xmltv provides additional information about programmes such
as keywords ("Zookeeper", "Newscast", "Lion", "Mystery"), and
categories ("Crime drama", "Movie", "Series"). It can also provide
detailed information about actors, writers, editors, composers, etc.

We parse this information and allow it to be searched from the GUI.

We make this configurable since having 20+ actors per movie can
increase memory usage of the server and the clients to which we
send this information.

We also offer an option to append this information on to the
description. This allows people with old clients to see the
information.

We cache this information in to a csv string so users can search
across multiple actors such as "Douglas.*Stallone" to find movies
where both actors starred, rather than searching across each actor
individually.

The category is not currently searchable via regex since I think
that should probably be a search box similar to content type.

We currently only parse and store a few of the credits, viz., actor,
guest, presenter, writer, and director. If people really search for
films based on the composer or editor then we can add it in the future.

This information is stored on the epg_broadcast rather than the
epg_episode since theoretically a programme could have different
information for different showings of the same programme.

For example, my broadcaster shows the same film in the same week but
prefixes the description of some showings with a keyword (such as
"Frightfest") with other film of the same genre to create a pseudo-boxset.
Thus if we ever scrape keywords from EIT we'd probably tag the particular
films with this keyword as a tag on which people could search.

Similarly for credits, a daytime showing of a programme can contain edits
for violence, swearing that are not in the late night showing, thus potentially
changing the cast despite being the same "episode", or perhaps one showing
is dubbed.

We also parse a few more age ratings since a number of programmes
only have "word" ratings rather than age ratings (TV-14 instead of 14).
Also the existing age could underflow since one rating system
uses negative numbers which don't fit in our unsigned byte.

Issue: #4441

src/epg.c
src/epg.h
src/epggrab.h
src/epggrab/module/xmltv.c
src/htsp_server.c
src/lang_str.h

index a15037dca2aedd5afb55a2e0c2229bbd077b701c..301fd8ed4c95eb3c437a227cca027c66b44c5a14 100644 (file)
--- a/src/epg.c
+++ b/src/epg.c
@@ -365,6 +365,8 @@ static int FNNAME \
 
 
 EPG_OBJECT_SET_FN(_epg_object_set_lang_str,    lang_str_t,    lang_str_destroy,    lang_str_compare,    lang_str_copy)
+EPG_OBJECT_SET_FN(_epg_object_set_string_list, string_list_t, string_list_destroy, string_list_cmp,     string_list_copy)
+EPG_OBJECT_SET_FN(_epg_object_set_htsmsg,      htsmsg_t,      htsmsg_destroy,      htsmsg_cmp,          htsmsg_copy)
 #undef EPG_OBJECT_SET_FN
 
 static int _epg_object_set_u8
@@ -1883,6 +1885,11 @@ static void _epg_broadcast_destroy ( void *eo )
   if (ebc->serieslink)  _epg_serieslink_rem_broadcast(ebc->serieslink, ebc);
   if (ebc->summary)     lang_str_destroy(ebc->summary);
   if (ebc->description) lang_str_destroy(ebc->description);
+  if (ebc->credits)     htsmsg_destroy(ebc->credits);
+  if (ebc->credits_cached) lang_str_destroy(ebc->credits_cached);
+  if (ebc->category)    string_list_destroy(ebc->category);
+  if (ebc->keyword)     string_list_destroy(ebc->keyword);
+  if (ebc->keyword_cached) lang_str_destroy(ebc->keyword_cached);
   _epg_object_destroy(eo, NULL);
   free(ebc);
 }
@@ -1979,6 +1986,12 @@ int epg_broadcast_change_finish
     save |= epg_broadcast_set_summary(broadcast, NULL, NULL);
   if (!(changes & EPG_CHANGED_DESCRIPTION))
     save |= epg_broadcast_set_description(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_CREDITS))
+    save |= epg_broadcast_set_credits(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_CATEGORY))
+    save |= epg_broadcast_set_category(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_KEYWORD))
+    save |= epg_broadcast_set_keyword(broadcast, NULL, NULL);
   return save;
 }
 
@@ -2004,6 +2017,9 @@ epg_broadcast_t *epg_broadcast_clone
     *save |= epg_broadcast_set_is_new(ebc, src->is_new, &changes);
     *save |= epg_broadcast_set_is_repeat(ebc, src->is_repeat, &changes);
     *save |= epg_broadcast_set_summary(ebc, src->summary, &changes);
+    *save |= epg_broadcast_set_credits(ebc, src->credits, &changes);
+    *save |= epg_broadcast_set_category(ebc, src->category, &changes);
+    *save |= epg_broadcast_set_keyword(ebc, src->keyword, &changes);
     *save |= epg_broadcast_set_description(ebc, src->description, &changes);
     *save |= epg_broadcast_set_serieslink(ebc, src->serieslink, &changes);
     *save |= epg_broadcast_set_episode(ebc, src->episode, &changes);
@@ -2177,6 +2193,72 @@ int epg_broadcast_set_description
                                   changed, EPG_CHANGED_DESCRIPTION);
 }
 
+int epg_broadcast_set_credits
+( epg_broadcast_t *b, htsmsg_t *credits, uint32_t *changed )
+{
+  if (!b) return 0;
+  const int mod = _epg_object_set_htsmsg(b, &b->credits, credits, changed, EPG_CHANGED_CREDITS);
+  if (mod) {
+      /* Copy in to cached csv for regex searching in autorec/GUI.
+       * We use just one string (rather than regex across each entry
+       * separately) so you could do a regex of "Douglas.*Stallone"
+       * to match the movies with the two actors.
+       */
+      if (!b->credits_cached) {
+          b->credits_cached = lang_str_create();
+      }
+      lang_str_set(&b->credits_cached, "", NULL);
+
+      if (b->credits) {
+        int add_sep = 0;
+        htsmsg_field_t *f;
+        HTSMSG_FOREACH(f, b->credits) {
+          if (add_sep) {
+            lang_str_append(b->credits_cached, ", ", NULL);
+          } else {
+            add_sep = 1;
+          }
+          lang_str_append(b->credits_cached, f->hmf_name, NULL);
+        }
+      } else {
+        if (b->credits_cached) {
+          lang_str_destroy(b->credits_cached);
+          b->credits_cached = NULL;
+        }
+      }
+  }
+  return mod;
+}
+
+int epg_broadcast_set_category
+( epg_broadcast_t *b, string_list_t *msg, uint32_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_string_list(b, &b->category, msg, changed, EPG_CHANGED_CATEGORY);
+}
+
+int epg_broadcast_set_keyword
+( epg_broadcast_t *b, string_list_t *msg, uint32_t *changed )
+{
+  if (!b) return 0;
+  const int mod = _epg_object_set_string_list(b, &b->keyword, msg, changed, EPG_CHANGED_KEYWORD);
+  if (mod) {
+    /* Copy in to cached csv for regex searching in autorec/GUI. */
+    if (msg) {
+      /* 1==>human readable */
+      char *str = string_list_2_csv(msg, ',', 1);
+      lang_str_set(&b->keyword_cached, str, NULL);
+      free(str);
+    } else {
+      if (b->keyword_cached) {
+        lang_str_destroy(b->keyword_cached);
+        b->keyword_cached = NULL;
+      }
+    }
+  }
+  return mod;
+}
+
 epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast )
 {
   if ( !broadcast ) return NULL;
@@ -2201,6 +2283,18 @@ const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang )
   return lang_str_get(b->summary, lang);
 }
 
+const char *epg_broadcast_get_credits_cached ( epg_broadcast_t *b, const char *lang)
+{
+  if (!b || !b->credits_cached) return NULL;
+  return lang_str_get(b->credits_cached, lang);
+}
+
+const char *epg_broadcast_get_keyword_cached ( epg_broadcast_t *b, const char *lang)
+{
+  if (!b || !b->keyword_cached) return NULL;
+  return lang_str_get(b->keyword_cached, lang);
+}
+
 const char *epg_broadcast_get_description ( epg_broadcast_t *b, const char *lang )
 {
   if (!b || !b->description) return NULL;
@@ -2243,6 +2337,15 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
     lang_str_serialize(broadcast->summary, m, "summary");
   if (broadcast->description)
     lang_str_serialize(broadcast->description, m, "description");
+  if (broadcast->credits)
+      htsmsg_add_msg(m, "credits", htsmsg_copy(broadcast->credits));
+  /* No need to serialize credits_cached since it is rebuilt from credits. */
+  if (broadcast->category)
+    string_list_serialize(broadcast->category, m, "category");
+  if (broadcast->keyword)
+    string_list_serialize(broadcast->keyword, m, "keyword");
+  /* No need to serialize keyword_cached since it is rebuilt from keyword */
+
   if (broadcast->serieslink)
     htsmsg_add_str(m, "serieslink", broadcast->serieslink->uri);
   
@@ -2257,6 +2360,8 @@ epg_broadcast_t *epg_broadcast_deserialize
   epg_episode_t *ee;
   epg_serieslink_t *esl;
   lang_str_t *ls;
+  htsmsg_t *hm;
+  string_list_t *sl;
   const char *str;
   uint32_t eid, u32, changes = 0, changes2 = 0;
   int64_t start, stop;
@@ -2318,6 +2423,18 @@ epg_broadcast_t *epg_broadcast_deserialize
     lang_str_destroy(ls);
   }
 
+  if ((hm = htsmsg_get_map(m, "credits"))) {
+      *save |= epg_broadcast_set_credits(ebc, hm, &changes);
+  }
+
+  if ((sl = string_list_deserialize(m, "keyword"))) {
+      *save |= epg_broadcast_set_keyword(ebc, sl, &changes);
+  }
+
+  if ((sl = string_list_deserialize(m, "category"))) {
+      *save |= epg_broadcast_set_category(ebc, sl, &changes);
+  }
+
   /* Series link */
   if ((str = htsmsg_get_str(m, "serieslink")))
     if ((esl = epg_serieslink_find_by_uri(str, ebc->grabber, 1, save, &changes2))) {
@@ -2783,7 +2900,13 @@ _eq_add ( epg_query_t *eq, epg_broadcast_t *e )
             regex_match(&eq->stitle_re, s)) {
           if ((s = epg_broadcast_get_description(e, lang)) == NULL ||
               regex_match(&eq->stitle_re, s)) {
-            return;
+            if ((s = epg_broadcast_get_credits_cached(e, lang)) == NULL ||
+                regex_match(&eq->stitle_re, s)) {
+              if ((s = epg_broadcast_get_keyword_cached(e, lang)) == NULL ||
+                  regex_match(&eq->stitle_re, s)) {
+                return;
+              }
+            }
           }
         }
       }
index 7be7b245a54a6f4ec962ba503c695314b71ad9ab..299dd28c9d6797c7ac269dda681d13461afdbf68 100644 (file)
--- a/src/epg.h
+++ b/src/epg.h
@@ -30,6 +30,7 @@
 struct channel;
 struct channel_tag;
 struct epggrab_module;
+struct string_list;
 
 /*
  * Map/List types
@@ -126,6 +127,9 @@ typedef enum epg_object_type
 #define EPG_CHANGED_SUMMARY       (1<<3)
 #define EPG_CHANGED_DESCRIPTION   (1<<4)
 #define EPG_CHANGED_IMAGE         (1<<5)
+#define EPG_CHANGED_CREDITS       (1<<6)
+#define EPG_CHANGED_CATEGORY      (1<<7)
+#define EPG_CHANGED_KEYWORD       (1<<8)
 #define EPG_CHANGED_SLAST         2
 
 typedef struct epg_object_ops {
@@ -511,7 +515,13 @@ struct epg_broadcast
   /* Broadcast level text */
   lang_str_t                *summary;          ///< Summary
   lang_str_t                *description;      ///< Description
-
+  htsmsg_t                  *credits;          ///< Cast/Credits map of name -> role type (actor, presenter, director, etc).
+  lang_str_t                *credits_cached;   ///< Comma separated cast (for regex searching in GUI/autorec). Kept in sync with cast_map
+  struct string_list        *category;         ///< Extra categories (typically from xmltv) such as "Western" or "Sumo Wrestling".
+                                               ///< These extra categories are often a superset of our EN 300 468 DVB genre.
+                                               ///< Currently not explicitly searchable in GUI.
+  struct string_list        *keyword;          ///< Extra keywords (typically from xmltv) such as "Wild West" or "Unicorn".
+  lang_str_t                *keyword_cached;   ///< Cached CSV version for regex searches.
   RB_ENTRY(epg_broadcast)    sched_link;       ///< Schedule link
   LIST_ENTRY(epg_broadcast)  ep_link;          ///< Episode link
   epg_episode_t             *episode;          ///< Episode shown
@@ -578,6 +588,15 @@ int epg_broadcast_set_summary
 int epg_broadcast_set_description
   ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
   __attribute__((warn_unused_result));
+int epg_broadcast_set_credits
+( epg_broadcast_t *b, htsmsg_t* msg, uint32_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_category
+( epg_broadcast_t *b, struct string_list* msg, uint32_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_keyword
+( epg_broadcast_t *b, struct string_list* msg, uint32_t *changed )
+  __attribute__((warn_unused_result));
 int epg_broadcast_set_serieslink
   ( epg_broadcast_t *b, epg_serieslink_t *sl, uint32_t *changed )
   __attribute__((warn_unused_result));
@@ -592,6 +611,11 @@ const char *epg_broadcast_get_summary
   ( epg_broadcast_t *b, const char *lang );
 const char *epg_broadcast_get_description
   ( epg_broadcast_t *b, const char *lang );
+/* Get the cached (csv) version for regex searching */
+const char *epg_broadcast_get_credits_cached
+  ( epg_broadcast_t *b, const char *lang );
+const char *epg_broadcast_get_keyword_cached
+  ( epg_broadcast_t *b, const char *lang );
 
 /* Serialization */
 htsmsg_t        *epg_broadcast_serialize   ( epg_broadcast_t *b );
index f65d67570c473c403665103e19f0a9a0c6ff591e..7944794a479327d629835b85d1eceb3054eb5fd9 100644 (file)
@@ -179,6 +179,9 @@ struct epggrab_module_int
   const char                   *args;     ///< Extra arguments
 
   int                           xmltv_chnum;
+  int                           xmltv_scrape_extra; ///< Scrape actors and extra details
+  int                           xmltv_scrape_onto_desc; ///< Include scraped actors
+    ///< and extra details on to programme description for viewing by legacy clients.
 
   /* Handle data */
   char*     (*grab)   ( void *mod );
index a519a54e79eb46a3598ca91168355949fa6ffae6..b96bb669f47c20c9cfbb86d445b88564b8acc250 100644 (file)
@@ -35,6 +35,7 @@
 #include "spawn.h"
 #include "file.h"
 #include "htsstr.h"
+#include "string_list.h"
 
 #include "lang_str.h"
 #include "epg.h"
@@ -380,7 +381,15 @@ static int _xmltv_parse_star_rating
 /*
  * Tries to get age ratingform <rating> element.
  * Expects integer representing minimal age of watcher.
- * Other rating types (non-integer, for example MPAA or VCHIP) are ignored.
+ * Other rating types (non-integer, for example MPAA or VCHIP) are
+ * mostly ignored, but we have some basic mappings for common
+ * ratings such as TV-MA which may only be the only ratings for
+ * some movies.
+ *
+ * We use the first rating that we find that returns a usable age.  Of
+ * course that means some programmes might not have the rating you
+ * expect for your country. For example one episode of a cooking
+ * programme has BBFC 18 but VCHIP TV-G.
  *
  * Attribute system is ignored.
  *
@@ -388,9 +397,8 @@ static int _xmltv_parse_star_rating
  * <rating system="pl"><value>16</value></rating>
  *
  * Currently non-working example:
- *    <rating system="MPAA">
- *     <value>PG</value>
- *     <icon src="pg_symbol.png" />
+ *    <rating system="CSA">
+ *     <value>-12</value>
  *   </rating>
  *
  * TODO - support for other rating systems:
@@ -406,13 +414,36 @@ static int _xmltv_parse_age_rating
   const char *s1;
 
   if (!ee || !body) return 0;
-  if (!(rating = htsmsg_get_map(body, "rating"))) return 0;
-  if (!(tags  = htsmsg_get_map(rating, "tags"))) return 0;
-  if (!(s1 = htsmsg_xml_get_cdata_str(tags, "value"))) return 0;
 
-  age = atoi(s1);
-
-  return epg_episode_set_age_rating(ee, age, changes);
+  htsmsg_field_t *f;
+  HTSMSG_FOREACH(f, body) {
+    if (!strcmp(f->hmf_name, "rating") && (rating = htsmsg_get_map_by_field(f))) {
+      if ((tags  = htsmsg_get_map(rating, "tags"))) {
+        if ((s1 = htsmsg_xml_get_cdata_str(tags, "value"))) {
+          /* We map some common ratings since some movies only
+           * have one of these flags rather than an age rating.
+           */
+          if (!strcmp(s1, "TV-G") || !strcmp(s1, "U"))
+            age = 3;
+          else if (!strcmp(s1, "TV-Y7") || !strcmp(s1, "PG"))
+            age = 7;
+          else if (!strcmp(s1, "TV-14"))
+            age = 14;
+          else if (!strcmp(s1, "TV-MA"))
+            age = 17;
+          else
+            age = atoi(s1);
+          /* Since age is uint8_t it means some rating systems can
+           * underflow and become very large, for example CSA has age
+           * rating of -10.
+           */
+          if (age > 0 && age < 22)
+            return epg_episode_set_age_rating(ee, age, changes);
+        }
+      }
+    }
+  }
+  return 0;
 }
 
 /*
@@ -454,6 +485,80 @@ _xmltv_parse_lang_str ( lang_str_t **ls, htsmsg_t *tags, const char *tname )
   }
 }
 
+/// Make a string list from the contents of all tags in the message
+/// that have tagname.
+__attribute__((warn_unused_result))
+static string_list_t *
+ _xmltv_make_str_list_from_matching(htsmsg_t *tags, const char *tagname)
+{
+  htsmsg_t *e;
+  htsmsg_field_t *f;
+  string_list_t *tag_list = NULL;
+
+  HTSMSG_FOREACH(f, tags) {
+    if (!strcmp(f->hmf_name, tagname) && (e = htsmsg_get_map_by_field(f))) {
+      const char *str = htsmsg_get_str(e, "cdata");
+      if (str && *str) {
+        if (!tag_list) tag_list = string_list_create();
+        string_list_insert(tag_list, str);
+      }
+    }
+  }
+
+  return tag_list;
+}
+
+
+/// Parse credits from the message tags and store the name/type (such
+/// as actor, director) in to out_credits (created if necessary).
+/// Also return a string list of the names only.
+///
+/// Sample input:
+/// <credits>
+///   <actor role="Bob">Fred Foo</actor>
+///   <actor role="Walt">Vic Vicson</actor>
+///   <director>Simon Scott</director>
+/// </credits>
+///
+/// Returns string list of {"Fred Foo", "Simon Scott", "Vic Vicson"} and
+/// out_credits containing the names and actor/director.
+__attribute__((warn_unused_result))
+static string_list_t *
+_xmltv_parse_credits(htsmsg_t **out_credits, htsmsg_t *tags)
+{
+  htsmsg_t *credits = htsmsg_get_map(tags, "credits");
+  if (!credits)
+    return NULL;
+  htsmsg_t *credits_tags;
+  if (!(credits_tags  = htsmsg_get_map(credits, "tags")))
+    return NULL;
+
+  string_list_t *credits_names = NULL;
+  htsmsg_t *e;
+  htsmsg_field_t *f;
+
+  HTSMSG_FOREACH(f, credits_tags) {
+    if ((!strcmp(f->hmf_name, "actor") ||
+         !strcmp(f->hmf_name, "director") ||
+         !strcmp(f->hmf_name, "guest") ||
+         !strcmp(f->hmf_name, "presenter") ||
+         !strcmp(f->hmf_name, "writer")
+         ) &&
+        (e = htsmsg_get_map_by_field(f)))  {
+      const char* str = htsmsg_get_str(e, "cdata");
+      if (str) {
+        if (!credits_names) credits_names = string_list_create();
+        string_list_insert(credits_names, str);
+
+        if (!*out_credits) *out_credits = htsmsg_create_map();
+        htsmsg_add_str(*out_credits, str, f->hmf_name);
+      }
+    }
+  }
+
+  return credits_names;
+}
+
 /**
  * Parse tags inside of a programme
  */
@@ -462,6 +567,8 @@ static int _xmltv_parse_programme_tags
    time_t start, time_t stop, const char *icon,
    epggrab_stats_t *stats)
 {
+  const int scrape_extra = ((epggrab_module_ext_t *)mod)->xmltv_scrape_extra;
+  const int scrape_onto_desc = ((epggrab_module_ext_t *)mod)->xmltv_scrape_onto_desc;
   int save = 0, save2 = 0, save3 = 0;
   epg_episode_t *ee = NULL;
   epg_serieslink_t *es = NULL;
@@ -488,8 +595,64 @@ static int _xmltv_parse_programme_tags
 
   /* Description (wait for episode first) */
   _xmltv_parse_lang_str(&desc, tags, "desc");
-  if (desc)
+
+  /* If user has requested it then retrieve additional information
+   * from programme such as credits and keywords.
+   */
+  if (scrape_extra || scrape_onto_desc) {
+    htsmsg_t      *credits        = NULL;
+    string_list_t *credits_names  = _xmltv_parse_credits(&credits, tags);
+    string_list_t *category       = _xmltv_make_str_list_from_matching(tags, "category");
+    string_list_t *keyword        = _xmltv_make_str_list_from_matching(tags, "keyword");
+
+    if (scrape_extra && credits) {
+      save3 |= epg_broadcast_set_credits(ebc, credits, &changes);
+    }
+
+    if (scrape_extra && category) {
+      save3 |= epg_broadcast_set_category(ebc, category, &changes);
+    }
+
+    if (scrape_extra && keyword) {
+      save3 |= epg_broadcast_set_keyword(ebc, keyword, &changes);
+    }
+
+    /* Convert the string list VAR to a human-readable csv and append
+     * it to the desc with a prefix of NAME.
+     */
+#define APPENDIT(VAR,NAME) \
+    if (VAR) { \
+      char *str = string_list_2_csv(VAR, ',', 1); \
+      if (str) {                                  \
+        lang_str_append(desc, "\n\n", NULL);      \
+        lang_str_append(desc, NAME, NULL);        \
+        lang_str_append(desc, str, NULL);         \
+        free(str);                                \
+      }                                           \
+    }
+
+    /* Append the details on to the description, mainly for legacy
+     * clients. This allow you to view the details in the description
+     * on old boxes/tablets that don't parse the newer fields or
+     * don't display them.
+     */
+    if (desc && scrape_onto_desc) {
+      APPENDIT(credits_names, N_("Credits: "));
+      APPENDIT(category, N_("Categories: "));
+      APPENDIT(keyword, N_("Keywords: "));
+    }
+
+    if (credits)          htsmsg_destroy(credits);
+    if (credits_names)    string_list_destroy(credits_names);
+    if (category)         string_list_destroy(category);
+    if (keyword)          string_list_destroy(keyword);
+
+#undef APPENDIT
+  } /* desc */
+
+  if (desc) {
     save3 |= epg_broadcast_set_description(ebc, desc, &changes);
+  } /* desc */
 
   /* summary */
   _xmltv_parse_lang_str(&summary, tags, "summary");
@@ -583,7 +746,6 @@ static int _xmltv_parse_programme_tags
   if (subtitle) lang_str_destroy(subtitle);
   if (desc)     lang_str_destroy(desc);
   if (summary)  lang_str_destroy(summary);
-  
   return save | save2 | save3;
 }
 
@@ -786,6 +948,29 @@ static int _xmltv_parse
   N_("Try to obtain channel numbers from the display-name xml tag. " \
      "If the first word is number, it is used as the channel number.")
 
+#define SCRAPE_EXTRA_NAME N_("Scrape credits and extra information")
+#define SCRAPE_EXTRA_DESC \
+  N_("Obtain list of credits (actors, etc.), keywords and extra information from the xml tags (if available). "  \
+     "Some xmltv providers supply a list of actors and additional keywords to " \
+     "describe programmes. This option will retrieve this additional information. " \
+     "This can be very detailed (20+ actors per movie) " \
+     "and will take a lot of memory and resources on this box, and will " \
+     "pass this information to your client machines and GUI too, using " \
+     "memory and resources on those boxes too. " \
+     "Do not enable on low-spec machines.")
+
+#define SCRAPE_ONTO_DESC_NAME N_("Alter programme description to include detailed information")
+#define SCRAPE_ONTO_DESC_DESC \
+  N_("If enabled then this will alter the programme descriptions to " \
+     "include information about actors, keywords and categories (if available from the xmltv file). " \
+     "This is useful for legacy clients that can not parse newer Tvheadend messages " \
+     "containing this information or do not display the information. "\
+     "For example the modified description might include 'Starring: Lorem Ipsum'. " \
+     "The description is altered for all clients, both legacy, modern, and GUI. "\
+     "Enabling scraping of detailed information can use significant resources (memory and CPU). "\
+     "You should not enable this if you use 'duplicate detect if different description' " \
+     "since the descriptions will change due to added information.")
+
 static htsmsg_t *
 xmltv_dn_chnum_list ( void *o, const char *lang )
 {
@@ -812,6 +997,22 @@ const idclass_t epggrab_mod_int_xmltv_class = {
       .opts   = PO_DOC_NLIST,
       .group  = 1
     },
+    {
+      .type   = PT_BOOL,
+      .id     = "scrape_extra",
+      .name   = SCRAPE_EXTRA_NAME,
+      .desc   = SCRAPE_EXTRA_DESC,
+      .off    = offsetof(epggrab_module_int_t, xmltv_scrape_extra),
+      .group  = 1
+    },
+    {
+      .type   = PT_BOOL,
+      .id     = "scrape_onto_desc",
+      .name   = SCRAPE_ONTO_DESC_NAME,
+      .desc   = SCRAPE_ONTO_DESC_DESC,
+      .off    = offsetof(epggrab_module_int_t, xmltv_scrape_onto_desc),
+      .group  = 1
+    },
     {}
   }
 };
@@ -830,6 +1031,22 @@ const idclass_t epggrab_mod_ext_xmltv_class = {
       .opts   = PO_DOC_NLIST,
       .group  = 1
     },
+    {
+      .type   = PT_BOOL,
+      .id     = "scrape_extra",
+      .name   = SCRAPE_EXTRA_NAME,
+      .desc   = SCRAPE_EXTRA_DESC,
+      .off    = offsetof(epggrab_module_int_t, xmltv_scrape_extra),
+      .group  = 1
+    },
+    {
+      .type   = PT_BOOL,
+      .id     = "scrape_onto_desc",
+      .name   = SCRAPE_ONTO_DESC_NAME,
+      .desc   = SCRAPE_ONTO_DESC_DESC,
+      .off    = offsetof(epggrab_module_int_t, xmltv_scrape_onto_desc),
+      .group  = 1
+    },
     {}
   }
 };
index 018f64b5e191cf476dc075f4b3f687f42bb77ac6..667f70dade15e443b432479410da27f1c21848f0 100644 (file)
@@ -35,6 +35,7 @@
 #include "descrambler/caid.h"
 #include "notify.h"
 #include "htsmsg_json.h"
+#include "string_list.h"
 #include "lang_codes.h"
 #if ENABLE_TIMESHIFT
 #include "timeshift.h"
@@ -1224,6 +1225,17 @@ htsp_build_event
       htsmsg_add_str(out, "summary", str);
   } else if((str = epg_broadcast_get_summary(e, lang)))
     htsmsg_add_str(out, "description", str);
+
+  if (e->credits) {
+    htsmsg_add_msg(out, "credits", htsmsg_copy(e->credits));
+  }
+  if (e->category) {
+    htsmsg_add_msg(out, "category", string_list_to_htsmsg(e->category));
+  }
+  if (e->keyword) {
+    htsmsg_add_msg(out, "keyword", string_list_to_htsmsg(e->keyword));
+  }
+
   if (e->serieslink) {
     htsmsg_add_u32(out, "serieslinkId", e->serieslink->id);
     if (e->serieslink->uri)
index d31dbddfdada86e3972d445e86dac79d5d76f76c..d0beebdb1d198d0d3f30ace7fcad9245f323ef0e 100644 (file)
@@ -64,9 +64,11 @@ lang_str_t     *lang_str_deserialize
 /* Compare */
 int             lang_str_compare ( const lang_str_t *ls1, const lang_str_t *ls2 );
 
-/* Empty */
-int             strempty(const char* c);
-int             lang_str_empty(lang_str_t* str);
+/* Is string empty? */
+int             strempty(const char* c)
+    __attribute__((warn_unused_result));
+int             lang_str_empty(lang_str_t* str)
+    __attribute__((warn_unused_result));
 
 /* Size in bytes */
 size_t          lang_str_size ( const lang_str_t *ls );