From: E.Smith <31170571+azlm8t@users.noreply.github.com> Date: Sun, 1 Oct 2017 01:46:53 +0000 (+0100) Subject: channel: Allow optional fuzzy matching when merging services, fixes #4709 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e6aba24a4fe3290b6b1fc51941547410517f29a7;p=thirdparty%2Ftvheadend.git channel: Allow optional fuzzy matching when merging services, fixes #4709 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. --- diff --git a/src/channels.c b/src/channels.c index 1c9edf3cb..aa77961f7 100644 --- a/src/channels.c +++ b/src/channels.c @@ -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 ) { diff --git a/src/channels.h b/src/channels.h index 430252b25..5257aa737 100644 --- a/src/channels.h +++ b/src/channels.h @@ -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) diff --git a/src/service_mapper.c b/src/service_mapper.c index def02c8c0..65f462a04 100644 --- a/src/service_mapper.c +++ b/src/service_mapper.c @@ -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", diff --git a/src/service_mapper.h b/src/service_mapper.h index 979a0ebc1..f205e66a4 100644 --- a/src/service_mapper.h +++ b/src/service_mapper.h @@ -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)