diff --git a/src/channels.c b/src/channels.c index ad332630..802787a1 100644 --- a/src/channels.c +++ b/src/channels.c @@ -164,6 +164,8 @@ channel_create(const char *name, int number) channel_t *ch, *x; int id; + if (!name || !*name) return NULL; + ch = RB_LAST(&channel_identifier_tree); if(ch == NULL) { id = 1; @@ -330,6 +332,8 @@ channel_rename(channel_t *ch, const char *newname) lock_assert(&global_lock); + if (!newname || !*newname) return 0; + if(channel_find_by_name(newname, 0, 0)) return -1; 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 8e98fa76..ad9cb442 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 && @@ -91,7 +91,7 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e) if(dae->dae_title != NULL && dae->dae_title[0] != '\0') { if(e->episode->title == NULL || regexec(&dae->dae_title_preg, e->episode->title, 0, NULL, 0)) - return 0; + return 0; } // Note: ignore channel test if we allow quality unlocking @@ -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) { @@ -275,7 +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); - htsmsg_add_u32(e, "contenttype",dae->dae_content_type); + htsmsg_add_u32(e, "contenttype",dae->dae_content_type.code); htsmsg_add_str(e, "title", dae->dae_title ?: ""); @@ -389,7 +383,7 @@ autorec_record_update(void *opaque, const char *id, htsmsg_t *values, } } - dae->dae_content_type = htsmsg_get_u32_or_default(values, "contenttype", 0); + 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) { @@ -470,7 +464,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) @@ -503,7 +497,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; @@ -535,7 +530,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 2cf7d2dd..3c7b0214 100644 --- a/src/epg.c +++ b/src/epg.c @@ -343,33 +343,33 @@ int epg_brand_set_season_count ( epg_brand_t *brand, uint16_t count ) static void _epg_brand_add_season ( epg_brand_t *brand, epg_season_t *season ) { - LIST_INSERT_SORTED(&brand->seasons, season, blink, _season_order); _epg_object_getref(brand); _epg_object_set_updated(brand); + LIST_INSERT_SORTED(&brand->seasons, season, blink, _season_order); } static void _epg_brand_rem_season ( epg_brand_t *brand, epg_season_t *season ) { LIST_REMOVE(season, blink); - _epg_object_putref(brand); _epg_object_set_updated(brand); + _epg_object_putref(brand); } static void _epg_brand_add_episode ( epg_brand_t *brand, epg_episode_t *episode ) { - LIST_INSERT_SORTED(&brand->episodes, episode, blink, _episode_order); _epg_object_getref(brand); _epg_object_set_updated(brand); + LIST_INSERT_SORTED(&brand->episodes, episode, blink, _episode_order); } static void _epg_brand_rem_episode ( epg_brand_t *brand, epg_episode_t *episode ) { LIST_REMOVE(episode, blink); - _epg_object_putref(brand); _epg_object_set_updated(brand); + _epg_object_putref(brand); } htsmsg_t *epg_brand_serialize ( epg_brand_t *brand ) @@ -509,17 +509,17 @@ int epg_season_set_brand ( epg_season_t *season, epg_brand_t *brand, int u ) static void _epg_season_add_episode ( epg_season_t *season, epg_episode_t *episode ) { - LIST_INSERT_SORTED(&season->episodes, episode, slink, _episode_order); _epg_object_getref(season); _epg_object_set_updated(season); + LIST_INSERT_SORTED(&season->episodes, episode, slink, _episode_order); } static void _epg_season_rem_episode ( epg_season_t *season, epg_episode_t *episode ) { LIST_REMOVE(episode, slink); - _epg_object_putref(season); _epg_object_set_updated(season); + _epg_object_putref(season); } htsmsg_t *epg_season_serialize ( epg_season_t *season ) @@ -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,69 +765,52 @@ 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 ( epg_episode_t *episode, epg_broadcast_t *broadcast ) { - LIST_INSERT_SORTED(&episode->broadcasts, broadcast, ep_link, _ebc_start_cmp); _epg_object_getref(episode); _epg_object_set_updated(episode); + LIST_INSERT_SORTED(&episode->broadcasts, broadcast, ep_link, _ebc_start_cmp); } static void _epg_episode_rem_broadcast ( epg_episode_t *episode, epg_broadcast_t *broadcast ) { LIST_REMOVE(broadcast, ep_link); - _epg_object_putref(episode); _epg_object_set_updated(episode); + _epg_object_putref(episode); } size_t epg_episode_number_format @@ -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); @@ -1045,6 +1050,7 @@ static epg_broadcast_t *_epg_channel_add_broadcast ret = *bcast; *bcast = NULL; _epg_object_create(ret); + // Note: sets updated _epg_object_getref(ret); /* No change */ @@ -1054,6 +1060,7 @@ static epg_broadcast_t *_epg_channel_add_broadcast /* Extend in time */ } else { ret->stop = (*bcast)->stop; + _epg_object_set_updated(ret); } } @@ -1334,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", @@ -1505,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++ ) { @@ -1517,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; } /* ************************************************************************** @@ -1531,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 */ @@ -1551,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; @@ -1562,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; @@ -1604,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.h b/src/epggrab.h index f4d8b019..ead00f65 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -61,12 +61,18 @@ typedef struct epggrab_stats * Grabber Channels * *************************************************************************/ +/* + * Lists + */ +RB_HEAD(epggrab_channel_tree, epggrab_channel); +typedef struct epggrab_channel_tree epggrab_channel_tree_t; + /* * Grab channel */ typedef struct epggrab_channel { - RB_ENTRY(epggrab_channel) link; ///< Global link + RB_ENTRY(epggrab_channel) link; ///< Global link epggrab_module_t *mod; ///< Linked module char *id; ///< Grabber's ID @@ -74,19 +80,10 @@ typedef struct epggrab_channel char *name; ///< Channel name char *icon; ///< Channel icon int number; ///< Channel number - - char **sname; ///< Service name's - uint16_t *sid; ///< Service ID's - + struct channel *channel; ///< Mapped channel } epggrab_channel_t; -/* - * Channel list structure - */ -RB_HEAD(epggrab_channel_tree, epggrab_channel); -typedef struct epggrab_channel_tree epggrab_channel_tree_t; - /* * Access functions */ @@ -95,11 +92,9 @@ htsmsg_t* epggrab_channel_list ( void ); /* * Mutators */ -int epggrab_channel_set_name ( epggrab_channel_t *ch, const char *name ); -int epggrab_channel_set_icon ( epggrab_channel_t *ch, const char *icon ); -int epggrab_channel_set_number ( epggrab_channel_t *ch, int number ); -int epggrab_channel_set_sname ( epggrab_channel_t *ch, const char **sname ); -int epggrab_channel_set_sid ( epggrab_channel_t *ch, const uint16_t *sid ); +int epggrab_channel_set_name ( epggrab_channel_t *ch, const char *name ); +int epggrab_channel_set_icon ( epggrab_channel_t *ch, const char *icon ); +int epggrab_channel_set_number ( epggrab_channel_t *ch, int number ); /* * Updated/link diff --git a/src/epggrab/channel.c b/src/epggrab/channel.c index abd57633..16bccbad 100644 --- a/src/epggrab/channel.c +++ b/src/epggrab/channel.c @@ -36,39 +36,13 @@ // returns 1 if link made int epggrab_channel_link ( epggrab_channel_t *ec, channel_t *ch ) { - service_t *sv; - int match = 0, i; + int match = 0; if (!ec || !ch) return 0; if (ec->channel) return 0; if (ec->name && !strcmp(ec->name, ch->ch_name)) match = 1; - else { - LIST_FOREACH(sv, &ch->ch_services, s_ch_link) { - if (ec->sid) { - i = 0; - while (ec->sid[i]) { - if (sv->s_dvb_service_id == ec->sid[i]) { - match = 1; - break; - } - i++; - } - } - if (!match && ec->sname) { - i = 0; - while (ec->sname[i]) { - if (!strcmp(ec->sname[i], sv->s_svcname)) { - match = 1; - break; - } - i++; - } - } - if (match) break; - } - } if (match) { tvhlog(LOG_INFO, ec->mod->id, "linking %s to %s", @@ -129,63 +103,6 @@ int epggrab_channel_set_number ( epggrab_channel_t *ec, int number ) return save; } -/* Set service IDs */ -int epggrab_channel_set_sid - ( epggrab_channel_t *ec, const uint16_t *sid ) -{ - int save = 0, i; - if ( !ec || !sid ) return 0; - if (!ec->sid) save = 1; - else { - i = 0; - while ( ec->sid[i] && sid[i] ) { - if ( ec->sid[i] != sid[i] ) break; - i++; - } - if (ec->sid[i] || sid[i]) save = 1; - } - if (save) { - i = 0; - while (ec->sid[i++]); - if (ec->sid) free(ec->sid); - ec->sid = calloc(i, sizeof(uint16_t)); - memcpy(ec->sid, sid, i * sizeof(uint16_t)); - } - return save; -} - -/* Set names */ -int epggrab_channel_set_sname ( epggrab_channel_t *ec, const char **sname ) -{ - int save = 0, i = 0; - if ( !ec || !sname ) return 0; - if (!ec->sname) save = 1; - else { - while ( ec->sname[i] && sname[i] ) { - if (strcmp(ec->sname[i], sname[i])) break; - i++; - } - if (ec->sname[i] || sname[i]) save = 1; - } - if (save) { - if (ec->sname) { - i = 0; - while (ec->sname[i]) - free(ec->sname[i++]); - free(ec->sname); - } - i = 0; - while (sname[i++]); - ec->sname = calloc(i+1, sizeof(char*)); - i = 0; - while (sname[i]) { - ec->sname[i] = strdup(sname[i]); - i++; - } - } - return save; -} - /* Channel settings updated */ void epggrab_channel_updated ( epggrab_channel_t *ec ) { diff --git a/src/epggrab/module.c b/src/epggrab/module.c index bf075455..7217426f 100644 --- a/src/epggrab/module.c +++ b/src/epggrab/module.c @@ -142,9 +142,7 @@ void epggrab_module_parse void epggrab_module_ch_save ( void *_m, epggrab_channel_t *ch ) { - int i; htsmsg_t *m = htsmsg_create_map(); - htsmsg_t *a; epggrab_module_t *mod = _m; if (ch->name) @@ -153,24 +151,6 @@ void epggrab_module_ch_save ( void *_m, epggrab_channel_t *ch ) htsmsg_add_str(m, "icon", ch->icon); if (ch->channel) htsmsg_add_u32(m, "channel", ch->channel->ch_id); - if (ch->sid) { - a = htsmsg_create_list(); - i = 0; - while (ch->sid[i]) { - htsmsg_add_u32(a, NULL, ch->sid[i]); - i++; - } - htsmsg_add_msg(m, "sid", a); - } - if (ch->sname) { - a = htsmsg_create_list(); - i = 0; - while (ch->sname[i]) { - htsmsg_add_str(a, NULL, ch->sname[i]); - i++; - } - htsmsg_add_msg(m, "sname", a); - } if (ch->number) htsmsg_add_u32(m, "number", ch->number); @@ -210,11 +190,9 @@ void epggrab_module_ch_mod ( void *mod, channel_t *ch ) static void _epggrab_module_channel_load ( epggrab_module_t *mod, htsmsg_t *m, const char *id ) { - int save = 0, i; + int save = 0; const char *str; uint32_t u32; - htsmsg_t *a; - htsmsg_field_t *f; epggrab_channel_t *ch = epggrab_channel_find(mod->channels, id, 1, &save, mod); @@ -222,28 +200,6 @@ static void _epggrab_module_channel_load ch->name = strdup(str); if ((str = htsmsg_get_str(m, "icon"))) ch->icon = strdup(str); - if ((a = htsmsg_get_list(m, "sid"))) { - i = 0; - HTSMSG_FOREACH(f, a) i++; - if (i) { - ch->sid = calloc(i+1, sizeof(uint16_t)); - i = 0; - HTSMSG_FOREACH(f, a) { - ch->sid[i++] = (uint16_t)f->hmf_s64; - } - } - } - if ((a = htsmsg_get_list(m, "sname"))) { - i = 0; - HTSMSG_FOREACH(f, a) i++; - if (i) { - ch->sname = calloc(i+1, sizeof(char*)); - i = 0; - HTSMSG_FOREACH(f, a) { - ch->sname[i++] = strdup(f->hmf_str); - } - } - } if(!htsmsg_get_u32(m, "number", &u32)) ch->number = u32; diff --git a/src/epggrab/module/eit.c b/src/epggrab/module/eit.c index b23592bb..a89a382f 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 *egl = NULL; 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,10 @@ 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 ) { + if (!egl) egl = calloc(1, sizeof(epg_genre_list_t)); + epg_genre_list_add_by_eit(egl, *ptr); + } } break; @@ -376,8 +376,8 @@ static int _eit_callback char *uri; uri = epg_hash(title, summary, desc); if (uri) { - ee = epg_episode_find_by_uri(uri, 1, &save2); - save |= epg_broadcast_set_episode(ebc, ee); + if ((ee = epg_episode_find_by_uri(uri, 1, &save2))) + save |= epg_broadcast_set_episode(ebc, ee); free(uri); } } @@ -392,14 +392,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) && egl ) + save |= epg_episode_set_genre(ee, egl); #if TODO_ADD_EXTRA if ( extra ) save |= epg_episode_set_extra(ee, extra); #endif } + + /* Tidy up */ if (extra) free(extra); + if (egl) epg_genre_list_destroy(egl); } /* 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 8793f7d2..621eb743 100644 --- a/src/epggrab/module/pyepg.c +++ b/src/epggrab/module/pyepg.c @@ -55,23 +55,29 @@ 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 ) { int save = 0; epggrab_channel_t *ch; - htsmsg_t *attr, *tags, *e; - htsmsg_field_t *f; + htsmsg_t *attr, *tags; const char *str; uint32_t u32; - const char *sname[11]; - uint16_t sid[11]; - int sid_idx = 0, sname_idx = 0; if ( data == NULL ) return 0; @@ -90,34 +96,6 @@ static int _pyepg_parse_channel ( htsmsg_t *data, epggrab_stats_t *stats ) if ((!htsmsg_xml_get_cdata_u32(tags, "number", &u32))) save |= epggrab_channel_set_number(ch, u32); - HTSMSG_FOREACH(f, tags) { - if (!strcmp(f->hmf_name, "sid")) { - if (sid_idx < 10) { - e = htsmsg_get_map_by_field(f); - if (!htsmsg_get_u32(e, "cdata", &u32)) { - sid[sid_idx] = (uint16_t)u32; - sid_idx++; - } - } - } else if (!strcmp(f->hmf_name, "sname")) { - if (sname_idx < 10) { - e = htsmsg_get_map_by_field(f); - if ((str = htsmsg_get_str(e, "cdata"))) { - sname[sname_idx] = str; - sname_idx++; - } - } - } - } - if (sid_idx) { - sid[sid_idx] = 0; - save |= epggrab_channel_set_sid(ch, sid); - } - if (sname_idx) { - sname[sname_idx] = NULL; - save |= epggrab_channel_set_sname(ch, sname); - } - /* Update */ if (save) { epggrab_channel_updated(ch); @@ -229,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; @@ -294,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 b34fab50..38cbf0e8 100644 --- a/src/epggrab/module/xmltv.c +++ b/src/epggrab/module/xmltv.c @@ -286,6 +286,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 */ @@ -296,12 +314,12 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags, int save = 0, save2 = 0; epg_episode_t *ee; epg_broadcast_t *ebc; + epg_genre_list_t *egl; int sn = 0, sc = 0, en = 0, ec = 0, pn = 0, pc = 0; const char *onscreen = NULL; char *uri; - const char *title = htsmsg_xml_get_cdata_str(tags, "title"); - const char *desc = htsmsg_xml_get_cdata_str(tags, "desc"); - const char *category[2]; + const char *title = htsmsg_xml_get_cdata_str(tags, "title"); + const char *desc = htsmsg_xml_get_cdata_str(tags, "desc"); get_episode_info(tags, &onscreen, &sn, &sc, &en, &ec, &pn, &pc); /* Ignore */ @@ -315,11 +333,12 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags, stats->episodes.total++; if (save) stats->episodes.created++; - 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 edc7d8be..8716c059 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. ' +