From 2819dbbf610c277d3fb4b83fca661879518349aa Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Thu, 17 May 2012 12:03:05 +0100 Subject: [PATCH] started work on epggrab module for pyepg (demo). --- src/epggrab.c | 18 +- src/epggrab/pyepg.c | 394 ++++++++++++++++++++++++++++++++++++++ src/epggrab/pyepg.h | 7 + src/{ => epggrab}/xmltv.c | 0 src/{ => epggrab}/xmltv.h | 0 5 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 src/epggrab/pyepg.c create mode 100644 src/epggrab/pyepg.h rename src/{ => epggrab}/xmltv.c (100%) rename src/{ => epggrab}/xmltv.h (100%) diff --git a/src/epggrab.c b/src/epggrab.c index d06f48c4..622462d1 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -5,11 +5,12 @@ #include "settings.h" #include "tvheadend.h" #include "epggrab.h" +#include "epggrab/pyepg.h" /* Thread protection */ -int epggrab_confver; -pthread_mutex_t epggrab_mutex; -pthread_cond_t epggrab_cond; +int epggrab_confver; +pthread_mutex_t epggrab_mutex; +pthread_cond_t epggrab_cond; /* Config */ uint32_t epggrab_advanced; @@ -18,6 +19,9 @@ uint32_t epggrab_interval; epggrab_module_t* epggrab_module; epggrab_sched_list_t epggrab_schedule; +/* Modules */ +epggrab_module_t* epggrab_module_pyepg; + /* Internal prototypes */ static void* _epggrab_thread ( void* ); static time_t _epggrab_thread_simple ( void ); @@ -37,6 +41,9 @@ void epggrab_init ( void ) epggrab_interval = 12; // hours epggrab_module = NULL; // disabled + /* Initialise modules */ + epggrab_module_pyepg = pyepg_init(); + /* Start thread */ pthread_t tid; pthread_attr_t tattr; @@ -126,6 +133,7 @@ void* _epggrab_thread ( void* p ) epggrab_module_t* epggrab_module_find_by_name ( const char *name ) { + if ( strcmp(name, "pyepg") == 0 ) return epggrab_module_pyepg; return NULL; } @@ -156,7 +164,7 @@ void _epggrab_load ( void ) HTSMSG_FOREACH(f, s) { if ((e = htsmsg_get_map_by_field(f)) != NULL) { es = calloc(1, sizeof(epggrab_sched_t)); - str = htsmsg_get_str(e, "module"); + str = htsmsg_get_str(e, "mod"); if (str) es->mod = epggrab_module_find_by_name(str); str = htsmsg_get_str(e, "opts"); if (str) es->opts = strdup(str); @@ -192,7 +200,7 @@ void _epggrab_save ( void ) s = htsmsg_create_list(); LIST_FOREACH(es, &epggrab_schedule, es_link) { e = htsmsg_create_map(); - if ( es->mod ) htsmsg_add_str(e, "module", es->mod->name()); + if ( es->mod ) htsmsg_add_str(e, "mod", es->mod->name()); if ( es->opts ) htsmsg_add_str(e, "opts", es->opts); c = htsmsg_create_map(); cron_pack(&es->cron, c); diff --git a/src/epggrab/pyepg.c b/src/epggrab/pyepg.c new file mode 100644 index 00000000..4f881bfc --- /dev/null +++ b/src/epggrab/pyepg.c @@ -0,0 +1,394 @@ +/* + * PyEPG grabber + */ + +#include +#include +#include +#include "htsmsg_xml.h" +#include "tvheadend.h" +#include "spawn.h" +#include "epg.h" +#include "epggrab/pyepg.h" + +void _pyepg_grab ( const char **argv ); +void _pyepg_parse ( htsmsg_t *data ); +void _pyepg_parse_epg ( htsmsg_t *data ); +int _pyepg_parse_channel ( htsmsg_t *data ); +int _pyepg_parse_brand ( htsmsg_t *data ); +int _pyepg_parse_season ( htsmsg_t *data ); +int _pyepg_parse_episode ( htsmsg_t *data ); +int _pyepg_parse_broadcast ( htsmsg_t *data, epg_channel_t *channel ); +int _pyepg_parse_schedule ( htsmsg_t *data ); +int _pyepg_parse_time ( const char *str, time_t *tm ); + +/* ************************************************************************ + * Module Setup + * ***********************************************************************/ + +static epggrab_module_t pyepg_module; + +static const char* pyepg_name ( void ) +{ + return "pyepg"; +} + +static void pyepg_run ( const char *iopts ) +{ + int i = 1; + const char *argv[32]; // 32 args max! + char *toksave, *tok; + char *opts = strdup(iopts); + + /* TODO: do something better! */ + argv[0] = "/usr/bin/pyepg"; + if ( opts ) { + tok = strtok_r(opts, " ", &toksave); + while ( tok != NULL ) { + argv[i++] = tok; + tok = strtok_r(NULL, " ", &toksave); + } + argv[i] = NULL; + } + + _pyepg_grab(argv); + free(opts); +} + +epggrab_module_t* pyepg_init ( void ) +{ + pyepg_module.enable = NULL; + pyepg_module.disable = NULL; + pyepg_module.name = pyepg_name; + pyepg_module.run = pyepg_run; + return &pyepg_module; +} + +/* ************************************************************************** + * Grabber + * *************************************************************************/ + +void _pyepg_grab ( const char **argv ) +{ + int i = 0, outlen; + char *outbuf; + char cmdstr[1024], errbuf[100]; + time_t t1, t2; + htsmsg_t *body; + + /* Debug */ + cmdstr[0] = '\0'; + while ( argv[i] ) { + strcat(cmdstr, argv[i++]); + if ( argv[i] ) strcat(cmdstr, " "); + } + tvhlog(LOG_DEBUG, "pyepg", "grab %s", cmdstr); + + /* Grab */ + time(&t1); + outlen = spawn_and_store_stdout(argv[0], (char *const*)argv, &outbuf); + if ( outlen < 1 ) { + tvhlog(LOG_ERR, "pyepg", "no output detected"); + return; + } + time(&t2); + tvhlog(LOG_DEBUG, "pyepg", "grab took %d seconds", t2 - t1); + + /* Extract */ + body = htsmsg_xml_deserialize(outbuf, errbuf, sizeof(errbuf)); + if ( !body ) { + tvhlog(LOG_ERR, "pyepg", "unable to parse output [e=%s]", errbuf); + return; + } + + /* Parse */ + pthread_mutex_lock(&global_lock); + _pyepg_parse(body); + pthread_mutex_unlock(&global_lock); + htsmsg_destroy(body); +} + +/* ************************************************************************** + * Parsing + * *************************************************************************/ + +void _pyepg_parse ( htsmsg_t *data ) +{ + htsmsg_t *tags, *epg; + + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return; + + /* PyEPG format */ + if ((epg = htsmsg_get_map(tags, "epg")) != NULL) _pyepg_parse_epg(epg); + + /* XMLTV format */ + if ((epg = htsmsg_get_map(tags, "tv")) != NULL) return;// TODO: add +} + +int _pyepg_parse_channel ( htsmsg_t *data ) +{ + int save = 0; + htsmsg_t *attr, *tags; + const char *id, *name = NULL; + epg_channel_t *channel; + + if ( data == NULL ) return 0; + + if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0; + if ((id = htsmsg_get_str(attr, "id")) == NULL) return 0; + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0; + + /* Find channel */ + if ((channel = epg_channel_find(id, name, NULL, NULL)) == NULL) return 0; + // TODO: need to save if created + + return save; +} + +int _pyepg_parse_brand ( htsmsg_t *data ) +{ + int save = 0; + htsmsg_t *attr, *tags; + epg_brand_t *brand; + const char *str; + uint32_t u32; + + if ( data == NULL ) return 0; + + if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0; + if ((str = htsmsg_get_str(attr, "id")) == NULL) return 0; + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0; + + /* Find brand */ + if ((brand = epg_brand_find_by_id(str, 1)) == NULL) return 0; + // TODO: do we need to save if created? + + /* Set title */ + if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) { + save |= epg_brand_set_title(brand, str); + } + + /* Set summary */ + if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) { + save |= epg_brand_set_summary(brand, str); + } + + /* Set icon */ +#if TODO + if ((str = htsmsg_xml_get_cdata_str(tags, "icon"))) { + save |= epg_brand_set_icon(brand, str); + } +#endif + + /* Set season count */ + if (htsmsg_xml_get_cdata_u32(tags, "series-count", &u32)) { + save |= epg_brand_set_season_count(brand, u32); + } + + return save; +} + +int _pyepg_parse_season ( htsmsg_t *data ) +{ + int save = 0; + htsmsg_t *attr, *tags; + epg_season_t *season; + epg_brand_t *brand; + const char *str; + uint32_t u32; + + if ( data == NULL ) return 0; + + if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0; + if ((str = htsmsg_get_str(attr, "id")) == NULL) return 0; + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0; + + /* Find series */ + if ((season = epg_season_find_by_id(str, 1)) == NULL) return 0; + // TODO: do we need to save if created? + + /* Set brand */ + if ((str = htsmsg_xml_get_cdata_str(tags, "brand"))) { + if ((brand = epg_brand_find_by_id(str, 0))) { + save |= epg_season_set_brand(season, brand); + } + } + + /* Set title */ +#if TODO + if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) { + save |= epg_season_set_title(season, str); + } + + /* Set summary */ + if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) { + save |= epg_season_set_summary(season, str); + } + + /* Set icon */ + if ((str = htsmsg_xml_get_cdata_str(tags, "icon"))) { + save |= epg_season_set_icon(season, str); + } +#endif + + /* Set season number */ + if (htsmsg_xml_get_cdata_u32(tags, "number", &u32)) { + save |= epg_season_set_number(season, u32); + } + + /* Set episode count */ + if (htsmsg_xml_get_cdata_u32(tags, "episode-count", &u32)) { + save |= epg_season_set_episode_count(season, u32); + } + + return save; +} + +int _pyepg_parse_episode ( htsmsg_t *data ) +{ + int save = 0; + htsmsg_t *attr, *tags; + epg_episode_t *episode; + epg_season_t *season; + epg_brand_t *brand; + const char *str; + uint32_t u32; + + if ( data == NULL ) return 0; + + if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0; + if ((str = htsmsg_get_str(attr, "id")) == NULL) return 0; + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0; + + /* Find episode */ + if ((episode = epg_episode_find_by_id(str, 1)) == NULL) return 0; + // TODO: do we need to save if created? + + /* Set brand */ + if ((str = htsmsg_xml_get_cdata_str(tags, "brand"))) { + if ((brand = epg_brand_find_by_id(str, 0))) { + save |= epg_episode_set_brand(episode, brand); + } + } + + /* Set season */ + if ((str = htsmsg_xml_get_cdata_str(tags, "season"))) { + if ((season = epg_season_find_by_id(str, 0))) { + save |= epg_episode_set_season(episode, season); + } + } + + /* Set title/subtitle */ + if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) { + save |= epg_episode_set_title(episode, str); + } + if ((str = htsmsg_xml_get_cdata_str(tags, "subtitle"))) { + save |= epg_episode_set_subtitle(episode, str); + } + + /* Set summary */ + if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) { + save |= epg_episode_set_summary(episode, str); + } + + /* Number */ + if (htsmsg_xml_get_cdata_u32(tags, "number", &u32)) { + save |= epg_episode_set_number(episode, u32); + } + + /* Genre */ + // TODO: can actually have multiple! +#if TODO + if ((str = htsmsg_xml_get_cdata_str(tags, "genre"))) { + // TODO: conversion? + save |= epg_episode_set_genre(episode, str); + } +#endif + + /* TODO: extra metadata */ + + return save; +} + +int _pyepg_parse_broadcast ( htsmsg_t *data, epg_channel_t *channel ) +{ + int save = 0; + htsmsg_t *attr;//, *tags; + epg_episode_t *episode; + epg_broadcast_t *broadcast; + const char *id, *start, *stop; + time_t tm_start, tm_stop; + + if ( data == NULL || channel == NULL ) return 0; + + if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0; + if ((id = htsmsg_get_str(attr, "episode")) == NULL) return 0; + if ((start = htsmsg_get_str(attr, "start")) == NULL ) return 0; + if ((stop = htsmsg_get_str(attr, "stop")) == NULL ) return 0; + + /* Find episode */ + if ((episode = epg_episode_find_by_id(id, 1)) == NULL) return 0; + + /* Parse times */ + if (!_pyepg_parse_time(start, &tm_start)) return 0; + if (!_pyepg_parse_time(stop, &tm_stop)) return 0; + + /* Find broadcast */ + // TODO: need to think about this + if ((broadcast = epg_broadcast_find(channel, episode, tm_start, tm_stop, 1)) == NULL) return 0; + save = 1; + + /* TODO: extra metadata */ + + return save; +} + +int _pyepg_parse_schedule ( htsmsg_t *data ) +{ + int save = 0; + htsmsg_t *attr, *tags; + htsmsg_field_t *f; + epg_channel_t *channel; + const char *str; + + if ( data == NULL ) return 0; + + if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0; + if ((str = htsmsg_get_str(attr, "channel")) == NULL) return 0; + if ((channel = epg_channel_find_by_id(str, 0)) == NULL) return 0; + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0; + + HTSMSG_FOREACH(f, tags) { + if (strcmp(f->hmf_name, "broadcast")) { + save |= _pyepg_parse_broadcast(htsmsg_get_map_by_field(f), channel); + } + } + + return save; +} + +void _pyepg_parse_epg ( htsmsg_t *data ) +{ + int save = 0; + htsmsg_t *tags; + htsmsg_field_t *f; + + if ((tags = htsmsg_get_map(data, "tags")) == NULL) return; + + HTSMSG_FOREACH(f, tags) { + if (strcmp(f->hmf_name, "channel") == 0 ) { + save |= _pyepg_parse_channel(htsmsg_get_map_by_field(f)); + } else if (strcmp(f->hmf_name, "brand") == 0 ) { + save |= _pyepg_parse_brand(htsmsg_get_map_by_field(f)); + } else if (strcmp(f->hmf_name, "series") == 0 ) { + save |= _pyepg_parse_season(htsmsg_get_map_by_field(f)); + } else if (strcmp(f->hmf_name, "episode") == 0 ) { + save |= _pyepg_parse_episode(htsmsg_get_map_by_field(f)); + } else if (strcmp(f->hmf_name, "schedule") == 0 ) { + save |= _pyepg_parse_schedule(htsmsg_get_map_by_field(f)); + } + } + + /* Updated */ + if (save) epg_updated(); +} diff --git a/src/epggrab/pyepg.h b/src/epggrab/pyepg.h new file mode 100644 index 00000000..d7afe9bc --- /dev/null +++ b/src/epggrab/pyepg.h @@ -0,0 +1,7 @@ +/* + * PyEPG grabber module + */ + +#include "epggrab.h" + +epggrab_module_t* pyepg_init ( void ); diff --git a/src/xmltv.c b/src/epggrab/xmltv.c similarity index 100% rename from src/xmltv.c rename to src/epggrab/xmltv.c diff --git a/src/xmltv.h b/src/epggrab/xmltv.h similarity index 100% rename from src/xmltv.h rename to src/epggrab/xmltv.h