Add selectable channel databases feature (v0.6.0)
This update introduces GUI-based channel database management, allowing users to enable or disable specific country databases for channel matching. Key Changes: - Convert fields from static list to @property method for dynamic database detection - Add _get_channel_databases() method to scan and extract database metadata - Update _load_channels_data() to filter by enabled databases from settings - Support new database format with country_code, country_name, and version metadata - Maintain backward compatibility with legacy array format - Add dynamic checkbox fields for each detected database in plugin settings - Default behavior: US enabled, all others disabled (or enable if only one database exists) - Update fuzzy_matcher.py GEOGRAPHIC_PATTERNS to handle any country code prefix (CC:, CC , CCC:, CCC ) - Add comprehensive README documentation for new database format and GUI management - Include sample CA_channels.json demonstrating new metadata format Features: - Selectable channel databases through GUI settings - Multi-country support with automatic country code prefix handling - Clear database labels showing country name and version in settings - Improved matching accuracy by enabling only relevant regional databases Version: 0.6.0
This commit is contained in:
154
README.md
154
README.md
@@ -15,6 +15,8 @@ Before installing or using this plugin, it is **highly recommended** that you cr
|
|||||||
* **Advanced Fuzzy Matching**: Automatically finds and assigns streams to channels using an advanced fuzzy-matching engine (`fuzzy_matcher.py`).
|
* **Advanced Fuzzy Matching**: Automatically finds and assigns streams to channels using an advanced fuzzy-matching engine (`fuzzy_matcher.py`).
|
||||||
* **Unlimited Stream Support**: Fetches and processes ALL available streams regardless of quantity (no 10,000 stream limit).
|
* **Unlimited Stream Support**: Fetches and processes ALL available streams regardless of quantity (no 10,000 stream limit).
|
||||||
* **Enhanced OTA Callsign Matching**: Uses a robust `*_channels.json` database for superior callsign extraction and matching for Over-The-Air broadcast channels.
|
* **Enhanced OTA Callsign Matching**: Uses a robust `*_channels.json` database for superior callsign extraction and matching for Over-The-Air broadcast channels.
|
||||||
|
* **Selectable Channel Databases** *(NEW v0.6.0)*: Enable or disable specific channel databases through the GUI settings.
|
||||||
|
* **Multi-Country Support** *(NEW v0.6.0)*: Support for multiple country databases with automatic country code prefix handling (e.g., `CA:`, `UK `).
|
||||||
* **Multi-Stream Assignment**: Assigns **all** matching streams to each channel (e.g., 4K, FHD, HD versions), sorted by quality.
|
* **Multi-Stream Assignment**: Assigns **all** matching streams to each channel (e.g., 4K, FHD, HD versions), sorted by quality.
|
||||||
* **Quality Prioritization**: Sorts matched streams by quality (4K → FHD → HD → (H) → (F) → (D) → SD → Slow).
|
* **Quality Prioritization**: Sorts matched streams by quality (4K → FHD → HD → (H) → (F) → (D) → SD → Slow).
|
||||||
* **Channel Visibility Management**: Automatically enables/disables channels based on stream assignments and duplicate detection.
|
* **Channel Visibility Management**: Automatically enables/disables channels based on stream assignments and duplicate detection.
|
||||||
@@ -44,11 +46,44 @@ Before installing or using this plugin, it is **highly recommended** that you cr
|
|||||||
|
|
||||||
Stream-Mapparr uses `*_channels.json` files to improve OTA (Over-The-Air) and cable channel matching. The plugin includes `US_channels.json` by default, but you can create additional database files for other countries or regions.
|
Stream-Mapparr uses `*_channels.json` files to improve OTA (Over-The-Air) and cable channel matching. The plugin includes `US_channels.json` by default, but you can create additional database files for other countries or regions.
|
||||||
|
|
||||||
|
**NEW in v0.6.0**: Channel databases are now **selectable within the GUI**! You can enable or disable specific databases in the plugin settings.
|
||||||
|
|
||||||
### Database File Format
|
### Database File Format
|
||||||
|
|
||||||
Channel database files follow the naming pattern: `[COUNTRY_CODE]_channels.json` (e.g., `US_channels.json`, `CA_channels.json`, `UK_channels.json`)
|
Channel database files follow the naming pattern: `[COUNTRY_CODE]_channels.json` (e.g., `US_channels.json`, `CA_channels.json`, `UK_channels.json`)
|
||||||
|
|
||||||
Each file contains a JSON array of channel objects with three required fields:
|
#### Recommended Format (v0.6.0+)
|
||||||
|
|
||||||
|
The recommended format includes metadata at the top level:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"country_code": "CA",
|
||||||
|
"country_name": "Canada",
|
||||||
|
"version": "2025-11-10",
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"channel_name": "CBC",
|
||||||
|
"category": "News",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "CTV",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Global",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Legacy Format (Still Supported)
|
||||||
|
|
||||||
|
The legacy format is still supported and uses a direct array:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
@@ -61,17 +96,25 @@ Each file contains a JSON array of channel objects with three required fields:
|
|||||||
"channel_name": "CTV",
|
"channel_name": "CTV",
|
||||||
"category": "Entertainment",
|
"category": "Entertainment",
|
||||||
"type": "National"
|
"type": "National"
|
||||||
},
|
|
||||||
{
|
|
||||||
"channel_name": "Global",
|
|
||||||
"category": "Entertainment",
|
|
||||||
"type": "National"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note**: If using the legacy format without metadata, the database will be displayed in settings using the filename.
|
||||||
|
|
||||||
### Field Descriptions
|
### Field Descriptions
|
||||||
|
|
||||||
|
#### Metadata Fields (Recommended Format Only)
|
||||||
|
|
||||||
|
| Field | Required | Description | Examples |
|
||||||
|
|:---|:---|:---|:---|
|
||||||
|
| **country_code** | Recommended | Two-letter ISO country code | `US`, `CA`, `UK`, `AU`, `DE` |
|
||||||
|
| **country_name** | Recommended | Full country/region name | `United States`, `Canada`, `United Kingdom` |
|
||||||
|
| **version** | Optional | Database version or date | `2025-11-10`, `1.0`, `v2` |
|
||||||
|
| **channels** | Yes | Array of channel objects | See below |
|
||||||
|
|
||||||
|
#### Channel Object Fields
|
||||||
|
|
||||||
| Field | Required | Description | Examples |
|
| Field | Required | Description | Examples |
|
||||||
|:---|:---|:---|:---|
|
|:---|:---|:---|:---|
|
||||||
| **channel_name** | Yes | The channel name or callsign | `CBC`, `BBC One`, `WSBT`, `Sky Sports` |
|
| **channel_name** | Yes | The channel name or callsign | `CBC`, `BBC One`, `WSBT`, `Sky Sports` |
|
||||||
@@ -113,58 +156,97 @@ Each file contains a JSON array of channel objects with three required fields:
|
|||||||
```
|
```
|
||||||
* The plugin will automatically detect and use all `*_channels.json` files in the directory
|
* The plugin will automatically detect and use all `*_channels.json` files in the directory
|
||||||
|
|
||||||
### Example: Creating UK_channels.json
|
### Example: Creating UK_channels.json (Recommended Format)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
{
|
||||||
{
|
"country_code": "UK",
|
||||||
"channel_name": "BBC One",
|
"country_name": "United Kingdom",
|
||||||
"category": "Entertainment",
|
"version": "2025-11-11",
|
||||||
"type": "National"
|
"channels": [
|
||||||
},
|
{
|
||||||
{
|
"channel_name": "BBC One",
|
||||||
"channel_name": "BBC Two",
|
"category": "Entertainment",
|
||||||
"category": "Entertainment",
|
"type": "National"
|
||||||
"type": "National"
|
},
|
||||||
},
|
{
|
||||||
{
|
"channel_name": "BBC Two",
|
||||||
"channel_name": "ITV",
|
"category": "Entertainment",
|
||||||
"category": "Entertainment",
|
"type": "National"
|
||||||
"type": "National"
|
},
|
||||||
},
|
{
|
||||||
{
|
"channel_name": "ITV",
|
||||||
"channel_name": "Channel 4",
|
"category": "Entertainment",
|
||||||
"category": "Entertainment",
|
"type": "National"
|
||||||
"type": "National"
|
},
|
||||||
},
|
{
|
||||||
{
|
"channel_name": "Channel 4",
|
||||||
"channel_name": "Sky Sports",
|
"category": "Entertainment",
|
||||||
"category": "Sports",
|
"type": "National"
|
||||||
"type": "National"
|
},
|
||||||
}
|
{
|
||||||
]
|
"channel_name": "Sky Sports",
|
||||||
|
"category": "Sports",
|
||||||
|
"type": "National"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Managing Channel Databases in the GUI
|
||||||
|
|
||||||
|
**NEW in v0.6.0**: All channel databases are now manageable through the plugin settings!
|
||||||
|
|
||||||
|
1. **Viewing Available Databases**
|
||||||
|
* Navigate to **Plugins** → **Stream-Mapparr** → **Settings**
|
||||||
|
* Scroll to the **"📚 Channel Databases"** section
|
||||||
|
* All detected `*_channels.json` files will be listed with checkboxes
|
||||||
|
|
||||||
|
2. **Enabling/Disabling Databases**
|
||||||
|
* Check the box next to a database to enable it for matching
|
||||||
|
* Uncheck the box to disable it
|
||||||
|
* By default, only the **US** database is enabled
|
||||||
|
* If only one database exists, it will be enabled by default
|
||||||
|
|
||||||
|
3. **Database Labels**
|
||||||
|
* Databases using the **recommended format** show: `Country Name (vVersion)`
|
||||||
|
* Example: `Canada (v2025-11-10)`
|
||||||
|
* Databases using the **legacy format** show: `Filename`
|
||||||
|
* Example: `UK_channels.json`
|
||||||
|
|
||||||
|
4. **Country Code Prefix Handling**
|
||||||
|
* Stream names may be prefixed with country codes (e.g., `CA: CBC`, `UK BBC One`)
|
||||||
|
* The plugin automatically removes these prefixes during matching
|
||||||
|
* Supported formats: `CC:`, `CC `, `CCC:`, or `CCC ` (where C = letter)
|
||||||
|
|
||||||
### Tips for Better Matching
|
### Tips for Better Matching
|
||||||
|
|
||||||
* Include all variations of channel names (e.g., `BBC 1`, `BBC One`, `BBC1`)
|
* Include all variations of channel names (e.g., `BBC 1`, `BBC One`, `BBC1`)
|
||||||
* Add both full names and abbreviations (e.g., `The Sports Network`, `TSN`)
|
* Add both full names and abbreviations (e.g., `The Sports Network`, `TSN`)
|
||||||
* Include regional variants if applicable (e.g., `BBC One London`, `BBC One Scotland`)
|
* Include regional variants if applicable (e.g., `BBC One London`, `BBC One Scotland`)
|
||||||
* Use the exact callsigns for OTA broadcast stations
|
* Use the exact callsigns for OTA broadcast stations
|
||||||
* Test your database by running the plugin and checking the logs for matching activity
|
* Enable only the databases relevant to your region for better matching accuracy
|
||||||
|
* Use the recommended format with metadata for clearer identification in the GUI
|
||||||
|
* Test your database by enabling it in settings and checking the logs for matching activity
|
||||||
|
|
||||||
## Settings Reference
|
## Settings Reference
|
||||||
|
|
||||||
| Setting | Type | Default | Description |
|
| Setting | Type | Default | Description |
|
||||||
|:---|:---|:---|:---|
|
|:---|:---|:---|:---|
|
||||||
| **Fuzzy Match Threshold** | `number` | 85 | Minimum similarity score (0-100) for fuzzy matching. Higher values require closer matches. |
|
| **Overwrite Existing Streams** | `boolean` | True | If enabled, removes all existing streams and replaces with matched streams |
|
||||||
|
| **Fuzzy Match Threshold** | `number` | 85 | Minimum similarity score (0-100) for fuzzy matching. Higher values require closer matches |
|
||||||
| **Dispatcharr URL** | `string` | - | Full URL of your Dispatcharr instance (e.g., `http://192.168.1.10:9191`) |
|
| **Dispatcharr URL** | `string` | - | Full URL of your Dispatcharr instance (e.g., `http://192.168.1.10:9191`) |
|
||||||
| **Dispatcharr Admin Username** | `string` | - | Username for API authentication |
|
| **Dispatcharr Admin Username** | `string` | - | Username for API authentication |
|
||||||
| **Dispatcharr Admin Password** | `password` | - | Password for API authentication |
|
| **Dispatcharr Admin Password** | `password` | - | Password for API authentication |
|
||||||
| **Profile Name** | `string` | - | Name of an existing Channel Profile to process (e.g., "Primary", "Sports") |
|
| **Profile Name** | `string` | - | Name of an existing Channel Profile to process (e.g., "Primary", "Sports") |
|
||||||
| **Channel Groups** | `string` | - | Comma-separated group names to process, or leave empty for all groups |
|
| **Channel Groups** | `string` | - | Comma-separated group names to process, or leave empty for all groups |
|
||||||
| **Ignore Tags** | `string` | - | Comma-separated tags to ignore during matching (e.g., `4K, [4K], [Dead]`) |
|
| **Ignore Tags** | `string` | - | Comma-separated tags to ignore during matching (e.g., `4K, [4K], [Dead]`) |
|
||||||
|
| **Ignore Quality Tags** | `boolean` | True | Remove quality-related patterns like [4K], HD, (SD) during matching |
|
||||||
|
| **Ignore Regional Tags** | `boolean` | True | Remove regional indicators like "East" during matching |
|
||||||
|
| **Ignore Geographic Tags** | `boolean` | True | Remove geographic prefixes like US:, CA:, UK: during matching |
|
||||||
|
| **Ignore Miscellaneous Tags** | `boolean` | True | Remove miscellaneous tags like (CX), (Backup) during matching |
|
||||||
| **Visible Channel Limit** | `number` | 1 | Number of channels per matching group that will be visible and have streams added |
|
| **Visible Channel Limit** | `number` | 1 | Number of channels per matching group that will be visible and have streams added |
|
||||||
|
| **Enable [Database]** *(v0.6.0)* | `boolean` | US: True, Others: False | Enable or disable specific channel databases for matching |
|
||||||
|
|
||||||
## Usage Guide
|
## Usage Guide
|
||||||
|
|
||||||
|
|||||||
107
Stream-Mapparr/CA_channels.json
Normal file
107
Stream-Mapparr/CA_channels.json
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"country_code": "CA",
|
||||||
|
"country_name": "Canada",
|
||||||
|
"version": "2025-11-11",
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"channel_name": "CBC",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "CBC News Network",
|
||||||
|
"category": "News",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "CTV",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "CTV News Channel",
|
||||||
|
"category": "News",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Global",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Citytv",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "TSN",
|
||||||
|
"category": "Sports",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "The Sports Network",
|
||||||
|
"category": "Sports",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Sportsnet",
|
||||||
|
"category": "Sports",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "TVA",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Ici Radio-Canada",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "CTV Comedy Channel",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "CTV Drama Channel",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Discovery Channel",
|
||||||
|
"category": "Documentary",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "History",
|
||||||
|
"category": "Documentary",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Food Network Canada",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "HGTV Canada",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "W Network",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Showcase",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_name": "Space",
|
||||||
|
"category": "Entertainment",
|
||||||
|
"type": "National"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -44,11 +44,14 @@ REGIONAL_PATTERNS = [
|
|||||||
r'\s[Ee][Aa][Ss][Tt]',
|
r'\s[Ee][Aa][Ss][Tt]',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Geographic prefix patterns: US:, USA:, etc.
|
# Geographic prefix patterns: US:, USA:, CA:, UK:, etc.
|
||||||
GEOGRAPHIC_PATTERNS = [
|
GEOGRAPHIC_PATTERNS = [
|
||||||
# Geographic prefixes
|
# Geographic prefixes at start with colon: "US:", "CA:", "UK:", etc. (any 2-3 letter code followed by colon)
|
||||||
r'\bUSA?:\s', # "US:" or "USA:"
|
r'^[A-Z]{2,3}:\s*',
|
||||||
r'\bUS\s', # "US " at word boundary
|
# Geographic prefixes at start with space: "US ", "CA ", "UK ", etc. (any 2-3 letter code followed by space)
|
||||||
|
r'^[A-Z]{2,3}\s+',
|
||||||
|
# Legacy USA pattern for backward compatibility
|
||||||
|
r'\bUSA?:\s',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Miscellaneous patterns: (CX), (Backup), single-letter tags, etc.
|
# Miscellaneous patterns: (CX), (Backup), single-letter tags, etc.
|
||||||
|
|||||||
@@ -29,108 +29,151 @@ LOGGER.setLevel(logging.INFO)
|
|||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
"""Dispatcharr Stream-Mapparr Plugin"""
|
"""Dispatcharr Stream-Mapparr Plugin"""
|
||||||
|
|
||||||
name = "Stream-Mapparr"
|
name = "Stream-Mapparr"
|
||||||
version = "0.5.0d"
|
version = "0.6.0"
|
||||||
description = "🎯 Automatically add matching streams to channels based on name similarity and quality precedence with enhanced fuzzy matching"
|
description = "🎯 Automatically add matching streams to channels based on name similarity and quality precedence with enhanced fuzzy matching"
|
||||||
|
|
||||||
# Settings rendered by UI
|
@property
|
||||||
fields = [
|
def fields(self):
|
||||||
{
|
"""Dynamically generate settings fields including channel database selection."""
|
||||||
"id": "overwrite_streams",
|
# Static fields that are always present
|
||||||
"label": "🔄 Overwrite Existing Streams",
|
static_fields = [
|
||||||
"type": "boolean",
|
{
|
||||||
"default": True,
|
"id": "overwrite_streams",
|
||||||
"help_text": "If enabled, all existing streams will be removed and replaced with matched streams. If disabled, only new streams will be added (existing streams preserved).",
|
"label": "🔄 Overwrite Existing Streams",
|
||||||
},
|
"type": "boolean",
|
||||||
{
|
"default": True,
|
||||||
"id": "fuzzy_match_threshold",
|
"help_text": "If enabled, all existing streams will be removed and replaced with matched streams. If disabled, only new streams will be added (existing streams preserved).",
|
||||||
"label": "🎯 Fuzzy Match Threshold",
|
},
|
||||||
"type": "number",
|
{
|
||||||
"default": 85,
|
"id": "fuzzy_match_threshold",
|
||||||
"help_text": "Minimum similarity score (0-100) for fuzzy matching. Higher values require closer matches. Default: 85",
|
"label": "🎯 Fuzzy Match Threshold",
|
||||||
},
|
"type": "number",
|
||||||
{
|
"default": 85,
|
||||||
"id": "dispatcharr_url",
|
"help_text": "Minimum similarity score (0-100) for fuzzy matching. Higher values require closer matches. Default: 85",
|
||||||
"label": "🌐 Dispatcharr URL",
|
},
|
||||||
"type": "string",
|
{
|
||||||
"default": "",
|
"id": "dispatcharr_url",
|
||||||
"placeholder": "http://192.168.1.10:9191",
|
"label": "🌐 Dispatcharr URL",
|
||||||
"help_text": "URL of your Dispatcharr instance (from your browser address bar). Example: http://127.0.0.1:9191",
|
"type": "string",
|
||||||
},
|
"default": "",
|
||||||
{
|
"placeholder": "http://192.168.1.10:9191",
|
||||||
"id": "dispatcharr_username",
|
"help_text": "URL of your Dispatcharr instance (from your browser address bar). Example: http://127.0.0.1:9191",
|
||||||
"label": "👤 Dispatcharr Admin Username",
|
},
|
||||||
"type": "string",
|
{
|
||||||
"help_text": "Your admin username for the Dispatcharr UI. Required for API access.",
|
"id": "dispatcharr_username",
|
||||||
},
|
"label": "👤 Dispatcharr Admin Username",
|
||||||
{
|
"type": "string",
|
||||||
"id": "dispatcharr_password",
|
"help_text": "Your admin username for the Dispatcharr UI. Required for API access.",
|
||||||
"label": "🔑 Dispatcharr Admin Password",
|
},
|
||||||
"type": "string",
|
{
|
||||||
"input_type": "password",
|
"id": "dispatcharr_password",
|
||||||
"help_text": "Your admin password for the Dispatcharr UI. Required for API access.",
|
"label": "🔑 Dispatcharr Admin Password",
|
||||||
},
|
"type": "string",
|
||||||
{
|
"input_type": "password",
|
||||||
"id": "profile_name",
|
"help_text": "Your admin password for the Dispatcharr UI. Required for API access.",
|
||||||
"label": "📋 Profile Name",
|
},
|
||||||
"type": "string",
|
{
|
||||||
"default": "",
|
"id": "profile_name",
|
||||||
"placeholder": "Sports, Movies, News",
|
"label": "📋 Profile Name",
|
||||||
"help_text": "*** Required Field *** - The name(s) of existing Channel Profile(s) to process channels from. Multiple profiles can be specified separated by commas.",
|
"type": "string",
|
||||||
},
|
"default": "",
|
||||||
{
|
"placeholder": "Sports, Movies, News",
|
||||||
"id": "selected_groups",
|
"help_text": "*** Required Field *** - The name(s) of existing Channel Profile(s) to process channels from. Multiple profiles can be specified separated by commas.",
|
||||||
"label": "📁 Channel Groups (comma-separated)",
|
},
|
||||||
"type": "string",
|
{
|
||||||
"default": "",
|
"id": "selected_groups",
|
||||||
"placeholder": "Sports, News, Entertainment",
|
"label": "📁 Channel Groups (comma-separated)",
|
||||||
"help_text": "Specific channel groups to process, or leave empty for all groups.",
|
"type": "string",
|
||||||
},
|
"default": "",
|
||||||
{
|
"placeholder": "Sports, News, Entertainment",
|
||||||
"id": "ignore_tags",
|
"help_text": "Specific channel groups to process, or leave empty for all groups.",
|
||||||
"label": "🏷️ Ignore Tags (comma-separated)",
|
},
|
||||||
"type": "string",
|
{
|
||||||
"default": "",
|
"id": "ignore_tags",
|
||||||
"placeholder": "4K, [4K], \" East\", \"[Dead]\"",
|
"label": "🏷️ Ignore Tags (comma-separated)",
|
||||||
"help_text": "Tags to ignore when matching streams. Use quotes to preserve spaces/special chars (e.g., \" East\" for tags with leading space).",
|
"type": "string",
|
||||||
},
|
"default": "",
|
||||||
{
|
"placeholder": "4K, [4K], \" East\", \"[Dead]\"",
|
||||||
"id": "ignore_quality_tags",
|
"help_text": "Tags to ignore when matching streams. Use quotes to preserve spaces/special chars (e.g., \" East\" for tags with leading space).",
|
||||||
"label": "🎬 Ignore Quality Tags",
|
},
|
||||||
"type": "boolean",
|
{
|
||||||
"default": True,
|
"id": "ignore_quality_tags",
|
||||||
"help_text": "If enabled, hardcoded quality tags like [4K], [HD], (UHD), etc., will be ignored during matching.",
|
"label": "🎬 Ignore Quality Tags",
|
||||||
},
|
"type": "boolean",
|
||||||
{
|
"default": True,
|
||||||
"id": "ignore_regional_tags",
|
"help_text": "If enabled, hardcoded quality tags like [4K], [HD], (UHD), etc., will be ignored during matching.",
|
||||||
"label": "🌍 Ignore Regional Tags",
|
},
|
||||||
"type": "boolean",
|
{
|
||||||
"default": True,
|
"id": "ignore_regional_tags",
|
||||||
"help_text": "If enabled, hardcoded regional tags like 'East' will be ignored during matching.",
|
"label": "🌍 Ignore Regional Tags",
|
||||||
},
|
"type": "boolean",
|
||||||
{
|
"default": True,
|
||||||
"id": "ignore_geographic_tags",
|
"help_text": "If enabled, hardcoded regional tags like 'East' will be ignored during matching.",
|
||||||
"label": "🗺️ Ignore Geographic Tags",
|
},
|
||||||
"type": "boolean",
|
{
|
||||||
"default": True,
|
"id": "ignore_geographic_tags",
|
||||||
"help_text": "If enabled, hardcoded geographic prefixes like 'US:', 'USA:' will be ignored during matching.",
|
"label": "🗺️ Ignore Geographic Tags",
|
||||||
},
|
"type": "boolean",
|
||||||
{
|
"default": True,
|
||||||
"id": "ignore_misc_tags",
|
"help_text": "If enabled, hardcoded geographic prefixes like 'US:', 'USA:' will be ignored during matching.",
|
||||||
"label": "🏷️ Ignore Miscellaneous Tags",
|
},
|
||||||
"type": "boolean",
|
{
|
||||||
"default": True,
|
"id": "ignore_misc_tags",
|
||||||
"help_text": "If enabled, miscellaneous tags like (CX), (Backup), and single-letter tags will be ignored during matching.",
|
"label": "🏷️ Ignore Miscellaneous Tags",
|
||||||
},
|
"type": "boolean",
|
||||||
{
|
"default": True,
|
||||||
"id": "visible_channel_limit",
|
"help_text": "If enabled, miscellaneous tags like (CX), (Backup), and single-letter tags will be ignored during matching.",
|
||||||
"label": "👁️ Visible Channel Limit",
|
},
|
||||||
"type": "number",
|
{
|
||||||
"default": 1,
|
"id": "visible_channel_limit",
|
||||||
"help_text": "Number of channels that will be visible and have streams added. Channels are prioritized by quality tags, then by channel number.",
|
"label": "👁️ Visible Channel Limit",
|
||||||
},
|
"type": "number",
|
||||||
]
|
"default": 1,
|
||||||
|
"help_text": "Number of channels that will be visible and have streams added. Channels are prioritized by quality tags, then by channel number.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add channel database section header
|
||||||
|
static_fields.append({
|
||||||
|
"id": "channel_databases_header",
|
||||||
|
"type": "info",
|
||||||
|
"label": "📚 Channel Databases",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Dynamically add channel database enable/disable fields
|
||||||
|
try:
|
||||||
|
databases = self._get_channel_databases()
|
||||||
|
|
||||||
|
if databases:
|
||||||
|
for db_info in databases:
|
||||||
|
db_id = db_info['id']
|
||||||
|
db_label = db_info['label']
|
||||||
|
db_default = db_info['default']
|
||||||
|
|
||||||
|
static_fields.append({
|
||||||
|
"id": f"db_enabled_{db_id}",
|
||||||
|
"type": "boolean",
|
||||||
|
"label": f"Enable {db_label}",
|
||||||
|
"help_text": f"Enable or disable the {db_label} channel database for matching.",
|
||||||
|
"default": db_default
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
static_fields.append({
|
||||||
|
"id": "no_databases_found",
|
||||||
|
"type": "info",
|
||||||
|
"label": "⚠️ No channel databases found. Place XX_channels.json files in the plugin directory.",
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.error(f"[Stream-Mapparr] Error loading channel databases for settings: {e}")
|
||||||
|
static_fields.append({
|
||||||
|
"id": "database_error",
|
||||||
|
"type": "info",
|
||||||
|
"label": f"⚠️ Error loading channel databases: {e}",
|
||||||
|
})
|
||||||
|
|
||||||
|
return static_fields
|
||||||
|
|
||||||
# Actions for Dispatcharr UI
|
# Actions for Dispatcharr UI
|
||||||
actions = [
|
actions = [
|
||||||
@@ -201,9 +244,71 @@ class Plugin:
|
|||||||
self.loaded_streams = []
|
self.loaded_streams = []
|
||||||
self.channel_stream_matches = []
|
self.channel_stream_matches = []
|
||||||
self.fuzzy_matcher = None
|
self.fuzzy_matcher = None
|
||||||
|
|
||||||
LOGGER.info(f"[Stream-Mapparr] {self.name} Plugin v{self.version} initialized")
|
LOGGER.info(f"[Stream-Mapparr] {self.name} Plugin v{self.version} initialized")
|
||||||
|
|
||||||
|
def _get_channel_databases(self):
|
||||||
|
"""
|
||||||
|
Scan for channel database files and return metadata for each.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of dicts with 'id', 'label', 'default', and 'file_path' keys
|
||||||
|
"""
|
||||||
|
plugin_dir = os.path.dirname(__file__)
|
||||||
|
databases = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
from glob import glob
|
||||||
|
pattern = os.path.join(plugin_dir, '*_channels.json')
|
||||||
|
channel_files = sorted(glob(pattern))
|
||||||
|
|
||||||
|
for channel_file in channel_files:
|
||||||
|
try:
|
||||||
|
filename = os.path.basename(channel_file)
|
||||||
|
# Extract country code from filename (e.g., "US" from "US_channels.json")
|
||||||
|
country_code = filename.split('_')[0].upper()
|
||||||
|
|
||||||
|
# Try to read the file and extract metadata
|
||||||
|
with open(channel_file, 'r', encoding='utf-8') as f:
|
||||||
|
file_data = json.load(f)
|
||||||
|
|
||||||
|
# Check if it's the new format with metadata
|
||||||
|
if isinstance(file_data, dict) and 'country_code' in file_data:
|
||||||
|
country_name = file_data.get('country_name', filename)
|
||||||
|
version = file_data.get('version', '')
|
||||||
|
if version:
|
||||||
|
label = f"{country_name} (v{version})"
|
||||||
|
else:
|
||||||
|
label = country_name
|
||||||
|
else:
|
||||||
|
# Old format or missing metadata - use filename
|
||||||
|
label = filename
|
||||||
|
|
||||||
|
# Determine default value: US enabled by default, or if only one database, enable it
|
||||||
|
# We'll check the count later
|
||||||
|
default = (country_code == 'US')
|
||||||
|
|
||||||
|
databases.append({
|
||||||
|
'id': country_code,
|
||||||
|
'label': label,
|
||||||
|
'default': default,
|
||||||
|
'file_path': channel_file,
|
||||||
|
'filename': filename
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.warning(f"[Stream-Mapparr] Error reading database file {channel_file}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If only one database exists, enable it by default
|
||||||
|
if len(databases) == 1:
|
||||||
|
databases[0]['default'] = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.error(f"[Stream-Mapparr] Error scanning for channel databases: {e}")
|
||||||
|
|
||||||
|
return databases
|
||||||
|
|
||||||
def _initialize_fuzzy_matcher(self, match_threshold=85):
|
def _initialize_fuzzy_matcher(self, match_threshold=85):
|
||||||
"""Initialize the fuzzy matcher with configured threshold."""
|
"""Initialize the fuzzy matcher with configured threshold."""
|
||||||
if self.fuzzy_matcher is None:
|
if self.fuzzy_matcher is None:
|
||||||
@@ -545,33 +650,86 @@ class Plugin:
|
|||||||
|
|
||||||
return sorted(streams, key=get_quality_index)
|
return sorted(streams, key=get_quality_index)
|
||||||
|
|
||||||
def _load_channels_data(self, logger):
|
def _load_channels_data(self, logger, settings=None):
|
||||||
"""Load channel data from *_channels.json files."""
|
"""
|
||||||
|
Load channel data from enabled *_channels.json files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
logger: Logger instance
|
||||||
|
settings: Plugin settings dict (optional, for filtering by enabled databases)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of channel data from enabled databases
|
||||||
|
"""
|
||||||
plugin_dir = os.path.dirname(__file__)
|
plugin_dir = os.path.dirname(__file__)
|
||||||
channels_data = []
|
channels_data = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Find all *_channels.json files
|
# Get all available databases
|
||||||
from glob import glob
|
databases = self._get_channel_databases()
|
||||||
pattern = os.path.join(plugin_dir, '*_channels.json')
|
|
||||||
channel_files = glob(pattern)
|
if not databases:
|
||||||
|
|
||||||
if channel_files:
|
|
||||||
for channel_file in channel_files:
|
|
||||||
try:
|
|
||||||
with open(channel_file, 'r', encoding='utf-8') as f:
|
|
||||||
file_data = json.load(f)
|
|
||||||
channels_data.extend(file_data)
|
|
||||||
logger.info(f"[Stream-Mapparr] Loaded {len(file_data)} channels from {os.path.basename(channel_file)}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[Stream-Mapparr] Error loading {channel_file}: {e}")
|
|
||||||
|
|
||||||
logger.info(f"[Stream-Mapparr] Loaded total of {len(channels_data)} channels from {len(channel_files)} file(s)")
|
|
||||||
else:
|
|
||||||
logger.warning(f"[Stream-Mapparr] No *_channels.json files found in {plugin_dir}")
|
logger.warning(f"[Stream-Mapparr] No *_channels.json files found in {plugin_dir}")
|
||||||
|
return channels_data
|
||||||
|
|
||||||
|
# Filter to only enabled databases
|
||||||
|
enabled_databases = []
|
||||||
|
for db_info in databases:
|
||||||
|
db_id = db_info['id']
|
||||||
|
setting_key = f"db_enabled_{db_id}"
|
||||||
|
|
||||||
|
# Check if this database is enabled in settings
|
||||||
|
if settings:
|
||||||
|
is_enabled = settings.get(setting_key, db_info['default'])
|
||||||
|
else:
|
||||||
|
# No settings provided, use default
|
||||||
|
is_enabled = db_info['default']
|
||||||
|
|
||||||
|
if is_enabled:
|
||||||
|
enabled_databases.append(db_info)
|
||||||
|
|
||||||
|
if not enabled_databases:
|
||||||
|
logger.warning("[Stream-Mapparr] No channel databases are enabled. Please enable at least one database in settings.")
|
||||||
|
return channels_data
|
||||||
|
|
||||||
|
# Load data from enabled databases
|
||||||
|
for db_info in enabled_databases:
|
||||||
|
channel_file = db_info['file_path']
|
||||||
|
db_label = db_info['label']
|
||||||
|
country_code = db_info['id']
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(channel_file, 'r', encoding='utf-8') as f:
|
||||||
|
file_data = json.load(f)
|
||||||
|
|
||||||
|
# Handle both old and new format
|
||||||
|
if isinstance(file_data, dict) and 'channels' in file_data:
|
||||||
|
# New format with metadata
|
||||||
|
channels_list = file_data['channels']
|
||||||
|
# Add country_code to each channel for prefix handling
|
||||||
|
for channel in channels_list:
|
||||||
|
channel['_country_code'] = country_code
|
||||||
|
elif isinstance(file_data, list):
|
||||||
|
# Old format - direct array
|
||||||
|
channels_list = file_data
|
||||||
|
# Add country_code to each channel for prefix handling
|
||||||
|
for channel in channels_list:
|
||||||
|
channel['_country_code'] = country_code
|
||||||
|
else:
|
||||||
|
logger.error(f"[Stream-Mapparr] Invalid format in {channel_file}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
channels_data.extend(channels_list)
|
||||||
|
logger.info(f"[Stream-Mapparr] Loaded {len(channels_list)} channels from {db_label}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[Stream-Mapparr] Error loading {channel_file}: {e}")
|
||||||
|
|
||||||
|
logger.info(f"[Stream-Mapparr] Loaded total of {len(channels_data)} channels from {len(enabled_databases)} enabled database(s)")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[Stream-Mapparr] Error loading channel data files: {e}")
|
logger.error(f"[Stream-Mapparr] Error loading channel data files: {e}")
|
||||||
|
|
||||||
return channels_data
|
return channels_data
|
||||||
|
|
||||||
def _is_ota_channel(self, channel_info):
|
def _is_ota_channel(self, channel_info):
|
||||||
@@ -1313,7 +1471,7 @@ class Plugin:
|
|||||||
logger.info("[Stream-Mapparr] Settings validated successfully, proceeding with preview...")
|
logger.info("[Stream-Mapparr] Settings validated successfully, proceeding with preview...")
|
||||||
|
|
||||||
# Load channel data from channels.json
|
# Load channel data from channels.json
|
||||||
channels_data = self._load_channels_data(logger)
|
channels_data = self._load_channels_data(logger, settings)
|
||||||
|
|
||||||
# Load processed data
|
# Load processed data
|
||||||
with open(self.processed_data_file, 'r') as f:
|
with open(self.processed_data_file, 'r') as f:
|
||||||
@@ -1522,9 +1680,9 @@ class Plugin:
|
|||||||
token, error = self._get_api_token(settings, logger)
|
token, error = self._get_api_token(settings, logger)
|
||||||
if error:
|
if error:
|
||||||
return {"status": "error", "message": error}
|
return {"status": "error", "message": error}
|
||||||
|
|
||||||
# Load channel data from channels.json
|
# Load channel data from channels.json
|
||||||
channels_data = self._load_channels_data(logger)
|
channels_data = self._load_channels_data(logger, settings)
|
||||||
|
|
||||||
# Load processed data
|
# Load processed data
|
||||||
with open(self.processed_data_file, 'r') as f:
|
with open(self.processed_data_file, 'r') as f:
|
||||||
|
|||||||
Reference in New Issue
Block a user