]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
channel: Allow optional fuzzy matching when merging services, fixes #4709
authorE.Smith <31170571+azlm8t@users.noreply.github.com>
Sun, 1 Oct 2017 01:46:53 +0000 (02:46 +0100)
committerJaroslav Kysela <perex@perex.cz>
Wed, 8 Nov 2017 16:50:30 +0000 (17:50 +0100)
For historical reasons, our DVB-T and DVB-S have different names
for the same channels. They often differ in case and spacing.
So we have 'One' and 'ONE', '5+1HD' and '5 +1HD'.

So allow an optional fuzzy matching checkbox to ignore whitespace
and HD markers. This allows the channels to be merged. The
exact name chosen depends on the order of mapping, so if the
HD channel is mapped first then they would all merge in to this
name, but if a non-HD channel is the first one created then that
name is chosen. However the name could be subsequently modified
by the user if they desire.

src/channels.c
src/channels.h
src/service_mapper.c
src/service_mapper.h

index 1c9edf3cb42ec6d9d0bced3a043b2c29f5d5bc53..aa77961f7ad64497ad2b848ad33eaf1859d5aaf0 100644 (file)
@@ -578,6 +578,68 @@ channel_find_by_name ( const char *name )
   return ch;
 }
 
+
+/// Copy name without space and HD suffix, lowercase in to a new
+/// buffer
+static char *
+channel_make_fuzzy_name(const char *name)
+{
+  if (!name) return NULL;
+  const size_t len = strlen(name);
+  char *fuzzy_name = malloc(len + 1);
+  char *ch_fuzzy = fuzzy_name;
+  const char *ch = name;
+
+  for (; *ch ; ++ch) {
+    /* Strip trailing 'HD'. */
+    if (*ch == 'H' && *(ch+1) == 'D' && *(ch+2) == 0)
+      break;
+
+    if (!isspace(*ch)) {
+      *ch_fuzzy++ = tolower(*ch);
+    }
+  }
+  /* Terminate the string */
+  *ch_fuzzy = 0;
+  return fuzzy_name;
+}
+
+channel_t *
+channel_find_by_name_fuzzy ( const char *name )
+{
+  channel_t *ch;
+  const char *s;
+
+  if (name == NULL)
+    return NULL;
+
+  char *fuzzy_name = channel_make_fuzzy_name(name);
+
+  CHANNEL_FOREACH(ch) {
+    if (!ch->ch_enabled) continue;
+    s = channel_get_name(ch, NULL);
+    if (s == NULL) continue;
+    /* We need case insensitive since historical constraints means we
+     * often have channels with slightly different case on DVB-T vs
+     * DVB-S such as 'One' and 'ONE'.
+     */
+    if (strcasecmp(s, name) == 0) break;
+    if (strcasecmp(s, fuzzy_name) == 0) break;
+
+    /* If here, we don't have an obvious match, so we need to fixup
+     * the ch name to see if it then matches. We can use strcmp
+     * since both names are already lowercased.
+     */
+    char *s_fuzzy_name = channel_make_fuzzy_name(s);
+    const int is_match = !strcmp(s_fuzzy_name, fuzzy_name);
+    free(s_fuzzy_name);
+    if (is_match) break;
+  }
+
+  free(fuzzy_name);
+  return ch;
+}
+
 channel_t *
 channel_find_by_id ( uint32_t i )
 {
index 430252b25413661d684913527b96d919e1cba24c..5257aa7371b3b8704b1113e79d8be2563d48c079 100644 (file)
@@ -130,6 +130,13 @@ channel_t *channel_create0
 void channel_delete(channel_t *ch, int delconf);
 
 channel_t *channel_find_by_name(const char *name);
+/// Apply fuzzy matching when finding a channel such as ignoring
+/// whitespace, case, and stripping HD suffix. This means that
+/// 'Channel 5+1', 'Channel 5 +1', 'Channel 5+1HD' and
+/// 'Channel 5 +1HD' can all be merged together.
+/// Since channel names aren't unique, this returns the
+/// first match (similar to channel_find_by_name).
+channel_t *channel_find_by_name_fuzzy(const char *name);
 #define channel_find_by_uuid(u)\
   (channel_t*)idnode_find(u, &channel_class, NULL)
 
index def02c8c0177f53bbb74655698b3184172497734..65f462a0421d50a753f8b8bd5467cf013eb5972b 100644 (file)
@@ -224,8 +224,13 @@ service_mapper_process
 
   /* Find existing channel */
   name = service_get_channel_name(s);
-  if (!bq && conf->merge_same_name && name && *name)
+  if (!bq && conf->merge_same_name && name && *name) {
+    /* Try exact match first */
     chn = channel_find_by_name(name);
+    if (!chn && conf->merge_same_name_fuzzy) {
+      chn = channel_find_by_name_fuzzy(name);
+    }
+  }
   if (!chn) {
     chn = channel_create(NULL, NULL, NULL);
     chn->ch_bouquet = bq;
@@ -528,6 +533,19 @@ static const idclass_t service_mapper_conf_class = {
       .desc   = N_("Merge services with the same name into one channel."),
       .off    = offsetof(service_mapper_t, d.merge_same_name),
     },
+    {
+      .type   = PT_BOOL,
+      .id     = "merge_same_name_fuzzy",
+      .name   = N_("Use fuzzy mapping if merging same name"),
+      .desc   = N_("If merge same name is enabled then "
+                   "merge services with the same name into one channel but "
+                   "using fuzzy logic such as ignoring whitespace, case and "
+                   "some channel suffixes such as HD. So 'Channel 5+1', "
+                   "'Channel 5 +1', 'Channel 5+1HD' and 'Channel 5 +1HD' would "
+                   "all merge in to the same channel. The exact name chosen "
+                   "depends on the order the channels are mapped."),
+      .off    = offsetof(service_mapper_t, d.merge_same_name_fuzzy),
+    },
     {
       .type   = PT_BOOL,
       .id     = "type_tags",
index 979a0ebc14749d27b260332e7f1c801e6c56a5a3..f205e66a4bdd2e75488383b4383ac36f28e2add4 100644 (file)
@@ -26,6 +26,7 @@ typedef struct service_mapper_conf
   int check_availability; ///< Check service is receivable
   int encrypted;          ///< Include encrypted services
   int merge_same_name;    ///< Merge entries with the same name
+  int merge_same_name_fuzzy;    ///< Merge entries with the same name with fuzzy matching (ignore case, etc)
   int type_tags;          ///< Create tags based on the service type (SDTV/HDTV/Radio)
   int provider_tags;      ///< Create tags based on provider name
   int network_tags;       ///< Create tags based on network name (useful for multi adapter equipments)