More refactoring.

This commit is contained in:
SergeantPanda 2025-03-20 08:45:23 -05:00
parent 86e6c942d7
commit dc43fb41b5
3 changed files with 149 additions and 39 deletions

View file

@ -12,6 +12,7 @@ from apps.proxy.config import TSConfig as Config
from .. import proxy_server
from ..redis_keys import RedisKeys
from ..constants import EventType
from ..url_utils import get_stream_info_for_switch
logger = logging.getLogger("ts_proxy")
@ -43,18 +44,30 @@ class ChannelService:
return success
@staticmethod
def change_stream_url(channel_id, new_url, user_agent=None):
def change_stream_url(channel_id, new_url=None, user_agent=None, target_stream_id=None):
"""
Change the URL of an existing stream.
Args:
channel_id: UUID of the channel
new_url: New stream URL
new_url: New stream URL (optional if target_stream_id is provided)
user_agent: Optional user agent to update
target_stream_id: Optional target stream ID to switch to
Returns:
dict: Result information including success status and diagnostics
"""
# If no direct URL is provided but a target stream is, get URL from target stream
if not new_url and target_stream_id:
stream_info = get_stream_info_for_switch(channel_id, target_stream_id)
if 'error' in stream_info:
return {
'status': 'error',
'message': stream_info['error']
}
new_url = stream_info['url']
user_agent = stream_info['user_agent']
# Check if channel exists
in_local_managers = channel_id in proxy_server.stream_managers
in_local_buffers = channel_id in proxy_server.stream_buffers

View file

@ -0,0 +1,129 @@
"""
Utilities for handling stream URLs and transformations.
"""
import logging
import re
from typing import Optional, Tuple
from django.shortcuts import get_object_or_404
from apps.channels.models import Channel, Stream
from apps.m3u.models import M3UAccount, M3UAccountProfile
from core.models import UserAgent, CoreSettings
logger = logging.getLogger("ts_proxy")
def generate_stream_url(channel_id: str) -> Tuple[str, str, bool]:
"""
Generate the appropriate stream URL for a channel based on its profile settings.
Args:
channel_id: The UUID of the channel
Returns:
Tuple[str, str, bool]: (stream_url, user_agent, transcode_flag)
"""
# Get channel and related objects
channel = get_object_or_404(Channel, uuid=channel_id)
stream_id, profile_id = channel.get_stream()
if stream_id is None or profile_id is None:
logger.error(f"No stream assigned to channel {channel_id}")
return None, None, False
# Get the M3U account profile for URL pattern
stream = get_object_or_404(Stream, pk=stream_id)
profile = get_object_or_404(M3UAccountProfile, pk=profile_id)
# Get the appropriate user agent
m3u_account = M3UAccount.objects.get(id=profile.m3u_account.id)
stream_user_agent = UserAgent.objects.get(id=m3u_account.user_agent.id).user_agent
if stream_user_agent is None:
stream_user_agent = UserAgent.objects.get(id=CoreSettings.get_default_user_agent_id())
logger.debug(f"No user agent found for account, using default: {stream_user_agent}")
# Generate stream URL based on the selected profile
input_url = stream.url
stream_url = transform_url(input_url, profile.search_pattern, profile.replace_pattern)
# Check if transcoding is needed
stream_profile = channel.get_stream_profile()
if stream_profile.is_proxy() or stream_profile is None:
transcode = False
else:
transcode = True
# Get profile name as string
profile_value = str(stream_profile)
return stream_url, stream_user_agent, transcode, profile_value
def transform_url(input_url: str, search_pattern: str, replace_pattern: str) -> str:
"""
Transform a URL using regex pattern replacement.
Args:
input_url: The base URL to transform
search_pattern: The regex search pattern
replace_pattern: The replacement pattern
Returns:
str: The transformed URL
"""
try:
logger.debug("Executing URL pattern replacement:")
logger.debug(f" base URL: {input_url}")
logger.debug(f" search: {search_pattern}")
# Handle backreferences in the replacement pattern
safe_replace_pattern = re.sub(r'\$(\d+)', r'\\\1', replace_pattern)
logger.debug(f" replace: {replace_pattern}")
logger.debug(f" safe replace: {safe_replace_pattern}")
# Apply the transformation
stream_url = re.sub(search_pattern, safe_replace_pattern, input_url)
logger.debug(f"Generated stream url: {stream_url}")
return stream_url
except Exception as e:
logger.error(f"Error transforming URL: {e}")
return input_url # Return original URL on error
def get_stream_info_for_switch(channel_id: str, target_stream_id: Optional[int] = None) -> dict:
"""
Get stream information for a channel switch, optionally to a specific stream ID.
Args:
channel_id: The UUID of the channel
target_stream_id: Optional specific stream ID to switch to
Returns:
dict: Stream information including URL, user agent and transcode flag
"""
channel = get_object_or_404(Channel, uuid=channel_id)
# Use the target stream if specified, otherwise use current stream
if target_stream_id:
stream_id = target_stream_id
# Find a compatible profile for this stream
profiles = M3UAccountProfile.objects.filter(stream=stream_id)
if not profiles.exists():
logger.error(f"No profile found for stream {stream_id}")
return {'error': 'No profile found for stream'}
profile_id = profiles.first().id
else:
stream_id, profile_id = channel.get_stream()
if stream_id is None or profile_id is None:
return {'error': 'No stream assigned to channel'}
# Generate the URL using our utility
stream_url, user_agent, transcode, profile_value = generate_stream_url(channel_id)
return {
'url': stream_url,
'user_agent': user_agent,
'transcode': transcode,
'profile': profile_value,
'stream_id': stream_id,
'profile_id': profile_id
}

