Merge branch 'epg-metadata' into epg-rewrite

This commit is contained in:
Adam Sutton 2012-06-20 16:17:53 +01:00
commit a31e2d303d
14 changed files with 482 additions and 139 deletions

View file

@ -0,0 +1,58 @@
<div class="hts-doc-text">
<p>
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.
</p>
<h2>Grabber Types</h2>
<ul>
<li>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.</li>
<li>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.</li>
<li>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.</li>
</ul>
<h2>Grabber Modules</h2>
<ul>
<li>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.</li>
<li>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.</li>
<li>XMLTV - This is a IP network based scraper, for more information about XMLTV please visit <a href="http://www.xmltv.org">http://www.xmltv.org</a>. 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.</li>
<li>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 <a href='http://github.com/adamsutton/PyEPG'>http://github.com/adamsutton/PyEPG</a>.</li>
</ul>
<h2>Configuration options</h2>
<p>
<dl>
<dt>Module:
<dd>Select which internal grabber to use.
<dt>Grab interval
<dd>Time period between grabs.
<dt>External interfaces
<dd>Check tick boxes for whichever you want to make available, the Path column displays where the unix socket you need to use lives.
<dt>OTA interfaces
<dd>Check tick boxes for whichever you want to use.
</dl>
Changes to any of these settings must be confirmed by pressing the
'Save configuration' button before taking effect.
</p>
<h2>Notes</h2>
<p>
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.
</p>
<p>
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.
</p>
</div>

View file

@ -1,33 +0,0 @@
<div class="hts-doc-text">
<img src="docresources/xmltvtab.png">
<p>
This tab is used to configure XML-TV. For more information about XML-TV
and its use, please visit
<a href="http://www.xmltv.org">http://www.xmltv.org</a>.
<p>
Configuration options:
<dl>
<dt>XML-TV Source
<dd>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.
<dt>Grab interval
<dd>Hours between each grab.
<dt>Enable grabbing
<dd>Uncheck this if you wish to disable grabbing.
</dl>
Changes to any of these settings must be confirmed by pressing the
'Save configuration' button before taking effect.
</div>

View file

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

View file

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

View file

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

279
src/epg.c
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 += '<div class="x-epg-desc">' + dt.format('l H:i') + '&nbsp;&nbsp;&nbsp;' + ab.channel + '</div>';
// TODO: record option?
}
}
e.dom.innerHTML = html;
}
function showRelated (s)
{
// TODO: must be a way to constrain this
var e = Ext.get('related')
html = '';
if ( s.getTotalCount() > 0 ) {

View file

@ -176,7 +176,6 @@ tvheadend.epggrab = function() {
dataIndex : 'path',
width : 300,
sortable : false,
// TODO: editable?
},
]);
@ -258,7 +257,8 @@ tvheadend.epggrab = function() {
var helpButton = new Ext.Button({
text : 'Help',
handler : function() {
alert('TODO: help info');
new tvheadend.help('EPG Grab Configuration',
'config_epggrab.html');
}
});