]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
bouquet,fastscan: initial implementation
authorJaroslav Kysela <perex@perex.cz>
Wed, 29 Oct 2014 16:01:11 +0000 (17:01 +0100)
committerJaroslav Kysela <perex@perex.cz>
Sat, 8 Nov 2014 20:05:37 +0000 (21:05 +0100)
21 files changed:
Makefile
data/conf/fastscan [new file with mode: 0644]
src/api.c
src/api.h
src/api/api_bouquet.c [new file with mode: 0644]
src/api/api_channel.c
src/api/api_service.c
src/bouquet.c [new file with mode: 0644]
src/bouquet.h [new file with mode: 0644]
src/idnode.c
src/idnode.h
src/input/mpegts.c
src/input/mpegts.h
src/input/mpegts/dvb.h
src/input/mpegts/dvb_psi.c
src/input/mpegts/mpegts_network.c
src/main.c
src/service.c
src/service.h
src/webui/static/app/cteditor.js
src/webui/static/app/tvheadend.js

index 5cde334cc2a49a367912cbad089ded4c4f689c86..2e49779241ad027d148596ed34457b359873bf0c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -152,7 +152,8 @@ SRCS =  src/version.c \
        src/cron.c \
        src/esfilter.c \
        src/intlconv.c \
-       src/profile.c
+       src/profile.c \
+       src/bouquet.c
 
 SRCS-${CONFIG_UPNP} += \
        src/upnp.c
@@ -173,7 +174,8 @@ SRCS += \
        src/api/api_access.c \
        src/api/api_dvr.c \
        src/api/api_caclient.c \
-       src/api/api_profile.c
+       src/api/api_profile.c \
+       src/api/api_bouquet.c
 
 SRCS += \
        src/parsers/parsers.c \
@@ -229,6 +231,7 @@ SRCS-$(CONFIG_MPEGTS) += \
        src/input/mpegts/dvb_support.c \
        src/input/mpegts/dvb_charset.c \
        src/input/mpegts/dvb_psi.c \
+       src/input/mpegts/fastscan.c \
        src/input/mpegts/tsdemux.c \
        src/input/mpegts/mpegts_mux_sched.c \
   src/input/mpegts/mpegts_network_scan.c \
diff --git a/data/conf/fastscan b/data/conf/fastscan
new file mode 100644 (file)
index 0000000..3c92b10
--- /dev/null
@@ -0,0 +1,50 @@
+[
+  {
+    "name": "Canal Digitaal",
+    "position": 192,
+    "frequency": 12515000,
+    "pid": 900
+  },
+  {
+    "name": "TV Vlaanderen",
+    "position": 192,
+    "frequency": 12515000,
+    "pid": 901
+  },
+  {
+    "name": "TéléSAT",
+    "position": 192,
+    "frequency": 12515000,
+    "pid": 920
+  },
+  {
+    "name": "Mobistar NL",
+    "position": 192,
+    "frequency": 12515000,
+    "pid": 930
+  },
+  {
+    "name": "Mobistar FR",
+    "position": 192,
+    "frequency": 12515000,
+    "pid": 940
+  },
+  {
+    "name": "AustriaSat",
+    "position": 192,
+    "frequency": 12515000,
+    "pid": 950
+  },
+  {
+    "name": "Skylink: Czech Republic",
+    "position": 235,
+    "frequency": 12070000,
+    "pid": 30
+  },
+  {
+    "name": "Skylink: Slovak Republic",
+    "position": 235,
+    "frequency": 12070000,
+    "pid": 31
+  }
+]
index 8594706abb595e96d4c4b47f550208084d3c37ea..b089b9b75d678313b230adccc963d95d3c9e9448 100644 (file)
--- a/src/api.c
+++ b/src/api.c
@@ -125,6 +125,7 @@ void api_init ( void )
   api_mpegts_init();
   api_service_init();
   api_channel_init();
+  api_bouquet_init();
   api_epg_init();
   api_epggrab_init();
   api_status_init();
index 4928484d2d5731e344a66d9e312542236ac8dc6f..e5b360d26b099e279c8aefd2653d160efd93c6a9 100644 (file)
--- a/src/api.h
+++ b/src/api.h
@@ -64,6 +64,7 @@ void api_idnode_init        ( void );
 void api_input_init         ( void );
 void api_service_init       ( void );
 void api_channel_init       ( void );
+void api_bouquet_init       ( void );
 void api_mpegts_init        ( void );
 void api_epg_init           ( void );
 void api_epggrab_init       ( void );