View file

@ -21,6 +21,7 @@ from rest_framework.permissions import IsAuthenticated
from .constants import ChannelState, EventType, StreamType
from .config_helper import ConfigHelper
from .services.channel_service import ChannelService
from .url_utils import generate_stream_url, transform_url, get_stream_info_for_switch
# Configure logging properly
logger = logging.getLogger("ts_proxy")
@ -52,50 +53,17 @@ def stream_ts(request, channel_id):
# Initialize the channel (but don't wait for completion)
logger.info(f"[{client_id}] Starting channel {channel_id} initialization")
# Get stream details from channel model
stream_id, profile_id = channel.get_stream()
if stream_id is None or profile_id is None:
# Use the utility function to get stream URL and settings
stream_url, stream_user_agent, transcode, profile_value = generate_stream_url(channel_id)
if stream_url is None:
return JsonResponse({'error': 'Channel not available'}, status=404)
# Load in necessary objects for the stream
logger.info(f"Fetching stream ID {stream_id}")
stream = get_object_or_404(Stream, pk=stream_id)
logger.info(f"Fetching profile ID {profile_id}")
profile = get_object_or_404(M3UAccountProfile, pk=profile_id)
# Load in the user-agent for the STREAM connection (not client)
m3u_account = M3UAccount.objects.get(id=profile.m3u_account.id)
stream_user_agent = UserAgent.objects.get(id=m3u_account.user_agent.id).user_agent
if stream_user_agent is None:
stream_user_agent = UserAgent.objects.get(id=CoreSettings.get_default_user_agent_id())
logger.debug(f"No user agent found for account, using default: {stream_user_agent}")
else:
logger.debug(f"User agent found for account: {stream_user_agent}")
# Generate stream URL based on the selected profile
input_url = stream.url
logger.debug("Executing the following pattern replacement:")
logger.debug(f" search: {profile.search_pattern}")
safe_replace_pattern = re.sub(r'\$(\d+)', r'\\\1', profile.replace_pattern)
logger.debug(f" replace: {profile.replace_pattern}")
logger.debug(f" safe replace: {safe_replace_pattern}")
stream_url = re.sub(profile.search_pattern, safe_replace_pattern, input_url)
logger.debug(f"Generated stream url: {stream_url}")
# Generate transcode command if needed
stream_profile = channel.get_stream_profile()
if stream_profile.is_redirect():
return HttpResponseRedirect(stream_url)
# Need to check if profile is transcoded
logger.debug(f"Using profile {stream_profile} for stream {stream_id}")
if stream_profile.is_proxy() or stream_profile is None:
transcode = False
else:
transcode = True
# Initialize channel using the service
profile_value = str(stream_profile)
# Initialize channel with the stream's user agent (not the client's)
success = ChannelService.initialize_channel(
channel_id, stream_url, stream_user_agent, transcode, profile_value
)