From 7d38bc1943fdd51961a065f225669f39b95b5b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Tue, 16 Mar 2010 21:37:03 +0000 Subject: [PATCH] * Add parsing of episode information from XMLTV and display it in the WebUI EPG --- debian/changelog | 3 + src/epg.c | 14 ++++ src/epg.h | 14 ++++ src/webui/extjs.c | 3 + src/webui/static/app/epg.js | 8 ++ src/xmltv.c | 141 ++++++++++++++++++++++++++++++++++++ 6 files changed, 183 insertions(+) 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); }