From dcd4e0bc9692d9ac8e92059b1196ee90fe0e0204 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 12:34:31 +0100 Subject: [PATCH 1/8] Updated EPG code to include some missing metadata handling. --- src/epg.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/epg.h | 23 ++++++++++ 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/src/epg.c b/src/epg.c index 78c6847f..7f1fec79 100644 --- a/src/epg.c +++ b/src/epg.c @@ -378,8 +378,9 @@ static void _epg_object_putref ( epg_object_t *eo ) if (!eo->refcount) eo->destroy(eo); } -static void _epg_object_set_updated ( epg_object_t *eo ) +static void _epg_object_set_updated ( void *p ) { + epg_object_t *eo = (epg_object_t*)p; if (!eo->_updated) { eo->_updated = 1; LIST_INSERT_HEAD(&epg_object_updated, eo, up_link); @@ -470,6 +471,30 @@ static int _epg_object_set_str return save; } +static int _epg_object_set_u8 + ( void *o, uint8_t *old, const uint8_t new ) +{ + int save = 0; + if ( *old != new ) { + *old = new; + _epg_object_set_updated(o); + save = 1; + } + return save; +} + +static int _epg_object_set_u16 + ( void *o, uint16_t *old, const uint16_t new ) +{ + int save = 0; + if ( *old != new ) { + *old = new; + _epg_object_set_updated(o); + save = 1; + } + return save; +} + /* ************************************************************************** * Brand * *************************************************************************/ @@ -1346,6 +1371,66 @@ int epg_broadcast_set_episode return save; } +int epg_broadcast_set_is_widescreen ( epg_broadcast_t *b, uint8_t ws ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_widescreen, ws); +} + +int epg_broadcast_set_is_hd ( epg_broadcast_t *b, uint8_t hd ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_hd, hd); +} + +int epg_broadcast_set_is_bw ( epg_broadcast_t *b, uint8_t bw ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_bw, bw); +} + +int epg_broadcast_set_lines ( epg_broadcast_t *b, uint16_t lines ) +{ + if (!b) return 0; + return _epg_object_set_u16(b, &b->lines, lines); +} + +int epg_broadcast_set_aspect ( epg_broadcast_t *b, uint16_t aspect ) +{ + if (!b) return 0; + return _epg_object_set_u16(b, &b->aspect, aspect); +} + +int epg_broadcast_set_is_deafsigned ( epg_broadcast_t *b, uint8_t ds ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_deafsigned, ds); +} + +int epg_broadcast_set_is_subtitled ( epg_broadcast_t *b, uint8_t st ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_subtitled, st); +} + +int epg_broadcast_set_is_audio_desc ( epg_broadcast_t *b, uint8_t ad ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_audio_desc, ad); +} + +int epg_broadcast_set_is_new ( epg_broadcast_t *b, uint8_t n ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_new, n); +} + +int epg_broadcast_set_is_repeat ( epg_broadcast_t *b, uint8_t r ) +{ + if (!b) return 0; + return _epg_object_set_u8(b, &b->is_repeat, r); +} + epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast ) { if ( !broadcast ) return NULL; @@ -1365,6 +1450,26 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast ) htsmsg_add_u32(m, "channel", broadcast->channel->ch_id); if (broadcast->dvb_eid) htsmsg_add_u32(m, "dvb_eid", broadcast->dvb_eid); + if (broadcast->is_widescreen) + 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_widescreen", 1); + if (broadcast->lines) + htsmsg_add_u32(m, "lines", broadcast->lines); + if (broadcast->aspect) + htsmsg_add_u32(m, "aspect", broadcast->aspect); + if (broadcast->is_deafsigned) + htsmsg_add_u32(m, "is_deafsigned", 1); + if (broadcast->is_subtitled) + htsmsg_add_u32(m, "is_subtitled", 1); + if (broadcast->is_audio_desc) + htsmsg_add_u32(m, "is_audio_desc", 1); + if (broadcast->is_new) + htsmsg_add_u32(m, "is_new", 1); + if (broadcast->is_repeat) + htsmsg_add_u32(m, "is_repeat", 1); return m; } @@ -1376,7 +1481,7 @@ epg_broadcast_t *epg_broadcast_deserialize epg_broadcast_t *ret, **ebc = _epg_broadcast_skel(); epg_episode_t *ee; const char *str; - uint32_t chid, eid, start, stop; + uint32_t chid, eid, start, stop, u32; if ( htsmsg_get_u32(m, "start", &start) ) return NULL; if ( htsmsg_get_u32(m, "stop", &stop) ) return NULL; @@ -1401,6 +1506,28 @@ epg_broadcast_t *epg_broadcast_deserialize ch = channel_find_by_identifier(chid); } + /* Get metadata */ + if (!htsmsg_get_u32(m, "is_widescreen", &u32)) + (*ebc)->is_widescreen = 1; + if (!htsmsg_get_u32(m, "is_hd", &u32)) + (*ebc)->is_hd = 1; + if (!htsmsg_get_u32(m , "is_bw", &u32)) + (*ebc)->is_bw = 1; + if (!htsmsg_get_u32(m, "lines", &u32)) + (*ebc)->lines = u32; + if (!htsmsg_get_u32(m, "aspect", &u32)) + (*ebc)->aspect = u32; + if (!htsmsg_get_u32(m, "is_deafsigned", &u32)) + (*ebc)->is_deafsigned = 1; + if (!htsmsg_get_u32(m, "is_subtitled", &u32)) + (*ebc)->is_subtitled = 1; + if (!htsmsg_get_u32(m, "is_audio_desc", &u32)) + (*ebc)->is_audio_desc = 1; + if (!htsmsg_get_u32(m, "is_new", &u32)) + (*ebc)->is_new = 1; + if (!htsmsg_get_u32(m, "is_repeat", &u32)) + (*ebc)->is_repeat = 1; + /* Add to channel */ if ( ch ) { ret = _epg_channel_add_broadcast(ch, ebc, create, save); diff --git a/src/epg.h b/src/epg.h index defa092d..2714d344 100644 --- a/src/epg.h +++ b/src/epg.h @@ -205,6 +205,8 @@ struct epg_episode uint16_t part_count; ///< For multipart episodes char *image; ///< Episode image + // TODO: certification and rating + LIST_ENTRY(epg_episode) blink; ///< Brand link LIST_ENTRY(epg_episode) slink; ///< Season link epg_brand_t *brand; ///< (Grand-)Parent brand @@ -290,6 +292,7 @@ struct epg_broadcast /* Some quality info */ uint8_t is_widescreen; ///< Is widescreen uint8_t is_hd; ///< Is HD + uint8_t is_bw; ///< Is black and white uint16_t lines; ///< Lines in image (quality) uint16_t aspect; ///< Aspect ratio (*100) @@ -318,6 +321,26 @@ epg_broadcast_t *epg_broadcast_find_by_eid ( int eid, struct channel *ch ); /* Mutators */ int epg_broadcast_set_episode ( epg_broadcast_t *b, epg_episode_t *e ) __attribute__((warn_unused_result)); +int epg_broadcast_set_is_widescreen ( epg_broadcast_t *b, uint8_t ws ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_is_hd ( epg_broadcast_t *b, uint8_t hd ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_is_bw ( epg_broadcast_t *b, uint8_t bw ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_lines ( epg_broadcast_t *b, uint16_t lines ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_aspect ( epg_broadcast_t *b, uint16_t aspect ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_is_deafsigned ( epg_broadcast_t *b, uint8_t ds ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_is_subtitled ( epg_broadcast_t *b, uint8_t st ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_is_audio_desc ( epg_broadcast_t *b, uint8_t ad ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_is_new ( epg_broadcast_t *b, uint8_t n ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_is_repeat ( epg_broadcast_t *b, uint8_t r ) + __attribute__((warn_unused_result)); /* Accessors */ epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *b ); From 907da7f330071ea4555dfc3926cd34d1ce3068f4 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 12:35:09 +0100 Subject: [PATCH 2/8] Updated XMLTV code to use new broadcast metadata. --- src/epggrab/xmltv.c | 95 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c index 3cabdc9e..fa29e02f 100644 --- a/src/epggrab/xmltv.c +++ b/src/epggrab/xmltv.c @@ -204,6 +204,85 @@ get_episode_info } } +/* + * Process video quality flags + * + * Note: this is very rough/approx someone might be able to do a much better + * job + */ +static int +parse_vid_quality ( epg_broadcast_t *ebc, htsmsg_t *m ) +{ + int save = 0; + int hd = 0, lines = 0, aspect = 0; + const char *str; + if (!ebc || !m) return 0; + + if ((str = htsmsg_xml_get_cdata_str(m, "colour"))) + save |= epg_broadcast_set_is_bw(ebc, strcmp(str, "no") ? 0 : 1); + if ((str = htsmsg_xml_get_cdata_str(m, "quality"))) { + if (strstr(str, "HD")) { + hd = 1; + } else if (strstr(str, "480")) { + lines = 480; + aspect = 150; + } else if (strstr(str, "576")) { + lines = 576; + aspect = 133; + } else if (strstr(str, "720")) { + lines = 720; + hd = 1; + aspect = 178; + } else if (strstr(str, "1080")) { + lines = 1080; + hd = 1; + aspect = 178; + } + } + if ((str = htsmsg_xml_get_cdata_str(m, "aspect"))) { + int w, h; + if (sscanf(str, "%d:%d", &w, &h) == 2) { + aspect = (100 * w) / h; + } + } + save |= epg_broadcast_set_is_hd(ebc, hd); + if (aspect) { + save |= epg_broadcast_set_is_widescreen(ebc, hd || aspect > 137); + save |= epg_broadcast_set_aspect(ebc, aspect); + } + if (lines) + save |= epg_broadcast_set_lines(ebc, lines); + + return save; +} + +/* + * Parse accessibility data + */ +static int +parse_accessibility ( epg_broadcast_t *ebc, htsmsg_t *m ) +{ + int save = 0; + htsmsg_t *tag; + htsmsg_field_t *f; + const char *str; + + HTSMSG_FOREACH(f, m) { + if(!strcmp(f->hmf_name, "subtitles")) { + if ((tag = htsmsg_get_map_by_field(f))) { + str = htsmsg_xml_get_attr_str(tag, "type"); + if (str && !strcmp(str, "teletext")) + save |= epg_broadcast_set_is_subtitled(ebc, 1); + else if (str && !strcmp(str, "deaf-signed")) + save |= epg_broadcast_set_is_deafsigned(ebc, 1); + } + } else if (!strcmp(f->hmf_name, "audio-described")) { + save |= epg_broadcast_set_is_audio_desc(ebc, 1); + } + } + return save; +} + /** * Parse tags inside of a programme */ @@ -242,6 +321,7 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags, if (en) save |= epg_episode_set_number(ee, en); if (save) stats->episodes.modified++; + // TODO: need to handle certification and ratings // TODO: need to handle season numbering! // TODO: need to handle onscreen numbering //if (onscreen) save |= epg_episode_set_onscreen(ee, onscreen); @@ -252,6 +332,21 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags, stats->broadcasts.total++; if (save2) stats->broadcasts.created++; save2 |= epg_broadcast_set_episode(ebc, ee); + + /* Quality metadata */ + save2 |= parse_vid_quality(ebc, htsmsg_get_map(tags, "video")); + + /* Accessibility */ + save2 |= parse_accessibility(ebc, tags); + + /* Misc */ + if (htsmsg_get_map(tags, "previously-shown")) + save |= epg_broadcast_set_is_repeat(ebc, 1); + else if (htsmsg_get_map(tags, "premiere") || + htsmsg_get_map(tags, "new")) + save |= epg_broadcast_set_is_new(ebc, 1); + + /* Stats */ if (save2) stats->broadcasts.modified++; } From 46a00e636df65620018e4c4831f96092fdfd8c16 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 13:31:25 +0100 Subject: [PATCH 3/8] Re-done the episode numbering variables to make it possible to store season numbers without a season (for things like xmltv etc...). --- src/dvr/dvr_autorec.c | 4 +- src/dvr/mkmux.c | 23 ++++---- src/epg.c | 125 ++++++++++++++++++++++++++++++++++-------- src/epg.h | 27 ++++----- 4 files changed, 129 insertions(+), 50 deletions(-) diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index c94c78ca..b18a8321 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -137,7 +137,7 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e) // Note: dae_epnum is unset then all values are 0 and this will // always return 1 - epg_episode_number_full(e->episode, &epnum); + epg_episode_get_epnum(e->episode, &epnum); if(epg_episode_number_cmp(&dae->dae_epnum, &epnum) < 0) return 0; @@ -567,7 +567,7 @@ void dvr_autorec_add_series_link atime = (t.tm_hour * 60) + t.tm_min; } if (cfg->dvr_sl_more_recent) { - epg_episode_number_full(ee, &epnum); + epg_episode_get_epnum(ee, &epnum); epnump = &epnum; } _dvr_autorec_add(dvr_config_name, event->episode->title, diff --git a/src/dvr/mkmux.c b/src/dvr/mkmux.c index faaa7477..37fbc750 100644 --- a/src/dvr/mkmux.c +++ b/src/dvr/mkmux.c @@ -501,12 +501,6 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc) if(ch) addtag(q, build_tag_string("TVCHANNEL", ch->ch_name, 0, NULL)); -#if TODO_EP_NUMBER_ONSCREEN - if(ee && ee->onscreen) - addtag(q, build_tag_string("SYNOPSIS", - ee->onscreen, 0, NULL)); -#endif - if(de && de->de_desc) addtag(q, build_tag_string("SUMMARY", de->de_desc, 0, NULL)); else if (ee && ee->description) @@ -515,15 +509,20 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc) addtag(q, build_tag_string("SUMMARY", ee->summary, 0, NULL)); if (ee) { - if(ee->number) - addtag(q, build_tag_int("PART_NUMBER", ee->number, + 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(ee->season && ee->season->number) - addtag(q, build_tag_int("PART_NUMBER", ee->season->number, + if(num.s_num) + addtag(q, build_tag_int("PART_NUMBER", num.s_num, 60, "SEASON")); - if(ee->part_number) - addtag(q, build_tag_int("PART_NUMBER", ee->part_number, + 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, 0, NULL)); } return q; diff --git a/src/epg.c b/src/epg.c index 7f1fec79..f62390ab 100644 --- a/src/epg.c +++ b/src/epg.c @@ -80,16 +80,23 @@ static int _season_order ( const void *_a, const void *_b ) return a->number - b->number; } +// Note: this will do nothing with text episode numbering static int _episode_order ( const void *_a, const void *_b ) { - int r; + int r, as, bs; const epg_episode_t *a = (const epg_episode_t*)_a; const epg_episode_t *b = (const epg_episode_t*)_b; - r = _season_order(a->season, b->season); + if (!a) return -1; + if (!b) return 1; + if (a->season) as = a->season->number; + else as = a->epnum.s_num; + if (b->season) bs = b->season->number; + else bs = b->epnum.s_num; + r = as - bs; if (r) return r; - if (!a || !a->number) return 1; - if (!b || !b->number) return -1; - return a->number - b->number; + r = a->epnum.e_num - b->epnum.e_num; + if (r) return r; + return a->epnum.p_num - b->epnum.p_num; } /* ************************************************************************** @@ -817,6 +824,53 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save ) * Episode * *************************************************************************/ +static htsmsg_t *epg_episode_num_serialize ( epg_episode_num_t *num ) +{ + htsmsg_t *m; + if (!num) return NULL; + m = htsmsg_create_map(); + if (num->e_num) + htsmsg_add_u32(m, "e_num", num->e_num); + if (num->e_cnt) + htsmsg_add_u32(m, "e_cnt", num->e_cnt); + if (num->s_num) + htsmsg_add_u32(m, "s_num", num->e_num); + if (num->s_cnt) + htsmsg_add_u32(m, "s_cnt", num->e_cnt); + if (num->p_num) + htsmsg_add_u32(m, "p_num", num->e_num); + if (num->p_cnt) + htsmsg_add_u32(m, "p_cnt", num->e_cnt); + if (num->text) + htsmsg_add_str(m, "text", num->text); + return m; +} + +static epg_episode_num_t *epg_episode_num_deserialize + ( htsmsg_t *m, epg_episode_num_t *num ) +{ + const char *str; + uint32_t u32; + if (!m) return NULL; + if (!num) num = calloc(1, sizeof(epg_episode_num_t)); + if (!htsmsg_get_u32(m, "e_num", &u32)) + num->e_num = u32; + if (!htsmsg_get_u32(m, "e_cnt", &u32)) + num->e_cnt = u32; + if (!htsmsg_get_u32(m, "s_num", &u32)) + num->s_num = u32; + if (!htsmsg_get_u32(m, "s_cnt", &u32)) + num->s_cnt = u32; + if (!htsmsg_get_u32(m, "p_num", &u32)) + num->p_num = u32; + if (!htsmsg_get_u32(m, "p_cnt", &u32)) + num->p_cnt = u32; + if ((str = htsmsg_get_str(m, "text"))) + num->text = strdup(str); + + return num; +} + static void _epg_episode_destroy ( epg_object_t *eo ) { epg_episode_t *ee = (epg_episode_t*)eo; @@ -839,6 +893,7 @@ static void _epg_episode_destroy ( epg_object_t *eo ) if (ee->description) free(ee->description); if (ee->genre) free(ee->genre); if (ee->image) free(ee->image); + if (ee->epnum.text) free(ee->epnum.text); free(ee); } @@ -901,8 +956,8 @@ int epg_episode_set_number ( epg_episode_t *episode, uint16_t number ) { int save = 0; if ( !episode || !number ) return 0; - if ( episode->number != number ) { - episode->number = number; + if ( episode->epnum.e_num != number ) { + episode->epnum.e_num = number; _epg_object_set_updated((epg_object_t*)episode); save = 1; } @@ -913,19 +968,40 @@ int epg_episode_set_part ( epg_episode_t *episode, uint16_t part, uint16_t count { int save = 0; if ( !episode || !part ) return 0; - if ( episode->part_number != part ) { - episode->part_number = part; + if ( episode->epnum.p_num != part ) { + episode->epnum.p_num = part; _epg_object_set_updated((epg_object_t*)episode); save = 1; } - if ( count && episode->part_count != count ) { - episode->part_count = count; + if ( count && episode->epnum.p_cnt != count ) { + episode->epnum.p_cnt = count; _epg_object_set_updated((epg_object_t*)episode); save = 1; } return save; } +int epg_episode_set_epnum ( epg_episode_t *episode, epg_episode_num_t *num ) +{ + int save = 0; + if (!episode || !num || (!num->e_num && !num->text)) return 0; + if ( episode->epnum.e_num != num->e_num ) save = 1; + else if ( episode->epnum.e_cnt != num->e_cnt ) save = 1; + else if ( episode->epnum.s_num != num->s_num ) save = 1; + else if ( episode->epnum.s_cnt != num->s_cnt ) save = 1; + else if ( episode->epnum.p_num != num->p_num ) save = 1; + else if ( episode->epnum.p_cnt != num->p_cnt ) save = 1; + else if ( !episode->epnum.text || + (num->text && strcmp(num->text, episode->epnum.text)) ) save = 1; + if (save) { + if (episode->epnum.text) free(episode->epnum.text); + episode->epnum = *num; + if (episode->epnum.text) strdup(episode->epnum.text); + save = 1; + } + return save; +} + int epg_episode_set_brand ( epg_episode_t *episode, epg_brand_t *brand ) { int save = 0; @@ -1030,7 +1106,7 @@ size_t epg_episode_number_format size_t i = 0; if (!episode) return 0; epg_episode_num_t num; - epg_episode_number_full(episode, &num); + epg_episode_get_epnum(episode, &num); if ( num.e_num ) { if (pre) i += snprintf(&buf[i], len-i, "%s", pre); if ( sfmt && num.s_num ) { @@ -1042,17 +1118,17 @@ size_t epg_episode_number_format i += snprintf(&buf[i], len-i, efmt, num.e_num); if ( cfmt && num.e_cnt ) i+= snprintf(&buf[i], len-i, cfmt, num.e_cnt); + } else if ( num.text ) { + strncpy(buf, num.text, len); + i = MAX(strlen(num.text), len); } return i; } -void epg_episode_number_full ( epg_episode_t *ee, epg_episode_num_t *num ) +void epg_episode_get_epnum ( epg_episode_t *ee, epg_episode_num_t *num ) { if (!ee || !num) return; - memset(num, 0, sizeof(epg_episode_num_t)); - num->e_num = ee->number; - num->p_num = ee->part_number; - num->p_cnt = ee->part_count; + *num = ee->epnum; if (ee->season) { num->e_cnt = ee->season->episode_count; num->s_num = ee->season->number; @@ -1102,12 +1178,7 @@ htsmsg_t *epg_episode_serialize ( epg_episode_t *episode ) htsmsg_add_str(m, "summary", episode->summary); if (episode->description) htsmsg_add_str(m, "description", episode->description); - if (episode->number) - htsmsg_add_u32(m, "number", episode->number); - if (episode->part_count && episode->part_count) { - htsmsg_add_u32(m, "part-number", episode->part_number); - htsmsg_add_u32(m, "part-count", episode->part_count); - } + htsmsg_add_msg(m, "epnum", epg_episode_num_serialize(&episode->epnum)); if (episode->brand) htsmsg_add_str(m, "brand", episode->brand->uri); if (episode->season) @@ -1123,6 +1194,8 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save ) epg_brand_t *eb; uint32_t u32, u32a; const char *str; + epg_episode_num_t num; + htsmsg_t *sub; if ( !_epg_object_deserialize(m, *skel) ) return NULL; if ( !(ee = epg_episode_find_by_uri((*skel)->uri, create, save)) ) return NULL; @@ -1135,6 +1208,12 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save ) *save |= epg_episode_set_summary(ee, str); if ( (str = htsmsg_get_str(m, "description")) ) *save |= epg_episode_set_description(ee, str); + if ( (sub = htsmsg_get_map(m, "epnum")) ) { + epg_episode_num_deserialize(sub, &num); + *save |= epg_episode_set_epnum(ee, &num); + if (num.text) free(num.text); + } + // Note: retained for compat if ( !htsmsg_get_u32(m, "number", &u32) ) *save |= epg_episode_set_number(ee, u32); if ( !htsmsg_get_u32(m, "part-number", &u32) && diff --git a/src/epg.h b/src/epg.h index 2714d344..a978067c 100644 --- a/src/epg.h +++ b/src/epg.h @@ -181,12 +181,13 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save ); */ typedef struct epg_episode_num { - uint16_t s_num; ///< Series number - uint16_t s_cnt; ///< Series count - uint16_t e_num; ///< Episode number - uint16_t e_cnt; ///< Episode count - uint16_t p_num; ///< Part number - uint16_t p_cnt; ///< Part count + uint16_t s_num; ///< Series number + uint16_t s_cnt; ///< Series count + uint16_t e_num; ///< Episode number + uint16_t e_cnt; ///< Episode count + uint16_t p_num; ///< Part number + uint16_t p_cnt; ///< Part count + char *text; ///< Arbitary text description of episode num } epg_episode_num_t; /* Object */ @@ -200,9 +201,8 @@ struct epg_episode char *description; ///< An extended description uint8_t *genre; ///< Episode genre(s) int genre_cnt; ///< Genre count - uint16_t number; ///< The episode number - uint16_t part_number; ///< For multipart episodes - uint16_t part_count; ///< For multipart episodes + epg_episode_num_t epnum; ///< Episode numbering + // Note: do not use epnum directly! use the accessor routine char *image; ///< Episode image // TODO: certification and rating @@ -231,11 +231,11 @@ int epg_episode_set_description ( epg_episode_t *e, const char *description ) __attribute__((warn_unused_result)); int epg_episode_set_number ( epg_episode_t *e, uint16_t number ) __attribute__((warn_unused_result)); -int epg_episode_set_onscreen ( epg_episode_t *e, const char *onscreen ) - __attribute__((warn_unused_result)); int epg_episode_set_part ( epg_episode_t *e, uint16_t number, uint16_t count ) __attribute__((warn_unused_result)); +int epg_episode_set_epnum ( epg_episode_t *e, epg_episode_num_t *num ) + __attribute__((warn_unused_result)); int epg_episode_set_brand ( epg_episode_t *e, epg_brand_t *b ) __attribute__((warn_unused_result)); int epg_episode_set_season ( epg_episode_t *e, epg_season_t *s ) @@ -247,6 +247,9 @@ int epg_episode_set_genre_str ( epg_episode_t *e, const char **s ) int epg_episode_set_image ( epg_episode_t *e, const char *i ) __attribute__((warn_unused_result)); +// Note: this does NOT strdup the text field +void epg_episode_get_epnum + ( epg_episode_t *e, epg_episode_num_t *epnum ); /* EpNum format helper */ // output string will be: // if (episode_num) @@ -262,8 +265,6 @@ size_t epg_episode_number_format const char *pre, const char *sfmt, const char *sep, const char *efmt, const char *cfmt ); -void epg_episode_number_full - ( epg_episode_t *e, epg_episode_num_t *epnum ); int epg_episode_number_cmp ( epg_episode_num_t *a, epg_episode_num_t *b ); From 046c93b981cf1ae6ecfa83183d276d4de58dbc8a Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 13:38:00 +0100 Subject: [PATCH 4/8] Updated a couple of TODOs relating to episode number in EPG code. --- src/epg.c | 23 +++++++++++------------ src/epg.h | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/epg.c b/src/epg.c index f62390ab..46570ab6 100644 --- a/src/epg.c +++ b/src/epg.c @@ -112,7 +112,7 @@ static void _epg_event_deserialize ( htsmsg_t *c, epggrab_stats_t *stats ) uint32_t e_start = 0; uint32_t e_stop = 0; uint32_t u32; - const char *title, *desc; + const char *title, *desc, *str; char *uri; int save = 0; @@ -145,8 +145,10 @@ static void _epg_event_deserialize ( htsmsg_t *c, epggrab_stats_t *stats ) save |= epg_episode_set_number(ee, u32); if (!htsmsg_get_u32(c, "part", &u32)) save |= epg_episode_set_part(ee, u32, 0); - // TODO: season number! - // TODO: onscreen + 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); @@ -262,8 +264,6 @@ void epg_init ( void ) memset(&stats, 0, sizeof(stats)); while ( remain > 4 ) { - // TODO: would be nice if htsmsg_binary handled this for us! - /* Get message length */ int msglen = (rp[0] << 24) | (rp[1] << 16) | (rp[2] << 8) | rp[3]; remain -= 4; @@ -1149,18 +1149,17 @@ int epg_episode_number_cmp ( epg_episode_num_t *a, epg_episode_num_t *b ) } } + +// 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. int epg_episode_fuzzy_match ( epg_episode_t *episode, const char *uri, const char *title, const char *summary, const char *description ) { - // TODO: this is pretty noddy and likely to fail! - // hence the reason I don't recommend mixing external grabbers and EIT 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; - // TODO: could we do fuzzy string matching on the description/summary - // : there are a few algorithms that might work, but some early testing - // : suggested it wasn't clear cut enough to make sensible decisions. return 0; } @@ -1417,8 +1416,8 @@ epg_broadcast_t* epg_broadcast_find_by_time epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, channel_t *ch ) { - // TODO: channel left in for now in case I decide to change implementation - // to simplify the search! + // Note: I have left channel_t param, just in case I decide to change + // to use it for shorter search return (epg_broadcast_t*)_epg_object_find_by_id(id); } diff --git a/src/epg.h b/src/epg.h index a978067c..a973a9f4 100644 --- a/src/epg.h +++ b/src/epg.h @@ -385,7 +385,7 @@ typedef struct epg_query_result { void epg_query_free(epg_query_result_t *eqr); /* Sorting */ -// TODO: comparator function input? +// WIBNI: might be useful to have a user defined comparator function void epg_query_sort(epg_query_result_t *eqr); /* Query routines */ From 4c71167c44aa4ec4cda5820feeb77053553af19f Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 14:05:12 +0100 Subject: [PATCH 5/8] Removed some pointless TODO messages and updated the config help for EPG Grab. --- docs/html/config_epggrab.html | 58 +++++++++++++++++++++++++++++++++ docs/html/config_xmltv.html | 33 ------------------- src/webui/static/app/epg.js | 6 +--- src/webui/static/app/epggrab.js | 4 +-- 4 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 docs/html/config_epggrab.html delete mode 100644 docs/html/config_xmltv.html diff --git a/docs/html/config_epggrab.html b/docs/html/config_epggrab.html new file mode 100644 index 00000000..a0d2c0cc --- /dev/null +++ b/docs/html/config_epggrab.html @@ -0,0 +1,58 @@ +
+ +

