]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
EPG genre code has been re-written. I have removed the use of a nasty array (terminat...
authorAdam Sutton <dev@adamsutton.me.uk>
Fri, 6 Jul 2012 14:30:36 +0000 (15:30 +0100)
committerAdam Sutton <dev@adamsutton.me.uk>
Fri, 6 Jul 2012 14:30:36 +0000 (15:30 +0100)
Relates to #29

14 files changed:
src/dvr/dvr.h
src/dvr/dvr_autorec.c
src/dvr/dvr_db.c
src/dvr/mkmux.c
src/epg.c
src/epg.h
src/epggrab/module/eit.c
src/epggrab/module/opentv.c
src/epggrab/module/pyepg.c
src/epggrab/module/xmltv.c
src/htsp.c
src/webui/extjs.c
src/webui/static/app/dvr.js
src/webui/static/app/epg.js

index 41bc40143dae28fec0e0e1e8d8da2880e5dee6ff..d960205f4a961a9d87fedd39601f3df6052acde1 100644 (file)
@@ -132,7 +132,7 @@ typedef struct dvr_entry {
   char *de_ititle;     /* Internal title optionally with channelname
                          date and time pre/post/fixed */
   char *de_desc;       /* Description in UTF-8 (from EPG) */
-  uint8_t de_content_type; /* Content type (from EPG) */
+  epg_genre_t de_content_type; /* Content type (from EPG) */
 
   dvr_prio_t de_pri;
 
@@ -205,7 +205,7 @@ typedef struct dvr_autorec_entry {
   char *dae_title;
   regex_t dae_title_preg;
   
-  uint8_t dae_content_type;
+  epg_genre_t dae_content_type;
 
   int dae_approx_time; /* Minutes from midnight */
 
@@ -259,7 +259,7 @@ dvr_entry_t *dvr_entry_create(const char *dvr_config_name,
                               channel_t *ch, time_t start, time_t stop, 
             time_t start_extra, time_t stop_extra,
                              const char *title, const char *description,
-            uint8_t content_type,
+            epg_genre_t *content_type,
                              const char *creator, dvr_autorec_entry_t *dae,
                              dvr_prio_t pri);
 
@@ -327,7 +327,7 @@ void dvr_query_sort(dvr_query_result_t *dqr);
  */
 void dvr_autorec_add(const char *dvr_config_name,
                      const char *title, const char *channel,
-                    const char *tag, uint8_t content_type,
+                    const char *tag, epg_genre_t *content_type,
                     const char *creator, const char *comment);
 
 void dvr_autorec_add_series_link(const char *dvr_config_name,
index 0a32501cb5eda89c151c604bbfd1f7e374d0554d..ad9cb442a878e2ad963dd955376f48c7016ecac7 100644 (file)
@@ -74,7 +74,7 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
 
   if(dae->dae_channel == NULL &&
      dae->dae_channel_tag == NULL &&
-     dae->dae_content_type == 0 &&
+     dae->dae_content_type.code == 0 &&
      (dae->dae_title == NULL ||
      dae->dae_title[0] == '\0') &&
      dae->dae_brand == NULL &&
@@ -109,15 +109,9 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
       return 0;
   }
 
-  if(dae->dae_content_type != 0) {
-    int i, ok = 0;
-    for (i = 0; i < e->episode->genre_cnt; i++) {
-      if (e->episode->genre[i] == dae->dae_content_type) {
-        ok = 1;
-        break;
-      }
-    }
-    if (!ok) return 0;
+  if(dae->dae_content_type.code != 0) {
+    if (!epg_genre_list_contains(&e->episode->genre, &dae->dae_content_type, 1))
+      return 0;
   }
 
   if(dae->dae_approx_time != 0) {
@@ -256,7 +250,6 @@ build_weekday_mask(const char *str)
 static htsmsg_t *
 autorec_record_build(dvr_autorec_entry_t *dae)
 {
-  const char *s;
   char str[30];
   htsmsg_t *e = htsmsg_create_map();
 
@@ -276,11 +269,7 @@ autorec_record_build(dvr_autorec_entry_t *dae)
   if(dae->dae_channel_tag != NULL)
     htsmsg_add_str(e, "tag", dae->dae_channel_tag->ct_name);
 
-  // Note: Mixed usage creates problems, for now we have to store
-  //       both values!
-  htsmsg_add_u32(e, "contenttype",dae->dae_content_type);
-  if ((s = epg_content_group_get_name(dae->dae_content_type)))
-    htsmsg_add_str(e, "contentgrp", s);
+  htsmsg_add_u32(e, "contenttype",dae->dae_content_type.code);
 
   htsmsg_add_str(e, "title", dae->dae_title ?: "");
 
@@ -394,15 +383,7 @@ autorec_record_update(void *opaque, const char *id, htsmsg_t *values,
     }
   }
 
-  // Note: unfortunately there is a mixed usage here, DVR code uses
-  // contenttype however UI code uses contentgrp. so we test for both!
-  if (htsmsg_get_u32(values, "contenttype", &u32)) {
-    if ((s = htsmsg_get_str(values, "contentgrp")))
-      u32 = epg_content_group_find_by_name(s);
-    else
-      u32 = 0;
-  }
-  dae->dae_content_type = u32;
+  dae->dae_content_type.code = htsmsg_get_u32_or_default(values, "contenttype", 0);
 
   if((s = htsmsg_get_str(values, "approx_time")) != NULL) {
     if(strchr(s, ':') != NULL) {
@@ -483,7 +464,7 @@ dvr_autorec_init(void)
 static void
 _dvr_autorec_add(const char *config_name,
                 const char *title, channel_t *ch,
-               const char *tag, uint8_t content_type,
+               const char *tag, epg_genre_t *content_type,
     epg_brand_t *brand, epg_season_t *season,
     int approx_time, epg_episode_num_t *epnum,
                const char *creator, const char *comment)
@@ -516,7 +497,8 @@ _dvr_autorec_add(const char *config_name,
   }
 
   dae->dae_enabled = 1;
-  dae->dae_content_type = content_type;
+  if (content_type)
+    dae->dae_content_type.code = content_type->code;
 
   if(brand) {
     dae->dae_brand = brand;
@@ -548,7 +530,7 @@ _dvr_autorec_add(const char *config_name,
 void
 dvr_autorec_add(const char *config_name,
                 const char *title, const char *channel,
-               const char *tag, uint8_t content_type,
+               const char *tag, epg_genre_t *content_type,
                const char *creator, const char *comment)
 {
   channel_t *ch = NULL;
index 08ca7bdc996272b249ad85048fe4c4c2005dcd94..61a435cb1cffc6644e605ce452a10704d50e2fba 100644 (file)
@@ -249,7 +249,7 @@ static dvr_entry_t *_dvr_entry_create (
   channel_t *ch, time_t start, time_t stop, 
   time_t start_extra, time_t stop_extra,
        const char *title, const char *description,
-  uint8_t content_type,
+  epg_genre_t *content_type,
        const char *creator, dvr_autorec_entry_t *dae,
        dvr_prio_t pri)
 {
@@ -301,7 +301,7 @@ static dvr_entry_t *_dvr_entry_create (
   de->de_creator = strdup(creator);
   de->de_title   = strdup(title);
   de->de_desc    = description ? strdup(description) : NULL;
-  de->de_content_type = content_type;
+  if (content_type) de->de_content_type = *content_type;
   de->de_bcast   = e;
   if (e) e->getref((epg_object_t*)e);
 
@@ -334,7 +334,7 @@ dvr_entry_create(const char *config_name,
                  channel_t *ch, time_t start, time_t stop, 
                  time_t start_extra, time_t stop_extra,
                             const char *title, const char *description,
-                 uint8_t content_type,
+                 epg_genre_t *content_type,
                             const char *creator, dvr_autorec_entry_t *dae,
                  dvr_prio_t pri)
 {
@@ -363,7 +363,7 @@ dvr_entry_create_by_event(const char *config_name,
                            e->episode->title,
                            e->episode->description ? e->episode->description
                                                    : e->episode->summary,
-                           e->episode->genre_cnt ? e->episode->genre[0] : 0,
+                           LIST_FIRST(&e->episode->genre),
                            creator, dae, pri);
 }
 
@@ -530,7 +530,7 @@ dvr_db_load_one(htsmsg_t *c, int id)
   }
 
 
-  de->de_content_type = htsmsg_get_u32_or_default(c, "contenttype", 0);
+  de->de_content_type.code = htsmsg_get_u32_or_default(c, "contenttype", 0);
 
   if (!htsmsg_get_u32(c, "broadcast", &bcid)) {
     de->de_bcast = epg_broadcast_find_by_id(bcid, ch);
@@ -606,8 +606,8 @@ dvr_entry_save(dvr_entry_t *de)
   if(de->de_autorec != NULL)
     htsmsg_add_str(m, "autorec", de->de_autorec->dae_id);
 
-  if(de->de_content_type)
-    htsmsg_add_u32(m, "contenttype", de->de_content_type);
+  if(de->de_content_type.code)
+    htsmsg_add_u32(m, "contenttype", de->de_content_type.code);
 
   if(de->de_bcast)
     htsmsg_add_u32(m, "broadcast", de->de_bcast->id);
@@ -666,10 +666,12 @@ static dvr_entry_t *_dvr_entry_update
   }
 
   if (e) {
-    if (e->episode && 
-        e->episode->genre_cnt && e->episode->genre_cnt != de->de_content_type) {
-      de->de_content_type = e->episode->genre[0];
-      save = 1;
+    epg_genre_t *g;
+    if (e->episode && (g = LIST_FIRST(&e->episode->genre))) {
+      if (g->code != de->de_content_type.code) {
+        de->de_content_type.code = g->code;
+        save = 1;
+      }
     }
     if (de->de_bcast != e) {
       de->de_bcast->putref((epg_object_t*)de->de_bcast);
index 37fbc750658b63aab91cee724747c17c99b74c03..b0018ce696dc1ed672c05dcecbe5e2196e6bc1a5 100644 (file)
@@ -464,9 +464,9 @@ static htsbuf_queue_t *
 _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
 {
   htsbuf_queue_t *q = htsbuf_queue_alloc(0);
-  char datestr[64];
+  char datestr[64], ctype[100];
+  const epg_genre_t *eg = NULL;
   struct tm tm;
-  const char *ctype = NULL;
   localtime_r(de ? &de->de_start : &ebc->start, &tm);
   epg_episode_t *ee = NULL;
   channel_t *ch;
@@ -490,12 +490,12 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
 
   addtag(q, build_tag_string("ORIGINAL_MEDIA_TYPE", "TV", 0, NULL));
 
-  if(de && de->de_content_type) {
-    ctype = epg_genre_get_name(de->de_content_type, 0);
-  } else if (ee && ee->genre_cnt) {
-    ctype = epg_genre_get_name(ee->genre[0], 0);
+  if(de && de->de_content_type.code) {
+    eg = &de->de_content_type;
+  } else if (ee) {
+    eg = LIST_FIRST(&ee->genre);
   }
-  if(ctype != NULL)
+  if(eg && epg_genre_get_str(eg, 1, 0, ctype, 100))
     addtag(q, build_tag_string("CONTENT_TYPE", ctype, 0, NULL));
 
   if(ch)
index 10ce1a7ece51eba3def853dc6f18ed733e11cac4..3c7b021409721ef950e76e426e72441166af58d7 100644 (file)
--- a/src/epg.c
+++ b/src/epg.c
@@ -618,6 +618,7 @@ static epg_episode_num_t *epg_episode_num_deserialize
 
 static void _epg_episode_destroy ( void *eo )
 {
+  epg_genre_t *g;
   epg_episode_t *ee = eo;
   if (LIST_FIRST(&ee->broadcasts)) {
     tvhlog(LOG_CRIT, "epg", "attempt to destroy episode with broadcasts");
@@ -630,7 +631,10 @@ static void _epg_episode_destroy ( void *eo )
   if (ee->subtitle)    free(ee->subtitle);
   if (ee->summary)     free(ee->summary);
   if (ee->description) free(ee->description);
-  if (ee->genre)       free(ee->genre);
+  while ((g = LIST_FIRST(&ee->genre))) {
+    LIST_REMOVE(g, link);
+    free(g);
+  }
   if (ee->image)       free(ee->image);
   if (ee->epnum.text)  free(ee->epnum.text);
   free(ee);
@@ -761,53 +765,36 @@ int epg_episode_set_season ( epg_episode_t *episode, epg_season_t *season )
   return save;
 }
 
-int epg_episode_set_genre ( epg_episode_t *ee, const uint8_t *genre, int cnt )
+int epg_episode_set_genre ( epg_episode_t *ee, epg_genre_list_t *genre )
 {
-  int i, save = 0;
-  if (!ee || !genre || !cnt) return 0;
-  if (cnt != ee->genre_cnt)
-    save = 1;
-  else {
-    for (i = 0; i < cnt; i++ ) {
-      if (genre[i] != ee->genre[i]) {
-        save = 1;
-        break;
-      }
+  int save = 0;
+  epg_genre_t *g1, *g2;
+
+  /* Remove old */
+  g1 = LIST_FIRST(&ee->genre);
+  while (g1) {
+    g2 = LIST_NEXT(g1, link);
+    if (!epg_genre_list_contains(genre, g1, 0)) {
+      LIST_REMOVE(g1, link);
+      save = 1;
     }
+    g1 = g2;
   }
-  if (save) {
-    if (cnt > ee->genre_cnt)
-      ee->genre     = realloc(ee->genre, cnt * sizeof(uint8_t));
-    memcpy(ee->genre, genre, cnt * sizeof(uint8_t));
-    ee->genre_cnt = cnt;
+  
+  /* Insert all entries */
+  LIST_FOREACH(g1, genre, link) {
+    save |= epg_genre_list_add(&ee->genre, g1);
   }
-  return save;
-}
 
-// Note: only works for the EN 300 468 defined names
-int epg_episode_set_genre_str ( epg_episode_t *ee, const char **gstr )
-{
-  static int gcnt = 0;
-  static uint8_t *genre;
-  int cnt = 0;
-  while (gstr[cnt]) cnt++;
-  if (!cnt) return 0;
-  if (cnt > gcnt) {
-    genre = realloc(genre, sizeof(uint8_t) * cnt);
-    gcnt  = cnt;
-  }
-  cnt = 0;
-  while (gstr[cnt]) {
-    genre[cnt] = epg_genre_find_by_name(gstr[cnt]);
-    cnt++;
-  }
-  return epg_episode_set_genre(ee, genre, gcnt);
+  return save;
 }
 
 int epg_episode_set_is_bw ( epg_episode_t *e, uint8_t bw )
 {
+  int save = 0;
   if (!e) return 0;
   return _epg_object_set_u8(e, &e->is_bw, bw);
+  return save;
 }
 
 static void _epg_episode_add_broadcast 
@@ -893,7 +880,8 @@ int epg_episode_fuzzy_match
 
 htsmsg_t *epg_episode_serialize ( epg_episode_t *episode )
 {
-  htsmsg_t *m;
+  epg_genre_t *eg;
+  htsmsg_t *m, *a = NULL;
   if (!episode || !episode->uri) return NULL;
   if (!(m = _epg_object_serialize((epg_object_t*)episode))) return NULL;
   htsmsg_add_str(m, "uri", episode->uri);
@@ -905,7 +893,12 @@ htsmsg_t *epg_episode_serialize ( epg_episode_t *episode )
     htsmsg_add_str(m, "summary", episode->summary);
   if (episode->description)
     htsmsg_add_str(m, "description", episode->description);
-  htsmsg_add_msg(m, "epnum", epg_episode_num_serialize(&episode->epnum)); 
+  htsmsg_add_msg(m, "epnum", epg_episode_num_serialize(&episode->epnum));
+  LIST_FOREACH(eg, &episode->genre, link) {
+    if (!a) a = htsmsg_create_list();
+    htsmsg_add_u32(a, NULL, eg->code);
+  }
+  if (a) htsmsg_add_msg(m, "genre", a);
   if (episode->brand)
     htsmsg_add_str(m, "brand", episode->brand->uri);
   if (episode->season)
@@ -924,6 +917,7 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
   const char *str;
   epg_episode_num_t num;
   htsmsg_t *sub;
+  htsmsg_field_t *f;
   
   if ( !_epg_object_deserialize(m, *skel) ) return NULL;
   if ( !(ee = epg_episode_find_by_uri((*skel)->uri, create, save)) ) return NULL;
@@ -941,6 +935,17 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
     *save |= epg_episode_set_epnum(ee, &num);
     if (num.text) free(num.text);
   }
+  if ( (sub = htsmsg_get_list(m, "genre")) ) {
+    epg_genre_list_t *egl = calloc(1, sizeof(epg_genre_list_t));
+    HTSMSG_FOREACH(f, sub) {
+      epg_genre_t genre;
+      genre.code = (uint8_t)f->hmf_s64;
+      epg_genre_list_add(egl, &genre);
+    }
+    *save |= epg_episode_set_genre(ee, egl);
+    epg_genre_list_destroy(egl);
+  }
+  
   if ( (str = htsmsg_get_str(m, "season")) )
     if ( (es = epg_season_find_by_uri(str, 0, NULL)) )
       *save |= epg_episode_set_season(ee, es);
@@ -1336,11 +1341,12 @@ epg_broadcast_t *epg_broadcast_deserialize
  * Genre
  * *************************************************************************/
 
+// TODO: make this configurable
 // FULL(ish) list from EN 300 468, I've excluded the last category
 // that relates more to broadcast content than what I call a "genre"
 // these will be handled elsewhere as broadcast metadata
 static const char *_epg_genre_names[16][16] = {
-  {},
+  { "" },
   {
     "Movie/Drama",
     "detective/thriller",
@@ -1507,7 +1513,7 @@ static int _genre_str_match ( const char *a, const char *b )
   return (a[i] == '\0' && b[j] == '\0'); // end of string(both)
 }
 
-uint8_t epg_genre_find_by_name ( const char *name )
+static uint8_t _epg_genre_find_by_name ( const char *name )
 {
   uint8_t a, b;
   for ( a = 1; a < 11; a++ ) {
@@ -1519,12 +1525,131 @@ uint8_t epg_genre_find_by_name ( const char *name )
   return 0; // undefined
 }
 
-const char *epg_genre_get_name ( uint8_t genre, int full )
+uint8_t epg_genre_get_eit ( const epg_genre_t *genre )
+{
+  if (!genre) return 0;
+  return genre->code;
+}
+
+size_t epg_genre_get_str ( const epg_genre_t *genre, int major_only,
+                           int major_prefix, char *buf, size_t len )
+{
+  int maj, min;
+  size_t ret = 0;
+  if (!genre || !buf) return 0;
+  maj = (genre->code >> 4) & 0xf;
+  if (!_epg_genre_names[maj][0]) return 0;
+  min = major_only ? 0 : (genre->code & 0xf);
+  if (!min || major_prefix ) {
+    ret = snprintf(buf, len, "%s", _epg_genre_names[maj][0]);
+    if (min) ret += snprintf(buf+ret, len-ret, " : ");
+  }
+  if (min && _epg_genre_names[maj][min]) {
+    ret += snprintf(buf+ret, len-ret, "%s", _epg_genre_names[maj][min]);
+  }
+  return ret;
+}
+
+int epg_genre_list_add ( epg_genre_list_t *list, epg_genre_t *genre )
+{
+  epg_genre_t *g1, *g2;
+  if (!list || !genre || !genre->code) return 0;
+  g1 = LIST_FIRST(list);
+  if (!g1) {
+    g2 = calloc(1, sizeof(epg_genre_t));
+    g2->code = genre->code;
+    LIST_INSERT_HEAD(list, g2, link);
+  } else {
+    while (g1) {
+    
+      /* Already exists */
+      if (g1->code == genre->code) return 0;
+
+      /* Update a major only entry */
+      if (g1->code == (genre->code & 0xF0)) {
+        g1->code = genre->code;
+        break;
+      }
+
+      /* Insert before */
+      if (g1->code > genre->code) {
+        g2 = calloc(1, sizeof(epg_genre_t));
+        g2->code = genre->code;
+        LIST_INSERT_BEFORE(g1, g2, link);
+        break;
+      }
+
+      /* Insert after (end) */
+      if (!(g2 = LIST_NEXT(g1, link))) {
+        g2 = calloc(1, sizeof(epg_genre_t));
+        g2->code = genre->code;
+        LIST_INSERT_AFTER(g1, g2, link);
+        break;
+      }
+
+      /* Next */
+      g1 = g2;
+    }
+  }
+  return 1;
+}
+
+int epg_genre_list_add_by_eit ( epg_genre_list_t *list, uint8_t eit )
 {
-  int a, b = 0;
-  a = (genre >> 4) & 0xF;
-  if (full) b = (genre & 0xF);
-  return _epg_genre_names[a][b];
+  epg_genre_t g;
+  g.code = eit;
+  return epg_genre_list_add(list, &g);
+}
+
+int epg_genre_list_add_by_str ( epg_genre_list_t *list, const char *str )
+{
+  epg_genre_t g;
+  g.code = _epg_genre_find_by_name(str);
+  return epg_genre_list_add(list, &g);
+}
+
+// Note: if partial=1 and genre is a major only category then all minor
+// entries will also match
+int epg_genre_list_contains 
+  ( epg_genre_list_t *list, epg_genre_t *genre, int partial )
+{
+  uint8_t mask = 0xFF;
+  epg_genre_t *g;
+  if (!list || !genre) return 0;
+  if (partial && !(genre->code & 0x0F)) mask = 0xF0;
+  LIST_FOREACH(g, list, link) {
+    if ((g->code & mask) == genre->code) break;
+  }
+  return g ? 1 : 0;
+}
+
+void epg_genre_list_destroy ( epg_genre_list_t *list )
+{
+  epg_genre_t *g;
+  while ((g = LIST_FIRST(list))) {
+    LIST_REMOVE(g, link);
+    free(g);
+  }
+  free(list);
+}
+
+htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix )
+{
+  int i, j;
+  htsmsg_t *e, *m;
+  m = htsmsg_create_list();
+  for (i = 0; i < 16; i++ ) {
+    for (j = 0; j < (major_only ? 1 : 16); j++) {
+      if (_epg_genre_names[i][j]) {
+        e = htsmsg_create_map();
+        htsmsg_add_u32(e, "code", i << 4 | j);
+        htsmsg_add_str(e, "name", _epg_genre_names[i][j]);
+        // TODO: use major_prefix
+        htsmsg_add_msg(m, NULL, e);
+      }
+    }
+  }
+  return m;
 }
 
 /* **************************************************************************
@@ -1533,12 +1658,12 @@ const char *epg_genre_get_name ( uint8_t genre, int full )
 
 static void _eqr_add 
   ( epg_query_result_t *eqr, epg_broadcast_t *e,
-    uint8_t genre, regex_t *preg, time_t start )
+    epg_genre_t *genre, regex_t *preg, time_t start )
 {
   /* Ignore */
   if ( e->stop < start ) return;
-  if ( genre && e->episode->genre_cnt && e->episode->genre[0] != genre ) return;
   if ( !e->episode->title ) return;
+  if ( genre && !epg_genre_list_contains(&e->episode->genre, genre, 1) ) return;
   if ( preg && regexec(preg, e->episode->title, 0, NULL, 0) ) return;
 
   /* More space */
@@ -1553,7 +1678,7 @@ static void _eqr_add
 }
 
 static void _eqr_add_channel 
-  ( epg_query_result_t *eqr, channel_t *ch, uint8_t genre,
+  ( epg_query_result_t *eqr, channel_t *ch, epg_genre_t *genre,
     regex_t *preg, time_t start )
 {
   epg_broadcast_t *ebc;
@@ -1564,7 +1689,7 @@ static void _eqr_add_channel
 
 void epg_query0
   ( epg_query_result_t *eqr, channel_t *channel, channel_tag_t *tag,
-    uint8_t genre, const char *title )
+    epg_genre_t *genre, const char *title )
 {
   time_t now;
   channel_tag_mapping_t *ctm;
@@ -1606,12 +1731,11 @@ void epg_query0
 }
 
 void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
-              const char *genre, const char *title)
+              epg_genre_t *genre, const char *title)
 {
   channel_t     *ch = channel ? channel_find_by_name(channel, 0, 0) : NULL;
   channel_tag_t *ct = tag     ? channel_tag_find_by_name(tag, 0)    : NULL;
-  uint8_t        ge = genre   ? epg_genre_find_by_name(genre) : 0;
-  epg_query0(eqr, ch, ct, ge, title);
+  epg_query0(eqr, ch, ct, genre, title);
 }
 
 void epg_query_free(epg_query_result_t *eqr)
index 116fec8a3f17920aa95b01bc6b6a251f85539d08..e04365b5f0affdf53ced6bab51f0c535521d6ef3 100644 (file)
--- a/src/epg.h
+++ b/src/epg.h
@@ -28,7 +28,7 @@ struct channel;
 struct channel_tag;
 
 /*
- * Map types
+ * Map/List types
  */
 LIST_HEAD(epg_object_list,     epg_object);
 RB_HEAD  (epg_object_tree,     epg_object);
@@ -37,10 +37,12 @@ LIST_HEAD(epg_season_list,     epg_season);
 LIST_HEAD(epg_episode_list,    epg_episode);
 LIST_HEAD(epg_broadcast_list,  epg_broadcast);
 RB_HEAD  (epg_broadcast_tree,  epg_broadcast);
+LIST_HEAD(epg_genre_list,      epg_genre);
 
 /*
  * Typedefs (most are redundant!)
  */
+typedef struct epg_genre           epg_genre_t;
 typedef struct epg_object          epg_object_t;
 typedef struct epg_brand           epg_brand_t;
 typedef struct epg_season          epg_season_t;
@@ -52,6 +54,38 @@ typedef struct epg_broadcast_list  epg_broadcast_list_t;
 typedef struct epg_broadcast_tree  epg_broadcast_tree_t;
 typedef struct epg_object_list     epg_object_list_t;
 typedef struct epg_object_tree     epg_object_tree_t;
+typedef struct epg_genre_list      epg_genre_list_t;
+
+/* ************************************************************************
+ * Genres
+ * ***********************************************************************/
+
+/* Genre object */
+struct epg_genre
+{
+  LIST_ENTRY(epg_genre) link;
+  uint8_t               code;
+};
+
+/* Accessors */
+uint8_t epg_genre_get_eit ( const epg_genre_t *genre );
+size_t  epg_genre_get_str ( const epg_genre_t *genre, int major_only,
+                            int major_prefix, char *buf, size_t len );
+
+/* Delete */
+void epg_genre_list_destroy   ( epg_genre_list_t *list );
+
+/* Add to list */
+int epg_genre_list_add        ( epg_genre_list_t *list, epg_genre_t *genre );
+int epg_genre_list_add_by_eit ( epg_genre_list_t *list, uint8_t eit );
+int epg_genre_list_add_by_str ( epg_genre_list_t *list, const char *str );
+
+/* Search */
+int epg_genre_list_contains
+  ( epg_genre_list_t *list, epg_genre_t *genre, int partial );
+
+/* List all available genres */
+htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix );
 
 /* ************************************************************************
  * Generic Object
@@ -198,11 +232,10 @@ struct epg_episode
   char                      *subtitle;      ///< Sub-title
   char                      *summary;       ///< Summary
   char                      *description;   ///< An extended description
-  uint8_t                   *genre;         ///< Episode genre(s)
-  int                        genre_cnt;     ///< Genre count
+  char                      *image;         ///< Episode image
+  epg_genre_list_t           genre;         ///< Episode genre(s)
   epg_episode_num_t          epnum;         ///< Episode numbering
   // Note: do not use epnum directly! use the accessor routine
-  char                      *image;         ///< Episode image
 
   uint8_t                    is_bw;            ///< Is black and white
   // TODO: certification and rating
@@ -213,7 +246,6 @@ struct epg_episode
   epg_brand_t               *brand;         ///< (Grand-)Parent brand
   epg_season_t              *season;        ///< Parent season
   epg_broadcast_list_t       broadcasts;    ///< Broadcast list
-
 };
 
 /* Lookup */
@@ -241,7 +273,7 @@ int epg_episode_set_brand        ( epg_episode_t *e, epg_brand_t *b )
   __attribute__((warn_unused_result));
 int epg_episode_set_season       ( epg_episode_t *e, epg_season_t *s )
   __attribute__((warn_unused_result));
-int epg_episode_set_genre        ( epg_episode_t *e, const uint8_t *g, int c )
+int epg_episode_set_genre        ( epg_episode_t *e, epg_genre_list_t *g )
   __attribute__((warn_unused_result));
 int epg_episode_set_genre_str    ( epg_episode_t *e, const char **s )
   __attribute__((warn_unused_result));
@@ -359,13 +391,6 @@ epg_broadcast_t *epg_broadcast_deserialize
 /* Unlink */
 void epg_channel_unlink ( struct channel *ch );
 
-/* ************************************************************************
- * Genre
- * ***********************************************************************/
-
-uint8_t     epg_genre_find_by_name ( const char *name );
-const char *epg_genre_get_name     ( uint8_t genre, int full );
-
 /* ************************************************************************
  * Querying
  * ***********************************************************************/
@@ -387,9 +412,9 @@ void epg_query_sort(epg_query_result_t *eqr);
 
 /* Query routines */
 void epg_query0(epg_query_result_t *eqr, struct channel *ch,
-                struct channel_tag *ct, uint8_t type, const char *title);
+                struct channel_tag *ct, epg_genre_t *genre, const char *title);
 void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
-              const char *contentgroup, const char *title);
+              epg_genre_t *genre, const char *title);
 
 
 /* ************************************************************************
index 83e9f82320a864a0117dce9e3f23fe0badc7e3ef..f8eb26d6814ae2fb3afb1b849a636f99ad366733 100644 (file)
@@ -152,13 +152,12 @@ static int _eit_callback
   channel_t *ch;
   epg_broadcast_t *ebc;
   epg_episode_t *ee;
+  epg_genre_list_t genre;
   eit_status_t *sta;
   int resched = 0, save = 0, save2 = 0, dllen, dtag, dlen;
   uint16_t tsid, sid, eid;
   uint8_t bw, hd, ws, ad, ds, st;
   time_t start, stop;
-  int genre_idx = 0;
-  uint8_t genre[10];
   char title[256];
   char summary[256];
   char desc[5000];
@@ -260,7 +259,6 @@ static int _eit_callback
     /* Process tags */
     *title    = *summary = *desc = 0;
     extra     = NULL;
-    genre_idx = 0;
     hd = ws = bw = ad = st = ds = 0;
     while(dllen > 0) {
       dtag = ptr[0];
@@ -296,8 +294,8 @@ static int _eit_callback
             dlen -= 2;
             if   ( *ptr == 0xb1 )
               bw = 1;
-            else if ( *ptr < 0xb0 && genre_idx < sizeof(genre) )
-              genre[genre_idx++] = *ptr;
+            else if ( *ptr < 0xb0 )
+              epg_genre_list_add_by_eit(&genre, *ptr);
           }
           break;
 
@@ -392,14 +390,17 @@ static int _eit_callback
         save |= epg_episode_set_summary(ee, summary);
       if ( !ee->description && *desc )
         save |= epg_episode_set_description(ee, desc);
-      if ( !ee->genre_cnt && genre_idx )
-        save |= epg_episode_set_genre(ee, genre, genre_idx);
+      if ( !LIST_FIRST(&ee->genre) && LIST_FIRST(&genre) )
+        save |= epg_episode_set_genre(ee, &genre);
 #if TODO_ADD_EXTRA
       if ( extra )
         save |= epg_episode_set_extra(ee, extra);
 #endif
     }
+
+    /* Tidy up */
     if (extra) free(extra);
+    epg_genre_list_destroy(&genre);
   }
   
   /* Update EPG */
index 248a11bd8487763b87617ddeaee4cce100719def..8e8c8f93b88d4b7a06768ae2ff0ea0ebc0685eba 100644 (file)
@@ -405,8 +405,12 @@ static int _opentv_parse_event_section
           save |= epg_episode_set_summary(ee, ev.summary);
         if (ev.desc)
           save |= epg_episode_set_description(ee, ev.desc);
-        if (ev.cat)
-          save |= epg_episode_set_genre(ee, &ev.cat, 1);
+        if (ev.cat) {
+          epg_genre_list_t *egl = calloc(1, sizeof(epg_genre_list_t));
+          epg_genre_list_add_by_eit(egl, ev.cat);
+          save |= epg_episode_set_genre(ee, egl);
+          epg_genre_list_destroy(egl);
+        }
         // Note: don't override the season (since the ID is channel specific
         //       it'll keep changing!
         if (ev.series && !ee->season) {
index 0d35f04b3a3873a36e27bf8729e10d2654a7c337..621eb743571c15f7995cd42ac3299fc7b058a80e 100644 (file)
@@ -55,10 +55,20 @@ static int _pyepg_parse_time ( const char *str, time_t *out )
   return ret;
 }
 
-static const uint8_t *_pyepg_parse_genre ( htsmsg_t *tags, int *cnt )
+static epg_genre_list_t
+*_pyepg_parse_genre ( htsmsg_t *tags )
 {
-  // TODO: implement this
-  return NULL;
+  htsmsg_t *e;
+  htsmsg_field_t *f;
+  epg_genre_list_t *egl = NULL;
+  HTSMSG_FOREACH(f, tags) {
+    if (!strcmp(f->hmf_name, "genre") && (e = htsmsg_get_map_by_field(f))) {
+      if (!egl) { egl = calloc(1, sizeof(epg_genre_list_t)); printf("alloc %p\n", egl); }
+      printf("GENRE %s\n", htsmsg_get_str(e, "cdata"));
+      epg_genre_list_add_by_str(egl, htsmsg_get_str(e, "cdata"));
+    }
+  }
+  return egl;
 }
 
 static int _pyepg_parse_channel ( htsmsg_t *data, epggrab_stats_t *stats )
@@ -197,14 +207,14 @@ static int _pyepg_parse_season ( htsmsg_t *data, epggrab_stats_t *stats )
 
 static int _pyepg_parse_episode ( htsmsg_t *data, epggrab_stats_t *stats )
 {
-  int genre_cnt, save = 0;
+  int save = 0;
   htsmsg_t *attr, *tags;
   epg_episode_t *episode;
   epg_season_t *season;
   epg_brand_t *brand;
   const char *str;
   uint32_t u32, pc, pn;
-  const uint8_t *genre;
+  epg_genre_list_t *egl;
 
   if ( data == NULL ) return 0;
 
@@ -262,8 +272,9 @@ static int _pyepg_parse_episode ( htsmsg_t *data, epggrab_stats_t *stats )
   }
 
   /* Genre */
-  if ((genre = _pyepg_parse_genre(tags, &genre_cnt))) {
-    save |= epg_episode_set_genre(episode, genre, genre_cnt);
+  if ((egl = _pyepg_parse_genre(tags))) {
+    save |= epg_episode_set_genre(episode, egl);
+    epg_genre_list_destroy(egl);
   }
 
   /* Content */
index b34fab50b2787b64459fc2411f2b7f9ea5e68573..38cbf0e8fd4cb68b233272f531c5962fc504c7b6 100644 (file)
@@ -286,6 +286,24 @@ xmltv_parse_accessibility ( epg_broadcast_t *ebc, htsmsg_t *m )
   return save;
 }
 
+/*
+ * Parse category list
+ */
+static epg_genre_list_t
+*_xmltv_parse_categories ( htsmsg_t *tags )
+{
+  htsmsg_t *e;
+  htsmsg_field_t *f;
+  epg_genre_list_t *egl = NULL;
+  HTSMSG_FOREACH(f, tags) {
+    if (!strcmp(f->hmf_name, "category") && (e = htsmsg_get_map_by_field(f))) {
+      if (!egl) egl = calloc(1, sizeof(epg_genre_list_t));
+      epg_genre_list_add_by_str(egl, htsmsg_get_str(e, "cdata"));
+    }
+  }
+  return egl;
+}
+
 /**
  * Parse tags inside of a programme
  */
@@ -296,12 +314,12 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags,
   int save = 0, save2 = 0;
   epg_episode_t *ee;
   epg_broadcast_t *ebc;
+  epg_genre_list_t *egl;
   int sn = 0, sc = 0, en = 0, ec = 0, pn = 0, pc = 0;
   const char *onscreen = NULL;
   char *uri;
-  const char *title = htsmsg_xml_get_cdata_str(tags, "title");
-  const char *desc  = htsmsg_xml_get_cdata_str(tags, "desc");
-  const char *category[2];
+  const char *title    = htsmsg_xml_get_cdata_str(tags, "title");
+  const char *desc     = htsmsg_xml_get_cdata_str(tags, "desc");
   get_episode_info(tags, &onscreen, &sn, &sc, &en, &ec, &pn, &pc);
 
   /* Ignore */
@@ -315,11 +333,12 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags,
   stats->episodes.total++;
   if (save) stats->episodes.created++;
 
-  category[0] = htsmsg_xml_get_cdata_str(tags, "category");
-  category[1] = NULL;
   if (title)     save |= epg_episode_set_title(ee, title);
   if (desc)      save |= epg_episode_set_description(ee, desc);
-  if (*category) save |= epg_episode_set_genre_str(ee, category);
+  if ((egl = _xmltv_parse_categories(tags))) {
+    save |= epg_episode_set_genre(ee, egl);
+    epg_genre_list_destroy(egl);
+  }
   if (pn)        save |= epg_episode_set_part(ee, pn, pc);
   if (en)        save |= epg_episode_set_number(ee, en);
   if (save) stats->episodes.modified++;
index edc7d8beb541a0ffa9550d09286f8f927f70aab8..8716c05906ca6966cd46db7e0952fa632a7dc301 100644 (file)
@@ -709,10 +709,11 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
   htsmsg_t *out, *eventIds;
   const char *query;
   int c, i;
-  uint32_t channelid, tagid, epg_content_dvbcode = 0;
+  uint32_t channelid, tagid, epg_content_dvbcode;
   channel_t *ch = NULL;
   channel_tag_t *ct = NULL;
   epg_query_result_t eqr;
+  epg_genre_t genre, *eg = NULL;
   
   //only mandatory parameter is the query
   if( (query = htsmsg_get_str(in, "query")) == NULL )
@@ -724,10 +725,13 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
   if( !(htsmsg_get_u32(in, "tagId", &tagid)) )
     ct = channel_tag_find_by_identifier(tagid);
 
-  htsmsg_get_u32(in, "contentType", &epg_content_dvbcode);
+  if (!htsmsg_get_u32(in, "contentType", &epg_content_dvbcode)) {
+    genre.code = epg_content_dvbcode;
+    eg = &genre;
+  }
 
   //do the query
-  epg_query0(&eqr, ch, ct, epg_content_dvbcode, query);
+  epg_query0(&eqr, ch, ct, eg, query);
   c = eqr.eqr_entries;
 
   // create reply
@@ -754,6 +758,7 @@ htsp_build_event(epg_broadcast_t *e)
   htsmsg_t *out;
   epg_broadcast_t *n;
   dvr_entry_t *de;
+  epg_genre_t *g;
 
   out = htsmsg_create_map();
 
@@ -769,9 +774,8 @@ htsp_build_event(epg_broadcast_t *e)
     else if(e->episode->summary != NULL)
       htsmsg_add_str(out, "description", e->episode->summary);
 
-    // TODO: only supports one entry!
-    if(e->episode->genre_cnt)
-      htsmsg_add_u32(out, "contentType", e->episode->genre[0]);
+    if((g = LIST_FIRST(&e->episode->genre)))
+      htsmsg_add_u32(out, "contentType", g->code);
   }
 
   if((de = dvr_entry_find_by_event(e)) != NULL) {
index a143ac69cd3fef7baf53a0f63604f78d0ec71209..c688b06363ee001b026baa0b1dbdec5755d814bd 100644 (file)
@@ -414,24 +414,11 @@ static int
 extjs_ecglist(http_connection_t *hc, const char *remain, void *opaque)
 {
   htsbuf_queue_t *hq = &hc->hc_reply;
-  htsmsg_t *out, *array, *c;
-  const char *s;
-  int i;
-
-  out = htsmsg_create_map();
-  array = htsmsg_create_list();
-
-  for(i = 0; i < 16; i++) {
-    if((s = epg_genre_get_name(i<<4, 0)) == NULL)
-      continue;
-
-    c = htsmsg_create_map();
-    htsmsg_add_str(c, "name", s);
-    htsmsg_add_msg(array, NULL, c);
-  }
+  htsmsg_t *out, *array;
 
+  out   = htsmsg_create_map();
+  array = epg_genres_list_all(1, 0);
   htsmsg_add_msg(out, "entries", array);
-
   htsmsg_json_serialize(out, hq, 0);
   htsmsg_destroy(out);
   http_output_content(hc, "text/x-json; charset=UTF-8");
@@ -658,13 +645,13 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
   epg_query_result_t eqr;
   epg_broadcast_t *e;
   epg_episode_t *ee = NULL;
+  epg_genre_t *eg = NULL, genre;
   channel_t *ch;
   int start = 0, end, limit, i;
   const char *s;
   char buf[100];
   const char *channel = http_arg_get(&hc->hc_req_args, "channel");
   const char *tag     = http_arg_get(&hc->hc_req_args, "tag");
-  const char *cgrp    = http_arg_get(&hc->hc_req_args, "contentgrp");
   const char *title   = http_arg_get(&hc->hc_req_args, "title");
 
   if(channel && !channel[0]) channel = NULL;
@@ -678,12 +665,17 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
   else
     limit = 20; /* XXX */
 
+  if ((s = http_arg_get(&hc->hc_req_args, "contenttype"))) {
+    genre.code = atoi(s);
+    eg = &genre;
+  }
+
   out = htsmsg_create_map();
   array = htsmsg_create_list();
 
   pthread_mutex_lock(&global_lock);
 
-  epg_query(&eqr, channel, tag, cgrp, title);
+  epg_query(&eqr, channel, tag, eg, title);
 
   epg_query_sort(&eqr);
 
@@ -724,9 +716,9 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
     htsmsg_add_u32(m, "end", e->stop);
     htsmsg_add_u32(m, "duration", e->stop - e->start);
     
-    if(ee->genre_cnt)
-      if((s = epg_genre_get_name(ee->genre[0], 0)))
-        htsmsg_add_str(m, "contentgrp", s);
+    if((eg = LIST_FIRST(&ee->genre))) {
+      htsmsg_add_u32(m, "contenttype", eg->code);
+    }
 
     dvr_entry_t *de;
     if((de = dvr_entry_find_by_event(e)) != NULL)
@@ -1000,13 +992,17 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
     htsmsg_add_u32(out, "success", 1);
 
   } else if(!strcmp(op, "createAutoRec")) {
-    const char *cgrp = http_arg_get(&hc->hc_req_args, "contentgrp");
+    epg_genre_t genre, *eg = NULL;
+    if ((s = http_arg_get(&hc->hc_req_args, "contenttype"))) {
+      genre.code = atoi(s);
+      eg = &genre;
+    }
 
     dvr_autorec_add(http_arg_get(&hc->hc_req_args, "config_name"),
                     http_arg_get(&hc->hc_req_args, "title"),
                    http_arg_get(&hc->hc_req_args, "channel"),
                    http_arg_get(&hc->hc_req_args, "tag"),
-                   cgrp ? epg_genre_find_by_name(cgrp) : 0,
+        eg,
                    hc->hc_representative, "Created from EPG query");
 
     out = htsmsg_create_map();
index 0647ba41d09c7fe34956093e448939240f40feef..dcd2974610acfea4000ad4d59e1a629d591554f8 100644 (file)
@@ -470,9 +470,13 @@ tvheadend.autoreceditor = function() {
                emptyText: 'Only include tag...'
            })
        },{
-           header: "Content Group",
-           dataIndex: 'contentgrp',
+           header: "Genre",
+           dataIndex: 'contenttype',
+      renderer: function(v) {
+        return tvheadend.contentGroupLookupName(v);
+      },
            editor: new Ext.form.ComboBox({
+    valueField: 'code',
                displayField:'name',
                store: tvheadend.ContentGroupStore,
                mode: 'local',
@@ -635,7 +639,7 @@ tvheadend.dvr = function() {
 
     
     tvheadend.autorecRecord = Ext.data.Record.create([
-       'enabled','title', 'brand', 'channel','tag','creator','contentgrp','comment',
+       'enabled','title', 'brand', 'channel','tag','creator','contenttype','comment',
        'weekdays', 'pri', 'approx_time', 'config_name'
     ]);
     
index 87ec839e24bed7278b7840f8314dcbdc725f94be..fbb08b2245906e6fae2242b53a0df2f70562cfa3 100644 (file)
@@ -9,12 +9,25 @@ tvheadend.brands = new Ext.data.JsonStore({
 
 tvheadend.ContentGroupStore = new Ext.data.JsonStore({
     root:'entries',
-    fields: [{name: 'name'}],
+    fields: ['name', 'code'],
     autoLoad: true,
     url:'ecglist'
 });
 
-tvheadend.ContentGroupStore.setDefaultSort('name', 'ASC');
+tvheadend.contentGroupLookupName = function(code)
+{
+  ret = "";
+  tvheadend.ContentGroupStore.each(function(r)
+  {
+    if      (r.data.code == code)
+      ret = r.data.name;
+    else if (ret == "" && r.data.code == code & 0xF0)
+      ret = r.data.name;
+  });
+  return ret;
+}
+
+tvheadend.ContentGroupStore.setDefaultSort('code', 'ASC');
 
 tvheadend.epgDetails = function(event) {
 
@@ -29,7 +42,7 @@ tvheadend.epgDetails = function(event) {
     content += '<div class="x-epg-desc">' + event.episode + '</div>';
     content += '<div class="x-epg-desc">' + event.description + '</div>';
 
-    content += '<div class="x-epg-meta">' + event.contentgrp + '</div>';
+    content += '<div class="x-epg-meta">' + tvheadend.contentGroupLookupName(event.contenttype) + '</div>';
 
     if(event.ext_desc != null)
       content += '<div class="x-epg-meta">' + event.ext_desc + '</div>';
@@ -206,7 +219,7 @@ tvheadend.epg = function() {
             {name: 'start', type: 'date', dateFormat: 'U' /* unix time */},
             {name: 'end', type: 'date', dateFormat: 'U' /* unix time */},
             {name: 'duration'},
-           {name: 'contentgrp'},
+           {name: 'contenttype'},
            {name: 'schedstate'}
        ])
    });
@@ -298,10 +311,12 @@ tvheadend.epg = function() {
            renderer: renderText
        },{
            width: 250,
-           id:'contentgrp',
+           id:'contenttype',
            header: "Content Type",
-           dataIndex: 'contentgrp',
-           renderer: renderText
+           dataIndex: 'contenttype',
+           renderer: function(v) {
+        return tvheadend.contentGroupLookupName(v);
+      }
        }
     ]);
 
@@ -357,7 +372,7 @@ tvheadend.epg = function() {
     function epgQueryClear() {
        epgStore.baseParams.channel    = null;
        epgStore.baseParams.tag        = null;
-       epgStore.baseParams.contentgrp = null;
+       epgStore.baseParams.contenttype = null;
        epgStore.baseParams.title      = null;
 
        epgFilterChannels.setValue("");
@@ -383,8 +398,8 @@ tvheadend.epg = function() {
     });
 
     epgFilterContentGroup.on('select', function(c, r) {
-       if(epgStore.baseParams.contentgrp != r.data.name) {
-           epgStore.baseParams.contentgrp = r.data.name;
+       if(epgStore.baseParams.contenttype != r.data.code) {
+           epgStore.baseParams.contenttype = r.data.code;
            epgStore.reload();
        }
     });
@@ -477,8 +492,8 @@ tvheadend.epg = function() {
            epgStore.baseParams.channel    : "<i>Don't care</i>";
        var tag = epgStore.baseParams.tag ?
            epgStore.baseParams.tag        : "<i>Don't care</i>";
-       var contentgrp = epgStore.baseParams.contentgrp ?
-           epgStore.baseParams.contentgrp : "<i>Don't care</i>";
+       var contenttype = epgStore.baseParams.contenttype ?
+           epgStore.baseParams.contenttype : "<i>Don't care</i>";
 
        Ext.MessageBox.confirm('Auto Recorder',
                               'This will create an automatic rule that ' +
@@ -488,7 +503,7 @@ tvheadend.epg = function() {
                               '<div class="x-smallhdr">Title:</div>' + title + '<br>' +
                               '<div class="x-smallhdr">Channel:</div>' + channel + '<br>' +
                               '<div class="x-smallhdr">Tag:</div>' + tag + '<br>' +
-                              '<div class="x-smallhdr">Content Group:</div>' + contentgrp + '<br>' +
+                              '<div class="x-smallhdr">Genre:</div>' + contenttype + '<br>' +
                               '<br>' +
                               'Currently this will match (and record) ' + 
                               epgStore.getTotalCount() + ' events. ' +