First stab at updating the opentv module to pre-collect event data before reporting to EPG and also to detect completion.
This commit is contained in:
parent
51c43adda9
commit
567c7be55f
3 changed files with 255 additions and 115 deletions
|
@ -35,17 +35,25 @@
|
|||
|
||||
/* ************************************************************************
|
||||
* Configuration
|
||||
*
|
||||
* TODO: I think much of this may be moved to a generic lib, as some of
|
||||
* the other OTA EPGs will have similar config reqs
|
||||
*
|
||||
* TODO: some of this is a bit overkill, for example the global tree of
|
||||
* dicts/provs etc... they're only used to get things up and running
|
||||
* ***********************************************************************/
|
||||
|
||||
#define OPENTV_SCAN_MAX 600 // 10min max scan period
|
||||
#define OPENTV_SCAN_PER 3600 // 1hour interval
|
||||
|
||||
|
||||
/* Data carousel status */
|
||||
typedef struct opentv_status
|
||||
{
|
||||
LIST_ENTRY(opentv_status) link;
|
||||
int pid;
|
||||
enum {
|
||||
OPENTV_STA_INIT,
|
||||
OPENTV_STA_STARTED,
|
||||
OPENTV_STA_COMPLETE
|
||||
} status;
|
||||
uint8_t start[20];
|
||||
} opentv_status_t;
|
||||
|
||||
/* Huffman dictionary */
|
||||
typedef struct opentv_dict
|
||||
{
|
||||
|
@ -78,6 +86,7 @@ typedef struct opentv_module
|
|||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
time_t updated;
|
||||
LIST_HEAD(,opentv_status) status;
|
||||
} opentv_module_t;
|
||||
|
||||
/*
|
||||
|
@ -228,54 +237,101 @@ static channel_t *_opentv_find_channel ( int tsid, int sid )
|
|||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* OpenTV data processing
|
||||
* OpenTV event processing
|
||||
* ***********************************************************************/
|
||||
|
||||
#define MAX_LEN_TITLE 1024
|
||||
#define MAX_LEN_SUMMARY 1024
|
||||
#define MAX_LEN_DESC 2048
|
||||
#define OPENTV_TITLE_LEN 1024
|
||||
#define OPENTV_SUMMARY_LEN 1024
|
||||
#define OPENTV_DESC_LEN 2048
|
||||
|
||||
#define OPENTV_TITLE 0x01
|
||||
#define OPENTV_SUMMARY 0x02
|
||||
|
||||
/* Internal event structure */
|
||||
typedef struct opentv_event
|
||||
{
|
||||
int eid;
|
||||
int start;
|
||||
int duration;
|
||||
char title[MAX_LEN_TITLE];
|
||||
char summary[MAX_LEN_SUMMARY];
|
||||
char description[MAX_LEN_DESC];
|
||||
int serieslink;
|
||||
uint8_t cat;
|
||||
RB_ENTRY(opentv_event) ev_link; ///< List of partial events
|
||||
uint16_t cid; ///< Channel ID
|
||||
uint16_t eid; ///< Events ID
|
||||
time_t start; ///< Start time
|
||||
time_t stop; ///< Event stop time
|
||||
char *title; ///< Event title
|
||||
char *summary; ///< Event summary
|
||||
char *desc; ///< Event description
|
||||
uint8_t cat; ///< Event category
|
||||
uint16_t series; ///< Series (link) reference
|
||||
|
||||
uint8_t status; ///< 0x1=title, 0x2=summary
|
||||
} opentv_event_t;
|
||||
|
||||
/* Parse huffman encoded string */
|
||||
static const char *_parse_string
|
||||
( opentv_prov_t *prov, uint8_t *buf, int len, char *outb, int outl )
|
||||
/* List of partial events */
|
||||
RB_HEAD(, opentv_event) _opentv_events;
|
||||
|
||||
/* Event list comparator */
|
||||
static int _ev_cmp ( void *_a, void *_b )
|
||||
{
|
||||
return huffman_decode(prov->dict->codes, buf, len, 0x20, outb, outl);
|
||||
opentv_event_t *a = (opentv_event_t*)_a;
|
||||
opentv_event_t *b = (opentv_event_t*)_b;
|
||||
int r = a->cid - b->cid;
|
||||
if (r) return r;
|
||||
return a->eid - b->eid;
|
||||
}
|
||||
|
||||
/* Parse huffman encoded string */
|
||||
static char *_opentv_parse_string
|
||||
( opentv_prov_t *prov, uint8_t *buf, int len )
|
||||
{
|
||||
int ok = 0;
|
||||
char *ret, *tmp;
|
||||
|
||||
// Note: unlikely decoded string will be longer (though its possible)
|
||||
ret = tmp = malloc(len+1);
|
||||
*ret = 0;
|
||||
if (huffman_decode(prov->dict->codes, buf, len, 0x20, tmp, len)) {
|
||||
|
||||
/* Ignore (empty) strings */
|
||||
while (*tmp) {
|
||||
if (*tmp > 0x20) {
|
||||
ok = 1;
|
||||
break;
|
||||
}
|
||||
tmp++;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse a specific record */
|
||||
static int _opentv_parse_event_record
|
||||
( opentv_prov_t *prov, opentv_event_t *ev, uint8_t *buf, int len )
|
||||
( opentv_prov_t *prov, opentv_event_t *ev, uint8_t *buf, int len, time_t mjd )
|
||||
{
|
||||
uint8_t rtag = buf[0];
|
||||
uint8_t rlen = buf[1];
|
||||
if (rlen+2 <= len) {
|
||||
switch (rtag) {
|
||||
case 0xb5: // title
|
||||
ev->start = (((int)buf[2] << 9) | (buf[3] << 1));
|
||||
ev->duration = (((int)buf[4] << 9) | (buf[5] << 1));
|
||||
ev->start = (((int)buf[2] << 9) | (buf[3] << 1))
|
||||
+ mjd;
|
||||
ev->stop = (((int)buf[4] << 9) | (buf[5] << 1))
|
||||
+ ev->start;
|
||||
ev->cat = buf[6];
|
||||
_parse_string(prov, buf+9, rlen-7, ev->title, MAX_LEN_TITLE);
|
||||
if (!ev->title)
|
||||
ev->title = _opentv_parse_string(prov, buf+9, rlen-7);
|
||||
break;
|
||||
case 0xb9: // summary
|
||||
_parse_string(prov, buf+2, rlen, ev->summary, MAX_LEN_SUMMARY);
|
||||
if (!ev->summary)
|
||||
ev->summary = _opentv_parse_string(prov, buf+2, rlen);
|
||||
break;
|
||||
case 0xbb: // description
|
||||
_parse_string(prov, buf+2, rlen, ev->description, MAX_LEN_DESC);
|
||||
if (!ev->desc)
|
||||
ev->desc = _opentv_parse_string(prov, buf+2, rlen);
|
||||
break;
|
||||
case 0xc1: // series link
|
||||
ev->serieslink = ((int)buf[2] << 8) | buf[3];
|
||||
ev->series = ((uint16_t)buf[2] << 8) | buf[3];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -286,28 +342,44 @@ static int _opentv_parse_event_record
|
|||
|
||||
/* Parse a specific event */
|
||||
static int _opentv_parse_event
|
||||
( opentv_prov_t *prov, opentv_event_t *ev, uint8_t *buf, int len )
|
||||
( opentv_prov_t *prov, uint8_t *buf, int len, int cid, time_t mjd,
|
||||
opentv_event_t **ev )
|
||||
{
|
||||
static opentv_event_t *skel = NULL;
|
||||
int slen = ((int)buf[2] & 0xf << 8) | buf[3];
|
||||
int i = 4;
|
||||
ev->eid = ((uint16_t)buf[0] << 8) | buf[1];
|
||||
while (i < slen+4) {
|
||||
i += _opentv_parse_event_record(prov, ev, buf+i, len-i);
|
||||
|
||||
/* Create/Find event entry */
|
||||
if (!skel) skel = calloc(1, sizeof(opentv_event_t));
|
||||
skel->cid = cid;
|
||||
skel->eid = ((uint16_t)buf[0] << 8) | buf[1];
|
||||
*ev = RB_INSERT_SORTED(&_opentv_events, skel, ev_link, _ev_cmp);
|
||||
if (!*ev) {
|
||||
*ev = skel;
|
||||
skel = NULL;
|
||||
}
|
||||
|
||||
/* Process records */
|
||||
if (*ev) {
|
||||
while (i < slen+4) {
|
||||
i += _opentv_parse_event_record(prov, *ev, buf+i, len-i, mjd);
|
||||
}
|
||||
}
|
||||
return slen+4;
|
||||
}
|
||||
|
||||
/* Parse an event section */
|
||||
static int _opentv_parse_event_section
|
||||
( opentv_module_t *mod, uint8_t *buf, int len )
|
||||
( opentv_module_t *mod, uint8_t *buf, int len, int type )
|
||||
{
|
||||
int i, cid, mjd, save = 0;
|
||||
int i, cid, save = 0;
|
||||
time_t mjd;
|
||||
char *uri;
|
||||
epggrab_channel_t *ec;
|
||||
epg_broadcast_t *ebc;
|
||||
epg_episode_t *ee;
|
||||
epg_season_t *es;
|
||||
opentv_event_t ev;
|
||||
opentv_event_t *ev;
|
||||
|
||||
/* Channel */
|
||||
cid = ((int)buf[0] << 8) | buf[1];
|
||||
|
@ -317,64 +389,59 @@ static int _opentv_parse_event_section
|
|||
|
||||
/* Time (start/stop referenced to this) */
|
||||
mjd = ((int)buf[5] << 8) | buf[6];
|
||||
mjd = (mjd - 40587) * 86400;
|
||||
|
||||
/* Loop around event entries */
|
||||
i = 7;
|
||||
while (i < len) {
|
||||
memset(&ev, 0, sizeof(opentv_event_t));
|
||||
i += _opentv_parse_event(mod->prov, &ev, buf+i, len-i);
|
||||
i += _opentv_parse_event(mod->prov, buf+i, len-i, cid, mjd, &ev);
|
||||
|
||||
/* Create/Find broadcast */
|
||||
if (ev.start && ev.duration) {
|
||||
time_t start = ev.start + ((mjd - 40587) * 86400);
|
||||
time_t stop = start + ev.duration;
|
||||
ebc = epg_broadcast_find_by_time(ec->channel, start, stop, ev.eid, 1, &save);
|
||||
/* Error */
|
||||
if (!ev) continue;
|
||||
|
||||
/* Incomplete */
|
||||
ev->status |= type;
|
||||
if (ev->status != (OPENTV_TITLE | OPENTV_SUMMARY)) continue;
|
||||
|
||||
/* Find episode */
|
||||
uri = epg_hash(ev->title, ev->summary, ev->desc);
|
||||
if (uri) {
|
||||
ee = epg_episode_find_by_uri(uri, 1, &save);
|
||||
free(uri);
|
||||
} else {
|
||||
ebc = epg_broadcast_find_by_eid(ev.eid, ec->channel);
|
||||
ee = NULL;
|
||||
}
|
||||
|
||||
if (ebc) {
|
||||
|
||||
/* Create/Find episode */
|
||||
if (*ev.description || *ev.summary) {
|
||||
uri = md5sum(*ev.description ? ev.description : ev.summary);
|
||||
ee = epg_episode_find_by_uri(uri, 1, &save);
|
||||
free(uri);
|
||||
} else if (ebc) {
|
||||
ee = ebc->episode;
|
||||
/* Set episode data */
|
||||
if (ee) {
|
||||
if (ev->title)
|
||||
save |= epg_episode_set_title(ee, ev->title);
|
||||
if (ev->summary)
|
||||
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);
|
||||
// Note: don't override the season (since the ID is channel specific
|
||||
// it'll keep changing!
|
||||
if (ev->series && !ee->season) {
|
||||
es = _opentv_find_season(mod, cid, ev->series);
|
||||
if (es) save |= epg_episode_set_season(ee, es);
|
||||
}
|
||||
|
||||
/* DVB Event Id */
|
||||
// TODO: this causes serious problems for channels backed by multiple
|
||||
// services where the ids don't match
|
||||
if (ebc->dvb_eid != ev.eid) {
|
||||
ebc->dvb_eid = ev.eid;
|
||||
save = 1;
|
||||
}
|
||||
|
||||
/* Set episode data */
|
||||
if (ee) {
|
||||
if (*ev.description)
|
||||
save |= epg_episode_set_description(ee, ev.description);
|
||||
if (*ev.summary)
|
||||
save |= epg_episode_set_summary(ee, ev.summary);
|
||||
if (*ev.title)
|
||||
save |= epg_episode_set_title(ee, ev.title);
|
||||
if (ev.cat)
|
||||
save |= epg_episode_set_genre(ee, &ev.cat, 1);
|
||||
|
||||
// Note: we don't overwrite an existing season, the reason for
|
||||
// this is that seasons in opentv are channel specific but episodes
|
||||
// are not (to allow episode matching) so this is a bit of bodge
|
||||
// to make things play nice
|
||||
if (ev.serieslink && !ee->season) {
|
||||
es = _opentv_find_season(mod, cid, ev.serieslink);
|
||||
if (es) save |= epg_episode_set_season(ee, es);
|
||||
}
|
||||
|
||||
/* Find broadcast */
|
||||
ebc = epg_broadcast_find_by_time(ec->channel, ev->start, ev->stop,
|
||||
ev->eid, 1, &save);
|
||||
if (ebc)
|
||||
save |= epg_broadcast_set_episode(ebc, ee);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove and cleanup */
|
||||
RB_REMOVE(&_opentv_events, ev, ev_link);
|
||||
if (ev->title) free(ev->title);
|
||||
if (ev->summary) free(ev->summary);
|
||||
if (ev->desc) free(ev->desc);
|
||||
free(ev);
|
||||
}
|
||||
|
||||
/* Update EPG */
|
||||
|
@ -382,37 +449,19 @@ static int _opentv_parse_event_section
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void _opentv_table_callback
|
||||
( opentv_module_t *m, th_dvb_mux_instance_t *tdmi )
|
||||
{
|
||||
epggrab_ota_mux_t *ota;
|
||||
ota = epggrab_ota_register((epggrab_module_t*)m, tdmi,
|
||||
OPENTV_SCAN_MAX, OPENTV_SCAN_PER);
|
||||
if (epggrab_ota_begin(ota)) {
|
||||
// setup internal status
|
||||
}
|
||||
}
|
||||
/* ************************************************************************
|
||||
* OpenTV channel processing
|
||||
* ***********************************************************************/
|
||||
|
||||
static int _opentv_event_callback
|
||||
( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p )
|
||||
{
|
||||
opentv_module_t *mod = (opentv_module_t*)p;
|
||||
_opentv_table_callback(mod, tdmi);
|
||||
return _opentv_parse_event_section(mod, buf, len);
|
||||
}
|
||||
|
||||
// TODO: this function is currently a bit of a mess
|
||||
// TODO: bouqets are ignored, what useful info can we get from them?
|
||||
static int _opentv_channel_callback
|
||||
( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p )
|
||||
static int _opentv_bat_section
|
||||
( opentv_module_t *mod, uint8_t *buf, int len )
|
||||
{
|
||||
opentv_module_t *mod = (opentv_module_t*)p;
|
||||
epggrab_channel_t *ec;
|
||||
int tsid, cid;//, cnum;
|
||||
uint16_t sid;
|
||||
int i, j, k, tdlen, dlen, dtag, tslen;
|
||||
channel_t *ch;
|
||||
_opentv_table_callback(mod, tdmi);
|
||||
i = 7 + ((((int)buf[5] & 0xf) << 8) | buf[6]);
|
||||
tslen = (((int)buf[i] & 0xf) << 8) | buf[i+1];
|
||||
i += 2;
|
||||
|
@ -458,6 +507,91 @@ static int _opentv_channel_callback
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* Table Callbacks
|
||||
* ***********************************************************************/
|
||||
|
||||
static opentv_module_t *_opentv_table_callback
|
||||
( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p )
|
||||
{
|
||||
th_dvb_table_t *tdt = (th_dvb_table_t*)p;
|
||||
opentv_module_t *mod = (opentv_module_t*)tdt->tdt_opaque;
|
||||
epggrab_ota_mux_t *ota;
|
||||
opentv_status_t *sta;
|
||||
|
||||
/* Ignore (not enough data) */
|
||||
if (len < 20) return NULL;
|
||||
|
||||
/* Register */
|
||||
ota = epggrab_ota_register((epggrab_module_t*)mod, tdmi,
|
||||
OPENTV_SCAN_MAX, OPENTV_SCAN_PER);
|
||||
if (!ota) return NULL;
|
||||
|
||||
/* Finished */
|
||||
if (epggrab_ota_is_complete(ota)) return NULL;
|
||||
|
||||
/* Begin */
|
||||
if (epggrab_ota_begin(ota)) {
|
||||
LIST_FOREACH(sta, &mod->status, link)
|
||||
sta->status = OPENTV_STA_INIT;
|
||||
}
|
||||
|
||||
/* Insert */
|
||||
LIST_FOREACH(sta, &mod->status, link)
|
||||
if (sta->pid == tdt->tdt_pid) break;
|
||||
if (!sta) {
|
||||
sta = calloc(1, sizeof(opentv_status_t));
|
||||
LIST_INSERT_HEAD(&mod->status, sta, link);
|
||||
}
|
||||
|
||||
/* Set init */
|
||||
if (!sta->status) {
|
||||
sta->status = OPENTV_STA_STARTED;
|
||||
memcpy(sta->start, buf, 20);
|
||||
return mod;
|
||||
|
||||
/* Complete */
|
||||
} else if (sta->status == 1 && !memcmp(sta->start, buf, 20)) {
|
||||
sta->status = OPENTV_STA_COMPLETE;
|
||||
|
||||
/* Check rest */
|
||||
LIST_FOREACH(sta, &mod->status, link)
|
||||
if (sta->status != OPENTV_STA_COMPLETE) return mod;
|
||||
}
|
||||
|
||||
/* Mark complete */
|
||||
epggrab_ota_complete(ota);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int _opentv_title_callback
|
||||
( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p )
|
||||
{
|
||||
opentv_module_t *mod = _opentv_table_callback(tdmi, buf, len, tid, p);
|
||||
if (mod)
|
||||
return _opentv_parse_event_section(mod, buf, len, OPENTV_TITLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _opentv_summary_callback
|
||||
( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p )
|
||||
{
|
||||
opentv_module_t *mod = _opentv_table_callback(tdmi, buf, len, tid, p);
|
||||
if (mod)
|
||||
return _opentv_parse_event_section(mod, buf, len, OPENTV_SUMMARY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _opentv_channel_callback
|
||||
( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p )
|
||||
{
|
||||
opentv_module_t *mod = _opentv_table_callback(tdmi, buf, len, tid, p);
|
||||
if (mod)
|
||||
return _opentv_bat_section(mod, buf, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* Module Setup
|
||||
* ***********************************************************************/
|
||||
|
@ -482,7 +616,7 @@ static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi )
|
|||
fp->filter.mask[0] = 0xff;
|
||||
// TODO: what about 0x46 (service description)
|
||||
tdt_add(tdmi, fp, _opentv_channel_callback, mod,
|
||||
m->id, TDT_CRC, *t++, NULL);
|
||||
m->id, TDT_CRC | TDT_TDT, *t++, NULL);
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
|
@ -491,8 +625,8 @@ static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi )
|
|||
fp = dvb_fparams_alloc();
|
||||
fp->filter.filter[0] = 0xa0;
|
||||
fp->filter.mask[0] = 0xfc;
|
||||
tdt_add(tdmi, fp, _opentv_event_callback, mod,
|
||||
m->id, TDT_CRC, *t++, NULL);
|
||||
tdt_add(tdmi, fp, _opentv_title_callback, mod,
|
||||
m->id, TDT_CRC | TDT_TDT, *t++, NULL);
|
||||
}
|
||||
|
||||
/* Summaries */
|
||||
|
@ -501,8 +635,8 @@ static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi )
|
|||
fp = dvb_fparams_alloc();
|
||||
fp->filter.filter[0] = 0xa8;
|
||||
fp->filter.mask[0] = 0xfc;
|
||||
tdt_add(tdmi, fp, _opentv_event_callback, mod,
|
||||
m->id, TDT_CRC, *t++, NULL);
|
||||
tdt_add(tdmi, fp, _opentv_summary_callback, mod,
|
||||
m->id, TDT_CRC | TDT_TDT, *t++, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ void epggrab_ota_unregister_all
|
|||
*/
|
||||
int epggrab_ota_begin ( epggrab_ota_mux_t *ota )
|
||||
{
|
||||
if (ota->status != EPGGRAB_OTA_STARTED) {
|
||||
if (ota->status == EPGGRAB_OTA_IDLE) {
|
||||
ota->status = EPGGRAB_OTA_STARTED;
|
||||
time(&ota->started);
|
||||
return 1;
|
||||
|
@ -112,3 +112,8 @@ void epggrab_ota_timeout ( epggrab_ota_mux_t *ota )
|
|||
// TODO: for now just treat as completed, stops it being allowed
|
||||
// to immediately re-enter the queue
|
||||
}
|
||||
|
||||
int epggrab_ota_is_complete ( epggrab_ota_mux_t *ota )
|
||||
{
|
||||
return ota->status == EPGGRAB_OTA_COMPLETED;
|
||||
}
|
||||
|
|
|
@ -66,9 +66,10 @@ void epggrab_ota_unregister_all
|
|||
/*
|
||||
* Status
|
||||
*/
|
||||
int epggrab_ota_begin ( epggrab_ota_mux_t *ota );
|
||||
void epggrab_ota_complete ( epggrab_ota_mux_t *ota );
|
||||
void epggrab_ota_cancel ( epggrab_ota_mux_t *ota );
|
||||
void epggrab_ota_timeout ( epggrab_ota_mux_t *ota );
|
||||
int epggrab_ota_begin ( epggrab_ota_mux_t *ota );
|
||||
void epggrab_ota_complete ( epggrab_ota_mux_t *ota );
|
||||
void epggrab_ota_cancel ( epggrab_ota_mux_t *ota );
|
||||
void epggrab_ota_timeout ( epggrab_ota_mux_t *ota );
|
||||
int epggrab_ota_is_complete ( epggrab_ota_mux_t *ota );
|
||||
|
||||
#endif /* __TVH_EPGGRAB_OTA_H__ */
|
||||
|
|
Loading…
Add table
Reference in a new issue