]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
epg: remove episode object
authorJaroslav Kysela <perex@perex.cz>
Wed, 14 Feb 2018 12:14:22 +0000 (13:14 +0100)
committerJaroslav Kysela <perex@perex.cz>
Wed, 14 Feb 2018 12:14:52 +0000 (13:14 +0100)
16 files changed:
src/api/api_epg.c
src/dvr/dvr_autorec.c
src/dvr/dvr_db.c
src/dvr/dvr_rec.c
src/epg.c
src/epg.h
src/epgdb.c
src/epggrab/module/eit.c
src/epggrab/module/opentv.c
src/epggrab/module/psip.c
src/epggrab/module/xmltv.c
src/epggrab/private.h
src/htsp_server.c
src/muxer/muxer_mkv.c
src/webui/simpleui.c
src/webui/xmltv.c

index daf66e3dc9ab0861ce264876acdfc5b9a181bcb4..2fba65cd5182c2088c04eaf07529570da683a4c8 100644 (file)
@@ -76,7 +76,6 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang, access_t *perm, const cha
 {
   const char *s;
   char buf[64];
-  epg_episode_t *ee = eb->episode;
   channel_t     *ch = eb->channel;
   htsmsg_t *m, *m2;
   epg_episode_num_t epnum;
@@ -84,7 +83,7 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang, access_t *perm, const cha
   dvr_entry_t *de;
   char ubuf[UUID_HEX_SIZE];
 
-  if (!ee || !ch) return NULL;
+  if (!ch) return NULL;
 
   if (*blank == NULL)
     *blank = tvh_gettext_lang(lang, channel_blank_name);
@@ -93,11 +92,8 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang, access_t *perm, const cha
 
   /* EPG IDs */
   htsmsg_add_u32(m, "eventId", eb->id);
-  if (ee) {
-    htsmsg_add_u32(m, "episodeId", ee->id);
-    if (ee->uri && strncasecmp(ee->uri, "tvh://", 6))
-      htsmsg_add_str(m, "episodeUri", ee->uri);
-  }
+  if (eb->episode_uri && strncasecmp(eb->episode_uri, "tvh://", 6))
+    htsmsg_add_str(m, "episodeUri", eb->episode_uri);
   if (eb->serieslink_uri)
     htsmsg_add_str(m, "serieslinkUri", eb->serieslink_uri);
   
@@ -141,62 +137,60 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang, access_t *perm, const cha
     htsmsg_add_u32(m, "audiodesc", eb->is_audio_desc);
   if (eb->is_hd)
     htsmsg_add_u32(m, "hd", eb->is_hd);
+  if (eb->is_bw)
+    htsmsg_add_u32(m, "bw", eb->is_bw);
   if (eb->lines)
     htsmsg_add_u32(m, "lines", eb->lines);
   if (eb->aspect)
     htsmsg_add_u32(m, "aspect", eb->aspect);
 
   /* Episode info */
-  if (ee) {
-    
-    /* Number */
-    epg_episode_get_epnum(ee, &epnum);
-    if (epnum.s_num) {
-      htsmsg_add_u32(m, "seasonNumber", epnum.s_num);
-      if (epnum.s_cnt)
-        htsmsg_add_u32(m, "seasonCount", epnum.s_cnt);
-    }
-    if (epnum.e_num) {
-      htsmsg_add_u32(m, "episodeNumber", epnum.e_num);
-      if (epnum.e_cnt)
-        htsmsg_add_u32(m, "episodeCount", epnum.e_cnt);
-    }
-    if (epnum.p_num) {
-      htsmsg_add_u32(m, "partNumber", epnum.p_num);
-      if (epnum.p_cnt)
-        htsmsg_add_u32(m, "partCount", epnum.p_cnt);
-    }
-    if (epnum.text)
-      htsmsg_add_str(m, "episodeOnscreen", epnum.text);
-    else if (epg_episode_number_format(ee, buf, sizeof(buf), NULL,
-                                       "s%02d", ".", "e%02d", ""))
-      htsmsg_add_str(m, "episodeOnscreen", buf);
-
-    /* Image */
-    if (ee->image)
-      htsmsg_add_str(m, "image", ee->image);
-
-    /* Rating */
-    if (ee->star_rating)
-      htsmsg_add_u32(m, "starRating", ee->star_rating);
-    if (ee->age_rating)
-      htsmsg_add_u32(m, "ageRating", ee->age_rating);
-
-    if (ee->first_aired)
-      htsmsg_add_s64(m, "first_aired", ee->first_aired);
-    if (ee->copyright_year)
-      htsmsg_add_u32(m, "copyright_year", ee->copyright_year);
-
-    /* Content Type */
-    m2 = NULL;
-    LIST_FOREACH(eg, &ee->genre, link) {
-      if (m2 == NULL)
-        m2 = htsmsg_create_list();
-      htsmsg_add_u32(m2, NULL, eg->code);
-    }
-    if (m2)
-      htsmsg_add_msg(m, "genre", m2);
+  epg_broadcast_get_epnum(eb, &epnum);
+  if (epnum.s_num) {
+    htsmsg_add_u32(m, "seasonNumber", epnum.s_num);
+    if (epnum.s_cnt)
+      htsmsg_add_u32(m, "seasonCount", epnum.s_cnt);
+  }
+  if (epnum.e_num) {
+    htsmsg_add_u32(m, "episodeNumber", epnum.e_num);
+    if (epnum.e_cnt)
+      htsmsg_add_u32(m, "episodeCount", epnum.e_cnt);
   }
+  if (epnum.p_num) {
+    htsmsg_add_u32(m, "partNumber", epnum.p_num);
+    if (epnum.p_cnt)
+      htsmsg_add_u32(m, "partCount", epnum.p_cnt);
+  }
+  if (epnum.text)
+    htsmsg_add_str(m, "episodeOnscreen", epnum.text);
+  else if (epg_broadcast_epnumber_format(eb, buf, sizeof(buf), NULL,
+                                         "s%02d", ".", "e%02d", ""))
+    htsmsg_add_str(m, "episodeOnscreen", buf);
+
+  /* Image */
+  if (eb->image)
+    htsmsg_add_str(m, "image", eb->image);
+
+  /* Rating */
+  if (eb->star_rating)
+    htsmsg_add_u32(m, "starRating", eb->star_rating);
+  if (eb->age_rating)
+    htsmsg_add_u32(m, "ageRating", eb->age_rating);
+
+  if (eb->first_aired)
+    htsmsg_add_s64(m, "first_aired", eb->first_aired);
+  if (eb->copyright_year)
+    htsmsg_add_u32(m, "copyright_year", eb->copyright_year);
+
+  /* Content Type */
+  m2 = NULL;
+  LIST_FOREACH(eg, &eb->genre, link) {
+    if (m2 == NULL)
+      m2 = htsmsg_create_list();
+    htsmsg_add_u32(m2, NULL, eg->code);
+  }
+  if (m2)
+    htsmsg_add_msg(m, "genre", m2);
 
   /* Recording */
   if (eb->channel && !access_verify2(perm, ACCESS_RECORDER)) {
@@ -514,21 +508,26 @@ api_epg_grid
 
 static void
 api_epg_episode_broadcasts
-  ( access_t *perm, htsmsg_t *l, const char *lang, epg_episode_t *ep,
+  ( access_t *perm, htsmsg_t *l, const char *lang, epg_broadcast_t *ep,
     uint32_t *entries, epg_broadcast_t *ebc_skip )
 {
   epg_broadcast_t *ebc;
   channel_t *ch;
   htsmsg_t *m;
+  const char *uri = ep->episode_uri;
 
-  LIST_FOREACH(ebc, &ep->broadcasts, ep_link) {
-    ch = ebc->channel;
-    if (ch == NULL) continue;
-    if (ebc == ebc_skip) continue;
-    m = api_epg_entry(ebc, lang, perm, NULL);
-    htsmsg_add_msg(l, NULL, m);
-    (*entries)++;
-  }
+  if (uri == NULL || uri[0] == '\0')
+    return;
+
+  CHANNEL_FOREACH(ch)
+    if (channel_access(ch, perm, 0))
+      RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link)
+        if (ebc && ebc->episode_uri && ebc != ebc_skip &&
+            strcmp(uri, ebc->episode_uri) == 0) {
+          m = api_epg_entry(ebc, lang, perm, NULL);
+          htsmsg_add_msg(l, NULL, m);
+          (*entries)++;
+        }
 }
 
 static int
@@ -547,8 +546,8 @@ api_epg_alternative
   lang = access_get_lang(perm, htsmsg_get_str(args, "lang"));
   pthread_mutex_lock(&global_lock);
   e = epg_broadcast_find_by_id(id);
-  if (e && e->episode)
-    api_epg_episode_broadcasts(perm, l, lang, e->episode, &entries, e);
+  if (e)
+    api_epg_episode_broadcasts(perm, l, lang, e, &entries, e);
   pthread_mutex_unlock(&global_lock);
   free(lang);
 
index d3c607ed0c0d3d15e521a10a7490010175aff38b..e140d72a9a6bfc690cb8fd25e64e41f9e9e89872 100644 (file)
@@ -151,7 +151,6 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
   double duration;
 
   if (!e->channel) return 0;
-  if (!e->episode) return 0;
   if(dae->dae_enabled == 0 || dae->dae_weekdays == 0)
     return 0;
 
@@ -205,7 +204,7 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
     epg_genre_t ct;
     memset(&ct, 0, sizeof(ct));
     ct.code = dae->dae_content_type;
-    if (!epg_genre_list_contains(&e->episode->genre, &ct, 1))
+    if (!epg_genre_list_contains(&e->genre, &ct, 1))
       return 0;
   }
 
@@ -274,8 +273,8 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
    * dae_star_rating is zero then that means "do not check
    * star rating of episode".
    */
-  if (e->episode && dae->dae_star_rating)
-    if (e->episode->star_rating < dae->dae_star_rating)
+  if (dae->dae_star_rating)
+    if (e->star_rating < dae->dae_star_rating)
       return 0;
 
   /* Do not check title if the event is from the serieslink group */
@@ -283,16 +282,16 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
      dae->dae_title != NULL && dae->dae_title[0] != '\0') {
     lang_str_ele_t *ls;
     if (!dae->dae_fulltext) {
-      if(!e->episode->title) return 0;
-      RB_FOREACH(ls, e->episode->title, link)
+      if(!e->title) return 0;
+      RB_FOREACH(ls, e->title, link)
         if (!regex_match(&dae->dae_title_regex, ls->str)) break;
     } else {
       ls = NULL;
-      if (e->episode->title)
-        RB_FOREACH(ls, e->episode->title, link)
+      if (e->title)
+        RB_FOREACH(ls, e->title, link)
           if (!regex_match(&dae->dae_title_regex, ls->str)) break;
-      if (!ls && e->episode->subtitle)
-        RB_FOREACH(ls, e->episode->subtitle, link)
+      if (!ls && e->subtitle)
+        RB_FOREACH(ls, e->subtitle, link)
           if (!regex_match(&dae->dae_title_regex, ls->str)) break;
       if (!ls && e->summary)
         RB_FOREACH(ls, e->summary, link)
@@ -388,7 +387,7 @@ dvr_autorec_add_series_link(const char *dvr_config_name,
   const char *chname;
   char *title;
   const char *name;
-  if (!event || !event->episode)
+  if (!event)
     return NULL;
   chname = channel_get_name(event->channel, NULL);
   if (!chname)
index 28560be0777f03d3cdb5ded3863497bf13eeb6aa..6fdf9d441794d2c71d6c1265a7b3012d2289601b 100644 (file)
@@ -826,11 +826,11 @@ recording:
 static char *
 dvr_entry_get_episode(epg_broadcast_t *bcast, char *buf, int len)
 {
-  if (!bcast || !bcast->episode)
+  if (!bcast)
     return NULL;
-  if (epg_episode_number_format(bcast->episode,
-                                buf, len, NULL,
-                                _("Season %d"), ".", _("Episode %d"), "/%d"))
+  if (epg_broadcast_epnumber_format(bcast,
+                                    buf, len, NULL,
+                                    _("Season %d"), ".", _("Episode %d"), "/%d"))
     return buf;
   return NULL;
 }
@@ -870,7 +870,7 @@ dvr_entry_fuzzy_match(dvr_entry_t *de, epg_broadcast_t *e, uint16_t eid, int64_t
     return 0;
 
   /* episode check */
-  epg_episode_get_epnum(e->episode, &epnum);
+  epg_broadcast_get_epnum(e, &epnum);
   if (epg_episode_number_cmpfull(&epnum, &de->de_epnum))
     return 0;
 
@@ -1043,32 +1043,27 @@ dvr_entry_create_from_htsmsg(htsmsg_t *conf, epg_broadcast_t *e)
       htsmsg_add_s64(conf, "start", e->start);
     if (!htsmsg_field_find(conf, "stop"))
       htsmsg_add_s64(conf, "stop", e->stop);
-    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->title)
+      lang_str_serialize(e->title, conf, "title");
+    if (e->subtitle)
+      lang_str_serialize(e->subtitle, conf, "subtitle");
     if (e->description)
       lang_str_serialize(e->description, conf, "description");
-    else if (e->episode && e->episode->description)
-      lang_str_serialize(e->episode->description, conf, "description");
     else if (e->summary) {
       lang_str_serialize(e->summary, conf, "description");
       summary_used = 1;
-    } else if (e->episode && e->episode->summary)
-      lang_str_serialize(e->episode->summary, conf, "description");
+    }
     if (!summary_used && e->summary)
       lang_str_serialize(e->summary, conf, "summary");
-    else if (e->episode && e->episode->summary)
-      lang_str_serialize(e->episode->summary, conf, "summary");
-    if (e->episode && (s = dvr_entry_get_episode(e, tbuf, sizeof(tbuf))))
+    if ((s = dvr_entry_get_episode(e, tbuf, sizeof(tbuf))) != NULL)
       htsmsg_add_str(conf, "episode", s);
-    if (e->episode && e->episode->copyright_year)
-      htsmsg_add_u32(conf, "copyright_year", e->episode->copyright_year);
-    if (e->episode && e->episode->uri)
-      htsmsg_add_str(conf, "uri", e->episode->uri);
-    if (e->episode && e->episode->image)
-      htsmsg_add_str(conf, "image", e->episode->image);
-    genre = LIST_FIRST(&e->episode->genre);
+    if (e->copyright_year)
+      htsmsg_add_u32(conf, "copyright_year", e->copyright_year);
+    if (e->episode_uri)
+      htsmsg_add_str(conf, "uri", e->episode_uri);
+    if (e->image)
+      htsmsg_add_str(conf, "image", e->image);
+    genre = LIST_FIRST(&e->genre);
     if (genre)
       htsmsg_add_u32(conf, "content_type", genre->code / 16);
   }
@@ -1124,7 +1119,7 @@ static time_t dvr_entry_get_segment_stop_extra( dvr_entry_t *de )
     return 0;
   }
 
-  ep_uri = e->episode->uri;
+  ep_uri = e->episode_uri;
 
   /* If not a segmented programme then no segment extra time */
   if (!ep_uri || strncmp(ep_uri, "crid://", 7) || !strstr(ep_uri, "#"))
@@ -1160,9 +1155,9 @@ static time_t dvr_entry_get_segment_stop_extra( dvr_entry_t *de )
   max_progs_to_check = 10;
   for (next = epg_broadcast_get_next(e);
        --max_progs_to_check && stop < maximum_stop_time &&
-         next && next->episode && next->start < stop + THREE_HOURS;
+         next && next->start < stop + THREE_HOURS;
        next = epg_broadcast_get_next(next)) {
-    next_uri = next->episode->uri;
+    next_uri = next->episode_uri;
     if (next_uri && strcmp(ep_uri, next_uri) == 0) {
       /* Identical CRID+IMI. So that means that programme is a
        * segment part of this programme. So extend our stop time
@@ -1170,7 +1165,7 @@ static time_t dvr_entry_get_segment_stop_extra( dvr_entry_t *de )
        */
       segment_stop_extra = next->stop - stop;
       tvhinfo(LS_DVR, "Increasing stop for \"%s\" on \"%s\" \"%s\" by %"PRId64" seconds at start %"PRId64" and original stop %"PRId64,
-              lang_str_get(e->episode->title, NULL), DVR_CH_NAME(de), ep_uri, (int64_t)segment_stop_extra, (int64_t)start, (int64_t)stop);
+              lang_str_get(e->title, NULL), DVR_CH_NAME(de), ep_uri, (int64_t)segment_stop_extra, (int64_t)start, (int64_t)stop);
     }
   }
 
@@ -1690,7 +1685,7 @@ dvr_entry_create_by_autorec(int enabled, epg_broadcast_t *e, dvr_autorec_entry_t
   /* Identical duplicate detection
      NOTE: Semantic duplicate detection is deferred to the start time of recording and then done using _dvr_duplicate_event by dvr_timer_start_recording. */
   LIST_FOREACH(de, &dvrentries, de_global_link) {
-    if (de->de_bcast == e || (de->de_bcast && de->de_bcast->episode == e->episode))
+    if (de->de_bcast == e || epg_episode_match(de->de_bcast, e))
       if (strcmp(dae->dae_owner ?: "", de->de_owner ?: "") == 0)
         return;
   }
@@ -1704,7 +1699,7 @@ dvr_entry_create_by_autorec(int enabled, epg_broadcast_t *e, dvr_autorec_entry_t
 
     if (count >= max_count) {
       tvhinfo(LS_DVR, "Autorecord \"%s\": Not scheduling \"%s\" because of autorecord max schedules limit reached",
-              dae->dae_name, lang_str_get(e->episode->title, NULL));
+              dae->dae_name, lang_str_get(e->title, NULL));
       return;
     }
   }
@@ -2066,22 +2061,22 @@ static dvr_entry_t *_dvr_entry_update
   }
 
   /* Title */
