forked from Mirrors/Dispatcharr
Convert custom_properties to jsonb in the backend.
This commit is contained in:
parent
a87f7c875d
commit
6f6c28ca7c
21 changed files with 395 additions and 379 deletions
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.2.4 on 2025-09-02 14:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0002_remove_user_channel_groups_user_channel_profiles_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='custom_properties',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
]
|
||||
13
apps/accounts/migrations/0004_auto_20250902_0931.py
Normal file
13
apps/accounts/migrations/0004_auto_20250902_0931.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Generated by Django 5.2.4 on 2025-09-02 14:31
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0003_alter_user_custom_properties'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
|
|
@ -21,7 +21,7 @@ class User(AbstractUser):
|
|||
related_name="users",
|
||||
)
|
||||
user_level = models.IntegerField(default=UserLevel.STREAMER)
|
||||
custom_properties = models.TextField(null=True, blank=True)
|
||||
custom_properties = models.JSONField(default=dict, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
|
|
|||
|
|
@ -537,9 +537,7 @@ class ChannelViewSet(viewsets.ModelViewSet):
|
|||
name = stream.name
|
||||
|
||||
# Check if client provided a channel_number; if not, auto-assign one.
|
||||
stream_custom_props = (
|
||||
json.loads(stream.custom_properties) if stream.custom_properties else {}
|
||||
)
|
||||
stream_custom_props = stream.custom_properties or {}
|
||||
|
||||
channel_number = None
|
||||
if "tvg-chno" in stream_custom_props:
|
||||
|
|
@ -734,9 +732,7 @@ class ChannelViewSet(viewsets.ModelViewSet):
|
|||
|
||||
channel_group = stream.channel_group
|
||||
|
||||
stream_custom_props = (
|
||||
json.loads(stream.custom_properties) if stream.custom_properties else {}
|
||||
)
|
||||
stream_custom_props = stream.custom_properties or {}
|
||||
|
||||
channel_number = None
|
||||
if "tvg-chno" in stream_custom_props:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 5.2.4 on 2025-09-02 14:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dispatcharr_channels', '0024_alter_channelgroupm3uaccount_channel_group'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='channelgroupm3uaccount',
|
||||
name='custom_properties',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recording',
|
||||
name='custom_properties',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='stream',
|
||||
name='custom_properties',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -94,7 +94,7 @@ class Stream(models.Model):
|
|||
db_index=True,
|
||||
)
|
||||
last_seen = models.DateTimeField(db_index=True, default=datetime.now)
|
||||
custom_properties = models.TextField(null=True, blank=True)
|
||||
custom_properties = models.JSONField(default=dict, blank=True, null=True)
|
||||
|
||||
# Stream statistics fields
|
||||
stream_stats = models.JSONField(
|
||||
|
|
@ -565,7 +565,7 @@ class ChannelGroupM3UAccount(models.Model):
|
|||
m3u_account = models.ForeignKey(
|
||||
M3UAccount, on_delete=models.CASCADE, related_name="channel_group"
|
||||
)
|
||||
custom_properties = models.TextField(null=True, blank=True)
|
||||
custom_properties = models.JSONField(default=dict, blank=True, null=True)
|
||||
enabled = models.BooleanField(default=True)
|
||||
auto_channel_sync = models.BooleanField(
|
||||
default=False,
|
||||
|
|
@ -599,7 +599,7 @@ class Recording(models.Model):
|
|||
start_time = models.DateTimeField()
|
||||
end_time = models.DateTimeField()
|
||||
task_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
custom_properties = models.TextField(null=True, blank=True)
|
||||
custom_properties = models.JSONField(default=dict, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.channel.name} - {self.start_time} to {self.end_time}"
|
||||
|
|
|
|||
|
|
@ -211,17 +211,12 @@ class ChannelGroupM3UAccountSerializer(serializers.ModelSerializer):
|
|||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
|
||||
custom_props = {}
|
||||
if instance.custom_properties:
|
||||
try:
|
||||
custom_props = json.loads(instance.custom_properties)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
custom_props = {}
|
||||
custom_props = instance.custom_properties or {}
|
||||
|
||||
return data
|
||||
|
||||
def to_internal_value(self, data):
|
||||
# Accept both dict and JSON string for custom_properties
|
||||
# Accept both dict and JSON string for custom_properties (for backward compatibility)
|
||||
val = data.get("custom_properties")
|
||||
if isinstance(val, str):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.2.4 on 2025-09-02 14:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('epg', '0014_epgsource_extracted_file_path'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='programdata',
|
||||
name='custom_properties',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -150,7 +150,7 @@ class ProgramData(models.Model):
|
|||
sub_title = models.CharField(max_length=255, blank=True, null=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
tvg_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
custom_properties = models.TextField(null=True, blank=True)
|
||||
custom_properties = models.JSONField(default=dict, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} ({self.start_time} - {self.end_time})"
|
||||
|
|
|
|||
|
|
@ -1234,10 +1234,7 @@ def parse_programs_for_tvg_id(epg_id):
|
|||
|
||||
if custom_props:
|
||||
logger.trace(f"Number of custom properties: {len(custom_props)}")
|
||||
try:
|
||||
custom_properties_json = json.dumps(custom_props)
|
||||
except Exception as e:
|
||||
logger.error(f"Error serializing custom properties to JSON: {e}", exc_info=True)
|
||||
custom_properties_json = custom_props
|
||||
|
||||
programs_to_create.append(ProgramData(
|
||||
epg=epg,
|
||||
|
|
|
|||
|
|
@ -43,11 +43,8 @@ class M3UAccountAdmin(admin.ModelAdmin):
|
|||
def vod_enabled_display(self, obj):
|
||||
"""Display whether VOD is enabled for this account"""
|
||||
if obj.custom_properties:
|
||||
try:
|
||||
custom_props = json.loads(obj.custom_properties)
|
||||
return "Yes" if custom_props.get('enable_vod', False) else "No"
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
custom_props = obj.custom_properties or {}
|
||||
return "Yes" if custom_props.get('enable_vod', False) else "No"
|
||||
return "No"
|
||||
vod_enabled_display.short_description = "VOD Enabled"
|
||||
vod_enabled_display.boolean = True
|
||||
|
|
|
|||
|
|
@ -100,11 +100,8 @@ class M3UAccountViewSet(viewsets.ModelViewSet):
|
|||
|
||||
# Check current VOD setting
|
||||
if instance.custom_properties:
|
||||
try:
|
||||
custom_props = json.loads(instance.custom_properties)
|
||||
old_vod_enabled = custom_props.get("enable_vod", False)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
custom_props = instance.custom_properties or {}
|
||||
old_vod_enabled = custom_props.get("enable_vod", False)
|
||||
|
||||
# Handle file upload first, if any
|
||||
file_path = None
|
||||
|
|
@ -187,11 +184,8 @@ class M3UAccountViewSet(viewsets.ModelViewSet):
|
|||
# Check if VOD is enabled
|
||||
vod_enabled = False
|
||||
if account.custom_properties:
|
||||
try:
|
||||
custom_props = json.loads(account.custom_properties)
|
||||
vod_enabled = custom_props.get("enable_vod", False)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
custom_props = account.custom_properties or {}
|
||||
vod_enabled = custom_props.get("enable_vod", False)
|
||||
|
||||
if not vod_enabled:
|
||||
return Response(
|
||||
|
|
@ -236,11 +230,7 @@ class M3UAccountViewSet(viewsets.ModelViewSet):
|
|||
"enabled": enabled,
|
||||
"auto_channel_sync": auto_sync,
|
||||
"auto_sync_channel_start": sync_start,
|
||||
"custom_properties": (
|
||||
custom_properties
|
||||
if isinstance(custom_properties, str)
|
||||
else json.dumps(custom_properties)
|
||||
),
|
||||
"custom_properties": custom_properties,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -255,11 +245,7 @@ class M3UAccountViewSet(viewsets.ModelViewSet):
|
|||
m3u_account=account,
|
||||
defaults={
|
||||
"enabled": enabled,
|
||||
"custom_properties": (
|
||||
custom_properties
|
||||
if isinstance(custom_properties, str)
|
||||
else json.dumps(custom_properties)
|
||||
),
|
||||
"custom_properties": custom_properties,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,8 @@ class M3UAccountForm(forms.ModelForm):
|
|||
|
||||
# Set initial value for enable_vod from custom_properties
|
||||
if self.instance and self.instance.custom_properties:
|
||||
try:
|
||||
import json
|
||||
custom_props = json.loads(self.instance.custom_properties)
|
||||
self.fields['enable_vod'].initial = custom_props.get('enable_vod', False)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
custom_props = self.instance.custom_properties or {}
|
||||
self.fields['enable_vod'].initial = custom_props.get('enable_vod', False)
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
|
|
@ -42,17 +38,11 @@ class M3UAccountForm(forms.ModelForm):
|
|||
enable_vod = self.cleaned_data.get('enable_vod', False)
|
||||
|
||||
# Parse existing custom_properties
|
||||
custom_props = {}
|
||||
if instance.custom_properties:
|
||||
try:
|
||||
import json
|
||||
custom_props = json.loads(instance.custom_properties)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
custom_props = {}
|
||||
custom_props = instance.custom_properties or {}
|
||||
|
||||
# Update VOD preference
|
||||
custom_props['enable_vod'] = enable_vod
|
||||
instance.custom_properties = json.dumps(custom_props)
|
||||
instance.custom_properties = custom_props
|
||||
|
||||
if commit:
|
||||
instance.save()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.2.4 on 2025-09-02 14:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('m3u', '0016_m3uaccount_priority'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='m3uaccount',
|
||||
name='custom_properties',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='m3ufilter',
|
||||
name='custom_properties',
|
||||
field=models.JSONField(blank=True, default=dict, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -85,7 +85,7 @@ class M3UAccount(models.Model):
|
|||
account_type = models.CharField(choices=Types.choices, default=Types.STADNARD)
|
||||
username = models.CharField(max_length=255, null=True, blank=True)
|
||||
password = models.CharField(max_length=255, null=True, blank=True)
|
||||
custom_properties = models.TextField(null=True, blank=True)
|
||||
custom_properties = models.JSONField(default=dict, blank=True, null=True)
|
||||
refresh_interval = models.IntegerField(default=0)
|
||||
refresh_task = models.ForeignKey(
|
||||
PeriodicTask, on_delete=models.SET_NULL, null=True, blank=True
|
||||
|
|
@ -184,7 +184,7 @@ class M3UFilter(models.Model):
|
|||
help_text="If True, matching items are excluded; if False, only matches are included.",
|
||||
)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
custom_properties = models.TextField(null=True, blank=True)
|
||||
custom_properties = models.JSONField(default=dict, blank=True, null=True)
|
||||
|
||||
def applies_to(self, stream_name, group_name):
|
||||
target = group_name if self.filter_type == "group" else stream_name
|
||||
|
|
|
|||
|
|
@ -129,12 +129,7 @@ class M3UAccountSerializer(serializers.ModelSerializer):
|
|||
data = super().to_representation(instance)
|
||||
|
||||
# Parse custom_properties to get VOD preference
|
||||
custom_props = {}
|
||||
if instance.custom_properties:
|
||||
try:
|
||||
custom_props = json.loads(instance.custom_properties)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
custom_props = {}
|
||||
custom_props = instance.custom_properties or {}
|
||||
|
||||
data["enable_vod"] = custom_props.get("enable_vod", False)
|
||||
return data
|
||||
|
|
@ -144,17 +139,12 @@ class M3UAccountSerializer(serializers.ModelSerializer):
|
|||
enable_vod = validated_data.pop("enable_vod", None)
|
||||
|
||||
if enable_vod is not None:
|
||||
# Parse existing custom_properties
|
||||
custom_props = {}
|
||||
if instance.custom_properties:
|
||||
try:
|
||||
custom_props = json.loads(instance.custom_properties)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
custom_props = {}
|
||||
# Get existing custom_properties
|
||||
custom_props = instance.custom_properties or {}
|
||||
|
||||
# Update VOD preference
|
||||
custom_props["enable_vod"] = enable_vod
|
||||
validated_data["custom_properties"] = json.dumps(custom_props)
|
||||
validated_data["custom_properties"] = custom_props
|
||||
|
||||
# Pop out channel group memberships so we can handle them manually
|
||||
channel_group_data = validated_data.pop("channel_group", [])
|
||||
|
|
@ -192,16 +182,11 @@ class M3UAccountSerializer(serializers.ModelSerializer):
|
|||
enable_vod = validated_data.pop("enable_vod", False)
|
||||
|
||||
# Parse existing custom_properties or create new
|
||||
custom_props = {}
|
||||
if validated_data.get("custom_properties"):
|
||||
try:
|
||||
custom_props = json.loads(validated_data["custom_properties"])
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
custom_props = {}
|
||||
custom_props = validated_data.get("custom_properties", {})
|
||||
|
||||
# Set VOD preference
|
||||
custom_props["enable_vod"] = enable_vod
|
||||
validated_data["custom_properties"] = json.dumps(custom_props)
|
||||
validated_data["custom_properties"] = custom_props
|
||||
|
||||
return super().create(validated_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -538,21 +538,13 @@ def process_groups(account, groups):
|
|||
|
||||
for group in group_objs:
|
||||
custom_props = groups.get(group.name, {})
|
||||
custom_props_json = json.dumps(custom_props)
|
||||
|
||||
if group.name in existing_relationships:
|
||||
# Update existing relationship if xc_id has changed (preserve other custom properties)
|
||||
existing_rel = existing_relationships[group.name]
|
||||
|
||||
# Parse existing custom properties
|
||||
try:
|
||||
existing_custom_props = (
|
||||
json.loads(existing_rel.custom_properties)
|
||||
if existing_rel.custom_properties
|
||||
else {}
|
||||
)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
existing_custom_props = {}
|
||||
# Get existing custom properties (now JSONB, no need to parse)
|
||||
existing_custom_props = existing_rel.custom_properties or {}
|
||||
|
||||
# Get the new xc_id from groups data
|
||||
new_xc_id = custom_props.get("xc_id")
|
||||
|
|
@ -568,16 +560,40 @@ def process_groups(account, groups):
|
|||
# Remove xc_id if it's no longer provided (e.g., converting from XC to standard)
|
||||
del updated_custom_props["xc_id"]
|
||||
|
||||
existing_rel.custom_properties = json.dumps(updated_custom_props)
|
||||
existing_rel.custom_properties = updated_custom_props
|
||||
relations_to_update.append(existing_rel)
|
||||
logger.debug(f"Updated custom properties for group '{group.name}' - account {account.id}")
|
||||
logger.debug(f"Updated xc_id for group '{group.name}' from '{existing_xc_id}' to '{new_xc_id}' - account {account.id}")
|
||||
else:
|
||||
logger.debug(f"xc_id unchanged for group '{group.name}' - account {account.id}")
|
||||
else:
|
||||
# Create new relationship - but check if there's an existing relationship that might have user settings
|
||||
# This can happen if the group was temporarily removed and is now back
|
||||
try:
|
||||
potential_existing = ChannelGroupM3UAccount.objects.filter(
|
||||
m3u_account=account,
|
||||
channel_group=group
|
||||
).first()
|
||||
|
||||
if potential_existing:
|
||||
# Merge with existing custom properties to preserve user settings
|
||||
existing_custom_props = potential_existing.custom_properties or {}
|
||||
|
||||
# Merge new properties with existing ones
|
||||
merged_custom_props = existing_custom_props.copy()
|
||||
merged_custom_props.update(custom_props)
|
||||
custom_props = merged_custom_props
|
||||
logger.debug(f"Merged custom properties for existing relationship: group '{group.name}' - account {account.id}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not check for existing relationship: {str(e)}")
|
||||
# Fall back to using just the new custom properties
|
||||
pass
|
||||
|
||||
# Create new relationship
|
||||
relations_to_create.append(
|
||||
ChannelGroupM3UAccount(
|
||||
channel_group=group,
|
||||
m3u_account=account,
|
||||
custom_properties=custom_props_json,
|
||||
custom_properties=custom_props,
|
||||
enabled=True, # Default to enabled
|
||||
)
|
||||
)
|
||||
|
|
@ -768,7 +784,7 @@ def process_xc_category_direct(account_id, batch, groups, hash_keys):
|
|||
"m3u_account": account,
|
||||
"channel_group_id": int(group_id),
|
||||
"stream_hash": stream_hash,
|
||||
"custom_properties": json.dumps(stream),
|
||||
"custom_properties": stream,
|
||||
}
|
||||
|
||||
if stream_hash not in stream_hashes:
|
||||
|
|
@ -870,7 +886,7 @@ def process_m3u_batch_direct(account_id, batch, groups, hash_keys):
|
|||
f.regex_pattern,
|
||||
(
|
||||
re.IGNORECASE
|
||||
if json.loads(f.custom_properties or "{}").get(
|
||||
if (f.custom_properties or {}).get(
|
||||
"case_sensitive", True
|
||||
)
|
||||
== False
|
||||
|
|
@ -933,7 +949,7 @@ def process_m3u_batch_direct(account_id, batch, groups, hash_keys):
|
|||
"m3u_account": account,
|
||||
"channel_group_id": int(groups.get(group_title)),
|
||||
"stream_hash": stream_hash,
|
||||
"custom_properties": json.dumps(stream_info["attributes"]),
|
||||
"custom_properties": stream_info["attributes"],
|
||||
}
|
||||
|
||||
if stream_hash not in stream_hashes:
|
||||
|
|
@ -1506,31 +1522,20 @@ def sync_auto_channels(account_id, scan_start_time=None):
|
|||
channel_sort_reverse = False
|
||||
stream_profile_id = None
|
||||
if group_relation.custom_properties:
|
||||
try:
|
||||
group_custom_props = json.loads(group_relation.custom_properties)
|
||||
force_dummy_epg = group_custom_props.get("force_dummy_epg", False)
|
||||
override_group_id = group_custom_props.get("group_override")
|
||||
name_regex_pattern = group_custom_props.get("name_regex_pattern")
|
||||
name_replace_pattern = group_custom_props.get(
|
||||
"name_replace_pattern"
|
||||
)
|
||||
name_match_regex = group_custom_props.get("name_match_regex")
|
||||
channel_profile_ids = group_custom_props.get("channel_profile_ids")
|
||||
channel_sort_order = group_custom_props.get("channel_sort_order")
|
||||
channel_sort_reverse = group_custom_props.get(
|
||||
"channel_sort_reverse", False
|
||||
)
|
||||
stream_profile_id = group_custom_props.get("stream_profile_id")
|
||||
except Exception:
|
||||
force_dummy_epg = False
|
||||
override_group_id = None
|
||||
name_regex_pattern = None
|
||||
name_replace_pattern = None
|
||||
name_match_regex = None
|
||||
channel_profile_ids = None
|
||||
channel_sort_order = None
|
||||
channel_sort_reverse = False
|
||||
stream_profile_id = None
|
||||
group_custom_props = group_relation.custom_properties
|
||||
force_dummy_epg = group_custom_props.get("force_dummy_epg", False)
|
||||
override_group_id = group_custom_props.get("group_override")
|
||||
name_regex_pattern = group_custom_props.get("name_regex_pattern")
|
||||
name_replace_pattern = group_custom_props.get(
|
||||
"name_replace_pattern"
|
||||
)
|
||||
name_match_regex = group_custom_props.get("name_match_regex")
|
||||
channel_profile_ids = group_custom_props.get("channel_profile_ids")
|
||||
channel_sort_order = group_custom_props.get("channel_sort_order")
|
||||
channel_sort_reverse = group_custom_props.get(
|
||||
"channel_sort_reverse", False
|
||||
)
|
||||
stream_profile_id = group_custom_props.get("stream_profile_id")
|
||||
|
||||
# Determine which group to use for created channels
|
||||
target_group = channel_group
|
||||
|
|
@ -1730,11 +1735,7 @@ def sync_auto_channels(account_id, scan_start_time=None):
|
|||
processed_stream_ids.add(stream.id)
|
||||
try:
|
||||
# Parse custom properties for additional info
|
||||
stream_custom_props = (
|
||||
json.loads(stream.custom_properties)
|
||||
if stream.custom_properties
|
||||
else {}
|
||||
)
|
||||
stream_custom_props = stream.custom_properties or {}
|
||||
tvc_guide_stationid = stream_custom_props.get("tvc-guide-stationid")
|
||||
|
||||
# --- REGEX FIND/REPLACE LOGIC ---
|
||||
|
|
@ -2018,11 +2019,8 @@ def refresh_single_m3u_account(account_id):
|
|||
# Check if VOD is enabled for this account
|
||||
vod_enabled = False
|
||||
if account.custom_properties:
|
||||
try:
|
||||
custom_props = json.loads(account.custom_properties)
|
||||
vod_enabled = custom_props.get('enable_vod', False)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
vod_enabled = False
|
||||
custom_props = account.custom_properties or {}
|
||||
vod_enabled = custom_props.get('enable_vod', False)
|
||||
|
||||
except M3UAccount.DoesNotExist:
|
||||
# The M3U account doesn't exist, so delete the periodic task if it exists
|
||||
|
|
@ -2257,27 +2255,18 @@ def refresh_single_m3u_account(account_id):
|
|||
group_id = rel.channel_group.id
|
||||
|
||||
# Load the custom properties with the xc_id
|
||||
try:
|
||||
custom_props = (
|
||||
json.loads(rel.custom_properties)
|
||||
if rel.custom_properties
|
||||
else {}
|
||||
custom_props = rel.custom_properties or {}
|
||||
if "xc_id" in custom_props:
|
||||
filtered_groups[group_name] = {
|
||||
"xc_id": custom_props["xc_id"],
|
||||
"channel_group_id": group_id,
|
||||
}
|
||||
logger.debug(
|
||||
f"Added group {group_name} with xc_id {custom_props['xc_id']}"
|
||||
)
|
||||
if "xc_id" in custom_props:
|
||||
filtered_groups[group_name] = {
|
||||
"xc_id": custom_props["xc_id"],
|
||||
"channel_group_id": group_id,
|
||||
}
|
||||
logger.debug(
|
||||
f"Added group {group_name} with xc_id {custom_props['xc_id']}"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"No xc_id found in custom properties for group {group_name}"
|
||||
)
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
logger.error(
|
||||
f"Error parsing custom properties for group {group_name}: {str(e)}"
|
||||
else:
|
||||
logger.warning(
|
||||
f"No xc_id found in custom properties for group {group_name}"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
|
|
|||
|
|
@ -463,206 +463,202 @@ def generate_epg(request, profile_name=None, user=None):
|
|||
|
||||
# Process custom properties if available
|
||||
if prog.custom_properties:
|
||||
try:
|
||||
custom_data = json.loads(prog.custom_properties)
|
||||
custom_data = prog.custom_properties or {}
|
||||
|
||||
# Add categories if available
|
||||
if "categories" in custom_data and custom_data["categories"]:
|
||||
for category in custom_data["categories"]:
|
||||
program_xml.append(f" <category>{html.escape(category)}</category>")
|
||||
# Add categories if available
|
||||
if "categories" in custom_data and custom_data["categories"]:
|
||||
for category in custom_data["categories"]:
|
||||
program_xml.append(f" <category>{html.escape(category)}</category>")
|
||||
|
||||
# Add keywords if available
|
||||
if "keywords" in custom_data and custom_data["keywords"]:
|
||||
for keyword in custom_data["keywords"]:
|
||||
program_xml.append(f" <keyword>{html.escape(keyword)}</keyword>")
|
||||
# Add keywords if available
|
||||
if "keywords" in custom_data and custom_data["keywords"]:
|
||||
for keyword in custom_data["keywords"]:
|
||||
program_xml.append(f" <keyword>{html.escape(keyword)}</keyword>")
|
||||
|
||||
# Handle episode numbering - multiple formats supported
|
||||
# Prioritize onscreen_episode over standalone episode for onscreen system
|
||||
if "onscreen_episode" in custom_data:
|
||||
program_xml.append(f' <episode-num system="onscreen">{html.escape(custom_data["onscreen_episode"])}</episode-num>')
|
||||
elif "episode" in custom_data:
|
||||
program_xml.append(f' <episode-num system="onscreen">E{custom_data["episode"]}</episode-num>')
|
||||
# Handle episode numbering - multiple formats supported
|
||||
# Prioritize onscreen_episode over standalone episode for onscreen system
|
||||
if "onscreen_episode" in custom_data:
|
||||
program_xml.append(f' <episode-num system="onscreen">{html.escape(custom_data["onscreen_episode"])}</episode-num>')
|
||||
elif "episode" in custom_data:
|
||||
program_xml.append(f' <episode-num system="onscreen">E{custom_data["episode"]}</episode-num>')
|
||||
|
||||
# Handle dd_progid format
|
||||
if 'dd_progid' in custom_data:
|
||||
program_xml.append(f' <episode-num system="dd_progid">{html.escape(custom_data["dd_progid"])}</episode-num>')
|
||||
# Handle dd_progid format
|
||||
if 'dd_progid' in custom_data:
|
||||
program_xml.append(f' <episode-num system="dd_progid">{html.escape(custom_data["dd_progid"])}</episode-num>')
|
||||
|
||||
# Handle external database IDs
|
||||
for system in ['thetvdb.com', 'themoviedb.org', 'imdb.com']:
|
||||
if f'{system}_id' in custom_data:
|
||||
program_xml.append(f' <episode-num system="{system}">{html.escape(custom_data[f"{system}_id"])}</episode-num>')
|
||||
# Handle external database IDs
|
||||
for system in ['thetvdb.com', 'themoviedb.org', 'imdb.com']:
|
||||
if f'{system}_id' in custom_data:
|
||||
program_xml.append(f' <episode-num system="{system}">{html.escape(custom_data[f"{system}_id"])}</episode-num>')
|
||||
|
||||
# Add season and episode numbers in xmltv_ns format if available
|
||||
if "season" in custom_data and "episode" in custom_data:
|
||||
season = (
|
||||
int(custom_data["season"]) - 1
|
||||
if str(custom_data["season"]).isdigit()
|
||||
else 0
|
||||
)
|
||||
episode = (
|
||||
int(custom_data["episode"]) - 1
|
||||
if str(custom_data["episode"]).isdigit()
|
||||
else 0
|
||||
)
|
||||
program_xml.append(f' <episode-num system="xmltv_ns">{season}.{episode}.</episode-num>')
|
||||
# Add season and episode numbers in xmltv_ns format if available
|
||||
if "season" in custom_data and "episode" in custom_data:
|
||||
season = (
|
||||
int(custom_data["season"]) - 1
|
||||
if str(custom_data["season"]).isdigit()
|
||||
else 0
|
||||
)
|
||||
episode = (
|
||||
int(custom_data["episode"]) - 1
|
||||
if str(custom_data["episode"]).isdigit()
|
||||
else 0
|
||||
)
|
||||
program_xml.append(f' <episode-num system="xmltv_ns">{season}.{episode}.</episode-num>')
|
||||
|
||||
# Add language information
|
||||
if "language" in custom_data:
|
||||
program_xml.append(f' <language>{html.escape(custom_data["language"])}</language>')
|
||||
# Add language information
|
||||
if "language" in custom_data:
|
||||
program_xml.append(f' <language>{html.escape(custom_data["language"])}</language>')
|
||||
|
||||
if "original_language" in custom_data:
|
||||
program_xml.append(f' <orig-language>{html.escape(custom_data["original_language"])}</orig-language>')
|
||||
if "original_language" in custom_data:
|
||||
program_xml.append(f' <orig-language>{html.escape(custom_data["original_language"])}</orig-language>')
|
||||
|
||||
# Add length information
|
||||
if "length" in custom_data and isinstance(custom_data["length"], dict):
|
||||
length_value = custom_data["length"].get("value", "")
|
||||
length_units = custom_data["length"].get("units", "minutes")
|
||||
program_xml.append(f' <length units="{html.escape(length_units)}">{html.escape(str(length_value))}</length>')
|
||||
# Add length information
|
||||
if "length" in custom_data and isinstance(custom_data["length"], dict):
|
||||
length_value = custom_data["length"].get("value", "")
|
||||
length_units = custom_data["length"].get("units", "minutes")
|
||||
program_xml.append(f' <length units="{html.escape(length_units)}">{html.escape(str(length_value))}</length>')
|
||||
|
||||
# Add video information
|
||||
if "video" in custom_data and isinstance(custom_data["video"], dict):
|
||||
program_xml.append(" <video>")
|
||||
for attr in ['present', 'colour', 'aspect', 'quality']:
|
||||
if attr in custom_data["video"]:
|
||||
program_xml.append(f" <{attr}>{html.escape(custom_data['video'][attr])}</{attr}>")
|
||||
program_xml.append(" </video>")
|
||||
# Add video information
|
||||
if "video" in custom_data and isinstance(custom_data["video"], dict):
|
||||
program_xml.append(" <video>")
|
||||
for attr in ['present', 'colour', 'aspect', 'quality']:
|
||||
if attr in custom_data["video"]:
|
||||
program_xml.append(f" <{attr}>{html.escape(custom_data['video'][attr])}</{attr}>")
|
||||
program_xml.append(" </video>")
|
||||
|
||||
# Add audio information
|
||||
if "audio" in custom_data and isinstance(custom_data["audio"], dict):
|
||||
program_xml.append(" <audio>")
|
||||
for attr in ['present', 'stereo']:
|
||||
if attr in custom_data["audio"]:
|
||||
program_xml.append(f" <{attr}>{html.escape(custom_data['audio'][attr])}</{attr}>")
|
||||
program_xml.append(" </audio>")
|
||||
# Add audio information
|
||||
if "audio" in custom_data and isinstance(custom_data["audio"], dict):
|
||||
program_xml.append(" <audio>")
|
||||
for attr in ['present', 'stereo']:
|
||||
if attr in custom_data["audio"]:
|
||||
program_xml.append(f" <{attr}>{html.escape(custom_data['audio'][attr])}</{attr}>")
|
||||
program_xml.append(" </audio>")
|
||||
|
||||
# Add subtitles information
|
||||
if "subtitles" in custom_data and isinstance(custom_data["subtitles"], list):
|
||||
for subtitle in custom_data["subtitles"]:
|
||||
if isinstance(subtitle, dict):
|
||||
subtitle_type = subtitle.get("type", "")
|
||||
type_attr = f' type="{html.escape(subtitle_type)}"' if subtitle_type else ""
|
||||
program_xml.append(f" <subtitles{type_attr}>")
|
||||
if "language" in subtitle:
|
||||
program_xml.append(f" <language>{html.escape(subtitle['language'])}</language>")
|
||||
program_xml.append(" </subtitles>")
|
||||
# Add subtitles information
|
||||
if "subtitles" in custom_data and isinstance(custom_data["subtitles"], list):
|
||||
for subtitle in custom_data["subtitles"]:
|
||||
if isinstance(subtitle, dict):
|
||||
subtitle_type = subtitle.get("type", "")
|
||||
type_attr = f' type="{html.escape(subtitle_type)}"' if subtitle_type else ""
|
||||
program_xml.append(f" <subtitles{type_attr}>")
|
||||
if "language" in subtitle:
|
||||
program_xml.append(f" <language>{html.escape(subtitle['language'])}</language>")
|
||||
program_xml.append(" </subtitles>")
|
||||
|
||||
# Add rating if available
|
||||
if "rating" in custom_data:
|
||||
rating_system = custom_data.get("rating_system", "TV Parental Guidelines")
|
||||
program_xml.append(f' <rating system="{html.escape(rating_system)}">')
|
||||
program_xml.append(f' <value>{html.escape(custom_data["rating"])}</value>')
|
||||
program_xml.append(f" </rating>")
|
||||
# Add rating if available
|
||||
if "rating" in custom_data:
|
||||
rating_system = custom_data.get("rating_system", "TV Parental Guidelines")
|
||||
program_xml.append(f' <rating system="{html.escape(rating_system)}">')
|
||||
program_xml.append(f' <value>{html.escape(custom_data["rating"])}</value>')
|
||||
program_xml.append(f" </rating>")
|
||||
|
||||
# Add star ratings
|
||||
if "star_ratings" in custom_data and isinstance(custom_data["star_ratings"], list):
|
||||
for star_rating in custom_data["star_ratings"]:
|
||||
if isinstance(star_rating, dict) and "value" in star_rating:
|
||||
system_attr = f' system="{html.escape(star_rating["system"])}"' if "system" in star_rating else ""
|
||||
program_xml.append(f" <star-rating{system_attr}>")
|
||||
program_xml.append(f" <value>{html.escape(star_rating['value'])}</value>")
|
||||
program_xml.append(" </star-rating>")
|
||||
# Add star ratings
|
||||
if "star_ratings" in custom_data and isinstance(custom_data["star_ratings"], list):
|
||||
for star_rating in custom_data["star_ratings"]:
|
||||
if isinstance(star_rating, dict) and "value" in star_rating:
|
||||
system_attr = f' system="{html.escape(star_rating["system"])}"' if "system" in star_rating else ""
|
||||
program_xml.append(f" <star-rating{system_attr}>")
|
||||
program_xml.append(f" <value>{html.escape(star_rating['value'])}</value>")
|
||||
program_xml.append(" </star-rating>")
|
||||
|
||||
# Add reviews
|
||||
if "reviews" in custom_data and isinstance(custom_data["reviews"], list):
|
||||
for review in custom_data["reviews"]:
|
||||
if isinstance(review, dict) and "content" in review:
|
||||
review_type = review.get("type", "text")
|
||||
attrs = [f'type="{html.escape(review_type)}"']
|
||||
if "source" in review:
|
||||
attrs.append(f'source="{html.escape(review["source"])}"')
|
||||
if "reviewer" in review:
|
||||
attrs.append(f'reviewer="{html.escape(review["reviewer"])}"')
|
||||
attr_str = " ".join(attrs)
|
||||
program_xml.append(f' <review {attr_str}>{html.escape(review["content"])}</review>')
|
||||
# Add reviews
|
||||
if "reviews" in custom_data and isinstance(custom_data["reviews"], list):
|
||||
for review in custom_data["reviews"]:
|
||||
if isinstance(review, dict) and "content" in review:
|
||||
review_type = review.get("type", "text")
|
||||
attrs = [f'type="{html.escape(review_type)}"']
|
||||
if "source" in review:
|
||||
attrs.append(f'source="{html.escape(review["source"])}"')
|
||||
if "reviewer" in review:
|
||||
attrs.append(f'reviewer="{html.escape(review["reviewer"])}"')
|
||||
attr_str = " ".join(attrs)
|
||||
program_xml.append(f' <review {attr_str}>{html.escape(review["content"])}</review>')
|
||||
|
||||
# Add images
|
||||
if "images" in custom_data and isinstance(custom_data["images"], list):
|
||||
for image in custom_data["images"]:
|
||||
if isinstance(image, dict) and "url" in image:
|
||||
attrs = []
|
||||
for attr in ['type', 'size', 'orient', 'system']:
|
||||
if attr in image:
|
||||
attrs.append(f'{attr}="{html.escape(image[attr])}"')
|
||||
attr_str = " " + " ".join(attrs) if attrs else ""
|
||||
program_xml.append(f' <image{attr_str}>{html.escape(image["url"])}</image>')
|
||||
# Add images
|
||||
if "images" in custom_data and isinstance(custom_data["images"], list):
|
||||
for image in custom_data["images"]:
|
||||
if isinstance(image, dict) and "url" in image:
|
||||
attrs = []
|
||||
for attr in ['type', 'size', 'orient', 'system']:
|
||||
if attr in image:
|
||||
attrs.append(f'{attr}="{html.escape(image[attr])}"')
|
||||
attr_str = " " + " ".join(attrs) if attrs else ""
|
||||
program_xml.append(f' <image{attr_str}>{html.escape(image["url"])}</image>')
|
||||
|
||||
# Add enhanced credits handling
|
||||
if "credits" in custom_data:
|
||||
program_xml.append(" <credits>")
|
||||
credits = custom_data["credits"]
|
||||
# Add enhanced credits handling
|
||||
if "credits" in custom_data:
|
||||
program_xml.append(" <credits>")
|
||||
credits = custom_data["credits"]
|
||||
|
||||
# Handle different credit types
|
||||
for role in ['director', 'writer', 'adapter', 'producer', 'composer', 'editor', 'presenter', 'commentator', 'guest']:
|
||||
if role in credits:
|
||||
people = credits[role]
|
||||
if isinstance(people, list):
|
||||
for person in people:
|
||||
program_xml.append(f" <{role}>{html.escape(person)}</{role}>")
|
||||
else:
|
||||
program_xml.append(f" <{role}>{html.escape(people)}</{role}>")
|
||||
|
||||
# Handle actors separately to include role and guest attributes
|
||||
if "actor" in credits:
|
||||
actors = credits["actor"]
|
||||
if isinstance(actors, list):
|
||||
for actor in actors:
|
||||
if isinstance(actor, dict):
|
||||
name = actor.get("name", "")
|
||||
role_attr = f' role="{html.escape(actor["role"])}"' if "role" in actor else ""
|
||||
guest_attr = ' guest="yes"' if actor.get("guest") else ""
|
||||
program_xml.append(f" <actor{role_attr}{guest_attr}>{html.escape(name)}</actor>")
|
||||
else:
|
||||
program_xml.append(f" <actor>{html.escape(actor)}</actor>")
|
||||
# Handle different credit types
|
||||
for role in ['director', 'writer', 'adapter', 'producer', 'composer', 'editor', 'presenter', 'commentator', 'guest']:
|
||||
if role in credits:
|
||||
people = credits[role]
|
||||
if isinstance(people, list):
|
||||
for person in people:
|
||||
program_xml.append(f" <{role}>{html.escape(person)}</{role}>")
|
||||
else:
|
||||
program_xml.append(f" <actor>{html.escape(actors)}</actor>")
|
||||
program_xml.append(f" <{role}>{html.escape(people)}</{role}>")
|
||||
|
||||
program_xml.append(" </credits>")
|
||||
|
||||
# Add program date if available (full date, not just year)
|
||||
if "date" in custom_data:
|
||||
program_xml.append(f' <date>{html.escape(custom_data["date"])}</date>')
|
||||
|
||||
# Add country if available
|
||||
if "country" in custom_data:
|
||||
program_xml.append(f' <country>{html.escape(custom_data["country"])}</country>')
|
||||
|
||||
# Add icon if available
|
||||
if "icon" in custom_data:
|
||||
program_xml.append(f' <icon src="{html.escape(custom_data["icon"])}" />')
|
||||
|
||||
# Add special flags as proper tags with enhanced handling
|
||||
if custom_data.get("previously_shown", False):
|
||||
prev_shown_details = custom_data.get("previously_shown_details", {})
|
||||
attrs = []
|
||||
if "start" in prev_shown_details:
|
||||
attrs.append(f'start="{html.escape(prev_shown_details["start"])}"')
|
||||
if "channel" in prev_shown_details:
|
||||
attrs.append(f'channel="{html.escape(prev_shown_details["channel"])}"')
|
||||
attr_str = " " + " ".join(attrs) if attrs else ""
|
||||
program_xml.append(f" <previously-shown{attr_str} />")
|
||||
|
||||
if custom_data.get("premiere", False):
|
||||
premiere_text = custom_data.get("premiere_text", "")
|
||||
if premiere_text:
|
||||
program_xml.append(f" <premiere>{html.escape(premiere_text)}</premiere>")
|
||||
# Handle actors separately to include role and guest attributes
|
||||
if "actor" in credits:
|
||||
actors = credits["actor"]
|
||||
if isinstance(actors, list):
|
||||
for actor in actors:
|
||||
if isinstance(actor, dict):
|
||||
name = actor.get("name", "")
|
||||
role_attr = f' role="{html.escape(actor["role"])}"' if "role" in actor else ""
|
||||
guest_attr = ' guest="yes"' if actor.get("guest") else ""
|
||||
program_xml.append(f" <actor{role_attr}{guest_attr}>{html.escape(name)}</actor>")
|
||||
else:
|
||||
program_xml.append(f" <actor>{html.escape(actor)}</actor>")
|
||||
else:
|
||||
program_xml.append(" <premiere />")
|
||||
program_xml.append(f" <actor>{html.escape(actors)}</actor>")
|
||||
|
||||
if custom_data.get("last_chance", False):
|
||||
last_chance_text = custom_data.get("last_chance_text", "")
|
||||
if last_chance_text:
|
||||
program_xml.append(f" <last-chance>{html.escape(last_chance_text)}</last-chance>")
|
||||
else:
|
||||
program_xml.append(" <last-chance />")
|
||||
program_xml.append(" </credits>")
|
||||
|
||||
if custom_data.get("new", False):
|
||||
program_xml.append(" <new />")
|
||||
# Add program date if available (full date, not just year)
|
||||
if "date" in custom_data:
|
||||
program_xml.append(f' <date>{html.escape(custom_data["date"])}</date>')
|
||||
|
||||
if custom_data.get('live', False):
|
||||
program_xml.append(' <live />')
|
||||
# Add country if available
|
||||
if "country" in custom_data:
|
||||
program_xml.append(f' <country>{html.escape(custom_data["country"])}</country>')
|
||||
|
||||
except Exception as e:
|
||||
program_xml.append(f" <!-- Error parsing custom properties: {html.escape(str(e))} -->")
|
||||
# Add icon if available
|
||||
if "icon" in custom_data:
|
||||
program_xml.append(f' <icon src="{html.escape(custom_data["icon"])}" />')
|
||||
|
||||
# Add special flags as proper tags with enhanced handling
|
||||
if custom_data.get("previously_shown", False):
|
||||
prev_shown_details = custom_data.get("previously_shown_details", {})
|
||||
attrs = []
|
||||
if "start" in prev_shown_details:
|
||||
attrs.append(f'start="{html.escape(prev_shown_details["start"])}"')
|
||||
if "channel" in prev_shown_details:
|
||||
attrs.append(f'channel="{html.escape(prev_shown_details["channel"])}"')
|
||||
attr_str = " " + " ".join(attrs) if attrs else ""
|
||||
program_xml.append(f" <previously-shown{attr_str} />")
|
||||
|
||||
if custom_data.get("premiere", False):
|
||||
premiere_text = custom_data.get("premiere_text", "")
|
||||
if premiere_text:
|
||||
program_xml.append(f" <premiere>{html.escape(premiere_text)}</premiere>")
|
||||
else:
|
||||
program_xml.append(" <premiere />")
|
||||
|
||||
if custom_data.get("last_chance", False):
|
||||
last_chance_text = custom_data.get("last_chance_text", "")
|
||||
if last_chance_text:
|
||||
program_xml.append(f" <last-chance>{html.escape(last_chance_text)}</last-chance>")
|
||||
else:
|
||||
program_xml.append(" <last-chance />")
|
||||
|
||||
if custom_data.get("new", False):
|
||||
program_xml.append(" <new />")
|
||||
|
||||
if custom_data.get('live', False):
|
||||
program_xml.append(' <live />')
|
||||
|
||||
program_xml.append(" </programme>")
|
||||
|
||||
|
|
@ -697,9 +693,7 @@ def xc_get_user(request):
|
|||
return None
|
||||
|
||||
user = get_object_or_404(User, username=username)
|
||||
custom_properties = (
|
||||
json.loads(user.custom_properties) if user.custom_properties else {}
|
||||
)
|
||||
custom_properties = user.custom_properties or {}
|
||||
|
||||
if "xc_password" not in custom_properties:
|
||||
return None
|
||||
|
|
@ -1494,42 +1488,35 @@ def xc_get_vod_info(request, user, vod_id):
|
|||
|
||||
# Add detailed info from custom_properties if available
|
||||
if movie.custom_properties:
|
||||
try:
|
||||
if isinstance(movie.custom_properties, dict):
|
||||
custom_data = movie.custom_properties
|
||||
else:
|
||||
custom_data = json.loads(movie.custom_properties)
|
||||
custom_data = movie.custom_properties or {}
|
||||
|
||||
# Extract detailed info
|
||||
#detailed_info = custom_data.get('detailed_info', {})
|
||||
detailed_info = movie_relation.custom_properties.get('detailed_info', {})
|
||||
# Update movie_data with detailed info
|
||||
movie_data.update({
|
||||
'director': custom_data.get('director') or detailed_info.get('director', ''),
|
||||
'actors': custom_data.get('actors') or detailed_info.get('actors', ''),
|
||||
'country': custom_data.get('country') or detailed_info.get('country', ''),
|
||||
'release_date': custom_data.get('release_date') or detailed_info.get('release_date') or detailed_info.get('releasedate', ''),
|
||||
'youtube_trailer': custom_data.get('youtube_trailer') or detailed_info.get('youtube_trailer') or detailed_info.get('trailer', ''),
|
||||
'backdrop_path': custom_data.get('backdrop_path') or detailed_info.get('backdrop_path', []),
|
||||
'cover_big': detailed_info.get('cover_big', ''),
|
||||
'bitrate': detailed_info.get('bitrate', 0),
|
||||
'video': detailed_info.get('video', {}),
|
||||
'audio': detailed_info.get('audio', {}),
|
||||
})
|
||||
# Extract detailed info
|
||||
#detailed_info = custom_data.get('detailed_info', {})
|
||||
detailed_info = movie_relation.custom_properties.get('detailed_info', {})
|
||||
# Update movie_data with detailed info
|
||||
movie_data.update({
|
||||
'director': custom_data.get('director') or detailed_info.get('director', ''),
|
||||
'actors': custom_data.get('actors') or detailed_info.get('actors', ''),
|
||||
'country': custom_data.get('country') or detailed_info.get('country', ''),
|
||||
'release_date': custom_data.get('release_date') or detailed_info.get('release_date') or detailed_info.get('releasedate', ''),
|
||||
'youtube_trailer': custom_data.get('youtube_trailer') or detailed_info.get('youtube_trailer') or detailed_info.get('trailer', ''),
|
||||
'backdrop_path': custom_data.get('backdrop_path') or detailed_info.get('backdrop_path', []),
|
||||
'cover_big': detailed_info.get('cover_big', ''),
|
||||
'bitrate': detailed_info.get('bitrate', 0),
|
||||
'video': detailed_info.get('video', {}),
|
||||
'audio': detailed_info.get('audio', {}),
|
||||
})
|
||||
|
||||
# Override with detailed_info values where available
|
||||
for key in ['name', 'description', 'year', 'genre', 'rating', 'tmdb_id', 'imdb_id']:
|
||||
if detailed_info.get(key):
|
||||
movie_data[key] = detailed_info[key]
|
||||
# Override with detailed_info values where available
|
||||
for key in ['name', 'description', 'year', 'genre', 'rating', 'tmdb_id', 'imdb_id']:
|
||||
if detailed_info.get(key):
|
||||
movie_data[key] = detailed_info[key]
|
||||
|
||||
# Handle plot vs description
|
||||
if detailed_info.get('plot'):
|
||||
movie_data['description'] = detailed_info['plot']
|
||||
elif detailed_info.get('description'):
|
||||
movie_data['description'] = detailed_info['description']
|
||||
|
||||
except (json.JSONDecodeError, AttributeError, TypeError) as e:
|
||||
logger.warning(f"Error parsing custom_properties for movie {movie.id}: {e}")
|
||||
# Handle plot vs description
|
||||
if detailed_info.get('plot'):
|
||||
movie_data['description'] = detailed_info['plot']
|
||||
elif detailed_info.get('description'):
|
||||
movie_data['description'] = detailed_info['description']
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process movie data: {e}")
|
||||
|
|
@ -1593,9 +1580,7 @@ def xc_movie_stream(request, username, password, stream_id, extension):
|
|||
|
||||
user = get_object_or_404(User, username=username)
|
||||
|
||||
custom_properties = (
|
||||
json.loads(user.custom_properties) if user.custom_properties else {}
|
||||
)
|
||||
custom_properties = user.custom_properties or {}
|
||||
|
||||
if "xc_password" not in custom_properties:
|
||||
return JsonResponse({"error": "Invalid credentials"}, status=401)
|
||||
|
|
@ -1642,9 +1627,7 @@ def xc_series_stream(request, username, password, stream_id, extension):
|
|||
|
||||
user = get_object_or_404(User, username=username)
|
||||
|
||||
custom_properties = (
|
||||
json.loads(user.custom_properties) if user.custom_properties else {}
|
||||
)
|
||||
custom_properties = user.custom_properties or {}
|
||||
|
||||
if "xc_password" not in custom_properties:
|
||||
return JsonResponse({"error": "Invalid credentials"}, status=401)
|
||||
|
|
|
|||
|
|
@ -467,9 +467,7 @@ def stream_xc(request, username, password, channel_id):
|
|||
extension = pathlib.Path(channel_id).suffix
|
||||
channel_id = pathlib.Path(channel_id).stem
|
||||
|
||||
custom_properties = (
|
||||
json.loads(user.custom_properties) if user.custom_properties else {}
|
||||
)
|
||||
custom_properties = user.custom_properties or {}
|
||||
|
||||
if "xc_password" not in custom_properties:
|
||||
return Response({"error": "Invalid credentials"}, status=401)
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@ const User = ({ user = null, isOpen, onClose }) => {
|
|||
const onSubmit = async () => {
|
||||
const values = form.getValues();
|
||||
|
||||
const { ...customProps } = JSON.parse(user?.custom_properties || '{}');
|
||||
const customProps = user?.custom_properties || {};
|
||||
|
||||
// Always save xc_password, even if it's empty (to allow clearing)
|
||||
customProps.xc_password = values.xc_password || '';
|
||||
delete values.xc_password;
|
||||
|
||||
values.custom_properties = JSON.stringify(customProps);
|
||||
values.custom_properties = customProps;
|
||||
|
||||
// If 'All' is included, clear this and we assume access to all channels
|
||||
if (values.channel_profiles.includes('0')) {
|
||||
|
|
@ -112,7 +112,7 @@ const User = ({ user = null, isOpen, onClose }) => {
|
|||
|
||||
useEffect(() => {
|
||||
if (user?.id) {
|
||||
const customProps = JSON.parse(user.custom_properties || '{}');
|
||||
const customProps = user.custom_properties || {};
|
||||
|
||||
form.setValues({
|
||||
username: user.username,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const RecordingCard = ({ recording }) => {
|
|||
API.deleteRecording(id);
|
||||
};
|
||||
|
||||
const customProps = JSON.parse(recording.custom_properties || '{}');
|
||||
const customProps = recording.custom_properties || {};
|
||||
let recordingName = 'Custom Recording';
|
||||
if (customProps.program) {
|
||||
recordingName = customProps.program.title;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue