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;
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 */
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);
*/
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,
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 &&
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) {
static htsmsg_t *
autorec_record_build(dvr_autorec_entry_t *dae)
{
- const char *s;
char str[30];
htsmsg_t *e = htsmsg_create_map();
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 ?: "");
}
}
- // 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) {
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)
}
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;
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;
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)
{
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);
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)
{
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);
}
}
- 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);
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);
}
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);
_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;
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)
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");
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);
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;
- }
+ int save = 0;
+ epg_genre_t *g1, *g2;
+
+ /* 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;
}
- 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;
+
+ /* Insert all entries */
+ LIST_FOREACH(g1, genre, link) {
+ save |= epg_genre_list_add(&ee->genre, g1);
}
- return save;
-}
-// 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;
- }
- cnt = 0;
- while (gstr[cnt]) {
- genre[cnt] = epg_genre_find_by_name(gstr[cnt]);
- cnt++;
- }
- 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
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);
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)
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;
*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);
* 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",
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++ ) {
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 )
+{
+ 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 )
{
- int a, b = 0;
- a = (genre >> 4) & 0xF;
- if (full) b = (genre & 0xF);
- return _epg_genre_names[a][b];
+ 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;
}
/* **************************************************************************
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 */
}
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;
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;
}
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)
struct channel_tag;
/*
- * Map types
+ * Map/List types
*/
LIST_HEAD(epg_object_list, epg_object);
RB_HEAD (epg_object_tree, epg_object);
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;
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
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
epg_brand_t *brand; ///< (Grand-)Parent brand
epg_season_t *season; ///< Parent season
epg_broadcast_list_t broadcasts; ///< Broadcast list
-
};
/* Lookup */
__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));
/* 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
* ***********************************************************************/
/* 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);
/* ************************************************************************
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];
/* Process tags */
*title = *summary = *desc = 0;
extra = NULL;
- genre_idx = 0;
hd = ws = bw = ad = st = ds = 0;
while(dllen > 0) {
dtag = ptr[0];
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;
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 */
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) {
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 )
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;
}
/* 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 */
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
*/
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 */
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++;
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 )
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
htsmsg_t *out;
epg_broadcast_t *n;
dvr_entry_t *de;
+ epg_genre_t *g;
out = htsmsg_create_map();
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) {
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");
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;
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);
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)
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();
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',
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'
]);
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) {
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>';
{name: 'start', type: 'date', dateFormat: 'U' /* unix time */},
{name: 'end', type: 'date', dateFormat: 'U' /* unix time */},
{name: 'duration'},
- {name: 'contentgrp'},
+ {name: 'contenttype'},
{name: 'schedstate'}
])
});
renderer: renderText
},{
width: 250,
- id:'contentgrp',
+ id:'contenttype',
header: "Content Type",
- dataIndex: 'contentgrp',
- renderer: renderText
+ dataIndex: 'contenttype',
+ renderer: function(v) {
+ return tvheadend.contentGroupLookupName(v);
+ }
}
]);
function epgQueryClear() {
epgStore.baseParams.channel = null;
epgStore.baseParams.tag = null;
- epgStore.baseParams.contentgrp = null;
+ epgStore.baseParams.contenttype = null;
epgStore.baseParams.title = null;
epgFilterChannels.setValue("");
});
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();
}
});
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 ' +
'<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. ' +