-  if (e && e->episode && e->episode->title) {
-    save |= lang_str_set2(&de->de_title, e->episode->title) ? DVR_UPDATED_TITLE : 0;
+  if (e && e->title) {
+    save |= lang_str_set2(&de->de_title, e->title) ? DVR_UPDATED_TITLE : 0;
   } else if (title) {
     save |= lang_str_set(&de->de_title, title, lang) ? DVR_UPDATED_TITLE : 0;
   }
 
   /* Subtitle */
-  if (e && e->episode && e->episode->subtitle) {
-    save |= lang_str_set2(&de->de_subtitle, e->episode->subtitle) ? DVR_UPDATED_SUBTITLE : 0;
+  if (e &&e->subtitle) {
+    save |= lang_str_set2(&de->de_subtitle, e->subtitle) ? DVR_UPDATED_SUBTITLE : 0;
   } else if (subtitle) {
     save |= lang_str_set(&de->de_subtitle, subtitle, lang) ? DVR_UPDATED_SUBTITLE : 0;
   }
 
   /* Summary */
-  if (e && e->episode && e->episode->summary) {
-    save |= lang_str_set2(&de->de_summary, e->episode->summary) ? DVR_UPDATED_SUMMARY : 0;
+  if (e && e->summary) {
+    save |= lang_str_set2(&de->de_summary, e->summary) ? DVR_UPDATED_SUMMARY : 0;
   } else if (summary) {
     save |= lang_str_set(&de->de_summary, summary, lang) ? DVR_UPDATED_SUMMARY : 0;
   }
@@ -2095,19 +2090,15 @@ static dvr_entry_t *_dvr_entry_update
   /* Description */
   if (e && e->description) {
     save |= lang_str_set2(&de->de_desc, e->description) ? DVR_UPDATED_DESCRIPTION : 0;
-  } else if (e && e->episode && e->episode->description) {
-    save |= lang_str_set2(&de->de_desc, e->episode->description) ? DVR_UPDATED_DESCRIPTION : 0;
   } else if (e && e->summary) {
     save |= lang_str_set2(&de->de_desc, e->summary) ? DVR_UPDATED_DESCRIPTION : 0;
-  } else if (e && e->episode && e->episode->summary) {
-    save |= lang_str_set2(&de->de_desc, e->episode->summary) ? DVR_UPDATED_DESCRIPTION : 0;
   } else if (desc) {
     save |= lang_str_set(&de->de_desc, desc, lang) ? DVR_UPDATED_DESCRIPTION : 0;
   }
 
   /* Genre */
-  if (e && e->episode) {
-    epg_genre_t *g = LIST_FIRST(&e->episode->genre);
+  if (e) {
+    epg_genre_t *g = LIST_FIRST(&e->genre);
     if (g && (g->code / 16) != de->de_content_type) {
       de->de_content_type = g->code / 16;
       save |= DVR_UPDATED_GENRE;
@@ -2121,8 +2112,8 @@ static dvr_entry_t *_dvr_entry_update
   }
 
   /* Episode */
-  if (de->de_bcast->episode) {
-    epg_episode_get_epnum(de->de_bcast->episode, &epnum);
+  if (de->de_bcast) {
+    epg_broadcast_get_epnum(de->de_bcast, &epnum);
   } else {
     memset(&epnum, 0, sizeof(epnum));
   }
@@ -3260,8 +3251,8 @@ dvr_entry_class_disp_episode_get(void *o)
     lang = idnode_lang(o);
     snprintf(buf1, sizeof(buf1), "%s %%d", tvh_gettext_lang(lang, N_("Season")));
     snprintf(buf2, sizeof(buf2), "%s %%d", tvh_gettext_lang(lang, N_("Episode")));
-    epg_episode_epnum_format(&de->de_epnum, prop_sbuf, PROP_SBUF_LEN, NULL,
-                             buf1, ".", buf2, "/%d");
+    epg_episode_num_format(&de->de_epnum, prop_sbuf, PROP_SBUF_LEN, NULL,
+                           buf1, ".", buf2, "/%d");
     return &prop_sbuf_ptr;
   } else if (de->de_epnum.text) {
     prop_ptr = de->de_epnum.text;
@@ -3374,8 +3365,8 @@ dvr_entry_class_image_url_get_as_property(void *o)
    * future may have a generic image that will be updated nearer the
    * broadcast date with a more specific image.
    */
-  if (de->de_bcast && de->de_bcast->episode && de->de_bcast->episode->image) {
-    snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", de->de_bcast->episode->image);
+  if (de->de_bcast && de->de_bcast && de->de_bcast->image) {
+    snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", de->de_bcast->image);
     return &prop_sbuf_ptr;
   }
 
@@ -3414,8 +3405,7 @@ dvr_entry_class_first_aired_get(void *o)
 {
   static time_t null = 0;
   const dvr_entry_t *de = (const dvr_entry_t *)o;
-  return de && de->de_bcast && de->de_bcast->episode ?
-    &de->de_bcast->episode->first_aired : &null;
+  return de && de->de_bcast ? &de->de_bcast->first_aired : &null;
 }
 
 static const void *
@@ -3462,9 +3452,9 @@ dvr_entry_class_genre_get(void *o)
 {
   const dvr_entry_t *de = (dvr_entry_t *)o;
   htsmsg_t *l = htsmsg_create_list();
-  if (de->de_bcast && de->de_bcast->episode) {
+  if (de->de_bcast && de->de_bcast) {
     epg_genre_t *eg;
-    LIST_FOREACH(eg, &de->de_bcast->episode->genre, link) {
+    LIST_FOREACH(eg, &de->de_bcast->genre, link) {
       htsmsg_add_u32(l, NULL, eg->code);
     }
   }
index 9cb7a91cdcb33f4a0f97d207130bad91596da3e9..5f54c59dd68ed9ee45472099459831d7e2333edf 100644 (file)
@@ -369,11 +369,11 @@ dvr_sub_episode(const char *id, const char *fmt, const void *aux, char *tmp, siz
   const dvr_entry_t *de = aux;
   char buf[64];
 
-  if (de->de_bcast == NULL || de->de_bcast->episode == NULL)
+  if (de->de_bcast == NULL)
     return "";
-  epg_episode_number_format(de->de_bcast->episode,
-                            buf, sizeof(buf),
-                            NULL, "S%02d", NULL, "E%02d", NULL);
+  epg_broadcast_epnumber_format(de->de_bcast,
+                                buf, sizeof(buf),
+                                NULL, "S%02d", NULL, "E%02d", NULL);
   return dvr_do_prefix(id, fmt, buf, tmp, tmplen);
 }
 
@@ -383,8 +383,7 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char
   char date_buf[512] = { 0 };
   char episode_buf[512] = { 0 };
   const dvr_entry_t *de = aux;
-  /* Can't be const due to call to epg_episode_number_format */
-  /*const*/ epg_episode_t *episode = de->de_bcast ? de->de_bcast->episode : 0;
+  epg_broadcast_t *ebc = de->de_bcast;
 
   *tmp = 0;
   const char *title    = lang_str_get(de->de_title, NULL);
@@ -433,11 +432,11 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char
   else if (fmt && *fmt == '2')       /* Force to be a series (not a movie) */
     is_movie = 0;
   else {
-    if (de->de_bcast && de->de_bcast->category) {
+    if (ebc && ebc->category) {
       /* We've parsed categories from xmltv. So check if it has the movie category. */
       is_movie =
-        string_list_contains_string(de->de_bcast->category, "movie") ||
-        string_list_contains_string(de->de_bcast->category, "film");
+        string_list_contains_string(ebc->category, "movie") ||
+        string_list_contains_string(ebc->category, "film");
     } else {
       /* No xmltv categories parsed. So have to use less-accurate genre instead. */
 
@@ -449,7 +448,7 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char
          * series/episode number then assume must be an episode,
          * otherwise we default to movie.
          */
-        if (episode && (episode->epnum.s_num || episode->epnum.e_num))
+        if (ebc && (ebc->epnum.s_num || ebc->epnum.e_num))
             is_movie = 0;
       }
     }
@@ -461,12 +460,12 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char
     /* Include the year if available. This helps scraper differentiate
      * between numerous remakes of the same film.
      */
-    if (episode) {
-      if (episode->copyright_year) {
-        sprintf(date_buf, "%04d", episode->copyright_year);
+    if (ebc) {
+      if (ebc->copyright_year) {
+        sprintf(date_buf, "%04d", ebc->copyright_year);
       } else {
         /* Some providers use first_aired as really the copyright date. */
-        const time_t first_aired = episode->first_aired;
+        const time_t first_aired = ebc->first_aired;
         if (first_aired) {
           /* Get just the year part */
           struct tm tm;
@@ -478,13 +477,13 @@ _dvr_sub_scraper_friendly(const char *id, const char *fmt, const void *aux, char
     }
   } else {
     /* Not a movie */
-    if (episode) {
+    if (ebc) {
       /* Get episode information */
-      epg_episode_number_format(episode,
-                                episode_buf, sizeof(episode_buf),
-                                NULL, "S%02d", NULL, "E%02d", NULL);
+      epg_broadcast_epnumber_format(ebc,
+                                    episode_buf, sizeof(episode_buf),
+                                    NULL, "S%02d", NULL, "E%02d", NULL);
 
-      const time_t first_aired = episode->first_aired;
+      const time_t first_aired = ebc->first_aired;
       if (first_aired) {
         /* Get as yyyy-mm-dd since programme could be one episode a day/week,
          * unlike films which only needs the year.
@@ -584,9 +583,9 @@ dvr_sub_genre(const char *id, const char *fmt, const void *aux, char *tmp, size_
   epg_genre_t *genre;
   char buf[64];
 
-  if (de->de_bcast == NULL || de->de_bcast->episode == NULL)
+  if (de->de_bcast == NULL)
     return "";
-  genre = LIST_FIRST(&de->de_bcast->episode->genre);
+  genre = LIST_FIRST(&de->de_bcast->genre);
   if (!genre || !genre->code)
     return "";
   epg_genre_get_str(genre, 0, 1, buf, sizeof(buf), "en");
index 0fe21dcfa870015e3bcb45e217490108cdd314a0..3325b5e1b168efeccc0d71bc072ab9d131077094 100644 (file)
--- a/src/epg.c
+++ b/src/epg.c
@@ -73,11 +73,6 @@ static int _id_cmp ( const void *a, const void *b )
   return ((epg_object_t*)a)->id - ((epg_object_t*)b)->id;
 }
 
-static int _uri_cmp ( const void *a, const void *b )
-{
-  return strcmp(((epg_object_t*)a)->uri, ((epg_object_t*)b)->uri);
-}
-
 static int _ebc_start_cmp ( const void *a, const void *b )
 {
   return ((epg_broadcast_t*)a)->start - ((epg_broadcast_t*)b)->start;
@@ -92,7 +87,7 @@ void epg_updated ( void )
   /* Remove unref'd */
   while ((eo = LIST_FIRST(&epg_object_unref))) {
     tvhtrace(LS_EPG,
-             "unref'd object %u (%s) created during update", eo->id, eo->uri);
+             "unref'd object %u created during update", eo->id);
     LIST_REMOVE(eo, un_link);
     eo->ops->destroy(eo);
   }
@@ -117,9 +112,8 @@ static void _epg_object_destroy
   ( epg_object_t *eo, epg_object_tree_t *tree )
 {
   assert(eo->refcount == 0);
-  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] destroy",
-           eo, eo->id, eo->type, eo->uri);
-  if (eo->uri) free(eo->uri);
+  tvhtrace(LS_EPG, "eo [%p, %u, %d] destroy",
+           eo, eo->id, eo->type);
   if (tree) RB_REMOVE(tree, eo, uri_link);
   if (eo->_updated) LIST_REMOVE(eo, up_link);
   RB_REMOVE(epg_id_tree(eo), eo, id_link);
@@ -128,8 +122,8 @@ static void _epg_object_destroy
 static void _epg_object_getref ( void *o )
 {
   epg_object_t *eo = o;
-  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] getref %d",
-           eo, eo->id, eo->type, eo->uri, eo->refcount+1);
+  tvhtrace(LS_EPG, "eo [%p, %u, %d] getref %d",
+           eo, eo->id, eo->type, eo->refcount+1);
   if (eo->refcount == 0) LIST_REMOVE(eo, un_link);
   eo->refcount++;
 }
@@ -137,8 +131,8 @@ static void _epg_object_getref ( void *o )
 static int _epg_object_putref ( void *o )
 {
   epg_object_t *eo = o;
-  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] putref %d",
-           eo, eo->id, eo->type, eo->uri, eo->refcount-1);
+  tvhtrace(LS_EPG, "eo [%p, %u, %d] putref %d",
+           eo, eo->id, eo->type, eo->refcount-1);
   assert(eo->refcount>0);
   eo->refcount--;
   if (!eo->refcount) {
@@ -151,8 +145,8 @@ static int _epg_object_putref ( void *o )
 static void _epg_object_set_updated0 ( void *o )
 {
   epg_object_t *eo = o;
-  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] updated",
-           eo, eo->id, eo->type, eo->uri);
+  tvhtrace(LS_EPG, "eo [%p, %u, %d] updated",
+           eo, eo->id, eo->type);
   eo->_updated = 1;
   eo->updated  = gclk();
   LIST_INSERT_HEAD(&epg_object_updated, eo, up_link);
@@ -192,8 +186,8 @@ static void _epg_object_create ( void *o )
   uint32_t id = eo->id;
   if (!id) eo->id = ++_epg_object_idx;
   if (!eo->id) eo->id = ++_epg_object_idx;
-  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] created",
-           eo, eo->id, eo->type, eo->uri);
+  tvhtrace(LS_EPG, "eo [%p, %u, %d] created",
+           eo, eo->id, eo->type);
   _epg_object_set_updated(eo);
   LIST_INSERT_HEAD(&epg_object_unref, eo, un_link);
   while (1) {
@@ -208,41 +202,6 @@ static void _epg_object_create ( void *o )
   }
 }
 
-static epg_object_t *_epg_object_find_by_uri
-  ( const char *uri, epggrab_module_t *src, int create, int *save,
-    uint32_t *changes, epg_object_tree_t *tree, epg_object_t **skel )
-{
-  epg_object_t *eo;
-  int _save;
-
-  assert(skel != NULL);
-  lock_assert(&global_lock);
-
-  (*skel)->uri = (char*)uri;
-
-  /* Find only */
-  if (!create) {
-    eo = RB_FIND(tree, *skel, uri_link, _uri_cmp);
-  
-  /* Find/create */
-  } else {
-    eo = RB_INSERT_SORTED(tree, *skel, uri_link, _uri_cmp);
-    if (!eo) {
-      if (changes) *changes |= EPG_CHANGED_CREATE;
-      *save        = 1;
-      eo           = *skel;
-      *skel        = NULL;
-      eo->uri      = strdup(uri);
-      _epg_object_create(eo);
-    }
-  }
-  if (eo) {
-    _save = _epg_object_set_grabber(eo, src);
-    if (save) *save |= _save;
-  }
-  return eo;
-}
-
 epg_object_t *epg_object_find_by_id ( uint32_t id, epg_object_type_t type )
 {
   epg_object_t *eo, temp;
@@ -257,14 +216,12 @@ static htsmsg_t * _epg_object_serialize ( void *o )
 {
   htsmsg_t *m;
   epg_object_t *eo = o;
-  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s] serialize",
-           eo, eo->id, eo->type, eo->uri);
+  tvhtrace(LS_EPG, "eo [%p, %u, %d] serialize",
+           eo, eo->id, eo->type);
   if (!eo->id || !eo->type) return NULL;
   m = htsmsg_create_map();
   htsmsg_add_u32(m, "id", eo->id);
   htsmsg_add_u32(m, "type", eo->type);
-  if (eo->uri)
-    htsmsg_add_str(m, "uri", eo->uri);
   if (eo->grabber)
     htsmsg_add_str(m, "grabber", eo->grabber->id);
   htsmsg_add_s64(m, "updated", eo->updated);
@@ -279,21 +236,20 @@ static epg_object_t *_epg_object_deserialize ( htsmsg_t *m, epg_object_t *eo )
   if (htsmsg_get_u32(m, "id",   &eo->id)) return NULL;
   if (htsmsg_get_u32(m, "type", &u32))    return NULL;
   if (u32 != eo->type)                    return NULL;
-  eo->uri = (char*)htsmsg_get_str(m, "uri");
   if ((s = htsmsg_get_str(m, "grabber")))
     eo->grabber = epggrab_module_find_by_id(s);
   if (!htsmsg_get_s64(m, "updated", &s64)) {
     _epg_object_set_updated(eo);
     eo->updated = s64;
   }
-  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s, %s, %s] deserialize",
-           eo, eo->id, eo->type, eo->uri, s, eo->grabber ? eo->grabber->id : NULL);
+  tvhtrace(LS_EPG, "eo [%p, %u, %d, %s, %s] deserialize",
+           eo, eo->id, eo->type, s, eo->grabber ? eo->grabber->id : NULL);
   return eo;
 }
 
 static int _epg_object_set_str
   ( void *o, char **old, const char *newstr,
-    uint32_t *changed, uint32_t cflag )
+    epg_changes_t *changed, epg_changes_t cflag )
 {
   int save = 0;
   epg_object_t *eo = o;
@@ -313,7 +269,7 @@ static int _epg_object_set_str
 #define EPG_OBJECT_SET_FN(FNNAME,TYPE,DESTROY,COMPARE,COPY) \
 static int FNNAME \
   ( void *o, TYPE **old, const TYPE *new, \
-    uint32_t *changed, uint32_t cflag ) \
+    epg_changes_t *changed, epg_changes_t cflag ) \
 { \
   if (!o) return 0; \
   if (changed) *changed |= cflag; \
@@ -344,7 +300,7 @@ EPG_OBJECT_SET_FN(_epg_object_set_htsmsg,      htsmsg_t,      htsmsg_destroy,
 #define EPG_OBJECT_SET_FN(FNNAME,TYPE) \
 static int FNNAME \
   ( void *o, TYPE *old, const TYPE nval, \
-    uint32_t *changed, uint32_t cflag ) \
+    epg_changes_t *changed, epg_changes_t cflag ) \
 { \
   int save; \
   if (!o) return 0; \
@@ -364,8 +320,6 @@ htsmsg_t *epg_object_serialize ( epg_object_t *eo )
 {
   if (!eo) return NULL;
   switch (eo->type) {
-    case EPG_EPISODE:
-      return epg_episode_serialize((epg_episode_t*)eo);
     case EPG_BROADCAST:
       return epg_broadcast_serialize((epg_broadcast_t*)eo);
     default:
@@ -379,8 +333,6 @@ epg_object_t *epg_object_deserialize ( htsmsg_t *msg, int create, int *save )
   if (!msg) return NULL;
   type = htsmsg_get_u32_or_default(msg, "type", 0);
   switch (type) {
-    case EPG_EPISODE:
-      return (epg_object_t*)epg_episode_deserialize(msg, create, save);
     case EPG_BROADCAST:
       return (epg_object_t*)epg_broadcast_deserialize(msg, create, save);
   }
@@ -438,318 +390,7 @@ void epg_episode_epnum_deserialize
     num->text = strdup(str);
 }
 
-static void _epg_episode_destroy ( void *eo )
-{
-  epg_genre_t *g;
-  epg_episode_t *ee = eo;
-  if (LIST_FIRST(&ee->broadcasts)) {
-    tvhlog(LOG_CRIT, LS_EPG, "attempt to destroy episode with broadcasts");
-    assert(0);
-  }
-  if (ee->title)       lang_str_destroy(ee->title);
-  if (ee->subtitle)    lang_str_destroy(ee->subtitle);
-  if (ee->summary)     lang_str_destroy(ee->summary);
-  if (ee->description) lang_str_destroy(ee->description);
-  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);
-  _epg_object_destroy(eo, &epg_episodes);
-  free(ee);
-}
-
-static void _epg_episode_updated ( void *eo )
-{
-}
-
-static epg_object_ops_t _epg_episode_ops = {
-  .getref  = _epg_object_getref,
-  .putref  = _epg_object_putref,
-  .destroy = _epg_episode_destroy,
-  .update  = _epg_episode_updated,
-};
-
-static epg_object_t **_epg_episode_skel ( void )
-{
-  static epg_object_t *skel = NULL;
-  if (!skel) {
-    skel = calloc(1, sizeof(epg_episode_t));
-    skel->type = EPG_EPISODE;
-    skel->ops  = &_epg_episode_ops;
-  }
-  return &skel;
-}
-
-epg_episode_t* epg_episode_find_by_uri
-  ( const char *uri, epggrab_module_t *src, int create,
-    int *save, uint32_t *changed )
-{
-  return (epg_episode_t*)
-    _epg_object_find_by_uri(uri, src, create, save, changed,
-                            &epg_episodes,
-                            _epg_episode_skel());
-}
-
-epg_episode_t *epg_episode_find_by_id ( uint32_t id )
-{
-  return (epg_episode_t*)epg_object_find_by_id(id, EPG_EPISODE);
-}
-
-epg_episode_t *epg_episode_find_by_broadcast
-  ( epg_broadcast_t *ebc, epggrab_module_t *src,
-    int create, int *save, uint32_t *changed )
-{
-  char uri[UUID_HEX_SIZE+50], ubuf[UUID_HEX_SIZE];
-  if (!ebc) return NULL;
-  if (ebc->episode) {
-    _epg_object_set_grabber(ebc->episode, src);
-    return ebc->episode;
-  }
-  if (!create) return NULL;
-  snprintf(uri, sizeof(uri)-1, "tvh://channel-%s/bcast-%u/episode",
-           idnode_uuid_as_str(&ebc->channel->ch_id, ubuf), ebc->id);
-  return epg_episode_find_by_uri(uri, src, 1, save, changed);
-}
-
-int epg_episode_change_finish
-  ( epg_episode_t *episode, uint32_t changes, int merge )
-{
-  int save = 0;
-  if (merge) return 0;
-  if (changes & EPG_CHANGED_CREATE) return 0;
-  if (!(changes & EPG_CHANGED_TITLE))
-    save |= epg_episode_set_title(episode, NULL, NULL);
-  if (!(changes & EPG_CHANGED_SUBTITLE))
-    save |= epg_episode_set_subtitle(episode, NULL, NULL);
-  if (!(changes & EPG_CHANGED_SUMMARY))
-    save |= epg_episode_set_summary(episode, NULL, NULL);
-  if (!(changes & EPG_CHANGED_DESCRIPTION))
-    save |= epg_episode_set_description(episode, NULL, NULL);
-  if (!(changes & EPG_CHANGED_IMAGE))
-    save |= epg_episode_set_image(episode, NULL, NULL);
-  if (!(changes & EPG_CHANGED_EPSER_NUM))
-    save |= _epg_object_set_u16(episode, &episode->epnum.s_num, 0, NULL, 0);
-  if (!(changes & EPG_CHANGED_EPSER_CNT))
-    save |= _epg_object_set_u16(episode, &episode->epnum.s_cnt, 0, NULL, 0);
-  if (!(changes & EPG_CHANGED_EPNUM_NUM))
-    save |= _epg_object_set_u16(episode, &episode->epnum.e_num, 0, NULL, 0);
-  if (!(changes & EPG_CHANGED_EPNUM_CNT))
-    save |= _epg_object_set_u16(episode, &episode->epnum.e_cnt, 0, NULL, 0);
-  if (!(changes & EPG_CHANGED_EPPAR_NUM))
-    save |= _epg_object_set_u16(episode, &episode->epnum.p_num, 0, NULL, 0);
-  if (!(changes & EPG_CHANGED_EPPAR_CNT))
-    save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt, 0, NULL, 0);
-  if (!(changes & EPG_CHANGED_EPTEXT))
-    save |= _epg_object_set_str(episode, &episode->epnum.text, NULL, NULL, 0);
-  if (!(changes & EPG_CHANGED_GENRE))
-    save |= epg_episode_set_genre(episode, NULL, NULL);
-  if (!(changes & EPG_CHANGED_IS_BW))
-    save |= epg_episode_set_is_bw(episode, 0, NULL);
-  if (!(changes & EPG_CHANGED_STAR_RATING))
-    save |= epg_episode_set_star_rating(episode, 0, NULL);
-  if (!(changes & EPG_CHANGED_AGE_RATING))
-    save |= epg_episode_set_age_rating(episode, 0, NULL);
-  if (!(changes & EPG_CHANGED_FIRST_AIRED))
-    save |= epg_episode_set_first_aired(episode, 0, NULL);
-  if (!(changes & EPG_CHANGED_COPYRIGHT_YEAR))
-    save |= epg_episode_set_copyright_year(episode, 0, NULL);
-  return save;
-}
-
-int epg_episode_set_title
-  ( epg_episode_t *episode, const lang_str_t *title, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_lang_str(episode, &episode->title, title,
-                                  changed, EPG_CHANGED_TITLE);
-}
-
-int epg_episode_set_subtitle
-  ( epg_episode_t *episode, const lang_str_t *subtitle, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_lang_str(episode, &episode->subtitle,
-                                  subtitle, changed, EPG_CHANGED_SUBTITLE);
-}
-
-int epg_episode_set_summary
-  ( epg_episode_t *episode, const lang_str_t *summary, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_lang_str(episode, &episode->summary,
-                                  summary, changed, EPG_CHANGED_SUMMARY);
-}
-
-int epg_episode_set_description
-  ( epg_episode_t *episode, const lang_str_t *desc, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_lang_str(episode, &episode->description,
-                                  desc, changed, EPG_CHANGED_DESCRIPTION);
-}
-
-int epg_episode_set_image
-  ( epg_episode_t *episode, const char *image, uint32_t *changed )
-{
-  int save;
-  if (!episode) return 0;
-  save = _epg_object_set_str(episode, &episode->image, image,
-                             changed, EPG_CHANGED_IMAGE);
-  if (save)
-    imagecache_get_id(image);
-  return save;
-}
-
-int epg_episode_set_number
-  ( epg_episode_t *episode, uint16_t number, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_u16(episode, &episode->epnum.e_num, number,
-                             changed, EPG_CHANGED_EPNUM_NUM);
-}
-
-int epg_episode_set_part
-  ( epg_episode_t *episode, uint16_t part, uint16_t count,
-    uint32_t *changed )
-{
-  int save = 0;
-  if (!episode) return 0;
-  save |= _epg_object_set_u16(episode, &episode->epnum.p_num, part,
-                              changed, EPG_CHANGED_EPPAR_NUM);
-  save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt, count,
-                              changed, EPG_CHANGED_EPPAR_CNT);
-  return save;
-}
-
-int epg_episode_set_epnum
-  ( epg_episode_t *episode, epg_episode_num_t *num, uint32_t *changed )
-{
-  int save = 0;
-  static epg_episode_num_t _zero = { 0 };
-  if (!episode)
-    return 0;
-  if (!num)
-    num = &_zero;
-  if (num->s_num)
-    save |= _epg_object_set_u16(episode, &episode->epnum.s_num,
-                                num->s_num, changed, EPG_CHANGED_EPSER_NUM);
-  if (num->s_cnt)
-    save |= _epg_object_set_u16(episode, &episode->epnum.s_cnt,
-                                num->s_cnt, changed, EPG_CHANGED_EPSER_CNT);
-  if (num->e_num)
-    save |= _epg_object_set_u16(episode, &episode->epnum.e_num,
-                                num->e_num, changed, EPG_CHANGED_EPNUM_NUM);
-  if (num->e_cnt)
-    save |= _epg_object_set_u16(episode, &episode->epnum.e_cnt,
-                                num->e_cnt, changed, EPG_CHANGED_EPNUM_CNT);
-  if (num->p_num)
-    save |= _epg_object_set_u16(episode, &episode->epnum.p_num,
-                                num->p_num, changed, EPG_CHANGED_EPPAR_NUM);
-  if (num->p_cnt)
-    save |= _epg_object_set_u16(episode, &episode->epnum.p_cnt,
-                                num->p_cnt, changed, EPG_CHANGED_EPPAR_CNT);
-  if (num->text)
-    save |= _epg_object_set_str(episode, &episode->epnum.text,
-                                num->text, changed, EPG_CHANGED_EPTEXT);
-  return save;
-}
-
-int epg_episode_set_genre
-  ( epg_episode_t *ee, epg_genre_list_t *genre, uint32_t *changed )
-{
-  int save = 0;
-  epg_genre_t *g1, *g2;
-
-  if (!ee) return 0;
-
-  if (changed) *changed |= EPG_CHANGED_GENRE;
-
-  g1 = LIST_FIRST(&ee->genre);
-
-  /* Remove old */
-  while (g1) {
-    g2 = LIST_NEXT(g1, link);
-    if (!epg_genre_list_contains(genre, g1, 0)) {
-      LIST_REMOVE(g1, link);
-      free(g1);
-      save = 1;
-    }
-    g1 = g2;
-  }
-  
-  /* Insert all entries */
-  if (genre) {
-    LIST_FOREACH(g1, genre, link)
-      save |= epg_genre_list_add(&ee->genre, g1);
-  }
-
-  return save;
-}
-
-int epg_episode_set_is_bw
-  ( epg_episode_t *episode, uint8_t bw, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_u8(episode, &episode->is_bw, bw,
-                            changed, EPG_CHANGED_IS_BW);
-}
-
-int epg_episode_set_star_rating
-  ( epg_episode_t *episode, uint8_t stars, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_u8(episode, &episode->star_rating, stars,
-                            changed, EPG_CHANGED_STAR_RATING);
-}
-
-int epg_episode_set_copyright_year
-  ( epg_episode_t *episode, uint16_t year, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_u16(episode, &episode->copyright_year, year,
-                            changed, EPG_CHANGED_COPYRIGHT_YEAR);
-}
-
-int epg_episode_set_age_rating
-  ( epg_episode_t *episode, uint8_t age, uint32_t *changed )
-{
-  if (!episode) return 0;
-  return _epg_object_set_u8(episode, &episode->age_rating, age,
-                            changed, EPG_CHANGED_AGE_RATING);
-}
-
-int epg_episode_set_first_aired
-  ( epg_episode_t *episode, time_t aired, uint32_t *changed )
-{
-  if (!episode) return 0;
-  if (changed) *changed |= EPG_CHANGED_FIRST_AIRED;
-  if (episode->first_aired != aired) {
-    episode->first_aired = aired;
-    _epg_object_set_updated(episode);
-    return 1;
-  }
-  return 0;
-}
-
-static void _epg_episode_add_broadcast 
-  ( epg_episode_t *episode, epg_broadcast_t *broadcast )
-{
-  _epg_object_getref(episode);
-  _epg_object_set_updated(episode);
-  LIST_INSERT_SORTED(&episode->broadcasts, broadcast, ep_link, _ebc_start_cmp);
-}
-
-static void _epg_episode_rem_broadcast
-  ( epg_episode_t *episode, epg_broadcast_t *broadcast )
-{
-  LIST_REMOVE(broadcast, ep_link);
-  _epg_object_set_updated(episode);
-  _epg_object_putref(episode);
-}
-
-size_t epg_episode_epnum_format
+size_t epg_episode_num_format
   ( epg_episode_num_t *epnum, char *buf, size_t len,
     const char *pre,  const char *sfmt,
     const char *sep,  const char *efmt,
@@ -776,29 +417,6 @@ size_t epg_episode_epnum_format
   return i;
 }
 
-size_t epg_episode_number_format
-  ( epg_episode_t *episode, char *buf, size_t len,
-    const char *pre,  const char *sfmt,
-    const char *sep,  const char *efmt,
-    const char *cfmt )
-{
-  if (!episode) return 0;
-  epg_episode_num_t num;
-  epg_episode_get_epnum(episode, &num);
-  return epg_episode_epnum_format(&num, buf, len, pre,
-                                  sfmt, sep, efmt, cfmt);
-}
-
-void epg_episode_get_epnum ( const epg_episode_t *ee, epg_episode_num_t *num )
-{
-  if (!ee || !num) {
-    if (num)
-      memset(num, 0, sizeof(*num));
-    return;
-  }
-  *num = ee->epnum;
-}
-
 int epg_episode_number_cmp ( const epg_episode_num_t *a, const epg_episode_num_t *b )
 {
   if (a->e_num) {
@@ -832,157 +450,6 @@ int epg_episode_number_cmpfull ( const epg_episode_num_t *a, const epg_episode_n
   return strcasecmp(a->text ?: "", b->text ?: "");
 }
 
-// WIBNI: this could do with soem proper matching, maybe some form of
-//        fuzzy string match. I did try a few things, but none of them
-//        were very reliable.
-#if TODO_FUZZY_MATCH
-int epg_episode_fuzzy_match
-  ( epg_episode_t *episode, const char *uri, const char *title,
-    const char *summary, const char *description )
-{
-  if (!episode) return 0;
-  if (uri && episode->uri && !strcmp(episode->uri, uri)) return 1;
-  if (title && episode->title && (strstr(title, episode->title) || strstr(episode->title, title))) return 1;
-  return 0;
-}
-#endif
-
-htsmsg_t *epg_episode_serialize ( epg_episode_t *episode )
-{
-  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;
-  if (episode->title)
-    lang_str_serialize(episode->title, m, "title");
-  if (episode->subtitle)
-    lang_str_serialize(episode->subtitle, m, "subtitle");
-  if (episode->summary)
-    lang_str_serialize(episode->summary, m, "summary");
-  if (episode->description)
-    lang_str_serialize(episode->description, m, "description");
-  htsmsg_add_msg(m, "epnum", epg_episode_epnum_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->is_bw)
-    htsmsg_add_u32(m, "is_bw", 1);
-  if (episode->star_rating)
-    htsmsg_add_u32(m, "star_rating", episode->star_rating);
-  if (episode->copyright_year)
-    htsmsg_add_u32(m, "copyright_year", episode->copyright_year);
-  if (episode->age_rating)
-    htsmsg_add_u32(m, "age_rating", episode->age_rating);
-  if (episode->first_aired)
-    htsmsg_add_s64(m, "first_aired", episode->first_aired);
-  if (episode->image)
-    htsmsg_add_str(m, "image", episode->image);
-
-  return m;
-}
-
-epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
-{
-  epg_object_t **skel = _epg_episode_skel();
-  epg_episode_t *ee;
-  const char *str;
-  epg_episode_num_t num;
-  htsmsg_t *sub;
-  htsmsg_field_t *f;
-  uint32_t u32, changes = 0;
-  int64_t s64;
-  lang_str_t *ls;
-  
-  if (!_epg_object_deserialize(m, *skel)) return NULL;
-  if (!(ee = epg_episode_find_by_uri((*skel)->uri, (*skel)->grabber,
-                                     create, save, &changes)))
-    return NULL;
-  
-  if ((ls = lang_str_deserialize(m, "title"))) {
-    *save |= epg_episode_set_title(ee, ls, &changes);
-    lang_str_destroy(ls);
-  }
-  if ((ls = lang_str_deserialize(m, "subtitle"))) {
-    *save |= epg_episode_set_subtitle(ee, ls, &changes);
-    lang_str_destroy(ls);
-  }
-  if ((ls = lang_str_deserialize(m, "summary"))) {
-    *save |= epg_episode_set_summary(ee, ls, &changes);
-    lang_str_destroy(ls);
-  }
-  if ((ls = lang_str_deserialize(m, "description"))) {
-    *save |= epg_episode_set_description(ee, ls, &changes);
-    lang_str_destroy(ls);
-  }
-  if ((sub = htsmsg_get_map(m, "epnum"))) {
-    epg_episode_epnum_deserialize(sub, &num);
-    *save |= epg_episode_set_epnum(ee, &num, &changes);
-    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, &changes);
-    epg_genre_list_destroy(egl);
-  }
-  
-  if (!htsmsg_get_u32(m, "is_bw", &u32))
-    *save |= epg_episode_set_is_bw(ee, u32, &changes);
-
-  if (!htsmsg_get_u32(m, "star_rating", &u32))
-    *save |= epg_episode_set_star_rating(ee, u32, &changes);
-
-  if (!htsmsg_get_u32(m, "copyright_year", &u32))
-    *save |= epg_episode_set_copyright_year(ee, u32, &changes);
-
-  if (!htsmsg_get_u32(m, "age_rating", &u32))
-    *save |= epg_episode_set_age_rating(ee, u32, &changes);
-
-  if (!htsmsg_get_s64(m, "first_aired", &s64))
-    *save |= epg_episode_set_first_aired(ee, (time_t)s64, &changes);
-
-  if ((str = htsmsg_get_str(m, "image")))
-    *save |= epg_episode_set_image(ee, str, &changes);
-
-  *save |= epg_episode_change_finish(ee, changes, 0);
-
-  return ee;
-}
-
-const char *epg_episode_get_title 
-  ( const epg_episode_t *e, const char *lang )
-{
-  if (!e || !e->title) return NULL;
-  return lang_str_get(e->title, lang);
-}
-
-const char *epg_episode_get_subtitle 
-  ( const epg_episode_t *e, const char *lang )
-{
-  if (!e || !e->subtitle) return NULL;
-  return lang_str_get(e->subtitle, lang);
-}
-
-const char *epg_episode_get_summary
-  ( const epg_episode_t *e, const char *lang )
-{
-  if (!e || !e->summary) return NULL;
-  return lang_str_get(e->summary, lang);
-}
-
-const char *epg_episode_get_description 
-  ( const epg_episode_t *e, const char *lang )
-{
-  if (!e || !e->description) return NULL;
-  return lang_str_get(e->description, lang);
-}
-
 /* **************************************************************************
  * Channel
  * *************************************************************************/
@@ -1071,7 +538,7 @@ static void _epg_channel_timer_callback ( void *p )
 
 static epg_broadcast_t *_epg_channel_add_broadcast 
   ( channel_t *ch, epg_broadcast_t **bcast, epggrab_module_t *src,
-    int create, int *save, uint32_t *changed )
+    int create, int *save, epg_changes_t *changed )
 {
   int timer = 0;
   epg_broadcast_t *ebc, *ret;
@@ -1256,12 +723,10 @@ static int epg_match_event_fuzzy(epg_broadcast_t *a, epg_broadcast_t *b)
     return 0;
 
   /* episode check */
-  if (a->episode && b->episode) {
-    epg_episode_get_epnum(a->episode, &num1);
-    epg_episode_get_epnum(b->episode, &num2);
-    if (epg_episode_number_cmp(&num1, &num2) == 0)
-      return 1;
-  }
+  epg_broadcast_get_epnum(a, &num1);
+  epg_broadcast_get_epnum(b, &num2);
+  if (epg_episode_number_cmp(&num1, &num2) == 0)
+    return 1;
 
   return 0;
 }
@@ -1289,6 +754,7 @@ epg_broadcast_t *epg_match_now_next ( channel_t *ch, epg_broadcast_t *ebc )
 static void _epg_broadcast_destroy ( void *eo )
 {
   epg_broadcast_t *ebc = eo;
+  epg_genre_t *eg;
   char id[16];
 
   if (ebc->_created) {
@@ -1296,15 +762,23 @@ static void _epg_broadcast_destroy ( void *eo )
     snprintf(id, sizeof(id), "%u", ebc->id);
     notify_delayed(id, "epg", "delete");
   }
-  if (ebc->episode)     _epg_episode_rem_broadcast(ebc->episode, ebc);
+  if (ebc->title)       lang_str_destroy(ebc->summary);
+  if (ebc->subtitle)    lang_str_destroy(ebc->subtitle);
   if (ebc->summary)     lang_str_destroy(ebc->summary);
   if (ebc->description) lang_str_destroy(ebc->description);
+  while ((eg = LIST_FIRST(&ebc->genre))) {
+    LIST_REMOVE(eg, link);
+    free(eg);
+  }
+  free(ebc->image);
+  free(ebc->epnum.text);
   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);
   free(ebc->serieslink_uri);
+  free(ebc->episode_uri);
   _epg_object_destroy(eo, NULL);
   assert(LIST_EMPTY(&ebc->dvr_entries));
   free(ebc);
@@ -1378,7 +852,7 @@ static epg_broadcast_t **_epg_broadcast_skel ( void )
 
 epg_broadcast_t *epg_broadcast_find_by_time
   ( channel_t *channel, epggrab_module_t *src,
-    time_t start, time_t stop, int create, int *save, uint32_t *changed )
+    time_t start, time_t stop, int create, int *save, epg_changes_t *changed )
 {
   epg_broadcast_t **ebc;
   if (!channel || !start || !stop) return NULL;
@@ -1393,15 +867,15 @@ epg_broadcast_t *epg_broadcast_find_by_time
 }
 
 int epg_broadcast_change_finish
-  ( epg_broadcast_t *broadcast, uint32_t changes, int merge )
+  ( epg_broadcast_t *broadcast, epg_changes_t changes, int merge )
 {
   int save = 0;
   if (merge) return 0;
   if (changes & EPG_CHANGED_CREATE) return 0;
-  if (!(changes & EPG_CHANGED_EPISODE))
-    save |= epg_broadcast_set_episode(broadcast, NULL, NULL);
   if (!(changes & EPG_CHANGED_SERIESLINK))
     save |= epg_broadcast_set_serieslink_uri(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_EPISODE))
+    save |= epg_broadcast_set_episode_uri(broadcast, NULL, NULL);
   if (!(changes & EPG_CHANGED_DVB_EID))
     save |= epg_broadcast_set_dvb_eid(broadcast, 0, NULL);
   if (!(changes & EPG_CHANGED_IS_WIDESCREEN))
@@ -1422,10 +896,42 @@ int epg_broadcast_change_finish
     save |= epg_broadcast_set_is_new(broadcast, 0, NULL);
   if (!(changes & EPG_CHANGED_IS_REPEAT))
     save |= epg_broadcast_set_is_repeat(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_IS_BW))
+    save |= epg_broadcast_set_is_bw(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_STAR_RATING))
+    save |= epg_broadcast_set_star_rating(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_AGE_RATING))
+    save |= epg_broadcast_set_age_rating(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_IMAGE))
+    save |= epg_broadcast_set_image(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_GENRE))
+    save |= epg_broadcast_set_genre(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_TITLE))
+    save |= epg_broadcast_set_title(broadcast, NULL, NULL);
+  if (!(changes & EPG_CHANGED_SUBTITLE))
+    save |= epg_broadcast_set_subtitle(broadcast, NULL, NULL);
   if (!(changes & EPG_CHANGED_SUMMARY))
     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_EPSER_NUM))
