- 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
gevent registers a pthread_atfork handler that never yields. Any
subprocess.Popen/run call in a uWSGI greenlet hangs indefinitely at
fork() before the child process even starts.
- dispatcharr/gevent_patch.py: new; monkey.patch_all() + psycogreen,
loaded via uWSGI import= in all four ini files
- live_proxy/input/manager.py: posix_spawn + _SpawnedProcess; removed
dead forwarder code
- live_proxy/input/http_streamer.py: O_NONBLOCK on relay pipe + EAGAIN
retry (blocking write to a full pipe stalls the gevent hub)
- live_proxy/utils.py: new posix_spawn_proc() helper with O_NONBLOCK
stdin, shared by both output managers
- live_proxy/output/fmp4/manager.py, output/profile/manager.py:
posix_spawn_proc(); _write_all() treats EAGAIN (None) as cooperative
select.select wait instead of fatal error
- core/views.py (stream_view): posix_spawn; fixed pre-existing bug
where return StreamingHttpResponse(...) was indented inside
stream_generator, making the success path always return None
- connect/handlers/script.py: _posix_run() with posix_spawn +
cooperative select reads + non-blocking waitpid; fixes deadlock when
a script integration fires on a uWSGI-context event (client_connect
fires in the live proxy, not Celery)
- Enable gevent cooperative multitasking in all uWSGI worker configs
(gevent-early-monkey-patch + import dispatcharr.gevent_patch)
- Rewrite WebSocket group sends to bypass asyncio in gevent workers:
_gevent_ws_send() replicates the channels_redis 4.x wire format
directly via synchronous Redis so send_websocket_update() and
_send_async() no longer fail silently after epoll is patched out
- Fix PostgreSQL connection exhaustion: CONN_MAX_AGE=0 + explicit
close_old_connections() in stream manager and cleanup watchdog loops
- Fix stream proxy race: register client before the connect-wait loop
so the cleanup watchdog never sees zero clients on a live channel
- Channel list/logo/profile queryset optimisations: conditional DISTINCT,
EXISTS semi-joins for filter-options, channel_count annotation to
eliminate N+1 in LogoSerializer, prefetched memberships in
ChannelProfileSerializer
- JsonResponse for channel ID list and summary endpoints
- Implemented HDHR output profile URL support for specific transcode profiles.
- Introduced a default output profile setting in Stream Settings.
- Updated API views to handle channel profiles and output profiles.
- Migrated preferred region and auto-import settings to system settings.
- Enhanced frontend forms to include output profile selection and descriptions.
trigger_event in apps/connect/utils.py iterates pm.list_plugins() and
on disabled plugins emits a debug log that accesses dict items as if
they were attributes:
logger.debug(f"Skipping disabled plugin id={plugin.key} name={plugin.name}")
Python evaluates f-string arguments eagerly even when the logger
discards the message at INFO level, so this raises AttributeError on
the first disabled plugin encountered. The exception bubbles out of
trigger_event with no try/except in the loop, aborting dispatch for
every plugin sorted after the disabled one.
Effect for users: any plugin subscribed to events via `events: [...]`
on an action silently never receives events whenever any
alphabetically-earlier plugin is disabled. Manual button actions still
work, masking the failure as a plugin bug rather than a dispatch one.
Fix: replace the two attribute accesses with dict access. Add a
regression test under apps/connect/tests/ that mocks PluginManager to
return [disabled, enabled-with-events], asserts the enabled plugin's
action is dispatched, and a sanity check that non-matching events
aren't dispatched. Verified the test fails against the original code
with the expected AttributeError and passes with the fix.
- Added support for customizable title and description matching modes in series rules.
- Implemented a preview feature for series rules to show matching upcoming programs.
- Created a new SeriesRuleEditorModal for editing series rules with improved UI.
- Refactored query parsing logic into reusable functions for better maintainability.
- Updated API to handle new parameters for series rule creation and evaluation.
- Enhanced the ProgramRecordingModal and SeriesRecordingModal to integrate the new rule editor.
- Added pagination for program search results in the API.
- Scope regex previews to the M3U account being edited
- Empty-state message extended from "No streams in this group yet." to "No streams in this group yet. Run an M3U refresh first to populate streams."