A high-performance VapourSynth port of DoViBaker's core functionality.
This plugin bakes Dolby Vision Profile 7 FEL streams into PQ12 output by processing Base Layer (BL), Enhancement Layer (EL), and RPU metadata.
- Cross-platform — native VapourSynth support (
macOS,Linux,Windows) - High performance — ~7× faster than the original (benchmarks)
- Improvements & Bug Fixes
Download a binary from Releases or build from source:
core.std.LoadPlugin("/path/to/libfelbaker.dylib") # .so on Linux, .dll on Windowscore.fel.Bake(bl, el, no_resample=0, dovi_metadata=0, non_parallel=0)| Parameter | Default | Description |
|---|---|---|
bl |
— | DoVi Base Layer clip |
el |
— | DoVi Enhancement Layer clip |
no_resample |
0 |
1 – disable internal resampling (BL/EL dimensions/chroma must match) |
dovi_metadata |
0 |
1 – attach DoVi frame properties |
non_parallel |
0 |
1 – force single-threaded mode |
Returns: RGB48 when no_resample=0 or BL/EL are 4:4:4; otherwise YUV420P12 (use ToRGB to finalize).
Converts YUV420P12 output from Bake(no_resample=1) to RGB48.
core.fel.ToRGB(baked, non_parallel=0)| Parameter | Default | Description |
|---|---|---|
baked |
— | Bake(no_resample=1) output |
non_parallel |
0 |
1 – force single-threaded mode |
Returns: RGB48
Using VapourSynth resizer (recommended — faster, more accurate)
import vapoursynth as vs
core = vs.core
core.std.LoadPlugin("/path/to/libffms2.dylib") # .so on Linux, .dll on Windows
core.std.LoadPlugin("/path/to/libfelbaker.dylib") # .so on Linux, .dll on Windows
bl = core.ffms2.Source("BL.hevc", threads=4)
el = core.ffms2.Source("EL.hevc", threads=4)
# Upscale EL to match BL resolution (e.g., 4K)
el_4k = core.resize.Spline16(el, width=3840, height=2160)
# Bake without internal resampling
baked = core.fel.Bake(bl, el_4k, no_resample=1)
# Convert to 4:4:4 before final RGB conversion
baked444 = core.resize.Spline16(baked, format=vs.YUV444P12)
core.fel.ToRGB(baked444).set_output()Using simplified Spline16 resampler (DoViBaker-compatible)
import vapoursynth as vs
core = vs.core
core.std.LoadPlugin("/path/to/libffms2.dylib") # .so on Linux, .dll on Windows
core.std.LoadPlugin("/path/to/libfelbaker.dylib") # .so on Linux, .dll on Windows
bl = core.ffms2.Source("BL.hevc", threads=4)
el = core.ffms2.Source("EL.hevc", threads=4)
core.fel.Bake(bl, el).set_output()Encoding to ProRes via FFmpeg
vspipe bake.vpy - | ffmpeg -f rawvideo -pixel_format gbrp16le \
-colorspace rgb -color_primaries bt2020 -color_trc smpte2084 -color_range pc \
-field_order progressive -video_size 3840x2160 -r 24000/1001 -i - \
-c:v prores_videotoolbox -profile:v 4 \
-colorspace bt2020nc -color_primaries bt2020 -color_trc smpte2084 \
-an output.mov⚙️ Adjust
-video_sizeand-rto match the source.
| Property | Value |
|---|---|
_Matrix |
0 (RGB) |
_ColorRange |
0 (Full Range) |
_SceneChangePrev |
1 for first frame in a scene |
DoVi metadata (dovi_metadata=1)
_dovi_dynamic_min_pq_dovi_dynamic_max_pq_dovi_dynamic_max_content_light_level_dovi_static_master_display_max_luminance_dovi_static_master_display_min_luminance_dovi_static_max_content_light_level_dovi_static_max_avg_content_light_level_dovi_static_max_pq
💡 Non-zero only when present in RPU
- Intermediate bit-depth — correctly clamped to 12-bit (spec-compliant) instead of original 16-bit
- Color range — RGB output
_ColorRangeis now always0(Full Range) - MMR mapping — fixed bug where rightmost luma pixels were ignored during chroma mapping
🛠️️ To revert to original behavior, uncomment
// COMPATblocks.
Results reflect the best of 10 runs, with negligible variance between mean and peak performance.
| Platform | Plugin | Resizer | Raw Output | FFmpeg Null |
|---|---|---|---|---|
| Windows x64 (AVX2) | DoViBaker | Internal | 48.2s | 49.9s |
| Windows x64 (AVX2) | FelBaker | Internal | 6.6s | 7.8s |
| Windows x64 (AVX2) | FelBaker | VapourSynth | 6.2s | 7.5s |
| macOS ARM64 (M4) | FelBaker | Internal | 2.5s | 3.6s |
| macOS ARM64 (M4) | FelBaker | VapourSynth | 2.5s | 3.6s |
🚀 Performance appears RAM-bandwidth limited rather than CPU-bound. When encoding to ProRes or similar, the speed difference should be less pronounced as encoding becomes the bottleneck.
Test systems & methodology
- Windows 11 (x64) – AMD Ryzen 7 5875U (AVX2)
- macOS 26 (ARM64) – Mac Mini M4 (Base)
Frame Source: ffms2 with threads=8 (performance stabilized at threads=4+ on both systems).
DoViBaker v0.4.5.1 (AviSynth+ 3.7.5):
LoadPlugin("ffms2.dll")
LoadPlugin("DoViBaker_x64.dll")
bl=FFVideoSource("samples/BL-150.hevc", threads=8)
el=FFVideoSource("samples/EL-150.hevc", threads=8)
DoViBaker(bl,el)
time ./avs2pipemod64.exe -rawvideo bake.avs > /dev/null # Raw output
time ./ffmpeg.exe -i bake.avs -f null - # FFmpeg NullFelBaker (VapourSynth R73):
# Internal resizer
import vapoursynth as vs
core = vs.core
core.std.LoadPlugin('ffms2.dll') # macOS: libffms2.dylib
core.std.LoadPlugin('felbaker.dll') # macOS: libfelbaker.dylib
bl = core.ffms2.Source("samples/BL-150.hevc", threads=8)
el = core.ffms2.Source("samples/EL-150.hevc", threads=8)
core.fel.Bake(bl, el, no_resample=0, non_parallel=0, dovi_metadata=1).set_output()# VapourSynth resizer
import vapoursynth as vs
core = vs.core
core.std.LoadPlugin('ffms2.dll') # macOS: libffms2.dylib
core.std.LoadPlugin('felbaker.dll') # macOS: libfelbaker.dylib
bl = core.ffms2.Source("samples/BL-150.hevc", threads=8)
el = core.ffms2.Source("samples/EL-150.hevc", threads=8)
el_4k = core.resize.Spline16(el, width=3840, height=2160, format=vs.YUV420P10)
baked = core.fel.Bake(bl, el_4k, no_resample=1, non_parallel=0, dovi_metadata=1)
bl_4k = core.resize.Spline16(baked, width=3840, height=2160, format=vs.YUV444P12)
core.fel.ToRGB(bl_4k, non_parallel=0).set_output()# Raw output
time vspipe bake.vpy - > /dev/null
# FFmpeg Null
time vspipe bake.vpy - | ffmpeg -f rawvideo -pixel_format gbrp16le \
-colorspace rgb -color_primaries bt2020 -color_trc smpte2084 -color_range pc \
-field_order progressive -video_size 3840x2160 -r 24000/1001 -i - -f null -- Python 3.12+
- Meson 1.10+ (older versions should work but are untested)
- Ninja
- Rust (with
cargoandcargo-c)
💡 For build configuration examples, see
.github/workflows/build-release.yml.
meson setup <buildDir>
meson compile -C <buildDir>🚀 FelBaker was developed and optimized using
ClangonmacOS; To maintain peak performance,Clangis highly recommended on all platforms — benchmarks show thatMSVCbuilds may be over 50% slower.
Configure via meson setup <buildDir> -D<option>=<value>.
| Option | Values | Description |
|---|---|---|
cpu_tier |
generic (default)maxnative |
generic – M1 / AVX2 / v8.2max – M4 / AVX-512 / v9.2native – auto-detect |
tests |
truefalse (default) |
Enable and build tests |
install_plugin |
truefalse (default) |
Install the VapourSynth plugin |
win_deterministic |
truefalse (default) |
Enable deterministic Windows builds |
📋 See
meson.optionsfor all available options.
To fully clean the build (including Rust artifacts):
meson compile -C <buildDir> cargo-clean
meson compile -C <buildDir> --cleanFEL baking integrity is validated via SHA-256 checksums of the RGB output.
⚠️ test/samplesare heavily compressed and serve only to validate logic, not visual quality.
meson setup <buildDir> -Dtests=true
meson test -C <buildDir>🔍 CLion Integration: Tests are detected as
pytestcases. Since they depend on the plugin binary, a Meson compile pre-run step should be added to ensure the plugin is rebuilt before execution.
Test cases may be defined in test/tests.toml. Available parameters are documented within that file.
After adding or removing a test case, test/test_integration_generated.py must be regenerated:
meson compile -C <buildDir> refresh-tests
⚠️ The[avisynth]test case (disabled by default) requires uncommenting// COMPATblocks to pass, as it validates against the original DoViBaker checksums.
- DoViBaker by erazortt
- dovi_tool by quietvoid
- VapourSynth by myrsloik