+    save |= _epg_object_set_u16(broadcast, &broadcast->epnum.s_num, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPSER_CNT))
+    save |= _epg_object_set_u16(broadcast, &broadcast->epnum.s_cnt, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPNUM_NUM))
+    save |= _epg_object_set_u16(broadcast, &broadcast->epnum.e_num, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPNUM_CNT))
+    save |= _epg_object_set_u16(broadcast, &broadcast->epnum.e_cnt, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPPAR_NUM))
+    save |= _epg_object_set_u16(broadcast, &broadcast->epnum.p_num, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPPAR_CNT))
+    save |= _epg_object_set_u16(broadcast, &broadcast->epnum.p_cnt, 0, NULL, 0);
+  if (!(changes & EPG_CHANGED_EPTEXT))
+    save |= _epg_object_set_str(broadcast, &broadcast->epnum.text, NULL, NULL, 0);
+  if (!(changes & EPG_CHANGED_FIRST_AIRED))
+    save |= epg_broadcast_set_first_aired(broadcast, 0, NULL);
+  if (!(changes & EPG_CHANGED_COPYRIGHT_YEAR))
+    save |= epg_broadcast_set_copyright_year(broadcast, 0, NULL);
   if (!(changes & EPG_CHANGED_CREDITS))
     save |= epg_broadcast_set_credits(broadcast, NULL, NULL);
   if (!(changes & EPG_CHANGED_CATEGORY))
