Merge branch 'epg-opentv' into epg-rewrite

This commit is contained in:
Adam Sutton 2012-06-19 23:34:51 +01:00
commit aece0209f1
22 changed files with 1259 additions and 342 deletions

View file

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

File diff suppressed because one or more lines are too long

View 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
]
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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