diff --git a/docs/html/config_epggrab.html b/docs/html/config_epggrab.html
new file mode 100644
index 00000000..a0d2c0cc
--- /dev/null
+++ b/docs/html/config_epggrab.html
@@ -0,0 +1,58 @@
+
+
+
+ This tab is used to configure EPG grabbing capabilities. TVheadend supports
+ a variety of different EPG grabbing mechanisms. These fall into 3 broad
+ categories, within which there are a variety of specific grabber implementations.
+
+
+
Grabber Types
+
+ - Over the Air (OTA) - these receive EPG data directly from the DVB network. This is often the easiest way to get up and running, and does provide timely updates should scheduling change. However the information isn't always as rich as some of the other grabbers.
+ - Interanl - These are grabbers which can be internally initiated from within TVheadend based on IP based grabbing solutions (see below). This can be a quick way to get richer EPG data where you don't have decent OTA support.
+ - External - These provide the option to run the grabber scripts externally and to pump the data into TVheadend via Unix domain sockets. It provides more complex configurations using things like cronjob's etc.
+
+
+
Grabber Modules
+
+ - EIT - This is a DVB standards compatible EIT grabber. Typically it will
+ retrieve now/next information, though on some networks there may be more
+ extensive data published.
+ - OpenTV - This is a proprietary OTA EPG grabber. Its known to be used on the SKY networks, but others may use it. You need two configuration files to define settings for your particular network, if you don't see yours listed please visit IRC #hts for help.
+ - XMLTV - This is a IP network based scraper, for more information about XMLTV please visit http://www.xmltv.org. To make use of the internal XMLTV grabber you must have tv_find_grabbers installed. If you install new grabbers you will need to restart TVheadend to pick these up asthey're loaded at startup.
+ - PyEPG - This is another IP network based scraper. It currently only supports the Atlas UK system (for which you need a key), but it does provide a very rich EPG data set. For more information see http://github.com/adamsutton/PyEPG.
+
+
+
Configuration options
+
+
+ - Module:
+
- Select which internal grabber to use.
+
+
- Grab interval
+
- Time period between grabs.
+
+
- External interfaces
+
- Check tick boxes for whichever you want to make available, the Path column displays where the unix socket you need to use lives.
+
+
- OTA interfaces
+
- Check tick boxes for whichever you want to use.
+
+
+ Changes to any of these settings must be confirmed by pressing the
+ 'Save configuration' button before taking effect.
+
+
+
Notes
+
+
+ XMLTV/PyEPG - if you are using the internal versions of these modules then
+ you must first configure them (if required) externally as TVHeadend provides
+ no support for this. Once configured though TVheadend will do the rest.
+
+
+
+ Mixed grabbers - Although as much as possible is done to avoid contention, generally speaking using a mixture of grabbers should be avoided (where each grabber updates the same channels). They typically tend to contain differing information which results in them "fighting" over which information is correct and can result in a high level of EPG update messages.
+
+
+
diff --git a/docs/html/config_xmltv.html b/docs/html/config_xmltv.html
deleted file mode 100644
index a47503cd..00000000
--- a/docs/html/config_xmltv.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-

