Merge branch 'epg-opentv' into epg-rewrite
This commit is contained in:
commit
aece0209f1
22 changed files with 1259 additions and 342 deletions
4
Makefile
4
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 \
|
||||
|
|
1
data/epggrab/opentv/dict/skyeng
Normal file
1
data/epggrab/opentv/dict/skyeng
Normal file
File diff suppressed because one or more lines are too long
16
data/epggrab/opentv/prov/skyuk
Normal file
16
data/epggrab/opentv/prov/skyuk
Normal file
|
@ -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
|
||||
]
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -41,70 +41,20 @@
|
|||
#include "psi.h"
|
||||
#include "notify.h"
|
||||
#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 +274,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,
|
||||
|
@ -332,8 +282,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 &&
|
||||
t->tdt_callback == callback && t->tdt_opaque == opaque) {
|
||||
free(tdt);
|
||||
free(fparams);
|
||||
return;
|
||||
|
@ -364,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
|
||||
*/
|
||||
|
@ -481,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)
|
||||
*/
|
||||
|
@ -1286,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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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"
|
||||
|
@ -28,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;
|
||||
|
@ -663,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);
|
||||
|
@ -712,6 +711,7 @@ static void _epggrab_load ( void )
|
|||
eit_load();
|
||||
xmltv_load();
|
||||
pyepg_load();
|
||||
opentv_load();
|
||||
}
|
||||
|
||||
void epggrab_save ( void )
|
||||
|
@ -725,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);
|
||||
|
@ -741,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;
|
||||
|
@ -811,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
|
||||
|
||||
|
@ -819,6 +806,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();
|
||||
|
@ -855,6 +843,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;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <pthread.h>
|
||||
#include "channels.h"
|
||||
#include "dvb/dvb.h"
|
||||
|
||||
typedef struct epggrab_module epggrab_module_t;
|
||||
|
||||
|
@ -85,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
|
||||
|
@ -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 );
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -159,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 );
|
||||
|
@ -187,4 +189,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__ */
|
||||
|
|
|
@ -19,11 +19,16 @@
|
|||
#include <string.h>
|
||||
#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"
|
||||
|
||||
static epggrab_module_t _eit_mod;
|
||||
|
||||
/* ************************************************************************
|
||||
* Module Setup
|
||||
* Processing
|
||||
* ***********************************************************************/
|
||||
|
||||
static const char *
|
||||
|
@ -34,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,
|
||||
|
@ -51,9 +55,6 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop,
|
|||
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;
|
||||
|
@ -93,17 +94,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;
|
||||
|
||||
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 (!_eit_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 epggrab_module_t _eit_mod;
|
||||
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");
|
||||
*((uint8_t*)&_eit_mod.flags) = EPGGRAB_MODULE_SPECIAL;
|
||||
_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!
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
650
src/epggrab/opentv.c
Normal file
650
src/epggrab/opentv.c
Normal file
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
* Electronic Program Guide - opentv epg 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/dvb/dmx.h>
|
||||
#include "tvheadend.h"
|
||||
#include "dvb/dvb.h"
|
||||
#include "channels.h"
|
||||
#include "huffman.h"
|
||||
#include "epg.h"
|
||||
#include "epggrab/opentv.h"
|
||||
#include "subscriptions.h"
|
||||
#include "streaming.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
|
||||
*
|
||||
* 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 *id;
|
||||
huffman_node_t *codes;
|
||||
RB_ENTRY(opentv_dict) h_link;
|
||||
} opentv_dict_t;
|
||||
|
||||
/* Provider configuration */
|
||||
typedef struct opentv_prov
|
||||
{
|
||||
char *id;
|
||||
char *name;
|
||||
RB_ENTRY(opentv_prov) h_link;
|
||||
|
||||
int nid;
|
||||
int tsid;
|
||||
int sid;
|
||||
int *channel;
|
||||
int *title;
|
||||
int *summary;
|
||||
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
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
time_t updated;
|
||||
} 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;
|
||||
struct opentv_prov_tree _opentv_provs;
|
||||
|
||||
static int _dict_cmp ( void *a, void *b )
|
||||
{
|
||||
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)->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;
|
||||
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 int _opentv_dict_load ( const char *id, htsmsg_t *m )
|
||||
{
|
||||
opentv_dict_t *dict = calloc(1, sizeof(opentv_dict_t));
|
||||
dict->id = (char*)id;
|
||||
if (RB_INSERT_SORTED(&_opentv_dicts, dict, h_link, _dict_cmp)) {
|
||||
tvhlog(LOG_WARNING, "opentv", "ignore duplicate dictionary %s", id);
|
||||
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->id = strdup(id);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int _opentv_prov_load ( const char *id, htsmsg_t *m )
|
||||
{
|
||||
htsmsg_t *cl, *tl, *sl;
|
||||
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 -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->id = (char*)id;
|
||||
if (RB_INSERT_SORTED(&_opentv_provs, prov, h_link, _prov_cmp)) {
|
||||
tvhlog(LOG_WARNING, "opentv", "ignore duplicate provider %s", id);
|
||||
free(prov);
|
||||
return 0;
|
||||
} else {
|
||||
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);
|
||||
prov->title = _pid_list_to_array(tl);
|
||||
prov->summary = _pid_list_to_array(sl);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* EPG Object wrappers
|
||||
* ***********************************************************************/
|
||||
|
||||
static epggrab_channel_t *_opentv_find_epggrab_channel
|
||||
( opentv_module_t *mod, int cid, int create, int *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 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 service_t *_opentv_find_service ( 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
* ***********************************************************************/
|
||||
|
||||
#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[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 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, outb, outl);
|
||||
}
|
||||
|
||||
/* 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];
|
||||
_parse_string(prov, buf+9, rlen-7, ev->title, MAX_LEN_TITLE);
|
||||
break;
|
||||
case 0xb9: // summary
|
||||
_parse_string(prov, buf+2, rlen, ev->summary, MAX_LEN_SUMMARY);
|
||||
break;
|
||||
case 0xbb: // description
|
||||
_parse_string(prov, buf+2, rlen, ev->description, MAX_LEN_DESC);
|
||||
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+4) {
|
||||
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;
|
||||
epg_season_t *es;
|
||||
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;
|
||||
if (!*ec->channel->ch_name) return 0; // ignore unnamed channels
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Create/Find broadcast */
|
||||
if (ev.start && ev.duration) {
|
||||
time_t start = ev.start + ((mjd - 40587) * 86400);
|
||||
time_t stop = start + ev.duration;
|
||||
ebc = epg_broadcast_find_by_time(ec->channel, start, stop, 1, &save);
|
||||
} else {
|
||||
ebc = epg_broadcast_find_by_eid(ev.eid, ec->channel);
|
||||
}
|
||||
|
||||
if (ebc) {
|
||||
|
||||
/* Create/Find episode */
|
||||
if (*ev.description || *ev.summary) {
|
||||
uri = md5sum(*ev.description ? ev.description : ev.summary);
|
||||
ee = epg_episode_find_by_uri(uri, 1, &save);
|
||||
free(uri);
|
||||
} else if (ebc) {
|
||||
ee = ebc->episode;
|
||||
}
|
||||
|
||||
/* DVB Event Id */
|
||||
// TODO: this causes serious problems for channels backed by multiple
|
||||
// services where the ids don't match
|
||||
if (ebc->dvb_eid != ev.eid) {
|
||||
ebc->dvb_eid = ev.eid;
|
||||
save = 1;
|
||||
}
|
||||
|
||||
/* Set episode data */
|
||||
if (ee) {
|
||||
if (*ev.description)
|
||||
save |= epg_episode_set_description(ee, ev.description);
|
||||
if (*ev.summary)
|
||||
save |= epg_episode_set_summary(ee, ev.summary);
|
||||
if (*ev.title)
|
||||
save |= epg_episode_set_title(ee, ev.title);
|
||||
if (ev.cat)
|
||||
save |= epg_episode_set_genre(ee, &ev.cat, 1);
|
||||
|
||||
// Note: we don't overwrite an existing season, the reason for
|
||||
// this is that seasons in opentv are channel specific but episodes
|
||||
// are not (to allow episode matching) so this is a bit of bodge
|
||||
// to make things play nice
|
||||
if (ev.serieslink && !ee->season) {
|
||||
es = _opentv_find_season(mod, cid, ev.serieslink);
|
||||
if (es) save |= epg_episode_set_season(ee, es);
|
||||
}
|
||||
|
||||
save |= epg_broadcast_set_episode(ebc, ee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 )
|
||||
{
|
||||
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;
|
||||
i = 7 + ((((int)buf[5] & 0xf) << 8) | buf[6]);
|
||||
tslen = (((int)buf[i] & 0xf) << 8) | buf[i+1];
|
||||
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);
|
||||
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 = _opentv_find_channel(tsid, sid);
|
||||
if (ch) {
|
||||
int save = 0;
|
||||
ec = _opentv_find_epggrab_channel(mod, cid, 1, &save);
|
||||
if (save) {
|
||||
// Note: could use set_sid() but not nec.
|
||||
ec->channel = ch;
|
||||
//TODO: must be configurable
|
||||
//epggrab_channel_set_number(ec, cnum);
|
||||
}
|
||||
}
|
||||
k += 9;
|
||||
dlen -= 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* Tuning Thread
|
||||
* ***********************************************************************/
|
||||
|
||||
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;
|
||||
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");
|
||||
|
||||
do {
|
||||
|
||||
/* 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");
|
||||
|
||||
/* Terminate subscription */
|
||||
pthread_mutex_lock(&global_lock);
|
||||
if (sub) subscription_unsubscribe(sub);
|
||||
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);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* 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 )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void opentv_init ( epggrab_module_list_t *list )
|
||||
{
|
||||
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"))) {
|
||||
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");
|
||||
|
||||
/* Create modules */
|
||||
RB_FOREACH(p, &_opentv_provs, h_link) {
|
||||
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;
|
||||
mod->prov = p;
|
||||
*((uint8_t*)&mod->flags) = EPGGRAB_MODULE_OTA;
|
||||
LIST_INSERT_HEAD(list, ((epggrab_module_t*)mod), link);
|
||||
}
|
||||
}
|
||||
|
||||
void opentv_load ( void )
|
||||
{
|
||||
// TODO: do we want to keep a list of channels stored?
|
||||
}
|
29
src/epggrab/opentv.h
Normal file
29
src/epggrab/opentv.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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 );
|
||||
|
||||
#endif /* __TVH_EPGGRAB_OPENTV_H__ */
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
112
src/huffman.c
Normal file
112
src/huffman.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#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;
|
||||
huffman_node_t *ret;
|
||||
if (!(m = hts_settings_load(path))) return NULL;
|
||||
ret = huffman_tree_build(m);
|
||||
htsmsg_destroy(m);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
char *huffman_decode
|
||||
( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask,
|
||||
char *outb, int outl )
|
||||
{
|
||||
char *ret = outb;
|
||||
huffman_node_t *node = tree;
|
||||
if (!len) return NULL;
|
||||
|
||||
outl--; // leave space for NULL
|
||||
while (len) {
|
||||
len--;
|
||||
while (mask) {
|
||||
if (*data & mask) {
|
||||
node = node->b1;
|
||||
} else {
|
||||
node = node->b0;
|
||||
}
|
||||
mask >>= 1;
|
||||
if (!node) goto end;
|
||||
if (node->data) {
|
||||
char *t = node->data;
|
||||
while (*t && outl) {
|
||||
*outb = *t;
|
||||
outb++; t++; outl--;
|
||||
}
|
||||
if (!outl) goto end;
|
||||
node = tree;
|
||||
}
|
||||
}
|
||||
mask = 0x80;
|
||||
data++;
|
||||
}
|
||||
end:
|
||||
*outb = '\0';
|
||||
return ret;
|
||||
}
|
39
src/huffman.h
Normal file
39
src/huffman.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __TVH_HUFFMAN_H__
|
||||
#define __TVH_HUFFMAN_H__
|
||||
|
||||
#include <sys/types.h>
|
||||
#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 uint8_t *data, size_t len, uint8_t mask,
|
||||
char *outb, int outl );
|
||||
|
||||
#endif
|
|
@ -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")) )
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue