diff --git a/apps/output/views.py b/apps/output/views.py
index e33bf4ca..8f1790b3 100644
--- a/apps/output/views.py
+++ b/apps/output/views.py
@@ -187,6 +187,54 @@ def generate_m3u(request, profile_name=None, user=None):
return response
+def generate_fallback_programs(channel_id, channel_name, now, num_days, program_length_hours, fallback_title, fallback_description):
+ """
+ Generate dummy programs using custom fallback templates when patterns don't match.
+
+ Args:
+ channel_id: Channel ID for the programs
+ channel_name: Channel name to use as fallback in templates
+ now: Current datetime (in UTC)
+ num_days: Number of days to generate programs for
+ program_length_hours: Length of each program in hours
+ fallback_title: Custom fallback title template (empty string if not provided)
+ fallback_description: Custom fallback description template (empty string if not provided)
+
+ Returns:
+ List of program dictionaries
+ """
+ programs = []
+
+ # Use custom fallback title or channel name as default
+ title = fallback_title if fallback_title else channel_name
+
+ # Use custom fallback description or a simple default message
+ if fallback_description:
+ description = fallback_description
+ else:
+ description = f"EPG information is currently unavailable for {channel_name}"
+
+ # Create programs for each day
+ for day in range(num_days):
+ day_start = now + timedelta(days=day)
+
+ # Create programs with specified length throughout the day
+ for hour_offset in range(0, 24, program_length_hours):
+ # Calculate program start and end times
+ start_time = day_start + timedelta(hours=hour_offset)
+ end_time = start_time + timedelta(hours=program_length_hours)
+
+ programs.append({
+ "channel_id": channel_id,
+ "start_time": start_time,
+ "end_time": end_time,
+ "title": title,
+ "description": description,
+ })
+
+ return programs
+
+
def generate_dummy_programs(channel_id, channel_name, num_days=1, program_length_hours=4, epg_source=None):
"""
Generate dummy EPG programs for channels.
@@ -216,11 +264,26 @@ def generate_dummy_programs(channel_id, channel_name, num_days=1, program_length
epg_source.custom_properties
)
# If custom generation succeeded, return those programs
- # If it returned empty (pattern didn't match), fall through to default
+ # If it returned empty (pattern didn't match), check for custom fallback templates
if custom_programs:
return custom_programs
else:
- logger.info(f"Custom pattern didn't match for '{channel_name}', using default dummy EPG")
+ logger.info(f"Custom pattern didn't match for '{channel_name}', checking for custom fallback templates")
+
+ # Check if custom fallback templates are provided
+ custom_props = epg_source.custom_properties
+ fallback_title = custom_props.get('fallback_title_template', '').strip()
+ fallback_description = custom_props.get('fallback_description_template', '').strip()
+
+ # If custom fallback templates exist, use them instead of default
+ if fallback_title or fallback_description:
+ logger.info(f"Using custom fallback templates for '{channel_name}'")
+ return generate_fallback_programs(
+ channel_id, channel_name, now, num_days,
+ program_length_hours, fallback_title, fallback_description
+ )
+ else:
+ logger.info(f"No custom fallback templates found, using default dummy EPG")
# Default humorous program descriptions based on time of day
time_descriptions = {
diff --git a/frontend/src/components/forms/DummyEPG.jsx b/frontend/src/components/forms/DummyEPG.jsx
index 48b19106..2b6984a8 100644
--- a/frontend/src/components/forms/DummyEPG.jsx
+++ b/frontend/src/components/forms/DummyEPG.jsx
@@ -49,6 +49,9 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
useState('');
const [endedTitleTemplate, setEndedTitleTemplate] = useState('');
const [endedDescriptionTemplate, setEndedDescriptionTemplate] = useState('');
+ const [fallbackTitleTemplate, setFallbackTitleTemplate] = useState('');
+ const [fallbackDescriptionTemplate, setFallbackDescriptionTemplate] =
+ useState('');
const [channelLogoUrl, setChannelLogoUrl] = useState('');
const [programPosterUrl, setProgramPosterUrl] = useState('');
const [timezoneOptions, setTimezoneOptions] = useState([]);
@@ -73,6 +76,8 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
upcoming_description_template: '',
ended_title_template: '',
ended_description_template: '',
+ fallback_title_template: '',
+ fallback_description_template: '',
channel_logo_url: '',
program_poster_url: '',
name_source: 'channel',
@@ -463,6 +468,9 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
custom.upcoming_description_template || '',
ended_title_template: custom.ended_title_template || '',
ended_description_template: custom.ended_description_template || '',
+ fallback_title_template: custom.fallback_title_template || '',
+ fallback_description_template:
+ custom.fallback_description_template || '',
channel_logo_url: custom.channel_logo_url || '',
program_poster_url: custom.program_poster_url || '',
name_source: custom.name_source || 'channel',
@@ -487,6 +495,10 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
);
setEndedTitleTemplate(custom.ended_title_template || '');
setEndedDescriptionTemplate(custom.ended_description_template || '');
+ setFallbackTitleTemplate(custom.fallback_title_template || '');
+ setFallbackDescriptionTemplate(
+ custom.fallback_description_template || ''
+ );
setChannelLogoUrl(custom.channel_logo_url || '');
setProgramPosterUrl(custom.program_poster_url || '');
} else {
@@ -501,6 +513,8 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
setUpcomingDescriptionTemplate('');
setEndedTitleTemplate('');
setEndedDescriptionTemplate('');
+ setFallbackTitleTemplate('');
+ setFallbackDescriptionTemplate('');
setChannelLogoUrl('');
setProgramPosterUrl('');
}
@@ -571,6 +585,9 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
custom.upcoming_description_template || '',
ended_title_template: custom.ended_title_template || '',
ended_description_template: custom.ended_description_template || '',
+ fallback_title_template: custom.fallback_title_template || '',
+ fallback_description_template:
+ custom.fallback_description_template || '',
channel_logo_url: custom.channel_logo_url || '',
program_poster_url: custom.program_poster_url || '',
name_source: custom.name_source || 'channel',
@@ -593,6 +610,8 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
setUpcomingDescriptionTemplate(custom.upcoming_description_template || '');
setEndedTitleTemplate(custom.ended_title_template || '');
setEndedDescriptionTemplate(custom.ended_description_template || '');
+ setFallbackTitleTemplate(custom.fallback_title_template || '');
+ setFallbackDescriptionTemplate(custom.fallback_description_template || '');
setChannelLogoUrl(custom.channel_logo_url || '');
setProgramPosterUrl(custom.program_poster_url || '');
@@ -872,6 +891,53 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => {
}}
/>
+ {/* Fallback Templates */}
+
+
+
+ When patterns don't match the channel/stream name, use these custom
+ fallback templates instead of the default placeholder messages.
+ Leave empty to use the built-in humorous fallback descriptions.
+
+
+ {
+ const value = e.target.value;
+ setFallbackTitleTemplate(value);
+ form.setFieldValue(
+ 'custom_properties.fallback_title_template',
+ value
+ );
+ }}
+ />
+
+