program_opencl inherits its output hw_frames_ctx from the input link.
When the input came via hwmap:mode=read, the inherited context creates
CL_MEM_READ_ONLY images for output - kernel writes are silently
discarded, producing black frames ('kein video').
Fix: download to CPU, then hwupload=derive_device=opencl to create a
fresh writable OpenCL context before program_opencl. Matches Jellyfin's
existing iHD doOclTonemap pattern (GetVaapiVidFiltersPrefered:5711).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hwdownload+hwupload produced output with no video. Switch back to
hwmap reverse=1 to VAAPI (then hwmap to QSV). Now that scale_opencl is
gone and the shader processes NV12 natively, the OpenCL frames remain
in the VAAPI-linked pool so reverse=1 is viable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OpenCL 1.x fract() requires 2 arguments; the single-argument form
is OpenCL 2.0+. Replace all usages with crt_fract(x) = x - floor(x).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scale_opencl does not support rgba output in this jellyfin-ffmpeg build.
Rewrite the OpenCL kernel to accept and emit NV12 planes directly
(src_y, src_uv, dst_y, dst_uv) doing YCbCr↔RGB conversion internally.
Remove the scale_opencl=format=rgba and scale_opencl=format=nv12
wrappers from GetCrtShaderOclFilters — program_opencl alone is enough.
VAAPI decoder path: hwdownload+hwupload to QSV (safe; program_opencl
creates new output frames without a VAAPI reverse link).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All previous hwmap changes were based on diagnosing exit code 8 as a
mapping failure, but the real cause was always build_opts. Now that
build_opts is removed, use the same VAAPI→OpenCL→reverse(VAAPI)→QSV
pattern that Jellyfin already uses for the doOclTonemap+isVaInVaOut
path instead of the unnecessary QSV intermediate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
program_opencl in this jellyfin-ffmpeg version does not have a build_opts
option. This caused FFmpeg to exit with code 8 before even running.
The shader already has #ifndef SHADOW_MASK / #define SHADOW_MASK 2 / #endif
as defaults, so removing build_opts keeps the same Aperture Grille mask.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hwmap reverse=1 only works back to the device the OpenCL context was
derived from. When using a VAAPI decoder, go VAAPI→QSV first (zero-copy
on Intel, they share the same libva surfaces), so the OpenCL context is
then QSV-derived and reverse=1 back to QSV works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hwmap=derive_device=vaapi:mode=write:reverse=1 after scale_opencl fails
because scale_opencl creates a new OpenCL surface that has no association
with the original VAAPI surface. reverse=1 requires an existing mapping.
On Intel, VAAPI and QSV share the same libva surfaces, so mapping the
post-CRT OpenCL frame directly to QSV (mode=write:reverse=1) works the
same way it does in the doOclTonemap path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous (wrong) change encoded options as streamOptions[key]=value.
ParseStreamOptions reads IQueryCollection verbatim, so the key would be
stored as "streamOptions[crtShader]" instead of "crtShader", breaking
TryGetValue("crtShader"). Restore the original plain format so the
server-side lookup works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SDR case (QSV decoder + QSV encoder, no OCL tonemap) was never
handled — the frame stayed in QSV VRAM with no OpenCL round-trip,
so the CRT filter was simply never applied.
Add else-if (isQsvDecoder) branch that maps QSV→OpenCL, applies the
CRT kernel, then reverse-maps back to QSV.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inject ILogger<EncodingHelper> and log all conditions individually
so we can see exactly which check is failing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SupportsFilter("program_opencl") always returned false because
program_opencl was missing from the _requiredFilters list in
EncoderValidator. GetFFmpegFilters() only reports filters that are
in that whitelist, so IsCrtShaderEnabled() was silently always false.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Insert VAAPI→OpenCL→CRT→VAAPI round-trip (isVaapiDecoder path) and
inline OCL filters (doOclTonemap path) into GetIntelQsvVaapiVidFiltersPrefered
so the CRT Lottes shader is applied when the h264_qsv encoder is active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IsCrtShaderEnabled now guards on SupportsHwaccel("opencl"),
SupportsFilter("program_opencl") and SupportsFilter("scale_opencl")
so the filter is silently skipped instead of causing an FFmpeg error
when OpenCL is unavailable
- Drop w=iw:h=ih from scale_opencl (iw/ih are not valid expressions in
the OpenCL scale filter; omitting them defaults to input dimensions)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FFmpeg suppresses progress output (fps, time, bitrate) when loglevel is set to error
- Add -stats flag to force progress updates regardless of log level
- Fixes dashboard not showing transcode info (codec, progress, etc.)
- Add FfmpegLogLevel property to EncodingOptions (default: 'error')
- Use configuration value in GetInputModifier instead of hardcoded value
- Allows changing log verbosity without rebuild via encoding.xml
- Possible values: quiet, panic, fatal, error, warning, info, verbose, debug, trace
- Set FFmpeg loglevel to 'error' to suppress repetitive warnings like 'Skipping NAL unit'
- Add client IP and User-Agent to exception middleware logging for better debugging
Implement ForceRemoteSourceTranscoding permission check that disables
direct play and direct stream for remote media sources, forcing transcoding
when the user has this permission enabled.
HTTP/HTTPS streams are now considered remote unless explicitly localhost,
rather than relying on local network detection. This ensures proper handling
of streams that appear to be in the local network but should be treated as remote sources.