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