# 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_name": "Canada",
"version": "2025-11-11",
"version": "2025-11-10",
"channels": [
{
"channel_name": "CBC",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "CBC Toronto",
"category": "Entertainment",
"type": "Local"
},
{
"channel_name": "CBC Vancouver",
"category": "Entertainment",
"type": "Local"
},
{
"channel_name": "CBC News Network",
"category": "News",
@@ -18,28 +28,128 @@
"category": "Entertainment",
"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",
"category": "News",
"type": "National"
},
{
"channel_name": "CP24",
"category": "News",
"type": "Regional"
},
{
"channel_name": "BNN Bloomberg",
"category": "News",
"type": "National"
},
{
"channel_name": "Global",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Global Toronto",
"category": "Entertainment",
"type": "Local"
},
{
"channel_name": "Citytv",
"category": "Entertainment",
"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",
"category": "Sports",
"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",
"type": "National"
},
@@ -49,13 +159,93 @@
"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",
"type": "National"
},
{
"channel_name": "Ici Radio-Canada",
"category": "Entertainment",
"channel_name": "W Network",
"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"
},
{
@@ -69,39 +259,74 @@
"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",
"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",
"type": "National"
"type": "Regional"
},
{
"channel_name": "Food Network Canada",
"category": "Entertainment",
"type": "National"
"channel_name": "TFO",
"category": "Documentary",
"type": "Regional"
},
{
"channel_name": "HGTV Canada",
"category": "Entertainment",
"type": "National"
"channel_name": "Knowledge Network",
"category": "Documentary",
"type": "Regional"
},
{
"channel_name": "W Network",
"channel_name": "Télé-Québec",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Showcase",
"category": "Entertainment",
"type": "National"
},
{
"channel_name": "Space",
"category": "Entertainment",
"type": "National"
"type": "Regional"
}
]
}

View File

@@ -1,4 +1,8 @@
[
{
"country_code": "US",
"country_name": "United States",
"version": "2025-10-30",
"channels": [
{
"channel_name": "3ABN",
"category": "Religious",
@@ -20160,3 +20164,4 @@
"type": "Broadcast (OTA)"
}
]
}

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 json
import logging
import unicodedata
from glob import glob
# Version: YY.DDD.HHMM (Julian date format: Year.DayOfYear.Time)
__version__ = "25.317.1900"
__version__ = "24.332.1600"
# Setup logging
LOGGER = logging.getLogger("plugins.fuzzy_matcher")
@@ -21,21 +22,25 @@ LOGGER = logging.getLogger("plugins.fuzzy_matcher")
# Quality-related patterns: [4K], HD, (SD), etc.
QUALITY_PATTERNS = [
# Bracketed quality tags: [4K], [UHD], [FHD], [HD], [SD], [Unknown], [Unk], [Slow], [Dead]
r'\[(4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead)\]',
r'\[(?:4k|uhd|fhd|hd|sd|unknown|unk|slow|dead)\]',
# Quality tags in any format: brackets, parentheses, or standalone
# These patterns match quality tags at the beginning, middle, or end of names
# 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.
r'\s(?:4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD)\s',
# Bracketed quality tags: [4K], [UHD], [FHD], [HD], [SD], etc.
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.
r'\s(?:4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD)$',
# Parenthesized quality tags: (4K), (UHD), (FHD), (HD), (SD), etc.
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.
r'\b(?:4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD):?\s',
# Standalone quality tags at START of string (with word boundary)
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)
r'\s\((4K|UHD|FHD|HD|SD|Unknown|Unk|Slow|Dead|FD|Backup)\)',
# Standalone quality tags at END of string (with word boundary)
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.
@@ -46,21 +51,31 @@ REGIONAL_PATTERNS = [
# Geographic prefix patterns: US:, USA:, etc.
GEOGRAPHIC_PATTERNS = [
# Geographic prefixes
r'\bUSA?:\s', # "US:" or "USA:"
r'\bUS\s', # "US " at word boundary
# Country codes in various formats
# Matches patterns like: US, USA, FR, UK, CA, DE, etc.
# 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.
MISC_PATTERNS = [
# Single letter tags in parentheses: (A), (B), (C), etc.
r'\([A-Z]\)',
# Special tags
r'\s\(CX\)', # Cinemax tag
# Backup tags
r'\([bB]ackup\)',
# Remove ALL content within parentheses (e.g., (CX), (B), (PRIME), (Backup), etc.)
r'\s*\([^)]*\)\s*',
]
@@ -293,10 +308,10 @@ class FuzzyMatcher:
Args:
name: Name to normalize
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_geographic: If True, remove geographic prefix patterns (e.g., US:, USA)
ignore_misc: If True, remove miscellaneous patterns (e.g., (CX), (Backup), single-letter tags)
ignore_geographic: If True, remove ALL country code patterns (e.g., US, USA, US:, |FR|, FR -, [UK])
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_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
original_name = name
# Remove leading parenthetical prefixes like (SP2), (D1), etc.
name = re.sub(r'^\([^\)]+\)\s*', '', name)
# Remove ALL leading parenthetical prefixes like (US) (PRIME2), (SP2), (D1), etc.
# 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: ")
# 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):
"""
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()
# Replace non-alphanumeric with space

File diff suppressed because it is too large Load Diff