- 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."
Layered on top of the prior perf commits, removes additional per-row queries
Channel list (apps/channels): add auto_created_by to select_related so ChannelSerializer.get_auto_created_by_name does not fire one SELECT per row. Replace instance.streams.all().order_by(channelstream__order) in to_representation with a read off the prefetched channelstream_set.
M3U account list (apps/m3u): prefetch_related "filters" on the viewset queryset and sort filters in Python over the prefetch in M3UAccountSerializer.get_filters.
Frontend (EditableCell.jsx): align the saveValue useCallback dependency from [row.original.id] to [row.original] in the Number, Group, EPG, and Logo cells, matching the pattern applied to the Text cell.