]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
IPTV: Add automatic network (m3u)
authorJaroslav Kysela <perex@perex.cz>
Tue, 13 Oct 2015 15:29:25 +0000 (17:29 +0200)
committerJaroslav Kysela <perex@perex.cz>
Tue, 13 Oct 2015 15:30:43 +0000 (17:30 +0200)
Makefile
src/httpc.c
src/input/mpegts/iptv/iptv.c
src/input/mpegts/iptv/iptv_auto.c [new file with mode: 0644]
src/input/mpegts/iptv/iptv_private.h
src/input/mpegts/iptv/iptv_service.c

index 300917add5132bebd52d059c3b3b402f9f5c45c1..730ec70bad197ecc4f4bbc06abca5d1f6dc00e1b 100644 (file)
--- 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)
 
index 1124d18976a0a735a90c69c0d5cfb2de7492ba26..b317c6872694b5befc4e0932e73e7b98ff41ce92 100644 (file)
@@ -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);
index 63865235c6a08693735fb8b4e7f3271247f59f9b..dfe78999a6a46c2edc533802eceb7d6efbae2187 100644 (file)
@@ -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 (file)
index 0000000..6afa0c3
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "tvheadend.h"
+#include "http.h"
+#include "iptv_private.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+/*
+ *
+ */
+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;
+  }
+}
index 289601a588947a4e7fff7f4aaa3ea0bac58d2a67..a7e16fab90fb466cec8b860295c8fdf020b5842d 100644 (file)
@@ -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 );
index ba1a469d42db5c1b6a03df1246f9ea22da830f5c..b274f19c4e6563786a6760cf5a9832cce6d4ea67 100644 (file)
@@ -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)