diff --git a/src/api/api_bouquet.c b/src/api/api_bouquet.c
new file mode 100644 (file)
index 0000000..bd3e5e9
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ *  API - bouquet calls
+ *
+ *  Copyright (C) 2014 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/>.
+ */
+
+#ifndef __TVH_API_BOUQUET_H__
+#define __TVH_API_BOUQUET_H__
+
+#include "tvheadend.h"
+#include "bouquet.h"
+#include "access.h"
+#include "api.h"
+
+static void
+api_bouquet_grid
+  ( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf )
+{
+  bouquet_t *bq;
+
+  RB_FOREACH(bq, &bouquets, bq_link)
+    idnode_set_add(ins, (idnode_t*)bq, &conf->filter);
+}
+
+static int
+api_bouquet_create
+  ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+  htsmsg_t *conf;
+  bouquet_t *bq;
+
+  if (!(conf  = htsmsg_get_map(args, "conf")))
+    return EINVAL;
+
+  pthread_mutex_lock(&global_lock);
+  bq = bouquet_create(NULL, conf, NULL, NULL);
+  if (bq)
+    bouquet_save(bq, 0);
+  pthread_mutex_unlock(&global_lock);
+
+  return 0;
+}
+
+void api_bouquet_init ( void )
+{
+  static api_hook_t ah[] = {
+    { "bouquet/class",   ACCESS_ADMIN, api_idnode_class, (void*)&bouquet_class },
+    { "bouquet/grid",    ACCESS_ADMIN, api_idnode_grid,  api_bouquet_grid },
+    { "bouquet/create",  ACCESS_ADMIN, api_bouquet_create, NULL },
+
+    { NULL },
+  };
+
+  api_register_all(ah);
+}
+
+#endif /* __TVH_API_BOUQUET_H__ */
index aaf87b451eb18a57f5934c24528d6f7e9088a418..58ffe6bb0b3f7ae69f519233f847ceeb9a257d0a 100644 (file)
@@ -17,8 +17,8 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __TVH_API_SERVICE_H__
-#define __TVH_API_SERVICE_H__
+#ifndef __TVH_API_CHANNEL_H__
+#define __TVH_API_CHANNEL_H__
 
 #include "tvheadend.h"
 #include "channels.h"
@@ -145,4 +145,4 @@ void api_channel_init ( void )
 }
 
 
-#endif /* __TVH_API_IDNODE_H__ */
+#endif /* __TVH_API_CHANNEL_H__ */
index 38012e4297098b2488da6be7883560bdfc2a4270..f0bd3909ba76d4212701cf30d7c3a59199ae4c9b 100644 (file)
@@ -199,4 +199,4 @@ void api_service_init ( void )
 }
 
 
