Complete update to the EPG genre setup. Mostly this is now achieved using an listable structure (to remove the ugly array implementation) however some code still deals directly with the uint8_t code value.

This commit is contained in:
Adam Sutton 2012-07-06 14:59:04 +01:00
parent c07fd8d59f
commit b361262d6b
14 changed files with 373 additions and 184 deletions

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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)

232
src/epg.c
View file

@ -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)

View file

@ -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);
/* ************************************************************************

View file

@ -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 */

View file

@ -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) {

View file

@ -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 */

View file

@ -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++;

View file

@ -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) {

View file

@ -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();

View file

@ -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'
]);

View file

@ -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 += '<div class="x-epg-desc">' + event.episode + '</div>';
content += '<div class="x-epg-desc">' + event.description + '</div>';
content += '<div class="x-epg-meta">' + event.contentgrp + '</div>';
content += '<div class="x-epg-meta">' + tvheadend.contentGroupLookupName(event.contenttype) + '</div>';
if(event.ext_desc != null)
content += '<div class="x-epg-meta">' + event.ext_desc + '</div>';
@ -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 : "<i>Don't care</i>";
var tag = epgStore.baseParams.tag ?
epgStore.baseParams.tag : "<i>Don't care</i>";
var contentgrp = epgStore.baseParams.contentgrp ?
epgStore.baseParams.contentgrp : "<i>Don't care</i>";
var contenttype = epgStore.baseParams.contenttype ?
epgStore.baseParams.contenttype : "<i>Don't care</i>";
Ext.MessageBox.confirm('Auto Recorder',
'This will create an automatic rule that ' +
@ -488,7 +503,7 @@ tvheadend.epg = function() {
'<div class="x-smallhdr">Title:</div>' + title + '<br>' +
'<div class="x-smallhdr">Channel:</div>' + channel + '<br>' +
'<div class="x-smallhdr">Tag:</div>' + tag + '<br>' +
'<div class="x-smallhdr">Content Group:</div>' + contentgrp + '<br>' +
'<div class="x-smallhdr">Genre:</div>' + contenttype + '<br>' +
'<br>' +
'Currently this will match (and record) ' +
epgStore.getTotalCount() + ' events. ' +