-
-
- This tab is used to configure XML-TV. For more information about XML-TV
- and its use, please visit
- http://www.xmltv.org.
-
-
- Configuration options:
-
- - XML-TV Source
-
- Select which grabber to use.
- When the drop down list is pressed Tvheadend will issue a scan
- for available grabbers on the host system. This result will be
- cached in the web user interface. Thus, if you need to rescan due to
- a newly installed grabber you must reload the web interface.
- (There is no need to restart Tvheadend itself).
-
- When you select a grabber more information will pop up regarding
- further configuration of the grabber.
-
-
- Grab interval
-
- Hours between each grab.
-
-
- Enable grabbing
-
- Uncheck this if you wish to disable grabbing.
-
- Changes to any of these settings must be confirmed by pressing the
- 'Save configuration' button before taking effect.
-
-
diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c
index c94c78ca..8e98fa76 100644
--- a/src/dvr/dvr_autorec.c
+++ b/src/dvr/dvr_autorec.c
@@ -95,10 +95,6 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
}
// Note: ignore channel test if we allow quality unlocking
- // TODO: should we only apply this setting if this is actually
- /// created as a series link?
- // TODO: I could just REMOVE the channel, but I think we probably still
- // want the channel as a "preferred" option
cfg = dvr_config_find_by_name_default(dae->dae_config_name);
if (cfg->dvr_sl_quality_lock)
if(dae->dae_channel != NULL &&
@@ -137,7 +133,7 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
// Note: dae_epnum is unset then all values are 0 and this will
// always return 1
- epg_episode_number_full(e->episode, &epnum);
+ epg_episode_get_epnum(e->episode, &epnum);
if(epg_episode_number_cmp(&dae->dae_epnum, &epnum) < 0)
return 0;
@@ -548,7 +544,6 @@ dvr_autorec_add(const char *config_name,
NULL, NULL, 0, NULL, creator, comment);
}
-/* TODO: configurable brand/series selection */
void dvr_autorec_add_series_link
( const char *dvr_config_name, epg_broadcast_t *event,
const char *creator, const char *comment )
@@ -567,7 +562,7 @@ void dvr_autorec_add_series_link
atime = (t.tm_hour * 60) + t.tm_min;
}
if (cfg->dvr_sl_more_recent) {
- epg_episode_number_full(ee, &epnum);
+ epg_episode_get_epnum(ee, &epnum);
epnump = &epnum;
}
_dvr_autorec_add(dvr_config_name, event->episode->title,
@@ -601,19 +596,15 @@ dvr_autorec_check_event(epg_broadcast_t *e)
void dvr_autorec_check_brand(epg_brand_t *b)
{
-#ifdef TODO_BRAND_UPDATED_SUPPORT
// Note: for the most part this will only be relevant should an episode
// to which a broadcast is linked suddenly get added to a new brand
// this is pretty damn unlikely!
-#endif
}
void dvr_autorec_check_season(epg_season_t *s)
{
-#ifdef TODO_SEASON_SUPPORT
// Note: I guess new episodes might have been added, but again its likely
// this will already have been picked up by the check_event call
-#endif
}
/**
diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c
index 40378ffb..b9a7e371 100644
--- a/src/dvr/dvr_db.c
+++ b/src/dvr/dvr_db.c
@@ -369,7 +369,6 @@ dvr_entry_create_by_event(const char *config_name,
static int _dvr_duplicate_event ( epg_broadcast_t *e )
{
- // TODO: include other searches?
dvr_entry_t *de;
LIST_FOREACH(de, &dvrentries, de_global_link) {
if (de->de_bcast && (de->de_bcast->episode == e->episode)) return 1;
@@ -386,7 +385,6 @@ dvr_entry_create_by_autorec(epg_broadcast_t *e, dvr_autorec_entry_t *dae)
char buf[200];
/* Dup detection */
- // TODO: need to allow overrides
if (_dvr_duplicate_event(e)) return;
if(dae->dae_creator) {
@@ -838,7 +836,6 @@ dvr_entry_find_by_event_fuzzy(epg_broadcast_t *e)
dvr_entry_t *
dvr_entry_find_by_episode(epg_broadcast_t *e)
{
- // TODO: should be configurable?
if (e->episode) {
dvr_entry_t *de;
epg_broadcast_t *ebc;
diff --git a/src/dvr/mkmux.c b/src/dvr/mkmux.c
index faaa7477..37fbc750 100644
--- a/src/dvr/mkmux.c
+++ b/src/dvr/mkmux.c
@@ -501,12 +501,6 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
if(ch)
addtag(q, build_tag_string("TVCHANNEL", ch->ch_name, 0, NULL));
-#if TODO_EP_NUMBER_ONSCREEN
- if(ee && ee->onscreen)
- addtag(q, build_tag_string("SYNOPSIS",
- ee->onscreen, 0, NULL));
-#endif
-
if(de && de->de_desc)
addtag(q, build_tag_string("SUMMARY", de->de_desc, 0, NULL));
else if (ee && ee->description)
@@ -515,15 +509,20 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
addtag(q, build_tag_string("SUMMARY", ee->summary, 0, NULL));
if (ee) {
- if(ee->number)
- addtag(q, build_tag_int("PART_NUMBER", ee->number,
+ epg_episode_num_t num;
+ epg_episode_get_epnum(ee, &num);
+ if(num.e_num)
+ addtag(q, build_tag_int("PART_NUMBER", num.e_num,
0, NULL));
- if(ee->season && ee->season->number)
- addtag(q, build_tag_int("PART_NUMBER", ee->season->number,
+ if(num.s_num)
+ addtag(q, build_tag_int("PART_NUMBER", num.s_num,
60, "SEASON"));
- if(ee->part_number)
- addtag(q, build_tag_int("PART_NUMBER", ee->part_number,
+ if(num.p_num)
+ addtag(q, build_tag_int("PART_NUMBER", num.p_num,
40, "PART"));
+ if (num.text)
+ addtag(q, build_tag_string("SYNOPSIS",
+ num.text, 0, NULL));
}
return q;
diff --git a/src/epg.c b/src/epg.c
index 35056a3b..12f7010f 100644
--- a/src/epg.c
+++ b/src/epg.c
@@ -80,16 +80,23 @@ static int _season_order ( const void *_a, const void *_b )
return a->number - b->number;
}
+// Note: this will do nothing with text episode numbering
static int _episode_order ( const void *_a, const void *_b )
{
- int r;
+ int r, as, bs;
const epg_episode_t *a = (const epg_episode_t*)_a;
const epg_episode_t *b = (const epg_episode_t*)_b;
- r = _season_order(a->season, b->season);
+ if (!a) return -1;
+ if (!b) return 1;
+ if (a->season) as = a->season->number;
+ else as = a->epnum.s_num;
+ if (b->season) bs = b->season->number;
+ else bs = b->epnum.s_num;
+ r = as - bs;
if (r) return r;
- if (!a || !a->number) return 1;
- if (!b || !b->number) return -1;
- return a->number - b->number;
+ r = a->epnum.e_num - b->epnum.e_num;
+ if (r) return r;
+ return a->epnum.p_num - b->epnum.p_num;
}
/* **************************************************************************
@@ -105,7 +112,7 @@ static void _epg_event_deserialize ( htsmsg_t *c, epggrab_stats_t *stats )
uint32_t e_start = 0;
uint32_t e_stop = 0;
uint32_t u32;
- const char *title, *desc;
+ const char *title, *desc, *str;
char *uri;
int save = 0;
@@ -138,8 +145,10 @@ static void _epg_event_deserialize ( htsmsg_t *c, epggrab_stats_t *stats )
save |= epg_episode_set_number(ee, u32);
if (!htsmsg_get_u32(c, "part", &u32))
save |= epg_episode_set_part(ee, u32, 0);
- // TODO: season number!
- // TODO: onscreen
+ if (!htsmsg_get_u32(c, "season", &u32))
+ ee->epnum.s_num = u32;
+ if ((str = htsmsg_get_str(c, "epname")))
+ ee->epnum.text = strdup(str);
/* Set episode */
save |= epg_broadcast_set_episode(ebc, ee);
@@ -255,8 +264,6 @@ void epg_init ( void )
memset(&stats, 0, sizeof(stats));
while ( remain > 4 ) {
- // TODO: would be nice if htsmsg_binary handled this for us!
-
/* Get message length */
int msglen = (rp[0] << 24) | (rp[1] << 16) | (rp[2] << 8) | rp[3];
remain -= 4;
@@ -378,8 +385,9 @@ static void _epg_object_putref ( epg_object_t *eo )
if (!eo->refcount) eo->destroy(eo);
}
-static void _epg_object_set_updated ( epg_object_t *eo )
+static void _epg_object_set_updated ( void *p )
{
+ epg_object_t *eo = (epg_object_t*)p;
if (!eo->_updated) {
eo->_updated = 1;
LIST_INSERT_HEAD(&epg_object_updated, eo, up_link);
@@ -470,6 +478,30 @@ static int _epg_object_set_str
return save;
}
+static int _epg_object_set_u8
+ ( void *o, uint8_t *old, const uint8_t new )
+{
+ int save = 0;
+ if ( *old != new ) {
+ *old = new;
+ _epg_object_set_updated(o);
+ save = 1;
+ }
+ return save;
+}
+
+static int _epg_object_set_u16
+ ( void *o, uint16_t *old, const uint16_t new )
+{
+ int save = 0;
+ if ( *old != new ) {
+ *old = new;
+ _epg_object_set_updated(o);
+ save = 1;
+ }
+ return save;
+}
+
/* **************************************************************************
* Brand
* *************************************************************************/
@@ -792,6 +824,53 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save )
* Episode
* *************************************************************************/
+static htsmsg_t *epg_episode_num_serialize ( epg_episode_num_t *num )
+{
+ htsmsg_t *m;
+ if (!num) return NULL;
+ m = htsmsg_create_map();
+ if (num->e_num)
+ htsmsg_add_u32(m, "e_num", num->e_num);
+ if (num->e_cnt)
+ htsmsg_add_u32(m, "e_cnt", num->e_cnt);
+ if (num->s_num)
+ htsmsg_add_u32(m, "s_num", num->e_num);
+ if (num->s_cnt)
+ htsmsg_add_u32(m, "s_cnt", num->e_cnt);
+ if (num->p_num)
+ htsmsg_add_u32(m, "p_num", num->e_num);
+ if (num->p_cnt)
+ htsmsg_add_u32(m, "p_cnt", num->e_cnt);
+ if (num->text)
+ htsmsg_add_str(m, "text", num->text);
+ return m;
+}
+
+static epg_episode_num_t *epg_episode_num_deserialize
+ ( htsmsg_t *m, epg_episode_num_t *num )
+{
+ const char *str;
+ uint32_t u32;
+ if (!m) return NULL;
+ if (!num) num = calloc(1, sizeof(epg_episode_num_t));
+ if (!htsmsg_get_u32(m, "e_num", &u32))
+ num->e_num = u32;
+ if (!htsmsg_get_u32(m, "e_cnt", &u32))
+ num->e_cnt = u32;
+ if (!htsmsg_get_u32(m, "s_num", &u32))
+ num->s_num = u32;
+ if (!htsmsg_get_u32(m, "s_cnt", &u32))
+ num->s_cnt = u32;
+ if (!htsmsg_get_u32(m, "p_num", &u32))
+ num->p_num = u32;
+ if (!htsmsg_get_u32(m, "p_cnt", &u32))
+ num->p_cnt = u32;
+ if ((str = htsmsg_get_str(m, "text")))
+ num->text = strdup(str);
+
+ return num;
+}
+
static void _epg_episode_destroy ( epg_object_t *eo )
{
epg_episode_t *ee = (epg_episode_t*)eo;
@@ -814,6 +893,7 @@ static void _epg_episode_destroy ( epg_object_t *eo )
if (ee->description) free(ee->description);
if (ee->genre) free(ee->genre);
if (ee->image) free(ee->image);
+ if (ee->epnum.text) free(ee->epnum.text);
free(ee);
}
@@ -876,8 +956,8 @@ int epg_episode_set_number ( epg_episode_t *episode, uint16_t number )
{
int save = 0;
if ( !episode || !number ) return 0;
- if ( episode->number != number ) {
- episode->number = number;
+ if ( episode->epnum.e_num != number ) {
+ episode->epnum.e_num = number;
_epg_object_set_updated((epg_object_t*)episode);
save = 1;
}
@@ -888,19 +968,40 @@ int epg_episode_set_part ( epg_episode_t *episode, uint16_t part, uint16_t count
{
int save = 0;
if ( !episode || !part ) return 0;
- if ( episode->part_number != part ) {
- episode->part_number = part;
+ if ( episode->epnum.p_num != part ) {
+ episode->epnum.p_num = part;
_epg_object_set_updated((epg_object_t*)episode);
save = 1;
}
- if ( count && episode->part_count != count ) {
- episode->part_count = count;
+ if ( count && episode->epnum.p_cnt != count ) {
+ episode->epnum.p_cnt = count;
_epg_object_set_updated((epg_object_t*)episode);
save = 1;
}
return save;
}
+int epg_episode_set_epnum ( epg_episode_t *episode, epg_episode_num_t *num )
+{
+ int save = 0;
+ if (!episode || !num || (!num->e_num && !num->text)) return 0;
+ if ( episode->epnum.e_num != num->e_num ) save = 1;
+ else if ( episode->epnum.e_cnt != num->e_cnt ) save = 1;
+ else if ( episode->epnum.s_num != num->s_num ) save = 1;
+ else if ( episode->epnum.s_cnt != num->s_cnt ) save = 1;
+ else if ( episode->epnum.p_num != num->p_num ) save = 1;
+ else if ( episode->epnum.p_cnt != num->p_cnt ) save = 1;
+ else if ( !episode->epnum.text ||
+ (num->text && strcmp(num->text, episode->epnum.text)) ) save = 1;
+ if (save) {
+ if (episode->epnum.text) free(episode->epnum.text);
+ episode->epnum = *num;
+ if (episode->epnum.text) strdup(episode->epnum.text);
+ save = 1;
+ }
+ return save;
+}
+
int epg_episode_set_brand ( epg_episode_t *episode, epg_brand_t *brand )
{
int save = 0;
@@ -981,6 +1082,12 @@ int epg_episode_set_genre_str ( epg_episode_t *ee, const char **gstr )
return epg_episode_set_genre(ee, genre, gcnt);
}
+int epg_episode_set_is_bw ( epg_episode_t *e, uint8_t bw )
+{
+ if (!e) return 0;
+ return _epg_object_set_u8(e, &e->is_bw, bw);
+}
+
static void _epg_episode_add_broadcast
( epg_episode_t *episode, epg_broadcast_t *broadcast )
{
@@ -1005,7 +1112,7 @@ size_t epg_episode_number_format
size_t i = 0;
if (!episode) return 0;
epg_episode_num_t num;
- epg_episode_number_full(episode, &num);
+ epg_episode_get_epnum(episode, &num);
if ( num.e_num ) {
if (pre) i += snprintf(&buf[i], len-i, "%s", pre);
if ( sfmt && num.s_num ) {
@@ -1017,17 +1124,17 @@ size_t epg_episode_number_format
i += snprintf(&buf[i], len-i, efmt, num.e_num);
if ( cfmt && num.e_cnt )
i+= snprintf(&buf[i], len-i, cfmt, num.e_cnt);
+ } else if ( num.text ) {
+ strncpy(buf, num.text, len);
+ i = MAX(strlen(num.text), len);
}
return i;
}
-void epg_episode_number_full ( epg_episode_t *ee, epg_episode_num_t *num )
+void epg_episode_get_epnum ( epg_episode_t *ee, epg_episode_num_t *num )
{
if (!ee || !num) return;
- memset(num, 0, sizeof(epg_episode_num_t));
- num->e_num = ee->number;
- num->p_num = ee->part_number;
- num->p_cnt = ee->part_count;
+ *num = ee->epnum;
if (ee->season) {
num->e_cnt = ee->season->episode_count;
num->s_num = ee->season->number;
@@ -1048,18 +1155,17 @@ int epg_episode_number_cmp ( epg_episode_num_t *a, epg_episode_num_t *b )
}
}
+
+// WIBNI: this could do with soem proper matching, maybe some form of
+// fuzzy string match. I did try a few things, but none of them
+// were very reliable.
int epg_episode_fuzzy_match
( epg_episode_t *episode, const char *uri, const char *title,
const char *summary, const char *description )
{
- // TODO: this is pretty noddy and likely to fail!
- // hence the reason I don't recommend mixing external grabbers and EIT
if ( !episode ) return 0;
if ( uri && episode->uri && !strcmp(episode->uri, uri) ) return 1;
if ( title && episode->title && (strstr(title, episode->title) || strstr(episode->title, title)) ) return 1;
- // TODO: could we do fuzzy string matching on the description/summary
- // : there are a few algorithms that might work, but some early testing
- // : suggested it wasn't clear cut enough to make sensible decisions.
return 0;
}
@@ -1077,16 +1183,13 @@ htsmsg_t *epg_episode_serialize ( epg_episode_t *episode )
htsmsg_add_str(m, "summary", episode->summary);
if (episode->description)
htsmsg_add_str(m, "description", episode->description);
- if (episode->number)
- htsmsg_add_u32(m, "number", episode->number);
- if (episode->part_count && episode->part_count) {
- htsmsg_add_u32(m, "part-number", episode->part_number);
- htsmsg_add_u32(m, "part-count", episode->part_count);
- }
+ htsmsg_add_msg(m, "epnum", epg_episode_num_serialize(&episode->epnum));
if (episode->brand)
htsmsg_add_str(m, "brand", episode->brand->uri);
if (episode->season)
htsmsg_add_str(m, "season", episode->season->uri);
+ if (episode->is_bw)
+ htsmsg_add_u32(m, "is_bw", 1);
return m;
}
@@ -1098,6 +1201,8 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
epg_brand_t *eb;
uint32_t u32, u32a;
const char *str;
+ epg_episode_num_t num;
+ htsmsg_t *sub;
if ( !_epg_object_deserialize(m, *skel) ) return NULL;
if ( !(ee = epg_episode_find_by_uri((*skel)->uri, create, save)) ) return NULL;
@@ -1110,6 +1215,12 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
*save |= epg_episode_set_summary(ee, str);
if ( (str = htsmsg_get_str(m, "description")) )
*save |= epg_episode_set_description(ee, str);
+ if ( (sub = htsmsg_get_map(m, "epnum")) ) {
+ epg_episode_num_deserialize(sub, &num);
+ *save |= epg_episode_set_epnum(ee, &num);
+ if (num.text) free(num.text);
+ }
+ // Note: retained for compat
if ( !htsmsg_get_u32(m, "number", &u32) )
*save |= epg_episode_set_number(ee, u32);
if ( !htsmsg_get_u32(m, "part-number", &u32) &&
@@ -1123,6 +1234,8 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
if ( (eb = epg_brand_find_by_uri(str, 0, NULL)) )
*save |= epg_episode_set_brand(ee, eb);
+ *save |= epg_episode_set_is_bw(ee, htsmsg_get_u32_or_default(m , "is_bw", 0));
+
return ee;
}
@@ -1313,8 +1426,8 @@ epg_broadcast_t* epg_broadcast_find_by_time
epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, channel_t *ch )
{
- // TODO: channel left in for now in case I decide to change implementation
- // to simplify the search!
+ // Note: I have left channel_t param, just in case I decide to change
+ // to use it for shorter search
return (epg_broadcast_t*)_epg_object_find_by_id(id);
}
@@ -1346,6 +1459,60 @@ int epg_broadcast_set_episode
return save;
}
+int epg_broadcast_set_is_widescreen ( epg_broadcast_t *b, uint8_t ws )
+{
+ if (!b) return 0;
+ return _epg_object_set_u8(b, &b->is_widescreen, ws);
+}
+
+int epg_broadcast_set_is_hd ( epg_broadcast_t *b, uint8_t hd )
+{
+ if (!b) return 0;
+ return _epg_object_set_u8(b, &b->is_hd, hd);
+}
+
+int epg_broadcast_set_lines ( epg_broadcast_t *b, uint16_t lines )
+{
+ if (!b) return 0;
+ return _epg_object_set_u16(b, &b->lines, lines);
+}
+
+int epg_broadcast_set_aspect ( epg_broadcast_t *b, uint16_t aspect )
+{
+ if (!b) return 0;
+ return _epg_object_set_u16(b, &b->aspect, aspect);
+}
+
+int epg_broadcast_set_is_deafsigned ( epg_broadcast_t *b, uint8_t ds )
+{
+ if (!b) return 0;
+ return _epg_object_set_u8(b, &b->is_deafsigned, ds);
+}
+
+int epg_broadcast_set_is_subtitled ( epg_broadcast_t *b, uint8_t st )
+{
+ if (!b) return 0;
+ return _epg_object_set_u8(b, &b->is_subtitled, st);
+}
+
+int epg_broadcast_set_is_audio_desc ( epg_broadcast_t *b, uint8_t ad )
+{
+ if (!b) return 0;
+ return _epg_object_set_u8(b, &b->is_audio_desc, ad);
+}
+
+int epg_broadcast_set_is_new ( epg_broadcast_t *b, uint8_t n )
+{
+ if (!b) return 0;
+ return _epg_object_set_u8(b, &b->is_new, n);
+}
+
+int epg_broadcast_set_is_repeat ( epg_broadcast_t *b, uint8_t r )
+{
+ if (!b) return 0;
+ return _epg_object_set_u8(b, &b->is_repeat, r);
+}
+
epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast )
{
if ( !broadcast ) return NULL;
@@ -1365,6 +1532,24 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
htsmsg_add_u32(m, "channel", broadcast->channel->ch_id);
if (broadcast->dvb_eid)
htsmsg_add_u32(m, "dvb_eid", broadcast->dvb_eid);
+ if (broadcast->is_widescreen)
+ htsmsg_add_u32(m, "is_widescreen", 1);
+ if (broadcast->is_hd)
+ htsmsg_add_u32(m, "is_hd", 1);
+ if (broadcast->lines)
+ htsmsg_add_u32(m, "lines", broadcast->lines);
+ if (broadcast->aspect)
+ htsmsg_add_u32(m, "aspect", broadcast->aspect);
+ if (broadcast->is_deafsigned)
+ htsmsg_add_u32(m, "is_deafsigned", 1);
+ if (broadcast->is_subtitled)
+ htsmsg_add_u32(m, "is_subtitled", 1);
+ if (broadcast->is_audio_desc)
+ htsmsg_add_u32(m, "is_audio_desc", 1);
+ if (broadcast->is_new)
+ htsmsg_add_u32(m, "is_new", 1);
+ if (broadcast->is_repeat)
+ htsmsg_add_u32(m, "is_repeat", 1);
return m;
}
@@ -1376,7 +1561,7 @@ epg_broadcast_t *epg_broadcast_deserialize
epg_broadcast_t *ret, **ebc = _epg_broadcast_skel();
epg_episode_t *ee;
const char *str;
- uint32_t chid, eid, start, stop;
+ uint32_t chid, eid, start, stop, u32;
if ( htsmsg_get_u32(m, "start", &start) ) return NULL;
if ( htsmsg_get_u32(m, "stop", &stop) ) return NULL;
@@ -1401,6 +1586,26 @@ epg_broadcast_t *epg_broadcast_deserialize
ch = channel_find_by_identifier(chid);
}
+ /* Get metadata */
+ if (!htsmsg_get_u32(m, "is_widescreen", &u32))
+ (*ebc)->is_widescreen = 1;
+ if (!htsmsg_get_u32(m, "is_hd", &u32))
+ (*ebc)->is_hd = 1;
+ if (!htsmsg_get_u32(m, "lines", &u32))
+ (*ebc)->lines = u32;
+ if (!htsmsg_get_u32(m, "aspect", &u32))
+ (*ebc)->aspect = u32;
+ if (!htsmsg_get_u32(m, "is_deafsigned", &u32))
+ (*ebc)->is_deafsigned = 1;
+ if (!htsmsg_get_u32(m, "is_subtitled", &u32))
+ (*ebc)->is_subtitled = 1;
+ if (!htsmsg_get_u32(m, "is_audio_desc", &u32))
+ (*ebc)->is_audio_desc = 1;
+ if (!htsmsg_get_u32(m, "is_new", &u32))
+ (*ebc)->is_new = 1;
+ if (!htsmsg_get_u32(m, "is_repeat", &u32))
+ (*ebc)->is_repeat = 1;
+
/* Add to channel */
if ( ch ) {
ret = _epg_channel_add_broadcast(ch, ebc, create, save);
diff --git a/src/epg.h b/src/epg.h
index defa092d..5927b60b 100644
--- a/src/epg.h
+++ b/src/epg.h
@@ -181,12 +181,13 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save );
*/
typedef struct epg_episode_num
{
- uint16_t s_num; ///< Series number
- uint16_t s_cnt; ///< Series count
- uint16_t e_num; ///< Episode number
- uint16_t e_cnt; ///< Episode count
- uint16_t p_num; ///< Part number
- uint16_t p_cnt; ///< Part count
+ uint16_t s_num; ///< Series number
+ uint16_t s_cnt; ///< Series count
+ uint16_t e_num; ///< Episode number
+ uint16_t e_cnt; ///< Episode count
+ uint16_t p_num; ///< Part number
+ uint16_t p_cnt; ///< Part count
+ char *text; ///< Arbitary text description of episode num
} epg_episode_num_t;
/* Object */
@@ -200,11 +201,14 @@ struct epg_episode
char *description; ///< An extended description
uint8_t *genre; ///< Episode genre(s)
int genre_cnt; ///< Genre count
- uint16_t number; ///< The episode number
- uint16_t part_number; ///< For multipart episodes
- uint16_t part_count; ///< For multipart episodes
+ epg_episode_num_t epnum; ///< Episode numbering
+ // Note: do not use epnum directly! use the accessor routine
char *image; ///< Episode image
+ uint8_t is_bw; ///< Is black and white
+ // TODO: certification and rating
+ // TODO: film/year
+
LIST_ENTRY(epg_episode) blink; ///< Brand link
LIST_ENTRY(epg_episode) slink; ///< Season link
epg_brand_t *brand; ///< (Grand-)Parent brand
@@ -229,11 +233,11 @@ int epg_episode_set_description ( epg_episode_t *e, const char *description )
__attribute__((warn_unused_result));
int epg_episode_set_number ( epg_episode_t *e, uint16_t number )
__attribute__((warn_unused_result));
-int epg_episode_set_onscreen ( epg_episode_t *e, const char *onscreen )
- __attribute__((warn_unused_result));
int epg_episode_set_part ( epg_episode_t *e,
uint16_t number, uint16_t count )
__attribute__((warn_unused_result));
+int epg_episode_set_epnum ( epg_episode_t *e, epg_episode_num_t *num )
+ __attribute__((warn_unused_result));
int epg_episode_set_brand ( epg_episode_t *e, epg_brand_t *b )
__attribute__((warn_unused_result));
int epg_episode_set_season ( epg_episode_t *e, epg_season_t *s )
@@ -244,7 +248,12 @@ int epg_episode_set_genre_str ( epg_episode_t *e, const char **s )
__attribute__((warn_unused_result));
int epg_episode_set_image ( epg_episode_t *e, const char *i )
__attribute__((warn_unused_result));
+int epg_episode_set_is_bw ( epg_episode_t *b, uint8_t bw )
+ __attribute__((warn_unused_result));
+// Note: this does NOT strdup the text field
+void epg_episode_get_epnum
+ ( epg_episode_t *e, epg_episode_num_t *epnum );
/* EpNum format helper */
// output string will be:
// if (episode_num)
@@ -260,8 +269,6 @@ size_t epg_episode_number_format
const char *pre, const char *sfmt,
const char *sep, const char *efmt,
const char *cfmt );
-void epg_episode_number_full
- ( epg_episode_t *e, epg_episode_num_t *epnum );
int epg_episode_number_cmp
( epg_episode_num_t *a, epg_episode_num_t *b );
@@ -318,6 +325,24 @@ epg_broadcast_t *epg_broadcast_find_by_eid ( int eid, struct channel *ch );
/* Mutators */
int epg_broadcast_set_episode ( epg_broadcast_t *b, epg_episode_t *e )
__attribute__((warn_unused_result));
+int epg_broadcast_set_is_widescreen ( epg_broadcast_t *b, uint8_t ws )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_is_hd ( epg_broadcast_t *b, uint8_t hd )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_lines ( epg_broadcast_t *b, uint16_t lines )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_aspect ( epg_broadcast_t *b, uint16_t aspect )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_is_deafsigned ( epg_broadcast_t *b, uint8_t ds )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_is_subtitled ( epg_broadcast_t *b, uint8_t st )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_is_audio_desc ( epg_broadcast_t *b, uint8_t ad )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_is_new ( epg_broadcast_t *b, uint8_t n )
+ __attribute__((warn_unused_result));
+int epg_broadcast_set_is_repeat ( epg_broadcast_t *b, uint8_t r )
+ __attribute__((warn_unused_result));
/* Accessors */
epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *b );
@@ -361,7 +386,7 @@ typedef struct epg_query_result {
void epg_query_free(epg_query_result_t *eqr);
/* Sorting */
-// TODO: comparator function input?
+// WIBNI: might be useful to have a user defined comparator function
void epg_query_sort(epg_query_result_t *eqr);
/* Query routines */
diff --git a/src/epggrab.c b/src/epggrab.c
index c36b7bbb..62948eae 100644
--- a/src/epggrab.c
+++ b/src/epggrab.c
@@ -100,8 +100,6 @@ static void _epggrab_module_grab ( epggrab_module_t *mod )
/*
* Socket handler
- *
- * TODO: could make this threaded to allow multiple simultaneous inputs
*/
static void _epggrab_socket_handler ( epggrab_module_t *mod, int s )
{
@@ -170,10 +168,6 @@ static void* _epggrab_internal_thread ( void* p )
/*
* External (socket) grab thread
- *
- * TODO: I could common all of this up and have a single thread
- * servicing all the available sockets, but we're unlikely to
- * have a massive number of modules enabled anyway!
*/
static void *_epggrab_socket_thread ( void *p )
{
@@ -202,7 +196,6 @@ static int _ch_id_cmp ( void *a, void *b )
((epggrab_channel_t*)b)->id);
}
-// TODO: add other matches
static int _ch_link ( epggrab_channel_t *ec, channel_t *ch )
{
service_t *sv;
@@ -249,7 +242,6 @@ static int _ch_link ( epggrab_channel_t *ec, channel_t *ch )
return match;
}
-// TODO: could use TCP socket to allow remote access
int epggrab_module_enable_socket ( epggrab_module_t *mod, uint8_t e )
{
pthread_t tid;
@@ -343,7 +335,6 @@ htsmsg_t *epggrab_module_trans_xml
return ret;
}
-// TODO: add extra metadata
void epggrab_module_channel_save
( epggrab_module_t *mod, epggrab_channel_t *ch )
{
@@ -373,6 +364,8 @@ void epggrab_module_channel_save
}
htsmsg_add_msg(m, "sname", a);
}
+ if (ch->number)
+ htsmsg_add_u32(m, "number", ch->number);
hts_settings_save(m, "epggrab/%s/channels/%s", mod->id, ch->id);
}
@@ -413,6 +406,8 @@ static void epggrab_module_channel_load
i++;
}
}
+ if(!htsmsg_get_u32(m, "number", &u32))
+ ch->number = u32;
if (!htsmsg_get_u32(m, "channel", &u32))
ch->channel = channel_find_by_identifier(u32);
@@ -508,7 +503,6 @@ int epggrab_channel_set_name ( epggrab_channel_t *ec, const char *name )
return save;
}
-// TODO: what a mess!
int epggrab_channel_set_sid
( epggrab_channel_t *ec, const uint16_t *sid, int num )
{
@@ -535,7 +529,6 @@ int epggrab_channel_set_sid
return save;
}
-// TODO: what a mess!
int epggrab_channel_set_sname ( epggrab_channel_t *ec, const char **sname )
{
int save = 0, i = 0;
@@ -595,9 +588,6 @@ int epggrab_channel_set_number ( epggrab_channel_t *ec, int number )
return save;
}
-// TODO: add other match critera
-// TODO: add additional metadata updates
-// TODO: add configurable updating
void epggrab_channel_link ( epggrab_channel_t *ec )
{
channel_t *ch;
@@ -618,8 +608,6 @@ void epggrab_channel_updated ( epggrab_channel_t *ec )
epggrab_module_channel_save(ec->mod, ec);
}
-// TODO: currently lists ALL channels from ALL active modules
-// TODO: won't work if channels are handled internally within module!
htsmsg_t *epggrab_channel_list ( void )
{
char name[100];
diff --git a/src/epggrab.h b/src/epggrab.h
index 88e3bb15..59a35897 100644
--- a/src/epggrab.h
+++ b/src/epggrab.h
@@ -48,7 +48,6 @@ typedef struct epggrab_channel
char *icon; ///< Channel icon
int number; ///< Channel number
- // TODO: I think we might need a list of channels!
struct channel *channel; ///< Mapped channel
} epggrab_channel_t;
diff --git a/src/epggrab/pyepg.c b/src/epggrab/pyepg.c
index 72760838..a5e71030 100644
--- a/src/epggrab/pyepg.c
+++ b/src/epggrab/pyepg.c
@@ -26,6 +26,7 @@
#include "spawn.h"
#include "epg.h"
#include "epggrab/pyepg.h"
+#include "epggrab/xmltv.h"
#include "channels.h"
static epggrab_channel_tree_t _pyepg_channels;
@@ -293,7 +294,9 @@ static int _pyepg_parse_episode ( htsmsg_t *data, epggrab_stats_t *stats )
save |= epg_episode_set_genre(episode, genre, genre_cnt);
}
- /* TODO: extra metadata */
+ /* Content */
+ if ((htsmsg_get_map(tags, "blackandwhite")))
+ save |= epg_episode_set_is_bw(episode, 1);
if (save) stats->episodes.modified++;
@@ -304,11 +307,12 @@ static int _pyepg_parse_broadcast
( htsmsg_t *data, channel_t *channel, epggrab_stats_t *stats )
{
int save = 0;
- htsmsg_t *attr;//, *tags;
+ htsmsg_t *attr, *tags;
epg_episode_t *episode;
epg_broadcast_t *broadcast;
const char *id, *start, *stop;
time_t tm_start, tm_stop;
+ uint32_t u32;
if ( data == NULL || channel == NULL ) return 0;
@@ -316,6 +320,7 @@ static int _pyepg_parse_broadcast
if ((id = htsmsg_get_str(attr, "episode")) == NULL) return 0;
if ((start = htsmsg_get_str(attr, "start")) == NULL ) return 0;
if ((stop = htsmsg_get_str(attr, "stop")) == NULL ) return 0;
+ if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
/* Find episode */
if ((episode = epg_episode_find_by_uri(id, 1, &save)) == NULL) return 0;
@@ -333,8 +338,23 @@ static int _pyepg_parse_broadcast
/* Set episode */
save |= epg_broadcast_set_episode(broadcast, episode);
- /* TODO: extra metadata */
-
+ /* Quality */
+ u32 = htsmsg_get_map(tags, "hd") ? 1 : 0;
+ save |= epg_broadcast_set_is_hd(broadcast, u32);
+ u32 = htsmsg_get_map(tags, "widescreen") ? 1 : 0;
+ save |= epg_broadcast_set_is_widescreen(broadcast, u32);
+ // TODO: lines, aspect
+
+ /* Accessibility */
+ // Note: reuse XMLTV parse code as this is the same
+ xmltv_parse_accessibility(broadcast, tags);
+
+ /* New/Repeat */
+ u32 = htsmsg_get_map(tags, "new") || htsmsg_get_map(tags, "premiere");
+ save |= epg_broadcast_set_is_new(broadcast, u32);
+ u32 = htsmsg_get_map(tags, "repeat") ? 1 : 0;
+ save |= epg_broadcast_set_is_repeat(broadcast, u32);
+
if (save) stats->broadcasts.modified++;
return save;
diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c
index 3cabdc9e..2cfb5298 100644
--- a/src/epggrab/xmltv.c
+++ b/src/epggrab/xmltv.c
@@ -204,6 +204,85 @@ get_episode_info
}
}
+/*
+ * Process video quality flags
+ *
+ * Note: this is very rough/approx someone might be able to do a much better
+ * job
+ */
+static int
+parse_vid_quality ( epg_broadcast_t *ebc, epg_episode_t *ee, htsmsg_t *m )
+{
+ int save = 0;
+ int hd = 0, lines = 0, aspect = 0;
+ const char *str;
+ if (!ebc || !m) return 0;
+
+ if ((str = htsmsg_xml_get_cdata_str(m, "colour")))
+ save |= epg_episode_set_is_bw(ee, strcmp(str, "no") ? 0 : 1);
+ if ((str = htsmsg_xml_get_cdata_str(m, "quality"))) {
+ if (strstr(str, "HD")) {
+ hd = 1;
+ } else if (strstr(str, "480")) {
+ lines = 480;
+ aspect = 150;
+ } else if (strstr(str, "576")) {
+ lines = 576;
+ aspect = 133;
+ } else if (strstr(str, "720")) {
+ lines = 720;
+ hd = 1;
+ aspect = 178;
+ } else if (strstr(str, "1080")) {
+ lines = 1080;
+ hd = 1;
+ aspect = 178;
+ }
+ }
+ if ((str = htsmsg_xml_get_cdata_str(m, "aspect"))) {
+ int w, h;
+ if (sscanf(str, "%d:%d", &w, &h) == 2) {
+ aspect = (100 * w) / h;
+ }
+ }
+ save |= epg_broadcast_set_is_hd(ebc, hd);
+ if (aspect) {
+ save |= epg_broadcast_set_is_widescreen(ebc, hd || aspect > 137);
+ save |= epg_broadcast_set_aspect(ebc, aspect);
+ }
+ if (lines)
+ save |= epg_broadcast_set_lines(ebc, lines);
+
+ return save;
+}
+
+/*
+ * Parse accessibility data
+ */
+int
+xmltv_parse_accessibility ( epg_broadcast_t *ebc, htsmsg_t *m )
+{
+ int save = 0;
+ htsmsg_t *tag;
+ htsmsg_field_t *f;
+ const char *str;
+
+ HTSMSG_FOREACH(f, m) {
+ if(!strcmp(f->hmf_name, "subtitles")) {
+ if ((tag = htsmsg_get_map_by_field(f))) {
+ str = htsmsg_xml_get_attr_str(tag, "type");
+ if (str && !strcmp(str, "teletext"))
+ save |= epg_broadcast_set_is_subtitled(ebc, 1);
+ else if (str && !strcmp(str, "deaf-signed"))
+ save |= epg_broadcast_set_is_deafsigned(ebc, 1);
+ }
+ } else if (!strcmp(f->hmf_name, "audio-described")) {
+ save |= epg_broadcast_set_is_audio_desc(ebc, 1);
+ }
+ }
+ return save;
+}
+
/**
* Parse tags inside of a programme
*/
@@ -242,16 +321,32 @@ _xmltv_parse_programme_tags(channel_t *ch, htsmsg_t *tags,
if (en) save |= epg_episode_set_number(ee, en);
if (save) stats->episodes.modified++;
+ // TODO: need to handle certification and ratings
// TODO: need to handle season numbering!
// TODO: need to handle onscreen numbering
//if (onscreen) save |= epg_episode_set_onscreen(ee, onscreen);
/* Create/Find broadcast */
ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save2);
- if ( ebc != NULL ) {
+ if ( ebc ) {
stats->broadcasts.total++;
if (save2) stats->broadcasts.created++;
save2 |= epg_broadcast_set_episode(ebc, ee);
+
+ /* Quality metadata */
+ save2 |= parse_vid_quality(ebc, ee, htsmsg_get_map(tags, "video"));
+
+ /* Accessibility */
+ save2 |= xmltv_parse_accessibility(ebc, tags);
+
+ /* Misc */
+ if (htsmsg_get_map(tags, "previously-shown"))
+ save |= epg_broadcast_set_is_repeat(ebc, 1);
+ else if (htsmsg_get_map(tags, "premiere") ||
+ htsmsg_get_map(tags, "new"))
+ save |= epg_broadcast_set_is_new(ebc, 1);
+
+ /* Stats */
if (save2) stats->broadcasts.modified++;
}
diff --git a/src/epggrab/xmltv.h b/src/epggrab/xmltv.h
index 7a2d1bee..104842a7 100644
--- a/src/epggrab/xmltv.h
+++ b/src/epggrab/xmltv.h
@@ -25,4 +25,7 @@
void xmltv_init ( epggrab_module_list_t *list );
void xmltv_load ( void );
+// reused by pyepg
+int xmltv_parse_accessibility ( epg_broadcast_t *ebc, htsmsg_t *m );
+
#endif
diff --git a/src/webui/static/app/epg.js b/src/webui/static/app/epg.js
index 3cf34f4d..87ec839e 100644
--- a/src/webui/static/app/epg.js
+++ b/src/webui/static/app/epg.js
@@ -1,12 +1,11 @@
tvheadend.brands = new Ext.data.JsonStore({
root: 'entries',
- // TODO: this is not ALL fields, just those that I'm likely to use here
fields: [ 'uri', 'title' ],
autoLoad: true,
url : 'epgobject',
baseParams : { op : 'brandList' }
});
-// TODO: we might want this to periodically update!
+// WIBNI: might want this store to periodically update
tvheadend.ContentGroupStore = new Ext.data.JsonStore({
root:'entries',
@@ -117,7 +116,6 @@ tvheadend.epgDetails = function(event) {
}
function showAlternatives (s) {
- // TODO: must be a way to constrain this
var e = Ext.get('altbcast')
html = '';
if ( s.getTotalCount() > 0 ) {
@@ -126,14 +124,12 @@ tvheadend.epgDetails = function(event) {
var ab = s.getAt(i).data;
var dt = Date.parseDate(ab.start, 'U');
html += '