-#endif /* __TVH_API_IDNODE_H__ */
+#endif /* __TVH_API_SERVICE_H__ */
diff --git a/src/bouquet.c b/src/bouquet.c
new file mode 100644 (file)
index 0000000..0881f91
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ *  tvheadend, bouquets
+ *  Copyright (C) 2014 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 "settings.h"
+#include "access.h"
+#include "bouquet.h"
+#include "service.h"
+
+bouquet_tree_t bouquets;
+
+/**
+ *
+ */
+static int
+_bq_cmp(const void *a, const void *b)
+{
+  return strcmp(((bouquet_t *)a)->bq_src ?: "", ((bouquet_t *)b)->bq_src ?: "");
+}
+
+/**
+ *
+ */
+bouquet_t *
+bouquet_create(const char *uuid, htsmsg_t *conf,
+               const char *name, const char *src)
+{
+  bouquet_t *bq, *bq2;
+  int i;
+
+  lock_assert(&global_lock);
+
+  bq = calloc(1, sizeof(bouquet_t));
+  bq->bq_services = idnode_set_create();
+
+  if (idnode_insert(&bq->bq_id, uuid, &bouquet_class, 0)) {
+    if (uuid)
+      tvherror("bouquet", "invalid uuid '%s'", uuid);
+    free(bq);
+    return NULL;
+  }
+
+  if (conf) {
+    bq->bq_in_load = 1;
+    idnode_load(&bq->bq_id, conf);
+    bq->bq_in_load = 0;
+    if (!htsmsg_get_bool(conf, "shield", &i) && i)
+      bq->bq_shield = 1;
+  }
+
+  if (name) {
+    free(bq->bq_name);
+    bq->bq_name = strdup(name);
+  }
+
+  if (src) {
+    free(bq->bq_src);
+    bq->bq_src = strdup(src);
+  }
+
+  bq2 = RB_INSERT_SORTED(&bouquets, bq, bq_link, _bq_cmp);
+  assert(bq2 == NULL);
+
+  bq->bq_saveflag = 1;
+
+  return bq;
+}
+
+/**
+ *
+ */
+static void
+bouquet_destroy(bouquet_t *bq)
+{
+  if (!bq)
+    return;
+
+  RB_REMOVE(&bouquets, bq, bq_link);
+  idnode_unlink(&bq->bq_id);
+
+  idnode_set_free(bq->bq_services);
+  free(bq->bq_name);
+  free(bq->bq_src);
+  free(bq);
+}
+
+/**
+ *
+ */
+void
+bouquet_destroy_by_service(service_t *t)
+{
+  bouquet_t *bq;
+
+  lock_assert(&global_lock);
+
+  RB_FOREACH(bq, &bouquets, bq_link)
+    if (idnode_set_exists(bq->bq_services, &t->s_id))
+      idnode_set_remove(bq->bq_services, &t->s_id);
+}
+
+/*
+ *
+ */
+bouquet_t *
+bouquet_find_by_source(const char *name, const char *src, int create)
+{
+  bouquet_t *bq;
+  bouquet_t bqs;
+
+  assert(src);
+
+  lock_assert(&global_lock);
+
+  bqs.bq_src = (char *)src;
+  bq = RB_FIND(&bouquets, &bqs, bq_link, _bq_cmp);
+  if (bq)
+    return bq;
+  if (create && name)
+    return bouquet_create(NULL, NULL, name, src);
+  return NULL;
+}
+
+/*
+ *
+ */
+void
+bouquet_add_service(bouquet_t *bq, service_t *s)
+{
+  lock_assert(&global_lock);
+
+  if (!idnode_set_exists(bq->bq_services, &s->s_id)) {
+    idnode_set_add(bq->bq_services, &s->s_id, NULL);
+    bq->bq_saveflag = 1;
+  }
+}
+
+/**
+ *
+ */
+void
+bouquet_save(bouquet_t *bq, int notify)
+{
+  htsmsg_t *c = htsmsg_create_map();
+  idnode_save(&bq->bq_id, c);
+  hts_settings_save(c, "bouquet/%s", idnode_uuid_as_str(&bq->bq_id));
+  if (bq->bq_shield)
+    htsmsg_add_bool(c, "shield", 1);
+  htsmsg_destroy(c);
+  bq->bq_saveflag = 0;
+  if (notify)
+    idnode_notify_simple(&bq->bq_id);
+}
+
+/* **************************************************************************
+ * Class definition
+ * **************************************************************************/
+
+static void
+bouquet_class_save(idnode_t *self)
+{
+  bouquet_save((bouquet_t *)self, 0);
+}
+
+static void
+bouquet_class_delete(idnode_t *self)
+{
+  bouquet_t *bq = (bouquet_t *)self;
+
+  if (!bq->bq_shield) {
+    hts_settings_remove("bouquet/%s", idnode_uuid_as_str(&bq->bq_id));
+    bouquet_destroy(bq);
+  } else {
+    idnode_set_free(bq->bq_services);
+    bq->bq_services = idnode_set_create();
+    bouquet_save(bq, 1);
+  }
+}
+
+static const char *
+bouquet_class_get_title (idnode_t *self)
+{
+  bouquet_t *bq = (bouquet_t *)self;
+
+  if (bq->bq_comment && bq->bq_comment[0] != '\0')
+    return bq->bq_comment;
+  return bq->bq_name ?: "";
+}
+
+static void
+bouquet_class_enabled_notify ( void *obj )
+{
+  bouquet_t *bq = obj;
+  service_t *s;
+  size_t z;
+
+  if (!bq->bq_enabled) {
+    for (z = 0; z < bq->bq_services->is_count; z++) {
+      s = (service_t *)bq->bq_services->is_array[z];
+      if (s->s_master_bouquet == bq)
+        s->s_master_bouquet = NULL;
+    }
+  }
+}
+
+static const void *
+bouquet_class_services_get ( void *obj )
+{
+  htsmsg_t *l = htsmsg_create_list();
+  bouquet_t *bq = obj;
+  size_t z;
+
+  /* Add all */
+  for (z = 0; z < bq->bq_services->is_count; z++)
+    htsmsg_add_str(l, NULL, idnode_uuid_as_str(bq->bq_services->is_array[z]));
+
+  return l;
+}
+
+static char *
+bouquet_class_services_rend ( void *obj )
+{
+  bouquet_t *bq = obj;
+  char buf[32];
+  snprintf(buf, sizeof(buf), "Services Count %zi", bq->bq_services->is_count);
+  return strdup(buf);
+}
+
+static int
+bouquet_class_services_set ( void *obj, const void *p )
+{
+  bouquet_t *bq = obj;
+
+  if (bq->bq_services_waiting)
+    htsmsg_destroy(bq->bq_services_waiting);
+  bq->bq_services_waiting = NULL;
+  if (bq->bq_in_load)
+    bq->bq_services_waiting = htsmsg_copy((htsmsg_t *)p);
+  return 0;
+}
+
+static const void *
+bouquet_class_services_count_get ( void *obj )
+{
+  static uint32_t u32;
+  bouquet_t *bq = obj;
+
+  u32 = bq->bq_services->is_count;
+  return &u32;
+}
+
+const idclass_t bouquet_class = {
+  .ic_class      = "bouquet",
+  .ic_caption    = "Bouquet",
+  .ic_event      = "bouquet",
+  .ic_perm_def   = ACCESS_ADMIN,
+  .ic_save       = bouquet_class_save,
+  .ic_get_title  = bouquet_class_get_title,
+  .ic_delete     = bouquet_class_delete,
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_BOOL,
+      .id       = "enabled",
+      .name     = "Enabled",
+      .off      = offsetof(bouquet_t, bq_enabled),
+      .notify   = bouquet_class_enabled_notify,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "name",
+      .name     = "Name",
+      .off      = offsetof(bouquet_t, bq_name),
+    },
+    {
+      .type     = PT_STR,
+      .id       = "source",
+      .name     = "Source",
+      .off      = offsetof(bouquet_t, bq_src),
+      .opts     = PO_RDONLY,
+    },
+    {
+      .type     = PT_STR,
+      .islist   = 1,
+      .id       = "services",
+      .name     = "Services",
+      .get      = bouquet_class_services_get,
+      .set      = bouquet_class_services_set,
+      .rend     = bouquet_class_services_rend,
+      .opts     = PO_RDONLY | PO_HIDDEN,
+    },
+    {
+      .type     = PT_U32,
+      .id       = "services_count",
+      .name     = "# Services",
+      .get      = bouquet_class_services_count_get,
+      .opts     = PO_RDONLY | PO_NOSAVE,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "comment",
+      .name     = "Comment",
+      .off      = offsetof(bouquet_t, bq_comment),
+    },
+    {
+      .type     = PT_U32,
+      .id       = "lcn_off",
+      .name     = "Channel Number Offset",
+      .off      = offsetof(bouquet_t, bq_lcn_offset),
+    },
+    {}
+  }
+};
+
+/**
+ *
+ */
+void
+bouquet_init(void)
+{
+  htsmsg_t *c, *m;
+  htsmsg_field_t *f;
+
+  RB_INIT(&bouquets);
+
+  /* Load */
+  if ((c = hts_settings_load("bouquet")) != NULL) {
+    HTSMSG_FOREACH(f, c) {
+      if (!(m = htsmsg_field_get_map(f))) continue;
+      (void)bouquet_create(f->hmf_name, m, NULL, NULL);
+    }
+    htsmsg_destroy(c);
+  }
+}
+
+void
+bouquet_service_resolve(void)
+{
+  bouquet_t *bq;
+  htsmsg_field_t *f;
+  service_t *s;
+  const char *str;
+  int saveflag;
+
+  lock_assert(&global_lock);
+
+  RB_FOREACH(bq, &bouquets, bq_link)  {
+    if (!bq->bq_services_waiting)
+      continue;
+    saveflag = bq->bq_saveflag;
+    HTSMSG_FOREACH(f, bq->bq_services_waiting) {
+      if ((str = htsmsg_field_get_str(f))) {
+        s = service_find_by_identifier(str);
+        if (s)
+          bouquet_add_service(bq, s);
+      }
+    }
+    htsmsg_destroy(bq->bq_services_waiting);
+    bq->bq_services_waiting = NULL;
+    bq->bq_saveflag = saveflag;
+  }
+}
+
+void
+bouquet_done(void)
+{
+  bouquet_t *bq;
+
+  pthread_mutex_lock(&global_lock);
+  while ((bq = RB_FIRST(&bouquets)) != NULL)
+    bouquet_destroy(bq);
+  pthread_mutex_unlock(&global_lock);
+}
diff --git a/src/bouquet.h b/src/bouquet.h
new file mode 100644 (file)
index 0000000..2e2537a
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ *  TV headend - Bouquets
+ *  Copyright (C) 2014 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/>.
+ */
+
+#ifndef BOUQUET_H_
+#define BOUQUET_H_
+
+#include "idnode.h"
+#include "htsmsg.h"
+#include "service.h"
+
+typedef struct bouquet {
+  idnode_t bq_id;
+  RB_ENTRY(bouquet) bq_link;
+
+  int           bq_saveflag;
+  int           bq_in_load;
+
+  int           bq_shield;
+  int           bq_enabled;
+  char         *bq_name;
+  char         *bq_src;
+  char         *bq_comment;
+  idnode_set_t *bq_services;
+  htsmsg_t     *bq_services_waiting;
+  uint32_t      bq_lcn_offset;
+
+} bouquet_t;
+
+typedef RB_HEAD(,bouquet) bouquet_tree_t;
+
+extern bouquet_tree_t bouquets;
+
+extern const idclass_t bouquet_class;
+
+/**
+ *
+ */
+bouquet_t *
+bouquet_create(const char *uuid, htsmsg_t *conf,
+               const char *name, const char *src);
+
+/**
+ *
+ */
+void
+bouquet_destroy_by_service(service_t *t);
+
+/**
+ *
+ */
+bouquet_t *
+bouquet_find_by_source(const char *name, const char *src, int create);
+
+/**
+ *
+ */
+void
+bouquet_add_service(bouquet_t *bq, service_t *s);
+
+/**
+ *
+ */
+void
+bouquet_save(bouquet_t *bq, int notify);
+
+/**
+ *
+ */
+void bouquet_init(void);
+void bouquet_service_resolve(void);
+void bouquet_done(void);
+
+#endif /* BOUQUET_H_ */
index e9d52c6120fd60cba62e1c5e345a5e267572bb7d..bf60af443815c63a51f6033d16c5ea787286cd85 100644 (file)
@@ -164,8 +164,8 @@ idnode_insert(idnode_t *in, const char *uuid, const idclass_t *class, int flags)
   } while (c != NULL && --retries > 0);
 
   if(c != NULL) {
-    fprintf(stderr, "Id node collision%s\n",
-            (flags & IDNODE_SHORT_UUID) ? " (short)" : "");
+    fprintf(stderr, "Id node collision (%s) %s\n",
+            uuid, (flags & IDNODE_SHORT_UUID) ? " (short)" : "");
     abort();
   }
   tvhtrace("idnode", "insert node %s", idnode_uuid_as_str(in));
