mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 10:45:27 +00:00
Pre Alpha v5
Added tvg-id's to channels and streams
This commit is contained in:
parent
d89bf35c0d
commit
128ab16210
8 changed files with 54 additions and 22 deletions
|
|
@ -5,9 +5,9 @@ from .models import Stream, Channel, ChannelGroup
|
|||
class StreamAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'id', 'name', 'group_name', 'custom_url',
|
||||
'current_viewers', 'is_transcoded', 'updated_at',
|
||||
'current_viewers', 'updated_at',
|
||||
)
|
||||
list_filter = ('group_name', 'is_transcoded')
|
||||
list_filter = ('group_name',)
|
||||
search_fields = ('name', 'custom_url', 'group_name')
|
||||
ordering = ('-updated_at',)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from django.shortcuts import get_object_or_404
|
|||
from .models import Stream, Channel, ChannelGroup
|
||||
from .serializers import StreamSerializer, ChannelSerializer, ChannelGroupSerializer
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# 1) Stream API (CRUD)
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
|
@ -21,20 +20,19 @@ class StreamViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
|
||||
# Exclude streams from inactive M3U accounts
|
||||
qs = qs.exclude(m3u_account__is_active=False)
|
||||
|
||||
assigned = self.request.query_params.get('assigned')
|
||||
if assigned is not None:
|
||||
# Streams that belong to a given channel?
|
||||
qs = qs.filter(channels__id=assigned)
|
||||
|
||||
|
||||
unassigned = self.request.query_params.get('unassigned')
|
||||
if unassigned == '1':
|
||||
# Streams that are not linked to any channel
|
||||
qs = qs.filter(channels__isnull=True)
|
||||
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# 2) Channel Group Management (CRUD)
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
|
@ -43,7 +41,6 @@ class ChannelGroupViewSet(viewsets.ModelViewSet):
|
|||
serializer_class = ChannelGroupSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# 3) Channel Management (CRUD)
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
|
@ -103,22 +100,22 @@ class ChannelViewSet(viewsets.ModelViewSet):
|
|||
stream_id = request.data.get('stream_id')
|
||||
if not stream_id:
|
||||
return Response({"error": "Missing stream_id"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
stream = get_object_or_404(Stream, pk=stream_id)
|
||||
|
||||
# Include the stream's tvg_id in the channel data
|
||||
channel_data = {
|
||||
'channel_number': request.data.get('channel_number', 0),
|
||||
'channel_name': request.data.get('channel_name', f"Channel from {stream.name}"),
|
||||
'tvg_id': stream.tvg_id, # Inherit tvg-id from the stream
|
||||
}
|
||||
serializer = self.get_serializer(data=channel_data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
channel = serializer.save()
|
||||
|
||||
# Optionally attach the stream to that channel
|
||||
# Optionally attach the stream to the channel
|
||||
channel.streams.add(stream)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# 4) Bulk Delete Streams
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
|
@ -145,7 +142,6 @@ class BulkDeleteStreamsAPIView(APIView):
|
|||
Stream.objects.filter(id__in=stream_ids).delete()
|
||||
return Response({"message": "Streams deleted successfully!"}, status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# 5) Bulk Delete Channels
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -4,3 +4,7 @@ class ChannelsConfig(AppConfig):
|
|||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.channels'
|
||||
verbose_name = "Channel & Stream Management"
|
||||
|
||||
def ready(self):
|
||||
# Import signals so they get registered.
|
||||
import apps.channels.signals
|
||||
|
|
|
|||
|
|
@ -43,6 +43,5 @@ class StreamForm(forms.ModelForm):
|
|||
'logo_url',
|
||||
'tvg_id',
|
||||
'local_file',
|
||||
'is_transcoded',
|
||||
'group_name',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ class Stream(models.Model):
|
|||
tvg_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
local_file = models.FileField(upload_to='uploads/', blank=True, null=True)
|
||||
current_viewers = models.PositiveIntegerField(default=0)
|
||||
is_transcoded = models.BooleanField(default=False)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
group_name = models.CharField(max_length=255, blank=True, null=True)
|
||||
stream_profile = models.ForeignKey(
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ class StreamSerializer(serializers.ModelSerializer):
|
|||
'tvg_id',
|
||||
'local_file',
|
||||
'current_viewers',
|
||||
'is_transcoded',
|
||||
'updated_at',
|
||||
'group_name',
|
||||
'stream_profile_id',
|
||||
|
|
|
|||
16
apps/channels/signals.py
Normal file
16
apps/channels/signals.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from django.db.models.signals import m2m_changed
|
||||
from django.dispatch import receiver
|
||||
from .models import Channel, Stream
|
||||
|
||||
@receiver(m2m_changed, sender=Channel.streams.through)
|
||||
def update_channel_tvg_id(sender, instance, action, reverse, model, pk_set, **kwargs):
|
||||
# When streams are added to a channel...
|
||||
if action == "post_add":
|
||||
# If the channel does not already have a tvg-id...
|
||||
if not instance.tvg_id:
|
||||
# Look for any of the newly added streams that have a nonempty tvg_id.
|
||||
streams_with_tvg = model.objects.filter(pk__in=pk_set).exclude(tvg_id__exact='')
|
||||
if streams_with_tvg.exists():
|
||||
# Update the channel's tvg_id with the first found tvg_id.
|
||||
instance.tvg_id = streams_with_tvg.first().tvg_id
|
||||
instance.save(update_fields=['tvg_id'])
|
||||
|
|
@ -117,14 +117,23 @@ def refresh_single_m3u_account(account_id):
|
|||
if line.startswith('#EXTINF'):
|
||||
tvg_name_match = re.search(r'tvg-name="([^"]*)"', line)
|
||||
tvg_logo_match = re.search(r'tvg-logo="([^"]*)"', line)
|
||||
# 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
|
||||
logo_url = tvg_logo_match.group(1) if tvg_logo_match else ""
|
||||
group_title = _get_group_title(line)
|
||||
|
||||
logger.debug(f"Parsed EXTINF: name={name}, logo_url={logo_url}, group_title={group_title}")
|
||||
current_info = {"name": name, "logo_url": logo_url, "group_title": group_title}
|
||||
logger.debug(f"Parsed EXTINF: name={name}, logo_url={logo_url}, tvg_id={tvg_id}, group_title={group_title}")
|
||||
current_info = {
|
||||
"name": name,
|
||||
"logo_url": logo_url,
|
||||
"group_title": group_title,
|
||||
"tvg_id": tvg_id, # save the tvg-id here
|
||||
}
|
||||
|
||||
elif current_info and line.startswith('http'):
|
||||
lower_line = line.lower()
|
||||
|
|
@ -145,7 +154,11 @@ def refresh_single_m3u_account(account_id):
|
|||
current_info = None
|
||||
continue
|
||||
|
||||
defaults = {"logo_url": current_info["logo_url"]}
|
||||
# Include tvg_id in the defaults so it gets saved
|
||||
defaults = {
|
||||
"logo_url": current_info["logo_url"],
|
||||
"tvg_id": current_info["tvg_id"]
|
||||
}
|
||||
try:
|
||||
obj, created = Stream.objects.update_or_create(
|
||||
name=current_info["name"],
|
||||
|
|
@ -203,11 +216,13 @@ def parse_m3u_file(file_path, account):
|
|||
tvg_name_match = re.search(r'tvg-name="([^"]*)"', line)
|
||||
tvg_logo_match = re.search(r'tvg-logo="([^"]*)"', line)
|
||||
fallback_name = line.split(",", 1)[-1].strip() if "," in line else "Stream"
|
||||
tvg_id_match = re.search(r'tvg-id="([^"]*)"', line)
|
||||
tvg_id = tvg_id_match.group(1) if tvg_id_match else ""
|
||||
|
||||
name = tvg_name_match.group(1) if tvg_name_match else fallback_name
|
||||
logo_url = tvg_logo_match.group(1) if tvg_logo_match else ""
|
||||
|
||||
current_info = {"name": name, "logo_url": logo_url}
|
||||
current_info = {"name": name, "logo_url": logo_url, "tvg_id": tvg_id}
|
||||
|
||||
elif current_info and line.startswith('http'):
|
||||
lower_line = line.lower()
|
||||
|
|
@ -216,7 +231,11 @@ def parse_m3u_file(file_path, account):
|
|||
current_info = None
|
||||
continue
|
||||
|
||||
defaults = {"logo_url": current_info["logo_url"]}
|
||||
defaults = {
|
||||
"logo_url": current_info["logo_url"],
|
||||
"tvg_id": current_info.get("tvg_id", "")
|
||||
}
|
||||
|
||||
try:
|
||||
obj, created = Stream.objects.update_or_create(
|
||||
name=current_info["name"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue