# Stream-Mapparr Plugin Changelog (v0.6.0a -> v0.6.0b)

## New Features
* **Smart Analysis & Recommendations:**
    * Added "What-if" analysis: Automatically tests matching at lower fuzzy thresholds (down to 65) to identify potential matches missed by current settings.
    * Added Token Mismatch detection: Analyzes unmatched streams to suggest specific prefixes or suffixes (e.g., "US:", "HD") to add to the "Ignore Tags" list.
* **Enhanced CSV Export:**
    * CSV files now include a detailed header section containing active settings, version info, and specific optimization recommendations based on the Smart Analysis.
    * Rows now include data on potential matches found at lower thresholds.
* **Rate Limiting:** Added a "None (Disabled)" option to remove all artificial delays for faster processing on local networks.
* **Notifications:** Implemented WebSocket-based frontend notifications to display progress bars and success/error toasts during long-running operations.

## Improvements
* **Matching Logic:** Improved fuzzy matching algorithm to handle token overlap, better detecting matches where word order differs (e.g., "Channel Name US" vs "US Channel Name").
* **Maintenance:** Added a "Cleanup Orphaned Tasks" action to remove stale Celery Beat schedules left behind by older plugin versions.
* **Scheduling:** Updated schedule saving logic to provide immediate feedback on the next calculated run times in the local timezone.

## Internal
* Added `_get_matches_at_thresholds` and `_analyze_token_mismatch` methods to power the new recommendation engine.
* Refined `_clean_channel_name` to support optional country prefix removal.
This commit is contained in:
Pirates IRC
2025-11-27 14:37:24 -06:00
committed by GitHub
parent f46a91540d
commit 55023af40e
7 changed files with 22160 additions and 21628 deletions

View File