@@ -958,6 +958,21 @@ idnode_set_add
   is->is_array[is->is_count++] = in;
 }
 
+void
+idnode_set_remove
+  ( idnode_set_t *is, idnode_t *in )
+{
+  size_t z;
+
+  for (z = 0; z < is->is_count; z++)
+    if (is->is_array[z] == in) {
+      memmove(&is->is_array[z], &is->is_array[z+1],
+              (is->is_count - z - 1) * sizeof(idnode_t *));
+      is->is_count--;
+      break;
+    }
+}
+
 int
 idnode_set_exists
   ( idnode_set_t *is, idnode_t * in )
index fbaae48f448fcaae228e48de7929843e18ab1e81..544e6f91e8cd881eb8ba9a03eb2e4017427dbbce 100644 (file)
@@ -200,11 +200,12 @@ int  idnode_filter
 #define idnode_set_create() calloc(1, sizeof(idnode_set_t))
 void idnode_set_add
   ( idnode_set_t *is, idnode_t *in, idnode_filter_t *filt );
+void idnode_set_remove ( idnode_set_t *is, idnode_t *in );
 int idnode_set_exists ( idnode_set_t *is, idnode_t *in );
-void idnode_set_sort    ( idnode_set_t *is, idnode_sort_t *s );
+void idnode_set_sort ( idnode_set_t *is, idnode_sort_t *s );
 void idnode_set_sort_by_title ( idnode_set_t *is );
 htsmsg_t *idnode_set_as_htsmsg ( idnode_set_t *is );
