diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index e2c3902d..30174a2c 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -1404,6 +1404,7 @@ def sync_auto_channels(account_id, scan_start_time=None): channel_profile_ids = None channel_sort_order = None channel_sort_reverse = False + stream_profile_id = None if group_relation.custom_properties: try: group_custom_props = json.loads(group_relation.custom_properties) @@ -1419,6 +1420,7 @@ def sync_auto_channels(account_id, scan_start_time=None): 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 @@ -1428,6 +1430,7 @@ def sync_auto_channels(account_id, scan_start_time=None): channel_profile_ids = None channel_sort_order = None channel_sort_reverse = False + stream_profile_id = None # Determine which group to use for created channels target_group = channel_group @@ -1560,6 +1563,21 @@ def sync_auto_channels(account_id, scan_start_time=None): else: profiles_to_assign = list(ChannelProfile.objects.all()) + # Get stream profile to assign if specified + from core.models import StreamProfile + stream_profile_to_assign = None + if stream_profile_id: + try: + stream_profile_to_assign = StreamProfile.objects.get(id=int(stream_profile_id)) + logger.info( + f"Will assign stream profile '{stream_profile_to_assign.name}' to auto-synced streams in group '{channel_group.name}'" + ) + except (StreamProfile.DoesNotExist, ValueError, TypeError): + logger.warning( + f"Stream profile with ID {stream_profile_id} not found for group '{channel_group.name}', streams will use default profile" + ) + stream_profile_to_assign = None + # Process each current stream current_channel_number = start_number @@ -1694,6 +1712,11 @@ def sync_auto_channels(account_id, scan_start_time=None): existing_channel.epg_data = current_epg_data channel_updated = True + # Handle stream profile updates for the channel + if stream_profile_to_assign and existing_channel.stream_profile != stream_profile_to_assign: + existing_channel.stream_profile = stream_profile_to_assign + channel_updated = True + if channel_updated: existing_channel.save() channels_updated += 1 @@ -1797,6 +1820,10 @@ def sync_auto_channels(account_id, scan_start_time=None): channel.logo = logo channel.save(update_fields=["logo"]) + # Handle stream profile assignment + if stream_profile_to_assign: + channel.stream_profile = stream_profile_to_assign + channel.save(update_fields=['stream_profile']) channels_created += 1 logger.debug( f"Created auto channel: {channel.channel_number} - {channel.name}" diff --git a/frontend/src/components/forms/LiveGroupFilter.jsx b/frontend/src/components/forms/LiveGroupFilter.jsx index 4e0aa4b8..4a473afe 100644 --- a/frontend/src/components/forms/LiveGroupFilter.jsx +++ b/frontend/src/components/forms/LiveGroupFilter.jsx @@ -19,6 +19,7 @@ import { } from '@mantine/core'; import { Info } from 'lucide-react'; import useChannelsStore from '../../store/channels'; +import useStreamProfilesStore from '../../store/streamProfiles'; import { CircleCheck, CircleX } from 'lucide-react'; // Custom item component for MultiSelect with tooltip @@ -35,8 +36,17 @@ const OptionWithTooltip = forwardRef( const LiveGroupFilter = ({ playlist, groupStates, setGroupStates }) => { const channelGroups = useChannelsStore((s) => s.channelGroups); const profiles = useChannelsStore((s) => s.profiles); + const streamProfiles = useStreamProfilesStore((s) => s.profiles); + const fetchStreamProfiles = useStreamProfilesStore((s) => s.fetchProfiles); const [groupFilter, setGroupFilter] = useState(''); + // Fetch stream profiles when component mounts + useEffect(() => { + if (streamProfiles.length === 0) { + fetchStreamProfiles(); + } + }, [streamProfiles.length, fetchStreamProfiles]); + useEffect(() => { if (Object.keys(channelGroups).length === 0) { return; @@ -279,6 +289,12 @@ const LiveGroupFilter = ({ playlist, groupStates, setGroupStates }) => { description: 'Specify the order in which channels are created (name, tvg_id, updated_at)', }, + { + value: 'stream_profile_assignment', + label: 'Stream Profile Assignment', + description: + 'Assign a specific stream profile to all channels in this group during auto sync', + }, ]} itemComponent={OptionWithTooltip} value={(() => { @@ -318,6 +334,12 @@ const LiveGroupFilter = ({ playlist, groupStates, setGroupStates }) => { ) { selectedValues.push('channel_sort_order'); } + if ( + group.custom_properties?.stream_profile_id !== + undefined + ) { + selectedValues.push('stream_profile_assignment'); + } return selectedValues; })()} onChange={(values) => { @@ -421,6 +443,22 @@ const LiveGroupFilter = ({ playlist, groupStates, setGroupStates }) => { delete newCustomProps.channel_sort_reverse; // Remove reverse when sort is removed } + // Handle stream_profile_assignment + if ( + selectedOptions.includes( + 'stream_profile_assignment' + ) + ) { + if ( + newCustomProps.stream_profile_id === + undefined + ) { + newCustomProps.stream_profile_id = null; + } + } else { + delete newCustomProps.stream_profile_id; + } + return { ...state, custom_properties: newCustomProps, @@ -601,6 +639,50 @@ const LiveGroupFilter = ({ playlist, groupStates, setGroupStates }) => { )} + {/* Show stream profile select only if stream_profile_assignment is selected */} + {group.custom_properties?.stream_profile_id !== + undefined && ( + +