@@ -1439,7 +945,7 @@ epg_broadcast_t *epg_broadcast_clone
   ( channel_t *channel, epg_broadcast_t *src, int *save )
 {
   epg_broadcast_t *ebc;
-  uint32_t changes = 0;
+  epg_changes_t changes = 0;
 
   if (!src) return NULL;
   ebc = epg_broadcast_find_by_time(channel, src->grabber,
@@ -1449,6 +955,7 @@ epg_broadcast_t *epg_broadcast_clone
     /* Copy metadata */
     *save |= epg_broadcast_set_is_widescreen(ebc, src->is_widescreen, &changes);
     *save |= epg_broadcast_set_is_hd(ebc, src->is_hd, &changes);
+    *save |= epg_broadcast_set_is_bw(ebc, src->is_bw, &changes);
     *save |= epg_broadcast_set_lines(ebc, src->lines, &changes);
     *save |= epg_broadcast_set_aspect(ebc, src->aspect, &changes);
     *save |= epg_broadcast_set_is_deafsigned(ebc, src->is_deafsigned, &changes);
@@ -1456,13 +963,23 @@ epg_broadcast_t *epg_broadcast_clone
     *save |= epg_broadcast_set_is_audio_desc(ebc, src->is_audio_desc, &changes);
     *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_star_rating(ebc, src->star_rating, &changes);
+    *save |= epg_broadcast_set_age_rating(ebc, src->age_rating, &changes);
+    *save |= epg_broadcast_set_image(ebc, src->image, &changes);
+    *save |= epg_broadcast_set_genre(ebc, &src->genre, &changes);
+    *save |= epg_broadcast_set_title(ebc, src->title, &changes);
+    *save |= epg_broadcast_set_subtitle(ebc, src->subtitle, &changes);
     *save |= epg_broadcast_set_summary(ebc, src->summary, &changes);
+    *save |= epg_broadcast_set_description(ebc, src->description, &changes);
+    *save |= epg_broadcast_set_epnum(ebc, &src->epnum, &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_uri(ebc, src->serieslink_uri, &changes);
-    *save |= epg_broadcast_set_episode(ebc, src->episode, &changes);
+    *save |= epg_broadcast_set_episode_uri(ebc, src->episode_uri, &changes);
+    *save |= epg_broadcast_set_first_aired(ebc, src->first_aired, &changes);
+    *save |= epg_broadcast_set_copyright_year(ebc, src->copyright_year, &changes);
     _epg_object_set_grabber(ebc, src->grabber);
     *save |= epg_broadcast_change_finish(ebc, changes, 0);
   }
@@ -1495,39 +1012,36 @@ int epg_broadcast_set_running
   return save;
 }
 
-int epg_broadcast_set_episode 
-  ( epg_broadcast_t *broadcast, epg_episode_t *episode, uint32_t *changed )
+int epg_broadcast_set_serieslink_uri
+  ( epg_broadcast_t *ebc, const char *uri, epg_changes_t *changed )
 {
   int save = 0;
-  if (!broadcast) return 0;
-  if (changed) *changed |= EPG_CHANGED_EPISODE;
-  if (broadcast->episode != episode) {
-    if (broadcast->episode)
-      _epg_episode_rem_broadcast(broadcast->episode, broadcast);
-    broadcast->episode = episode;
-    if (episode) _epg_episode_add_broadcast(episode, broadcast);
-    _epg_object_set_updated(broadcast);
+  if (!ebc) return 0;
+  if (changed) *changed |= EPG_CHANGED_SERIESLINK;
+  if (strcmp(ebc->serieslink_uri ?: "", uri ?: "")) {
+    free(ebc->serieslink_uri);
+    ebc->serieslink_uri = strdup(uri);
     save = 1;
   }
   return save;
 }
 
-int epg_broadcast_set_serieslink_uri
-  ( epg_broadcast_t *ebc, const char *uri, uint32_t *changed )
+int epg_broadcast_set_episode_uri
+  ( epg_broadcast_t *ebc, const char *uri, epg_changes_t *changed )
 {
   int save = 0;
   if (!ebc) return 0;
   if (changed) *changed |= EPG_CHANGED_SERIESLINK;
-  if (strcmp(ebc->serieslink_uri ?: "", uri ?: "")) {
-    free(ebc->serieslink_uri);
-    ebc->serieslink_uri = strdup(uri);
+  if (strcmp(ebc->episode_uri ?: "", uri ?: "")) {
+    free(ebc->episode_uri);
+    ebc->episode_uri = strdup(uri);
     save = 1;
   }
   return save;
 }
 
 int epg_broadcast_set_dvb_eid
-  ( epg_broadcast_t *b, uint16_t dvb_eid, uint32_t *changed )
+  ( epg_broadcast_t *b, uint16_t dvb_eid, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u16(b, &b->dvb_eid, dvb_eid,
@@ -1535,7 +1049,7 @@ int epg_broadcast_set_dvb_eid
 }
 
 int epg_broadcast_set_is_widescreen
-  ( epg_broadcast_t *b, uint8_t ws, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t ws, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u8(b, &b->is_widescreen, ws,
@@ -1543,7 +1057,7 @@ int epg_broadcast_set_is_widescreen
 }
 
 int epg_broadcast_set_is_hd
-  ( epg_broadcast_t *b, uint8_t hd, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t hd, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u8(b, &b->is_hd, hd,
@@ -1551,7 +1065,7 @@ int epg_broadcast_set_is_hd
 }
 
 int epg_broadcast_set_lines
-  ( epg_broadcast_t *b, uint16_t lines, uint32_t *changed )
+  ( epg_broadcast_t *b, uint16_t lines, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u16(b, &b->lines, lines,
@@ -1559,7 +1073,7 @@ int epg_broadcast_set_lines
 }
 
 int epg_broadcast_set_aspect
-  ( epg_broadcast_t *b, uint16_t aspect, uint32_t *changed )
+  ( epg_broadcast_t *b, uint16_t aspect, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u16(b, &b->aspect, aspect,
@@ -1567,7 +1081,7 @@ int epg_broadcast_set_aspect
 }
 
 int epg_broadcast_set_is_deafsigned
-  ( epg_broadcast_t *b, uint8_t ds, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t ds, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u8(b, &b->is_deafsigned, ds,
@@ -1575,7 +1089,7 @@ int epg_broadcast_set_is_deafsigned
 }
 
 int epg_broadcast_set_is_subtitled
-  ( epg_broadcast_t *b, uint8_t st, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t st, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u8(b, &b->is_subtitled, st,
@@ -1583,7 +1097,7 @@ int epg_broadcast_set_is_subtitled
 }
 
 int epg_broadcast_set_is_audio_desc
-  ( epg_broadcast_t *b, uint8_t ad, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t ad, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u8(b, &b->is_audio_desc, ad,
@@ -1591,7 +1105,7 @@ int epg_broadcast_set_is_audio_desc
 }
 
 int epg_broadcast_set_is_new
-  ( epg_broadcast_t *b, uint8_t n, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t n, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u8(b, &b->is_new, n,
@@ -1599,15 +1113,47 @@ int epg_broadcast_set_is_new
 }
 
 int epg_broadcast_set_is_repeat
-  ( epg_broadcast_t *b, uint8_t r, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t r, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_u8(b, &b->is_repeat, r,
                             changed, EPG_CHANGED_IS_REPEAT);
 }
 
+int epg_broadcast_set_is_bw
+  ( epg_broadcast_t *b, uint8_t bw, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_u8(b, &b->is_bw, bw,
+                            changed, EPG_CHANGED_IS_BW);
+}
+
+int epg_broadcast_set_star_rating
+  ( epg_broadcast_t *b, uint8_t stars, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_u8(b, &b->star_rating, stars,
+                            changed, EPG_CHANGED_STAR_RATING);
+}
+
+int epg_broadcast_set_title
+  ( epg_broadcast_t *b, const lang_str_t *title, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_lang_str(b, &b->title, title,
+                                  changed, EPG_CHANGED_TITLE);
+}
+
+int epg_broadcast_set_subtitle
+  ( epg_broadcast_t *b, const lang_str_t *subtitle, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_lang_str(b, &b->subtitle,
+                                  subtitle, changed, EPG_CHANGED_SUBTITLE);
+}
+
 int epg_broadcast_set_summary
-  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
+  ( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_lang_str(b, &b->summary, str,
@@ -1615,7 +1161,7 @@ int epg_broadcast_set_summary
 }
 
 int epg_broadcast_set_description
-  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
+  ( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
 {
   if (!b) return 0;
   return _epg_object_set_lang_str(b, &b->description, str,
@@ -1623,7 +1169,7 @@ int epg_broadcast_set_description
 }
 
 int epg_broadcast_set_credits
-( epg_broadcast_t *b, const htsmsg_t *credits, uint32_t *changed )
+( epg_broadcast_t *b, const htsmsg_t *credits, epg_changes_t *changed )
 {
   if (!b) return 0;
   const int mod = _epg_object_set_htsmsg(b, &b->credits, credits, changed, EPG_CHANGED_CREDITS);
@@ -1660,14 +1206,14 @@ int epg_broadcast_set_credits
 }
 
 int epg_broadcast_set_category
-( epg_broadcast_t *b, const string_list_t *msg, uint32_t *changed )
+( epg_broadcast_t *b, const string_list_t *msg, epg_changes_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, const string_list_t *msg, uint32_t *changed )
+( epg_broadcast_t *b, const string_list_t *msg, epg_changes_t *changed )
 {
   if (!b) return 0;
   const int mod = _epg_object_set_string_list(b, &b->keyword, msg, changed, EPG_CHANGED_KEYWORD);
@@ -1688,6 +1234,133 @@ int epg_broadcast_set_keyword
   return mod;
 }
 
+int epg_broadcast_set_image
+  ( epg_broadcast_t *b, const char *image, epg_changes_t *changed )
+{
+  int save;
+  if (!b) return 0;
+  save = _epg_object_set_str(b, &b->image, image,
+                             changed, EPG_CHANGED_IMAGE);
+  if (save)
+    imagecache_get_id(image);
+  return save;
+}
+
+int epg_broadcast_set_epnumber
+  ( epg_broadcast_t *b, uint16_t number, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_u16(b, &b->epnum.e_num, number,
+                             changed, EPG_CHANGED_EPNUM_NUM);
+}
+
+int epg_broadcast_set_eppart
+  ( epg_broadcast_t *b, uint16_t part, uint16_t count,
+    epg_changes_t *changed )
+{
+  int save = 0;
+  if (!b) return 0;
+  save |= _epg_object_set_u16(b, &b->epnum.p_num, part,
+                              changed, EPG_CHANGED_EPPAR_NUM);
+  save |= _epg_object_set_u16(b, &b->epnum.p_cnt, count,
+                              changed, EPG_CHANGED_EPPAR_CNT);
+  return save;
+}
+
+int epg_broadcast_set_epnum
+  ( epg_broadcast_t *b, epg_episode_num_t *num, epg_changes_t *changed )
+{
+  int save = 0;
+  static epg_episode_num_t _zero = { 0 };
+  if (!b)
+    return 0;
+  if (!num)
+    num = &_zero;
+  if (num->s_num)
+    save |= _epg_object_set_u16(b, &b->epnum.s_num,
+                                num->s_num, changed, EPG_CHANGED_EPSER_NUM);
+  if (num->s_cnt)
+    save |= _epg_object_set_u16(b, &b->epnum.s_cnt,
+                                num->s_cnt, changed, EPG_CHANGED_EPSER_CNT);
+  if (num->e_num)
+    save |= _epg_object_set_u16(b, &b->epnum.e_num,
+                                num->e_num, changed, EPG_CHANGED_EPNUM_NUM);
+  if (num->e_cnt)
+    save |= _epg_object_set_u16(b, &b->epnum.e_cnt,
+                                num->e_cnt, changed, EPG_CHANGED_EPNUM_CNT);
+  if (num->p_num)
+    save |= _epg_object_set_u16(b, &b->epnum.p_num,
+                                num->p_num, changed, EPG_CHANGED_EPPAR_NUM);
+  if (num->p_cnt)
+    save |= _epg_object_set_u16(b, &b->epnum.p_cnt,
+                                num->p_cnt, changed, EPG_CHANGED_EPPAR_CNT);
+  if (num->text)
+    save |= _epg_object_set_str(b, &b->epnum.text,
+                                num->text, changed, EPG_CHANGED_EPTEXT);
+  return save;
+}
+
+int epg_broadcast_set_genre
+  ( epg_broadcast_t *b, epg_genre_list_t *genre, epg_changes_t *changed )
+{
+  int save = 0;
+  epg_genre_t *g1, *g2;
+
+  if (!b) return 0;
+
+  if (changed) *changed |= EPG_CHANGED_GENRE;
+
+  g1 = LIST_FIRST(&b->genre);
+
+  /* Remove old */
+  while (g1) {
+    g2 = LIST_NEXT(g1, link);
+    if (!epg_genre_list_contains(genre, g1, 0)) {
+      LIST_REMOVE(g1, link);
+      free(g1);
+      save = 1;
+    }
+    g1 = g2;
+  }
+  
+  /* Insert all entries */
+  if (genre) {
+    LIST_FOREACH(g1, genre, link)
+      save |= epg_genre_list_add(&b->genre, g1);
+  }
+
+  return save;
+}
+
+int epg_broadcast_set_copyright_year
+  ( epg_broadcast_t *b, uint16_t year, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_u16(b, &b->copyright_year, year,
+                             changed, EPG_CHANGED_COPYRIGHT_YEAR);
+}
+
+int epg_broadcast_set_age_rating
+  ( epg_broadcast_t *b, uint8_t age, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  return _epg_object_set_u8(b, &b->age_rating, age,
+                            changed, EPG_CHANGED_AGE_RATING);
+}
+
+int epg_broadcast_set_first_aired
+  ( epg_broadcast_t *b, time_t aired, epg_changes_t *changed )
+{
+  if (!b) return 0;
+  if (changed) *changed |= EPG_CHANGED_FIRST_AIRED;
+  if (b->first_aired != aired) {
+    b->first_aired = aired;
+    _epg_object_set_updated(b);
+    return 1;
+  }
+  return 0;
+}
+
 epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast )
 {
   if ( !broadcast ) return NULL;
@@ -1696,14 +1369,14 @@ epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast )
 
 const char *epg_broadcast_get_title ( epg_broadcast_t *b, const char *lang )
 {
-  if (!b || !b->episode) return NULL;
-  return epg_episode_get_title(b->episode, lang);
+  if (!b && !b->title) return NULL;
+  return lang_str_get(b->title, lang);
 }
 
 const char *epg_broadcast_get_subtitle ( epg_broadcast_t *b, const char *lang )
 {
-  if (!b || !b->episode) return NULL;
-  return epg_episode_get_subtitle(b->episode, lang);
+  if (!b && !b->subtitle) return NULL;
+  return lang_str_get(b->subtitle, lang);
 }
 
 const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang )
@@ -1730,16 +1403,38 @@ const char *epg_broadcast_get_description ( epg_broadcast_t *b, const char *lang
   return lang_str_get(b->description, lang);
 }
 
+void epg_broadcast_get_epnum ( const epg_broadcast_t *b, epg_episode_num_t *num )
+{
+  if (!b || !num) {
+    if (num)
+      memset(num, 0, sizeof(*num));
+    return;
+  }
+  *num = b->epnum;
+}
+
+size_t epg_broadcast_epnumber_format
+  ( epg_broadcast_t *b, char *buf, size_t len,
+    const char *pre,  const char *sfmt,
+    const char *sep,  const char *efmt,
+    const char *cfmt )
+{
+  if (!b) return 0;
+  epg_episode_num_t num;
+  epg_broadcast_get_epnum(b, &num);
+  return epg_episode_num_format(&num, buf, len, pre,
+                                sfmt, sep, efmt, cfmt);
+}
+
 htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
 {
-  htsmsg_t *m;
+  htsmsg_t *m, *a;
+  epg_genre_t *eg;
   char ubuf[UUID_HEX_SIZE];
   if (!broadcast) return NULL;
-  if (!broadcast->episode || !broadcast->episode->uri) return NULL;
   if (!(m = _epg_object_serialize((epg_object_t*)broadcast))) return NULL;
   htsmsg_add_s64(m, "start", broadcast->start);
   htsmsg_add_s64(m, "stop", broadcast->stop);
-  htsmsg_add_str(m, "episode", broadcast->episode->uri);
   if (broadcast->channel)
     htsmsg_add_str(m, "channel", channel_get_uuid(broadcast->channel, ubuf));
   if (broadcast->dvb_eid)
@@ -1748,6 +1443,8 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
     htsmsg_add_u32(m, "is_widescreen", 1);
   if (broadcast->is_hd)
     htsmsg_add_u32(m, "is_hd", 1);
+  if (broadcast->is_bw)
+    htsmsg_add_u32(m, "is_bw", 1);
   if (broadcast->lines)
     htsmsg_add_u32(m, "lines", broadcast->lines);
   if (broadcast->aspect)
@@ -1762,12 +1459,32 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
     htsmsg_add_u32(m, "is_new", 1);
   if (broadcast->is_repeat)
     htsmsg_add_u32(m, "is_repeat", 1);
+  if (broadcast->star_rating)
+    htsmsg_add_u32(m, "star_rating", broadcast->star_rating);
+  if (broadcast->age_rating)
+    htsmsg_add_u32(m, "age_rating", broadcast->age_rating);
+  if (broadcast->image)
+    htsmsg_add_str(m, "image", broadcast->image);
+  if (broadcast->title)
+    lang_str_serialize(broadcast->summary, m, "title");
+  if (broadcast->subtitle)
+    lang_str_serialize(broadcast->summary, m, "subtitle");
   if (broadcast->summary)
     lang_str_serialize(broadcast->summary, m, "summary");
   if (broadcast->description)
     lang_str_serialize(broadcast->description, m, "description");
+  htsmsg_add_msg(m, "epnum", epg_episode_epnum_serialize(&broadcast->epnum));
+  LIST_FOREACH(eg, &broadcast->genre, link) {
+    if (!a) a = htsmsg_create_list();
+    htsmsg_add_u32(a, NULL, eg->code);
+  }
+  if (a) htsmsg_add_msg(m, "genre", a);
+  if (broadcast->copyright_year)
+    htsmsg_add_u32(m, "copyright_year", broadcast->copyright_year);
+  if (broadcast->first_aired)
+    htsmsg_add_s64(m, "first_aired", broadcast->first_aired);
   if (broadcast->credits)
-      htsmsg_add_msg(m, "credits", htsmsg_copy(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");
@@ -1776,7 +1493,6 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
   /* No need to serialize keyword_cached since it is rebuilt from keyword */
   if (broadcast->serieslink_uri)
     htsmsg_add_str(m, "serieslink", broadcast->serieslink_uri);
-  
   return m;
 }
 
@@ -1785,13 +1501,15 @@ epg_broadcast_t *epg_broadcast_deserialize
 {
   channel_t *ch = NULL;
   epg_broadcast_t *ebc, **skel = _epg_broadcast_skel();
-  epg_episode_t *ee;
   lang_str_t *ls;
   htsmsg_t *hm;
+  htsmsg_field_t *f;
   string_list_t *sl;
   const char *str;
-  uint32_t eid, u32, changes = 0;
-  int64_t start, stop;
+  uint32_t eid, u32;
+  epg_changes_t changes = 0;
+  int64_t start, stop, s64;
+  epg_episode_num_t num;
 
   if (htsmsg_get_s64(m, "start", &start)) return NULL;
   if (htsmsg_get_s64(m, "stop", &stop)) return NULL;
@@ -1802,9 +1520,6 @@ epg_broadcast_t *epg_broadcast_deserialize
 
   _epg_object_deserialize(m, (epg_object_t*)*skel);
 
-  if (!(ee  = epg_episode_find_by_uri(str, (*skel)->grabber, 0, NULL, NULL)))
-    return NULL;
-
   /* Set properties */
   (*skel)->start   = start;
   (*skel)->stop    = stop;
@@ -1825,6 +1540,8 @@ epg_broadcast_t *epg_broadcast_deserialize
     *save |= epg_broadcast_set_is_widescreen(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_hd", &u32))
     *save |= epg_broadcast_set_is_hd(ebc, u32, &changes);
+  if (!htsmsg_get_u32(m, "is_bw", &u32))
+    *save |= epg_broadcast_set_is_bw(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "lines", &u32))
     *save |= epg_broadcast_set_lines(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "aspect", &u32))
@@ -1839,38 +1556,69 @@ epg_broadcast_t *epg_broadcast_deserialize
     *save |= epg_broadcast_set_is_new(ebc, u32, &changes);
   if (!htsmsg_get_u32(m, "is_repeat", &u32))
     *save |= epg_broadcast_set_is_repeat(ebc, u32, &changes);
+  if (!htsmsg_get_u32(m, "star_rating", &u32))
+    *save |= epg_broadcast_set_star_rating(ebc, u32, &changes);
+  if (!htsmsg_get_u32(m, "age_rating", &u32))
+    *save |= epg_broadcast_set_age_rating(ebc, u32, &changes);
+
+  if ((str = htsmsg_get_str(m, "image")))
+    *save |= epg_broadcast_set_image(ebc, str, &changes);
 
+  if ((hm = htsmsg_get_list(m, "genre"))) {
+    epg_genre_list_t *egl = calloc(1, sizeof(epg_genre_list_t));
+    HTSMSG_FOREACH(f, hm) {
+      epg_genre_t genre;
+      genre.code = (uint8_t)f->hmf_s64;
+      epg_genre_list_add(egl, &genre);
+    }
+    *save |= epg_broadcast_set_genre(ebc, egl, &changes);
+    epg_genre_list_destroy(egl);
+  }
+
+  if ((ls = lang_str_deserialize(m, "title"))) {
+    *save |= epg_broadcast_set_title(ebc, ls, &changes);
+    lang_str_destroy(ls);
+  }
+  if ((ls = lang_str_deserialize(m, "subtitle"))) {
+    *save |= epg_broadcast_set_subtitle(ebc, ls, &changes);
+    lang_str_destroy(ls);
+  }
   if ((ls = lang_str_deserialize(m, "summary"))) {
     *save |= epg_broadcast_set_summary(ebc, ls, &changes);
     lang_str_destroy(ls);
   }
-
   if ((ls = lang_str_deserialize(m, "description"))) {
     *save |= epg_broadcast_set_description(ebc, ls, &changes);
     lang_str_destroy(ls);
   }
 
-  if ((hm = htsmsg_get_map(m, "credits"))) {
-      *save |= epg_broadcast_set_credits(ebc, hm, &changes);
+  if ((hm = htsmsg_get_map(m, "epnum"))) {
+    epg_episode_epnum_deserialize(hm, &num);
+    *save |= epg_broadcast_set_epnum(ebc, &num, &changes);
+    if (num.text) free(num.text);
   }
 
+  if (!htsmsg_get_u32(m, "copyright_year", &u32))
+    *save |= epg_broadcast_set_copyright_year(ebc, u32, &changes);
+  if (!htsmsg_get_s64(m, "first_aired", &s64))
+    *save |= epg_broadcast_set_first_aired(ebc, (time_t)s64, &changes);
+
+  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);
-      string_list_destroy(sl);
+    *save |= epg_broadcast_set_keyword(ebc, sl, &changes);
+    string_list_destroy(sl);
   }
-
   if ((sl = string_list_deserialize(m, "category"))) {
-      *save |= epg_broadcast_set_category(ebc, sl, &changes);
-      string_list_destroy(sl);
+    *save |= epg_broadcast_set_category(ebc, sl, &changes);
+    string_list_destroy(sl);
   }
 
   /* Series link */
   if ((str = htsmsg_get_str(m, "serieslink")))
     *save |= epg_broadcast_set_serieslink_uri(ebc, str, &changes);
 
-  /* Set the episode */
-  *save |= epg_broadcast_set_episode(ebc, ee, &changes);
-
   *save |= epg_broadcast_change_finish(ebc, changes, 0);
 
   return ebc;
@@ -2286,7 +2034,6 @@ static void
 _eq_add ( epg_query_t *eq, epg_broadcast_t *e )
 {
   const char *s, *lang = eq->lang;
-  epg_episode_t *ep;
   int fulltext = eq->stitle && eq->fulltext;
 
   /* Filtering */
@@ -2298,11 +2045,10 @@ _eq_add ( epg_query_t *eq, epg_broadcast_t *e )
     int64_t duration = (int64_t)e->stop - (int64_t)e->start;
     if (_eq_comp_num(&eq->duration, duration)) return;
   }
-  ep = e->episode;
   if (eq->stars.comp != EC_NO)
-    if (_eq_comp_num(&eq->stars, ep->star_rating)) return;
+    if (_eq_comp_num(&eq->stars, e->star_rating)) return;
   if (eq->age.comp != EC_NO)
-    if (_eq_comp_num(&eq->age, ep->age_rating)) return;
+    if (_eq_comp_num(&eq->age, e->age_rating)) return;
   if (eq->channel_num.comp != EC_NO)
     if (_eq_comp_num(&eq->channel_num, channel_get_number(e->channel))) return;
   if (eq->channel_name.comp != EC_NO)
@@ -2335,7 +2081,7 @@ _eq_add ( epg_query_t *eq, epg_broadcast_t *e )
     for (i = 0; i < eq->genre_count; i++) {
       genre.code = eq->genre[i];
       if (genre.code == 0) continue;
-      if (epg_genre_list_contains(&e->episode->genre, &genre, 1)) r++;
+      if (epg_genre_list_contains(&e->genre, &genre, 1)) r++;
     }
     if (!r) return;
   }
@@ -2344,9 +2090,9 @@ _eq_add ( epg_query_t *eq, epg_broadcast_t *e )
       return;
   }
   if (fulltext) {
-    if ((s = epg_episode_get_title(ep, lang)) == NULL ||
+    if ((s = epg_broadcast_get_title(e, lang)) == NULL ||
         regex_match(&eq->stitle_re, s)) {
-      if ((s = epg_episode_get_subtitle(ep, lang)) == NULL ||
+      if ((s = epg_broadcast_get_subtitle(e, lang)) == NULL ||
           regex_match(&eq->stitle_re, s)) {
         if ((s = epg_broadcast_get_summary(e, lang)) == NULL ||
             regex_match(&eq->stitle_re, s)) {
@@ -2365,12 +2111,12 @@ _eq_add ( epg_query_t *eq, epg_broadcast_t *e )
     }
   }
   if (eq->title.comp != EC_NO || (eq->stitle && !fulltext)) {
-    if ((s = epg_episode_get_title(ep, lang)) == NULL) return;
+    if ((s = epg_broadcast_get_title(e, lang)) == NULL) return;
     if (eq->stitle && !fulltext && regex_match(&eq->stitle_re, s)) return;
     if (eq->title.comp != EC_NO && _eq_comp_str(&eq->title, s)) return;
   }
   if (eq->subtitle.comp != EC_NO) {
-    if ((s = epg_episode_get_subtitle(ep, lang)) == NULL) return;
+    if ((s = epg_broadcast_get_subtitle(e, lang)) == NULL) return;
     if (_eq_comp_str(&eq->subtitle, s)) return;
   }
   if (eq->summary.comp != EC_NO) {
@@ -2396,10 +2142,8 @@ static void
 _eq_add_channel ( epg_query_t *eq, channel_t *ch )
 {
   epg_broadcast_t *ebc;
-  RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
-    if (ebc->episode)
-      _eq_add(eq, ebc);
-  }
+  RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link)
+    _eq_add(eq, ebc);
 }
 
 static int
@@ -2561,37 +2305,37 @@ static int _epg_sort_channel_num_ascending ( const void *a, const void *b, void
 
 static int _epg_sort_channel_num_descending ( const void *a, const void *b, void *eq )
 {
-  int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel);
-  int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel);
+  const int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel);
+  const int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel);
   return v2 - v1;
 }
 
 static int _epg_sort_stars_ascending ( const void *a, const void *b, void *eq )
 {
-  return (*(epg_broadcast_t**)a)->episode->star_rating - (*(epg_broadcast_t**)b)->episode->star_rating;
+  return (*(epg_broadcast_t**)a)->star_rating - (*(epg_broadcast_t**)b)->star_rating;
 }
 
 static int _epg_sort_stars_descending ( const void *a, const void *b, void *eq )
 {
-  return (*(epg_broadcast_t**)b)->episode->star_rating - (*(epg_broadcast_t**)a)->episode->star_rating;
+  return (*(epg_broadcast_t**)b)->star_rating - (*(epg_broadcast_t**)a)->star_rating;
 }
 
 static int _epg_sort_age_ascending ( const void *a, const void *b, void *eq )
 {
-  return (*(epg_broadcast_t**)a)->episode->age_rating - (*(epg_broadcast_t**)b)->episode->age_rating;
+  return (*(epg_broadcast_t**)a)->age_rating - (*(epg_broadcast_t**)b)->age_rating;
 }
 
 static int _epg_sort_age_descending ( const void *a, const void *b, void *eq )
 {
-  return (*(epg_broadcast_t**)b)->episode->age_rating - (*(epg_broadcast_t**)a)->episode->age_rating;
+  return (*(epg_broadcast_t**)b)->age_rating - (*(epg_broadcast_t**)a)->age_rating;
 }
 
-static uint64_t _epg_sort_genre_hash( epg_episode_t *ep )
+static uint64_t _epg_sort_genre_hash( epg_broadcast_t *b )
 {
   uint64_t h = 0, t;
   epg_genre_t *g;
 
-  LIST_FOREACH(g, &ep->genre, link) {
+  LIST_FOREACH(g, &b->genre, link) {
     t = h >> 28;
     h <<= 8;
     h += (uint64_t)g->code + t;
@@ -2601,8 +2345,8 @@ static uint64_t _epg_sort_genre_hash( epg_episode_t *ep )
 
 static int _epg_sort_genre_ascending ( const void *a, const void *b, void *eq )
 {
-  uint64_t v1 = _epg_sort_genre_hash((*(epg_broadcast_t**)a)->episode);
-  uint64_t v2 = _epg_sort_genre_hash((*(epg_broadcast_t**)b)->episode);
+  const uint64_t v1 = _epg_sort_genre_hash(*(epg_broadcast_t**)a);
+  const uint64_t v2 = _epg_sort_genre_hash(*(epg_broadcast_t**)b);
   return v1 - v2;
 }
 
@@ -2750,11 +2494,8 @@ int epg_config_deserialize( htsmsg_t *m )
 
 void epg_skel_done(void)
 {
-  epg_object_t **skel;
   epg_broadcast_t **broad;
 
-  skel = _epg_episode_skel();
-  free(*skel); *skel = NULL;
   broad = _epg_broadcast_skel();
   free(*broad); *broad = NULL;
 }
index 45c3eb8aa767411284097e9cec2268adc7867691..294812cae4fa54c4ada712a0aa8ff3983f64e0fc 100644 (file)
--- a/src/epg.h
+++ b/src/epg.h
@@ -38,7 +38,6 @@ struct epggrab_module;
  */
 typedef LIST_HEAD(,epg_object)     epg_object_list_t;
 typedef RB_HEAD  (,epg_object)     epg_object_tree_t;
-typedef LIST_HEAD(,epg_episode)    epg_episode_list_t;
 typedef LIST_HEAD(,epg_broadcast)  epg_broadcast_list_t;
 typedef RB_HEAD  (,epg_broadcast)  epg_broadcast_tree_t;
 typedef LIST_HEAD(,epg_genre)      epg_genre_list_t;
@@ -48,7 +47,6 @@ typedef LIST_HEAD(,epg_genre)      epg_genre_list_t;
  */
 typedef struct epg_genre           epg_genre_t;
 typedef struct epg_object          epg_object_t;
-typedef struct epg_episode         epg_episode_t;
 typedef struct epg_broadcast       epg_broadcast_t;
 
 extern int epg_in_load;
@@ -104,22 +102,48 @@ htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix, const char *la
 typedef enum epg_object_type
 {
   EPG_UNDEF,
-  EPG_EPISODE,
   EPG_BROADCAST,
 } epg_object_type_t;
 #define EPG_TYPEMAX EPG_BROADCAST
 
-/* Change flags - shared */
-#define EPG_CHANGED_CREATE        (1<<0)
-#define EPG_CHANGED_TITLE         (1<<1)
-#define EPG_CHANGED_SUBTITLE      (1<<2)
-#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
+/* Change type */
+typedef uint64_t epg_changes_t;
+
+/* Change flags */
+#define EPG_CHANGED_CREATE         (1ULL<<0)
+#define EPG_CHANGED_TITLE          (1ULL<<1)
+#define EPG_CHANGED_SUBTITLE       (1ULL<<2)
+#define EPG_CHANGED_SUMMARY        (1ULL<<3)
+#define EPG_CHANGED_DESCRIPTION    (1ULL<<4)
+#define EPG_CHANGED_IMAGE          (1ULL<<5)
+#define EPG_CHANGED_CREDITS        (1ULL<<6)
+#define EPG_CHANGED_CATEGORY       (1ULL<<7)
+#define EPG_CHANGED_KEYWORD        (1ULL<<8)
+#define EPG_CHANGED_DVB_EID        (1ULL<<9)
+#define EPG_CHANGED_IS_WIDESCREEN  (1ULL<<10)
+#define EPG_CHANGED_IS_HD          (1ULL<<11)
+#define EPG_CHANGED_LINES          (1ULL<<12)
+#define EPG_CHANGED_ASPECT         (1ULL<<13)
+#define EPG_CHANGED_DEAFSIGNED     (1ULL<<14)
+#define EPG_CHANGED_SUBTITLED      (1ULL<<15)
+#define EPG_CHANGED_AUDIO_DESC     (1ULL<<16)
+#define EPG_CHANGED_IS_NEW         (1ULL<<17)
+#define EPG_CHANGED_IS_REPEAT      (1ULL<<18)
+#define EPG_CHANGED_SERIESLINK     (1ULL<<19)
+#define EPG_CHANGED_EPISODE        (1ULL<<20)
+#define EPG_CHANGED_GENRE          (1ULL<<21)
+#define EPG_CHANGED_EPNUM_NUM      (1ULL<<22)
+#define EPG_CHANGED_EPNUM_CNT      (1ULL<<23)
+#define EPG_CHANGED_EPPAR_NUM      (1ULL<<24)
+#define EPG_CHANGED_EPPAR_CNT      (1ULL<<25)
+#define EPG_CHANGED_EPSER_NUM      (1ULL<<26)
+#define EPG_CHANGED_EPSER_CNT      (1ULL<<27)
+#define EPG_CHANGED_EPTEXT         (1ULL<<28)
+#define EPG_CHANGED_IS_BW          (1ULL<<29)
+#define EPG_CHANGED_STAR_RATING    (1ULL<<30)
+#define EPG_CHANGED_AGE_RATING     (1ULL<<31)
+#define EPG_CHANGED_FIRST_AIRED    (1ULL<<32)
+#define EPG_CHANGED_COPYRIGHT_YEAR (1ULL<<33)
 
 typedef struct epg_object_ops {
   void (*getref)  ( void *o );        ///< Get a reference
@@ -138,7 +162,6 @@ struct epg_object
  
   epg_object_type_t       type;       ///< Specific object type
   uint32_t                id;         ///< Internal ID
-  char                   *uri;        ///< Unique ID (from grabber)
   time_t                  updated;    ///< Last time object was changed
 
   uint8_t                 _updated;   ///< Flag to indicate updated
@@ -157,27 +180,9 @@ htsmsg_t     *epg_object_serialize   ( epg_object_t *eo );
 epg_object_t *epg_object_deserialize ( htsmsg_t *msg, int create, int *save );
 
 /* ************************************************************************
- * Episode
+ * Episode numbering
  * ***********************************************************************/
 
-/* Change flags */
-#define EPG_CHANGED_GENRE        (1<<(EPG_CHANGED_SLAST+1))
-#define EPG_CHANGED_EPNUM_NUM    (1<<(EPG_CHANGED_SLAST+2))
-#define EPG_CHANGED_EPNUM_CNT    (1<<(EPG_CHANGED_SLAST+3))
-#define EPG_CHANGED_EPPAR_NUM    (1<<(EPG_CHANGED_SLAST+4))
-#define EPG_CHANGED_EPPAR_CNT    (1<<(EPG_CHANGED_SLAST+5))
-#define EPG_CHANGED_EPSER_NUM    (1<<(EPG_CHANGED_SLAST+6))
-#define EPG_CHANGED_EPSER_CNT    (1<<(EPG_CHANGED_SLAST+7))
-#define EPG_CHANGED_EPTEXT       (1<<(EPG_CHANGED_SLAST+8))
-#define EPG_CHANGED_IS_BW        (1<<(EPG_CHANGED_SLAST+9))
-#define EPG_CHANGED_STAR_RATING  (1<<(EPG_CHANGED_SLAST+10))
-#define EPG_CHANGED_AGE_RATING   (1<<(EPG_CHANGED_SLAST+11))
-#define EPG_CHANGED_FIRST_AIRED  (1<<(EPG_CHANGED_SLAST+12))
-#define EPG_CHANGED_COPYRIGHT_YEAR (1<<(EPG_CHANGED_SLAST+13))
-
-/* Episode numbering object - this is for some back-compat and also
- * to allow episode information to be "collated" into easy to use object
- */
 typedef struct epg_episode_num
 {
   uint16_t s_num; ///< Series number
@@ -189,102 +194,9 @@ typedef struct epg_episode_num
   char     *text; ///< Arbitary text description of episode num
 } epg_episode_num_t;
 
-/* Object */
-struct epg_episode
-{
-  epg_object_t;                             ///< Parent object
-
-  lang_str_t                *title;         ///< Title
-  lang_str_t                *subtitle;      ///< Sub-title
-  lang_str_t                *summary;       ///< Summary
-  lang_str_t                *description;   ///< An extended description
-  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
-
-  uint8_t                    is_bw;          ///< Is black and white
-  uint8_t                    star_rating;    ///< Star rating
-  uint8_t                    age_rating;     ///< Age certificate
-  time_t                     first_aired;    ///< Original airdate
-  uint16_t                   copyright_year; ///< xmltv DTD gives a tag "date" (separate to previously-shown/first aired).
-                                             ///< This is the date programme was "finished...probably the copyright date."
-                                             ///< We'll call it copyright_year since words like "complete" and "finished"
-                                             ///< sound too similar to dvr recorded functionality. We'll only store the
-                                             ///< year since we only get year not month and day.
-  LIST_ENTRY(epg_episode)    blink;         ///< Brand link
-  LIST_ENTRY(epg_episode)    slink;         ///< Season link
-  epg_broadcast_list_t       broadcasts;    ///< Broadcast list
-};
-
-/* Lookup */
-epg_episode_t *epg_episode_find_by_uri
-  ( const char *uri, struct epggrab_module *src, int create, int *save, uint32_t *changes );
-epg_episode_t *epg_episode_find_by_id ( uint32_t id );
-epg_episode_t *epg_episode_find_by_broadcast
-  ( epg_broadcast_t *b, struct epggrab_module *src, int create, int *save, uint32_t *changes );
-
-/* Post-modify */
-int epg_episode_change_finish( epg_episode_t *s, uint32_t changed, int merge )
-  __attribute__((warn_unused_result));
-
-/* Accessors */
-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 );
-const char *epg_episode_get_summary
-  ( const epg_episode_t *e, const char *lang );
-const char *epg_episode_get_description
-  ( const epg_episode_t *e, const char *lang );
-
-/* Mutators */
-int epg_episode_set_title
-  ( epg_episode_t *e, const lang_str_t *title, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_subtitle
-  ( epg_episode_t *e, const lang_str_t *subtitle, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_summary
-  ( epg_episode_t *e, const lang_str_t *summary, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_description
-  ( epg_episode_t *e, const lang_str_t *description, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_number
-  ( epg_episode_t *e, uint16_t number, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_part
-  ( epg_episode_t *e, uint16_t number, uint16_t count, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_epnum
-  ( epg_episode_t *e, epg_episode_num_t *num, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_genre
-  ( epg_episode_t *e, epg_genre_list_t *g, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_image
-  ( epg_episode_t *e, const char *i, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_is_bw
-  ( epg_episode_t *e, uint8_t bw, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_first_aired
-  ( epg_episode_t *e, time_t aired, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_star_rating
-  ( epg_episode_t *e, uint8_t stars, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_copyright_year
-  ( epg_episode_t *e, uint16_t stars, uint32_t *changed )
-  __attribute__((warn_unused_result));
-int epg_episode_set_age_rating
-  ( epg_episode_t *e, uint8_t age, uint32_t *changed )
-  __attribute__((warn_unused_result));
+htsmsg_t *epg_episode_epnum_serialize( epg_episode_num_t *num );
+void epg_episode_epnum_deserialize( htsmsg_t *m, epg_episode_num_t *num );
 
-// Note: this does NOT strdup the text field
-void epg_episode_get_epnum
-  ( const epg_episode_t *e, epg_episode_num_t *epnum );
 /* EpNum format helper */
 // output string will be:
 // if (episode_num) 
@@ -295,64 +207,40 @@ void epg_episode_get_epnum
 //   ret += sprintf(efmt, episode_num)
 //   if (episode_cnt) ret += sprintf(cfmt, episode_cnt)
 // and will return num chars written
-size_t epg_episode_epnum_format
+size_t epg_episode_num_format
   ( epg_episode_num_t *epnum, char *buf, size_t len,
     const char *pre,  const char *sfmt,
     const char *sep,  const char *efmt,
     const char *cfmt );
-size_t epg_episode_number_format 
-  ( epg_episode_t *e, char *buf, size_t len,
-    const char *pre,  const char *sfmt,
-    const char *sep,  const char *efmt,
-    const char *cfmt );
 int epg_episode_number_cmp
   ( const epg_episode_num_t *a, const epg_episode_num_t *b );
 int epg_episode_number_cmpfull
   ( const epg_episode_num_t *a, const epg_episode_num_t *b );
 
-htsmsg_t *epg_episode_epnum_serialize( epg_episode_num_t *num );
-void epg_episode_epnum_deserialize( htsmsg_t *m, epg_episode_num_t *num );
-
-/* Matching */
-int epg_episode_fuzzy_match
-  ( epg_episode_t *ee, const char *uri, const char *title,
-    const char *summary, const char *description );
-
-/* Serialization */
-htsmsg_t      *epg_episode_serialize   ( epg_episode_t *b );
-epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save );
-
 /* ************************************************************************
  * Broadcast - specific airing (channel & time) of an episode
  * ***********************************************************************/
 
-#define EPG_CHANGED_DVB_EID      (1<<(EPG_CHANGED_SLAST+1))
-#define EPG_CHANGED_IS_WIDESCREEN (1<<(EPG_CHANGED_SLAST+2))
-#define EPG_CHANGED_IS_HD        (1<<(EPG_CHANGED_SLAST+3))
-#define EPG_CHANGED_LINES        (1<<(EPG_CHANGED_SLAST+4))
-#define EPG_CHANGED_ASPECT       (1<<(EPG_CHANGED_SLAST+5))
-#define EPG_CHANGED_DEAFSIGNED   (1<<(EPG_CHANGED_SLAST+6))
-#define EPG_CHANGED_SUBTITLED    (1<<(EPG_CHANGED_SLAST+7))
-#define EPG_CHANGED_AUDIO_DESC   (1<<(EPG_CHANGED_SLAST+8))
-#define EPG_CHANGED_IS_NEW       (1<<(EPG_CHANGED_SLAST+9))
-#define EPG_CHANGED_IS_REPEAT    (1<<(EPG_CHANGED_SLAST+10))
-#define EPG_CHANGED_EPISODE      (1<<(EPG_CHANGED_SLAST+11))
-#define EPG_CHANGED_SERIESLINK   (1<<(EPG_CHANGED_SLAST+12))
-
 /* Object */
 struct epg_broadcast
 {
   epg_object_t;                                ///< Parent object
+
+  struct channel            *channel;          ///< Channel being broadcast on
+  RB_ENTRY(epg_broadcast)    sched_link;       ///< Schedule link
+  LIST_HEAD(, dvr_entry)     dvr_entries;      ///< Associated DVR entries
   
+  /* */
   uint16_t                   dvb_eid;          ///< DVB Event ID
   time_t                     start;            ///< Start time
   time_t                     stop;             ///< End time
 
   /* Some quality info */
-  uint8_t                    is_widescreen;    ///< Is widescreen
-  uint8_t                    is_hd;            ///< Is HD
   uint16_t                   lines;            ///< Lines in image (quality)
   uint16_t                   aspect;           ///< Aspect ratio (*100)
+  uint8_t                    is_widescreen;    ///< Is widescreen
+  uint8_t                    is_hd;            ///< Is HD
+  uint8_t                    is_bw;            ///< Is black and white
 
   /* Some accessibility support */
   uint8_t                    is_deafsigned;    ///< In screen signing
@@ -360,14 +248,23 @@ struct epg_broadcast
   uint8_t                    is_audio_desc;    ///< Audio description
 
   /* Misc flags */
+  uint8_t                    star_rating;      ///< Star rating
+  uint8_t                    age_rating;       ///< Age certificate
   uint8_t                    is_new;           ///< New series / file premiere
   uint8_t                    is_repeat;        ///< Repeat screening
   uint8_t                    running;          ///< EPG running flag
   uint8_t                    update_running;   ///< new EPG running flag
 
   /* Broadcast level text */
+  lang_str_t                *title;            ///< Title
+  lang_str_t                *subtitle;         ///< Sub-title
   lang_str_t                *summary;          ///< Summary
   lang_str_t                *description;      ///< Description
+
+  char                      *image;            ///< Episode image
+  epg_genre_list_t           genre;            ///< Episode genre(s)
+  epg_episode_num_t          epnum;            ///< Episode numbering
+
   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
   string_list_t             *category;         ///< Extra categories (typically from xmltv) such as "Western" or "Sumo Wrestling".
@@ -375,26 +272,28 @@ struct epg_broadcast
                                                ///< Used with drop-down lists in the GUI.
   string_list_t             *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
-  LIST_ENTRY(epg_broadcast)  sl_link;          ///< SeriesLink link
   char                      *serieslink_uri;   ///< SeriesLink URI
-  struct channel            *channel;          ///< Channel being broadcast on
+  char                      *episode_uri;      ///< Episode URI
 
-  /* DVR */
-  LIST_HEAD(, dvr_entry)     dvr_entries;      ///< Associated DVR entries
+  // Note: do not use epnum directly! use the accessor routine
+
+  time_t                     first_aired;     ///< Original airdate
+  uint16_t                   copyright_year;  ///< xmltv DTD gives a tag "date" (separate to previously-shown/first aired).
+                                              ///< This is the date programme was "finished...probably the copyright date."
+                                              ///< We'll call it copyright_year since words like "complete" and "finished"
+                                              ///< sound too similar to dvr recorded functionality. We'll only store the
+                                              ///< year since we only get year not month and day.
 };
 
 /* Lookup */
 epg_broadcast_t *epg_broadcast_find_by_time 
   ( struct channel *ch, struct epggrab_module *src,
-    time_t start, time_t stop, int create, int *save, uint32_t *changes );
+    time_t start, time_t stop, int create, int *save, epg_changes_t *changes );
 epg_broadcast_t *epg_broadcast_find_by_eid ( struct channel *ch, uint16_t eid );
 epg_broadcast_t *epg_broadcast_find_by_id  ( uint32_t id );
 
 /* Post-modify */
-int epg_broadcast_change_finish( epg_broadcast_t *b, uint32_t changed, int merge )
+int epg_broadcast_change_finish( epg_broadcast_t *b, epg_changes_t changed, int merge )
   __attribute__((warn_unused_result));
 
 /* Special */
@@ -403,58 +302,94 @@ epg_broadcast_t *epg_broadcast_clone
 
 /* Mutators */
 int epg_broadcast_set_dvb_eid
-  ( epg_broadcast_t *b, uint16_t dvb_eid, uint32_t *changed )
+  ( epg_broadcast_t *b, uint16_t dvb_eid, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_running
   ( epg_broadcast_t *b, epg_running_t running )
   __attribute__((warn_unused_result));
-int epg_broadcast_set_episode
-  ( epg_broadcast_t *b, epg_episode_t *e, uint32_t *changed )
-  __attribute__((warn_unused_result));
 int epg_broadcast_set_is_widescreen
-  ( epg_broadcast_t *b, uint8_t ws, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t ws, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_hd
-  ( epg_broadcast_t *b, uint8_t hd, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t hd, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_lines 
-  ( epg_broadcast_t *b, uint16_t lines, uint32_t *changed )
+  ( epg_broadcast_t *b, uint16_t lines, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_aspect
-  ( epg_broadcast_t *b, uint16_t aspect, uint32_t *changed )
+  ( epg_broadcast_t *b, uint16_t aspect, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_deafsigned
-  ( epg_broadcast_t *b, uint8_t ds, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t ds, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_subtitled
-  ( epg_broadcast_t *b, uint8_t st, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t st, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_audio_desc
-  ( epg_broadcast_t *b, uint8_t ad, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t ad, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_new
-  ( epg_broadcast_t *b, uint8_t n, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t n, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_is_repeat
-  ( epg_broadcast_t *b, uint8_t r, uint32_t *changed )
+  ( epg_broadcast_t *b, uint8_t r, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_title
+  ( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_subtitle
+  ( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_summary
-  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
+  ( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_description
-  ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed )
+  ( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_credits
-( epg_broadcast_t *b, const htsmsg_t *msg, uint32_t *changed )
+( epg_broadcast_t *b, const htsmsg_t *msg, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_category
-( epg_broadcast_t *b, const string_list_t *msg, uint32_t *changed )
+( epg_broadcast_t *b, const string_list_t *msg, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_keyword
-( epg_broadcast_t *b, const string_list_t *msg, uint32_t *changed )
+( epg_broadcast_t *b, const string_list_t *msg, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 int epg_broadcast_set_serieslink_uri
-  ( epg_broadcast_t *b, const char *uri, uint32_t *changed )
+  ( epg_broadcast_t *b, const char *uri, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_episode_uri
+  ( epg_broadcast_t *b, const char *uri, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_epnumber
+  ( epg_broadcast_t *b, uint16_t number, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_eppart
+  ( epg_broadcast_t *b, uint16_t number, uint16_t count, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_epnum
+  ( epg_broadcast_t *b, epg_episode_num_t *num, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_genre
+  ( epg_broadcast_t *b, epg_genre_list_t *g, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_image
+  ( epg_broadcast_t *b, const char *i, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_is_bw
+  ( epg_broadcast_t *b, uint8_t bw, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_first_aired
+  ( epg_broadcast_t *b, time_t aired, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_star_rating
+  ( epg_broadcast_t *b, uint8_t stars, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_copyright_year
+  ( epg_broadcast_t *b, uint16_t stars, epg_changes_t *changed )
+  __attribute__((warn_unused_result));
+int epg_broadcast_set_age_rating
+  ( epg_broadcast_t *b, uint8_t age, epg_changes_t *changed )
   __attribute__((warn_unused_result));
 
 /* Accessors */
@@ -473,6 +408,22 @@ const char *epg_broadcast_get_credits_cached
 const char *epg_broadcast_get_keyword_cached
   ( epg_broadcast_t *b, const char *lang );
 
+/* Episode number heplers */
+// Note: this does NOT strdup the text field
+void epg_broadcast_get_epnum
+  ( const epg_broadcast_t *b, epg_episode_num_t *epnum );
+size_t epg_broadcast_epnumber_format 
+  ( epg_broadcast_t *b, char *buf, size_t len,
+    const char *pre,  const char *sfmt,
+    const char *sep,  const char *efmt,
+    const char *cfmt );
+
+static inline int epg_episode_match(epg_broadcast_t *a, epg_broadcast_t *b)
+{
+  if (a == NULL || b == NULL) return 0;
+  return strcmp(a->episode_uri ?: "", b->episode_uri ?: "") == 0;
+}
+
 /* Serialization */
 htsmsg_t        *epg_broadcast_serialize   ( epg_broadcast_t *b );
 epg_broadcast_t *epg_broadcast_deserialize 
index 95fcd35b4b3c1ca88ba16b8980a345d35b3e62a7..9f91d140b1653f38dc96264bc9a914348764b4f7 100644 (file)
@@ -36,7 +36,7 @@
 #include "config.h"
 #include "memoryinfo.h"
 
-#define EPG_DB_VERSION 2
+#define EPG_DB_VERSION 3
 #define EPG_DB_ALLOC_STEP (1024*1024)
 
 extern epg_object_tree_t epg_episodes;
@@ -46,66 +46,10 @@ extern epg_object_tree_t epg_episodes;
  * *************************************************************************/
 
 /*
- * Use for v1 databases
- */
-#if DEPRECATED
-static void _epgdb_v1_process ( htsmsg_t *c, epggrab_stats_t *stats )
-{
-  channel_t *ch;
-  epg_episode_t *ee;
-  epg_broadcast_t *ebc;
-  uint32_t ch_id = 0;
-  uint32_t e_start = 0;
-  uint32_t e_stop = 0;
-  uint32_t u32;
-  const char *title, *desc, *str;
-  char *uri;
-  int save = 0;
-
-  /* Check key info */
-  if(htsmsg_get_u32(c, "ch_id", &ch_id)) return;
-  if((ch = channel_find_by_id(ch_id)) == NULL) return;
-  if(htsmsg_get_u32(c, "start", &e_start)) return;
-  if(htsmsg_get_u32(c, "stop", &e_stop)) return;
-  if(!(title = htsmsg_get_str(c, "title"))) return;
-  
-  /* Create broadcast */
-  save = 0;
-  ebc  = epg_broadcast_find_by_time(ch, e_start, e_stop, 0, 1, &save);
-  if (!ebc) return;
-  if (save) stats->broadcasts.total++;
-
-  /* Create episode */
-  save = 0;
-  desc = htsmsg_get_str(c, "desc");
-  uri  = md5sum(desc ?: title);
-  ee   = epg_episode_find_by_uri(uri, 1, &save);
-  free(uri);
-  if (!ee) return;
-  if (save) stats->episodes.total++;
-  if (title)
-    save |= epg_episode_set_title(ee, title, NULL, NULL);
-  if (desc)
-    save |= epg_episode_set_summary(ee, desc, NULL, NULL);
-  if (!htsmsg_get_u32(c, "episode", &u32))
-    save |= epg_episode_set_number(ee, u32, NULL);
-  if (!htsmsg_get_u32(c, "part", &u32))
-    save |= epg_episode_set_part(ee, u32, 0, NULL);
-  if (!htsmsg_get_u32(c, "season", &u32))
-    ee->epnum.s_num = u32;
-  if ((str = htsmsg_get_str(c, "epname")))
-    ee->epnum.text  = strdup(str);
-
-  /* Set episode */
-  save |= epg_broadcast_set_episode(ebc, ee, NULL);
-}
-#endif
-
-/*
- * Process v2 data
+ * Process v3 data
  */
 static void
-_epgdb_v2_process( char **sect, htsmsg_t *m, epggrab_stats_t *stats )
+_epgdb_v3_process( char **sect, htsmsg_t *m, epggrab_stats_t *stats )
 {
   int save = 0;
   const char *s;
@@ -115,22 +59,6 @@ _epgdb_v2_process( char **sect, htsmsg_t *m, epggrab_stats_t *stats )
     if (*sect) free(*sect);
     *sect = strdup(s);
   
-  /* Brand */
-  } else if ( !strcmp(*sect, "brands") ) {
-    /* skip */
-      
-  /* Season */
-  } else if ( !strcmp(*sect, "seasons") ) {
-    /* skip */
-
-  /* Episode */
-  } else if ( !strcmp(*sect, "episodes") ) {
-    if (epg_episode_deserialize(m, 1, &save)) stats->episodes.total++;
-  
-  /* Series link */
-  } else if ( !strcmp(*sect, "serieslinks") ) {
-    /* skip */
-  
   /* Broadcasts */
   } else if ( !strcmp(*sect, "broadcasts") ) {
     if (epg_broadcast_deserialize(m, 1, &save)) stats->broadcasts.total++;
@@ -150,32 +78,6 @@ _epgdb_v2_process( char **sect, htsmsg_t *m, epggrab_stats_t *stats )
  * Memoryinfo
  */
 
-static void epg_memoryinfo_episodes_update(memoryinfo_t *my)
-{
-  epg_object_t *eo;
-  epg_episode_t *ee;
-  int64_t size = 0, count = 0;
-
-  RB_FOREACH(eo, &epg_episodes, uri_link) {
-    ee = (epg_episode_t *)eo;
-    size += sizeof(*ee);
-    size += tvh_strlen(ee->uri);
-    size += lang_str_size(ee->title);
-    size += lang_str_size(ee->subtitle);
-    size += lang_str_size(ee->summary);
-    size += lang_str_size(ee->description);
-    size += tvh_strlen(ee->image);
-    size += tvh_strlen(ee->epnum.text);
-    count++;
-  }
-  memoryinfo_update(my, size, count);
-}
-
-static memoryinfo_t epg_memoryinfo_episodes = {
-  .my_name = "EPG Episodes",
-  .my_update = epg_memoryinfo_episodes_update
-};
-
 static void epg_memoryinfo_broadcasts_update(memoryinfo_t *my)
 {
   channel_t *ch;
@@ -186,7 +88,12 @@ static void epg_memoryinfo_broadcasts_update(memoryinfo_t *my)
     if (ch->ch_epg_parent) continue;
     RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
       size += sizeof(*ebc);
-      size += tvh_strlen(ebc->uri);
+      size += tvh_strlen(ebc->image);
+      size += tvh_strlen(ebc->epnum.text);
+      size += tvh_strlen(ebc->episode_uri);
+      size += tvh_strlen(ebc->serieslink_uri);
+      size += lang_str_size(ebc->title);
+      size += lang_str_size(ebc->subtitle);
       size += lang_str_size(ebc->summary);
       size += lang_str_size(ebc->description);
       count++;
@@ -215,7 +122,7 @@ static void epg_mmap_sigbus (int sig, siginfo_t *siginfo, void *ptr)
  */
 void epg_init ( void )
 {
-  int fd = -1, binary2 = 0, r;
+  int fd = -1, r;
   struct stat st;
   size_t remain;
   uint8_t *mem, *rp, *zlib_mem = NULL;
@@ -224,7 +131,6 @@ void epg_init ( void )
   struct sigaction act, oldact;
   char *sect = NULL;
 
-  memoryinfo_register(&epg_memoryinfo_episodes);
   memoryinfo_register(&epg_memoryinfo_broadcasts);
 
   /* Find the right file (and version) */
@@ -273,10 +179,9 @@ void epg_init ( void )
   }
 
 #if ENABLE_ZLIB
-  if (remain > 12 && memcmp(rp, "\xff\xffGZIP0", 7) == 0 &&
+  if (remain > 12 && memcmp(rp, "\xff\xffGZIP01", 8) == 0 &&
       (rp[7] == '0' || rp[7] == '1')) {
     uint32_t orig = (rp[8] << 24) | (rp[9] << 16) | (rp[10] << 8) | rp[11];
-    binary2 = rp[7] == '1';
     tvhinfo(LS_EPGDB, "gzip format detected, inflating (ratio %.1f%% deflated size %zd)",
            (float)((remain * 100.0) / orig), remain);
     rp = zlib_mem = tvh_gzip_inflate(rp + 12, remain - 12, orig);
@@ -293,11 +198,7 @@ void epg_init ( void )
     /* Get message length */
     size_t msglen = remain;
     htsmsg_t *m;
-    if (binary2) {
-      r = htsmsg_binary2_deserialize(&m, rp, &msglen, NULL);
-    } else {
-      r = htsmsg_binary_deserialize(&m, rp, &msglen, NULL);
-    }
+    r = htsmsg_binary2_deserialize(&m, rp, &msglen, NULL);
 
     /* Safety check */
     if (r) {
@@ -314,8 +215,8 @@ void epg_init ( void )
 
     /* Process */
     switch (ver) {
-      case 2:
-        _epgdb_v2_process(&sect, m, &stats);
+      case 3:
+        _epgdb_v3_process(&sect, m, &stats);
         break;
       default:
         break;
@@ -339,7 +240,6 @@ void epg_init ( void )
   /* Stats */
   tvhinfo(LS_EPGDB, "loaded v%d", ver);
   tvhinfo(LS_EPGDB, "  config     %d", stats.config.total);
-  tvhinfo(LS_EPGDB, "  episodes   %d", stats.episodes.total);
   tvhinfo(LS_EPGDB, "  broadcasts %d", stats.broadcasts.total);
 
   /* Close file */
@@ -358,7 +258,6 @@ void epg_done ( void )
   CHANNEL_FOREACH(ch)
     epg_channel_unlink(ch);
   epg_skel_done();
-  memoryinfo_unregister(&epg_memoryinfo_episodes);
   memoryinfo_unregister(&epg_memoryinfo_broadcasts);
   pthread_mutex_unlock(&global_lock);
 }
@@ -374,12 +273,7 @@ static int _epg_write ( sbuf_t *sb, htsmsg_t *m )
   void *msgdata;
   if (m) {
     int r;
-#if ENABLE_ZLIB
-    if (config.epg_compress)
-      r = htsmsg_binary2_serialize(m, &msgdata, &msglen, 0x10000);
-    else
-#endif
-      r = htsmsg_binary_serialize(m, &msgdata, &msglen, 0x10000);
+    r = htsmsg_binary2_serialize(m, &msgdata, &msglen, 0x10000);
     htsmsg_destroy(m);
     if (!r) {
       ret = 0;
@@ -448,7 +342,6 @@ void epg_save_callback ( void *p )
 void epg_save ( void )
 {
   sbuf_t *sb = malloc(sizeof(*sb));
-  epg_object_t *eo;
   epg_broadcast_t *ebc;
   channel_t *ch;
   epggrab_stats_t stats;
@@ -468,11 +361,6 @@ void epg_save ( void )
   memset(&stats, 0, sizeof(stats));
   if ( _epg_write_sect(sb, "config") ) goto error;
   if (_epg_write(sb, epg_config_serialize())) goto error;
-  if ( _epg_write_sect(sb, "episodes") ) goto error;
-  RB_FOREACH(eo,  &epg_episodes, uri_link) {
-    if (_epg_write(sb, epg_episode_serialize((epg_episode_t*)eo))) goto error;
-    stats.episodes.total++;
-  }
   if ( _epg_write_sect(sb, "broadcasts") ) goto error;
   CHANNEL_FOREACH(ch) {
     if (ch->ch_epg_parent) continue;
@@ -486,7 +374,6 @@ void epg_save ( void )
 
   /* Stats */
   tvhinfo(LS_EPGDB, "queued to save (size %d)", sb->sb_ptr);
-  tvhinfo(LS_EPGDB, "  episodes   %d", stats.episodes.total);
   tvhinfo(LS_EPGDB, "  broadcasts %d", stats.broadcasts.total);
 
   return;
index af589edaf1b6f18b835327083d6bcb6facdea605..a1dc23c269581c5016bfd506dd09b0381e47f888 100644 (file)
@@ -609,10 +609,9 @@ static int _eit_process_event_one
   uint16_t eid;
   uint8_t running;
   epg_broadcast_t *ebc, _ebc;
-  epg_episode_t *ee = NULL, _ee;
   epg_running_t run;
   lang_str_t *title_copy = NULL;
-  uint32_t changes2 = 0, changes3 = 0;
+  epg_changes_t changes = 0;
   char tm1[32], tm2[32];
   int short_target = ((eit_module_t *)mod)->short_target;
 
@@ -625,7 +624,7 @@ static int _eit_process_event_one
   running = (ptr[10] >> 5) & 0x07;
 
   /* Find broadcast */
-  ebc  = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save2, &changes2);
+  ebc  = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save2, &changes);
   tvhtrace(LS_TBL_EIT, "svc='%s', ch='%s', eid=%5d, tbl=%02x, running=%d, start=%s,"
                        " stop=%s, ebc=%p",
            svc->s_dvb_svcname ?: "(null)",
@@ -644,17 +643,12 @@ static int _eit_process_event_one
     if (!ev->title)
       goto running;
     memset(&_ebc, 0, sizeof(_ebc));
-    if (*ev->uri && (ee = epg_episode_find_by_uri(ev->uri, mod, 0, 0, NULL))) {
-      _ee = *ee;
-    } else {
-      memset(&_ee, 0, sizeof(_ee));
-    }
-    _ebc.episode = &_ee;
     _ebc.dvb_eid = eid;
     _ebc.start = start;
     _ebc.stop = stop;
+    _ebc.episode_uri = ev->uri;
     _ebc.serieslink_uri = ev->suri;
-    _ee.title = title_copy = lang_str_copy(ev->title);
+    _ebc.title = title_copy = lang_str_copy(ev->title);
 
     ebc = epg_match_now_next(ch, &_ebc);
     tvhtrace(mod->subsys, "%s:  running state only ebc=%p", svc->s_dvb_svcname ?: "(null)", ebc);
@@ -667,72 +661,65 @@ static int _eit_process_event_one
    * Broadcast
    */
 
-  *save |= epg_broadcast_set_dvb_eid(ebc, eid, &changes2);
+  *save |= epg_broadcast_set_dvb_eid(ebc, eid, &changes);
 
   /* Summary/Description */
   if (ev->summary)
     if (short_target != 0 ||
         (ev->subtitle && lang_str_compare(ev->summary, ev->subtitle)))
-      *save |= epg_broadcast_set_summary(ebc, ev->summary, &changes2);
+      *save |= epg_broadcast_set_summary(ebc, ev->summary, &changes);
   if (ev->desc)
-    *save |= epg_broadcast_set_description(ebc, ev->desc, &changes2);
+    *save |= epg_broadcast_set_description(ebc, ev->desc, &changes);
 
   /* Broadcast Metadata */
-  *save |= epg_broadcast_set_is_hd(ebc, ev->hd, &changes2);
-  *save |= epg_broadcast_set_is_widescreen(ebc, ev->ws, &changes2);
-  *save |= epg_broadcast_set_is_audio_desc(ebc, ev->ad, &changes2);
-  *save |= epg_broadcast_set_is_subtitled(ebc, ev->st, &changes2);
-  *save |= epg_broadcast_set_is_deafsigned(ebc, ev->ds, &changes2);
+  *save |= epg_broadcast_set_is_hd(ebc, ev->hd, &changes);
+  *save |= epg_broadcast_set_is_widescreen(ebc, ev->ws, &changes);
+  *save |= epg_broadcast_set_is_audio_desc(ebc, ev->ad, &changes);
+  *save |= epg_broadcast_set_is_subtitled(ebc, ev->st, &changes);
+  *save |= epg_broadcast_set_is_deafsigned(ebc, ev->ds, &changes);
 
   /*
    * Series link
    */
 
   if (*ev->suri)
-    *save |= epg_broadcast_set_serieslink_uri(ebc, ev->suri, &changes2);
+    *save |= epg_broadcast_set_serieslink_uri(ebc, ev->suri, &changes);
 
   /*
    * Episode
    */
 
   /* Find episode */
-  if (*ev->uri) {
-    ee = epg_episode_find_by_uri(ev->uri, mod, 1, save, &changes3);
-  } else {
-    ee = epg_episode_find_by_broadcast(ebc, mod, 1, save, &changes3);
-  }
+  if (*ev->uri)
+    *save |= epg_broadcast_set_episode_uri(ebc, ev->suri, &changes);
 
   /* Update Episode */
-  if (ee) {
-    *save |= epg_broadcast_set_episode(ebc, ee, &changes2);
-    if (ev->is_new > 0)
-      *save |= epg_broadcast_set_is_new(ebc, ev->is_new - 1, &changes2);
-    *save |= epg_episode_set_is_bw(ee, ev->bw, &changes3);
-    if (ev->title)
-      *save |= epg_episode_set_title(ee, ev->title, &changes3);
-    if (ev->genre)
-      *save |= epg_episode_set_genre(ee, ev->genre, &changes3);
-    if (ev->parental)
-      *save |= epg_episode_set_age_rating(ee, ev->parental, &changes3);
-    if (ev->subtitle)
-      *save |= epg_episode_set_subtitle(ee, ev->subtitle, &changes3);
-    else if ((short_target == 0 || short_target == 2) && ev->summary)
-      *save |= epg_episode_set_subtitle(ee, ev->summary, &changes3);
+  if (ev->is_new > 0)
+    *save |= epg_broadcast_set_is_new(ebc, ev->is_new - 1, &changes);
+  *save |= epg_broadcast_set_is_bw(ebc, ev->bw, &changes);
+  if (ev->title)
+    *save |= epg_broadcast_set_title(ebc, ev->title, &changes);
+  if (ev->genre)
+    *save |= epg_broadcast_set_genre(ebc, ev->genre, &changes);
+  if (ev->parental)
+    *save |= epg_broadcast_set_age_rating(ebc, ev->parental, &changes);
+  if (ev->subtitle)
+    *save |= epg_broadcast_set_subtitle(ebc, ev->subtitle, &changes);
+  else if ((short_target == 0 || short_target == 2) && ev->summary)
+    *save |= epg_broadcast_set_subtitle(ebc, ev->summary, &changes);
 #if TODO_ADD_EXTRA
-    if (ev->extra)
-      *save |= epg_episode_set_extra(ee, extra, &changes3);
+  if (ev->extra)
+    *save |= epg_broadcast_set_extra(ebc, extra, &changes);
 #endif
-    /* save any found episode number */
-    if (ev->en.s_num || ev->en.e_num || ev->en.p_num)
-      *save |= epg_episode_set_epnum(ee, &ev->en, &changes3);
-    if (ev->first_aired > 0)
-      *save |= epg_episode_set_first_aired(ee, ev->first_aired, &changes3);
-    if (ev->copyright_year > 0)
-      *save |= epg_episode_set_copyright_year(ee, ev->copyright_year, &changes3);
-    *save |= epg_episode_change_finish(ee, changes3, 0);
-  }
-
-  *save |= epg_broadcast_change_finish(ebc, changes2, 0);
+  /* save any found episode number */
+  if (ev->en.s_num || ev->en.e_num || ev->en.p_num)
+    *save |= epg_broadcast_set_epnum(ebc, &ev->en, &changes);
+  if (ev->first_aired > 0)
+    *save |= epg_broadcast_set_first_aired(ebc, ev->first_aired, &changes);
+  if (ev->copyright_year > 0)
+    *save |= epg_broadcast_set_copyright_year(ebc, ev->copyright_year, &changes);
+
+  *save |= epg_broadcast_change_finish(ebc, changes, 0);
 
 
 running:
index 8159c44dda60dca294db16821fd8cad257a3ff92..42cd25be4110f59e7a69975ddd0eab5ae2699081 100644 (file)
@@ -293,11 +293,10 @@ opentv_parse_event_section_one
   int i, r, save = 0, merge;
   epggrab_module_t *src = (epggrab_module_t*)mod;
   epg_broadcast_t *ebc;
-  epg_episode_t *ee;
   opentv_event_t ev;
   char buffer[2048], *s;
   lang_str_t *ls;
-  uint32_t changes, changes2, changes3;
+  epg_changes_t changes;
 
   /* Loop around event entries */
   i = 7;
@@ -311,7 +310,7 @@ opentv_parse_event_section_one
      * Broadcast
      */
 
-    merge = changes = changes2 = changes3 = 0;
+    merge = changes = 0;
 
     /* Find broadcast */
     if (ev.start && ev.stop) {
@@ -360,64 +359,59 @@ opentv_parse_event_section_one
      * Episode
      */
 
-    if ((ee = epg_episode_find_by_broadcast(ebc, src, 1, &save, &changes3))) {
-      save |= epg_broadcast_set_episode(ebc, ee, &changes);
-      tvhdebug(LS_OPENTV, "  find episode %p", ee);
-      if (ev.title) {
-        tvhdebug(LS_OPENTV, "    title '%s'", ev.title);
-
-        /* try to cleanup the title */
-        if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.title, lang, &mod->p_cleanup_title)) {
-          tvhtrace(LS_OPENTV, "  clean title '%s'", buffer);
-          s = buffer;
-        } else {
-          s = ev.title;
-        }
-        ls = lang_str_create2(s, lang);
-        save |= epg_episode_set_title(ee, ls, &changes3);
-        lang_str_destroy(ls);
+    if (ev.title) {
+      tvhdebug(LS_OPENTV, "    title '%s'", ev.title);
+
+      /* try to cleanup the title */
+      if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.title, lang, &mod->p_cleanup_title)) {
+        tvhtrace(LS_OPENTV, "  clean title '%s'", buffer);
+        s = buffer;
+      } else {
+        s = ev.title;
       }
-      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, &changes3);
-        epg_genre_list_destroy(egl);
+      ls = lang_str_create2(s, lang);
+      save |= epg_broadcast_set_title(ebc, ls, &changes);
+      lang_str_destroy(ls);
+    }
+    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_broadcast_set_genre(ebc, egl, &changes);
+      epg_genre_list_destroy(egl);
+    }
+    if (ev.summary) {
+      epg_episode_num_t en;
+
+      memset(&en, 0, sizeof(en));
+      /* search for season number */
+      if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_snum))
+        if ((en.s_num = atoi(buffer)))
+          tvhtrace(LS_OPENTV,"  extract season number %d", en.s_num);
+      /* ...for episode number */
+      if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_enum))
+        if ((en.e_num = atoi(buffer)))
+          tvhtrace(LS_OPENTV,"  extract episode number %d", en.e_num);
+      /* ...for part number */
+      if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_pnum)) {
+        if (buffer[0] >= 'a' && buffer[0] <= 'z')
+          en.p_num = buffer[0] - 'a' + 1;
+        else
+          if (buffer[0] >= 'A' && buffer[0] <= 'Z')
+            en.p_num = buffer[0] - 'A' + 1;
+        if (en.p_num)
+          tvhtrace(LS_OPENTV,"  extract part number %d", en.p_num);
       }
-      if (ev.summary) {
-        epg_episode_num_t en;
-
-        memset(&en, 0, sizeof(en));
-        /* search for season number */
-        if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_snum))
-          if ((en.s_num = atoi(buffer)))
-            tvhtrace(LS_OPENTV,"  extract season number %d", en.s_num);
-        /* ...for episode number */
-        if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_enum))
-          if ((en.e_num = atoi(buffer)))
-            tvhtrace(LS_OPENTV,"  extract episode number %d", en.e_num);
-        /* ...for part number */
-        if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_pnum)) {
-          if (buffer[0] >= 'a' && buffer[0] <= 'z')
-            en.p_num = buffer[0] - 'a' + 1;
-          else
-            if (buffer[0] >= 'A' && buffer[0] <= 'Z')
-              en.p_num = buffer[0] - 'A' + 1;
-          if (en.p_num)
-            tvhtrace(LS_OPENTV,"  extract part number %d", en.p_num);
-        }
-        /* save any found number */
-        if (en.s_num || en.e_num || en.p_num)
-          save |= epg_episode_set_epnum(ee, &en, &changes3);
-
-        /* ...for subtitle */
-        if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_subt)) {
-          tvhtrace(LS_OPENTV, "  extract subtitle '%s'", buffer);
-          ls = lang_str_create2(buffer, lang);
-          save |= epg_episode_set_subtitle(ee, ls, &changes3);
-          lang_str_destroy(ls);
-        }
+      /* save any found number */
+      if (en.s_num || en.e_num || en.p_num)
+        save |= epg_broadcast_set_epnum(ebc, &en, &changes);
+
+      /* ...for subtitle */
+      if (eit_pattern_apply_list(buffer, sizeof(buffer), ev.summary, lang, &mod->p_subt)) {
+        tvhtrace(LS_OPENTV, "  extract subtitle '%s'", buffer);
+        ls = lang_str_create2(buffer, lang);
+        save |= epg_broadcast_set_subtitle(ebc, ls, &changes);
+        lang_str_destroy(ls);
       }
-      save |= epg_episode_change_finish(ee, changes3, merge);
     }
 
     save |= epg_broadcast_change_finish(ebc, changes, merge);
index d07ccb22e518e88096dbc23f6914f97fcaf7064f..759b7f4cff72e82bc4ca281f3f54dc6b3118d44a 100644 (file)
@@ -340,14 +340,13 @@ _psip_eit_callback_channel
   uint16_t eventid;
   uint32_t starttime, length;
   time_t start, stop;
-  int save = 0, save2, save3, i, size;
+  int save = 0, save2, i, size;
   uint8_t titlelen;
   unsigned int dlen;
   epg_broadcast_t *ebc;
-  epg_episode_t *ee;
   lang_str_t *title, *description;
   psip_desc_t *pd;
-  uint32_t changes2, changes3;
+  epg_changes_t changes2;
   epggrab_module_t *mod = (epggrab_module_t *)ps->ps_mod;
 
   for (i = 0; len >= 12 && i < count; len -= size, ptr += size, i++) {
@@ -385,7 +384,7 @@ _psip_eit_callback_channel
              eventid, start, length,
              lang_str_get(title, NULL), titlelen);
 
-    save2 = save3 = changes2 = changes3 = 0;
+    save2 = changes2 = 0;
 
     ebc = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save2, &changes2);
     tvhtrace(LS_PSIP, "  eid=%5d, start=%"PRItime_t", stop=%"PRItime_t", ebc=%p",
@@ -403,16 +402,11 @@ _psip_eit_callback_channel
       }
     }
 
-    ee = epg_episode_find_by_broadcast(ebc, mod, 1, &save3, &changes3);
-    if (ee) {
-      save2 |= epg_broadcast_set_episode(ebc, ee, &changes2);
-      save3 |= epg_episode_set_title(ee, title, &changes3);
-      save3 |= epg_episode_change_finish(ee, changes3, 0);
-    }
+    save |= epg_broadcast_set_title(ebc, title, &changes2);
 
     save |= epg_broadcast_change_finish(ebc, changes2, 0);
 
-    save |= save2 | save3;
+    save |= save2;
 
 next:
     lang_str_destroy(title);
@@ -548,7 +542,7 @@ _psip_ett_callback
   lang_str_t *description;
   idnode_list_mapping_t *ilm;
   channel_t            *ch;
-  uint32_t              changes;
+  epg_changes_t         changes;
 
   /* Validate */
   if (tableid != 0xcc) return -1;
@@ -603,7 +597,7 @@ _psip_ett_callback
         save |= epg_broadcast_change_finish(ebc, changes, 1);
         tvhtrace(LS_PSIP, "0x%04x: ETT tableid 0x%04X [%s], eventid 0x%04X (%d) ['%s'], ver %d",
                  mt->mt_pid, tsid, svc->s_dvb_svcname, eventid, eventid,
-                 lang_str_get(ebc->episode->title, "eng"), ver);
+                 lang_str_get(ebc->title, "eng"), ver);
       } else {
         found = 0;
       }
index 9a3f90db81a2c90ed533dc43f98c3301367c50ea..accd4e474ea6824488deca03e4837ec73ac1f8dc 100644 (file)
@@ -260,7 +260,7 @@ static void get_episode_info
  */
 static int
 xmltv_parse_vid_quality
-  ( epg_broadcast_t *ebc, htsmsg_t *m, int8_t *bw, uint32_t *changes )
+  ( epg_broadcast_t *ebc, htsmsg_t *m, int8_t *bw, epg_changes_t *changes )
 {
   int save = 0;
   int hd = 0, lines = 0, aspect = 0;
@@ -320,7 +320,7 @@ xmltv_parse_vid_quality
  */
 int
 xmltv_parse_accessibility 
-  ( epg_broadcast_t *ebc, htsmsg_t *m, uint32_t *changes )
+  ( epg_broadcast_t *ebc, htsmsg_t *m, epg_changes_t *changes )
 {
   int save = 0;
   htsmsg_t *tag;
@@ -348,7 +348,7 @@ xmltv_parse_accessibility
  */
 static int _xmltv_parse_previously_shown
   ( epg_broadcast_t *ebc, time_t *first_aired,
-    htsmsg_t *tag, uint32_t *changes )
+    htsmsg_t *tag, epg_changes_t *changes )
 {
   int ret;
   const char *start;
@@ -363,10 +363,10 @@ static int _xmltv_parse_previously_shown
  * Date finished, typically copyright date.
  */
 static int _xmltv_parse_date_finished
-  ( epg_episode_t *ee,
-    htsmsg_t *tag, uint32_t *changes )
+  ( epg_broadcast_t *ebc,
+    htsmsg_t *tag, epg_changes_t *changes )
 {
-  if (!ee || !tag) return 0;
+  if (!ebc || !tag) return 0;
   const char *str = htsmsg_xml_get_cdata_str(tag, "date");
   if (str) {
       /* Technically the date could contain information about month
@@ -381,7 +381,7 @@ static int _xmltv_parse_date_finished
           const uint16_t year = atoi(year_buf);
           /* Sanity check the year before copying it over. */
           if (year > 1800 && year < 2500) {
-              return epg_episode_set_copyright_year(ee, year, changes);
+              return epg_broadcast_set_copyright_year(ebc, year, changes);
           }
       }
   }
@@ -396,14 +396,14 @@ static int _xmltv_parse_date_finished
  *   </star-rating>
  */
 static int _xmltv_parse_star_rating
-  ( epg_episode_t *ee, htsmsg_t *body, uint32_t *changes )
+  ( epg_broadcast_t *ebc, htsmsg_t *body, epg_changes_t *changes )
 {
   double a, b;
   htsmsg_t *stars, *tags;
   const char *s1, *s2;
   char *s1end, *s2end;
 
-  if (!ee || !body) return 0;
+  if (!ebc || !body) return 0;
   if (!(stars = htsmsg_get_map(body, "star-rating"))) return 0;
   if (!(tags  = htsmsg_get_map(stars, "tags"))) return 0;
   if (!(s1 = htsmsg_xml_get_cdata_str(tags, "value"))) return 0;
@@ -413,7 +413,7 @@ static int _xmltv_parse_star_rating
   b = strtod(s2 + 1, &s2end);
   if ( a == 0.0f || b == 0.0f) return 0;
 
-  return epg_episode_set_star_rating(ee, (100 * a) / b, changes);
+  return epg_broadcast_set_star_rating(ebc, (100 * a) / b, changes);
 }
 
 /*
@@ -445,13 +445,13 @@ static int _xmltv_parse_star_rating
  * [rating system=advisory] values "strong sexual content","Language", etc
  */
 static int _xmltv_parse_age_rating
-  ( epg_episode_t *ee, htsmsg_t *body, uint32_t *changes )
+  ( epg_broadcast_t *ebc, htsmsg_t *body, epg_changes_t *changes )
 {
   uint8_t age;
   htsmsg_t *rating, *tags;
   const char *s1;
 
-  if (!ee || !body) return 0;
+  if (!ebc || !body) return 0;
 
   htsmsg_field_t *f;
   HTSMSG_FOREACH(f, body) {
@@ -476,7 +476,7 @@ static int _xmltv_parse_age_rating
            * rating of -10.
            */
           if (age > 0 && age < 22)
-            return epg_episode_set_age_rating(ee, age, changes);
+            return epg_broadcast_set_age_rating(ebc, age, changes);
         }
       }
     }
@@ -608,28 +608,30 @@ static int _xmltv_parse_programme_tags
   const int scrape_extra = ((epggrab_module_int_t *)mod)->xmltv_scrape_extra;
   const int scrape_onto_desc = ((epggrab_module_int_t *)mod)->xmltv_scrape_onto_desc;
   const int use_category_not_genre = ((epggrab_module_int_t *)mod)->xmltv_use_category_not_genre;
-  int save = 0, save2 = 0, save3 = 0;
-  epg_episode_t *ee = NULL;
+  int save = 0;
+  epg_changes_t changes = 0;
   epg_broadcast_t *ebc;
   epg_genre_list_t *egl;
   epg_episode_num_t epnum;
   memset(&epnum, 0, sizeof(epnum));
   char *suri = NULL, *uri = NULL;
+  const char *s;
   lang_str_t *title = NULL;
   lang_str_t *desc = NULL;
   lang_str_t *summary = NULL;
   lang_str_t *subtitle = NULL;
   time_t first_aired = 0;
   int8_t bw = -1;
-  uint32_t changes = 0, changes2 = 0, changes3 = 0;
 
   /*
    * Broadcast
    */
-  if (!(ebc = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save, &changes)))
+  ebc = epg_broadcast_find_by_time(ch, mod, start, stop, 1, &save, &changes);
+  if (!ebc)
     return 0;
   stats->broadcasts.total++;
-  if (save && (changes & EPG_CHANGED_CREATE)) stats->broadcasts.created++;
+  if (save && (changes & EPG_CHANGED_CREATE))
+    stats->broadcasts.created++;
 
   /* Description (wait for episode first) */
   _xmltv_parse_lang_str(&desc, tags, "desc");
@@ -643,17 +645,12 @@ static int _xmltv_parse_programme_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);
-    }
+    if (scrape_extra && credits)
+      save |= epg_broadcast_set_credits(ebc, credits, &changes);
+    if (scrape_extra && category)
+      save |= epg_broadcast_set_category(ebc, category, &changes);
+    if (scrape_extra && keyword)
+      save |= 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.
@@ -688,14 +685,13 @@ static int _xmltv_parse_programme_tags
 #undef APPENDIT
   } /* desc */
 
-  if (desc) {
-    save3 |= epg_broadcast_set_description(ebc, desc, &changes);
-  } /* desc */
+  if (desc)
+    save |= epg_broadcast_set_description(ebc, desc, &changes);
 
   /* summary */
   _xmltv_parse_lang_str(&summary, tags, "summary");
   if (summary)
-    save3 |= epg_broadcast_set_summary(ebc, summary, &changes);
+    save |= epg_broadcast_set_summary(ebc, summary, &changes);
 
   /* Quality metadata */
   save |= xmltv_parse_vid_quality(ebc, htsmsg_get_map(tags, "video"), &bw, &changes);
@@ -720,62 +716,61 @@ static int _xmltv_parse_programme_tags
    * Series Link
    */
   if (suri) {
+    s = ebc->serieslink_uri;
     save |= epg_broadcast_set_serieslink_uri(ebc, suri, &changes);
     free(suri);
     stats->seasons.total++;
-    if (save2 && (changes2 & EPG_CHANGED_CREATE)) stats->seasons.created++;
+    if (changes & EPG_CHANGED_SERIESLINK) {
+      if (s == NULL)
+        stats->seasons.created++;
+      else
+        stats->seasons.modified++;
+    }
   }
 
   /*
    * Episode
    */
   if (uri) {
-    ee = epg_episode_find_by_uri(uri, mod, 1, &save3, &changes3);
-    free(uri);
-    uri = NULL;
-  } else {
-    ee = epg_episode_find_by_broadcast(ebc, mod, 1, &save3, &changes3);
+    s = ebc->episode_uri;
+    save |= epg_broadcast_set_episode_uri(ebc, uri, &changes);
+    stats->episodes.total++;
+    if (changes & EPG_CHANGED_EPISODE) {
+      if (s == NULL)
+        stats->episodes.created++;
+      else
+        stats->episodes.modified++;
+    }
   }
-  save |= epg_broadcast_set_episode(ebc, ee, &changes);
-  if (ee)    stats->episodes.total++;
-  /* save3 is always set by epg_episode_find_by_uri call to
-   * _epg_object_set_grabber so need to also check for
-   * EPG_CHANGED_CREATE.
-   */
-  if (save3 && (changes3 & EPG_CHANGED_CREATE)) stats->episodes.created++;
-
-  if (ee) {
-    _xmltv_parse_lang_str(&title, tags, "title");
-    _xmltv_parse_lang_str(&subtitle, tags, "sub-title");
 
-    if (title) 
-      save3 |= epg_episode_set_title(ee, title, &changes3);
-    if (subtitle)
-      save3 |= epg_episode_set_subtitle(ee, subtitle, &changes3);
+  _xmltv_parse_lang_str(&title, tags, "title");
+  _xmltv_parse_lang_str(&subtitle, tags, "sub-title");
 
-    if (!use_category_not_genre && (egl = _xmltv_parse_categories(tags))) {
-      save3 |= epg_episode_set_genre(ee, egl, &changes3);
-      epg_genre_list_destroy(egl);
-    }
+  if (title)
+    save |= epg_broadcast_set_title(ebc, title, &changes);
+  if (subtitle)
+    save |= epg_broadcast_set_subtitle(ebc, subtitle, &changes);
 
-    if (bw != -1)
-      save3 |= epg_episode_set_is_bw(ee, (uint8_t)bw, &changes3);
+  if (!use_category_not_genre && (egl = _xmltv_parse_categories(tags))) {
+    save |= epg_broadcast_set_genre(ebc, egl, &changes);
+    epg_genre_list_destroy(egl);
+  }
 
-    save3 |= epg_episode_set_epnum(ee, &epnum, &changes3);
+  if (bw != -1)
+    save |= epg_broadcast_set_is_bw(ebc, (uint8_t)bw, &changes);
 
-    save3 |= _xmltv_parse_star_rating(ee, tags, &changes3);
+  save |= epg_broadcast_set_epnum(ebc, &epnum, &changes);
 
-    save3 |= _xmltv_parse_date_finished(ee, tags, &changes3);
+  save |= _xmltv_parse_star_rating(ebc, tags, &changes);
 
-    save3 |= _xmltv_parse_age_rating(ee, tags, &changes3);
+  save |= _xmltv_parse_date_finished(ebc, tags, &changes);
 
-    if (icon)
-      save3 |= epg_episode_set_image(ee, icon, &changes3);
+  save |= _xmltv_parse_age_rating(ebc, tags, &changes);
 
-    save3 |= epg_episode_set_first_aired(ee, first_aired, &changes3);
+  if (icon)
+    save |= epg_broadcast_set_image(ebc, icon, &changes);
 
-    save3 |= epg_episode_change_finish(ee, changes3, 0);
-  }
+  save |= epg_broadcast_set_first_aired(ebc, first_aired, &changes);
 
   save |= epg_broadcast_change_finish(ebc, changes, 0);
 
@@ -787,16 +782,15 @@ static int _xmltv_parse_programme_tags
    * the field exists in the message. This then means that the
    * "save" variable then indicate the record was modified.
    */
-  if (save &&  !(changes  & EPG_CHANGED_CREATE))  stats->broadcasts.modified++;
-  if (save2 && !(changes2 & EPG_CHANGED_CREATE))  stats->seasons.modified++;
-  if (save3 && !(changes3 & EPG_CHANGED_CREATE))  stats->episodes.modified++;
+  if (save && !(changes & EPG_CHANGED_CREATE))
+    stats->broadcasts.modified++;
 
   /* Cleanup */
   if (title)    lang_str_destroy(title);
   if (subtitle) lang_str_destroy(subtitle);
   if (desc)     lang_str_destroy(desc);
   if (summary)  lang_str_destroy(summary);
-  return save | save2 | save3;
+  return save;
 }
 
 /**
index 39e7e45c60a4c2618170aec8f838dca632815b0d..8b32c5b0f1769bbae3441deec0b192e49762a3a5 100644 (file)
@@ -178,7 +178,7 @@ epggrab_ota_service_del
  * *************************************************************************/
 
 int  xmltv_parse_accessibility
-  ( epg_broadcast_t *ebc, htsmsg_t *m, uint32_t *changes );
+  ( epg_broadcast_t *ebc, htsmsg_t *m, epg_changes_t *changes );
 
 /* Freesat huffman decoder */
 size_t freesat_huffman_decode
index 1120c4dfdd50b9cbcfd00b72b473c57ee25be691..291f2e16c90f922999431330886490c637f226ef 100644 (file)
@@ -1238,15 +1238,9 @@ htsp_build_event
   epg_genre_t *g;
   epg_episode_num_t epnum;
   const char *str;
-  epg_episode_t *ee = e->episode;
 
   /* Ignore? */
-  if (update) {
-    int ignore = 1;
-    if (e->updated > update) ignore = 0;
-    else if (ee && ee->updated > update) ignore = 0;
-    if (ignore) return NULL;
-  }
+  if (update && e->updated <= update) return NULL;
 
   out = htsmsg_create_map();
 
@@ -1296,28 +1290,27 @@ htsp_build_event
   if (e->serieslink_uri)
     htsmsg_add_str(out, "serieslinkUri", e->serieslink_uri);
 
-  if (ee) {
-    htsmsg_add_u32(out, "episodeId", ee->id);
-    if (ee->uri && strncasecmp(ee->uri,"tvh://",6))  /* tvh:// uris are internal */
-      htsmsg_add_str(out, "episodeUri", ee->uri);
-    if((g = LIST_FIRST(&ee->genre))) {
-      uint32_t code = g->code;
-      if (htsp->htsp_version < 6) code = (code >> 4) & 0xF;
-      htsmsg_add_u32(out, "contentType", code);
-    }
-    if (ee->age_rating)
-      htsmsg_add_u32(out, "ageRating", ee->age_rating);
-    if (ee->star_rating)
-      htsmsg_add_u32(out, "starRating", ee->star_rating);
-    if (ee->copyright_year)
-      htsmsg_add_u32(out, "copyrightYear", ee->copyright_year);
-    if (ee->first_aired)
-      htsmsg_add_s64(out, "firstAired", ee->first_aired);
-    epg_episode_get_epnum(ee, &epnum);
-    htsp_serialize_epnum(out, &epnum, NULL);
-    if (ee->image)
-      htsmsg_add_str(out, "image", ee->image);
+  /* tvh:// uris are internal */
+  if (e->episode_uri && strncasecmp(e->episode_uri, "tvh://", 6))
+    htsmsg_add_str(out, "episodeUri", e->episode_uri);
+
+  if((g = LIST_FIRST(&e->genre))) {
+    uint32_t code = g->code;
+    if (htsp->htsp_version < 6) code = (code >> 4) & 0xF;
+    htsmsg_add_u32(out, "contentType", code);
   }
+  if (e->age_rating)
+    htsmsg_add_u32(out, "ageRating", e->age_rating);
+  if (e->star_rating)
+    htsmsg_add_u32(out, "starRating", e->star_rating);
+  if (e->copyright_year)
+    htsmsg_add_u32(out, "copyrightYear", e->copyright_year);
+  if (e->first_aired)
+    htsmsg_add_s64(out, "firstAired", e->first_aired);
+  epg_broadcast_get_epnum(e, &epnum);
+  htsp_serialize_epnum(out, &epnum, NULL);
+  if (e->image)
+    htsmsg_add_str(out, "image", e->image);
 
   if (e->channel) {
     LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
index 0f18692249a2ef8f1c4478f62805fc2defc252c9..10216ab60e61d8768d4206d07d93c1219bd0e52b 100644 (file)
@@ -738,17 +738,11 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc,
   epg_genre_t eg0;
   struct tm tm;
   time_t t;
-  epg_episode_t *ee = NULL;
-  channel_t *ch = NULL;
+  channel_t *ch = ebc ? ebc->channel : NULL;
   lang_str_t *ls = NULL, *ls2 = NULL;
   const char *lang;
   const lang_code_list_t *langs;
-
-  if (ebc)                     ee = ebc->episode;
-  else if (de && de->de_bcast) ee = de->de_bcast->episode;
-
-  if (de)       ch = de->de_channel;
-  else if (ebc) ch = ebc->channel;
+  epg_episode_num_t num;
 
   if (de || ebc) {
     localtime_r(de ? &de->de_start : &ebc->start, &tm);
@@ -773,8 +767,8 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc,
     memset(&eg0, 0, sizeof(eg0));
     eg0.code = de->de_content_type;
     eg = &eg0;
-  } else if (ee) {
-    eg = LIST_FIRST(&ee->genre);
+  } else if (ebc) {
+    eg = LIST_FIRST(&ebc->genre);
   }
   if(eg && epg_genre_get_str(eg, 1, 0, ctype, 100, NULL))
     addtag(q, build_tag_string("CONTENT_TYPE", ctype, NULL, 0, NULL));
@@ -783,15 +777,10 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc,
     addtag(q, build_tag_string("TVCHANNEL",
                                channel_get_name(ch, channel_blank_name), NULL, 0, NULL));
 
-  if (ee && ee->summary)
-    ls = ee->summary;
-  else if (ebc && ebc->summary)
+  if (ebc && ebc->summary)
     ls = ebc->summary;
-
   if(de && de->de_desc)
     ls2 = de->de_desc;
-  else if (ee && ee->description)
-    ls2 = ee->description;
   else if (ebc && ebc->description)
     ls2 = ebc->description;
 
@@ -810,22 +799,19 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc,
       addtag(q, build_tag_string("DESCRIPTION", e->str, e->lang, 0, NULL));
   }
 
-  if (ee) {
-    epg_episode_num_t num;
-    epg_episode_get_epnum(ee, &num);
-    if(num.e_num)
-      addtag(q, build_tag_int("PART_NUMBER", num.e_num,
-                              0, NULL));
-    if(num.s_num)
-      addtag(q, build_tag_int("PART_NUMBER", num.s_num,
-                              60, "SEASON"));
-    if(num.p_num)
-      addtag(q, build_tag_int("PART_NUMBER", num.p_num,
-                              40, "PART"));
-    if (num.text)
-      addtag(q, build_tag_string("SYNOPSIS",
-                              num.text, NULL, 0, NULL));
-  }
+  epg_broadcast_get_epnum(ebc, &num);
+  if(num.e_num)
+    addtag(q, build_tag_int("PART_NUMBER", num.e_num,
+                              0, NULL));
+  if(num.s_num)
+    addtag(q, build_tag_int("PART_NUMBER", num.s_num,
+                              60, "SEASON"));
+  if(num.p_num)
+    addtag(q, build_tag_int("PART_NUMBER", num.p_num,
+                              40, "PART"));
+  if (num.text)
+    addtag(q, build_tag_string("SYNOPSIS",
+                               num.text, NULL, 0, NULL));
 
   if (comment) {
     lang = "eng";
index f2cc5fd54be4759ddf31a98a9642913772bf670c..64399e7d41fc4817de4fc07ed676ed7eede17123 100644 (file)
@@ -357,7 +357,7 @@ page_einfo(http_connection_t *hc, const char *remain, void *opaque)
              days[a.tm_wday], a.tm_mday, a.tm_mon + 1,
              a.tm_hour, a.tm_min, b.tm_hour, b.tm_min);
 
-  s = epg_episode_get_title(e->episode, lang);
+  s = epg_broadcast_get_title(e, lang);
   htsbuf_qprintf(hq, "<hr><b>\"%s\": \"%s\"</b><br><br>",
              channel_get_name(e->channel, lang), s ?: "");
   
index 20bb9de92cc19c707631bd9783435013abd35fdd..9117b23822b2ced41c42a922e593a54222b8d012 100644 (file)
@@ -106,21 +106,20 @@ http_xmltv_programme_one(htsbuf_queue_t *hq, const char *hostpath,
                          channel_t *ch, epg_broadcast_t *ebc)
 {
   char start[32], stop[32], ubuf[UUID_HEX_SIZE];
-  epg_episode_t *e = ebc->episode;
   lang_str_ele_t *lse;
 
-  if (e == NULL || e->title == NULL) return;
+  if (ebc->title == NULL) return;
   http_xmltv_time(start, ebc->start);
   http_xmltv_time(stop, ebc->stop);
   htsbuf_qprintf(hq, "<programme start=\"%s\" stop=\"%s\" channel=\"%s\">\n",
                  start, stop, idnode_uuid_as_str(&ch->ch_id, ubuf));
-  RB_FOREACH(lse, e->title, link) {
+  RB_FOREACH(lse, ebc->title, link) {
     htsbuf_qprintf(hq, "  <title lang=\"%s\">", lse->lang);
     htsbuf_append_and_escape_xml(hq, lse->str);
     htsbuf_append_str(hq, "</title>\n");
   }
-  if (e->subtitle)
-    RB_FOREACH(lse, e->subtitle, link) {
+  if (ebc->subtitle)
+    RB_FOREACH(lse, ebc->subtitle, link) {
       htsbuf_qprintf(hq, "  <sub-title lang=\"%s\">", lse->lang);
       htsbuf_append_and_escape_xml(hq, lse->str);
       htsbuf_append_str(hq, "</sub-title>\n");