From: Jaroslav Kysela Date: Tue, 13 Oct 2015 15:29:25 +0000 (+0200) Subject: IPTV: Add automatic network (m3u) X-Git-Tag: v4.2.1~1925 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=68b98925b985f3d5d422fe13d8508461f5614884;p=thirdparty%2Ftvheadend.git IPTV: Add automatic network (m3u) --- diff --git a/Makefile b/Makefile index 300917add..730ec70ba 100644 --- a/Makefile +++ b/Makefile @@ -364,7 +364,8 @@ SRCS-IPTV = \ src/input/mpegts/iptv/iptv_udp.c \ src/input/mpegts/iptv/iptv_rtsp.c \ src/input/mpegts/iptv/iptv_rtcp.c \ - src/input/mpegts/iptv/iptv_pipe.c + src/input/mpegts/iptv/iptv_pipe.c \ + src/input/mpegts/iptv/iptv_auto.c SRCS-${CONFIG_IPTV} += $(SRCS-IPTV) I18N-C += $(SRCS-IPTV) diff --git a/src/httpc.c b/src/httpc.c index 1124d1897..b317c6872 100644 --- a/src/httpc.c +++ b/src/httpc.c @@ -1414,7 +1414,7 @@ http_client_connect void http_client_register( http_client_t *hc ) { - assert(hc->hc_data_received || hc->hc_conn_closed); + assert(hc->hc_data_received || hc->hc_conn_closed || hc->hc_data_complete); assert(hc->hc_efd == NULL); pthread_mutex_lock(&http_lock); @@ -1507,7 +1507,6 @@ http_client_done ( void ) http_running = 0; tvh_write(http_pipe.wr, "", 1); pthread_join(http_client_tid, NULL); - assert(TAILQ_FIRST(&http_clients) == NULL); tvh_pipe_close(&http_pipe); tvhpoll_destroy(http_poll); free(http_user_agent); diff --git a/src/input/mpegts/iptv/iptv.c b/src/input/mpegts/iptv/iptv.c index 63865235c..dfe78999a 100644 --- a/src/input/mpegts/iptv/iptv.c +++ b/src/input/mpegts/iptv/iptv.c @@ -485,6 +485,9 @@ iptv_network_class_delete ( idnode_t *in ) { mpegts_network_t *mn = (mpegts_network_t*)in; + if (in->in_class == &iptv_auto_network_class) + iptv_auto_network_done((iptv_network_t *)in); + /* Remove config */ hts_settings_remove("input/iptv/networks/%s", idnode_uuid_as_sstr(in)); @@ -541,6 +544,34 @@ const idclass_t iptv_network_class = { } }; +const idclass_t iptv_auto_network_class = { + .ic_super = &iptv_network_class, + .ic_class = "iptv_auto_network", + .ic_caption = N_("IPTV Automatic Network"), + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .id = "url", + .name = N_("URL"), + .off = offsetof(iptv_network_t, in_url), + }, + { + .type = PT_U32, + .id = "refetch_period", + .name = N_("Re-fetch period (mins)"), + .off = offsetof(iptv_network_t, in_refetch_period), + .def.i = 60, + }, + { + .type = PT_BOOL, + .id = "ssl_peer_verify", + .name = N_("SSL verify peer"), + .off = offsetof(iptv_network_t, in_ssl_peer_verify), + }, + {} + } +}; + static mpegts_mux_t * iptv_network_create_mux2 ( mpegts_network_t *mn, htsmsg_t *conf ) @@ -575,7 +606,7 @@ iptv_network_config_save ( mpegts_network_t *mn ) iptv_network_t * iptv_network_create0 - ( const char *uuid, htsmsg_t *conf ) + ( const char *uuid, htsmsg_t *conf, const idclass_t *idc ) { iptv_network_t *in = calloc(1, sizeof(*in)); htsmsg_t *c; @@ -583,8 +614,7 @@ iptv_network_create0 /* Init Network */ in->in_priority = 1; in->in_streaming_priority = 1; - if (!mpegts_network_create0((mpegts_network_t *)in, - &iptv_network_class, + if (!mpegts_network_create0((mpegts_network_t *)in, idc, uuid, NULL, conf)) { free(in); return NULL; @@ -614,6 +644,9 @@ iptv_network_create0 } htsmsg_destroy(c); } + + if (idc == &iptv_auto_network_class) + iptv_auto_network_init(in); return in; } @@ -622,7 +655,7 @@ static mpegts_network_t * iptv_network_builder ( const idclass_t *idc, htsmsg_t *conf ) { - return (mpegts_network_t*)iptv_network_create0(NULL, conf); + return (mpegts_network_t*)iptv_network_create0(NULL, conf, idc); } /* ************************************************************************** @@ -635,9 +668,11 @@ iptv_network_init ( void ) htsmsg_t *c, *e; htsmsg_field_t *f; - /* Register builder */ + /* Register builders */ mpegts_network_register_builder(&iptv_network_class, iptv_network_builder); + mpegts_network_register_builder(&iptv_auto_network_class, + iptv_network_builder); /* Load settings */ if (!(c = hts_settings_load_r(1, "input/iptv/networks"))) @@ -646,7 +681,10 @@ iptv_network_init ( void ) HTSMSG_FOREACH(f, c) { if (!(e = htsmsg_get_map_by_field(f))) continue; if (!(e = htsmsg_get_map(e, "config"))) continue; - iptv_network_create0(f->hmf_name, e); + if (htsmsg_get_str(e, "url")) + iptv_network_create0(f->hmf_name, e, &iptv_auto_network_class); + else + iptv_network_create0(f->hmf_name, e, &iptv_network_class); } htsmsg_destroy(c); } diff --git a/src/input/mpegts/iptv/iptv_auto.c b/src/input/mpegts/iptv/iptv_auto.c new file mode 100644 index 000000000..6afa0c370 --- /dev/null +++ b/src/input/mpegts/iptv/iptv_auto.c @@ -0,0 +1,317 @@ +/* + * IPTV - automatic network based on playlists + * + * Copyright (C) 2015 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "http.h" +#include "iptv_private.h" + +#include +#include + +/* + * + */ +static void +iptv_auto_network_process_m3u_item(iptv_network_t *in, + const char *url, const char *name, + int *total, int *count) +{ + htsmsg_t *conf; + mpegts_mux_t *mm; + iptv_mux_t *im; + int change; + + if (url == NULL || + (strncmp(url, "file://", 7) && + strncmp(url, "http://", 7) && + strncmp(url, "https://", 8))) + return; + + LIST_FOREACH(mm, &in->mn_muxes, mm_network_link) { + im = (iptv_mux_t *)mm; + if (strcmp(im->mm_iptv_url ?: "", url) == 0) { + im->im_delete_flag = 0; + if (strcmp(im->mm_iptv_svcname ?: "", name ?: "")) { + free(im->mm_iptv_svcname); + im->mm_iptv_svcname = name ? strdup(name) : NULL; + change = 1; + } + if (change) + idnode_notify_changed(&im->mm_id); + (*total)++; + return; + } + } + + + conf = htsmsg_create_map(); + htsmsg_add_str(conf, "iptv_url", url); + if (name) + htsmsg_add_str(conf, "iptv_sname", name); + im = iptv_mux_create0(in, NULL, conf); + htsmsg_destroy(conf); + + if (im) { + im->mm_config_save((mpegts_mux_t *)im); + (*total)++; + (*count)++; + } +} + +/* + * + */ +static int +iptv_auto_network_process_m3u(iptv_network_t *in, char *data) +{ + char *url, *name = NULL; + int total = 0, count = 0; + + while (*data && *data != '\n') data++; + if (*data) data++; + while (*data) { + if (strncmp(data, "#EXTINF:", 8) == 0) { + name = NULL; + data += 8; + while (*data && *data != ',') data++; + if (*data == ',') { + data++; + while (*data && *data <= ' ') data++; + if (*data) + name = data; + } + while (*data && *data != '\n') data++; + if (*data) { *data = '\0'; data++; } + continue; + } + while (*data && *data <= ' ') data++; + url = data; + while (*data && *data != '\n') data++; + if (*data) { *data = '\0'; data++; } + if (*url) + iptv_auto_network_process_m3u_item(in, url, name, &total, &count); + } + + if (total == 0) + return -1; + tvhinfo("iptv", "m3u parse: %d new mux(es) in network '%s' (total %d)", + count, in->mn_network_name, total); + return 0; +} + +/* + * + */ +static int +iptv_auto_network_process(iptv_network_t *in, char *data, size_t len) +{ + mpegts_mux_t *mm; + int r = -1, count; + + /* note that we know that data are terminated with '\0' */ + + if (data == NULL || len == 0) + return -1; + + LIST_FOREACH(mm, &in->mn_muxes, mm_network_link) + ((iptv_mux_t *)mm)->im_delete_flag = 1; + + while (*data && *data <= ' ') data++; + + if (!strncmp(data, "#EXTM3U", 7)) + r = iptv_auto_network_process_m3u(in, data); + + if (r == 0) { + count = 0; + LIST_FOREACH(mm, &in->mn_muxes, mm_network_link) + if (((iptv_mux_t *)mm)->im_delete_flag) { + mm->mm_delete(mm, 1); + count++; + } + tvhinfo("iptv", "removed %d mux(es) from network '%s'", count, in->mn_network_name); + } else { + LIST_FOREACH(mm, &in->mn_muxes, mm_network_link) + ((iptv_mux_t *)mm)->im_delete_flag = 0; + tvherror("iptv", "unknown playlist format for network '%s'", in->mn_network_name); + } + + return -1; +} + +/* + * + */ +static int +iptv_auto_network_file(iptv_network_t *in, const char *filename) +{ + int fd; + struct stat st; + char *data; + size_t r; + off_t off; + + fd = tvh_open(filename, O_RDONLY, 0); + if (fd < 0) { + tvherror("iptv", "unable to open file '%s' (network '%s'): %s", + filename, in->mn_network_name, strerror(errno)); + return -1; + } + if (fstat(fd, &st) || st.st_size == 0) { + tvherror("iptv", "unable to stat file '%s' (network '%s'): %s", + filename, in->mn_network_name, strerror(errno)); + close(fd); + return -1; + } + data = malloc(st.st_size+1); + off = 0; + do { + r = read(fd, data + off, st.st_size - off); + if (r < 0) { + if (ERRNO_AGAIN(errno)) + continue; + break; + } + off += r; + } while (off != st.st_size); + close(fd); + + if (off == st.st_size) { + data[off] = '\0'; + return iptv_auto_network_process(in, data, off); + } + return -1; +} + +/* + * + */ +static void +iptv_auto_network_fetch_done(void *aux) +{ + http_client_t *hc = aux; + iptv_network_t *in = hc->hc_aux; + if (in->in_http_client) { + in->in_http_client = NULL; + http_client_close((http_client_t *)aux); + } +} + +/* + * + */ +static int +iptv_auto_network_fetch_complete(http_client_t *hc) +{ + iptv_network_t *in = hc->hc_aux; + + switch (hc->hc_code) { + case HTTP_STATUS_MOVED: + case HTTP_STATUS_FOUND: + case HTTP_STATUS_SEE_OTHER: + case HTTP_STATUS_NOT_MODIFIED: + return 0; + } + + pthread_mutex_lock(&global_lock); + + if (hc->hc_code == HTTP_STATUS_OK && hc->hc_result == 0 && hc->hc_data_size > 0) + iptv_auto_network_process(in, hc->hc_data, hc->hc_data_size); + else + tvherror("iptv", "unable to fetch data from url for network '%s' [%d-%d/%zd]", + in->mn_network_name, hc->hc_code, hc->hc_result, hc->hc_data_size); + + /* note: http_client_close must be called outside http_client callbacks */ + gtimer_arm(&in->in_fetch_timer, iptv_auto_network_fetch_done, hc, 0); + + pthread_mutex_unlock(&global_lock); + + return 0; +} + +/* + * + */ +static void +iptv_auto_network_fetch(void *aux) +{ + iptv_network_t *in = aux; + http_client_t *hc; + url_t u; + + if (strncmp(in->in_url, "file://", 7) == 0) { + iptv_auto_network_file(in, in->in_url + 7); + goto arm; + } + + gtimer_disarm(&in->in_auto_timer); + if (in->in_http_client) { + http_client_close(in->in_http_client); + in->in_http_client = NULL; + } + + memset(&u, 0, sizeof(u)); + if (urlparse(in->in_url, &u) < 0) { + tvherror("iptv", "wrong url for network '%s'", in->mn_network_name); + goto arm; + } + hc = http_client_connect(in, HTTP_VERSION_1_1, u.scheme, u.host, u.port, NULL); + if (hc == NULL) { + tvherror("iptv", "unable to open http client for network '%s'", in->mn_network_name); + goto arm; + } + hc->hc_handle_location = 1; + hc->hc_data_limit = 1024*1024; + hc->hc_data_complete = iptv_auto_network_fetch_complete; + http_client_register(hc); + http_client_ssl_peer_verify(hc, in->in_ssl_peer_verify); + if (http_client_simple(hc, &u) < 0) { + http_client_close(hc); + tvherror("iptv", "unable to send http command for network '%s'", in->mn_network_name); + goto arm; + } + + in->in_http_client = hc; + +arm: + gtimer_arm(&in->in_auto_timer, iptv_auto_network_fetch, in, + MAX(1, in->in_refetch_period) * 60); +} + +/* + * + */ +void +iptv_auto_network_init( iptv_network_t *in ) +{ + gtimer_arm(&in->in_auto_timer, iptv_auto_network_fetch, in, 0); +} + +/* + * + */ +void +iptv_auto_network_done( iptv_network_t *in ) +{ + gtimer_disarm(&in->in_auto_timer); + gtimer_disarm(&in->in_fetch_timer); + if (in->in_http_client) { + http_client_close(in->in_http_client); + in->in_http_client = NULL; + } +} diff --git a/src/input/mpegts/iptv/iptv_private.h b/src/input/mpegts/iptv/iptv_private.h index 289601a58..a7e16fab9 100644 --- a/src/input/mpegts/iptv/iptv_private.h +++ b/src/input/mpegts/iptv/iptv_private.h @@ -38,6 +38,8 @@ extern pthread_mutex_t iptv_lock; +struct http_client; + typedef struct iptv_input iptv_input_t; typedef struct iptv_network iptv_network_t; typedef struct iptv_mux iptv_mux_t; @@ -78,9 +80,16 @@ struct iptv_network uint32_t in_max_streams; uint32_t in_max_bandwidth; uint32_t in_max_timeout; + + char *in_url; + uint32_t in_refetch_period; + int in_ssl_peer_verify; + gtimer_t in_auto_timer; + gtimer_t in_fetch_timer; + struct http_client *in_http_client; }; -iptv_network_t *iptv_network_create0 ( const char *uuid, htsmsg_t *conf ); +iptv_network_t *iptv_network_create0 ( const char *uuid, htsmsg_t *conf, const idclass_t *idc ); struct iptv_mux { @@ -117,6 +126,8 @@ struct iptv_mux void *im_data; + int im_delete_flag; + }; iptv_mux_t* iptv_mux_create0 @@ -125,17 +136,24 @@ iptv_mux_t* iptv_mux_create0 struct iptv_service { mpegts_service_t; + char * s_iptv_svcname; }; iptv_service_t *iptv_service_create0 ( iptv_mux_t *im, uint16_t sid, uint16_t pmt_pid, const char *uuid, htsmsg_t *conf ); +extern const idclass_t iptv_network_class; +extern const idclass_t iptv_auto_network_class; + extern iptv_input_t *iptv_input; extern iptv_network_t *iptv_network; void iptv_mux_load_all ( void ); +void iptv_auto_network_init( iptv_network_t *in ); +void iptv_auto_network_done( iptv_network_t *in ); + void iptv_http_init ( void ); void iptv_udp_init ( void ); void iptv_rtsp_init ( void ); diff --git a/src/input/mpegts/iptv/iptv_service.c b/src/input/mpegts/iptv/iptv_service.c index ba1a469d4..b274f19c4 100644 --- a/src/input/mpegts/iptv/iptv_service.c +++ b/src/input/mpegts/iptv/iptv_service.c @@ -41,7 +41,8 @@ iptv_service_config_save ( service_t *s ) static void iptv_service_delete ( service_t *s, int delconf ) { - mpegts_mux_t *mm = ((mpegts_service_t *)s)->s_dvb_mux; + iptv_service_t *is = (iptv_service_t *)s; + mpegts_mux_t *mm = is->s_dvb_mux; char ubuf1[UUID_HEX_SIZE]; char ubuf2[UUID_HEX_SIZE]; @@ -56,6 +57,16 @@ iptv_service_delete ( service_t *s, int delconf ) mpegts_service_delete(s, 0); } +static const char * +iptv_service_channel_name ( service_t *s ) +{ + iptv_service_t *is = (iptv_service_t *)s; + iptv_mux_t *im = (iptv_mux_t *)is->s_dvb_mux; + if (im->mm_iptv_svcname && im->mm_iptv_svcname[0]) + return im->mm_iptv_svcname; + return is->s_dvb_svcname; +} + /* * Create */ @@ -69,8 +80,9 @@ iptv_service_create0 &mpegts_service_class, uuid, (mpegts_mux_t*)im, sid, pmt, conf); - is->s_config_save = iptv_service_config_save; - is->s_delete = iptv_service_delete; + is->s_config_save = iptv_service_config_save; + is->s_delete = iptv_service_delete; + is->s_channel_name = iptv_service_channel_name; /* Set default service name */ if (!is->s_dvb_svcname || !*is->s_dvb_svcname)