@@ -1,13 +1,23 @@
{ {
"country_code": "CA", "country_code": "CA",
"country_name": "Canada", "country_name": "Canada",
"version": "2025-11-11", "version": "2025-11-10",
"channels": [ "channels": [
{ {
"channel_name": "CBC", "channel_name": "CBC",
"category": "Entertainment", "category": "Entertainment",
"type": "National" "type": "National"
}, },
{
"channel_name": "CBC Toronto",
"category": "Entertainment",
"type": "Local"
},
{
"channel_name": "CBC Vancouver",
"category": "Entertainment",
"type": "Local"
},
{ {
"channel_name": "CBC News Network", "channel_name": "CBC News Network",
"category": "News", "category": "News",
@@ -18,28 +28,128 @@
"category": "Entertainment", "category": "Entertainment",
"type": "National" "type": "National"
}, },
{
"channel_name": "CTV Toronto",
"category": "Entertainment",
"type": "Local"
},
{
"channel_name": "CTV Vancouver",
"category": "Entertainment",
"type": "Local"
},
{
"channel_name": "CTV2",
"category": "Entertainment",
"type": "National"
},
{ {
"channel_name": "CTV News Channel", "channel_name": "CTV News Channel",
"category": "News", "category": "News",
"type": "National" "type": "National"
}, },
{
"channel_name": "CP24",
"category": "News",
"type": "Regional"
},
{
"channel_name": "BNN Bloomberg",
"category": "News",
"type": "National"
},
{ {
"channel_name": "Global", "channel_name": "Global",
"category": "Entertainment", "category": "Entertainment",
"type": "National" "type": "National"
}, },
{
"channel_name": "Global Toronto",
"category": "Entertainment",
"type": "Local"
},
{ {
"channel_name": "Citytv", "channel_name": "Citytv",
"category": "Entertainment", "category": "Entertainment",
"type": "National" "type": "National"
}, },
{
"channel_name": "Ici Radio-Canada Télé",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Ici RDI",
"category": "News",
"type": "National"
},
{
"channel_name": "Ici ARTV",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Ici Explora",
"category": "Documentary",
"type": "National"
},
{
"channel_name": "TVA",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "LCN",
"category": "News",
"type": "National"
},
{
"channel_name": "TVA Sports",
"category": "Sports",
"type": "National"
},
{
"channel_name": "Noovo",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "APTN",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "OMNI",
"category": "Entertainment",
"type": "National"
},
{ {
"channel_name": "TSN", "channel_name": "TSN",
"category": "Sports", "category": "Sports",
"type": "National" "type": "National"
}, },
{ {
"channel_name": "The Sports Network", "channel_name": "TSN1",
"category": "Sports",
"type": "National"
},
{
"channel_name": "TSN2",
"category": "Sports",
"type": "National"
},
{
"channel_name": "TSN3",
"category": "Sports",
"type": "National"
},
{
"channel_name": "TSN4",
"category": "Sports",
"type": "National"
},
{
"channel_name": "TSN5",
"category": "Sports", "category": "Sports",
"type": "National" "type": "National"
}, },
@@ -49,13 +159,93 @@
"type": "National" "type": "National"
}, },
{ {
"channel_name": "TVA", "channel_name": "Sportsnet One",
"category": "Sports",
"type": "National"
},
{
"channel_name": "Sportsnet 360",
"category": "Sports",
"type": "National"
},
{
"channel_name": "Sportsnet East",
"category": "Sports",
"type": "Regional"
},
{
"channel_name": "Sportsnet Ontario",
"category": "Sports",
"type": "Regional"
},
{
"channel_name": "Sportsnet West",
"category": "Sports",
"type": "Regional"
},
{
"channel_name": "Sportsnet Pacific",
"category": "Sports",
"type": "Regional"
},
{
"channel_name": "RDS",
"category": "Sports",
"type": "National"
},
{
"channel_name": "RDS2",
"category": "Sports",
"type": "National"
},
{
"channel_name": "Showcase",
"category": "Entertainment", "category": "Entertainment",
"type": "National" "type": "National"
}, },
{ {
"channel_name": "Ici Radio-Canada", "channel_name": "W Network",
"category": "Entertainment", "category": "Lifestyle",
"type": "National"
},
{
"channel_name": "History",
"category": "Documentary",
"type": "National"
},
{
"channel_name": "Discovery",
"category": "Documentary",
"type": "National"
},
{
"channel_name": "Food Network",
"category": "Lifestyle",
"type": "National"
},
{
"channel_name": "HGTV",
"category": "Lifestyle",
"type": "National"
},
{
"channel_name": "YTV",
"category": "Kids",
"type": "National"
},
{
"channel_name": "Treehouse",
"category": "Kids",
"type": "National"
},
{
"channel_name": "Teletoon",
"category": "Kids",
"type": "National"
},
{
"channel_name": "Family Channel",
"category": "Kids",
"type": "National" "type": "National"
}, },
{ {
@@ -69,39 +259,74 @@
"type": "National" "type": "National"
}, },
{ {
"channel_name": "Discovery Channel", "channel_name": "CTV Sci-Fi Channel",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Much",
"category": "Music",
"type": "National"
},
{
"channel_name": "Crave",
"category": "Movies",
"type": "National"
},
{
"channel_name": "Super Channel",
"category": "Movies",
"type": "National"
},
{
"channel_name": "Super Écran",
"category": "Movies",
"type": "National"
},
{
"channel_name": "Canal D",
"category": "Documentary", "category": "Documentary",
"type": "National" "type": "National"
}, },
{ {
"channel_name": "History", "channel_name": "Canal Vie",
"category": "Lifestyle",
"type": "National"
},
{
"channel_name": "SériesPlus",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Z",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Évasion",
"category": "Lifestyle",
"type": "National"
},
{
"channel_name": "TVO",
"category": "Documentary", "category": "Documentary",
"type": "National" "type": "Regional"
}, },
{ {
"channel_name": "Food Network Canada", "channel_name": "TFO",
"category": "Entertainment", "category": "Documentary",
"type": "National" "type": "Regional"
}, },
{ {
"channel_name": "HGTV Canada", "channel_name": "Knowledge Network",
"category": "Entertainment", "category": "Documentary",
"type": "National" "type": "Regional"
}, },
{ {
"channel_name": "W Network", "channel_name": "Télé-Québec",
"category": "Entertainment", "category": "Entertainment",
"type": "National" "type": "Regional"
},
{
"channel_name": "Showcase",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Space",
"category": "Entertainment",
"type": "National"
} }
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,9 @@
from .plugin import Plugin, fields, actions """
Dispatcharr Stream-Mapparr Plugin
Automatically adds matching streams to channels based on name similarity and quality
"""
from .plugin import Plugin
__version__ = "0.6.0"
__all__ = ["Plugin"]

View File

@@ -8,10 +8,11 @@ import os
import re import re
import json import json
import logging import logging
import unicodedata
from glob import glob from glob import glob
# Version: YY.DDD.HHMM (Julian date format: Year.DayOfYear.Time) # Version: YY.DDD.HHMM (Julian date format: Year.DayOfYear.Time)
__version__ = "25.317.1900" __version__ = "24.332.1600"
# Setup logging # Setup logging
LOGGER = logging.getLogger("plugins.fuzzy_matcher") LOGGER = logging.getLogger("plugins.fuzzy_matcher")
@@ -21,21 +22,25 @@ LOGGER = logging.getLogger("plugins.fuzzy_matcher")
# Quality-related patterns: [4K], HD, (SD), etc. # Quality-related patterns: [4K], HD, (SD), etc.
QUALITY_PATTERNS = [ QUALITY_PATTERNS = [
# Bracketed quality tags: [4K], [UHD], [FHD], [HD], [SD], [Unknown], [Unk], [Slow], [Dead] # Quality tags in any format: brackets, parentheses, or standalone
r'\[(4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead)\]', # These patterns match quality tags at the beginning, middle, or end of names
r'\[(?:4k|uhd|fhd|hd|sd|unknown|unk|slow|dead)\]', # Matches: [4K], (4K), 4K, [FHD], (FHD), FHD, etc.
# Quality keywords list: 4K, 8K, UHD, FHD, HD, SD, FD, Unknown, Unk, Slow, Dead, Backup
# Unbracketed quality tags in middle: " 4K ", " UHD ", " FHD ", " HD ", " SD ", etc. # Bracketed quality tags: [4K], [UHD], [FHD], [HD], [SD], etc.
r'\s(?:4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD)\s', r'\s*\[(4K|8K|UHD|FHD|HD|SD|FD|Unknown|Unk|Slow|Dead|Backup)\]\s*',
# Unbracketed quality tags at end: " 4K", " UHD", " FHD", " HD", " SD", etc. # Parenthesized quality tags: (4K), (UHD), (FHD), (HD), (SD), etc.
r'\s(?:4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD)$', r'\s*\((4K|8K|UHD|FHD|HD|SD|FD|Unknown|Unk|Slow|Dead|Backup)\)\s*',
# Word boundary quality tags with optional colon: "4K:", "UHD:", "FHD:", "HD:", etc. # Standalone quality tags at START of string (with word boundary)
r'\b(?:4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD):?\s', r'^\s*(4K|8K|UHD|FHD|HD|SD|FD|Unknown|Unk|Slow|Dead)\b\s*',
# Parenthesized quality tags: (4K), (UHD), (FHD), (HD), (SD), (Unknown), (Unk), (Slow), (Dead), (Backup) # Standalone quality tags at END of string (with word boundary)
r'\s\((4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD|Backup)\)', r'\s*\b(4K|8K|UHD|FHD|HD|SD|FD|Unknown|Unk|Slow|Dead)$',
# Standalone quality tags in MIDDLE (with word boundaries on both sides)
r'\s+\b(4K|8K|UHD|FHD|HD|SD|FD|Unknown|Unk|Slow|Dead)\b\s+',
] ]
# Regional indicator patterns: East, West, etc. # Regional indicator patterns: East, West, etc.
@@ -46,21 +51,31 @@ REGIONAL_PATTERNS = [
# Geographic prefix patterns: US:, USA:, etc. # Geographic prefix patterns: US:, USA:, etc.
GEOGRAPHIC_PATTERNS = [ GEOGRAPHIC_PATTERNS = [
# Geographic prefixes # Country codes in various formats
r'\bUSA?:\s', # "US:" or "USA:" # Matches patterns like: US, USA, FR, UK, CA, DE, etc.
r'\bUS\s', # "US " at word boundary # With separators: US:, USA:, |FR|, US -, FR -, etc.
# Format: XX: or XXX: (e.g., US:, USA:, FR:, UK:)
# This is safe because the colon clearly indicates a prefix
r'\b[A-Z]{2,3}:\s*',
# Format: XX - or XXX - (e.g., US - , USA - , FR - )
# Safe because the dash clearly indicates a separator
r'\b[A-Z]{2,3}\s*-\s*',
# Format: |XX| or |XXX| (e.g., |US|, |FR|, |UK|)
# Safe because pipes clearly indicate a tag
r'\|[A-Z]{2,3}\|\s*',
# Format: [XX] or [XXX] (e.g., [US], [FR], [UK])
# Safe because brackets clearly indicate a tag
r'\[[A-Z]{2,3}\]\s*',
] ]
# Miscellaneous patterns: (CX), (Backup), single-letter tags, etc. # Miscellaneous patterns: (CX), (Backup), single-letter tags, etc.
MISC_PATTERNS = [ MISC_PATTERNS = [
# Single letter tags in parentheses: (A), (B), (C), etc. # Remove ALL content within parentheses (e.g., (CX), (B), (PRIME), (Backup), etc.)
r'\([A-Z]\)', r'\s*\([^)]*\)\s*',
# Special tags
r'\s\(CX\)', # Cinemax tag
# Backup tags
r'\([bB]ackup\)',
] ]
@@ -293,10 +308,10 @@ class FuzzyMatcher:
Args: Args:
name: Name to normalize name: Name to normalize
user_ignored_tags: Additional user-configured tags to ignore (list of strings) user_ignored_tags: Additional user-configured tags to ignore (list of strings)
ignore_quality: If True, remove quality-related patterns (e.g., [4K], HD, (SD)) ignore_quality: If True, remove ALL quality indicators in any format (e.g., 4K, [4K], (4K), FHD, [FHD], (FHD), HD, SD, UHD, 8K)
ignore_regional: If True, remove regional indicator patterns (e.g., East) ignore_regional: If True, remove regional indicator patterns (e.g., East)
ignore_geographic: If True, remove geographic prefix patterns (e.g., US:, USA) ignore_geographic: If True, remove ALL country code patterns (e.g., US, USA, US:, |FR|, FR -, [UK])
ignore_misc: If True, remove miscellaneous patterns (e.g., (CX), (Backup), single-letter tags) ignore_misc: If True, remove ALL content within parentheses (e.g., (CX), (B), (PRIME), (Backup))
remove_cinemax: If True, remove "Cinemax" prefix (useful when channel name contains "max") remove_cinemax: If True, remove "Cinemax" prefix (useful when channel name contains "max")
remove_country_prefix: If True, remove country code prefixes (e.g., CA:, UK , DE: ) from start of name remove_country_prefix: If True, remove country code prefixes (e.g., CA:, UK , DE: ) from start of name
@@ -309,8 +324,13 @@ class FuzzyMatcher:
# Store original for logging # Store original for logging
original_name = name original_name = name
# Remove leading parenthetical prefixes like (SP2), (D1), etc. # Remove ALL leading parenthetical prefixes like (US) (PRIME2), (SP2), (D1), etc.
name = re.sub(r'^\([^\)]+\)\s*', '', name) # Loop until no more leading parentheses are found
while name.lstrip().startswith('('):
new_name = re.sub(r'^\s*\([^\)]+\)\s*', '', name)
if new_name == name: # No change, break to avoid infinite loop
break
name = new_name
# Remove country code prefix if requested (e.g., "CA:", "UK ", "USA: ") # Remove country code prefix if requested (e.g., "CA:", "UK ", "USA: ")
# This handles multi-country databases where streams may be prefixed with country codes # This handles multi-country databases where streams may be prefixed with country codes
@@ -482,8 +502,19 @@ class FuzzyMatcher:
def process_string_for_matching(self, s): def process_string_for_matching(self, s):
""" """
Normalize a string for token-sort fuzzy matching. Normalize a string for token-sort fuzzy matching.
Lowercases, removes punctuation, sorts tokens. Lowercases, removes accents, removes punctuation, sorts tokens.
Properly handles Unicode characters (e.g., French accents).
""" """
# First, normalize Unicode to decomposed form (NFD)
# This separates base characters from accent marks
# e.g., "é" becomes "e" + combining acute accent
s = unicodedata.normalize('NFD', s)
# Remove combining characters (accent marks)
# Keep only base characters
s = ''.join(char for char in s if unicodedata.category(char) != 'Mn')
# Convert to lowercase
s = s.lower() s = s.lower()
# Replace non-alphanumeric with space # Replace non-alphanumeric with space

File diff suppressed because it is too large Load Diff