+ This tab is used to configure EPG grabbing capabilities. TVheadend supports + a variety of different EPG grabbing mechanisms. These fall into 3 broad + categories, within which there are a variety of specific grabber implementations. +

+ +

Grabber Types

+
    +
  • Over the Air (OTA) - these receive EPG data directly from the DVB network. This is often the easiest way to get up and running, and does provide timely updates should scheduling change. However the information isn't always as rich as some of the other grabbers.
  • +
  • Interanl - These are grabbers which can be internally initiated from within TVheadend based on IP based grabbing solutions (see below). This can be a quick way to get richer EPG data where you don't have decent OTA support.
  • +
  • External - These provide the option to run the grabber scripts externally and to pump the data into TVheadend via Unix domain sockets. It provides more complex configurations using things like cronjob's etc.
  • +
+ +

Grabber Modules

+
    +
  • EIT - This is a DVB standards compatible EIT grabber. Typically it will + retrieve now/next information, though on some networks there may be more + extensive data published.
  • +
  • OpenTV - This is a proprietary OTA EPG grabber. Its known to be used on the SKY networks, but others may use it. You need two configuration files to define settings for your particular network, if you don't see yours listed please visit IRC #hts for help.
  • +
  • XMLTV - This is a IP network based scraper, for more information about XMLTV please visit http://www.xmltv.org. To make use of the internal XMLTV grabber you must have tv_find_grabbers installed. If you install new grabbers you will need to restart TVheadend to pick these up asthey're loaded at startup.
  • +
  • PyEPG - This is another IP network based scraper. It currently only supports the Atlas UK system (for which you need a key), but it does provide a very rich EPG data set. For more information see http://github.com/adamsutton/PyEPG.
  • +
