Merge pull request #7 from PiratesIRC/claude/multi-profile-validation-csv-headers-011CUvkSt1wZzVhCwFmCN7vw
Add multi-profile support and CSV comment headers
This commit is contained in:
BIN
Stream-Mapparr/__pycache__/plugin.cpython-311.pyc
Normal file
BIN
Stream-Mapparr/__pycache__/plugin.cpython-311.pyc
Normal file
Binary file not shown.
@@ -76,8 +76,8 @@ class Plugin:
|
|||||||
"label": "📋 Profile Name",
|
"label": "📋 Profile Name",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"placeholder": "Sports",
|
"placeholder": "Sports, Movies, News",
|
||||||
"help_text": "*** Required Field *** - The name of an existing Channel Profile to process channels from.",
|
"help_text": "*** Required Field *** - The name(s) of existing Channel Profile(s) to process channels from. Multiple profiles can be specified separated by commas.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "selected_groups",
|
"id": "selected_groups",
|
||||||
@@ -106,6 +106,11 @@ class Plugin:
|
|||||||
|
|
||||||
# Actions for Dispatcharr UI
|
# Actions for Dispatcharr UI
|
||||||
actions = [
|
actions = [
|
||||||
|
{
|
||||||
|
"id": "validate_settings",
|
||||||
|
"label": "✅ Validate Settings",
|
||||||
|
"description": "Validate all plugin settings (profiles, groups, API connection, etc.)",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "load_process_channels",
|
"id": "load_process_channels",
|
||||||
"label": "📥 Load/Process Channels",
|
"label": "📥 Load/Process Channels",
|
||||||
@@ -659,6 +664,7 @@ class Plugin:
|
|||||||
self._initialize_fuzzy_matcher(match_threshold)
|
self._initialize_fuzzy_matcher(match_threshold)
|
||||||
|
|
||||||
action_map = {
|
action_map = {
|
||||||
|
"validate_settings": self.validate_settings_action,
|
||||||
"load_process_channels": self.load_process_channels_action,
|
"load_process_channels": self.load_process_channels_action,
|
||||||
"preview_changes": self.preview_changes_action,
|
"preview_changes": self.preview_changes_action,
|
||||||
"add_streams_to_channels": self.add_streams_to_channels_action,
|
"add_streams_to_channels": self.add_streams_to_channels_action,
|
||||||
@@ -677,6 +683,178 @@ class Plugin:
|
|||||||
LOGGER.error(traceback.format_exc())
|
LOGGER.error(traceback.format_exc())
|
||||||
return {"status": "error", "message": str(e)}
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
def validate_settings_action(self, settings, logger):
|
||||||
|
"""Validate all plugin settings including profiles, groups, and API connection."""
|
||||||
|
validation_results = []
|
||||||
|
has_errors = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Validate API Connection
|
||||||
|
logger.info("[Stream-Mapparr] Validating API connection...")
|
||||||
|
token, error = self._get_api_token(settings, logger)
|
||||||
|
if error:
|
||||||
|
validation_results.append(f"❌ API Connection: FAILED - {error}")
|
||||||
|
has_errors = True
|
||||||
|
# Cannot continue without API access
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": "Validation failed:\n\n" + "\n".join(validation_results)
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
validation_results.append("✅ API Connection: SUCCESS")
|
||||||
|
|
||||||
|
# 2. Validate Profile Names
|
||||||
|
logger.info("[Stream-Mapparr] Validating profile names...")
|
||||||
|
profile_names_str = settings.get("profile_name", "").strip()
|
||||||
|
|
||||||
|
if not profile_names_str:
|
||||||
|
validation_results.append("❌ Profile Name: FAILED - No profile name configured")
|
||||||
|
has_errors = True
|
||||||
|
else:
|
||||||
|
profile_names = [name.strip() for name in profile_names_str.split(',') if name.strip()]
|
||||||
|
profiles = self._get_api_data("/api/channels/profiles/", token, settings, logger)
|
||||||
|
available_profile_names = [p.get('name') for p in profiles if 'name' in p]
|
||||||
|
|
||||||
|
# Check each profile
|
||||||
|
missing_profiles = []
|
||||||
|
found_profiles = []
|
||||||
|
for profile_name in profile_names:
|
||||||
|
found = False
|
||||||
|
for profile in profiles:
|
||||||
|
if profile.get('name', '').lower() == profile_name.lower():
|
||||||
|
found = True
|
||||||
|
found_profiles.append(profile_name)
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
missing_profiles.append(profile_name)
|
||||||
|
|
||||||
|
if missing_profiles:
|
||||||
|
validation_results.append(f"❌ Profile Name: FAILED - The following profiles were not found: {', '.join(missing_profiles)}")
|
||||||
|
validation_results.append(f" Available profiles: {', '.join(available_profile_names)}")
|
||||||
|
has_errors = True
|
||||||
|
else:
|
||||||
|
validation_results.append(f"✅ Profile Name: SUCCESS - Found {len(found_profiles)} profile(s): {', '.join(found_profiles)}")
|
||||||
|
|
||||||
|
# 3. Validate Channel Groups
|
||||||
|
logger.info("[Stream-Mapparr] Validating channel groups...")
|
||||||
|
selected_groups_str = settings.get("selected_groups", "").strip()
|
||||||
|
|
||||||
|
if selected_groups_str:
|
||||||
|
selected_groups = [g.strip() for g in selected_groups_str.split(',') if g.strip()]
|
||||||
|
|
||||||
|
# Get all groups
|
||||||
|
all_groups = []
|
||||||
|
page = 1
|
||||||
|
while True:
|
||||||
|
api_groups = self._get_api_data(f"/api/channels/groups/?page={page}", token, settings, logger)
|
||||||
|
|
||||||
|
if isinstance(api_groups, dict) and 'results' in api_groups:
|
||||||
|
all_groups.extend(api_groups['results'])
|
||||||
|
if not api_groups.get('next'):
|
||||||
|
break
|
||||||
|
page += 1
|
||||||
|
elif isinstance(api_groups, list):
|
||||||
|
all_groups.extend(api_groups)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
available_group_names = [g['name'] for g in all_groups if 'name' in g]
|
||||||
|
|
||||||
|
# Check each group
|
||||||
|
missing_groups = []
|
||||||
|
found_groups = []
|
||||||
|
for group_name in selected_groups:
|
||||||
|
if group_name in available_group_names:
|
||||||
|
found_groups.append(group_name)
|
||||||
|
else:
|
||||||
|
missing_groups.append(group_name)
|
||||||
|
|
||||||
|
if missing_groups:
|
||||||
|
validation_results.append(f"❌ Channel Groups: FAILED - The following groups were not found: {', '.join(missing_groups)}")
|
||||||
|
validation_results.append(f" Available groups: {', '.join(available_group_names[:20])}" + ("..." if len(available_group_names) > 20 else ""))
|
||||||
|
has_errors = True
|
||||||
|
else:
|
||||||
|
validation_results.append(f"✅ Channel Groups: SUCCESS - Found {len(found_groups)} group(s): {', '.join(found_groups)}")
|
||||||
|
else:
|
||||||
|
validation_results.append("✅ Channel Groups: Not specified (will use all groups)")
|
||||||
|
|
||||||
|
# 4. Validate Fuzzy Match Threshold
|
||||||
|
logger.info("[Stream-Mapparr] Validating fuzzy match threshold...")
|
||||||
|
match_threshold = settings.get("fuzzy_match_threshold", 85)
|
||||||
|
try:
|
||||||
|
match_threshold = int(match_threshold)
|
||||||
|
if 0 <= match_threshold <= 100:
|
||||||
|
validation_results.append(f"✅ Fuzzy Match Threshold: SUCCESS - Set to {match_threshold}")
|
||||||
|
else:
|
||||||
|
validation_results.append(f"❌ Fuzzy Match Threshold: WARNING - Value {match_threshold} is outside recommended range (0-100)")
|
||||||
|
has_errors = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
validation_results.append(f"❌ Fuzzy Match Threshold: FAILED - Invalid value: {match_threshold}")
|
||||||
|
has_errors = True
|
||||||
|
|
||||||
|
# 5. Validate Visible Channel Limit
|
||||||
|
logger.info("[Stream-Mapparr] Validating visible channel limit...")
|
||||||
|
visible_channel_limit_str = settings.get("visible_channel_limit", "1")
|
||||||
|
try:
|
||||||
|
visible_channel_limit = int(visible_channel_limit_str) if visible_channel_limit_str else 1
|
||||||
|
if visible_channel_limit >= 1:
|
||||||
|
validation_results.append(f"✅ Visible Channel Limit: SUCCESS - Set to {visible_channel_limit}")
|
||||||
|
else:
|
||||||
|
validation_results.append(f"❌ Visible Channel Limit: FAILED - Must be at least 1")
|
||||||
|
has_errors = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
validation_results.append(f"❌ Visible Channel Limit: FAILED - Invalid value: {visible_channel_limit_str}")
|
||||||
|
has_errors = True
|
||||||
|
|
||||||
|
# 6. Validate Fuzzy Matcher Initialization
|
||||||
|
logger.info("[Stream-Mapparr] Validating fuzzy matcher...")
|
||||||
|
try:
|
||||||
|
match_threshold = settings.get("fuzzy_match_threshold", 85)
|
||||||
|
try:
|
||||||
|
match_threshold = int(match_threshold)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
match_threshold = 85
|
||||||
|
|
||||||
|
self._initialize_fuzzy_matcher(match_threshold)
|
||||||
|
if self.fuzzy_matcher:
|
||||||
|
validation_results.append(f"✅ Fuzzy Matcher: SUCCESS - Initialized with threshold {match_threshold}")
|
||||||
|
else:
|
||||||
|
validation_results.append("⚠️ Fuzzy Matcher: WARNING - Could not initialize (will use fallback matching)")
|
||||||
|
except Exception as e:
|
||||||
|
validation_results.append(f"⚠️ Fuzzy Matcher: WARNING - {str(e)} (will use fallback matching)")
|
||||||
|
|
||||||
|
# 7. Check other settings
|
||||||
|
overwrite_streams = settings.get('overwrite_streams', True)
|
||||||
|
if isinstance(overwrite_streams, str):
|
||||||
|
overwrite_streams = overwrite_streams.lower() in ('true', 'yes', '1')
|
||||||
|
validation_results.append(f"ℹ️ Overwrite Existing Streams: {'Enabled' if overwrite_streams else 'Disabled'}")
|
||||||
|
|
||||||
|
ignore_tags_str = settings.get("ignore_tags", "").strip()
|
||||||
|
if ignore_tags_str:
|
||||||
|
ignore_tags = [tag.strip() for tag in ignore_tags_str.split(',') if tag.strip()]
|
||||||
|
validation_results.append(f"ℹ️ Ignore Tags: {len(ignore_tags)} tag(s) configured: {', '.join(ignore_tags)}")
|
||||||
|
else:
|
||||||
|
validation_results.append("ℹ️ Ignore Tags: None configured")
|
||||||
|
|
||||||
|
# Build summary message
|
||||||
|
if has_errors:
|
||||||
|
message = "Validation completed with errors:\n\n" + "\n".join(validation_results)
|
||||||
|
message += "\n\nPlease fix the errors above before proceeding."
|
||||||
|
return {"status": "error", "message": message}
|
||||||
|
else:
|
||||||
|
message = "All settings validated successfully!\n\n" + "\n".join(validation_results)
|
||||||
|
message += "\n\nYou can now proceed with 'Load/Process Channels'."
|
||||||
|
return {"status": "success", "message": message}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[Stream-Mapparr] Error validating settings: {str(e)}")
|
||||||
|
validation_results.append(f"❌ Unexpected error during validation: {str(e)}")
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": "Validation failed:\n\n" + "\n".join(validation_results)
|
||||||
|
}
|
||||||
|
|
||||||
def load_process_channels_action(self, settings, logger):
|
def load_process_channels_action(self, settings, logger):
|
||||||
"""Load and process channels from specified profile and groups."""
|
"""Load and process channels from specified profile and groups."""
|
||||||
try:
|
try:
|
||||||
@@ -685,43 +863,55 @@ class Plugin:
|
|||||||
if error:
|
if error:
|
||||||
return {"status": "error", "message": error}
|
return {"status": "error", "message": error}
|
||||||
|
|
||||||
profile_name = settings.get("profile_name", "").strip()
|
profile_names_str = settings.get("profile_name", "").strip()
|
||||||
selected_groups_str = settings.get("selected_groups", "").strip()
|
selected_groups_str = settings.get("selected_groups", "").strip()
|
||||||
ignore_tags_str = settings.get("ignore_tags", "").strip()
|
ignore_tags_str = settings.get("ignore_tags", "").strip()
|
||||||
visible_channel_limit_str = settings.get("visible_channel_limit", "1")
|
visible_channel_limit_str = settings.get("visible_channel_limit", "1")
|
||||||
visible_channel_limit = int(visible_channel_limit_str) if visible_channel_limit_str else 1
|
visible_channel_limit = int(visible_channel_limit_str) if visible_channel_limit_str else 1
|
||||||
|
|
||||||
if not profile_name:
|
if not profile_names_str:
|
||||||
return {"status": "error", "message": "Profile Name must be configured in the plugin settings."}
|
return {"status": "error", "message": "Profile Name must be configured in the plugin settings."}
|
||||||
|
|
||||||
if visible_channel_limit < 1:
|
if visible_channel_limit < 1:
|
||||||
return {"status": "error", "message": "Visible Channel Limit must be at least 1."}
|
return {"status": "error", "message": "Visible Channel Limit must be at least 1."}
|
||||||
|
|
||||||
|
# Parse profile names (support comma-separated list)
|
||||||
|
profile_names = [name.strip() for name in profile_names_str.split(',') if name.strip()]
|
||||||
|
logger.info(f"[Stream-Mapparr] Profile names configured: {profile_names}")
|
||||||
|
|
||||||
# Parse ignore tags
|
# Parse ignore tags
|
||||||
ignore_tags = []
|
ignore_tags = []
|
||||||
if ignore_tags_str:
|
if ignore_tags_str:
|
||||||
ignore_tags = [tag.strip() for tag in ignore_tags_str.split(',') if tag.strip()]
|
ignore_tags = [tag.strip() for tag in ignore_tags_str.split(',') if tag.strip()]
|
||||||
logger.info(f"[Stream-Mapparr] Ignore tags configured: {ignore_tags}")
|
logger.info(f"[Stream-Mapparr] Ignore tags configured: {ignore_tags}")
|
||||||
|
|
||||||
# Get all profiles to find the specified one
|
# Get all profiles to find the specified ones
|
||||||
logger.info("[Stream-Mapparr] Fetching channel profiles...")
|
logger.info("[Stream-Mapparr] Fetching channel profiles...")
|
||||||
profiles = self._get_api_data("/api/channels/profiles/", token, settings, logger)
|
profiles = self._get_api_data("/api/channels/profiles/", token, settings, logger)
|
||||||
|
|
||||||
target_profile = None
|
# Find all target profiles
|
||||||
for profile in profiles:
|
target_profiles = []
|
||||||
if profile.get('name', '').lower() == profile_name.lower():
|
profile_ids = []
|
||||||
target_profile = profile
|
for profile_name in profile_names:
|
||||||
break
|
found_profile = None
|
||||||
|
for profile in profiles:
|
||||||
if not target_profile:
|
if profile.get('name', '').lower() == profile_name.lower():
|
||||||
available_profiles = [p.get('name') for p in profiles if 'name' in p]
|
found_profile = profile
|
||||||
return {
|
break
|
||||||
"status": "error",
|
|
||||||
"message": f"Profile '{profile_name}' not found. Available profiles: {', '.join(available_profiles)}"
|
if not found_profile:
|
||||||
}
|
available_profiles = [p.get('name') for p in profiles if 'name' in p]
|
||||||
|
return {
|
||||||
profile_id = target_profile['id']
|
"status": "error",
|
||||||
logger.info(f"[Stream-Mapparr] Found profile: {profile_name} (ID: {profile_id})")
|
"message": f"Profile '{profile_name}' not found. Available profiles: {', '.join(available_profiles)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
target_profiles.append(found_profile)
|
||||||
|
profile_ids.append(found_profile['id'])
|
||||||
|
logger.info(f"[Stream-Mapparr] Found profile: {profile_name} (ID: {found_profile['id']})")
|
||||||
|
|
||||||
|
# For backward compatibility, use first profile ID
|
||||||
|
profile_id = profile_ids[0]
|
||||||
|
|
||||||
# Get all groups (handle pagination)
|
# Get all groups (handle pagination)
|
||||||
logger.info("[Stream-Mapparr] Fetching channel groups...")
|
logger.info("[Stream-Mapparr] Fetching channel groups...")
|
||||||
@@ -750,22 +940,22 @@ class Plugin:
|
|||||||
all_channels = self._get_api_data("/api/channels/channels/", token, settings, logger)
|
all_channels = self._get_api_data("/api/channels/channels/", token, settings, logger)
|
||||||
logger.info(f"[Stream-Mapparr] Retrieved {len(all_channels)} total channels")
|
logger.info(f"[Stream-Mapparr] Retrieved {len(all_channels)} total channels")
|
||||||
|
|
||||||
# Filter channels by profile membership
|
# Filter channels by profile membership (check all target profiles)
|
||||||
# Use Django ORM to check profile membership
|
# Use Django ORM to check profile membership
|
||||||
channels_in_profile = []
|
channels_in_profile = []
|
||||||
for channel in all_channels:
|
for channel in all_channels:
|
||||||
channel_id = channel['id']
|
channel_id = channel['id']
|
||||||
# Check if this channel is enabled in the target profile
|
# Check if this channel is enabled in any of the target profiles
|
||||||
is_in_profile = ChannelProfileMembership.objects.filter(
|
is_in_profile = ChannelProfileMembership.objects.filter(
|
||||||
channel_id=channel_id,
|
channel_id=channel_id,
|
||||||
channel_profile_id=profile_id,
|
channel_profile_id__in=profile_ids,
|
||||||
enabled=True
|
enabled=True
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
if is_in_profile:
|
if is_in_profile:
|
||||||
channels_in_profile.append(channel)
|
channels_in_profile.append(channel)
|
||||||
|
|
||||||
logger.info(f"[Stream-Mapparr] Found {len(channels_in_profile)} channels in profile '{profile_name}'")
|
logger.info(f"[Stream-Mapparr] Found {len(channels_in_profile)} channels in profile(s): {', '.join(profile_names)}")
|
||||||
|
|
||||||
# Filter by groups if specified
|
# Filter by groups if specified
|
||||||
if selected_groups_str:
|
if selected_groups_str:
|
||||||
@@ -852,29 +1042,69 @@ class Plugin:
|
|||||||
# Save to file
|
# Save to file
|
||||||
processed_data = {
|
processed_data = {
|
||||||
"loaded_at": datetime.now().isoformat(),
|
"loaded_at": datetime.now().isoformat(),
|
||||||
"profile_name": profile_name,
|
"profile_name": profile_names_str, # Store original comma-separated string
|
||||||
"profile_id": profile_id,
|
"profile_names": profile_names, # Store parsed list
|
||||||
|
"profile_id": profile_id, # First profile ID for backward compatibility
|
||||||
|
"profile_ids": profile_ids, # All profile IDs
|
||||||
"selected_groups": selected_groups,
|
"selected_groups": selected_groups,
|
||||||
"ignore_tags": ignore_tags,
|
"ignore_tags": ignore_tags,
|
||||||
"visible_channel_limit": visible_channel_limit,
|
"visible_channel_limit": visible_channel_limit,
|
||||||
"channels": channels_to_process,
|
"channels": channels_to_process,
|
||||||
"streams": all_streams_data
|
"streams": all_streams_data
|
||||||
}
|
}
|
||||||
|
|
||||||
with open(self.processed_data_file, 'w') as f:
|
with open(self.processed_data_file, 'w') as f:
|
||||||
json.dump(processed_data, f, indent=2)
|
json.dump(processed_data, f, indent=2)
|
||||||
|
|
||||||
logger.info("[Stream-Mapparr] Channel and stream data loaded and saved successfully")
|
logger.info("[Stream-Mapparr] Channel and stream data loaded and saved successfully")
|
||||||
|
|
||||||
|
profile_display = ', '.join(profile_names)
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"message": f"Successfully loaded {len(channels_to_process)} channels from profile '{profile_name}'{group_filter_info}\n\nFound {len(all_streams_data)} streams available for matching.\n\nVisible channel limit set to: {visible_channel_limit}\n\nYou can now run 'Preview Changes' or 'Add Streams to Channels'."
|
"message": f"Successfully loaded {len(channels_to_process)} channels from profile(s): {profile_display}{group_filter_info}\n\nFound {len(all_streams_data)} streams available for matching.\n\nVisible channel limit set to: {visible_channel_limit}\n\nYou can now run 'Preview Changes' or 'Add Streams to Channels'."
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[Stream-Mapparr] Error loading channels: {str(e)}")
|
logger.error(f"[Stream-Mapparr] Error loading channels: {str(e)}")
|
||||||
return {"status": "error", "message": f"Error loading channels: {str(e)}"}
|
return {"status": "error", "message": f"Error loading channels: {str(e)}"}
|
||||||
|
|
||||||
|
def _generate_csv_header_comment(self, settings, processed_data, total_visible_channels=0, total_matched_streams=0):
|
||||||
|
"""Generate CSV comment header with plugin version and settings info."""
|
||||||
|
profile_name = processed_data.get('profile_name', 'N/A')
|
||||||
|
selected_groups = processed_data.get('selected_groups', [])
|
||||||
|
ignore_tags = processed_data.get('ignore_tags', [])
|
||||||
|
visible_channel_limit = processed_data.get('visible_channel_limit', 1)
|
||||||
|
total_streams = len(processed_data.get('streams', []))
|
||||||
|
|
||||||
|
# Get settings
|
||||||
|
overwrite_streams = settings.get('overwrite_streams', True)
|
||||||
|
if isinstance(overwrite_streams, str):
|
||||||
|
overwrite_streams = overwrite_streams.lower() in ('true', 'yes', '1')
|
||||||
|
fuzzy_match_threshold = settings.get('fuzzy_match_threshold', 85)
|
||||||
|
|
||||||
|
# Build header lines
|
||||||
|
header_lines = [
|
||||||
|
f"# Stream-Mapparr Export",
|
||||||
|
f"# Plugin Version: {self.version}",
|
||||||
|
f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
|
||||||
|
f"#",
|
||||||
|
f"# Settings:",
|
||||||
|
f"# Overwrite Existing Streams: {'Yes' if overwrite_streams else 'No'}",
|
||||||
|
f"# Fuzzy Match Threshold: {fuzzy_match_threshold}",
|
||||||
|
f"# Profile Name: {profile_name}",
|
||||||
|
f"# Channel Groups: {', '.join(selected_groups) if selected_groups else 'All groups'}",
|
||||||
|
f"# Ignore Tags: {', '.join(ignore_tags) if ignore_tags else 'None'}",
|
||||||
|
f"# Visible Channel Limit: {visible_channel_limit}",
|
||||||
|
f"#",
|
||||||
|
f"# Statistics:",
|
||||||
|
f"# Total Visible Channels: {total_visible_channels}",
|
||||||
|
f"# Total Streams Available: {total_streams}",
|
||||||
|
f"# Total Matched Streams: {total_matched_streams}",
|
||||||
|
f"#",
|
||||||
|
]
|
||||||
|
|
||||||
|
return '\n'.join(header_lines) + '\n'
|
||||||
|
|
||||||
def _sort_channels_by_priority(self, channels):
|
def _sort_channels_by_priority(self, channels):
|
||||||
"""Sort channels by quality tag priority, then by channel number."""
|
"""Sort channels by quality tag priority, then by channel number."""
|
||||||
def get_priority_key(channel):
|
def get_priority_key(channel):
|
||||||
@@ -883,14 +1113,14 @@ class Plugin:
|
|||||||
quality_index = self.CHANNEL_QUALITY_TAG_ORDER.index(quality_tag)
|
quality_index = self.CHANNEL_QUALITY_TAG_ORDER.index(quality_tag)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
quality_index = len(self.CHANNEL_QUALITY_TAG_ORDER)
|
quality_index = len(self.CHANNEL_QUALITY_TAG_ORDER)
|
||||||
|
|
||||||
# Get channel number, default to 999999 if not available
|
# Get channel number, default to 999999 if not available
|
||||||
channel_number = channel.get('channel_number', 999999)
|
channel_number = channel.get('channel_number', 999999)
|
||||||
if channel_number is None:
|
if channel_number is None:
|
||||||
channel_number = 999999
|
channel_number = 999999
|
||||||
|
|
||||||
return (quality_index, channel_number)
|
return (quality_index, channel_number)
|
||||||
|
|
||||||
return sorted(channels, key=get_priority_key)
|
return sorted(channels, key=get_priority_key)
|
||||||
|
|
||||||
def preview_changes_action(self, settings, logger):
|
def preview_changes_action(self, settings, logger):
|
||||||
@@ -1007,10 +1237,24 @@ class Plugin:
|
|||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"stream_mapparr_preview_{timestamp}.csv"
|
filename = f"stream_mapparr_preview_{timestamp}.csv"
|
||||||
filepath = os.path.join("/data/exports", filename)
|
filepath = os.path.join("/data/exports", filename)
|
||||||
|
|
||||||
os.makedirs("/data/exports", exist_ok=True)
|
os.makedirs("/data/exports", exist_ok=True)
|
||||||
|
|
||||||
|
# Calculate total matched streams
|
||||||
|
total_matched = sum(1 for m in all_matches if m['matched_streams'] > 0 and m['will_update'])
|
||||||
|
|
||||||
|
# Write CSV with header comment
|
||||||
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
||||||
|
# Write comment header
|
||||||
|
header_comment = self._generate_csv_header_comment(
|
||||||
|
settings,
|
||||||
|
processed_data,
|
||||||
|
total_visible_channels=total_channels_to_update,
|
||||||
|
total_matched_streams=total_matched
|
||||||
|
)
|
||||||
|
csvfile.write(header_comment)
|
||||||
|
|
||||||
|
# Write CSV data
|
||||||
fieldnames = [
|
fieldnames = [
|
||||||
'will_update',
|
'will_update',
|
||||||
'channel_id',
|
'channel_id',
|
||||||
@@ -1245,10 +1489,24 @@ class Plugin:
|
|||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"stream_mapparr_update_{timestamp}.csv"
|
filename = f"stream_mapparr_update_{timestamp}.csv"
|
||||||
filepath = os.path.join("/data/exports", filename)
|
filepath = os.path.join("/data/exports", filename)
|
||||||
|
|
||||||
os.makedirs("/data/exports", exist_ok=True)
|
os.makedirs("/data/exports", exist_ok=True)
|
||||||
|
|
||||||
|
# Calculate total matched streams
|
||||||
|
total_matched = sum(1 for detail in update_details if detail['matched_streams'] > 0)
|
||||||
|
|
||||||
|
# Write CSV with header comment
|
||||||
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
||||||
|
# Write comment header
|
||||||
|
header_comment = self._generate_csv_header_comment(
|
||||||
|
settings,
|
||||||
|
processed_data,
|
||||||
|
total_visible_channels=channels_updated,
|
||||||
|
total_matched_streams=total_matched
|
||||||
|
)
|
||||||
|
csvfile.write(header_comment)
|
||||||
|
|
||||||
|
# Write CSV data
|
||||||
fieldnames = ['channel_name', 'stream_names', 'matched_streams']
|
fieldnames = ['channel_name', 'stream_names', 'matched_streams']
|
||||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
@@ -1508,10 +1766,24 @@ class Plugin:
|
|||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"stream_mapparr_visibility_{timestamp}.csv"
|
filename = f"stream_mapparr_visibility_{timestamp}.csv"
|
||||||
filepath = os.path.join("/data/exports", filename)
|
filepath = os.path.join("/data/exports", filename)
|
||||||
|
|
||||||
os.makedirs("/data/exports", exist_ok=True)
|
os.makedirs("/data/exports", exist_ok=True)
|
||||||
|
|
||||||
|
# Calculate total matched streams (channels with at least 1 stream that are enabled)
|
||||||
|
total_matched = sum(1 for ch_id in channels_to_enable if channel_stream_counts.get(ch_id, {}).get('stream_count', 0) > 0)
|
||||||
|
|
||||||
|
# Write CSV with header comment
|
||||||
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
||||||
|
# Write comment header
|
||||||
|
header_comment = self._generate_csv_header_comment(
|
||||||
|
settings,
|
||||||
|
processed_data,
|
||||||
|
total_visible_channels=channels_enabled,
|
||||||
|
total_matched_streams=total_matched
|
||||||
|
)
|
||||||
|
csvfile.write(header_comment)
|
||||||
|
|
||||||
|
# Write CSV data
|
||||||
fieldnames = [
|
fieldnames = [
|
||||||
'channel_id',
|
'channel_id',
|
||||||
'channel_name',
|
'channel_name',
|
||||||
|
|||||||
Reference in New Issue
Block a user