diff --git a/debian/changelog b/debian/changelog
index ce862f83..b60b56b7 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -15,6 +15,9 @@ hts-tvheadend (2.11-WIP) hts; urgency=low
to understand what is happening. It also makes (more) correct
wakeup from suspend hard to do.
+ * Add parsing of episode information from XMLTV and display it in the
+ WebUI EPG
+
hts-tvheadend (2.10) hts; urgency=high
* Fix a crash in HTSP server.
diff --git a/src/epg.c b/src/epg.c
index 2ecd38d7..fbe5b688 100644
--- a/src/epg.c
+++ b/src/epg.c
@@ -242,6 +242,19 @@ epg_event_set_content_type(event_t *e, epg_content_type_t *ect)
}
+/**
+ *
+ */
+void
+epg_event_set_episode(event_t *e, epg_episode_t *ee)
+{
+ e->e_episode.ee_season = ee->ee_season;
+ e->e_episode.ee_episode = ee->ee_episode;
+ e->e_episode.ee_part = ee->ee_part;
+
+ tvh_str_set(&e->e_episode.ee_onscreen, ee->ee_onscreen);
+}
+
/**
*
@@ -254,6 +267,7 @@ epg_event_destroy(event_t *e)
free(e->e_title);
free(e->e_desc);
+ free(e->e_episode.ee_onscreen);
LIST_REMOVE(e, e_global_link);
free(e);
}
diff --git a/src/epg.h b/src/epg.h
index 3946d5ba..e87de220 100644
--- a/src/epg.h
+++ b/src/epg.h
@@ -41,6 +41,16 @@ typedef struct epg_content_type {
uint8_t ect_dvbcode;
} epg_content_type_t;
+typedef struct epg_episode {
+
+ uint16_t ee_season;
+ uint16_t ee_episode;
+ uint16_t ee_part;
+
+ char *ee_onscreen;
+} epg_episode_t;
+
+
/*
* EPG event
*/
@@ -67,6 +77,8 @@ typedef struct event {
int e_dvb_id;
+ epg_episode_t e_episode;
+
} event_t;
@@ -87,6 +99,8 @@ void epg_event_set_ext_text(event_t *e, int ext_dn, const char *text);
void epg_event_set_content_type(event_t *e, epg_content_type_t *ect);
+void epg_event_set_episode(event_t *e, epg_episode_t *ee);
+
event_t *epg_event_create(channel_t *ch, time_t start, time_t stop,
int dvb_id, int *created);
diff --git a/src/webui/extjs.c b/src/webui/extjs.c
index a734122b..c7de9614 100644
--- a/src/webui/extjs.c
+++ b/src/webui/extjs.c
@@ -660,6 +660,9 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
if(e->e_desc != NULL)
htsmsg_add_str(m, "description", e->e_desc);
+ if(e->e_episode.ee_onscreen != NULL)
+ htsmsg_add_str(m, "episode", e->e_episode.ee_onscreen);
+
if(e->e_ext_desc != NULL)
htsmsg_add_str(m, "ext_desc", e->e_ext_desc);
diff --git a/src/webui/static/app/epg.js b/src/webui/static/app/epg.js
index ff23efb9..4317dfad 100644
--- a/src/webui/static/app/epg.js
+++ b/src/webui/static/app/epg.js
@@ -16,6 +16,7 @@ tvheadend.epgDetails = function(event) {
content += '
';
content += '
' + event.title + '
';
+ content += '' + event.episode + '
';
content += '' + event.description + '
';
content += '' + event.contentgrp + '
';
@@ -89,6 +90,7 @@ tvheadend.epg = function() {
{name: 'id'},
{name: 'channel'},
{name: 'title'},
+ {name: 'episode'},
{name: 'description'},
{name: 'ext_desc'},
{name: 'ext_item'},
@@ -148,6 +150,12 @@ tvheadend.epg = function() {
header: "Title",
dataIndex: 'title',
renderer: renderText
+ },{
+ width: 250,
+ id:'episode',
+ header: "Episode",
+ dataIndex: 'episode',
+ renderer: renderText
},{
width: 100,
id:'start',
diff --git a/src/xmltv.c b/src/xmltv.c
index edb8666a..8825e0fb 100644
--- a/src/xmltv.c
+++ b/src/xmltv.c
@@ -336,6 +336,141 @@ xmltv_parse_channel(htsmsg_t *body)
xmltv_save(xc);
}
+
+/**
+ * This is probably the most obscure formating there is. From xmltv.dtd:
+ *
+ *
+ * xmltv_ns: This is intended to be a general way to number episodes and
+ * parts of multi-part episodes. It is three numbers separated by dots,
+ * the first is the series or season, the second the episode number
+ * within that series, and the third the part number, if the programme is
+ * part of a two-parter. All these numbers are indexed from zero, and
+ * they can be given in the form 'X/Y' to show series X out of Y series
+ * made, or episode X out of Y episodes in this series, or part X of a
+ * Y-part episode. If any of these aren't known they can be omitted.
+ * You can put spaces whereever you like to make things easier to read.
+ *
+ * (NB 'part number' is not used when a whole programme is split in two
+ * for purely scheduling reasons; it's intended for cases where there
+ * really is a 'Part One' and 'Part Two'. The format doesn't currently
+ * have a way to represent a whole programme that happens to be split
+ * across two or more timeslots.)
+ *
+ * Some examples will make things clearer. The first episode of the
+ * second series is '1.0.0/1' . If it were a two-part episode, then the
+ * first half would be '1.0.0/2' and the second half '1.0.1/2'. If you
+ * know that an episode is from the first season, but you don't know
+ * which episode it is or whether it is part of a multiparter, you could
+ * give the episode-num as '0..'. Here the second and third numbers have
+ * been omitted. If you know that this is the first part of a three-part
+ * episode, which is the last episode of the first series of thirteen,
+ * its number would be '0 . 12/13 . 0/3'. The series number is just '0'
+ * because you don't know how many series there are in total - perhaps
+ * the show is still being made!
+ *
+ */
+
+static const char *
+xmltv_ns_get_parse_num(const char *s, int *ap, int *bp)
+{
+ int a = -1, b = -1;
+
+ while(1) {
+ if(!*s)
+ goto out;
+
+ if(*s == '.') {
+ s++;
+ goto out;
+ }
+
+ if(*s == '/')
+ break;
+
+ if(*s >= '0' && *s <= '9') {
+ if(a == -1)
+ a = 0;
+ a = a * 10 + *s - '0';
+ }
+ s++;
+ }
+
+ s++; // slash
+
+ while(1) {
+ if(!*s)
+ break;
+
+ if(*s == '.') {
+ s++;
+ break;
+ }
+
+ if(*s >= '0' && *s <= '9') {
+ if(b == -1)
+ b = 0;
+ b = b * 10 + *s - '0';
+ }
+ s++;
+ }
+
+
+ out:
+ if(ap) *ap = a;
+ if(bp) *bp = b;
+ return s;
+}
+
+
+
+
+static void
+parse_xmltv_ns_episode(const char *s, epg_episode_t *ee)
+{
+ int season;
+ int episode;
+ int part;
+
+ s = xmltv_ns_get_parse_num(s, &season, NULL);
+ s = xmltv_ns_get_parse_num(s, &episode, NULL);
+ xmltv_ns_get_parse_num(s, &part, NULL);
+
+ if(season != -1)
+ ee->ee_season = season + 1;
+ if(episode != -1)
+ ee->ee_episode = episode + 1;
+ if(part != -1)
+ ee->ee_part = part + 1;
+}
+
+/**
+ *
+ */
+static void
+get_episode_info(htsmsg_t *tags, epg_episode_t *ee)
+{
+ htsmsg_field_t *f;
+ htsmsg_t *c, *a;
+ const char *sys, *cdata;
+
+ memset(ee, 0, sizeof(epg_episode_t));
+
+ HTSMSG_FOREACH(f, tags) {
+ if((c = htsmsg_get_map_by_field(f)) == NULL ||
+ strcmp(f->hmf_name, "episode-num") ||
+ (a = htsmsg_get_map(c, "attrib")) == NULL ||
+ (cdata = htsmsg_get_str(c, "cdata")) == NULL ||
+ (sys = htsmsg_get_str(a, "system")) == NULL)
+ continue;
+
+ if(!strcmp(sys, "onscreen"))
+ tvh_str_set(&ee->ee_onscreen, cdata);
+ else if(!strcmp(sys, "xmltv_ns"))
+ parse_xmltv_ns_episode(cdata, ee);
+ }
+}
+
/**
* Parse tags inside of a programme
*/
@@ -348,6 +483,9 @@ xmltv_parse_programme_tags(xmltv_channel_t *xc, htsmsg_t *tags,
const char *title = xmltv_get_cdata_by_tag(tags, "title");
const char *desc = xmltv_get_cdata_by_tag(tags, "desc");
int created;
+ epg_episode_t episode;
+
+ get_episode_info(tags, &episode);
LIST_FOREACH(ch, &xc->xc_channels, ch_xc_link) {
if((e = epg_event_create(ch, start, stop, -1, &created)) == NULL)
@@ -358,7 +496,10 @@ xmltv_parse_programme_tags(xmltv_channel_t *xc, htsmsg_t *tags,
if(title != NULL) epg_event_set_title(e, title);
if(desc != NULL) epg_event_set_desc(e, desc);
+ epg_event_set_episode(e, &episode);
}
+
+ free(episode.ee_onscreen);
}