-void idnode_set_free    ( idnode_set_t *is );
+void idnode_set_free ( idnode_set_t *is );
 
 #endif /* __TVH_IDNODE_H__ */
 
index 7a105bb84ce8c4242cd48414501a65b8f9882512..4b65b89b152f0cfc9f0235c88f0091a149537fa5 100644 (file)
@@ -17,6 +17,7 @@
  */
 
 #include "input.h"
+#include "mpegts/fastscan.h"
 
 void
 mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
@@ -27,6 +28,9 @@ mpegts_init ( int linuxdvb_mask, str_list_t *satip_client,
   idclass_register(&mpegts_mux_class);
   idclass_register(&mpegts_service_class);
 
+  /* FastScan init */
+  dvb_fastscan_init();
+
   /* Network scanner */
 #if ENABLE_MPEGTS
   mpegts_network_scan_init();
@@ -97,6 +101,7 @@ mpegts_done ( void )
 #if ENABLE_TSFILE
   tvhftrace("main", tsfile_done);
 #endif
+  dvb_fastscan_done();
 }
 
 /******************************************************************************
index 55f544d7f4f0578238622649225991bec5dc687e..57a5c0470d85f9992d0bba4adba616c7e055fa85 100644 (file)
@@ -425,9 +425,9 @@ struct mpegts_service
    * Fields defined by DVB standard EN 300 468
    */
 
-  uint16_t s_dvb_service_id;
-  uint16_t s_dvb_channel_num;
+  uint32_t s_dvb_channel_num;
   uint16_t s_dvb_channel_minor;
+  uint16_t s_dvb_service_id;
   char    *s_dvb_svcname;
   char    *s_dvb_provider;
   char    *s_dvb_cridauth;
index 2aee8a39dd5d62edceb34d9b2b9525c398737b6e..2f6a733726c05eb0f0aaf1bad69ff2b500cc1d7c 100644 (file)
@@ -59,6 +59,10 @@ struct mpegts_mux;
 #define DVB_BAT_BASE                  0x48
 #define DVB_BAT_MASK                  0xF8
 
+#define DVB_FASTSCAN_NIT_BASE         0xBC
+#define DVB_FASTSCAN_SDT_BASE         0xBD
+#define DVB_FASTSCAN_MASK             0xFF
+
 #define DVB_VCT_T_BASE                0xC8
 #define DVB_VCT_C_BASE                0xC9
 #define DVB_VCT_MASK                  0xFF
