From: Jaroslav Kysela Date: Mon, 19 Oct 2015 16:46:48 +0000 (+0200) Subject: bouquet: add enigma2 parser, fixes #2994 X-Git-Tag: v4.2.1~1872 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ffd995cf9fd0fc8394f2b016cb34b7c0fd3362e3;p=thirdparty%2Ftvheadend.git bouquet: add enigma2 parser, fixes #2994 --- diff --git a/src/api/api_bouquet.c b/src/api/api_bouquet.c index 93f914a98..d5ad9eb61 100644 --- a/src/api/api_bouquet.c +++ b/src/api/api_bouquet.c @@ -64,7 +64,7 @@ api_bouquet_create htsmsg_t *conf; bouquet_t *bq; - if (!(conf = htsmsg_get_map(args, "conf"))) + if (!(conf = htsmsg_get_map(args, "conf"))) return EINVAL; pthread_mutex_lock(&global_lock); diff --git a/src/bouquet.c b/src/bouquet.c index 7b101d3e6..ebd3c60b8 100644 --- a/src/bouquet.c +++ b/src/bouquet.c @@ -23,10 +23,20 @@ #include "service.h" #include "channels.h" #include "service_mapper.h" +#include "download.h" + +typedef struct bouquet_download { + bouquet_t *bq; + download_t download; + gtimer_t timer; +} bouquet_download_t; bouquet_tree_t bouquets; static uint64_t bouquet_get_channel_number0(bouquet_t *bq, service_t *t); +static void bouquet_download_trigger(bouquet_t *bq); +static void bouquet_download_stop(void *aux); +static int bouquet_download_process(void *aux, const char *last_url, char *data, size_t len); /** * @@ -45,6 +55,8 @@ bouquet_create(const char *uuid, htsmsg_t *conf, const char *name, const char *src) { bouquet_t *bq, *bq2; + bouquet_download_t *bqd; + char buf[128]; int i; lock_assert(&global_lock); @@ -52,6 +64,7 @@ bouquet_create(const char *uuid, htsmsg_t *conf, bq = calloc(1, sizeof(bouquet_t)); bq->bq_services = idnode_set_create(1); bq->bq_active_services = idnode_set_create(1); + bq->bq_ext_url_period = 60; if (idnode_insert(&bq->bq_id, uuid, &bouquet_class, 0)) { if (uuid) @@ -78,11 +91,26 @@ bouquet_create(const char *uuid, htsmsg_t *conf, bq->bq_src = strdup(src); } + if (bq->bq_ext_url) { + free(bq->bq_src); + snprintf(buf, sizeof(buf), "exturl://%s", idnode_uuid_as_sstr(&bq->bq_id)); + bq->bq_src = strdup(buf); + bq->bq_download = bqd = calloc(1, sizeof(*bqd)); + bqd->bq = bq; + download_init(&bqd->download, "bouquet"); + bqd->download.process = bouquet_download_process; + bqd->download.stop = bouquet_download_stop; + bouquet_change_comment(bq, bq->bq_ext_url, 0); + } + bq2 = RB_INSERT_SORTED(&bouquets, bq, bq_link, _bq_cmp); assert(bq2 == NULL); bq->bq_saveflag = 1; + if (bq->bq_ext_url) + bouquet_download_trigger(bq); + return bq; } @@ -92,17 +120,25 @@ bouquet_create(const char *uuid, htsmsg_t *conf, static void bouquet_destroy(bouquet_t *bq) { + bouquet_download_t *bqd; + if (!bq) return; RB_REMOVE(&bouquets, bq, bq_link); idnode_unlink(&bq->bq_id); + if ((bqd = bq->bq_download) != NULL) { + download_done(&bqd->download); + free(bqd); + } + idnode_set_free(bq->bq_active_services); idnode_set_free(bq->bq_services); assert(bq->bq_services_waiting == NULL); free((char *)bq->bq_chtag_waiting); free(bq->bq_name); + free(bq->bq_ext_url); free(bq->bq_src); free(bq->bq_comment); free(bq); @@ -254,7 +290,7 @@ bouquet_map_channel(bouquet_t *bq, service_t *t) * */ void -bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, uint32_t tag) +bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, const char *tag) { service_lcn_t *tl; idnode_list_mapping_t *ilm; @@ -494,8 +530,8 @@ bouquet_get_channel_number(bouquet_t *bq, service_t *t) /* * */ -static uint32_t -bouquet_get_tag_number(bouquet_t *bq, service_t *t) +static const char * +bouquet_get_tag_name(bouquet_t *bq, service_t *t) { return 0; } @@ -763,7 +799,7 @@ bouquet_class_services_get ( void *obj ) bouquet_t *bq = obj; service_t *t; int64_t lcn; - uint32_t tag; + const char *tag; size_t z; /* Add all */ @@ -772,8 +808,8 @@ bouquet_class_services_get ( void *obj ) e = htsmsg_create_map(); if ((lcn = bouquet_get_channel_number0(bq, t)) != 0) htsmsg_add_s64(e, "lcn", lcn); - if ((tag = bouquet_get_tag_number(bq, t)) != 0) - htsmsg_add_s64(e, "tag", lcn); + if ((tag = bouquet_get_tag_name(bq, t)) != NULL) + htsmsg_add_str(e, "tag", tag); htsmsg_add_msg(m, idnode_uuid_as_sstr(&t->s_id), e); } @@ -813,6 +849,12 @@ bouquet_class_services_count_get ( void *obj ) return &u32; } +static void +bouquet_class_ext_url_notify ( void *obj, const char *lang ) +{ + bouquet_download_trigger((bouquet_t *)obj); +} + const idclass_t bouquet_class = { .ic_class = "bouquet", .ic_caption = N_("Bouquet"), @@ -887,6 +929,30 @@ const idclass_t bouquet_class = { .name = N_("Name"), .off = offsetof(bouquet_t, bq_name), }, + { + .type = PT_STR, + .id = "ext_url", + .name = N_("External URL"), + .off = offsetof(bouquet_t, bq_ext_url), + .opts = PO_HIDDEN | PO_MULTILINE, + .notify = bouquet_class_ext_url_notify, + }, + { + .type = PT_BOOL, + .id = "ssl_peer_verify", + .name = N_("ssl_peer_verify"), + .off = offsetof(bouquet_t, bq_ssl_peer_verify), + .opts = PO_ADVANCED | PO_HIDDEN, + .notify = bouquet_class_ext_url_notify, + }, + { + .type = PT_U32, + .id = "ext_url_period", + .name = N_("Re-fetch period (mins)"), + .off = offsetof(bouquet_t, bq_ext_url_period), + .opts = PO_ADVANCED | PO_HIDDEN, + .def.i = 60 + }, { .type = PT_STR, .id = "source", @@ -935,6 +1001,100 @@ const idclass_t bouquet_class = { } }; +/** + * + */ +static void +bouquet_download_trigger0(void *aux) +{ + bouquet_download_t *bqd = aux; + bouquet_t *bq = bqd->bq; + + download_start(&bqd->download, bq->bq_ext_url, bqd); + gtimer_arm(&bqd->timer, bouquet_download_trigger0, bqd, + MAX(1, bq->bq_ext_url_period) * 60); +} + +static void +bouquet_download_trigger(bouquet_t *bq) +{ + bouquet_download_t *bqd = bq->bq_download; + if (bqd) { + bqd->download.ssl_peer_verify = bq->bq_ssl_peer_verify; + bouquet_download_trigger0(bqd); + } +} + +static void +bouquet_download_stop(void *aux) +{ + bouquet_download_t *bqd = aux; + gtimer_disarm(&bqd->timer); +} + +static int +parse_enigma2(bouquet_t *bq, char *data) +{ + extern service_t *mpegts_service_find_e2(uint32_t stype, uint32_t sid, + uint32_t tsid, uint32_t onid, + uint32_t hash); + char *argv[11], *p, *tagname = NULL, *name; + long lv, stype, sid, tsid, onid, hash; + uint32_t seen = 0; + int n; + + while (*data) { + if (strncmp(data, "#NAME ", 6) == 0) { + for (data += 6, p = data; *data && *data != '\r' && *data != '\n'; data++); + if (*data) { *data = '\0'; data++; } + if (bq->bq_name == NULL || bq->bq_name[0] == '\0') + bq->bq_name = strdup(p); + } if (strncmp(data, "#SERVICE ", 9) == 0) { + data += 9, p = data; + while (*data && *data != '\r' && *data != '\n') data++; + if (*data) { *data = '\0'; data++; } + n = http_tokenize(p, argv, ARRAY_SIZE(argv), ':'); + if (n >= 10) { + if (strtol(argv[0], NULL, 0) != 1) goto next; /* item type */ + lv = strtol(argv[1], NULL, 16); /* service flags? */ + if (lv != 0 && lv != 0x64) goto next; + stype = strtol(argv[2], NULL, 16); /* DVB service type */ + sid = strtol(argv[3], NULL, 16); /* DVB service ID */ + tsid = strtol(argv[4], NULL, 16); + onid = strtol(argv[5], NULL, 16); + hash = strtol(argv[6], NULL, 16); + name = n > 10 ? argv[10] : NULL; + if (lv == 0) { + service_t *s = mpegts_service_find_e2(stype, sid, tsid, onid, hash); + if (s) + bouquet_add_service(bq, s, ++seen, tagname); + } else if (lv == 64) { + tagname = name; + } + } + } else { + while (*data && *data != '\r' && *data != '\n') data++; + } +next: + while (*data && (*data == '\r' || *data == '\n')) data++; + } + bouquet_completed(bq, seen); + tvhinfo("bouquet", "parsed Enigma2 bouquet %s (%d services)", bq->bq_name, seen); + return 0; +} + +static int +bouquet_download_process(void *aux, const char *last_url, char *data, size_t len) +{ + bouquet_download_t *bqd = aux; + while (*data) { + while (*data && *data < ' ') data++; + if (*data && !strncmp(data, "#NAME ", 6)) + return parse_enigma2(bqd->bq, data); + } + return 0; +} + /** * */ @@ -966,7 +1126,7 @@ bouquet_service_resolve(void) htsmsg_field_t *f; service_t *s; int64_t lcn; - uint32_t tag; + const char *tag; int saveflag; lock_assert(&global_lock); @@ -979,7 +1139,7 @@ bouquet_service_resolve(void) HTSMSG_FOREACH(f, bq->bq_services_waiting) { if ((e = htsmsg_field_get_map(f)) == NULL) continue; lcn = htsmsg_get_s64_or_default(e, "lcn", 0); - tag = htsmsg_get_u32_or_default(e, "tag", 0); + tag = htsmsg_get_str(e, "tag"); s = service_find_by_identifier(f->hmf_name); if (s) bouquet_add_service(bq, s, lcn, tag); diff --git a/src/bouquet.h b/src/bouquet.h index 692b4b3f2..06f8d1630 100644 --- a/src/bouquet.h +++ b/src/bouquet.h @@ -44,6 +44,9 @@ typedef struct bouquet { channel_tag_t*bq_chtag_ptr; const char *bq_chtag_waiting; char *bq_name; + char *bq_ext_url; + int bq_ssl_peer_verify; + int bq_ext_url_period; char *bq_src; char *bq_comment; idnode_set_t *bq_services; @@ -53,6 +56,8 @@ typedef struct bouquet { uint32_t bq_lcn_offset; uint64_t bq_last_lcn; + void *bq_download; + } bouquet_t; typedef RB_HEAD(,bouquet) bouquet_tree_t; @@ -85,7 +90,7 @@ bouquet_t * bouquet_find_by_source(const char *name, const char *src, int create void bouquet_map_to_channels(bouquet_t *bq); void bouquet_notify_channels(bouquet_t *bq); -void bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, uint32_t tag); +void bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, const char *tag); void bouquet_completed(bouquet_t *bq, uint32_t seen); void bouquet_change_comment(bouquet_t *bq, const char *comment, int replace); diff --git a/src/htsbuf.c b/src/htsbuf.c index c3b0a4c6f..70e2df716 100644 --- a/src/htsbuf.c +++ b/src/htsbuf.c @@ -475,4 +475,3 @@ htsbuf_to_string(htsbuf_queue_t *hq) htsbuf_read(hq, r, hq->hq_size); return r; } - diff --git a/src/input/mpegts.h b/src/input/mpegts.h index 283ecb720..eb0a05743 100644 --- a/src/input/mpegts.h +++ b/src/input/mpegts.h @@ -1008,6 +1008,10 @@ mpegts_service_t *mpegts_service_create_raw(mpegts_mux_t *mm); mpegts_service_t *mpegts_service_find ( mpegts_mux_t *mm, uint16_t sid, uint16_t pmt_pid, int create, int *save ); +service_t * +mpegts_service_find_e2 + ( uint32_t stype, uint32_t sid, uint32_t tsid, uint32_t onid, uint32_t hash); + mpegts_service_t * mpegts_service_find_by_pid ( mpegts_mux_t *mm, int pid ); diff --git a/src/input/mpegts/dvb_psi.c b/src/input/mpegts/dvb_psi.c index 91382917f..194b5ecf7 100644 --- a/src/input/mpegts/dvb_psi.c +++ b/src/input/mpegts/dvb_psi.c @@ -589,7 +589,7 @@ dvb_freesat_add_service snprintf(name, sizeof(name), "%s: %s", bi->name, fr->name); fr->bouquet = bouquet_find_by_source(name, src, 1); } - bouquet_add_service(fr->bouquet, (service_t *)s, (int64_t)lcn * CHANNEL_SPLIT, 0); + bouquet_add_service(fr->bouquet, (service_t *)s, (int64_t)lcn * CHANNEL_SPLIT, NULL); return fr->bouquet->bq_enabled; } @@ -1088,7 +1088,7 @@ dvb_bat_completed services_count++; if (bq->bq_enabled) bouquet_add_service(bq, (service_t *)bs->svc, - (int64_t)bs->lcn * CHANNEL_SPLIT, 0); + (int64_t)bs->lcn * CHANNEL_SPLIT, NULL); } bouquet_completed(bq, services_count); diff --git a/src/input/mpegts/iptv/iptv.c b/src/input/mpegts/iptv/iptv.c index 5c9521a02..7b1ca2cae 100644 --- a/src/input/mpegts/iptv/iptv.c +++ b/src/input/mpegts/iptv/iptv.c @@ -107,7 +107,7 @@ iptv_bouquet_update(void *aux) bouquet_change_comment(bq, in->in_url, 1); LIST_FOREACH(mm, &in->mn_muxes, mm_network_link) LIST_FOREACH(ms, &mm->mm_services, s_dvb_mux_link) { - bouquet_add_service(bq, (service_t *)ms, ((iptv_mux_t *)mm)->mm_iptv_chnum, 0); + bouquet_add_service(bq, (service_t *)ms, ((iptv_mux_t *)mm)->mm_iptv_chnum, NULL); seen++; } bouquet_completed(bq, seen); diff --git a/src/input/mpegts/mpegts_service.c b/src/input/mpegts/mpegts_service.c index d118037ad..2a7ec866c 100644 --- a/src/input/mpegts/mpegts_service.c +++ b/src/input/mpegts/mpegts_service.c @@ -538,6 +538,64 @@ mpegts_service_channel_icon ( service_t *s ) return NULL; } +#if ENABLE_MPEGTS_DVB +static int +mpegts_service_match_network(mpegts_network_t *mn, uint32_t hash) +{ + int pos = hash >> 16, pos2; + + pos = hash >> 16; + if (pos > 3600 || pos < 0) return 0; + pos = pos <= 1800 ? pos : pos - 3600; + if ((pos2 = dvb_network_get_orbital_pos(mn)) == INT_MAX) return 0; + return deltaI32(pos, pos2) <= 20; +} +#endif + +#if ENABLE_MPEGTS_DVB +static int +mpegts_service_match_mux(dvb_mux_t *mm, uint32_t hash) +{ + extern const idclass_t dvb_mux_dvbs_class; + dvb_mux_t *mmd; + int freq, pol; + + if ((hash & 0xffff) == 0) return 1; + if (!idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class)) return 0; + freq = (hash & 0x7fff) * 1000; + pol = (hash & 0x8000) ? DVB_POLARISATION_HORIZONTAL : DVB_POLARISATION_VERTICAL; + mmd = mm; + return mmd->lm_tuning.u.dmc_fe_qpsk.orbital_pos == pol && + deltaU32(mmd->lm_tuning.dmc_fe_freq, freq) < 2000; +} +#endif + +service_t * +mpegts_service_find_e2(uint32_t stype, uint32_t sid, uint32_t tsid, + uint32_t onid, uint32_t hash) +{ +#if ENABLE_MPEGTS_DVB + mpegts_network_t *mn; + mpegts_mux_t *mm; + mpegts_service_t *s; + + lock_assert(&global_lock); + + LIST_FOREACH(mn, &mpegts_network_all, mn_global_link) { + if (!idnode_is_instance(&mn->mn_id, &dvb_network_class)) continue; + if (!mpegts_service_match_network(mn, hash)) continue; + LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link) { + if (mm->mm_tsid != tsid || mm->mm_onid != onid) continue; + if (!mpegts_service_match_mux((dvb_mux_t *)mm, hash)) continue; + LIST_FOREACH(s, &mm->mm_services, s_dvb_mux_link) + if (s->s_dvb_service_id == sid) + return (service_t *)s; + } + } +#endif + return NULL; +} + static void mpegts_service_mapped ( service_t *t ) { diff --git a/src/webui/static/app/cteditor.js b/src/webui/static/app/cteditor.js index af28c19a8..4f4e1446e 100644 --- a/src/webui/static/app/cteditor.js +++ b/src/webui/static/app/cteditor.js @@ -35,6 +35,8 @@ tvheadend.bouquet = function(panel, index) { var list = 'enabled,rescan,name,maptoch,mapnolcn,lcn_off,mapnoname,mapradio,' + 'chtag,source,services_count,services_seen,comment'; + var alist = 'enabled,ext_url,name,maptoch,mapnolcn,lcn_off,mapnoname,mapradio,' + + 'chtag,comment'; tvheadend.idnode_grid(panel, { url: 'api/bouquet', @@ -58,6 +60,11 @@ tvheadend.bouquet = function(panel, index) comment: { width: 200 } }, list: list, + add: { + url: 'api/bouquet', + params: { list: alist }, + create: { } + }, del: true, edit: { params: { list: list } }, sort: {