Build absolute poster proxy URLs via build_absolute_uri_with_port so
program detail images resolve correctly in dev and behind reverse
proxies. Backfill sd_icon for programs when poster fetch is enabled
but a delta refresh skips program metadata. Add a dev-mode frontend
fallback for relative image paths and correct the poster caching
description in the SD settings UI.
Add content_advisory, content_ratings, event_details, runtime to
ProgramDetailSerializer. Display content advisory, sports venue/teams,
and first-aired date in ProgramDetailModal.
- Rename api_key to password (RemoveField + AddField) for clarity and reusability
- Move sd_changes_remaining/sd_changes_reset_at to custom_properties JSONField
instead of nullable columns on all EPGSource rows
- Replace sd_program_md5 column on ProgramData with SDProgramMD5 relation table
keyed by epg_source + program_id, fixing the key mismatch bug in program
MD5 delta detection
- Fix total_changed counter: .items() -> .values() so len() returns date count
not tuple length
- Add missing SD lineup management endpoints to api_views.py
(sd_lineups GET/POST/DELETE, sd_lineups_search POST)
- Regenerate migration 0023_schedules_direct using Django to ensure correct
index names and schema
- Add EPGSource username field and SDScheduleMD5 model for MD5 delta sync
- Implement full Schedules Direct fetch with SHA1 auth, token caching,
20-day schedule window, and MD5-based delta sync to minimize API usage
- Add lineup manager UI allowing users to search and manage SD lineups
- Add stations-only fetch on source creation for immediate EPG matching
- Enforce 2-hour minimum refresh interval to respect SD rate limits,
with first-fetch bypass when no prior full refresh exists
- Add real-time progress updates via synchronous Redis WebSocket delivery,
bypassing gevent.spawn for reliable delivery from Celery prefork workers
- Refactor EPGsTable status cell into standalone EPGStatusCell component
with direct Zustand subscription for live progress bar updates
- Guard XMLTV parse trigger for channels linked to SD EPG sources
- Add 15 backend tests covering auth, credentials, signals, and serializers
Migrations: 0023 (username field), 0024 (api_key help_text), 0025 (SD fields + SDScheduleMD5)
- Fix test user: set user_level=1 (STANDARD), REMOTE_ADDR="127.0.0.1" to
satisfy IsStandardUser + network_access_allowed checks; move user creation
to setUpTestData (1 DB write per class instead of per test)
- Fix parenthesis parser: group_result was computed then silently discarded;
rewrite parse_expression to recursively compose Q objects correctly so
queries like "(Newcastle OR NEW) AND (Villa OR AST)" work as documented
- Fix field selection performance: resolve allowed fields before serialization
and pass as context; get_channels/get_streams short-circuit when not requested
- Move import re to top of file; remove mid-file alias regex_module
- Convert ProgramSearchAPIView standalone APIView into @action(detail=False)
on ProgramViewSet so the router registers programs/search/ before
programs/{pk}/ automatically, eliminating the URL ordering dependency
- Scope permission to the action directly via permission_classes=[IsStandardUser]
rather than adding "search" to the global permission_classes_by_action dict
- Add test_title_parenthetical_grouping to cover the fixed parser path
- Tighten existing test assertions (case-insensitive, whole-word, description
AND, page_size cap) to catch regressions rather than passing vacuously
Co-authored-by: Copilot <copilot@github.com>
Replace DRF ModelSerializer with .values() + plain dict construction in
EPGGridAPIView, eliminating per-row serializer instantiation, field
object creation, and OrderedDict overhead across ~12k programs.
Pre-compute season/episode at import time instead of on every API request:
- Extract S/E from onscreen episode-num inline in extract_custom_properties()
immediately after storing the raw value, rather than in a separate pass
- Add missing description-based S/E fallback to parse_programs_for_tvg_id(),
bringing it in line with parse_programs_for_source()
Remove runtime extract_season_episode() call from ProgramDataSerializer and
EPGGridAPIView — values are now guaranteed to be in custom_properties after
import, so season/episode resolution is a plain dict lookup at request time.
Also remove unnecessary select_related("epg") JOIN and .count() query from
the grid endpoint.
Replace expanded inline cards with a ProgramDetailModal for viewing full program
details. Add real-time progress bars to currently-airing programs using direct DOM manipulation. Extract and display season/episode info from EPG data with a 3-tier fallback (custom_properties → onscreen_episode → description text).
Backend:
- Add ProgramDetailSerializer for rich EPG data (categories, credits, ratings, images, external IDs) served on the detail endpoint
- Add infer_is_live() for status badges (live, new, premiere, finale)
- Add shared S/E extraction utilities in apps/epg/utils.py
- Optimize querysets with select_related("epg")
Frontend:
- ProgramDetailModal with conditional field rendering and image fallback chain
- Progress bars via scaleX transform updated every second
- Parallel data fetching with Promise.all for channels + programs
- Browser tab visibility recovery for progress bars
- Now-line with triangle marker and sub-millisecond precision
- Card layout: title, season/episode + subtitle, time + badges
- Shared formatSeasonEpisode() and external URL helpers
Add is_new, is_live, is_premiere, and is_finale fields to the EPG program API and render them as colored badges in the TV guide.
- Compact card: S/E, LIVE, and NEW badges on the title row
- Expanded card: all badges (S/E, LIVE, NEW, PREMIERE, FINALE) on the subtitle row, now moved directly after title for consistency
- PREMIERE/FINALE detected from premiere_text (case-insensitive)
- PREMIERE/FINALE only shown in expanded view to conserve space
- Badge order: S/E → LIVE → NEW → PREMIERE → FINALE
- Consolidate serializer season/episode parsing into a single to_representation
pass: one custom_properties read, regex runs at most once per program
- Compact card: S/E badge inline with title; secondary row shows sub_title with
description fallback
- Expanded card: sub_title and S/E badge share a row above the description
- Fix secondary text color for legibility on dark backgrounds (#a0aec0 → #e2e8f0)
- Fix expanded card overflow (height:100% → flex:1 1 0) so action buttons stay visible
- Bump expanded card height 180 → 200px, description max-height 80 → 100px
Display sub_title (episode name) and season/episode badge on guide program cards. Extract season/episode from custom_properties with fallback parsing of onscreen_episode format (e.g. "S12 E6").
- Add season/episode SerializerMethodFields to ProgramDataSerializer
- Parse onscreen_episode string as fallback when xmltv_ns data absent
- Display sub_title with S/E badge on guide cards
- Show description for programs without sub_title (movies)
- Add formatSeasonEpisode utility with full test coverage
- Add 15 backend serializer tests and 12 frontend utility tests
Fulfills #1065
Add cron expression support for M3U and EPG refreshes with interactive
builder modal and quick reference examples. Refactor backup scheduling
to use shared ScheduleInput component for consistency.
- New CronBuilder modal with preset buttons and custom field editors
- Info popover with common cron expression examples
- Shared ScheduleInput component for interval/cron toggle
This enhancement introduces a powerful custom dummy EPG system that allows users to generate EPG programs on-demand by parsing channel or stream names using configurable regex patterns.
Key Features:
- Custom Pattern Matching: Define regex patterns to extract information from channel/stream names (teams, leagues, times, dates, etc.)
- Flexible Name Source: Choose to parse either the channel name or a specific stream name (by index)
- Timezone-Aware Scheduling: Automatic DST handling using pytz timezone names (e.g., 'US/Eastern', 'Europe/London')
- Time Format Support: Parse both 12-hour (AM/PM) and 24-hour time formats
- Date Parsing: Extract dates from names with flexible month/day/year patterns
- Custom Templates: Format EPG titles and descriptions using captured groups with {placeholder} syntax
- Upcoming/Ended Customization: Define custom titles and descriptions for programs before and after scheduled events
- Live Preview: Test patterns and templates in real-time with sample input
- Smart Program Generation: Automatically creates "Upcoming" and "Ended" programs around scheduled events
Use Cases:
- Sports channels with event details in stream names (e.g., "NHL 01: Bruins VS Leafs @ 8:00PM ET")
- Movie channels with genre/title/year information
- Racing events with driver/track/series details
- Any scenario where EPG data is embedded in channel/stream naming conventions
Technical Implementation:
- Backend: Pattern matching engine with timezone conversion and program scheduling logic
- Frontend: Interactive form with validation, pattern testing, and visual group preview
- Name Source Options: Parse from channel name or selectable stream index (1-based)
- Fallback Behavior: Uses standard dummy EPG if patterns don't match
- Custom Properties: Stores all configuration in EPGSource.custom_properties JSON field
Configuration Options:
- Title Pattern: Extract primary information (required)
- Time Pattern: Extract hour/minute/AM-PM (optional)
- Date Pattern: Extract month/day/year (optional)
- Timezone: Event timezone with automatic DST support
- Program Duration: Length of generated programs in minutes
- Title Template: Format EPG title using captured groups
- Description Template: Format EPG description using captured groups
- Upcoming Title Template: Custom title for programs before event starts (optional)
- Upcoming Description Template: Custom description for programs before event starts (optional)
- Ended Title Template: Custom title for programs after event ends (optional)
- Ended Description Template: Custom description for programs after event ends (optional)
- Name Source: Channel name or stream name
- Stream Index: Which stream to use when parsing stream names (1, 2, 3, etc.)
Closes#293