@@ -210,6 +214,8 @@ int dvb_nit_callback
   (struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
 int dvb_bat_callback  
   (struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
+int dvb_fs_sdt_callback
+  (struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
 int dvb_sdt_callback
   (struct mpegts_table *mt, const uint8_t *ptr, int len, int tableid);
 int dvb_tdt_callback
index 67d35c9231bcafc456cc3af1046fe23d27aa43f6..8f8a7abda8a09a8a0e0db5428afa4b68a94fbfd2 100644 (file)
@@ -24,6 +24,8 @@
 #include "lang_codes.h"
 #include "service.h"
 #include "dvb_charset.h"
+#include "bouquet.h"
+#include "fastscan.h"
 
 #include <assert.h>
 #include <stdio.h>
@@ -321,7 +323,7 @@ dvb_desc_service
 
 static int
 dvb_desc_service_list
-  ( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm )
+  ( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm, bouquet_t *bq )
 {
   uint16_t stype, sid;
   int i;
@@ -333,6 +335,8 @@ dvb_desc_service_list
     if (mm) {
       int save = 0;
       s = mpegts_service_find(mm, sid, 0, 1, &save);
+      if (bq)
+        bouquet_add_service(bq, (service_t *)s);
       if (save)
         s->s_config_save((service_t*)s);
     }
@@ -342,7 +346,7 @@ dvb_desc_service_list
 
 static int
 dvb_desc_local_channel
-  ( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm )
+  ( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm, bouquet_t *bq )
 {
   int save = 0;
   uint16_t sid, lcn;
@@ -354,6 +358,11 @@ dvb_desc_local_channel
     if (lcn && mm) {
       mpegts_service_t *s = mpegts_service_find(mm, sid, 0, 0, &save);
       if (s) {
+        if (bq && bq->bq_lcn_offset &&
+            (!s->s_master_bouquet || s->s_master_bouquet == bq)) {
+          lcn += bq->bq_lcn_offset;
+          s->s_master_bouquet = bq;
+        }
         if (s->s_dvb_channel_num != lcn) {
           s->s_dvb_channel_num = lcn;
           s->s_config_save((service_t*)s);
@@ -721,18 +730,20 @@ dvb_nit_callback
   mpegts_network_t *mn = mm->mm_network;
   char name[256], dauth[256];
   mpegts_table_state_t  *st  = NULL;
+  bouquet_t *bq = NULL;
   const char *charset;
 
   /* Net/Bat ID */
   nbid = (ptr[0] << 8) | ptr[1];
 
   /* Begin */
-  if (tableid != 0x40 && tableid != 0x41 && tableid != 0x4A) return -1;
+  if (tableid != 0x40 && tableid != 0x41 && tableid != 0x4A && tableid != 0xBC)
+    return -1;
   r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
   if (r != 1) return r;
 
   /* NIT */
-  if (tableid != 0x4A) {
+  if (tableid != 0x4A && tableid != 0xBC /* fastscan */) {
 
     /* Specific NID */
     if (mn->mn_nid) {
@@ -764,8 +775,13 @@ dvb_nit_callback
     }
   }
 
+  /* Fastscan */
+  if (tableid == 0xBC) {
+    tvhdebug(mt->mt_name, "fastscan %04X (%d) [%s]", nbid, nbid, name);
+    bq = mt->mt_opaque;
+
   /* BAT */
-  if (tableid == 0x4A) {
+  } else if (tableid == 0x4A) {
     tvhdebug(mt->mt_name, "bouquet %04X (%d) [%s]", nbid, nbid, name);
 
   /* NIT */
@@ -780,7 +796,6 @@ dvb_nit_callback
   DVB_LOOP_FOREACH(ptr, len, 0, lptr, llen, 6) {
     tsid  = (lptr[0] << 8) | lptr[1];
     onid  = (lptr[2] << 8) | lptr[3];
-    tvhdebug(mt->mt_name, "  onid %04X (%d) tsid %04X (%d)", onid, onid, tsid, tsid);
 
     /* Find existing mux */
     LIST_FOREACH(mux, &mn->mn_muxes, mm_network_link)
@@ -788,6 +803,33 @@ dvb_nit_callback
         break;
     charset = dvb_charset_find(mn, mux, NULL);
 
+#if ENABLE_MPEGTS_DVB
+    dauth[0] = 0;
+    if (mux && *name && tableid == 0x4A /* BAT */) {
+      if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbs_class)) {
+        dvb_mux_conf_t *mc = &((dvb_mux_t *)mux)->lm_tuning;
+        if (mc->u.dmc_fe_qpsk.orbital_dir) {
+          int pos = mc->u.dmc_fe_qpsk.orbital_pos;
+          if (mc->u.dmc_fe_qpsk.orbital_dir == 'W')
+            pos = -pos;
+          snprintf(dauth, sizeof(dauth), "dvb-bouquet://dvbs,%d/%s", pos, name);
+        }
+      } else if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbt_class)) {
+        snprintf(dauth, sizeof(dauth), "dvb-bouquet://dvbt/%s", name);
+      } else if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbc_class)) {
+        snprintf(dauth, sizeof(dauth), "dvb-bouquet://dvbc/%s", name);
+      }
+    }
+    if (dauth[0]) {
+      bouquet_t *bq2 = bouquet_find_by_source(name, dauth, 1);
+      if (bq2 != bq && bq && bq->bq_saveflag)
+        bouquet_save(bq, 1);
+      bq = bq2;
+    }
+#endif
+
+    tvhdebug(mt->mt_name, "  onid %04X (%d) tsid %04X (%d) mux %p bq %p", onid, onid, tsid, tsid, mux, bq);
+
     DVB_DESC_FOREACH(lptr, llen, 4, dlptr, dllen, dtag, dlen, dptr) {
       tvhtrace(mt->mt_name, "    dtag %02X dlen %d", dtag, dlen);
 
@@ -839,17 +881,20 @@ dvb_nit_callback
             mpegts_mux_set_crid_authority(mux, dauth);
           break;
         case DVB_DESC_LOCAL_CHAN:
-          if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux))
+          if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
             return -1;
           break;
         case DVB_DESC_SERVICE_LIST:
-          if (dvb_desc_service_list(mt->mt_name, dptr, dlen, mux))
+          if (dvb_desc_service_list(mt->mt_name, dptr, dlen, mux, bq))
             return -1;
           break;
       }
     }
   }
 
+  if (bq && bq->bq_saveflag)
+    bouquet_save(bq, 1);
+
   /* End */
   return dvb_table_end(mt, st, sect);
 }
@@ -940,7 +985,7 @@ dvb_sdt_callback
       int r;
       s->s_dvb_servicetype = stype;
       save = 1;
-      tvhtrace("sdt", "    type changed");
+      tvhtrace("sdt", "    type changed (old %i)", s->s_dvb_servicetype);
 
       /* Set tvh service type */
       if ((r = dvb_servicetype_lookup(stype)) != -1)
@@ -1100,6 +1145,144 @@ dvb_bat_callback
   return dvb_nit_callback(mt, ptr, len, tableid);
 }
 
