mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-26 03:24:43 +00:00
Merge branch 'main' of https://github.com/Dispatcharr/Dispatcharr into Proxy
This commit is contained in:
commit
fe83e8a03e
49 changed files with 813 additions and 401 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-02 13:52
|
||||
# Generated by Django 5.1.6 on 2025-03-05 22:07
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
|
|
@ -12,7 +12,7 @@ class Migration(migrations.Migration):
|
|||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('channels', '0001_initial'),
|
||||
('dispatcharr_channels', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
@ -31,7 +31,7 @@ class Migration(migrations.Migration):
|
|||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('avatar_config', models.JSONField(blank=True, default=dict, null=True)),
|
||||
('channel_groups', models.ManyToManyField(blank=True, related_name='users', to='channels.channelgroup')),
|
||||
('channel_groups', models.ManyToManyField(blank=True, related_name='users', to='dispatcharr_channels.channelgroup')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class User(AbstractUser):
|
|||
"""
|
||||
avatar_config = models.JSONField(default=dict, blank=True, null=True)
|
||||
channel_groups = models.ManyToManyField(
|
||||
'channels.ChannelGroup', # Updated reference to renamed model
|
||||
'dispatcharr_channels.ChannelGroup', # Updated reference to renamed model
|
||||
blank=True,
|
||||
related_name="users"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ class ChannelsConfig(AppConfig):
|
|||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.channels'
|
||||
verbose_name = "Channel & Stream Management"
|
||||
|
||||
label = 'dispatcharr_channels'
|
||||
|
||||
def ready(self):
|
||||
# Import signals so they get registered.
|
||||
import apps.channels.signals
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-02 13:52
|
||||
# Generated by Django 5.1.6 on 2025-03-05 22:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
|
@ -32,7 +32,7 @@ class Migration(migrations.Migration):
|
|||
('tvg_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('tvg_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('stream_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='channels', to='core.streamprofile')),
|
||||
('channel_group', models.ForeignKey(blank=True, help_text='Channel group this channel belongs to.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='channels', to='channels.channelgroup')),
|
||||
('channel_group', models.ForeignKey(blank=True, help_text='Channel group this channel belongs to.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='channels', to='dispatcharr_channels.channelgroup')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
|
@ -62,8 +62,8 @@ class Migration(migrations.Migration):
|
|||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='channels.channel')),
|
||||
('stream', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='channels.stream')),
|
||||
('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatcharr_channels.channel')),
|
||||
('stream', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatcharr_channels.stream')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['order'],
|
||||
|
|
@ -72,6 +72,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='channel',
|
||||
name='streams',
|
||||
field=models.ManyToManyField(blank=True, related_name='channels', through='channels.ChannelStream', to='channels.stream'),
|
||||
field=models.ManyToManyField(blank=True, related_name='channels', through='dispatcharr_channels.ChannelStream', to='dispatcharr_channels.stream'),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class StreamSerializer(serializers.ModelSerializer):
|
|||
|
||||
return fields
|
||||
|
||||
|
||||
#
|
||||
# Channel Group
|
||||
#
|
||||
|
|
@ -74,7 +75,8 @@ class ChannelSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
|
||||
streams = serializers.ListField(
|
||||
child=serializers.IntegerField(), write_only=True
|
||||
child=serializers.IntegerField(),
|
||||
write_only=True
|
||||
)
|
||||
stream_ids = serializers.SerializerMethodField()
|
||||
|
||||
|
|
@ -111,17 +113,28 @@ class ChannelSerializer(serializers.ModelSerializer):
|
|||
|
||||
def update(self, instance, validated_data):
|
||||
print("Validated Data:", validated_data)
|
||||
stream_ids = validated_data.get('streams', None)
|
||||
stream_ids = validated_data.pop('streams', None)
|
||||
print(f'stream ids: {stream_ids}')
|
||||
|
||||
# Update basic fields
|
||||
instance.name = validated_data.get('channel_name', instance.channel_name)
|
||||
# Update the actual Channel fields
|
||||
instance.channel_number = validated_data.get('channel_number', instance.channel_number)
|
||||
instance.channel_name = validated_data.get('channel_name', instance.channel_name)
|
||||
instance.logo_url = validated_data.get('logo_url', instance.logo_url)
|
||||
instance.tvg_id = validated_data.get('tvg_id', instance.tvg_id)
|
||||
instance.tvg_name = validated_data.get('tvg_name', instance.tvg_name)
|
||||
|
||||
# If serializer allows changing channel_group or stream_profile:
|
||||
if 'channel_group' in validated_data:
|
||||
instance.channel_group = validated_data['channel_group']
|
||||
if 'stream_profile' in validated_data:
|
||||
instance.stream_profile = validated_data['stream_profile']
|
||||
|
||||
instance.save()
|
||||
|
||||
# Handle the many-to-many 'streams'
|
||||
if stream_ids is not None:
|
||||
# Clear existing relationships
|
||||
instance.channelstream_set.all().delete()
|
||||
|
||||
# Add new streams in order
|
||||
for index, stream_id in enumerate(stream_ids):
|
||||
ChannelStream.objects.create(channel=instance, stream_id=stream_id, order=index)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-02 13:52
|
||||
# Generated by Django 5.1.6 on 2025-03-05 22:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-02 13:52
|
||||
# Generated by Django 5.1.6 on 2025-03-05 22:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
import gzip # <-- New import for gzip support
|
||||
from celery import shared_task
|
||||
from .models import EPGSource, EPGData, ProgramData
|
||||
from django.utils import timezone
|
||||
|
|
@ -29,7 +30,16 @@ def fetch_xmltv(source):
|
|||
response = requests.get(source.url, timeout=30)
|
||||
response.raise_for_status()
|
||||
logger.debug("XMLTV data fetched successfully.")
|
||||
root = ET.fromstring(response.content)
|
||||
|
||||
# If the URL ends with '.gz', decompress the response content
|
||||
if source.url.lower().endswith('.gz'):
|
||||
logger.debug("Detected .gz file. Decompressing...")
|
||||
decompressed_bytes = gzip.decompress(response.content)
|
||||
xml_data = decompressed_bytes.decode('utf-8')
|
||||
else:
|
||||
xml_data = response.text
|
||||
|
||||
root = ET.fromstring(xml_data)
|
||||
logger.debug("Parsed XMLTV XML content.")
|
||||
|
||||
# Group programmes by their tvg_id from the XMLTV file
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class LineupAPIView(APIView):
|
|||
{
|
||||
"GuideNumber": str(ch.channel_number),
|
||||
"GuideName": ch.channel_name,
|
||||
"URL": request.build_absolute_uri(f"/player/stream/{ch.id}")
|
||||
"URL": request.build_absolute_uri(f"/output/stream/{ch.id}")
|
||||
}
|
||||
for ch in channels
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-02 13:52
|
||||
# Generated by Django 5.1.6 on 2025-03-05 22:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class LineupAPIView(APIView):
|
|||
{
|
||||
"GuideNumber": str(ch.channel_number),
|
||||
"GuideName": ch.channel_name,
|
||||
"URL": request.build_absolute_uri(f"/player/stream/{ch.id}")
|
||||
"URL": request.build_absolute_uri(f"/output/stream/{ch.id}")
|
||||
}
|
||||
for ch in channels
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.1.6 on 2025-03-02 13:52
|
||||
# Generated by Django 5.1.6 on 2025-03-05 22:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from django.conf import settings
|
|||
from django.core.cache import cache
|
||||
from .models import M3UAccount
|
||||
from apps.channels.models import Stream
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -120,7 +122,7 @@ def refresh_single_m3u_account(account_id):
|
|||
# Extract tvg-id
|
||||
tvg_id_match = re.search(r'tvg-id="([^"]*)"', line)
|
||||
tvg_id = tvg_id_match.group(1) if tvg_id_match else ""
|
||||
|
||||
|
||||
fallback_name = line.split(",", 1)[-1].strip() if "," in line else "Default Stream"
|
||||
|
||||
name = tvg_name_match.group(1) if tvg_name_match else fallback_name
|
||||
|
|
@ -178,6 +180,14 @@ def refresh_single_m3u_account(account_id):
|
|||
|
||||
logger.info(f"Completed parsing. Created {created_count} new Streams, updated {updated_count} existing Streams, excluded {excluded_count} Streams.")
|
||||
release_lock('refresh_single_m3u_account', account_id)
|
||||
channel_layer = get_channel_layer()
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
"updates",
|
||||
{
|
||||
"type": "m3u_refresh",
|
||||
"message": {"success": True, "message": "M3U refresh completed successfully"}
|
||||
},
|
||||
)
|
||||
return f"Account {account_id} => Created {created_count}, updated {updated_count}, excluded {excluded_count} Streams."
|
||||
|
||||
def process_uploaded_m3u_file(file, account):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue