Issue: When `process_movie_batch` / `process_series_batch`
(apps/vod/tasks.py) creates duplicate vod_movie / vod_episode records
during a refresh, existing M3U*Relation rows get repointed to the new
records. The old UUIDs that external players (Emby / Jellyfin /
ChannelsDVR) cached in `.strm` URLs are left orphaned, and the proxy
404s on every subsequent request — even though the same request
already carries a stable `stream_id` that uniquely identifies a live
relation.
This patch makes `_get_content_and_relation` use a soft UUID lookup
and, if it misses, fall back to resolving content via
`M3UMovieRelation.stream_id` / `M3UEpisodeRelation.stream_id`. The
fallback respects the strictest match first: when
`preferred_m3u_account_id` is also present, that account is queried
first; otherwise the highest-priority active relation matching the
stream_id wins (same ordering the existing relation-selection logic
uses).
This is read-side graceful degradation, complementary to the
write-side root fix proposed in closed#973 (re-use the relation's
existing movie/series during refresh rather than rematching by TMDB
ID). The two PRs together would fully resolve#961: #973 prevents new
orphaning, this PR makes any URL that ships a stream_id survive past
orphaning. URLs without a stream_id (e.g. XC-compat
`/movie/<user>/<pass>/<id>.mkv`) are NOT covered by this patch — they
need the root fix.
Series-UUID lookups (`/proxy/vod/series/<UUID>`) are left UUID-only by
design — players cache episode and movie URLs, not series URLs. Same
pattern can be added as a follow-up if needed.
Tests: 7 new mock-based regression tests covering both branches,
verified against `:dev`. UUID-first happy path is unchanged
(M3U*Relation table never queried when the UUID resolves).
Related: #961 (open), closed#973 (root fix abandoned for inactivity).
- Replace per-entry ProgramData DB query with single batch fetch
- Pass pre-loaded EPGData objects to find_current_program_for_tvg_id
to avoid redundant select_related re-fetch
- Delete Redis programme index before XMLTV download so stale byte
offsets are never used during the refresh window
When selecting an EPG channel in the channel form, if that EPG entry's
programs had never been parsed, the preview showed "No current program".
This was because parse_programs_for_tvg_id requires a linked channel.
- Add force parameter to parse_programs_for_tvg_id to bypass channel check
- Auto-trigger parsing from current-programs API when no programs exist
- Check Redis task lock to prevent duplicate parse tasks from concurrent requests
- Frontend retries up to 3 times with 10s delay to pick up parsed results
- Skip parsing for dummy EPG sources
- Extended CurrentProgramsAPIView to accept epg_data_ids parameter
- Added getCurrentProgramForEpg() API method in frontend
- Added UI in Channel form showing current program with tooltip
- Shows loading state, no program state, and program details
- Includes expandable description, progress bar with elapsed/remaining time
- Matches stats page 'Now Playing' feature behavior
- Use 'youtube_trailer' key (matching api_views.py and advanced refresh)
instead of orphaned 'trailer' key that was never consumed.
- On basic sync, only write director/actors/release_date to
custom_properties when the field is currently empty. This prevents
basic sync from overwriting richer data previously stored by
refresh_movie_advanced_data, while still populating those fields for
movies that have never had an advanced refresh run.
Tested: logic verified against all three scenarios (preserve existing
rich data, populate empty fields, youtube_trailer key propagation).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_vod_streams on XC providers supplies director, cast/actors and
release_date for each movie, but process_movie_batch was only persisting
trailer in custom_properties. As a result those fields were always empty
in Dispatcharr's own get_vod_info / provider-info responses even when
the upstream provider returned correct data.
Changes:
- Extract director, actors (also mapped from 'cast') and release_date
from movie_data during the batch-import phase and store them in
custom_properties alongside the existing trailer field.
- Fix the update path to merge incoming custom_properties into the
existing dict (using {**existing, **incoming}) rather than overwriting
it wholesale, so that detailed_info and other keys written by the
advanced refresh task are preserved across subsequent basic syncs.
Fixes#1228
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Channel.get_stream_profile() read self.stream_profile directly, bypassing
the override system. The override-aware property effective_stream_profile_obj
already existed and called _resolve_effective_fk('stream_profile') which
correctly picks override.stream_profile when set, but nothing in the
streaming path called it.
As a result, a per-channel 'Stream Profile' override saved through the UI
was visible in the channel edit form (yellow pencil indicator appeared) but
silently ignored at stream time — Dispatcharr always used the global default
profile regardless of what was configured per-channel.
Fix: call effective_stream_profile_obj in get_stream_profile() so that all
callers (live proxy url_utils, input manager, views) automatically pick up
channel-level overrides without any further changes.
Fixes#1268
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The provider-info endpoints for both series and movies always returned
data from the highest-priority relation, ignoring the provider the user
had selected in the dropdown. Switching sources in the UI produced no
change in the episode list or movie details.
Root cause: the endpoints had no support for a relation_id query param,
and the frontend never passed one when the user changed the selection.
Fix (backend):
- series provider-info: accept ?relation_id=<id> and query that specific
M3USeriesRelation; fall back to highest-priority when omitted
- movie provider-info: same treatment for M3UMovieRelation
Fix (frontend):
- api.getSeriesInfo / api.getMovieProviderInfo accept an optional
relationId argument and append it to the query string
- useVODStore.fetchSeriesInfo / fetchMovieDetailsFromProvider forward
the argument to the API layer
- SeriesModal.onChangeSelectedProvider re-fetches series info (episodes
included) with the newly selected relation's id
- VODModal.onChangeSelectedProvider re-fetches movie details with the
newly selected relation's id
Fixes#1250
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug Fix: The plugin detail endpoint called GPG via `subprocess.Popen` to verify per-plugin manifest signatures. Replaced with a `_gpg_run()` helper that uses `os.posix_spawn`, matching the pattern used by the ffmpeg and script-handler fixes.
- channel_status.py: fold output_format and output_profile_id into the
existing hmget, reducing per-client Redis calls from 3 to 1
- utils.py: collapse 2x and 3x hget per key in scan_iter loops
(get_user_active_connections) to single hmget calls
- views.py: replace 3x hget on channel metadata in the worker-join path
of stream_ts with a single hmget