From 8c1562a8e8eb322947e33c88ff6ccbee6e77a9f0 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sat, 16 Jun 2012 20:27:53 +0100 Subject: [PATCH 01/25] Temporary hack to get htsmsg_json to accept (though ignore) escaped hex. --- src/htsmsg_json.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/htsmsg_json.c b/src/htsmsg_json.c index a302247d..89f78f6c 100644 --- a/src/htsmsg_json.c +++ b/src/htsmsg_json.c @@ -164,7 +164,7 @@ htsmsg_json_parse_string(const char *s, const char **endp) if(*s == '\\') { esc = 1; - } else if(*s == '"' && s[-1] != '\\') { + } else if(*s == '"' && (s[-1] != '\\' || s[-2] == '\\')) { *endp = s + 1; @@ -184,6 +184,8 @@ htsmsg_json_parse_string(const char *s, const char **endp) a++; if(*a == 'b') *b++ = '\b'; + else if (*a == '\\') + *b++ = '\\'; else if(*a == 'f') *b++ = '\f'; else if(*a == 'n') @@ -193,9 +195,12 @@ htsmsg_json_parse_string(const char *s, const char **endp) else if(*a == 't') *b++ = '\t'; else if(*a == 'u') { +#if TODO /* 4 hexdigits: Not supported */ free(r); return NULL; +#endif + a += 4; } else { *b++ = *a; } From d419ce76dc25e902cfa9687596847667efa28891 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sat, 16 Jun 2012 20:28:30 +0100 Subject: [PATCH 02/25] First stab at a huffman decoder that I can use for decoding opentv strings. --- src/huffman.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/huffman.h | 38 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 src/huffman.c create mode 100644 src/huffman.h diff --git a/src/huffman.c b/src/huffman.c new file mode 100644 index 00000000..fef49b38 --- /dev/null +++ b/src/huffman.c @@ -0,0 +1,109 @@ +/* + * TV headend - Huffman decoder + * Copyb1 (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include "huffman.h" +#include "htsmsg.h" +#include "settings.h" + +void huffman_tree_destroy ( huffman_node_t *n ) +{ + if (!n) return; + huffman_tree_destroy(n->b0); + huffman_tree_destroy(n->b1); + if (n->data) free(n->data); + free(n); +} + +huffman_node_t *huffman_tree_load ( const char *path ) +{ + htsmsg_t *m; + if (!(m = hts_settings_load(path))) return NULL; + return huffman_tree_build(m); +} + +huffman_node_t *huffman_tree_build ( htsmsg_t *m ) +{ + const char *code, *data, *c; + htsmsg_t *e; + htsmsg_field_t *f; + huffman_node_t *root = calloc(1, sizeof(huffman_node_t)); + HTSMSG_FOREACH(f, m) { + e = htsmsg_get_map_by_field(f); + c = code = htsmsg_get_str(e, "code"); + data = htsmsg_get_str(e, "data"); + if (code && data) { + huffman_node_t *node = root; + while (*c) { + if ( *c == '0' ) { + if (!node->b0) node->b0 = calloc(1, sizeof(huffman_node_t)); + node = node->b0; + } else if ( *c == '1' ) { + if (!node->b1) node->b1 = calloc(1, sizeof(huffman_node_t)); + node = node->b1; + } else { + htsmsg_destroy(m); + huffman_tree_destroy(root); + return NULL; + } + c++; + } + node->data = strdup(data); + } + } + + htsmsg_destroy(m); + return root; +} + +char *huffman_decode + ( huffman_node_t *tree, const char *data, size_t len, uint8_t mask ) +{ + char* ret; + size_t nalloc = len, nused = 0; + huffman_node_t *node = tree; + if (!len) return NULL; + + ret = malloc(nalloc); + ret[0] = '\0'; + while (len) { + len--; + while (mask) { + if (*data & mask) { + node = node->b1; + } else { + node = node->b0; + } + mask >>= 1; + if (!node) return ret; + if (node->data) { + size_t l = strlen(node->data); + if ((nalloc - nused) < l) { + nalloc *= 2; + ret = realloc(ret, nalloc); + } + strcat(ret, node->data); + node = tree; + } + } + mask = 0x80; + } + return ret; +} diff --git a/src/huffman.h b/src/huffman.h new file mode 100644 index 00000000..08ce873e --- /dev/null +++ b/src/huffman.h @@ -0,0 +1,38 @@ +/* + * TV headend - Huffman decoder + * Copyright (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_HUFFMAN_H__ +#define __TVH_HUFFMAN_H__ + +#include +#include "htsmsg.h" + +typedef struct huffman_node +{ + struct huffman_node *b0; + struct huffman_node *b1; + char *data; +} huffman_node_t; + +void huffman_tree_destroy ( huffman_node_t *tree ); +huffman_node_t *huffman_tree_load ( const char *path ); +huffman_node_t *huffman_tree_build ( htsmsg_t *codes ); +char *huffman_decode + ( huffman_node_t *tree, const char *data, size_t len, uint8_t mask ); + +#endif From d5f3e2c686e2c6b77d18a46a1c971fbb65f1163a Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sat, 16 Jun 2012 20:29:10 +0100 Subject: [PATCH 03/25] Initial code for opentv module, just testing at the moment. --- Makefile | 4 +- src/epggrab.c | 3 ++ src/epggrab/opentv.c | 92 ++++++++++++++++++++++++++++++++++++++++++++ src/epggrab/opentv.h | 27 +++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/epggrab/opentv.c create mode 100644 src/epggrab/opentv.h diff --git a/Makefile b/Makefile index d1e6956c..926e1e90 100644 --- a/Makefile +++ b/Makefile @@ -72,10 +72,12 @@ SRCS = src/main.c \ src/rawtsinput.c \ src/iptv_input.c \ src/avc.c \ + src/huffman.c \ SRCS += src/epggrab/pyepg.c\ src/epggrab/xmltv.c\ - src/epggrab/eit.c + src/epggrab/eit.c \ + src/epggrab/opentv.c SRCS += src/plumbing/tsfix.c \ src/plumbing/globalheaders.c \ diff --git a/src/epggrab.c b/src/epggrab.c index 203b638e..e276cae5 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -16,6 +16,7 @@ #include "epggrab/eit.h" #include "epggrab/xmltv.h" #include "epggrab/pyepg.h" +#include "epggrab/opentv.h" #include "channels.h" #include "spawn.h" #include "htsmsg_xml.h" @@ -712,6 +713,7 @@ static void _epggrab_load ( void ) eit_load(); xmltv_load(); pyepg_load(); + opentv_load(); } void epggrab_save ( void ) @@ -819,6 +821,7 @@ void epggrab_init ( void ) eit_init(&epggrab_modules); xmltv_init(&epggrab_modules); pyepg_init(&epggrab_modules); + opentv_init(&epggrab_modules); /* Load config */ _epggrab_load(); diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c new file mode 100644 index 00000000..30a082a1 --- /dev/null +++ b/src/epggrab/opentv.c @@ -0,0 +1,92 @@ +/* + * Electronic Program Guide - eit grabber + * Copyright (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include "tvheadend.h" +#include "dvb/dvb.h" +#include "channels.h" +#include "huffman.h" +#include "epg.h" +#include "epggrab/opentv.h" +#include "subscriptions.h" +#include "service.h" + +/* ************************************************************************ + * Module Setup + * ***********************************************************************/ + +static epggrab_module_t _opentv_mod; +static huffman_node_t *_opentv_dict; + +static void* _opentv_thread ( void *p ) +{ + int err; + th_dvb_adapter_t *tda; + th_dvb_mux_instance_t *tdmi; + service_t *svc = NULL; + + pthread_mutex_lock(&global_lock); + + printf("find service\n"); + TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) { + LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { + if ((svc = dvb_transport_find(tdmi, 4152, 0, NULL))) break; + } + } + + /* start */ + printf("found svc = %p\n", svc); + if(svc) err = service_start(svc, 1, 0); + printf("service_start = %d\n", err); + + pthread_mutex_unlock(&global_lock); + + while (1) { + sleep(10); + } + return NULL; +} + +static int _opentv_enable ( epggrab_module_t *m, uint8_t e ) +{ + pthread_t tid; + pthread_attr_t tattr; + pthread_attr_init(&tattr); + pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); + pthread_create(&tid, &tattr, _opentv_thread, NULL); + return 0; +} + +void opentv_init ( epggrab_module_list_t *list ) +{ + _opentv_mod.id = strdup("opentv"); + _opentv_mod.name = strdup("OpenTV EPG"); + _opentv_mod.enable = _opentv_enable; + *((uint8_t*)&_opentv_mod.flags) = EPGGRAB_MODULE_EXTERNAL; // TODO: hack + LIST_INSERT_HEAD(list, &_opentv_mod, link); + // Note: this is mostly ignored anyway as EIT is treated as a special case! +} + +void opentv_load ( void ) +{ + _opentv_dict = huffman_tree_load("epggrab/opentv/dict/skyuk"); + assert(_opentv_dict); + tvhlog(LOG_INFO, "opentv", "huffman tree loaded"); +} diff --git a/src/epggrab/opentv.h b/src/epggrab/opentv.h new file mode 100644 index 00000000..ba087cfd --- /dev/null +++ b/src/epggrab/opentv.h @@ -0,0 +1,27 @@ +/* + * Electronic Program Guide - opentv + * Copyright (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_EPGGRAB_OPENTV_H__ +#define __TVH_EPGGRAB_OPENTV_H__ + +#include "epggrab.h" + +void opentv_init ( epggrab_module_list_t *list ); +void opentv_load ( void ); + +#endif /* __TVH_EPGGRAB_OPENTV_H__ */ From d32eae5671e4f2c8384c341cb6d0f28ee1ecea30 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sat, 16 Jun 2012 21:04:55 +0100 Subject: [PATCH 04/25] Expose DVB table API within the rest of the app (needed by new grabbers). --- src/dvb/dvb.h | 51 ++++++++++++++++++++++++++++++++++++++++++ src/dvb/dvb_tables.c | 53 ++------------------------------------------ 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/dvb/dvb.h b/src/dvb/dvb.h index 80ff4556..4e4e6825 100644 --- a/src/dvb/dvb.h +++ b/src/dvb/dvb.h @@ -207,6 +207,46 @@ typedef struct th_dvb_adapter { } th_dvb_adapter_t; +/** + * DVB table + */ +typedef struct th_dvb_table { + /** + * Flags, must never be changed after creation. + * We inspect it without holding global_lock + */ + int tdt_flags; + + /** + * Cycle queue + * Tables that did not get a fd or filter in hardware will end up here + * waiting for any other table to be received so it can reuse that fd. + * Only linked if fd == -1 + */ + TAILQ_ENTRY(th_dvb_table) tdt_pending_link; + + /** + * File descriptor for filter + */ + int tdt_fd; + + LIST_ENTRY(th_dvb_table) tdt_link; + + char *tdt_name; + + void *tdt_opaque; + int (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, + uint8_t tableid, void *opaque); + + + int tdt_count; + int tdt_pid; + + struct dmx_sct_filter_params *tdt_fparams; + + int tdt_id; + +} th_dvb_table_t; extern struct th_dvb_adapter_queue dvb_adapters; @@ -331,6 +371,17 @@ void dvb_table_add_default(th_dvb_mux_instance_t *tdmi); void dvb_table_flush_all(th_dvb_mux_instance_t *tdmi); +struct dmx_sct_filter_params *dvb_fparams_alloc(void); +void +tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, + int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, + uint8_t tableid, void *opaque), void *opaque, + const char *name, int flags, int pid, th_dvb_table_t *tdt); + +#define TDT_CRC 0x1 +#define TDT_QUICKREQ 0x2 +#define TDT_CA 0x4 + /** * Satellite configuration */ diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index c9e7ad43..62fabd0c 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -43,68 +43,19 @@ #include "cwc.h" #include "epggrab/eit.h" -#define TDT_CRC 0x1 -#define TDT_QUICKREQ 0x2 -#define TDT_CA 0x4 - static void dvb_table_add_pmt(th_dvb_mux_instance_t *tdmi, int pmt_pid); static int tdt_id_tally; -/** - * - */ -typedef struct th_dvb_table { - /** - * Flags, must never be changed after creation. - * We inspect it without holding global_lock - */ - int tdt_flags; - - /** - * Cycle queue - * Tables that did not get a fd or filter in hardware will end up here - * waiting for any other table to be received so it can reuse that fd. - * Only linked if fd == -1 - */ - TAILQ_ENTRY(th_dvb_table) tdt_pending_link; - - /** - * File descriptor for filter - */ - int tdt_fd; - - LIST_ENTRY(th_dvb_table) tdt_link; - - char *tdt_name; - - void *tdt_opaque; - int (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, - uint8_t tableid, void *opaque); - - - int tdt_count; - int tdt_pid; - - struct dmx_sct_filter_params *tdt_fparams; - - int tdt_id; - -} th_dvb_table_t; - - - - /** * Helper for preparing a section filter parameter struct */ -static struct dmx_sct_filter_params * +struct dmx_sct_filter_params * dvb_fparams_alloc(void) { return calloc(1, sizeof(struct dmx_sct_filter_params)); } - /** * */ @@ -324,7 +275,7 @@ dvb_tdt_destroy(th_dvb_adapter_t *tda, th_dvb_mux_instance_t *tdmi, /** * Add a new DVB table */ -static void +void tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tableid, void *opaque), void *opaque, From c76fe802e434cd47e22cc76630212686e428dab1 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sun, 17 Jun 2012 13:55:46 +0100 Subject: [PATCH 05/25] Some minor mods and fixes to the huffman decoder. --- src/huffman.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/huffman.c b/src/huffman.c index fef49b38..b9c68bb0 100644 --- a/src/huffman.c +++ b/src/huffman.c @@ -35,8 +35,11 @@ void huffman_tree_destroy ( huffman_node_t *n ) huffman_node_t *huffman_tree_load ( const char *path ) { htsmsg_t *m; + huffman_node_t *ret; if (!(m = hts_settings_load(path))) return NULL; - return huffman_tree_build(m); + ret = huffman_tree_build(m); + htsmsg_destroy(m); + return ret; } huffman_node_t *huffman_tree_build ( htsmsg_t *m ) @@ -68,8 +71,6 @@ huffman_node_t *huffman_tree_build ( htsmsg_t *m ) node->data = strdup(data); } } - - htsmsg_destroy(m); return root; } @@ -77,7 +78,7 @@ char *huffman_decode ( huffman_node_t *tree, const char *data, size_t len, uint8_t mask ) { char* ret; - size_t nalloc = len, nused = 0; + size_t nalloc = len*4, nused = 0; huffman_node_t *node = tree; if (!len) return NULL; @@ -104,6 +105,7 @@ char *huffman_decode } } mask = 0x80; + data++; } return ret; } From d29100103fb6b6b090f2f5ee36b10b90fa105d7e Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sun, 17 Jun 2012 13:57:15 +0100 Subject: [PATCH 06/25] Provide top level API call to allow all epg grabbers to be informed about tuning events, as OTA grabbers will need to know this. --- src/dvb/dvb_fe.c | 4 ++++ src/epggrab.c | 8 ++++++++ src/epggrab.h | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/src/dvb/dvb_fe.c b/src/dvb/dvb_fe.c index 9e7b7890..64402bab 100644 --- a/src/dvb/dvb_fe.c +++ b/src/dvb/dvb_fe.c @@ -40,6 +40,8 @@ #include "notify.h" #include "dvr/dvr.h" +#include "epggrab.h" + /** * Return uncorrected block (since last read) * @@ -506,7 +508,9 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason) gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1); + dvb_table_add_default(tdmi); + epggrab_tune(tdmi); dvb_adapter_notify(tda); return 0; diff --git a/src/epggrab.c b/src/epggrab.c index e276cae5..38ae580e 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -858,6 +858,14 @@ void epggrab_channel_mod ( channel_t *ch ) } } +void epggrab_tune ( th_dvb_mux_instance_t *tdmi ) +{ + epggrab_module_t *m; + LIST_FOREACH(m, &epggrab_modules, link) { + if (m->tune) m->tune(m, tdmi); + } +} + epggrab_module_t* epggrab_module_find_by_id ( const char *id ) { epggrab_module_t *m; diff --git a/src/epggrab.h b/src/epggrab.h index 7a6aba19..36e5d87a 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -3,6 +3,7 @@ #include #include "channels.h" +#include "dvb/dvb.h" typedef struct epggrab_module epggrab_module_t; @@ -117,6 +118,9 @@ struct epggrab_module void (*ch_add) ( epggrab_module_t *m, channel_t *ch ); void (*ch_rem) ( epggrab_module_t *m, channel_t *ch ); void (*ch_mod) ( epggrab_module_t *m, channel_t *ch ); + + /* Transponder tuning */ + void (*tune) ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ); }; /* @@ -187,4 +191,9 @@ void epggrab_channel_add ( struct channel *ch ); void epggrab_channel_rem ( struct channel *ch ); void epggrab_channel_mod ( struct channel *ch ); +/* + * Transport handling + */ +void epggrab_tune ( th_dvb_mux_instance_t *tdmi ); + #endif /* __EPGGRAB_H__ */ From 8229cf463366f47ccd481c7c4fa1b0bd9b5097ee Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sun, 17 Jun 2012 14:52:22 +0100 Subject: [PATCH 07/25] Started to clean up opentv module, added some configuration (though currently its mostly ALL enabled). --- src/epggrab/opentv.c | 299 +++++++++++++++++++++++++++++++++++++++++-- src/epggrab/opentv.h | 2 + 2 files changed, 287 insertions(+), 14 deletions(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index 30a082a1..faa19e25 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "tvheadend.h" #include "dvb/dvb.h" #include "channels.h" @@ -27,27 +28,256 @@ #include "epggrab/opentv.h" #include "subscriptions.h" #include "service.h" +#include "htsmsg.h" +#include "settings.h" + +/* ************************************************************************ + * 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 + * ***********************************************************************/ + +typedef struct opentv_dict +{ + char *key; + huffman_node_t *codes; + RB_ENTRY(opentv_dict) h_link; +} opentv_dict_t; + +typedef struct opentv_prov +{ + char *key; + RB_ENTRY(opentv_prov) h_link; + int enabled; + + int tsid; + int sid; + int *channel; + int *title; + int *summary; + opentv_dict_t *dict; +} opentv_prov_t; + +RB_HEAD(opentv_dict_tree, opentv_dict); +RB_HEAD(opentv_prov_tree, opentv_prov); +struct opentv_dict_tree _opentv_dicts; +struct opentv_prov_tree _opentv_provs; + +static int _dict_cmp ( void *a, void *b ) +{ + return strcmp(((opentv_dict_t*)a)->key, ((opentv_dict_t*)b)->key); +} + +static int _prov_cmp ( void *a, void *b ) +{ + return strcmp(((opentv_prov_t*)a)->key, ((opentv_prov_t*)b)->key); +} + +static int* _pid_list_to_array ( htsmsg_t *m ) +{ + int i = 1; + int *ret; + htsmsg_field_t *f; + HTSMSG_FOREACH(f, m) + if (f->hmf_s64) i++; + ret = calloc(i, sizeof(int)); + i = 0; + HTSMSG_FOREACH(f, m) + if (f->hmf_s64) + ret[i++] = (int)f->hmf_s64; + return ret; +} + +static opentv_dict_t *_opentv_dict_find ( const char *key ) +{ + opentv_dict_t skel; + skel.key = (char*)key; + return RB_FIND(&_opentv_dicts, &skel, h_link, _dict_cmp); +} + +#if 0 +static opentv_prov_t *_opentv_prov_find ( const char *key ) +{ + opentv_prov_t skel; + skel.key = (char*)key; + return RB_FIND(&_opentv_provs, &skel, h_link, _prov_cmp); +} +#endif + +static int _opentv_dict_load ( const char *key, htsmsg_t *m ) +{ + opentv_dict_t *dict = calloc(1, sizeof(opentv_dict_t)); + dict->key = (char*)key; + if (RB_INSERT_SORTED(&_opentv_dicts, dict, h_link, _dict_cmp)) { + tvhlog(LOG_WARNING, "opentv", "ignore duplicate dictionary %s", key); + free(dict); + return 0; + } else { + dict->codes = huffman_tree_build(m); + if (!dict->codes) { + RB_REMOVE(&_opentv_dicts, dict, h_link); + free(dict); + return -1; + } else { + dict->key = strdup(key); + return 1; + } + } +} + +static int _opentv_prov_load ( const char *key, htsmsg_t *m ) +{ + htsmsg_t *cl, *tl, *sl; + uint32_t tsid, sid; + const char *str; + opentv_dict_t *dict; + opentv_prov_t *prov; + + /* Check config */ + if (!(str = htsmsg_get_str(m, "dict"))) return -1; + if (!(dict = _opentv_dict_find(str))) return -1; + if (!(cl = htsmsg_get_list(m, "channel"))) return -1; + if (!(tl = htsmsg_get_list(m, "title"))) return -1; + if (!(sl = htsmsg_get_list(m, "summary"))) return -1; + if (htsmsg_get_u32(m, "tsid", &tsid)) return -1; + if (htsmsg_get_u32(m, "sid", &sid)) return -1; + + prov = calloc(1, sizeof(opentv_prov_t)); + prov->key = (char*)key; + if (RB_INSERT_SORTED(&_opentv_provs, prov, h_link, _prov_cmp)) { + tvhlog(LOG_WARNING, "opentv", "ignore duplicate provider %s", key); + free(prov); + return 0; + } else { + prov->key = strdup(key); + prov->dict = dict; + prov->tsid = tsid; + prov->sid = sid; + prov->channel = _pid_list_to_array(cl); + prov->title = _pid_list_to_array(tl); + prov->summary = _pid_list_to_array(sl); + return 1; + } +} + +static void _opentv_prov_add_tables + ( opentv_prov_t *prov, th_dvb_mux_instance_t *tdmi ) +{ + tvhlog(LOG_INFO, "opentv", "install provider %s", prov->key); + /* Channels */ + /* Titles */ + /* Summaries */ +#if 0 + struct dmx_sct_filter_params *fp; + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0xa8; + fp->filter.mask[0] = 0xfc; + tdt_add(tdmi, fp, _opentv_callback, NULL, "opentv", TDT_CRC, 0x40, NULL); +#endif +} + +/* ************************************************************************ + * Parser + * ***********************************************************************/ + +#if 0 +static void _parse_short_desc ( uint8_t *buf, int len, uint16_t eid ) +{ +#if 0 + char *str = huffman_decode(_opentv_dict, (char*)buf, len, 0x20); + printf("EID: %5d, SUMMARY: %s\n", eid, str); + free(str); +#endif +} + +static int _parse_record ( uint8_t *buf, int len, uint16_t eid ) +{ + uint8_t rtag = buf[0]; + uint8_t rlen = buf[1]; + if (rlen+2 > len) return rlen+2; + switch (rtag) { + case 0xb5: + //_parse_title(buf+2, rlen); + break; + case 0xb9: + _parse_short_desc(buf+2, rlen, eid); + break; + case 0xbb: + //_parse_long_desc(buf+2, rlen); + break; + case 0xc1: + //_parse_series_link(buf+2, rlen); + break; + default: + break; + } + return rlen + 2; +} + +static int _parse_summary ( uint8_t *buf, int len ) +{ + uint16_t eid = ((uint16_t)buf[0] << 8) | buf[1]; + int slen = ((int)buf[2] & 0xf << 8) | buf[3]; + int i = 4; + if (slen+4 > len) return slen+4; + while (i < slen+4) { + i += _parse_record(buf+i, len-i, eid); + } + return i; +} + +static int _opentv_callback + ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) +{ + // TODO: currently this will only get summary tables! + int i = 0; + //uint16_t cid, mjd; + if (len < 20) return 0; + //cid = ((uint16_t)buf[0] << 8) | buf[1]; + //mjd = ((uint16_t)buf[2] << 8) | buf[3]; + i = 10; + while (i < len) { + i += _parse_summary(buf+i, len-i); + } + return 0; +} +#endif /* ************************************************************************ * Module Setup * ***********************************************************************/ static epggrab_module_t _opentv_mod; -static huffman_node_t *_opentv_dict; +static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) +{ + opentv_prov_t *prov; + if (!m->enabled) return; + + /* Check if this in our list of enabled providers */ + RB_FOREACH(prov, &_opentv_provs, h_link) { + if (prov->enabled && prov->tsid == tdmi->tdmi_transport_stream_id) + _opentv_prov_add_tables(prov, tdmi); + } +} + +#if 0 static void* _opentv_thread ( void *p ) { - int err; + int err = 0; th_dvb_adapter_t *tda; th_dvb_mux_instance_t *tdmi; service_t *svc = NULL; pthread_mutex_lock(&global_lock); - printf("find service\n"); TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) { LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { - if ((svc = dvb_transport_find(tdmi, 4152, 0, NULL))) break; + if ((svc = dvb_transport_find(tdmi, 4152, 0, NULL))) { + _opentv_tdmi = tdmi; + break; + } } } @@ -59,19 +289,21 @@ static void* _opentv_thread ( void *p ) pthread_mutex_unlock(&global_lock); while (1) { + // check status? sleep(10); } return NULL; } +#endif static int _opentv_enable ( epggrab_module_t *m, uint8_t e ) { - pthread_t tid; - pthread_attr_t tattr; - pthread_attr_init(&tattr); - pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); - pthread_create(&tid, &tattr, _opentv_thread, NULL); - return 0; + int save = 0; + if (m->enabled != e) { + m->enabled = e; + save = 1; + } + return save; } void opentv_init ( epggrab_module_list_t *list ) @@ -79,14 +311,53 @@ void opentv_init ( epggrab_module_list_t *list ) _opentv_mod.id = strdup("opentv"); _opentv_mod.name = strdup("OpenTV EPG"); _opentv_mod.enable = _opentv_enable; + _opentv_mod.tune = _opentv_tune; *((uint8_t*)&_opentv_mod.flags) = EPGGRAB_MODULE_EXTERNAL; // TODO: hack LIST_INSERT_HEAD(list, &_opentv_mod, link); - // Note: this is mostly ignored anyway as EIT is treated as a special case! } void opentv_load ( void ) { - _opentv_dict = huffman_tree_load("epggrab/opentv/dict/skyuk"); - assert(_opentv_dict); - tvhlog(LOG_INFO, "opentv", "huffman tree loaded"); + int r; + htsmsg_t *m, *e; + htsmsg_field_t *f; + opentv_prov_t *p; + + /* Load the dictionaries */ + if ((m = hts_settings_load("epggrab/opentv/dict"))) { + HTSMSG_FOREACH(f, m) { + if ((e = htsmsg_get_list(m, f->hmf_name))) { + if ((r = _opentv_dict_load(f->hmf_name, e))) { + if (r > 0) + tvhlog(LOG_INFO, "opentv", "dictionary %s loaded", f->hmf_name); + else + tvhlog(LOG_WARNING, "opentv", "dictionary %s failed", f->hmf_name); + } + } + } + htsmsg_destroy(m); + } + tvhlog(LOG_INFO, "opentv", "dictonaries loaded"); + + /* Load providers */ + if ((m = hts_settings_load("epggrab/opentv/prov"))) { + HTSMSG_FOREACH(f, m) { + if ((e = htsmsg_get_map_by_field(f))) { + if ((r = _opentv_prov_load(f->hmf_name, e))) { + if (r > 0) + tvhlog(LOG_INFO, "opentv", "provider %s loaded", f->hmf_name); + else + tvhlog(LOG_WARNING, "opentv", "provider %s failed", f->hmf_name); + } + } + } + htsmsg_destroy(m); + } + tvhlog(LOG_INFO, "opentv", "providers loaded"); + + /* TODO: do this properly */ + RB_FOREACH(p, &_opentv_provs, h_link) { + p->enabled = 1; + tvhlog(LOG_INFO, "opentv", "enabled %s", p->key); + } } diff --git a/src/epggrab/opentv.h b/src/epggrab/opentv.h index ba087cfd..e46525d9 100644 --- a/src/epggrab/opentv.h +++ b/src/epggrab/opentv.h @@ -19,8 +19,10 @@ #ifndef __TVH_EPGGRAB_OPENTV_H__ #define __TVH_EPGGRAB_OPENTV_H__ +#include "dvb/dvb.h" #include "epggrab.h" +void opentv_tune ( th_dvb_mux_instance_t *tdmi ); void opentv_init ( epggrab_module_list_t *list ); void opentv_load ( void ); From 1bc57c7207d9fe88b657b83f23c5150bf39cc2e6 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 00:13:59 +0100 Subject: [PATCH 08/25] Minor tweak to huffman api. --- src/huffman.c | 2 +- src/huffman.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/huffman.c b/src/huffman.c index b9c68bb0..16a539a0 100644 --- a/src/huffman.c +++ b/src/huffman.c @@ -75,7 +75,7 @@ huffman_node_t *huffman_tree_build ( htsmsg_t *m ) } char *huffman_decode - ( huffman_node_t *tree, const char *data, size_t len, uint8_t mask ) + ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask ) { char* ret; size_t nalloc = len*4, nused = 0; diff --git a/src/huffman.h b/src/huffman.h index 08ce873e..1bdd0408 100644 --- a/src/huffman.h +++ b/src/huffman.h @@ -33,6 +33,6 @@ void huffman_tree_destroy ( huffman_node_t *tree ); huffman_node_t *huffman_tree_load ( const char *path ); huffman_node_t *huffman_tree_build ( htsmsg_t *codes ); char *huffman_decode - ( huffman_node_t *tree, const char *data, size_t len, uint8_t mask ); + ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask ); #endif From dd330025411506ea421079dfe196d1c74f532dc0 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 00:15:04 +0100 Subject: [PATCH 09/25] Lots of messing about with the parsing (too much trial and error), but I think I mostly have it now. But I still need to figure out how best to get the data into the EPG. --- src/epggrab/opentv.c | 274 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 231 insertions(+), 43 deletions(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index faa19e25..be0c6017 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -31,6 +31,9 @@ #include "htsmsg.h" #include "settings.h" +static epggrab_module_t _opentv_mod; +static epggrab_channel_tree_t _opentv_channels; + /* ************************************************************************ * Configuration * @@ -161,50 +164,45 @@ static int _opentv_prov_load ( const char *key, htsmsg_t *m ) } } -static void _opentv_prov_add_tables - ( opentv_prov_t *prov, th_dvb_mux_instance_t *tdmi ) -{ - tvhlog(LOG_INFO, "opentv", "install provider %s", prov->key); - /* Channels */ - /* Titles */ - /* Summaries */ -#if 0 - struct dmx_sct_filter_params *fp; - fp = dvb_fparams_alloc(); - fp->filter.filter[0] = 0xa8; - fp->filter.mask[0] = 0xfc; - tdt_add(tdmi, fp, _opentv_callback, NULL, "opentv", TDT_CRC, 0x40, NULL); -#endif -} - /* ************************************************************************ * Parser * ***********************************************************************/ -#if 0 -static void _parse_short_desc ( uint8_t *buf, int len, uint16_t eid ) +static char *_parse_string ( opentv_prov_t *prov, uint8_t *buf, int len ) { -#if 0 - char *str = huffman_decode(_opentv_dict, (char*)buf, len, 0x20); - printf("EID: %5d, SUMMARY: %s\n", eid, str); - free(str); -#endif + return huffman_decode(prov->dict->codes, buf, len, 0x20); } -static int _parse_record ( uint8_t *buf, int len, uint16_t eid ) +static int _parse_record ( opentv_prov_t *prov, uint8_t *buf, int len, uint16_t eid ) { + char *str; uint8_t rtag = buf[0]; uint8_t rlen = buf[1]; + printf("_parse_record(): rtag=%02X, rlen=%d\n", rtag, rlen); if (rlen+2 > len) return rlen+2; switch (rtag) { case 0xb5: - //_parse_title(buf+2, rlen); +#if 0 + str = _parse_string(buf+2, rlen); + if (str) { + printf("EID: %d, TITLE: %s\n", eid, str); + free(str); + } break; +#endif case 0xb9: - _parse_short_desc(buf+2, rlen, eid); + str = _parse_string(prov, buf+2, rlen); + if (str) { + printf("EID: %d, SUMMARY: %s\n", eid, str); + free(str); + } break; case 0xbb: - //_parse_long_desc(buf+2, rlen); + str = _parse_string(prov, buf+2, rlen); + if (str) { + printf("EID: %d, DESCRIPTION: %s\n", eid, str); + free(str); + } break; case 0xc1: //_parse_series_link(buf+2, rlen); @@ -215,40 +213,229 @@ static int _parse_record ( uint8_t *buf, int len, uint16_t eid ) return rlen + 2; } -static int _parse_summary ( uint8_t *buf, int len ) +static int _parse_summary ( opentv_prov_t *prov, uint8_t *buf, int len, uint16_t cid ) { uint16_t eid = ((uint16_t)buf[0] << 8) | buf[1]; int slen = ((int)buf[2] & 0xf << 8) | buf[3]; int i = 4; + printf("_parse_summary(): cid=%d, eid=%d, slen=%d\n", cid, eid, slen); if (slen+4 > len) return slen+4; while (i < slen+4) { - i += _parse_record(buf+i, len-i, eid); + i += _parse_record(prov, buf+i, len-i, eid); } - return i; + return slen+4; } -static int _opentv_callback +static int _opentv_summary_callback ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) { // TODO: currently this will only get summary tables! int i = 0; - //uint16_t cid, mjd; - if (len < 20) return 0; - //cid = ((uint16_t)buf[0] << 8) | buf[1]; - //mjd = ((uint16_t)buf[2] << 8) | buf[3]; - i = 10; + uint16_t cid, mjd; + //if (len < 20) return 0; + cid = ((uint16_t)buf[0] << 8) | buf[1]; + mjd = ((uint16_t)buf[5] << 8) | buf[6]; + printf("summary callback(): len=%d, cid=%d, mjd=%d\n", len, cid, mjd); + for ( i = 0; i < 100; i++ ) { + printf("0x%02x ", buf[i]); + } + printf("\n"); + i = 7; while (i < len) { - i += _parse_summary(buf+i, len-i); + i += _parse_summary((opentv_prov_t*)p, buf+i, len-i, cid); } return 0; } -#endif + +static channel_t *_find_channel ( int tsid, int sid ) +{ + th_dvb_adapter_t *tda; + th_dvb_mux_instance_t *tdmi; + service_t *t = NULL; + + TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) { + LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { + if (tdmi->tdmi_transport_stream_id != tsid) continue; + LIST_FOREACH(t, &tdmi->tdmi_transports, s_group_link) { + if (t->s_dvb_service_id == sid) return t->s_ch; + } + } + } + return NULL; +} + +static int _opentv_channel_callback + ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) +{ + static epggrab_channel_t *ec; + int tsid, cid, cnum, bid; + uint16_t sid; + int i, j, k, tdlen, dlen, dtag, tslen; + char chid[16]; + channel_t *ch; + if (tid != 0x4a) return 0; + + // TODO: bouqets + bid = ((int)buf[0] << 8) | buf[1]; + printf("BID: %d\n", bid); + i = 7 + ((((int)buf[5] & 0xf) << 8) | buf[6]); + tslen = (((int)buf[i] & 0xf) << 8) | buf[i+1]; + printf("TSLEN: %d\n", tslen); + // TODO: this isn't quite right (should use tslen) + i += 2; + while (tslen > 0) { + tsid = ((int)buf[i] << 8) | buf[i+1]; + //nid = ((int)buf[i+2] << 8) | buf[i+3]; + tdlen = (((int)buf[i+4] & 0xf) << 8) | buf[i+5]; + j = i + 6; + i += (tdlen + 6); + tslen -= (tdlen + 6); + while (tdlen > 0) { + dtag = buf[j]; + dlen = buf[j+1]; + k = j + 2; + j += (dlen + 2); + tdlen -= (dlen + 2); + printf("dtag = 0x%02x, dlen=%d\n", dtag, dlen); + if (dtag == 0xb1) { + k += 2; + dlen -= 2; + while (dlen > 0) { + sid = ((int)buf[k] << 8) | buf[k+1]; + cid = ((int)buf[k+3] << 8) | buf[k+4]; + cnum = ((int)buf[k+5] << 8) | buf[k+6]; + + /* Find the channel */ + ch = _find_channel(tsid, sid); + if (ch) { + printf("FOUND tsid=%d, sid=%d, cid=%d, num=%d, ch=%s\n", + tsid, sid, cid, cnum, ch->ch_name); + int save = 0; + sprintf(chid, "opentv-%u", cid); + ec = epggrab_module_channel_find(&_opentv_mod, chid, 1, &save); + if (save) { + ec->channel = ch; // Note: could use set_sid() but not nec. + epggrab_channel_set_number(ec, cnum); + epggrab_channel_updated(ec); + } + } else { + printf("NOT FOUND tsid=%d, cid=%d, cnum=%d\n", tsid, cid, cnum); + } + k += 9; + dlen -= 9; + } + } + } + } + return 0; +} + +static int _opentv_title_callback + ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) +{ + int save = 0, save2 = 0; + char chid[16]; + int cid, mjd, i, eid; + time_t start, stop; + //uint8_t cat; + int l1, l2; + char *title; + channel_t *ch; + opentv_prov_t *prov = (opentv_prov_t*)p; + epg_broadcast_t *ebc; + epg_episode_t *ee; + char *uri; + + if (len < 20) return 0; + + /* Get channel/time */ + cid = ((int)buf[0] << 8) | buf[1]; + mjd = ((int)buf[5] << 8) | buf[6]; + sprintf(chid, "opentv-%d", cid); + epggrab_channel_t *ec = epggrab_module_channel_find(&_opentv_mod, chid, 0, NULL); + if (!ec || !ec->channel) { + printf("NOT FOUND cid=%d\n", cid); + return 0; + } + ch = ec->channel; + printf("opentv channel %s, len=%d\n", ch->ch_name, len); + + /* Process events */ + i = 7; + while ( i < len ) { + eid = ((int)buf[i] << 8) | buf[i+1]; + l1 = (((int)buf[i+2] & 0xf) << 8) | buf[i+3]; + printf("l1 = %d\n", l1); + if (buf[i+4] != 0xb5) return 0; + i += 4; + l2 = buf[i+1] - 7; + printf("l2 = %d\n", l2); + start = ((mjd - 40587) * 86400) + (((int)buf[i+2] << 9) | (buf[i+3] << 1)); + stop = start + (((int)buf[i+4] << 9) | (buf[i+5] << 1)); + title = huffman_decode(prov->dict->codes, buf+i+9, l2, 0x20); + uri = md5sum(title); + //cat = buf[i+6]; + printf("ch=%s, eid=%d, start=%lu, stop=%lu, title=%s\n", + ch->ch_name, eid, start, stop, title); + i += l1; + save = 0; + ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save); + if (ebc) { + ee = epg_episode_find_by_uri(uri, 1, &save); + save |= epg_episode_set_title(ee, title); + save |= epg_broadcast_set_episode(ebc, ee); + save2 = 1; + } + free(uri); + free(title); + } + if (save2) epg_updated(); + return 0; +} /* ************************************************************************ * Module Setup * ***********************************************************************/ -static epggrab_module_t _opentv_mod; +static void _opentv_prov_add_tables + ( opentv_prov_t *prov, th_dvb_mux_instance_t *tdmi ) +{ + int *t; + struct dmx_sct_filter_params *fp; + tvhlog(LOG_INFO, "opentv", "install provider %s", prov->key); + + /* Channels */ + t = prov->channel; + while (*t) { + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0x4a; + fp->filter.mask[0] = 0xff; + // TODO: what about 0x46 (service description) + printf("add filter pid=%d\n", *t); + tdt_add(tdmi, fp, _opentv_channel_callback, prov, + "opentv-c", TDT_CRC, *t++, NULL); + } + + /* Titles */ + t = prov->title; + while (*t) { + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0xa0; + fp->filter.mask[0] = 0xfc; + tdt_add(tdmi, fp, _opentv_title_callback, prov, + "opentv-t", TDT_CRC, *t++, NULL); + } + + /* Summaries */ + t = prov->summary; + while (*t) { + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0xa8; + fp->filter.mask[0] = 0xfc; + tdt_add(tdmi, fp, _opentv_summary_callback, prov, + "opentv-s", TDT_CRC, *t++, NULL); + } +} static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) { @@ -308,10 +495,11 @@ static int _opentv_enable ( epggrab_module_t *m, uint8_t e ) void opentv_init ( epggrab_module_list_t *list ) { - _opentv_mod.id = strdup("opentv"); - _opentv_mod.name = strdup("OpenTV EPG"); - _opentv_mod.enable = _opentv_enable; - _opentv_mod.tune = _opentv_tune; + _opentv_mod.id = strdup("opentv"); + _opentv_mod.name = strdup("OpenTV EPG"); + _opentv_mod.enable = _opentv_enable; + _opentv_mod.tune = _opentv_tune; + _opentv_mod.channels = &_opentv_channels; *((uint8_t*)&_opentv_mod.flags) = EPGGRAB_MODULE_EXTERNAL; // TODO: hack LIST_INSERT_HEAD(list, &_opentv_mod, link); } From b5577b7a95c2bbb0d90750f041bdd81eaac108e2 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 01:01:22 +0100 Subject: [PATCH 10/25] More hacking around the opentv code will now build a full EPG with title and summary. The other info is currently not included and things need a good tidy up. --- src/epg.c | 9 ++++ src/epg.h | 2 + src/epggrab/opentv.c | 97 ++++++++++++++++++++------------------------ 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/epg.c b/src/epg.c index cbbd4314..50108762 100644 --- a/src/epg.c +++ b/src/epg.c @@ -1318,6 +1318,15 @@ epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, channel_t *ch ) return (epg_broadcast_t*)_epg_object_find_by_id(id); } +epg_broadcast_t *epg_broadcast_find_by_eid ( int eid, channel_t *ch ) +{ + epg_broadcast_t *e; + RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) { + if (e->dvb_eid == eid) return e; + } + return NULL; +} + int epg_broadcast_set_episode ( epg_broadcast_t *broadcast, epg_episode_t *episode ) { diff --git a/src/epg.h b/src/epg.h index a5ea1367..defa092d 100644 --- a/src/epg.h +++ b/src/epg.h @@ -283,6 +283,7 @@ struct epg_broadcast { epg_object_t; ///< Parent object + int dvb_eid; ///< DVB Event ID time_t start; ///< Start time time_t stop; ///< End time @@ -312,6 +313,7 @@ struct epg_broadcast epg_broadcast_t *epg_broadcast_find_by_time ( struct channel *ch, time_t start, time_t stop, int create, int *save ); epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, struct channel *ch ); +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 ) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index be0c6017..71160ffb 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -168,21 +168,28 @@ static int _opentv_prov_load ( const char *key, htsmsg_t *m ) * Parser * ***********************************************************************/ +static epggrab_channel_t *_opentv_find_channel ( int cid, int create, int *save ) +{ + char chid[16]; + sprintf(chid, "opentv-%d", cid); + return epggrab_module_channel_find(&_opentv_mod, chid, create, save); +} + static char *_parse_string ( opentv_prov_t *prov, uint8_t *buf, int len ) { return huffman_decode(prov->dict->codes, buf, len, 0x20); } -static int _parse_record ( opentv_prov_t *prov, uint8_t *buf, int len, uint16_t eid ) +static int _parse_record ( opentv_prov_t *prov, uint8_t *buf, int len, epg_broadcast_t *ebc, int *save ) { + epg_episode_t *ee; char *str; uint8_t rtag = buf[0]; uint8_t rlen = buf[1]; - printf("_parse_record(): rtag=%02X, rlen=%d\n", rtag, rlen); if (rlen+2 > len) return rlen+2; switch (rtag) { - case 0xb5: #if 0 + case 0xb5: str = _parse_string(buf+2, rlen); if (str) { printf("EID: %d, TITLE: %s\n", eid, str); @@ -193,16 +200,17 @@ static int _parse_record ( opentv_prov_t *prov, uint8_t *buf, int len, uint16_t case 0xb9: str = _parse_string(prov, buf+2, rlen); if (str) { - printf("EID: %d, SUMMARY: %s\n", eid, str); + char *uri = md5sum(str); + ee = epg_episode_find_by_uri(uri, 1, save); + if (ee) *save |= epg_episode_set_summary(ee, str); + if (ee) *save |= epg_broadcast_set_episode(ebc, ee); free(str); + free(uri); } break; case 0xbb: - str = _parse_string(prov, buf+2, rlen); - if (str) { - printf("EID: %d, DESCRIPTION: %s\n", eid, str); - free(str); - } + //str = _parse_string(prov, buf+2, rlen); + // TODO break; case 0xc1: //_parse_series_link(buf+2, rlen); @@ -213,15 +221,16 @@ static int _parse_record ( opentv_prov_t *prov, uint8_t *buf, int len, uint16_t return rlen + 2; } -static int _parse_summary ( opentv_prov_t *prov, uint8_t *buf, int len, uint16_t cid ) +static int _parse_summary ( opentv_prov_t *prov, uint8_t *buf, int len, channel_t *ch, int *save ) { + epg_broadcast_t *ebc; uint16_t eid = ((uint16_t)buf[0] << 8) | buf[1]; int slen = ((int)buf[2] & 0xf << 8) | buf[3]; int i = 4; - printf("_parse_summary(): cid=%d, eid=%d, slen=%d\n", cid, eid, slen); + if (!(ebc = epg_broadcast_find_by_eid(eid, ch))) return slen+4; if (slen+4 > len) return slen+4; while (i < slen+4) { - i += _parse_record(prov, buf+i, len-i, eid); + i += _parse_record(prov, buf+i, len-i, ebc, save); } return slen+4; } @@ -229,21 +238,20 @@ static int _parse_summary ( opentv_prov_t *prov, uint8_t *buf, int len, uint16_t static int _opentv_summary_callback ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) { - // TODO: currently this will only get summary tables! + int save = 0; int i = 0; - uint16_t cid, mjd; - //if (len < 20) return 0; + uint16_t cid;//, mjd; + epggrab_channel_t *ec; + if (len < 20) return 0; cid = ((uint16_t)buf[0] << 8) | buf[1]; - mjd = ((uint16_t)buf[5] << 8) | buf[6]; - printf("summary callback(): len=%d, cid=%d, mjd=%d\n", len, cid, mjd); - for ( i = 0; i < 100; i++ ) { - printf("0x%02x ", buf[i]); - } - printf("\n"); + if (!(ec = _opentv_find_channel(cid, 0, NULL))) return 0; + if (!ec->channel) return 0; + //mjd = ((uint16_t)buf[5] << 8) | buf[6]; i = 7; while (i < len) { - i += _parse_summary((opentv_prov_t*)p, buf+i, len-i, cid); + i += _parse_summary((opentv_prov_t*)p, buf+i, len-i, ec->channel, &save); } + if (save) epg_updated(); return 0; } @@ -268,20 +276,15 @@ static int _opentv_channel_callback ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) { static epggrab_channel_t *ec; - int tsid, cid, cnum, bid; + int tsid, cid, cnum; uint16_t sid; int i, j, k, tdlen, dlen, dtag, tslen; - char chid[16]; channel_t *ch; if (tid != 0x4a) return 0; // TODO: bouqets - bid = ((int)buf[0] << 8) | buf[1]; - printf("BID: %d\n", bid); i = 7 + ((((int)buf[5] & 0xf) << 8) | buf[6]); tslen = (((int)buf[i] & 0xf) << 8) | buf[i+1]; - printf("TSLEN: %d\n", tslen); - // TODO: this isn't quite right (should use tslen) i += 2; while (tslen > 0) { tsid = ((int)buf[i] << 8) | buf[i+1]; @@ -296,7 +299,6 @@ static int _opentv_channel_callback k = j + 2; j += (dlen + 2); tdlen -= (dlen + 2); - printf("dtag = 0x%02x, dlen=%d\n", dtag, dlen); if (dtag == 0xb1) { k += 2; dlen -= 2; @@ -308,18 +310,13 @@ static int _opentv_channel_callback /* Find the channel */ ch = _find_channel(tsid, sid); if (ch) { - printf("FOUND tsid=%d, sid=%d, cid=%d, num=%d, ch=%s\n", - tsid, sid, cid, cnum, ch->ch_name); int save = 0; - sprintf(chid, "opentv-%u", cid); - ec = epggrab_module_channel_find(&_opentv_mod, chid, 1, &save); + ec = _opentv_find_channel(cid, 1, &save); if (save) { ec->channel = ch; // Note: could use set_sid() but not nec. epggrab_channel_set_number(ec, cnum); epggrab_channel_updated(ec); } - } else { - printf("NOT FOUND tsid=%d, cid=%d, cnum=%d\n", tsid, cid, cnum); } k += 9; dlen -= 9; @@ -334,16 +331,15 @@ static int _opentv_title_callback ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) { int save = 0, save2 = 0; - char chid[16]; int cid, mjd, i, eid; time_t start, stop; //uint8_t cat; int l1, l2; char *title; + epggrab_channel_t *ec; channel_t *ch; opentv_prov_t *prov = (opentv_prov_t*)p; epg_broadcast_t *ebc; - epg_episode_t *ee; char *uri; if (len < 20) return 0; @@ -351,40 +347,36 @@ static int _opentv_title_callback /* Get channel/time */ cid = ((int)buf[0] << 8) | buf[1]; mjd = ((int)buf[5] << 8) | buf[6]; - sprintf(chid, "opentv-%d", cid); - epggrab_channel_t *ec = epggrab_module_channel_find(&_opentv_mod, chid, 0, NULL); - if (!ec || !ec->channel) { - printf("NOT FOUND cid=%d\n", cid); - return 0; - } + if (!(ec = _opentv_find_channel(cid, 0, NULL))) return 0; + if (!ec->channel) return 0; ch = ec->channel; - printf("opentv channel %s, len=%d\n", ch->ch_name, len); /* Process events */ i = 7; while ( i < len ) { eid = ((int)buf[i] << 8) | buf[i+1]; l1 = (((int)buf[i+2] & 0xf) << 8) | buf[i+3]; - printf("l1 = %d\n", l1); if (buf[i+4] != 0xb5) return 0; i += 4; l2 = buf[i+1] - 7; - printf("l2 = %d\n", l2); start = ((mjd - 40587) * 86400) + (((int)buf[i+2] << 9) | (buf[i+3] << 1)); stop = start + (((int)buf[i+4] << 9) | (buf[i+5] << 1)); title = huffman_decode(prov->dict->codes, buf+i+9, l2, 0x20); uri = md5sum(title); //cat = buf[i+6]; - printf("ch=%s, eid=%d, start=%lu, stop=%lu, title=%s\n", - ch->ch_name, eid, start, stop, title); i += l1; save = 0; ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save); + save2 |= save; if (ebc) { - ee = epg_episode_find_by_uri(uri, 1, &save); - save |= epg_episode_set_title(ee, title); - save |= epg_broadcast_set_episode(ebc, ee); - save2 = 1; + // TODO: if EIDs do not match? + if (ebc->dvb_eid != eid) { + ebc->dvb_eid = eid; + save2 = 1; + } + if (ebc->episode) { + save2 |= epg_episode_set_title(ebc->episode, title); + } } free(uri); free(title); @@ -411,7 +403,6 @@ static void _opentv_prov_add_tables fp->filter.filter[0] = 0x4a; fp->filter.mask[0] = 0xff; // TODO: what about 0x46 (service description) - printf("add filter pid=%d\n", *t); tdt_add(tdmi, fp, _opentv_channel_callback, prov, "opentv-c", TDT_CRC, *t++, NULL); } From 8372a76425706d89088291599201c9632be4c3d1 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 13:32:06 +0100 Subject: [PATCH 11/25] Tidied up most of the parsing code and the way in which the config works (untested). --- src/epggrab/opentv.c | 544 +++++++++++++++++++++++-------------------- 1 file changed, 286 insertions(+), 258 deletions(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index 71160ffb..bdf8fd14 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -1,5 +1,5 @@ /* - * Electronic Program Guide - eit grabber + * Electronic Program Guide - opentv epg grabber * Copyright (C) 2012 Adam Sutton * * This program is free software: you can redistribute it and/or modify @@ -31,29 +31,32 @@ #include "htsmsg.h" #include "settings.h" -static epggrab_module_t _opentv_mod; -static epggrab_channel_tree_t _opentv_channels; - /* ************************************************************************ * 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 * ***********************************************************************/ +/* Huffman dictionary */ typedef struct opentv_dict { - char *key; + char *id; huffman_node_t *codes; RB_ENTRY(opentv_dict) h_link; } opentv_dict_t; +/* Provider configuration */ typedef struct opentv_prov { - char *key; + char *id; + char *name; RB_ENTRY(opentv_prov) h_link; - int enabled; + int nid; int tsid; int sid; int *channel; @@ -62,6 +65,16 @@ typedef struct opentv_prov opentv_dict_t *dict; } opentv_prov_t; +/* Extension of epggrab module to include linked provider */ +typedef struct opentv_module +{ + epggrab_module_t _; ///< Base struct + opentv_prov_t *prov; ///< Associated provider config +} opentv_module_t; + +/* + * Lists/Comparators + */ RB_HEAD(opentv_dict_tree, opentv_dict); RB_HEAD(opentv_prov_tree, opentv_prov); struct opentv_dict_tree _opentv_dicts; @@ -69,14 +82,25 @@ struct opentv_prov_tree _opentv_provs; static int _dict_cmp ( void *a, void *b ) { - return strcmp(((opentv_dict_t*)a)->key, ((opentv_dict_t*)b)->key); + return strcmp(((opentv_dict_t*)a)->id, ((opentv_dict_t*)b)->id); } static int _prov_cmp ( void *a, void *b ) { - return strcmp(((opentv_prov_t*)a)->key, ((opentv_prov_t*)b)->key); + return strcmp(((opentv_prov_t*)a)->id, ((opentv_prov_t*)b)->id); } +static opentv_dict_t *_opentv_dict_find ( const char *id ) +{ + opentv_dict_t skel; + skel.id = (char*)id; + return RB_FIND(&_opentv_dicts, &skel, h_link, _dict_cmp); +} + +/* + * Configuration loading + */ + static int* _pid_list_to_array ( htsmsg_t *m ) { int i = 1; @@ -92,28 +116,12 @@ static int* _pid_list_to_array ( htsmsg_t *m ) return ret; } -static opentv_dict_t *_opentv_dict_find ( const char *key ) -{ - opentv_dict_t skel; - skel.key = (char*)key; - return RB_FIND(&_opentv_dicts, &skel, h_link, _dict_cmp); -} - -#if 0 -static opentv_prov_t *_opentv_prov_find ( const char *key ) -{ - opentv_prov_t skel; - skel.key = (char*)key; - return RB_FIND(&_opentv_provs, &skel, h_link, _prov_cmp); -} -#endif - -static int _opentv_dict_load ( const char *key, htsmsg_t *m ) +static int _opentv_dict_load ( const char *id, htsmsg_t *m ) { opentv_dict_t *dict = calloc(1, sizeof(opentv_dict_t)); - dict->key = (char*)key; + dict->id = (char*)id; if (RB_INSERT_SORTED(&_opentv_dicts, dict, h_link, _dict_cmp)) { - tvhlog(LOG_WARNING, "opentv", "ignore duplicate dictionary %s", key); + tvhlog(LOG_WARNING, "opentv", "ignore duplicate dictionary %s", id); free(dict); return 0; } else { @@ -123,38 +131,42 @@ static int _opentv_dict_load ( const char *key, htsmsg_t *m ) free(dict); return -1; } else { - dict->key = strdup(key); + dict->id = strdup(id); return 1; } } } -static int _opentv_prov_load ( const char *key, htsmsg_t *m ) +static int _opentv_prov_load ( const char *id, htsmsg_t *m ) { htsmsg_t *cl, *tl, *sl; - uint32_t tsid, sid; - const char *str; + uint32_t tsid, sid, nid; + const char *str, *name; opentv_dict_t *dict; opentv_prov_t *prov; /* Check config */ + if (!(name = htsmsg_get_str(m, "name"))) return -1; if (!(str = htsmsg_get_str(m, "dict"))) return -1; if (!(dict = _opentv_dict_find(str))) return -1; if (!(cl = htsmsg_get_list(m, "channel"))) return -1; - if (!(tl = htsmsg_get_list(m, "title"))) return -1; + if (!(tl = htsmsg_get_list(m, "title"))) return -5; if (!(sl = htsmsg_get_list(m, "summary"))) return -1; + if (htsmsg_get_u32(m, "nid", &nid)) return -1; if (htsmsg_get_u32(m, "tsid", &tsid)) return -1; if (htsmsg_get_u32(m, "sid", &sid)) return -1; prov = calloc(1, sizeof(opentv_prov_t)); - prov->key = (char*)key; + prov->id = (char*)id; if (RB_INSERT_SORTED(&_opentv_provs, prov, h_link, _prov_cmp)) { - tvhlog(LOG_WARNING, "opentv", "ignore duplicate provider %s", key); + tvhlog(LOG_WARNING, "opentv", "ignore duplicate provider %s", id); free(prov); return 0; } else { - prov->key = strdup(key); + prov->id = strdup(id); + prov->name = strdup(name); prov->dict = dict; + prov->nid = nid; prov->tsid = tsid; prov->sid = sid; prov->channel = _pid_list_to_array(cl); @@ -165,102 +177,22 @@ static int _opentv_prov_load ( const char *key, htsmsg_t *m ) } /* ************************************************************************ - * Parser + * EPG Object wrappers * ***********************************************************************/ -static epggrab_channel_t *_opentv_find_channel ( int cid, int create, int *save ) +static epggrab_channel_t *_opentv_find_epggrab_channel + ( opentv_module_t *mod, int cid, int create, int *save ) { - char chid[16]; - sprintf(chid, "opentv-%d", cid); - return epggrab_module_channel_find(&_opentv_mod, chid, create, save); + char chid[32]; + sprintf(chid, "%s-%d", mod->prov->id, cid); + return epggrab_module_channel_find((epggrab_module_t*)mod, chid, create, save); } -static char *_parse_string ( opentv_prov_t *prov, uint8_t *buf, int len ) -{ - return huffman_decode(prov->dict->codes, buf, len, 0x20); -} - -static int _parse_record ( opentv_prov_t *prov, uint8_t *buf, int len, epg_broadcast_t *ebc, int *save ) -{ - epg_episode_t *ee; - char *str; - uint8_t rtag = buf[0]; - uint8_t rlen = buf[1]; - if (rlen+2 > len) return rlen+2; - switch (rtag) { -#if 0 - case 0xb5: - str = _parse_string(buf+2, rlen); - if (str) { - printf("EID: %d, TITLE: %s\n", eid, str); - free(str); - } - break; -#endif - case 0xb9: - str = _parse_string(prov, buf+2, rlen); - if (str) { - char *uri = md5sum(str); - ee = epg_episode_find_by_uri(uri, 1, save); - if (ee) *save |= epg_episode_set_summary(ee, str); - if (ee) *save |= epg_broadcast_set_episode(ebc, ee); - free(str); - free(uri); - } - break; - case 0xbb: - //str = _parse_string(prov, buf+2, rlen); - // TODO - break; - case 0xc1: - //_parse_series_link(buf+2, rlen); - break; - default: - break; - } - return rlen + 2; -} - -static int _parse_summary ( opentv_prov_t *prov, uint8_t *buf, int len, channel_t *ch, int *save ) -{ - epg_broadcast_t *ebc; - uint16_t eid = ((uint16_t)buf[0] << 8) | buf[1]; - int slen = ((int)buf[2] & 0xf << 8) | buf[3]; - int i = 4; - if (!(ebc = epg_broadcast_find_by_eid(eid, ch))) return slen+4; - if (slen+4 > len) return slen+4; - while (i < slen+4) { - i += _parse_record(prov, buf+i, len-i, ebc, save); - } - return slen+4; -} - -static int _opentv_summary_callback - ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) -{ - int save = 0; - int i = 0; - uint16_t cid;//, mjd; - epggrab_channel_t *ec; - if (len < 20) return 0; - cid = ((uint16_t)buf[0] << 8) | buf[1]; - if (!(ec = _opentv_find_channel(cid, 0, NULL))) return 0; - if (!ec->channel) return 0; - //mjd = ((uint16_t)buf[5] << 8) | buf[6]; - i = 7; - while (i < len) { - i += _parse_summary((opentv_prov_t*)p, buf+i, len-i, ec->channel, &save); - } - if (save) epg_updated(); - return 0; -} - -static channel_t *_find_channel ( int tsid, int sid ) +static channel_t *_opentv_find_channel ( int tsid, int sid ) { th_dvb_adapter_t *tda; th_dvb_mux_instance_t *tdmi; service_t *t = NULL; - TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) { LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { if (tdmi->tdmi_transport_stream_id != tsid) continue; @@ -272,17 +204,167 @@ static channel_t *_find_channel ( int tsid, int sid ) return NULL; } +/* ************************************************************************ + * OpenTV data processing + * ***********************************************************************/ + +typedef struct opentv_event +{ + int eid; + int start; + int duration; + char *title; + char *summary; + char *description; + int serieslink; + uint8_t cat; +} opentv_event_t; + +/* Parse huffman encoded string */ +static char *_parse_string ( opentv_prov_t *prov, uint8_t *buf, int len ) +{ + return huffman_decode(prov->dict->codes, buf, len, 0x20); +} + +/* Parse a specific record */ +static int _opentv_parse_event_record + ( opentv_prov_t *prov, opentv_event_t *ev, uint8_t *buf, int len ) +{ + 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->cat = buf[6]; + ev->title = _parse_string(prov, buf+7, rlen-7); + break; + case 0xb9: // summary + ev->summary = _parse_string(prov, buf+2, rlen); + break; + case 0xbb: // description + ev->description = _parse_string(prov, buf+2, rlen); + break; + case 0xc1: // series link + ev->serieslink = ((int)buf[2] << 8) | buf[3]; + break; + default: + break; + } + } + return rlen + 2; +} + +/* Parse a specific event */ +static int _opentv_parse_event + ( opentv_prov_t *prov, opentv_event_t *ev, uint8_t *buf, int len ) +{ + int slen = ((int)buf[2] & 0xf << 8) | buf[3]; + int i = 4; + ev->eid = ((uint16_t)buf[0] << 8) | buf[1]; + while (i < slen) { + i += _opentv_parse_event_record(prov, ev, buf+i, len-i); + } + return slen+4; +} + +/* Parse an event section */ +static int _opentv_parse_event_section + ( opentv_module_t *mod, uint8_t *buf, int len ) +{ + int i, cid, mjd, save = 0; + char *uri; + epggrab_channel_t *ec; + epg_broadcast_t *ebc; + epg_episode_t *ee; + opentv_event_t ev; + + /* Channel */ + cid = ((int)buf[0] << 8) | buf[1]; + if (!(ec = _opentv_find_epggrab_channel(mod, cid, 0, NULL))) return 0; + if (!ec->channel) return 0; + + /* Time (start/stop referenced to this) */ + mjd = ((int)buf[5] << 8) | buf[6]; + + /* 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); + + /* Process the event */ + + /* Create/Find broadcast */ + if (ev.start && ev.duration) { + time_t start = ev.start + ((mjd - 40587) * 86400); + time_t stop = ev.start + ev.duration; + ebc = epg_broadcast_find_by_time(ec->channel, start, stop, 1, &save); + } else { + ebc = epg_broadcast_find_by_eid(ev.eid, ec->channel); + } + + if (ebc) { + if (ebc->dvb_eid != ev.eid) { + ebc->dvb_eid = ev.eid; + save = 1; + } + + /* Create/Find episode */ + if (ev.description || ev.summary) { + uri = md5sum(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.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); + // TODO: series link + + save |= epg_broadcast_set_episode(ebc, ee); + } + } + + /* Cleanup */ + if (ev.title) free(ev.title); + if (ev.summary) free(ev.summary); + if (ev.description) free(ev.description); + } + + /* Update EPG */ + if (save) epg_updated(); + return 0; +} + +static int _opentv_event_callback + ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) +{ + return _opentv_parse_event_section((opentv_module_t*)p, 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 epggrab_channel_t *ec; - int tsid, cid, cnum; + 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; if (tid != 0x4a) return 0; - - // TODO: bouqets i = 7 + ((((int)buf[5] & 0xf) << 8) | buf[6]); tslen = (((int)buf[i] & 0xf) << 8) | buf[i+1]; i += 2; @@ -305,17 +387,18 @@ static int _opentv_channel_callback while (dlen > 0) { sid = ((int)buf[k] << 8) | buf[k+1]; cid = ((int)buf[k+3] << 8) | buf[k+4]; - cnum = ((int)buf[k+5] << 8) | buf[k+6]; + //cnum = ((int)buf[k+5] << 8) | buf[k+6]; /* Find the channel */ - ch = _find_channel(tsid, sid); + ch = _opentv_find_channel(tsid, sid); if (ch) { int save = 0; - ec = _opentv_find_channel(cid, 1, &save); + ec = _opentv_find_epggrab_channel(mod, cid, 1, &save); if (save) { - ec->channel = ch; // Note: could use set_sid() but not nec. - epggrab_channel_set_number(ec, cnum); - epggrab_channel_updated(ec); + // Note: could use set_sid() but not nec. + ec->channel = ch; + //TODO: must be configurable + //epggrab_channel_set_number(ec, cnum); } } k += 9; @@ -327,119 +410,10 @@ static int _opentv_channel_callback return 0; } -static int _opentv_title_callback - ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) -{ - int save = 0, save2 = 0; - int cid, mjd, i, eid; - time_t start, stop; - //uint8_t cat; - int l1, l2; - char *title; - epggrab_channel_t *ec; - channel_t *ch; - opentv_prov_t *prov = (opentv_prov_t*)p; - epg_broadcast_t *ebc; - char *uri; - - if (len < 20) return 0; - - /* Get channel/time */ - cid = ((int)buf[0] << 8) | buf[1]; - mjd = ((int)buf[5] << 8) | buf[6]; - if (!(ec = _opentv_find_channel(cid, 0, NULL))) return 0; - if (!ec->channel) return 0; - ch = ec->channel; - - /* Process events */ - i = 7; - while ( i < len ) { - eid = ((int)buf[i] << 8) | buf[i+1]; - l1 = (((int)buf[i+2] & 0xf) << 8) | buf[i+3]; - if (buf[i+4] != 0xb5) return 0; - i += 4; - l2 = buf[i+1] - 7; - start = ((mjd - 40587) * 86400) + (((int)buf[i+2] << 9) | (buf[i+3] << 1)); - stop = start + (((int)buf[i+4] << 9) | (buf[i+5] << 1)); - title = huffman_decode(prov->dict->codes, buf+i+9, l2, 0x20); - uri = md5sum(title); - //cat = buf[i+6]; - i += l1; - save = 0; - ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save); - save2 |= save; - if (ebc) { - // TODO: if EIDs do not match? - if (ebc->dvb_eid != eid) { - ebc->dvb_eid = eid; - save2 = 1; - } - if (ebc->episode) { - save2 |= epg_episode_set_title(ebc->episode, title); - } - } - free(uri); - free(title); - } - if (save2) epg_updated(); - return 0; -} - /* ************************************************************************ - * Module Setup + * Tuning Thread * ***********************************************************************/ -static void _opentv_prov_add_tables - ( opentv_prov_t *prov, th_dvb_mux_instance_t *tdmi ) -{ - int *t; - struct dmx_sct_filter_params *fp; - tvhlog(LOG_INFO, "opentv", "install provider %s", prov->key); - - /* Channels */ - t = prov->channel; - while (*t) { - fp = dvb_fparams_alloc(); - fp->filter.filter[0] = 0x4a; - fp->filter.mask[0] = 0xff; - // TODO: what about 0x46 (service description) - tdt_add(tdmi, fp, _opentv_channel_callback, prov, - "opentv-c", TDT_CRC, *t++, NULL); - } - - /* Titles */ - t = prov->title; - while (*t) { - fp = dvb_fparams_alloc(); - fp->filter.filter[0] = 0xa0; - fp->filter.mask[0] = 0xfc; - tdt_add(tdmi, fp, _opentv_title_callback, prov, - "opentv-t", TDT_CRC, *t++, NULL); - } - - /* Summaries */ - t = prov->summary; - while (*t) { - fp = dvb_fparams_alloc(); - fp->filter.filter[0] = 0xa8; - fp->filter.mask[0] = 0xfc; - tdt_add(tdmi, fp, _opentv_summary_callback, prov, - "opentv-s", TDT_CRC, *t++, NULL); - } -} - -static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) -{ - opentv_prov_t *prov; - if (!m->enabled) return; - - /* Check if this in our list of enabled providers */ - RB_FOREACH(prov, &_opentv_provs, h_link) { - if (prov->enabled && prov->tsid == tdmi->tdmi_transport_stream_id) - _opentv_prov_add_tables(prov, tdmi); - } -} - #if 0 static void* _opentv_thread ( void *p ) { @@ -474,8 +448,58 @@ static void* _opentv_thread ( void *p ) } #endif +/* ************************************************************************ + * Module Setup + * ***********************************************************************/ + +static epggrab_channel_tree_t _opentv_channels; + +static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) +{ + int *t; + struct dmx_sct_filter_params *fp; + opentv_module_t *mod = (opentv_module_t*)m; + + /* Install tables */ + if (m->enabled && (mod->prov->tsid == tdmi->tdmi_transport_stream_id)) { + tvhlog(LOG_INFO, "opentv", "install provider %s tables", mod->prov->id); + + /* Channels */ + t = mod->prov->channel; + while (*t) { + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0x4a; + 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); + } + + /* Titles */ + t = mod->prov->title; + while (*t) { + 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); + } + + /* Summaries */ + t = mod->prov->summary; + while (*t) { + 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); + } + } +} + static int _opentv_enable ( epggrab_module_t *m, uint8_t e ) { + // TODO: do I need to kick off a thread here or elsewhere? int save = 0; if (m->enabled != e) { m->enabled = e; @@ -485,22 +509,13 @@ static int _opentv_enable ( epggrab_module_t *m, uint8_t e ) } void opentv_init ( epggrab_module_list_t *list ) -{ - _opentv_mod.id = strdup("opentv"); - _opentv_mod.name = strdup("OpenTV EPG"); - _opentv_mod.enable = _opentv_enable; - _opentv_mod.tune = _opentv_tune; - _opentv_mod.channels = &_opentv_channels; - *((uint8_t*)&_opentv_mod.flags) = EPGGRAB_MODULE_EXTERNAL; // TODO: hack - LIST_INSERT_HEAD(list, &_opentv_mod, link); -} - -void opentv_load ( void ) { int r; htsmsg_t *m, *e; htsmsg_field_t *f; opentv_prov_t *p; + opentv_module_t *mod; + char buf[100]; /* Load the dictionaries */ if ((m = hts_settings_load("epggrab/opentv/dict"))) { @@ -533,10 +548,23 @@ void opentv_load ( void ) htsmsg_destroy(m); } tvhlog(LOG_INFO, "opentv", "providers loaded"); - - /* TODO: do this properly */ + + /* Create modules */ RB_FOREACH(p, &_opentv_provs, h_link) { - p->enabled = 1; - tvhlog(LOG_INFO, "opentv", "enabled %s", p->key); + mod = calloc(1, sizeof(opentv_module_t)); + sprintf(buf, "opentv-%s", p->id); + mod->_.id = strdup(buf); + sprintf(buf, "OpenTV: %s", p->name); + mod->_.name = strdup(buf); + mod->_.enable = _opentv_enable; + mod->_.tune = _opentv_tune; + mod->_.channels = &_opentv_channels; + *((uint8_t*)&mod->_.flags) = EPGGRAB_MODULE_OTA; + LIST_INSERT_HEAD(list, &mod->_, link); } } + +void opentv_load ( void ) +{ + // TODO: do we want to keep a list of channels stored? +} From a9ba8064208506e36c44161af1519b6e19c6ce86 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 13:33:02 +0100 Subject: [PATCH 12/25] Removed the EIT enabled flag and using proper OTA config system. --- src/epggrab.c | 15 ------ src/epggrab.h | 8 ++- src/epggrab/eit.c | 14 +++--- src/epggrab/pyepg.c | 2 +- src/epggrab/xmltv.c | 2 +- src/webui/extjs.c | 3 -- src/webui/static/app/epggrab.js | 87 +++++++++++++++++++++++++-------- 7 files changed, 79 insertions(+), 52 deletions(-) diff --git a/src/epggrab.c b/src/epggrab.c index 8302d3e2..c36b7bbb 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -29,7 +29,6 @@ pthread_mutex_t epggrab_mutex; pthread_cond_t epggrab_cond; /* Config */ -uint32_t epggrab_eitenabled; uint32_t epggrab_interval; epggrab_module_t* epggrab_module; epggrab_module_list_t epggrab_modules; @@ -664,7 +663,6 @@ static void _epggrab_load ( void ) /* Process */ if (m) { - htsmsg_get_u32(m, "eit", &epggrab_eitenabled); if (!htsmsg_get_u32(m, old ? "grab-interval" : "interval", &epggrab_interval)) if (old) epggrab_interval *= 3600; htsmsg_get_u32(m, "grab-enabled", &enabled); @@ -727,7 +725,6 @@ void epggrab_save ( void ) /* Save */ m = htsmsg_create_map(); - htsmsg_add_u32(m, "eitenabled", epggrab_eitenabled); htsmsg_add_u32(m, "interval", epggrab_interval); if ( epggrab_module ) htsmsg_add_str(m, "module", epggrab_module->id); @@ -743,17 +740,6 @@ void epggrab_save ( void ) htsmsg_destroy(m); } -int epggrab_set_eitenabled ( uint32_t eitenabled ) -{ - // TODO: could use module variable - int save = 0; - if ( epggrab_eitenabled != eitenabled ) { - save = 1; - epggrab_eitenabled = eitenabled; - } - return save; -} - int epggrab_set_interval ( uint32_t interval ) { int save = 0; @@ -813,7 +799,6 @@ int epggrab_enable_module_by_id ( const char *id, uint8_t e ) void epggrab_init ( void ) { /* Defaults */ - epggrab_eitenabled = 1; // on air grab enabled epggrab_interval = 12 * 3600; // hours epggrab_module = NULL; // disabled diff --git a/src/epggrab.h b/src/epggrab.h index 36e5d87a..88e3bb15 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -86,9 +86,9 @@ void epggrab_channel_link ( epggrab_channel_t *ch ); /* * Grabber flags */ -#define EPGGRAB_MODULE_SIMPLE 0x01 -#define EPGGRAB_MODULE_EXTERNAL 0x02 -#define EPGGRAB_MODULE_SPECIAL 0x04 +#define EPGGRAB_MODULE_INTERNAL 0x01 ///< IP based internally run +#define EPGGRAB_MODULE_EXTERNAL 0x02 ///< IP based externally run +#define EPGGRAB_MODULE_OTA 0x04 ///< DVB OTA EPGs /* * Grabber base class @@ -163,14 +163,12 @@ htsmsg_t* epggrab_module_list ( void ); * Configuration */ extern pthread_mutex_t epggrab_mutex; -extern uint32_t epggrab_eitenabled; extern uint32_t epggrab_interval; extern epggrab_module_t* epggrab_module; /* * Update */ -int epggrab_set_eitenabled ( uint32_t eitenabled ); int epggrab_set_interval ( uint32_t interval ); int epggrab_set_module ( epggrab_module_t *module ); int epggrab_set_module_by_id ( const char *id ); diff --git a/src/epggrab/eit.c b/src/epggrab/eit.c index 3670eacd..8ba807ec 100644 --- a/src/epggrab/eit.c +++ b/src/epggrab/eit.c @@ -22,8 +22,10 @@ #include "epg.h" #include "epggrab/eit.h" +static epggrab_module_t _eit_mod; + /* ************************************************************************ - * Module Setup + * Processing * ***********************************************************************/ static const char * @@ -47,13 +49,13 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, const char *description = NULL; char *uri; + /* Disabled? */ + if (!_eit_mod.enabled) return; + /* Ignore */ if (!ch || !ch->ch_name || !ch->ch_name[0]) return; if (!title) return; - /* Disabled? */ - if (!epggrab_eitenabled) return; - /* Find broadcast */ ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save); if (!ebc) return; @@ -97,13 +99,11 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, * Module Setup * ***********************************************************************/ -static epggrab_module_t _eit_mod; - void eit_init ( epggrab_module_list_t *list ) { _eit_mod.id = strdup("eit"); _eit_mod.name = strdup("EIT: On-Air Grabber"); - *((uint8_t*)&_eit_mod.flags) = EPGGRAB_MODULE_SPECIAL; + *((uint8_t*)&_eit_mod.flags) = EPGGRAB_MODULE_OTA; LIST_INSERT_HEAD(list, &_eit_mod, link); // Note: this is mostly ignored anyway as EIT is treated as a special case! } diff --git a/src/epggrab/pyepg.c b/src/epggrab/pyepg.c index 827ac798..72760838 100644 --- a/src/epggrab/pyepg.c +++ b/src/epggrab/pyepg.c @@ -427,7 +427,7 @@ void pyepg_init ( epggrab_module_list_t *list ) mod->ch_add = epggrab_module_channel_add; mod->ch_rem = epggrab_module_channel_rem; mod->ch_mod = epggrab_module_channel_mod; - *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_SIMPLE; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_INTERNAL; LIST_INSERT_HEAD(list, mod, link); _pyepg_module = mod; diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c index 0b959298..3cabdc9e 100644 --- a/src/epggrab/xmltv.c +++ b/src/epggrab/xmltv.c @@ -389,7 +389,7 @@ static void _xmltv_load_grabbers ( epggrab_module_list_t *list ) mod->name = malloc(200); mod->channels = &_xmltv_channels; sprintf((char*)mod->name, "XMLTV: %s", &outbuf[n]); - *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_SIMPLE; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_INTERNAL; mod->grab = epggrab_module_grab; mod->trans = epggrab_module_trans_xml; mod->parse = _xmltv_parse; diff --git a/src/webui/extjs.c b/src/webui/extjs.c index ee86d89c..d0dcdfbe 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -486,7 +486,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) pthread_mutex_lock(&epggrab_mutex); r = htsmsg_create_map(); - htsmsg_add_u32(r, "eitenabled", epggrab_eitenabled); if (epggrab_module) htsmsg_add_str(r, "module", epggrab_module->id); htsmsg_add_u32(r, "interval", epggrab_interval); @@ -514,8 +513,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) } else if (!strcmp(op, "saveSettings") ) { int save = 0; pthread_mutex_lock(&epggrab_mutex); - u32 = http_arg_get(&hc->hc_req_args, "eitenabled") ? 1 : 0; - save |= epggrab_set_eitenabled(u32); if ( (str = http_arg_get(&hc->hc_req_args, "interval")) ) save |= epggrab_set_interval(atoi(str)); if ( (str = http_arg_get(&hc->hc_req_args, "module")) ) diff --git a/src/webui/static/app/epggrab.js b/src/webui/static/app/epggrab.js index f889b476..30c30b71 100644 --- a/src/webui/static/app/epggrab.js +++ b/src/webui/static/app/epggrab.js @@ -7,8 +7,9 @@ tvheadend.epggrab = function() { /* * Module lists (I'm sure there is a better way!) */ - var EPGGRAB_MODULE_SIMPLE = 0x01; + var EPGGRAB_MODULE_INTERNAL = 0x01; var EPGGRAB_MODULE_EXTERNAL = 0x02; + var EPGGRAB_MODULE_OTA = 0x04; var moduleStore = new Ext.data.JsonStore({ root : 'entries', @@ -17,20 +18,23 @@ tvheadend.epggrab = function() { autoLoad : true, fields : [ 'id', 'name', 'path', 'flags', 'enabled' ] }); - var simpleModuleStore = new Ext.data.Store({ + var internalModuleStore = new Ext.data.Store({ recordType: moduleStore.recordType }); var externalModuleStore = new Ext.data.Store({ recordType: moduleStore.recordType }); + var otaModuleStore = new Ext.data.Store({ + recordType: moduleStore.recordType + }); moduleStore.on('load', function() { moduleStore.filterBy(function(r) { - return r.get('flags') & EPGGRAB_MODULE_SIMPLE; + return r.get('flags') & EPGGRAB_MODULE_INTERNAL; }); - r = new simpleModuleStore.recordType({ id: '', name : 'Disabled'}); - simpleModuleStore.add(r); + r = new internalModuleStore.recordType({ id: '', name : 'Disabled'}); + internalModuleStore.add(r); moduleStore.each(function(r) { - simpleModuleStore.add(r.copy()); + internalModuleStore.add(r.copy()); }); moduleStore.filterBy(function(r) { return r.get('flags') & EPGGRAB_MODULE_EXTERNAL; @@ -38,6 +42,12 @@ tvheadend.epggrab = function() { moduleStore.each(function(r) { externalModuleStore.add(r.copy()); }); + moduleStore.filterBy(function(r) { + return r.get('flags') & EPGGRAB_MODULE_OTA; + }); + moduleStore.each(function(r) { + otaModuleStore.add(r.copy()); + }); }); /* @@ -46,7 +56,7 @@ tvheadend.epggrab = function() { var confreader = new Ext.data.JsonReader( { root: 'epggrabSettings' }, - [ 'module', 'eitenabled', 'advanced', 'interval' ] + [ 'module', 'interval' ] ); /* **************************************************************** @@ -56,7 +66,7 @@ tvheadend.epggrab = function() { /* * Module selector */ - var simpleModule = new Ext.form.ComboBox({ + var internalModule = new Ext.form.ComboBox({ fieldLabel : 'Module', hiddenName : 'module', width : 300, @@ -66,7 +76,7 @@ tvheadend.epggrab = function() { editable : false, mode : 'local', triggerAction : 'all', - store : simpleModuleStore + store : internalModuleStore }); /* @@ -182,9 +192,38 @@ tvheadend.epggrab = function() { }, iconCls : 'icon-grid', }); - var advancedPanel = externalGrid; + + /* + * OTA modules + */ + + var otaSelectionModel = new Ext.grid.CheckboxSelectionModel({ + singleSelect : false, + listeners : { + 'rowselect' : function (s, ri, r) { + r.set('enabled', 1); + }, + 'rowdeselect' : function (s, ri, r) { + r.set('enabled', 0); + } + } + }); + + var otaGrid = new Ext.grid.EditorGridPanel({ + store : otaModuleStore, + cm : externalColumnModel, + sm : otaSelectionModel, + width : 600, + height : 150, + frame : false, + viewConfig : { + forceFit : true, + }, + iconCls : 'icon-grid', + }); + + /* HACK: get display working */ externalGrid.on('render', function(){ - // TODO: bit of hack to get selection working delay = new Ext.util.DelayedTask(function(){ rows = []; externalModuleStore.each(function(r){ @@ -194,16 +233,21 @@ tvheadend.epggrab = function() { }); delay.delay(100); }); + otaGrid.on('render', function(){ + delay = new Ext.util.DelayedTask(function(){ + rows = []; + otaModuleStore.each(function(r){ + if (r.get('enabled')) rows.push(r); + }); + otaSelectionModel.selectRecords(rows); + }); + delay.delay(100); + }); /* **************************************************************** * Form * ***************************************************************/ - var eitCheck = new Ext.form.Checkbox({ - fieldLabel : 'EIT Enabled', - name : 'eitenabled' - }); - var saveButton = new Ext.Button({ text : "Save configuration", tooltip : 'Save changes made to configuration below', @@ -231,12 +275,13 @@ tvheadend.epggrab = function() { defaultType : 'textfield', items : [ interval, - eitCheck, - simpleModule, + internalModule, intervalValue, intervalUnit, new Ext.form.Label({text: 'External Interfaces'}), - advancedPanel + externalGrid, + new Ext.form.Label({text: 'OTA Modules'}), + otaGrid, ], tbar: [ saveButton, @@ -264,6 +309,9 @@ tvheadend.epggrab = function() { externalModuleStore.each(function(r) { mods.push({id: r.get('id'), enabled: r.get('enabled') ? 1 : 0}); }); + otaModuleStore.each(function(r) { + mods.push({id: r.get('id'), enabled: r.get('enabled') ? 1 : 0}); + }); mods = Ext.util.JSON.encode(mods); confpanel.getForm().submit({ url : 'epggrab', @@ -280,4 +328,3 @@ tvheadend.epggrab = function() { return confpanel; } - From da8c45458915fee52a96661a2439ba0cf5d59b80 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 13:38:01 +0100 Subject: [PATCH 13/25] Allow multiple callbacks per PID (with limitations). --- src/dvb/dvb_tables.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index 62fabd0c..743ed66f 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -283,8 +283,13 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, { th_dvb_table_t *t; + // Allow multiple entries per PID, but only one per callback/opaque instance + // TODO: this could mean reading the same data multiple times, and not + // sure how well this will work! I know Andreas has some thoughts on + // this LIST_FOREACH(t, &tdmi->tdmi_tables, tdt_link) { - if(pid == t->tdt_pid) { + if(pid == t->tdt_pid && + tdt->tdt_callback == callback && tdt->tdt_opaque == opaque) { free(tdt); free(fparams); return; From 96668a6134a7227de517e0ee5712b6ecca45889d Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 13:38:36 +0100 Subject: [PATCH 14/25] Initial opentv data for skyuk. --- data/epggrab/opentv/dict/skyeng | 1 + data/epggrab/opentv/prov/skyuk | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 data/epggrab/opentv/dict/skyeng create mode 100644 data/epggrab/opentv/prov/skyuk diff --git a/data/epggrab/opentv/dict/skyeng b/data/epggrab/opentv/dict/skyeng new file mode 100644 index 00000000..076835e2 --- /dev/null +++ b/data/epggrab/opentv/dict/skyeng @@ -0,0 +1 @@ +[{"code": "101010111000110000101001100", "data": "\u0000"}, {"code": "101010111000110000101001101", "data": "\u0001"}, {"code": "101010111000110000101001110", "data": "\u0002"}, {"code": "101010111000110000101001111", "data": "\u0003"}, {"code": "101010111000110000101010000", "data": "\u0004"}, {"code": "101010111000110000101010001", "data": "\u0005"}, {"code": "101010111000110000101010010", "data": "\u0006"}, {"code": "101010111000110000101010011", "data": "\u0007"}, {"code": "101010111000110000101010100", "data": "\b"}, {"code": "0001000", "data": "\t"}, {"code": "1110111", "data": "\n"}, {"code": "101010111000110000101010101", "data": "\u000b"}, {"code": "101010111000110000101010110", "data": "\f"}, {"code": "101010111000110000101010111", "data": "\r"}, {"code": "101010111000110000101011000", "data": "\u000e"}, {"code": "101010111000110000101011001", "data": "\u000f"}, {"code": "101010111000110000101011010", "data": "\u0010"}, {"code": "101010111000110000101011011", "data": "\u0011"}, {"code": "101010111000110000101011100", "data": "\u0012"}, {"code": "101010111000110000101011101", "data": "\u0013"}, {"code": "101010111000110000101011110", "data": "\u0014"}, {"code": "101010111000110000101011111", "data": "\u0015"}, {"code": "101010111000110000101100000", "data": "\u0016"}, {"code": "101010111000110000101100001", "data": "\u0017"}, {"code": "101010111000110000101100010", "data": "\u0018"}, {"code": "101010111000110000101100011", "data": "\u0019"}, {"code": "101010111000110000101100100", "data": "\u001a"}, {"code": "101010111000110000101100101", "data": "\u001b"}, {"code": "101010111000110000101100110", "data": "\u001c"}, {"code": "101010111000110000101100111", "data": "\u001d"}, {"code": "101010111000110000101101000", "data": "\u001e"}, {"code": "101010111000110000101101001", "data": "\u001f"}, {"code": "110", "data": " "}, {"code": "01000011000", "data": "!"}, {"code": "101010111000110000101101010", "data": "\""}, {"code": "101010111000110000101101011", "data": "#"}, {"code": "1010101110001101", "data": "$"}, {"code": "101010111000110000101101100", "data": "%"}, {"code": "10000011101", "data": "&"}, {"code": "10000010", "data": "'"}, {"code": "11101000101", "data": "("}, {"code": "1010101100", "data": ")"}, {"code": "100010101110", "data": "*"}, {"code": "101010111000110000101101101", "data": "+"}, {"code": "1011000", "data": ","}, {"code": "10001011", "data": "-"}, {"code": "1110110", "data": "."}, {"code": "00010100011110", "data": "/"}, {"code": "111010010", "data": "0"}, {"code": "101100111", "data": "1"}, {"code": "1000101000", "data": "2"}, {"code": "1000001111", "data": "3"}, {"code": "0001010000", "data": "4"}, {"code": "1110101000", "data": "5"}, {"code": "1000101001", "data": "6"}, {"code": "1000100010", "data": "7"}, {"code": "10001010110", "data": "8"}, {"code": "0100001101", "data": "9"}, {"code": "11101000110", "data": ":"}, {"code": "00010100010", "data": ";"}, {"code": "1110100011111", "data": "<"}, {"code": "101010111000110000101101110", "data": "="}, {"code": "1110101001100", "data": ">"}, {"code": "111010100111", "data": "?"}, {"code": "101010111000110001", "data": "@"}, {"code": "11100010", "data": "A"}, {"code": "01000000", "data": "B"}, {"code": "01000010", "data": "C"}, {"code": "111000111", "data": "D"}, {"code": "1110100000", "data": "E"}, {"code": "101010100", "data": "F"}, {"code": "100010000", "data": "G"}, {"code": "101010101", "data": "H"}, {"code": "1110100001", "data": "I"}, {"code": "000101001", "data": "J"}, {"code": "1110100111", "data": "K"}, {"code": "100000110", "data": "L"}, {"code": "10001001", "data": "M"}, {"code": "111010111", "data": "N"}, {"code": "010000010", "data": "O"}, {"code": "00010101", "data": "P"}, {"code": "1000101010111", "data": "Q"}, {"code": "111010110", "data": "R"}, {"code": "0001001", "data": "S"}, {"code": "0001011", "data": "T"}, {"code": "10101011101", "data": "U"}, {"code": "11101010101", "data": "V"}, {"code": "10110010", "data": "W"}, {"code": "1110001101101111", "data": "X"}, {"code": "10101011110", "data": "Y"}, {"code": "1110101010000", "data": "Z"}, {"code": "10101011100011001", "data": "["}, {"code": "101010111000110000101101111", "data": "\\"}, {"code": "11100011011011100", "data": "]"}, {"code": "101010111000110000101110000", "data": "^"}, {"code": "101010111000110000101110001", "data": "_"}, {"code": "11101010010", "data": "`"}, {"code": "1001", "data": "a"}, {"code": "1110000", "data": "b"}, {"code": "111001", "data": "c"}, {"code": "01001", "data": "d"}, {"code": "1111", "data": "e"}, {"code": "100001", "data": "f"}, {"code": "100011", "data": "g"}, {"code": "10111", "data": "h"}, {"code": "0101", "data": "i"}, {"code": "11100011010", "data": "j"}, {"code": "1000000", "data": "k"}, {"code": "10100", "data": "l"}, {"code": "101011", "data": "m"}, {"code": "0111", "data": "n"}, {"code": "0011", "data": "o"}, {"code": "000111", "data": "p"}, {"code": "10101011011", "data": "q"}, {"code": "0010", "data": "r"}, {"code": "0000", "data": "s"}, {"code": "0110", "data": "t"}, {"code": "101101", "data": "u"}, {"code": "1010100", "data": "v"}, {"code": "000110", "data": "w"}, {"code": "1110101011", "data": "x"}, {"code": "010001", "data": "y"}, {"code": "1011001100", "data": "z"}, {"code": "101010111000110000101110010", "data": "{"}, {"code": "101010111000110000101110011", "data": "|"}, {"code": "101010111000110000101110100", "data": "}"}, {"code": "101010111000110000101110101", "data": "~"}, {"code": "101010111000110000101110110", "data": ""}, {"code": "101010111000110000101110111", "data": "€"}, {"code": "101010111000110000101111000", "data": ""}, {"code": "101010111000110000101111001", "data": "‚"}, {"code": "101010111000110000101111010", "data": "ƒ"}, {"code": "101010111000110000101111011", "data": "„"}, {"code": "101010111000110000101111100", "data": "…"}, {"code": "101010111000110000101111101", "data": "†"}, {"code": "101010111000110000101111110", "data": "‡"}, {"code": "101010111000110000101111111", "data": "ˆ"}, {"code": "101010111000110000110000000", "data": "‰"}, {"code": "101010111000110000110000001", "data": "Š"}, {"code": "101010111000110000110000010", "data": "‹"}, {"code": "101010111000110000110000011", "data": "Œ"}, {"code": "101010111000110000110000100", "data": ""}, {"code": "101010111000110000110000101", "data": "Ž"}, {"code": "101010111000110000110000110", "data": ""}, {"code": "101010111000110000110000111", "data": "€"}, {"code": "101010111000110000110001000", "data": ""}, {"code": "101010111000110000110001001", "data": "‚"}, {"code": "101010111000110000110001010", "data": "ƒ"}, {"code": "101010111000110000110001011", "data": "„"}, {"code": "101010111000110000110001100", "data": "…"}, {"code": "101010111000110000110001101", "data": "†"}, {"code": "101010111000110000110001110", "data": "‡"}, {"code": "101010111000110000110001111", "data": "ˆ"}, {"code": "101010111000110000110010000", "data": "‰"}, {"code": "101010111000110000110010001", "data": "Š"}, {"code": "101010111000110000110010010", "data": "‹"}, {"code": "11100011011011101", "data": "Œ"}, {"code": "101010111000110000110010011", "data": ""}, {"code": "101010111000110000110010100", "data": "Ž"}, {"code": "101010111000110000110010101", "data": ""}, {"code": "101010111000110000110010110", "data": " "}, {"code": "101010111000110000110010111", "data": "¡"}, {"code": "101010111000110000110011000", "data": "¢"}, {"code": "101010111000110000110011001", "data": "£"}, {"code": "101010111000110000110011010", "data": "¤"}, {"code": "101010111000110000110011011", "data": "¥"}, {"code": "101010111000110000110011100", "data": "¦"}, {"code": "101010111000110000110011101", "data": "§"}, {"code": "101010111000110000110011110", "data": "¨"}, {"code": "101010111000110000110011111", "data": "©"}, {"code": "101010111000110000110100000", "data": "ª"}, {"code": "101010111000110000110100001", "data": "«"}, {"code": "101010111000110000110100010", "data": "¬"}, {"code": "101010111000110000110100011", "data": "­"}, {"code": "101010111000110000110100100", "data": "®"}, {"code": "101010111000110000110100101", "data": "¯"}, {"code": "101010111000110000110100110", "data": "°"}, {"code": "101010111000110000110100111", "data": "±"}, {"code": "101010111000110000110101000", "data": "²"}, {"code": "101010111000110000110101001", "data": "³"}, {"code": "101010111000110000110101010", "data": "´"}, {"code": "101010111000110000110101011", "data": "µ"}, {"code": "101010111000110000110101100", "data": "¶"}, {"code": "101010111000110000110101101", "data": "·"}, {"code": "101010111000110000110101110", "data": "¸"}, {"code": "101010111000110000110101111", "data": "¹"}, {"code": "101010111000110000110110000", "data": "º"}, {"code": "101010111000110000110110001", "data": "»"}, {"code": "101010111000110000110110010", "data": "¼"}, {"code": "101010111000110000110110011", "data": "½"}, {"code": "101010111000110000110110100", "data": "¾"}, {"code": "101010111000110000110110101", "data": "¿"}, {"code": "101010111000110000110110110", "data": "À"}, {"code": "101010111000110000110110111", "data": "Á"}, {"code": "101010111000110000110111000", "data": "Â"}, {"code": "101010111000110000110111001", "data": "Ã"}, {"code": "101010111000110000110111010", "data": "Ä"}, {"code": "101010111000110000110111011", "data": "Å"}, {"code": "101010111000110000110111100", "data": "Æ"}, {"code": "101010111000110000110111101", "data": "Ç"}, {"code": "101010111000110000110111110", "data": "È"}, {"code": "101010111000110000110111111", "data": "É"}, {"code": "101010111000110000111000000", "data": "Ê"}, {"code": "101010111000110000111000001", "data": "Ë"}, {"code": "101010111000110000111000010", "data": "Ì"}, {"code": "101010111000110000111000011", "data": "Í"}, {"code": "101010111000110000111000100", "data": "Î"}, {"code": "101010111000110000111000101", "data": "Ï"}, {"code": "101010111000110000111000110", "data": "Ð"}, {"code": "101010111000110000111000111", "data": "Ñ"}, {"code": "101010111000110000111001000", "data": "Ò"}, {"code": "101010111000110000111001001", "data": "Ó"}, {"code": "101010111000110000111001010", "data": "Ô"}, {"code": "101010111000110000111001011", "data": "Õ"}, {"code": "101010111000110000111001100", "data": "Ö"}, {"code": "101010111000110000111001101", "data": "×"}, {"code": "101010111000110000111001110", "data": "Ø"}, {"code": "101010111000110000111001111", "data": "Ù"}, {"code": "101010111000110000111010000", "data": "Ú"}, {"code": "101010111000110000111010001", "data": "Û"}, {"code": "101010111000110000111010010", "data": "Ü"}, {"code": "101010111000110000111010011", "data": "Ý"}, {"code": "101010111000110000111010100", "data": "Þ"}, {"code": "101010111000110000111010101", "data": "ß"}, {"code": "101010111000110000111010110", "data": "à"}, {"code": "101010111000110000111010111", "data": "á"}, {"code": "101010111000110000111011000", "data": "â"}, {"code": "101010111000110000111011001", "data": "ã"}, {"code": "101010111000110000111011010", "data": "ä"}, {"code": "101010111000110000111011011", "data": "å"}, {"code": "101010111000110000111011100", "data": "æ"}, {"code": "101010111000110000111011101", "data": "ç"}, {"code": "101010111000110000111011110", "data": "è"}, {"code": "101010111000110000111011111", "data": "é"}, {"code": "101010111000110000111100000", "data": "ê"}, {"code": "101010111000110000111100001", "data": "ë"}, {"code": "101010111000110000111100010", "data": "ì"}, {"code": "101010111000110000111100011", "data": "í"}, {"code": "101010111000110000111100100", "data": "î"}, {"code": "101010111000110000111100101", "data": "ï"}, {"code": "101010111000110000111100110", "data": "ð"}, {"code": "101010111000110000111100111", "data": "ñ"}, {"code": "101010111000110000111101000", "data": "ò"}, {"code": "101010111000110000111101001", "data": "ó"}, {"code": "101010111000110000111101010", "data": "ô"}, {"code": "101010111000110000111101011", "data": "õ"}, {"code": "101010111000110000111101100", "data": "ö"}, {"code": "101010111000110000111101101", "data": "÷"}, {"code": "101010111000110000111101110", "data": "ø"}, {"code": "101010111000110000111101111", "data": "ù"}, {"code": "101010111000110000111110000", "data": "ú"}, {"code": "101010111000110000111110001", "data": "û"}, {"code": "101010111000110000111110010", "data": "ü"}, {"code": "101010111000110000111110011", "data": "ý"}, {"code": "101010111000110000111110100", "data": "þ"}, {"code": "101010111000110000111110101", "data": "ÿ"}, {"code": "10101011111110", "data": "(Including "}, {"code": "11101000100010", "data": "(New Series)"}, {"code": "1110100011101", "data": "(Part "}, {"code": "1110100110", "data": "(Repeat)"}, {"code": "010000111", "data": "(Stereo)"}, {"code": "010000011", "data": "(Stereo) (Teletext)"}, {"code": "1110001100", "data": "(Teletext)"}, {"code": "100000111001110", "data": "(Widescreen)"}, {"code": "101010111000111", "data": "Action"}, {"code": "10110011011111", "data": "Adventures"}, {"code": "0100001100100", "data": "America"}, {"code": "111010100110111", "data": "Animated"}, {"code": "0100001100101", "data": "Australia"}, {"code": "11101010100010", "data": "Away"}, {"code": "10101011111111", "data": "BBC"}, {"code": "11100011011000", "data": "Baby"}, {"code": "11101010100011", "data": "Best"}, {"code": "10110011011000", "data": "Big"}, {"code": "1000101011111", "data": "Bill"}, {"code": "1000101010000", "data": "Black"}, {"code": "1011001101110", "data": "Blue"}, {"code": "000101000110", "data": "Breakfast"}, {"code": "1010101111100", "data": "Britain"}, {"code": "1110100011100", "data": "British"}, {"code": "0100001100110", "data": "Business"}, {"code": "1010101111101", "data": "Call"}, {"code": "10101011100000", "data": "Cartoon"}, {"code": "10101011100001", "data": "Channel"}, {"code": "11101010100111", "data": "Children"}, {"code": "11100011011001", "data": "Clock"}, {"code": "11101000100011", "data": "Comedy"}, {"code": "111010101001010", "data": "Cook"}, {"code": "111010100110110", "data": "Country"}, {"code": "101010110100", "data": "Directed by "}, {"code": "0100001100111", "data": "Drama"}, {"code": "1000101010001", "data": "East"}, {"code": "100000111001111", "data": "Education"}, {"code": "00010100011111", "data": "English"}, {"code": "0001010001110", "data": "Europe"}, {"code": "10110011011001", "data": "Extra"}, {"code": "10101011100010", "data": "Final"}, {"code": "111000110110100", "data": "Financial"}, {"code": "111000110111", "data": "For"}, {"code": "11101000111101", "data": "French"}, {"code": "1000101010010", "data": "From"}, {"code": "1010101111110", "data": "George"}, {"code": "1000100011010", "data": "Get"}, {"code": "10001000110110", "data": "Girls"}, {"code": "10001000110111", "data": "Golden"}, {"code": "111010101001011", "data": "Golf"}, {"code": "1010101101010", "data": "Good"}, {"code": "11101000100100", "data": "Great"}, {"code": "111010101001100", "data": "Hampshire"}, {"code": "1000101010011", "data": "Headlines"}, {"code": "11101010011010", "data": "Hear"}, {"code": "1000001110000", "data": "Hill"}, {"code": "111000110110101", "data": "Hollywood"}, {"code": "1000101010100", "data": "Home"}, {"code": "11101000100101", "data": "Hour"}, {"code": "1000001110010", "data": "House"}, {"code": "1010101101011", "data": "How"}, {"code": "11101010100100", "data": "ITN"}, {"code": "111010101001101", "data": "Important"}, {"code": "1000101011110", "data": "Including"}, {"code": "11101000100110", "data": "International"}, {"code": "10001000111", "data": "John"}, {"code": "11101000100111", "data": "Last"}, {"code": "10000011100110", "data": "Late"}, {"code": "10001010101100", "data": "Learn"}, {"code": "10001010101101", "data": "Little"}, {"code": "1110100010000", "data": "Live"}, {"code": "11101000111100", "data": "London"}, {"code": "10110011011110", "data": "Look"}, {"code": "111000110110110", "data": "Lunch"}, {"code": "1000101010101", "data": "Man"}, {"code": "1000001110001", "data": "Mark"}, {"code": "101010111001", "data": "Meridian"}, {"code": "1011001101101", "data": "Michael"}, {"code": "101010111000110000111110110", "data": "Minutes"}, {"code": "101010111000110000111110111", "data": "More"}, {"code": "101010111000110000111111000", "data": "Morning"}, {"code": "101010111000110000111111001", "data": "Murder"}, {"code": "101010111000110000111111010", "data": "Nation"}, {"code": "101010111000110000111111011", "data": "Neighbours"}, {"code": "101010111000110000111111100", "data": "New"}, {"code": "101010111000110000111111101", "data": "News & Weather"}, {"code": "101010111000110000111111110", "data": "News And Weather"}, {"code": "101010111000110000111111111", "data": "Paul"}, {"code": "10101011100011000000000000", "data": "Plus"}, {"code": "10101011100011000000000001", "data": "Prayer"}, {"code": "10101011100011000000000010", "data": "Present"}, {"code": "10101011100011000000000011", "data": "Presented by"}, {"code": "10101011100011000000000100", "data": "Quiz"}, {"code": "10101011100011000000000101", "data": "Regional"}, {"code": "10101011100011000000000110", "data": "Represent"}, {"code": "10101011100011000000000111", "data": "Resource"}, {"code": "10101011100011000000001000", "data": "Review"}, {"code": "10101011100011000000001001", "data": "Richard"}, {"code": "10101011100011000000001010", "data": "School"}, {"code": "10101011100011000000001011", "data": "Series"}, {"code": "10101011100011000000001100", "data": "Service"}, {"code": "10101011100011000000001101", "data": "Show"}, {"code": "10101011100011000000001110", "data": "Smith"}, {"code": "10101011100011000000001111", "data": "South"}, {"code": "10101011100011000000010000", "data": "Sport"}, {"code": "10101011100011000000010001", "data": "Star"}, {"code": "10101011100011000000010010", "data": "Street"}, {"code": "10101011100011000000010011", "data": "TV"}, {"code": "10101011100011000000010100", "data": "Teaching"}, {"code": "10101011100011000000010101", "data": "The"}, {"code": "10101011100011000000010110", "data": "Today"}, {"code": "10101011100011000000010111", "data": "Tonight"}, {"code": "10101011100011000000011000", "data": "Weather"}, {"code": "10101011100011000000011001", "data": "Western"}, {"code": "10101011100011000000011010", "data": "Westminster"}, {"code": "10101011100011000000011011", "data": "William"}, {"code": "10101011100011000000011100", "data": "With"}, {"code": "10101011100011000000011101", "data": "World"}, {"code": "10101011100011000000011110", "data": "about"}, {"code": "10101011100011000000011111", "data": "action-packed"}, {"code": "10101011100011000000100000", "data": "adventure"}, {"code": "10101011100011000000100001", "data": "afternoon"}, {"code": "10101011100011000000100010", "data": "alert"}, {"code": "10101011100011000000100011", "data": "all-star cast"}, {"code": "10101011100011000000100100", "data": "and"}, {"code": "10101011100011000000100101", "data": "anywhere"}, {"code": "10101011100011000000100110", "data": "audience"}, {"code": "10101011100011000000100111", "data": "based"}, {"code": "10101011100011000000101000", "data": "book"}, {"code": "10101011100011000000101001", "data": "business"}, {"code": "10101011100011000000101010", "data": "but"}, {"code": "10101011100011000000101011", "data": "celebrity"}, {"code": "10101011100011000000101100", "data": "chance"}, {"code": "10101011100011000000101101", "data": "chat"}, {"code": "10101011100011000000101110", "data": "child"}, {"code": "10101011100011000000101111", "data": "classic"}, {"code": "10101011100011000000110000", "data": "consumer"}, {"code": "10101011100011000000110001", "data": "contestants"}, {"code": "10101011100011000000110010", "data": "continues"}, {"code": "10101011100011000000110011", "data": "controversial"}, {"code": "10101011100011000000110100", "data": "dealer"}, {"code": "10101011100011000000110101", "data": "deliver"}, {"code": "10101011100011000000110110", "data": "discuss"}, {"code": "10101011100011000000110111", "data": "document"}, {"code": "10101011100011000000111000", "data": "drama"}, {"code": "10101011100011000000111001", "data": "edition"}, {"code": "10101011100011000000111010", "data": "education"}, {"code": "10101011100011000000111011", "data": "events"}, {"code": "10101011100011000000111100", "data": "every"}, {"code": "10101011100011000000111101", "data": "excellent"}, {"code": "10101011100011000000111110", "data": "eyed"}, {"code": "10101011100011000000111111", "data": "family"}, {"code": "10101011100011000001000000", "data": "famous"}, {"code": "10101011100011000001000001", "data": "featur"}, {"code": "10101011100011000001000010", "data": "film"}, {"code": "10101011100011000001000011", "data": "football"}, {"code": "10101011100011000001000100", "data": "for"}, {"code": "10101011100011000001000101", "data": "from"}, {"code": "10101011100011000001000110", "data": "general knowledge"}, {"code": "10101011100011000001000111", "data": "get"}, {"code": "10101011100011000001001000", "data": "guest"}, {"code": "10101011100011000001001001", "data": "guests"}, {"code": "10101011100011000001001010", "data": "has"}, {"code": "10101011100011000001001011", "data": "have"}, {"code": "10101011100011000001001100", "data": "headline"}, {"code": "10101011100011000001001101", "data": "her"}, {"code": "10101011100011000001001110", "data": "his"}, {"code": "10101011100011000001001111", "data": "home and abroad"}, {"code": "10101011100011000001010000", "data": "host"}, {"code": "10101011100011000001010001", "data": "how"}, {"code": "10101011100011000001010010", "data": "in"}, {"code": "10101011100011000001010011", "data": "including"}, {"code": "10101011100011000001010100", "data": "international"}, {"code": "10101011100011000001010101", "data": "interview"}, {"code": "10101011100011000001010110", "data": "introduce"}, {"code": "10101011100011000001010111", "data": "investigat"}, {"code": "10101011100011000001011000", "data": "invites"}, {"code": "10101011100011000001011001", "data": "issue"}, {"code": "10101011100011000001011010", "data": "knowledge"}, {"code": "10101011100011000001011011", "data": "life"}, {"code": "10101011100011000001011100", "data": "live"}, {"code": "10101011100011000001011101", "data": "look"}, {"code": "10101011100011000001011110", "data": "magazine"}, {"code": "10101011100011000001011111", "data": "meets "}, {"code": "10101011100011000001100000", "data": "morning"}, {"code": "10101011100011000001100001", "data": "morning magazine"}, {"code": "10101011100011000001100010", "data": "music"}, {"code": "10101011100011000001100011", "data": "near"}, {"code": "10101011100011000001100100", "data": "network"}, {"code": "10101011100011000001100101", "data": "new"}, {"code": "10101011100011000001100110", "data": "new series"}, {"code": "10101011100011000001100111", "data": "night"}, {"code": "10101011100011000001101000", "data": "of"}, {"code": "10101011100011000001101001", "data": "on"}, {"code": "10101011100011000001101010", "data": "onight"}, {"code": "10101011100011000001101011", "data": "out"}, {"code": "10101011100011000001101100", "data": "over"}, {"code": "10101011100011000001101101", "data": "part"}, {"code": "10101011100011000001101110", "data": "people"}, {"code": "10101011100011000001101111", "data": "phone"}, {"code": "10101011100011000001110000", "data": "poli"}, {"code": "10101011100011000001110001", "data": "police"}, {"code": "10101011100011000001110010", "data": "political chat show"}, {"code": "10101011100011000001110011", "data": "popular"}, {"code": "10101011100011000001110100", "data": "presented by "}, {"code": "10101011100011000001110101", "data": "programm"}, {"code": "10101011100011000001110110", "data": "quiz"}, {"code": "10101011100011000001110111", "data": "reconstruction"}, {"code": "10101011100011000001111000", "data": "report"}, {"code": "10101011100011000001111001", "data": "review"}, {"code": "10101011100011000001111010", "data": "school"}, {"code": "10101011100011000001111011", "data": "series"}, {"code": "10101011100011000001111100", "data": "short "}, {"code": "10101011100011000001111101", "data": "show"}, {"code": "10101011100011000001111110", "data": "some"}, {"code": "10101011100011000001111111", "data": "starring"}, {"code": "10101011100011000010000000", "data": "stars"}, {"code": "10101011100011000010000001", "data": "stories"}, {"code": "10101011100011000010000010", "data": "story"}, {"code": "10101011100011000010000011", "data": "studio"}, {"code": "10101011100011000010000100", "data": "surprise"}, {"code": "10101011100011000010000101", "data": "teller"}, {"code": "10101011100011000010000110", "data": "that"}, {"code": "10101011100011000010000111", "data": "the"}, {"code": "10101011100011000010001000", "data": "their"}, {"code": "10101011100011000010001001", "data": "them"}, {"code": "10101011100011000010001010", "data": "they"}, {"code": "10101011100011000010001011", "data": "this"}, {"code": "10101011100011000010001100", "data": "through"}, {"code": "10101011100011000010001101", "data": "to"}, {"code": "10101011100011000010001110", "data": "top"}, {"code": "10101011100011000010001111", "data": "trans"}, {"code": "10101011100011000010010000", "data": "under"}, {"code": "10101011100011000010010001", "data": "up"}, {"code": "10101011100011000010010010", "data": "very"}, {"code": "10101011100011000010010011", "data": "video"}, {"code": "10101011100011000010010100", "data": "view"}, {"code": "10101011100011000010010101", "data": "vintage"}, {"code": "10101011100011000010010110", "data": "visit"}, {"code": "10101011100011000010010111", "data": "was"}, {"code": "10101011100011000010011000", "data": "way"}, {"code": "10101011100011000010011001", "data": "week"}, {"code": "10101011100011000010011010", "data": "well"}, {"code": "10101011100011000010011011", "data": "what"}, {"code": "10101011100011000010011100", "data": "when"}, {"code": "10101011100011000010011101", "data": "which"}, {"code": "10101011100011000010011110", "data": "while"}, {"code": "10101011100011000010011111", "data": "who"}, {"code": "10101011100011000010100000", "data": "will"}, {"code": "10101011100011000010100001", "data": "win"}, {"code": "10101011100011000010100010", "data": "with"}, {"code": "10101011100011000010100011", "data": "words"}, {"code": "10101011100011000010100100", "data": "world"}, {"code": "10101011100011000010100101", "data": "written"}, {"code": "100010001100", "data": "year"}, {"code": "10110011010", "data": "you"}] diff --git a/data/epggrab/opentv/prov/skyuk b/data/epggrab/opentv/prov/skyuk new file mode 100644 index 00000000..49e51ac3 --- /dev/null +++ b/data/epggrab/opentv/prov/skyuk @@ -0,0 +1,16 @@ +{ + "name": "Sky UK", + "dict": "skyeng", + "nid": 2, + "tsid": 2004, + "sid": 4152, + "channel" : [ + 17 + ], + "title": [ + 48, 49, 50, 51, 52, 53, 54, 55 + ], + "summary": [ + 64, 65, 66, 67, 68, 69, 70, 71 + ] +} From 3d5910bcf6d29abdcc9f19132a836ff47aa7d2b3 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 13:47:43 +0100 Subject: [PATCH 15/25] Moved EIT handling code from dvb_tables into the eit grab module. Needs tidying up. --- src/dvb/dvb_tables.c | 229 ----------------------------------------- src/epggrab/eit.c | 236 ++++++++++++++++++++++++++++++++++++++++++- src/epggrab/eit.h | 6 -- 3 files changed, 234 insertions(+), 237 deletions(-) diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index 743ed66f..cb7d39e8 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -41,7 +41,6 @@ #include "psi.h" #include "notify.h" #include "cwc.h" -#include "epggrab/eit.h" static void dvb_table_add_pmt(th_dvb_mux_instance_t *tdmi, int pmt_pid); @@ -320,95 +319,6 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, tdt_open_fd(tdmi, tdt); } - -/** - * DVB Descriptor; Short Event - */ -static int -dvb_desc_short_event(uint8_t *ptr, int len, - char *title, size_t titlelen, - char *desc, size_t desclen, - char *dvb_default_charset) -{ - int r; - - if(len < 4) - return -1; - ptr += 3; len -= 3; - - if((r = dvb_get_string_with_len(title, titlelen, ptr, len, dvb_default_charset)) < 0) - return -1; - ptr += r; len -= r; - - if((r = dvb_get_string_with_len(desc, desclen, ptr, len, dvb_default_charset)) < 0) - return -1; - - return 0; -} - -/** - * DVB Descriptor; Extended Event - */ -static int -dvb_desc_extended_event(uint8_t *ptr, int len, - char *desc, size_t desclen, - char *item, size_t itemlen, - char *text, size_t textlen, - char *dvb_default_charset) -{ - int count = ptr[4], r; - uint8_t *localptr = ptr + 5, *items = localptr; - int locallen = len - 5; - - /* terminate buffers */ - desc[0] = '\0'; item[0] = '\0'; text[0] = '\0'; - - while (items < (localptr + count)) - { - /* this only makes sense if we have 2 or more character left in buffer */ - if ((desclen - strlen(desc)) > 2) - { - /* get description -> append to desc if space left */ - if (desc[0] != '\0') - strncat(desc, "\n", 1); - if((r = dvb_get_string_with_len(desc + strlen(desc), - desclen - strlen(desc), - items, (localptr + count) - items, - dvb_default_charset)) < 0) - return -1; - } - - items += 1 + items[0]; - - /* this only makes sense if we have 2 or more character left in buffer */ - if ((itemlen - strlen(item)) > 2) - { - /* get item -> append to item if space left */ - if (item[0] != '\0') - strncat(item, "\n", 1); - if((r = dvb_get_string_with_len(item + strlen(item), - itemlen - strlen(item), - items, (localptr + count) - items, - dvb_default_charset)) < 0) - return -1; - } - - /* go to next item */ - items += 1 + items[0]; - } - - localptr += count; - locallen -= count; - count = localptr[0]; - - /* get text */ - if((r = dvb_get_string_with_len(text, textlen, localptr, locallen, dvb_default_charset)) < 0) - return -1; - - return 0; -} - - /** * DVB Descriptor; Service */ @@ -437,139 +347,6 @@ dvb_desc_service(uint8_t *ptr, int len, uint8_t *typep, return 0; } - -/** - * DVB EIT (Event Information Table) - */ -static int -dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, - uint8_t tableid, void *opaque) -{ - service_t *t; - channel_t *ch; - th_dvb_adapter_t *tda = tdmi->tdmi_adapter; - - uint16_t serviceid; - uint16_t transport_stream_id; - - uint16_t event_id; - time_t start_time, stop_time; - - int ok; - int duration; - int dllen; - uint8_t dtag, dlen; - - char title[256]; - char desc[5000]; - char extdesc[5000]; - char extitem[5000]; - char exttext[5000]; - uint8_t genre[10]; // max 10 genres - int genre_idx = 0; - - lock_assert(&global_lock); - - // printf("EIT!, tid = %x\n", tableid); - - if(tableid < 0x4e || tableid > 0x6f || len < 11) - return -1; - - serviceid = ptr[0] << 8 | ptr[1]; - // version = ptr[2] >> 1 & 0x1f; - // section_number = ptr[3]; - // last_section_number = ptr[4]; - transport_stream_id = ptr[5] << 8 | ptr[6]; - // original_network_id = ptr[7] << 8 | ptr[8]; - // segment_last_section_number = ptr[9]; - // last_table_id = ptr[10]; - - if((ptr[2] & 1) == 0) { - /* current_next_indicator == next, skip this */ - return -1; - } - - len -= 11; - ptr += 11; - - /* Search all muxes on adapter */ - LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) - if(tdmi->tdmi_transport_stream_id == transport_stream_id) - break; - - if(tdmi == NULL) - return -1; - - t = dvb_transport_find(tdmi, serviceid, 0, NULL); - if(t == NULL || !t->s_enabled || (ch = t->s_ch) == NULL) - return 0; - - if(!t->s_dvb_eit_enable) - return 0; - - while(len >= 12) { - ok = 1; - event_id = ptr[0] << 8 | ptr[1]; - start_time = dvb_convert_date(&ptr[2]); - duration = bcdtoint(ptr[7] & 0xff) * 3600 + - bcdtoint(ptr[8] & 0xff) * 60 + - bcdtoint(ptr[9] & 0xff); - dllen = ((ptr[10] & 0x0f) << 8) | ptr[11]; - - len -= 12; - ptr += 12; - - if(dllen > len) break; - stop_time = start_time + duration; - - *title = 0; - *desc = 0; - while(dllen > 0) { - dtag = ptr[0]; - dlen = ptr[1]; - - len -= 2; ptr += 2; dllen -= 2; - - if(dlen > len) break; - - switch(dtag) { - case DVB_DESC_SHORT_EVENT: - if(dvb_desc_short_event(ptr, dlen, - title, sizeof(title), - desc, sizeof(desc), - t->s_dvb_default_charset)) ok = 0; - break; - - case DVB_DESC_CONTENT: - if(dlen >= 2) { - if (genre_idx < 10) - genre[genre_idx++] = (*ptr); - } - break; - case DVB_DESC_EXT_EVENT: - if(dvb_desc_extended_event(ptr, dlen, - extdesc, sizeof(extdesc), - extitem, sizeof(extitem), - exttext, sizeof(exttext), - t->s_dvb_default_charset)) ok = 0; - break; - default: - break; - } - - len -= dlen; ptr += dlen; dllen -= dlen; - } - - /* Pass to EIT handler */ - if (ok) - eit_callback(ch, event_id, start_time, stop_time, - title, desc, extitem, extdesc, exttext, - genre, genre_idx); - } - return 0; -} - - /** * DVB SDT (Service Description Table) */ @@ -1242,12 +1019,6 @@ dvb_table_add_default_dvb(th_dvb_mux_instance_t *tdmi) fp->filter.mask[0] = 0xff; tdt_add(tdmi, fp, dvb_sdt_callback, NULL, "sdt", TDT_QUICKREQ | TDT_CRC, 0x11, NULL); - - /* Event Information table */ - - fp = dvb_fparams_alloc(); - tdt_add(tdmi, fp, dvb_eit_callback, NULL, "eit", - TDT_CRC, 0x12, NULL); } diff --git a/src/epggrab/eit.c b/src/epggrab/eit.c index 8ba807ec..60ffb9b7 100644 --- a/src/epggrab/eit.c +++ b/src/epggrab/eit.c @@ -19,6 +19,9 @@ #include #include "tvheadend.h" #include "channels.h" +#include "dvb/dvb.h" +#include "dvb/dvb_support.h" +#include "service.h" #include "epg.h" #include "epggrab/eit.h" @@ -36,8 +39,7 @@ longest_string ( const char *a, const char *b ) if (strlen(a) - strlen(b) >= 0) return a; } -// called from dvb_tables.c -void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, +static void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, const char *title, const char *desc, const char *extitem, const char *extdesc, const char *exttext, @@ -95,14 +97,244 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, free(uri); } + +/** + * DVB Descriptor; Short Event + */ +static int +dvb_desc_short_event(uint8_t *ptr, int len, + char *title, size_t titlelen, + char *desc, size_t desclen, + char *dvb_default_charset) +{ + int r; + + if(len < 4) + return -1; + ptr += 3; len -= 3; + + if((r = dvb_get_string_with_len(title, titlelen, ptr, len, dvb_default_charset)) < 0) + return -1; + ptr += r; len -= r; + + if((r = dvb_get_string_with_len(desc, desclen, ptr, len, dvb_default_charset)) < 0) + return -1; + + return 0; +} + +/** + * DVB Descriptor; Extended Event + */ +static int +dvb_desc_extended_event(uint8_t *ptr, int len, + char *desc, size_t desclen, + char *item, size_t itemlen, + char *text, size_t textlen, + char *dvb_default_charset) +{ + int count = ptr[4], r; + uint8_t *localptr = ptr + 5, *items = localptr; + int locallen = len - 5; + + /* terminate buffers */ + desc[0] = '\0'; item[0] = '\0'; text[0] = '\0'; + + while (items < (localptr + count)) + { + /* this only makes sense if we have 2 or more character left in buffer */ + if ((desclen - strlen(desc)) > 2) + { + /* get description -> append to desc if space left */ + if (desc[0] != '\0') + strncat(desc, "\n", 1); + if((r = dvb_get_string_with_len(desc + strlen(desc), + desclen - strlen(desc), + items, (localptr + count) - items, + dvb_default_charset)) < 0) + return -1; + } + + items += 1 + items[0]; + + /* this only makes sense if we have 2 or more character left in buffer */ + if ((itemlen - strlen(item)) > 2) + { + /* get item -> append to item if space left */ + if (item[0] != '\0') + strncat(item, "\n", 1); + if((r = dvb_get_string_with_len(item + strlen(item), + itemlen - strlen(item), + items, (localptr + count) - items, + dvb_default_charset)) < 0) + return -1; + } + + /* go to next item */ + items += 1 + items[0]; + } + + localptr += count; + locallen -= count; + count = localptr[0]; + + /* get text */ + if((r = dvb_get_string_with_len(text, textlen, localptr, locallen, dvb_default_charset)) < 0) + return -1; + + return 0; +} + + +/** + * DVB EIT (Event Information Table) + */ +static int +_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, + uint8_t tableid, void *opaque) +{ + service_t *t; + channel_t *ch; + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + epggrab_module_t *mod = (epggrab_module_t*)opaque; + + uint16_t serviceid; + uint16_t transport_stream_id; + + uint16_t event_id; + time_t start_time, stop_time; + + int ok; + int duration; + int dllen; + uint8_t dtag, dlen; + + char title[256]; + char desc[5000]; + char extdesc[5000]; + char extitem[5000]; + char exttext[5000]; + uint8_t genre[10]; // max 10 genres + int genre_idx = 0; + + /* Global disable */ + if (!mod->enabled) return 0; + + lock_assert(&global_lock); + + // printf("EIT!, tid = %x\n", tableid); + + if(tableid < 0x4e || tableid > 0x6f || len < 11) + return -1; + + serviceid = ptr[0] << 8 | ptr[1]; + // version = ptr[2] >> 1 & 0x1f; + // section_number = ptr[3]; + // last_section_number = ptr[4]; + transport_stream_id = ptr[5] << 8 | ptr[6]; + // original_network_id = ptr[7] << 8 | ptr[8]; + // segment_last_section_number = ptr[9]; + // last_table_id = ptr[10]; + + if((ptr[2] & 1) == 0) { + /* current_next_indicator == next, skip this */ + return -1; + } + + len -= 11; + ptr += 11; + + /* Search all muxes on adapter */ + LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) + if(tdmi->tdmi_transport_stream_id == transport_stream_id) + break; + + if(tdmi == NULL) + return -1; + + t = dvb_transport_find(tdmi, serviceid, 0, NULL); + if(t == NULL || !t->s_enabled || (ch = t->s_ch) == NULL) + return 0; + + if(!t->s_dvb_eit_enable) + return 0; + + while(len >= 12) { + ok = 1; + event_id = ptr[0] << 8 | ptr[1]; + start_time = dvb_convert_date(&ptr[2]); + duration = bcdtoint(ptr[7] & 0xff) * 3600 + + bcdtoint(ptr[8] & 0xff) * 60 + + bcdtoint(ptr[9] & 0xff); + dllen = ((ptr[10] & 0x0f) << 8) | ptr[11]; + + len -= 12; + ptr += 12; + + if(dllen > len) break; + stop_time = start_time + duration; + + *title = 0; + *desc = 0; + while(dllen > 0) { + dtag = ptr[0]; + dlen = ptr[1]; + + len -= 2; ptr += 2; dllen -= 2; + + if(dlen > len) break; + + switch(dtag) { + case DVB_DESC_SHORT_EVENT: + if(dvb_desc_short_event(ptr, dlen, + title, sizeof(title), + desc, sizeof(desc), + t->s_dvb_default_charset)) ok = 0; + break; + + case DVB_DESC_CONTENT: + if(dlen >= 2) { + if (genre_idx < 10) + genre[genre_idx++] = (*ptr); + } + break; + case DVB_DESC_EXT_EVENT: + if(dvb_desc_extended_event(ptr, dlen, + extdesc, sizeof(extdesc), + extitem, sizeof(extitem), + exttext, sizeof(exttext), + t->s_dvb_default_charset)) ok = 0; + break; + default: + break; + } + + len -= dlen; ptr += dlen; dllen -= dlen; + } + + /* Pass to EIT handler */ + if (ok) + eit_callback(ch, event_id, start_time, stop_time, + title, desc, extitem, extdesc, exttext, + genre, genre_idx); + } + return 0; +} + /* ************************************************************************ * Module Setup * ***********************************************************************/ +static void _eit_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) +{ + tdt_add(tdmi, NULL, _eit_callback, NULL, "eit", TDT_CRC, 0x12, NULL); +} + void eit_init ( epggrab_module_list_t *list ) { _eit_mod.id = strdup("eit"); _eit_mod.name = strdup("EIT: On-Air Grabber"); + _eit_mod.tune = _eit_tune; *((uint8_t*)&_eit_mod.flags) = EPGGRAB_MODULE_OTA; LIST_INSERT_HEAD(list, &_eit_mod, link); // Note: this is mostly ignored anyway as EIT is treated as a special case! diff --git a/src/epggrab/eit.h b/src/epggrab/eit.h index be58edeb..c278718b 100644 --- a/src/epggrab/eit.h +++ b/src/epggrab/eit.h @@ -24,10 +24,4 @@ void eit_init ( epggrab_module_list_t *list ); void eit_load ( void ); -void eit_callback ( struct channel *ch, int id, time_t start, time_t stop, - const char *title, const char *desc, - const char *extitem, const char *extdesc, - const char *exttext, - const uint8_t *genres, int genre_cnt ); - #endif From 0604f3a261f3a66b3d0d98681a4060bd8955edf8 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 14:50:53 +0100 Subject: [PATCH 16/25] Fix erroneous ptr usage spotted by andyb2k5 and removed redundant equiv test. --- src/epggrab/eit.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/epggrab/eit.c b/src/epggrab/eit.c index 60ffb9b7..0d90c2e4 100644 --- a/src/epggrab/eit.c +++ b/src/epggrab/eit.c @@ -51,9 +51,6 @@ static void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, const char *description = NULL; char *uri; - /* Disabled? */ - if (!_eit_mod.enabled) return; - /* Ignore */ if (!ch || !ch->ch_name || !ch->ch_name[0]) return; if (!title) return; @@ -196,7 +193,6 @@ _eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, service_t *t; channel_t *ch; th_dvb_adapter_t *tda = tdmi->tdmi_adapter; - epggrab_module_t *mod = (epggrab_module_t*)opaque; uint16_t serviceid; uint16_t transport_stream_id; @@ -218,7 +214,7 @@ _eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, int genre_idx = 0; /* Global disable */ - if (!mod->enabled) return 0; + if (!_eit_mod.enabled) return 0; lock_assert(&global_lock); From 55254b5fdbc976b63306e89b8bf23e9d4c8ca848 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 15:48:14 +0100 Subject: [PATCH 17/25] Another ptr bug spotted by andyb2k5. --- src/dvb/dvb_tables.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index cb7d39e8..89291f1d 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -288,7 +288,7 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, // this LIST_FOREACH(t, &tdmi->tdmi_tables, tdt_link) { if(pid == t->tdt_pid && - tdt->tdt_callback == callback && tdt->tdt_opaque == opaque) { + t->tdt_callback == callback && t->tdt_opaque == opaque) { free(tdt); free(fparams); return; From 1136af0da0a534a3a966a75d365ef11eee5f3a3a Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 16:26:07 +0100 Subject: [PATCH 18/25] Failed to properly setup the module provider config. --- src/epggrab/opentv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index bdf8fd14..bb7212f9 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -559,6 +559,7 @@ void opentv_init ( epggrab_module_list_t *list ) mod->_.enable = _opentv_enable; mod->_.tune = _opentv_tune; mod->_.channels = &_opentv_channels; + mod->prov = p; *((uint8_t*)&mod->_.flags) = EPGGRAB_MODULE_OTA; LIST_INSERT_HEAD(list, &mod->_, link); } From d4486a20a5e3fbb1c0be0561b629de989bf18d32 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 20:36:39 +0100 Subject: [PATCH 19/25] Fix erroneous parse of title info and invalid setting of event stop time. --- src/epggrab/opentv.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index bb7212f9..85c156d6 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -238,7 +238,7 @@ static int _opentv_parse_event_record ev->start = (((int)buf[2] << 9) | (buf[3] << 1)); ev->duration = (((int)buf[4] << 9) | (buf[5] << 1)); ev->cat = buf[6]; - ev->title = _parse_string(prov, buf+7, rlen-7); + ev->title = _parse_string(prov, buf+9, rlen-7); break; case 0xb9: // summary ev->summary = _parse_string(prov, buf+2, rlen); @@ -294,12 +294,10 @@ static int _opentv_parse_event_section memset(&ev, 0, sizeof(opentv_event_t)); i += _opentv_parse_event(mod->prov, &ev, buf+i, len-i); - /* Process the event */ - /* Create/Find broadcast */ if (ev.start && ev.duration) { time_t start = ev.start + ((mjd - 40587) * 86400); - time_t stop = ev.start + ev.duration; + time_t stop = start + ev.duration; ebc = epg_broadcast_find_by_time(ec->channel, start, stop, 1, &save); } else { ebc = epg_broadcast_find_by_eid(ev.eid, ec->channel); From e1fe8cc7611d86b0c92c68adc92ec3fd24974bfa Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 23:17:22 +0100 Subject: [PATCH 20/25] Try to make huffman decoder a bit more efficient. --- src/huffman.c | 23 ++++++++++++----------- src/huffman.h | 3 ++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/huffman.c b/src/huffman.c index 16a539a0..1a72c8e9 100644 --- a/src/huffman.c +++ b/src/huffman.c @@ -75,15 +75,14 @@ huffman_node_t *huffman_tree_build ( htsmsg_t *m ) } char *huffman_decode - ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask ) + ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask, + char *outb, int outl ) { - char* ret; - size_t nalloc = len*4, nused = 0; + char *ret = outb; huffman_node_t *node = tree; if (!len) return NULL; - ret = malloc(nalloc); - ret[0] = '\0'; + outl--; // leave space for NULL while (len) { len--; while (mask) { @@ -93,19 +92,21 @@ char *huffman_decode node = node->b0; } mask >>= 1; - if (!node) return ret; + if (!node) goto end; if (node->data) { - size_t l = strlen(node->data); - if ((nalloc - nused) < l) { - nalloc *= 2; - ret = realloc(ret, nalloc); + char *t = node->data; + while (*t && outl) { + *outb = *t; + outb++; t++; outl--; } - strcat(ret, node->data); + if (!outl) goto end; node = tree; } } mask = 0x80; data++; } +end: + *outb = '\0'; return ret; } diff --git a/src/huffman.h b/src/huffman.h index 1bdd0408..184a5a8f 100644 --- a/src/huffman.h +++ b/src/huffman.h @@ -33,6 +33,7 @@ void huffman_tree_destroy ( huffman_node_t *tree ); huffman_node_t *huffman_tree_load ( const char *path ); huffman_node_t *huffman_tree_build ( htsmsg_t *codes ); char *huffman_decode - ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask ); + ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask, + char *outb, int outl ); #endif From c6804137200e0b6894d10e2eec58702c217986b0 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 23:21:32 +0100 Subject: [PATCH 21/25] Attempt to improve decode efficiency (although I might now have tracked this down to problems elsewhere). --- src/epggrab/opentv.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index 85c156d6..f067006f 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -208,22 +208,27 @@ static channel_t *_opentv_find_channel ( int tsid, int sid ) * OpenTV data processing * ***********************************************************************/ +#define MAX_LEN_TITLE 1024 +#define MAX_LEN_SUMMARY 1024 +#define MAX_LEN_DESC 2048 + typedef struct opentv_event { int eid; int start; int duration; - char *title; - char *summary; - char *description; + char title[MAX_LEN_TITLE]; + char summary[MAX_LEN_SUMMARY]; + char description[MAX_LEN_DESC]; int serieslink; uint8_t cat; } opentv_event_t; /* Parse huffman encoded string */ -static char *_parse_string ( opentv_prov_t *prov, uint8_t *buf, int len ) +static const char *_parse_string + ( opentv_prov_t *prov, uint8_t *buf, int len, char *outb, int outl ) { - return huffman_decode(prov->dict->codes, buf, len, 0x20); + return huffman_decode(prov->dict->codes, buf, len, 0x20, outb, outl); } /* Parse a specific record */ @@ -238,13 +243,13 @@ static int _opentv_parse_event_record ev->start = (((int)buf[2] << 9) | (buf[3] << 1)); ev->duration = (((int)buf[4] << 9) | (buf[5] << 1)); ev->cat = buf[6]; - ev->title = _parse_string(prov, buf+9, rlen-7); + _parse_string(prov, buf+9, rlen-7, ev->title, MAX_LEN_TITLE); break; case 0xb9: // summary - ev->summary = _parse_string(prov, buf+2, rlen); + _parse_string(prov, buf+2, rlen, ev->summary, MAX_LEN_SUMMARY); break; case 0xbb: // description - ev->description = _parse_string(prov, buf+2, rlen); + _parse_string(prov, buf+2, rlen, ev->description, MAX_LEN_DESC); break; case 0xc1: // series link ev->serieslink = ((int)buf[2] << 8) | buf[3]; @@ -284,6 +289,7 @@ static int _opentv_parse_event_section cid = ((int)buf[0] << 8) | buf[1]; if (!(ec = _opentv_find_epggrab_channel(mod, cid, 0, NULL))) return 0; if (!ec->channel) return 0; + if (!*ec->channel->ch_name) return 0; // ignore unnamed channels /* Time (start/stop referenced to this) */ mjd = ((int)buf[5] << 8) | buf[6]; @@ -310,8 +316,8 @@ static int _opentv_parse_event_section } /* Create/Find episode */ - if (ev.description || ev.summary) { - uri = md5sum(ev.description ?: ev.summary); + 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) { @@ -320,24 +326,20 @@ static int _opentv_parse_event_section /* Set episode data */ if (ee) { - if (ev.description) + if (*ev.description) save |= epg_episode_set_description(ee, ev.description); - if (ev.summary) + if (*ev.summary) save |= epg_episode_set_summary(ee, ev.summary); - if (ev.title) + if (*ev.title) save |= epg_episode_set_title(ee, ev.title); if (ev.cat) save |= epg_episode_set_genre(ee, &ev.cat, 1); + // TODO: series link save |= epg_broadcast_set_episode(ebc, ee); } } - - /* Cleanup */ - if (ev.title) free(ev.title); - if (ev.summary) free(ev.summary); - if (ev.description) free(ev.description); } /* Update EPG */ From 26bfcde48d0b9b2d01b639a551c86a58b3d2c4fd Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Mon, 18 Jun 2012 23:50:36 +0100 Subject: [PATCH 22/25] Fix to summary parsing and inclusion of initial series link support. --- src/epggrab/opentv.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index f067006f..ccf5189b 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -188,6 +188,15 @@ static epggrab_channel_t *_opentv_find_epggrab_channel return epggrab_module_channel_find((epggrab_module_t*)mod, chid, create, save); } +static epg_season_t *_opentv_find_season + ( opentv_module_t *mod, int cid, int slink ) +{ + int save = 0; + char uri[64]; + sprintf(uri, "%s-%d-%d", mod->prov->id, cid, slink); + return epg_season_find_by_uri(uri, 1, &save); +} + static channel_t *_opentv_find_channel ( int tsid, int sid ) { th_dvb_adapter_t *tda; @@ -268,7 +277,7 @@ static int _opentv_parse_event int slen = ((int)buf[2] & 0xf << 8) | buf[3]; int i = 4; ev->eid = ((uint16_t)buf[0] << 8) | buf[1]; - while (i < slen) { + while (i < slen+4) { i += _opentv_parse_event_record(prov, ev, buf+i, len-i); } return slen+4; @@ -283,6 +292,7 @@ static int _opentv_parse_event_section epggrab_channel_t *ec; epg_broadcast_t *ebc; epg_episode_t *ee; + epg_season_t *es; opentv_event_t ev; /* Channel */ @@ -336,6 +346,10 @@ static int _opentv_parse_event_section save |= epg_episode_set_genre(ee, &ev.cat, 1); // TODO: series link + if (ev.serieslink) { + es = _opentv_find_season(mod, cid, ev.serieslink); + if (es) save |= epg_episode_set_season(ee, es); + } save |= epg_broadcast_set_episode(ebc, ee); } From 73773b31f7df1bfc796894e014c662b3446fe944 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Tue, 19 Jun 2012 00:17:06 +0100 Subject: [PATCH 23/25] Ensure quality/channel locking is enforced by default. --- src/dvr/dvr_db.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 541c29d6..552b132a 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -1074,6 +1074,7 @@ dvr_config_create(const char *name) cfg->dvr_sl_channel_lock = 1; // channel locked cfg->dvr_sl_time_lock = 0; // time slot (approx) locked cfg->dvr_sl_more_recent = 1; // Only record more reason episodes + cfg->dvr_sl_quality_lock = 1; // Don't attempt to ajust quality /* dup detect */ cfg->dvr_dup_detect_episode = 1; // detect dup episodes From 520e00ab60192bc60aabe9d49022922a86736afd Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Tue, 19 Jun 2012 00:17:45 +0100 Subject: [PATCH 24/25] Minor hack to make series linking work properly with sky epg info. --- src/epggrab/opentv.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index ccf5189b..f65a7052 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -320,10 +320,6 @@ static int _opentv_parse_event_section } if (ebc) { - if (ebc->dvb_eid != ev.eid) { - ebc->dvb_eid = ev.eid; - save = 1; - } /* Create/Find episode */ if (*ev.description || *ev.summary) { @@ -334,6 +330,14 @@ static int _opentv_parse_event_section ee = ebc->episode; } + /* 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) @@ -345,8 +349,11 @@ static int _opentv_parse_event_section if (ev.cat) save |= epg_episode_set_genre(ee, &ev.cat, 1); - // TODO: series link - if (ev.serieslink) { + // 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); } From 9f92923c5fb6d1b9a1f1526c0076feafc07985b5 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Tue, 19 Jun 2012 23:34:35 +0100 Subject: [PATCH 25/25] Added initial background tuning thread. May need some additional logic adding to make it work better. --- src/epggrab/opentv.c | 128 +++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 35 deletions(-) diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c index f65a7052..a423289e 100644 --- a/src/epggrab/opentv.c +++ b/src/epggrab/opentv.c @@ -27,6 +27,7 @@ #include "epg.h" #include "epggrab/opentv.h" #include "subscriptions.h" +#include "streaming.h" #include "service.h" #include "htsmsg.h" #include "settings.h" @@ -68,8 +69,11 @@ typedef struct opentv_prov /* Extension of epggrab module to include linked provider */ typedef struct opentv_module { - epggrab_module_t _; ///< Base struct - opentv_prov_t *prov; ///< Associated provider config + epggrab_module_t ; ///< Base struct + opentv_prov_t *prov; ///< Associated provider config + pthread_mutex_t mutex; + pthread_cond_t cond; + time_t updated; } opentv_module_t; /* @@ -197,7 +201,7 @@ static epg_season_t *_opentv_find_season return epg_season_find_by_uri(uri, 1, &save); } -static channel_t *_opentv_find_channel ( int tsid, int sid ) +static service_t *_opentv_find_service ( int tsid, int sid ) { th_dvb_adapter_t *tda; th_dvb_mux_instance_t *tdmi; @@ -206,13 +210,19 @@ static channel_t *_opentv_find_channel ( int tsid, int sid ) LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { if (tdmi->tdmi_transport_stream_id != tsid) continue; LIST_FOREACH(t, &tdmi->tdmi_transports, s_group_link) { - if (t->s_dvb_service_id == sid) return t->s_ch; + if (t->s_dvb_service_id == sid) return t; } } } return NULL; } +static channel_t *_opentv_find_channel ( int tsid, int sid ) +{ + service_t *t = _opentv_find_service(tsid, sid); + return t ? t->s_ch : NULL; +} + /* ************************************************************************ * OpenTV data processing * ***********************************************************************/ @@ -435,39 +445,77 @@ static int _opentv_channel_callback * Tuning Thread * ***********************************************************************/ -#if 0 +static void _opentv_stream ( void *p, streaming_message_t *sm ) +{ + // TODO: handle start? + // TODO: handle stop? +} + +// TODO: if channel not found we still wait +// TODO: make time periods configurable? +// TODO: dynamic detection of when to start/stop static void* _opentv_thread ( void *p ) { - int err = 0; - th_dvb_adapter_t *tda; - th_dvb_mux_instance_t *tdmi; - service_t *svc = NULL; + int err; + service_t *svc; + th_subscription_t *sub; + streaming_target_t stream; + time_t start; + struct timespec ts; + opentv_module_t *mod = (opentv_module_t*)p; + streaming_target_init(&stream, _opentv_stream, NULL, 0); + tvhlog(LOG_INFO, mod->id, "thread started\n"); - pthread_mutex_lock(&global_lock); + do { - TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) { - LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { - if ((svc = dvb_transport_find(tdmi, 4152, 0, NULL))) { - _opentv_tdmi = tdmi; - break; - } + /* Find channel and subscribe */ + tvhlog(LOG_DEBUG, mod->id, "grab begin %d %d", mod->prov->tsid, mod->prov->sid); + pthread_mutex_lock(&global_lock); + svc = _opentv_find_service(mod->prov->tsid, mod->prov->sid); + if (svc) { + sub = subscription_create_from_service(svc, mod->prov->id, + &stream, 0); + if (sub) subscription_change_weight(sub, 1); } - } + else + sub = NULL; + pthread_mutex_unlock(&global_lock); + if (sub) tvhlog(LOG_DEBUG, mod->id, "subscription added"); + + /* Allow scanning */ + if (sub) { + time(&start); + ts.tv_nsec = 0; + pthread_mutex_lock(&mod->mutex); + while ( mod->enabled ) { + ts.tv_sec = start + 300; + err = pthread_cond_timedwait(&mod->cond, &mod->mutex, &ts); + if (err == ETIMEDOUT ) break; + } + pthread_mutex_unlock(&mod->mutex); + } + tvhlog(LOG_DEBUG, mod->id, "grab complete"); - /* start */ - printf("found svc = %p\n", svc); - if(svc) err = service_start(svc, 1, 0); - printf("service_start = %d\n", err); + /* Terminate subscription */ + pthread_mutex_lock(&global_lock); + if (sub) subscription_unsubscribe(sub); + pthread_mutex_unlock(&global_lock); - pthread_mutex_unlock(&global_lock); + /* Wait */ + time(&mod->updated); + pthread_mutex_lock(&mod->mutex); + while ( mod->enabled ) { + ts.tv_sec = mod->updated + 3600; + err = pthread_cond_timedwait(&mod->cond, &mod->mutex, &ts); + if (err == ETIMEDOUT) break; + } + if (!mod->enabled) break; + pthread_mutex_unlock(&mod->mutex); + } while (1); + pthread_mutex_unlock(&mod->mutex); - while (1) { - // check status? - sleep(10); - } return NULL; } -#endif /* ************************************************************************ * Module Setup @@ -520,12 +568,22 @@ static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) static int _opentv_enable ( epggrab_module_t *m, uint8_t e ) { - // TODO: do I need to kick off a thread here or elsewhere? int save = 0; + pthread_t t; + pthread_attr_t ta; + opentv_module_t *mod = (opentv_module_t*)m; + pthread_mutex_lock(&mod->mutex); if (m->enabled != e) { m->enabled = e; + if (e) { + pthread_attr_init(&ta); + pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED); + pthread_create(&t, &ta, _opentv_thread, mod); + } else + pthread_cond_signal(&mod->cond); save = 1; } + pthread_mutex_unlock(&mod->mutex); return save; } @@ -574,15 +632,15 @@ void opentv_init ( epggrab_module_list_t *list ) RB_FOREACH(p, &_opentv_provs, h_link) { mod = calloc(1, sizeof(opentv_module_t)); sprintf(buf, "opentv-%s", p->id); - mod->_.id = strdup(buf); + mod->id = strdup(buf); sprintf(buf, "OpenTV: %s", p->name); - mod->_.name = strdup(buf); - mod->_.enable = _opentv_enable; - mod->_.tune = _opentv_tune; - mod->_.channels = &_opentv_channels; + mod->name = strdup(buf); + mod->enable = _opentv_enable; + mod->tune = _opentv_tune; + mod->channels = &_opentv_channels; mod->prov = p; - *((uint8_t*)&mod->_.flags) = EPGGRAB_MODULE_OTA; - LIST_INSERT_HEAD(list, &mod->_, link); + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_OTA; + LIST_INSERT_HEAD(list, ((epggrab_module_t*)mod), link); } }