+#if ENABLE_MPEGTS_DVB
+/*
+ * DVB fastscan table processing
+ */
+int
+dvb_fs_sdt_callback
+  (mpegts_table_t *mt, const uint8_t *ptr, int len, int tableid)
+{
+  int r, sect, last, ver;
+  uint8_t dtag;
+  int llen, dlen;
+  const uint8_t *lptr, *dptr;
+  uint16_t nbid = 0, onid, tsid, service_id;
+  mpegts_mux_t     *mm = mt->mt_mux, *mux;
+  mpegts_network_t *mn = mm->mm_network;
+  mpegts_table_state_t  *st  = NULL;
+  bouquet_t *bq = mt->mt_opaque;
+
+  /* Fastscan ID */
+  nbid = (ptr[0] << 8) | ptr[1];
+
+  /* Begin */
+  if (tableid != 0xBD)
+    return -1;
+  r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
+  if (r != 1) return r;
+  if (len < 5) return -1;
+  ptr += 5;
+  len -= 5;
+
+  while (len > 0) {
+    const char *charset;
+    char sprov[256] = "", sname[256] = "";
+    mpegts_service_t *s;
+    int stype = 0, save = 0;
+
+    onid = (ptr[0] << 8) | ptr[1];
+    tsid = (ptr[2] << 8) | ptr[3];
+    service_id = (ptr[4] << 8) | ptr[5];
+    /* (ptr[6] << 8) | ptr[7]   - video pid */
+    /* (ptr[7] << 8) | ptr[8]   - audio pid */
+    /* (ptr[9] << 8) | ptr[10]  - video ecm pid */
+    /* (ptr[10] << 8) | ptr[12] - audio ecm pid */
+    /* (ptr[14] << 8) | ptr[15] - pcr pid */
+
+    tvhdebug(mt->mt_name, "  service %04X (%d) onid %04X (%d) tsid %04X (%d)",
+             service_id, service_id, onid, onid, tsid, tsid);
+
+    /* Initialise the loop */
+    DVB_LOOP_INIT(ptr, len, 16, lptr, llen);
+
+    /* Find service */
+    s       = mpegts_service_find(mm, service_id, 0, 1, &save);
+    charset = dvb_charset_find(mn, mm, s);
+    if (bq && s)
+      bouquet_add_service(bq, (service_t *)s);
+
+    /* Descriptor loop */
+    DVB_DESC_EACH(lptr, llen, dtag, dlen, dptr) {
+      tvhtrace(mt->mt_name, "    dtag %02X dlen %d", dtag, dlen);
+      switch (dtag) {
+        case DVB_DESC_SERVICE:
+          if (dvb_desc_service(dptr, dlen, &stype, sprov,
+                                sizeof(sprov), sname, sizeof(sname), charset))
+            return -1;
+          break;
+        case DVB_DESC_LOCAL_CHAN:
+          /* Find existing mux */
+          LIST_FOREACH(mux, &mn->mn_muxes, mm_network_link)
+            if (mux->mm_onid == onid && mux->mm_tsid == tsid)
+              break;
+          if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
+            return -1;
+          break;
+        case DVB_DESC_SAT_DEL:
+          mux = dvb_desc_sat_del(mm, onid, tsid, dptr, dlen);
+          if (mux) {
+            mpegts_mux_set_onid(mux, onid);
+            mpegts_mux_set_tsid(mux, tsid, 0);
+          }
+          break;
+      }
+    }
+
+    tvhtrace(mt->mt_name, "    type %d name [%s] provider [%s]",
+             stype, sname, sprov);
+
+    /* Update service type */
+    if (stype && !s->s_dvb_servicetype) {
+      int r;
+      s->s_dvb_servicetype = stype;
+      save = 1;
+      tvhtrace(mt->mt_name, "    type changed");
+
+      /* Set tvh service type */
+      if ((r = dvb_servicetype_lookup(stype)) != -1)
+        s->s_servicetype = r;
+    }
+
+    /* Update name */
+    if (*sname && strcmp(s->s_dvb_svcname ?: "", sname)) {
+      if (!s->s_dvb_svcname) {
+        tvh_str_update(&s->s_dvb_svcname, sname);
+        save = 1;
+        tvhtrace(mt->mt_name, "    name changed");
+      }
+    }
+
+    /* Update provider */
+    if (*sprov && strcmp(s->s_dvb_provider ?: "", sprov)) {
+      if (!s->s_dvb_provider) {
+        tvh_str_update(&s->s_dvb_provider, sprov);
+        save = 1;
+        tvhtrace(mt->mt_name, "    provider changed");
+      }
+    }
+
+    if (save) {
+      /* Update nice name */
+      pthread_mutex_lock(&s->s_stream_mutex);
+      service_make_nicename((service_t*)s);
+      pthread_mutex_unlock(&s->s_stream_mutex);
+      tvhdebug(mt->mt_name, "  nicename %s", s->s_nicename);
+      /* Save changes */
+      idnode_changed(&s->s_id);
+      service_refresh_channel((service_t*)s);
+    }
+  }
+
+  if (bq && bq->bq_saveflag)
+    bouquet_save(bq, 1);
+
+  /* End */
+  return dvb_table_end(mt, st, sect);
+
+}
+#endif
+
 /**
  * PMT update reason flags
  */
@@ -1589,6 +1772,18 @@ psi_tables_default ( mpegts_mux_t *mm )
                    NULL, "cat", MT_QUICKREQ | MT_CRC, DVB_CAT_PID);
 }
 
+#if ENABLE_MPEGTS_DVB
+static void
+psi_tables_dvb_fastscan( void *aux, bouquet_t *bq, const char *name, int pid )
+{
+  tvhtrace("fastscan", "adding table %04X (%i) for '%s'", pid, pid, name);
+  mpegts_table_add(aux, DVB_FASTSCAN_NIT_BASE, DVB_FASTSCAN_MASK,
+                   dvb_nit_callback, bq, "fs_nit", MT_CRC, pid);
+  mpegts_table_add(aux, DVB_FASTSCAN_SDT_BASE, DVB_FASTSCAN_MASK,
+                   dvb_fs_sdt_callback, bq, "fs_sdt", MT_CRC, pid);
+}
+#endif
+
 void
 psi_tables_dvb ( mpegts_mux_t *mm )
 {
@@ -1599,6 +1794,17 @@ psi_tables_dvb ( mpegts_mux_t *mm )
                    DVB_SDT_PID);
   mpegts_table_add(mm, DVB_BAT_BASE, DVB_BAT_MASK, dvb_bat_callback,
                    NULL, "bat", MT_CRC, DVB_BAT_PID);