+ +

Configuration options

+

+

+
Module: +
Select which internal grabber to use. + +
Grab interval +
Time period between grabs. + +
External interfaces +
Check tick boxes for whichever you want to make available, the Path column displays where the unix socket you need to use lives. + +
OTA interfaces +
Check tick boxes for whichever you want to use. + +
+ Changes to any of these settings must be confirmed by pressing the + 'Save configuration' button before taking effect. +

+ +

Notes

+ +

+ XMLTV/PyEPG - if you are using the internal versions of these modules then + you must first configure them (if required) externally as TVHeadend provides + no support for this. Once configured though TVheadend will do the rest. +

+ +

+ Mixed grabbers - Although as much as possible is done to avoid contention, generally speaking using a mixture of grabbers should be avoided (where each grabber updates the same channels). They typically tend to contain differing information which results in them "fighting" over which information is correct and can result in a high level of EPG update messages. +

+ +
diff --git a/docs/html/config_xmltv.html b/docs/html/config_xmltv.html deleted file mode 100644 index a47503cd..00000000 --- a/docs/html/config_xmltv.html +++ /dev/null @@ -1,33 +0,0 @@ -
- - - -

- This tab is used to configure XML-TV. For more information about XML-TV - and its use, please visit - http://www.xmltv.org. - -