+#if ENABLE_MPEGTS_DVB
+  if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class)) {
+    dvb_mux_conf_t *mc = &((dvb_mux_t *)mm)->lm_tuning;
+    if (mc->dmc_fe_type == DVB_TYPE_S) {
+      int pos = mc->u.dmc_fe_qpsk.orbital_pos;
+      if (mc->u.dmc_fe_qpsk.orbital_dir == 'W')
+        pos = -pos;
+      dvb_fastscan_each(mm, pos, mc->dmc_fe_freq, psi_tables_dvb_fastscan);
+    }
+  }
+#endif
 }
 
 void
index b43d10f33a1948561409ecdfb746018a793f6ef8..e32d008d68222f5337c868ad21c8dfb22e37ce97 100644 (file)
@@ -382,7 +382,7 @@ mpegts_network_set_network_name
 {
   char buf[256];
   if (mn->mn_network_name) return 0;
-  if (!name || !strcmp(name, mn->mn_network_name ?: ""))
+  if (!name || name[0] == '\0' || !strcmp(name, mn->mn_network_name ?: ""))
     return 0;
   tvh_str_update(&mn->mn_network_name, name);
   mn->mn_display_name(mn, buf, sizeof(buf));
index 6bbf105e5096959f1a850179e38eac41611e619a..a193bab655f5f2f38a8b15250ad6526b71226736 100644 (file)
@@ -69,6 +69,7 @@
 #include "libav.h"
 #endif
 #include "profile.h"
+#include "bouquet.h"
 
 #ifdef PLATFORM_LINUX
 #include <sys/prctl.h>
@@ -813,6 +814,8 @@ main(int argc, char **argv)
   http_client_init(opt_user_agent);
   esfilter_init();
 
+  bouquet_init();
+
   service_init();
 
 #if ENABLE_MPEGTS
@@ -821,6 +824,8 @@ main(int argc, char **argv)
 
   channel_init();
 
+  bouquet_service_resolve();
+
   subscription_init();
 
   dvr_config_init();
@@ -917,6 +922,7 @@ main(int argc, char **argv)
   tvhftrace("main", service_mapper_done);
   tvhftrace("main", service_done);
   tvhftrace("main", channel_done);
+  tvhftrace("main", bouquet_done);
   tvhftrace("main", dvr_done);
   tvhftrace("main", subscription_done);
   tvhftrace("main", access_done);
index 1edabe82b31c0ddad9bfd1615c4d6a3a633d9504..415748fceb7f792b743ef5ae772116618a442de6 100644 (file)
@@ -46,6 +46,7 @@
 #include "input.h"
 #include "access.h"
 #include "esfilter.h"
+#include "bouquet.h"
 
 static void service_data_timeout(void *aux);
 static void service_class_delete(struct idnode *self);
@@ -799,6 +800,8 @@ service_destroy(service_t *t, int delconf)
 
   t->s_status = SERVICE_ZOMBIE;
 
+  bouquet_destroy_by_service(t);
+
   TAILQ_INIT(&t->s_filt_components);
   while((st = TAILQ_FIRST(&t->s_components)) != NULL)
     service_stream_destroy(t, st);
index 3097cb305cc2305b43c2d5167a250de18f33e52c..226a6251ed3f2153ef352b1ce6881307f4138154 100644 (file)
@@ -443,6 +443,11 @@ typedef struct service {
 
   int64_t s_current_pts;
 
+  /*
+   *
+   */
+  void *s_master_bouquet;
+
 } service_t;
 
 
index 79e2e96a8d6818014dfd4ed890e389fd887e2958..89560c34d2196c5734f9c45847608ed797bad0a8 100644 (file)
@@ -6,10 +6,9 @@ tvheadend.cteditor = function(panel, index)
 {
     tvheadend.idnode_grid(panel, {
         url: 'api/channeltag',
-        comet: 'channeltag',
         titleS: 'Channel Tag',
         titleP: 'Channel Tags',
-               iconCls: 'channelTags',
+        iconCls: 'channelTags',
         tabIndex: index,
         add: {
             url: 'api/channeltag',
@@ -27,3 +26,31 @@ tvheadend.cteditor = function(panel, index)
 
     return panel;
 };
+
+/*
+ * Bouquet
+ */
+tvheadend.bouquet = function(panel, index)
+{
+    var list = 'enabled,name,source,services_count,comment,lcn_off';
+
+    tvheadend.idnode_grid(panel, {
+        url: 'api/bouquet',
+        titleS: 'Bouquet',
+        titleP: 'Bouquets',
+        iconCls: 'bouquets',
+        tabIndex: index,
+        list: list,
+        del: true,
+        edit: { params: { list: list } },
+        sort: {
+          field: 'name',
+          direction: 'ASC'
+        },
+        help: function() {
+            new tvheadend.help('Bouquets', 'config_bouquet.html');
+        },
+    });
+
+    return panel;
+};
index 2348fd5c6eef1396064d5c6d26e788db9d32a95b..08834e8ebc341841d5726ed9559b45f4aa411a52 100644 (file)
@@ -366,6 +366,7 @@ function accessUpdate(o) {
         });
         tvheadend.channel_tab(chepg);
         tvheadend.cteditor(chepg);
+        tvheadend.bouquet(chepg);
         tvheadend.epggrab(chepg);
 
         cp.add(chepg);