- Configuration options: -

-
XML-TV Source -
Select which grabber to use. - When the drop down list is pressed Tvheadend will issue a scan - for available grabbers on the host system. This result will be - cached in the web user interface. Thus, if you need to rescan due to - a newly installed grabber you must reload the web interface. - (There is no need to restart Tvheadend itself). - - When you select a grabber more information will pop up regarding - further configuration of the grabber. - -
Grab interval -
Hours between each grab. - -
Enable grabbing -
Uncheck this if you wish to disable grabbing. -
- Changes to any of these settings must be confirmed by pressing the - 'Save configuration' button before taking effect. - -
diff --git a/src/webui/static/app/epg.js b/src/webui/static/app/epg.js index 3cf34f4d..87ec839e 100644 --- a/src/webui/static/app/epg.js +++ b/src/webui/static/app/epg.js @@ -1,12 +1,11 @@ tvheadend.brands = new Ext.data.JsonStore({ root: 'entries', - // TODO: this is not ALL fields, just those that I'm likely to use here fields: [ 'uri', 'title' ], autoLoad: true, url : 'epgobject', baseParams : { op : 'brandList' } }); -// TODO: we might want this to periodically update! +// WIBNI: might want this store to periodically update tvheadend.ContentGroupStore = new Ext.data.JsonStore({ root:'entries', @@ -117,7 +116,6 @@ tvheadend.epgDetails = function(event) { } function showAlternatives (s) { - // TODO: must be a way to constrain this var e = Ext.get('altbcast') html = ''; if ( s.getTotalCount() > 0 ) { @@ -126,14 +124,12 @@ tvheadend.epgDetails = function(event) { var ab = s.getAt(i).data; var dt = Date.parseDate(ab.start, 'U'); html += '
' + dt.format('l H:i') + '   ' + ab.channel + '
'; - // TODO: record option? } } e.dom.innerHTML = html; } function showRelated (s) { - // TODO: must be a way to constrain this var e = Ext.get('related') html = ''; if ( s.getTotalCount() > 0 ) { diff --git a/src/webui/static/app/epggrab.js b/src/webui/static/app/epggrab.js index 30c30b71..5dd90804 100644 --- a/src/webui/static/app/epggrab.js +++ b/src/webui/static/app/epggrab.js @@ -176,7 +176,6 @@ tvheadend.epggrab = function() { dataIndex : 'path', width : 300, sortable : false, - // TODO: editable? }, ]); @@ -258,7 +257,8 @@ tvheadend.epggrab = function() { var helpButton = new Ext.Button({ text : 'Help', handler : function() { - alert('TODO: help info'); + new tvheadend.help('EPG Grab Configuration', + 'config_epggrab.html'); } }); From 1d0690c4d13bb5f25b479255b361df9ce905890f Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 14:24:13 +0100 Subject: [PATCH 6/8] Added save/load of epggrab channel number, remove redundant TODOs and moved useful ones to github issues. --- src/epggrab.c | 20 ++++---------------- src/epggrab.h | 1 - 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/epggrab.c b/src/epggrab.c index c36b7bbb..62948eae 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -100,8 +100,6 @@ static void _epggrab_module_grab ( epggrab_module_t *mod ) /* * Socket handler - * - * TODO: could make this threaded to allow multiple simultaneous inputs */ static void _epggrab_socket_handler ( epggrab_module_t *mod, int s ) { @@ -170,10 +168,6 @@ static void* _epggrab_internal_thread ( void* p ) /* * External (socket) grab thread - * - * TODO: I could common all of this up and have a single thread - * servicing all the available sockets, but we're unlikely to - * have a massive number of modules enabled anyway! */ static void *_epggrab_socket_thread ( void *p ) { @@ -202,7 +196,6 @@ static int _ch_id_cmp ( void *a, void *b ) ((epggrab_channel_t*)b)->id); } -// TODO: add other matches static int _ch_link ( epggrab_channel_t *ec, channel_t *ch ) { service_t *sv; @@ -249,7 +242,6 @@ static int _ch_link ( epggrab_channel_t *ec, channel_t *ch ) return match; } -// TODO: could use TCP socket to allow remote access int epggrab_module_enable_socket ( epggrab_module_t *mod, uint8_t e ) { pthread_t tid; @@ -343,7 +335,6 @@ htsmsg_t *epggrab_module_trans_xml return ret; } -// TODO: add extra metadata void epggrab_module_channel_save ( epggrab_module_t *mod, epggrab_channel_t *ch ) { @@ -373,6 +364,8 @@ void epggrab_module_channel_save } htsmsg_add_msg(m, "sname", a); } + if (ch->number) + htsmsg_add_u32(m, "number", ch->number); hts_settings_save(m, "epggrab/%s/channels/%s", mod->id, ch->id); } @@ -413,6 +406,8 @@ static void epggrab_module_channel_load i++; } } + if(!htsmsg_get_u32(m, "number", &u32)) + ch->number = u32; if (!htsmsg_get_u32(m, "channel", &u32)) ch->channel = channel_find_by_identifier(u32); @@ -508,7 +503,6 @@ int epggrab_channel_set_name ( epggrab_channel_t *ec, const char *name ) return save; } -// TODO: what a mess! int epggrab_channel_set_sid ( epggrab_channel_t *ec, const uint16_t *sid, int num ) { @@ -535,7 +529,6 @@ int epggrab_channel_set_sid return save; } -// TODO: what a mess! int epggrab_channel_set_sname ( epggrab_channel_t *ec, const char **sname ) { int save = 0, i = 0; @@ -595,9 +588,6 @@ int epggrab_channel_set_number ( epggrab_channel_t *ec, int number ) return save; } -// TODO: add other match critera -// TODO: add additional metadata updates -// TODO: add configurable updating void epggrab_channel_link ( epggrab_channel_t *ec ) { channel_t *ch; @@ -618,8 +608,6 @@ void epggrab_channel_updated ( epggrab_channel_t *ec ) epggrab_module_channel_save(ec->mod, ec); } -// TODO: currently lists ALL channels from ALL active modules -// TODO: won't work if channels are handled internally within module! htsmsg_t *epggrab_channel_list ( void ) { char name[100]; diff --git a/src/epggrab.h b/src/epggrab.h index 88e3bb15..59a35897 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -48,7 +48,6 @@ typedef struct epggrab_channel char *icon; ///< Channel icon int number; ///< Channel number - // TODO: I think we might need a list of channels! struct channel *channel; ///< Mapped channel } epggrab_channel_t; From 376ca80ee5c489480f0fb7888a7cc41619aca633 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 14:44:55 +0100 Subject: [PATCH 7/8] Remove redundant TODOs and move interesting ones to github issues. --- src/dvr/dvr_autorec.c | 9 --------- src/dvr/dvr_db.c | 3 --- 2 files changed, 12 deletions(-) diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index b18a8321..8e98fa76 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -95,10 +95,6 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e) } // Note: ignore channel test if we allow quality unlocking - // TODO: should we only apply this setting if this is actually - /// created as a series link? - // TODO: I could just REMOVE the channel, but I think we probably still - // want the channel as a "preferred" option cfg = dvr_config_find_by_name_default(dae->dae_config_name); if (cfg->dvr_sl_quality_lock) if(dae->dae_channel != NULL && @@ -548,7 +544,6 @@ dvr_autorec_add(const char *config_name, NULL, NULL, 0, NULL, creator, comment); } -/* TODO: configurable brand/series selection */ void dvr_autorec_add_series_link ( const char *dvr_config_name, epg_broadcast_t *event, const char *creator, const char *comment ) @@ -601,19 +596,15 @@ dvr_autorec_check_event(epg_broadcast_t *e) void dvr_autorec_check_brand(epg_brand_t *b) { -#ifdef TODO_BRAND_UPDATED_SUPPORT // Note: for the most part this will only be relevant should an episode // to which a broadcast is linked suddenly get added to a new brand // this is pretty damn unlikely! -#endif } void dvr_autorec_check_season(epg_season_t *s) { -#ifdef TODO_SEASON_SUPPORT // Note: I guess new episodes might have been added, but again its likely // this will already have been picked up by the check_event call -#endif } /** diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 40378ffb..b9a7e371 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -369,7 +369,6 @@ dvr_entry_create_by_event(const char *config_name, static int _dvr_duplicate_event ( epg_broadcast_t *e ) { - // TODO: include other searches? dvr_entry_t *de; LIST_FOREACH(de, &dvrentries, de_global_link) { if (de->de_bcast && (de->de_bcast->episode == e->episode)) return 1; @@ -386,7 +385,6 @@ dvr_entry_create_by_autorec(epg_broadcast_t *e, dvr_autorec_entry_t *dae) char buf[200]; /* Dup detection */ - // TODO: need to allow overrides if (_dvr_duplicate_event(e)) return; if(dae->dae_creator) { @@ -838,7 +836,6 @@ dvr_entry_find_by_event_fuzzy(epg_broadcast_t *e) dvr_entry_t * dvr_entry_find_by_episode(epg_broadcast_t *e) { - // TODO: should be configurable? if (e->episode) { dvr_entry_t *de; epg_broadcast_t *ebc; From 8a3f6c7e1331fbed0414610f769724ab0ead2f8f Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Wed, 20 Jun 2012 16:16:06 +0100 Subject: [PATCH 8/8] Filled out a bit more of the EPG metadata parsing in pyepg and fixed a problem with is_bw being in the wrong object. --- src/epg.c | 20 ++++++++++---------- src/epg.h | 7 ++++--- src/epggrab/pyepg.c | 28 ++++++++++++++++++++++++---- src/epggrab/xmltv.c | 14 +++++++------- src/epggrab/xmltv.h | 3 +++ 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/epg.c b/src/epg.c index 46570ab6..c952569a 100644 --- a/src/epg.c +++ b/src/epg.c @@ -1082,6 +1082,12 @@ int epg_episode_set_genre_str ( epg_episode_t *ee, const char **gstr ) return epg_episode_set_genre(ee, genre, gcnt); } +int epg_episode_set_is_bw ( epg_episode_t *e, uint8_t bw ) +{ + if (!e) return 0; + return _epg_object_set_u8(e, &e->is_bw, bw); +} + static void _epg_episode_add_broadcast ( epg_episode_t *episode, epg_broadcast_t *broadcast ) { @@ -1182,6 +1188,8 @@ htsmsg_t *epg_episode_serialize ( epg_episode_t *episode ) htsmsg_add_str(m, "brand", episode->brand->uri); if (episode->season) htsmsg_add_str(m, "season", episode->season->uri); + if (episode->is_bw) + htsmsg_add_u32(m, "is_bw", 1); return m; } @@ -1226,6 +1234,8 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save ) if ( (eb = epg_brand_find_by_uri(str, 0, NULL)) ) *save |= epg_episode_set_brand(ee, eb); + *save |= epg_episode_set_is_bw(ee, htsmsg_get_u32_or_default(m , "is_bw", 0)); + return ee; } @@ -1461,12 +1471,6 @@ int epg_broadcast_set_is_hd ( epg_broadcast_t *b, uint8_t hd ) return _epg_object_set_u8(b, &b->is_hd, hd); } -int epg_broadcast_set_is_bw ( epg_broadcast_t *b, uint8_t bw ) -{ - if (!b) return 0; - return _epg_object_set_u8(b, &b->is_bw, bw); -} - int epg_broadcast_set_lines ( epg_broadcast_t *b, uint16_t lines ) { if (!b) return 0; @@ -1532,8 +1536,6 @@ 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_widescreen", 1); if (broadcast->lines) htsmsg_add_u32(m, "lines", broadcast->lines); if (broadcast->aspect) @@ -1589,8 +1591,6 @@ epg_broadcast_t *epg_broadcast_deserialize (*ebc)->is_widescreen = 1; if (!htsmsg_get_u32(m, "is_hd", &u32)) (*ebc)->is_hd = 1; - if (!htsmsg_get_u32(m , "is_bw", &u32)) - (*ebc)->is_bw = 1; if (!htsmsg_get_u32(m, "lines", &u32)) (*ebc)->lines = u32; if (!htsmsg_get_u32(m, "aspect", &u32)) diff --git a/src/epg.h b/src/epg.h index a973a9f4..5927b60b 100644 --- a/src/epg.h +++ b/src/epg.h @@ -205,7 +205,9 @@ struct epg_episode // Note: do not use epnum directly! use the accessor routine char *image; ///< Episode image + uint8_t is_bw; ///< Is black and white // TODO: certification and rating + // TODO: film/year LIST_ENTRY(epg_episode) blink; ///< Brand link LIST_ENTRY(epg_episode) slink; ///< Season link @@ -246,6 +248,8 @@ int epg_episode_set_genre_str ( epg_episode_t *e, const char **s ) __attribute__((warn_unused_result)); int epg_episode_set_image ( epg_episode_t *e, const char *i ) __attribute__((warn_unused_result)); +int epg_episode_set_is_bw ( epg_episode_t *b, uint8_t bw ) + __attribute__((warn_unused_result)); // Note: this does NOT strdup the text field void epg_episode_get_epnum @@ -293,7 +297,6 @@ struct epg_broadcast /* Some quality info */ uint8_t is_widescreen; ///< Is widescreen uint8_t is_hd; ///< Is HD - uint8_t is_bw; ///< Is black and white uint16_t lines; ///< Lines in image (quality) uint16_t aspect; ///< Aspect ratio (*100) @@ -326,8 +329,6 @@ int epg_broadcast_set_is_widescreen ( epg_broadcast_t *b, uint8_t ws ) __attribute__((warn_unused_result)); int epg_broadcast_set_is_hd ( epg_broadcast_t *b, uint8_t hd ) __attribute__((warn_unused_result)); -int epg_broadcast_set_is_bw ( epg_broadcast_t *b, uint8_t bw ) - __attribute__((warn_unused_result)); int epg_broadcast_set_lines ( epg_broadcast_t *b, uint16_t lines ) __attribute__((warn_unused_result)); int epg_broadcast_set_aspect ( epg_broadcast_t *b, uint16_t aspect ) diff --git a/src/epggrab/pyepg.c b/src/epggrab/pyepg.c index 72760838..a5e71030 100644 --- a/src/epggrab/pyepg.c +++ b/src/epggrab/pyepg.c @@ -26,6 +26,7 @@ #include "spawn.h" #include "epg.h" #include "epggrab/pyepg.h" +#include "epggrab/xmltv.h" #include "channels.h" static epggrab_channel_tree_t _pyepg_channels; @@ -293,7 +294,9 @@ static int _pyepg_parse_episode ( htsmsg_t *data, epggrab_stats_t *stats ) save |= epg_episode_set_genre(episode, genre, genre_cnt); } - /* TODO: extra metadata */ + /* Content */ + if ((htsmsg_get_map(tags, "blackandwhite"))) + save |= epg_episode_set_is_bw(episode, 1); if (save) stats->episodes.modified++; @@ -304,11 +307,12 @@ static int _pyepg_parse_broadcast ( htsmsg_t *data, channel_t *channel, epggrab_stats_t *stats ) { int save = 0; - htsmsg_t *attr;//, *tags; + htsmsg_t *attr, *tags; epg_episode_t *episode; epg_broadcast_t *broadcast; const char *id, *start, *stop; time_t tm_start, tm_stop; + uint32_t u32; if ( data == NULL || channel == NULL ) return 0; @@ -316,6 +320,7 @@ static int _pyepg_parse_broadcast if ((id = htsmsg_get_str(attr, "episode")) == NULL) return 0; if ((start = htsmsg_get_str(attr, "start")) == NULL ) return 0; if ((stop = htsmsg_get_str(attr, "stop")) == NULL ) return 0; + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0; /* Find episode */ if ((episode = epg_episode_find_by_uri(id, 1, &save)) == NULL) return 0; @@ -333,8 +338,23 @@ static int _pyepg_parse_broadcast /* Set episode */ save |= epg_broadcast_set_episode(broadcast, episode); - /* TODO: extra metadata */ - + /* Quality */ + u32 = htsmsg_get_map(tags, "hd") ? 1 : 0; + save |= epg_broadcast_set_is_hd(broadcast, u32); + u32 = htsmsg_get_map(tags, "widescreen") ? 1 : 0; + save |= epg_broadcast_set_is_widescreen(broadcast, u32); + // TODO: lines, aspect + + /* Accessibility */ + // Note: reuse XMLTV parse code as this is the same + xmltv_parse_accessibility(broadcast, tags); + + /* New/Repeat */ + u32 = htsmsg_get_map(tags, "new") || htsmsg_get_map(tags, "premiere"); + save |= epg_broadcast_set_is_new(broadcast, u32); + u32 = htsmsg_get_map(tags, "repeat") ? 1 : 0; + save |= epg_broadcast_set_is_repeat(broadcast, u32); + if (save) stats->broadcasts.modified++; return save; diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c index fa29e02f..2cfb5298 100644 --- a/src/epggrab/xmltv.c +++ b/src/epggrab/xmltv.c @@ -211,7 +211,7 @@ get_episode_info * job */ static int -parse_vid_quality ( epg_broadcast_t *ebc, htsmsg_t *m ) +parse_vid_quality ( epg_broadcast_t *ebc, epg_episode_t *ee, htsmsg_t *m ) { int save = 0; int hd = 0, lines = 0, aspect = 0; @@ -219,7 +219,7 @@ parse_vid_quality ( epg_broadcast_t *ebc, htsmsg_t *m ) if (!ebc || !m) return 0; if ((str = htsmsg_xml_get_cdata_str(m, "colour"))) - save |= epg_broadcast_set_is_bw(ebc, strcmp(str, "no") ? 0 : 1); + save |= epg_episode_set_is_bw(ee, strcmp(str, "no") ? 0 : 1); if ((str = htsmsg_xml_get_cdata_str(m, "quality"))) { if (strstr(str, "HD")) { hd = 1; @@ -259,8 +259,8 @@ parse_vid_quality ( epg_broadcast_t *ebc, htsmsg_t *m ) /* * Parse accessibility data */ -static int -parse_accessibility ( epg_broadcast_t *ebc, htsmsg_t *m ) +int +xmltv_parse_accessibility ( epg_broadcast_t *ebc, htsmsg_t *m ) { int save = 0; htsmsg_t *tag; @@ -328,16 +328,16 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags, /* Create/Find broadcast */ ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save2); - if ( ebc != NULL ) { + if ( ebc ) { stats->broadcasts.total++; if (save2) stats->broadcasts.created++; save2 |= epg_broadcast_set_episode(ebc, ee); /* Quality metadata */ - save2 |= parse_vid_quality(ebc, htsmsg_get_map(tags, "video")); + save2 |= parse_vid_quality(ebc, ee, htsmsg_get_map(tags, "video")); /* Accessibility */ - save2 |= parse_accessibility(ebc, tags); + save2 |= xmltv_parse_accessibility(ebc, tags); /* Misc */ if (htsmsg_get_map(tags, "previously-shown")) diff --git a/src/epggrab/xmltv.h b/src/epggrab/xmltv.h index 7a2d1bee..104842a7 100644 --- a/src/epggrab/xmltv.h +++ b/src/epggrab/xmltv.h @@ -25,4 +25,7 @@ void xmltv_init ( epggrab_module_list_t *list ); void xmltv_load ( void ); +// reused by pyepg +int xmltv_parse_accessibility ( epg_broadcast_t *ebc, htsmsg_t *m ); + #endif