scenechange
Scene change detection plugin for VapourSynth.
The project builds two VapourSynth plugins:
scenechange(namespacescd) detects scene changes and attaches_SceneChangePrevand_SceneChangeNextframe properties (0 or 1) so downstream temporal filters can avoid bleeding across cuts.temporalsoften2(namespacefocus2) is a temporal-averaging denoiser that honours those scene change properties.
The vapoursynth-scenechange Python package bundles both compiled plugins together with a small
wrapper (scenechange.TemporalSoften) that stitches them into a single function call.
Building and installing
Requirements:
- A C99 compiler (GCC, Clang, or MSVC).
- Meson 1.3 or newer, and Ninja.
- VapourSynth R55 or newer (API 4) headers. These are found via
vapoursynth.pc, or via thevapoursynthPython package (vapoursynth.get_include()), and are otherwise downloaded at configure time. The plugins useVapourSynth4.hand are not compatible with the legacy API 3.
Configure, build, and install:
meson setup build
meson compile -C build
meson install -C build
By default the plugins install to <libdir>/vapoursynth. Override the location with
-Dplugins_dir=/path/to/plugins at configure time. Other options are -Dtests=enabled (build the
cmocka test suite), -Dforce_generic=true (use the scalar kernel on SIMD-capable hosts),
-Ddocs=true (build the Doxygen API documentation), and -Dfetch_vapoursynth_headers=false
(never download VapourSynth4.h).
Python package
Prebuilt wheels that bundle both plugins are published to PyPI, so most users do not need to build anything from source:
pip install vapoursynth-scenechange
Following the VapourSynth packaging conventions, the
wheel installs the compiled plugins into <site-packages>/vapoursynth/plugins, which VapourSynth
autoloads. Nothing needs to be imported or loaded to make scd and focus2 available. The
scenechange Python module is installed alongside them and declares vapoursynth as a dependency.
Building the plugins from source with Meson (above) is only needed for development or for platforms
without a published wheel. See
Python wrapper for usage.
Usage
scd.Detect
clip = core.scd.Detect(clip, thresh, interval_h, interval_v, log)
Detect scene changes and attach _SceneChangePrev and _SceneChangeNext properties to the clip.
thresh: threshold for the average of luma differences between the previous and next frames. When the average exceeds this value the frame is judged a scene change. Range is 1 to254 * 2 ^ (bitdepth - 8); the default (or any out-of-range value) is15 * 2 ^ (bitdepth - 8).interval_h: horizontal interval, in pixels, used when measuring differences. Range is 1 to the clip width; the default is auto-adjusted.interval_v: vertical interval, in pixels, used when measuring differences. Range is 1 to the clip height; the default is auto-adjusted.log: optional path of a log file. When set, the frame numbers identified as scene changes are written as text. An absolute path is recommended. Unset by default.
scd.ApplyLog
clip = core.scd.ApplyLog(clip, log)
Apply _SceneChangePrev and _SceneChangeNext properties to the clip from a log previously
produced by scd.Detect.
Supported colour families are GRAY (8-bit and 16-bit) and YUV (8-bit, 9-bit, 10-bit, and 16-bit).
focus2.TemporalSoften2
clip = core.focus2.TemporalSoften2(
clip, radius, luma_threshold, chroma_threshold, scenechange, mode
)
radius: 1 to 7. Default is 4.luma_threshold: 0 to 255. For RGB clips this value applies to all planes. Default is 4.chroma_threshold: 0 to 255. Ignored for RGB and Gray clips. Default is 8.scenechange: when 0 the_SceneChange*frame properties are ignored. Default is 1.mode: 2.
YUV or Gray example. The plugins autoload from the VapourSynth plugins directory, so
core.std.LoadPlugin is only needed when they were installed somewhere else:
import vapoursynth as vs
core = vs.core
clip = core.scd.Detect(clip, thresh=20)
clip = core.focus2.TemporalSoften2(clip)
RGB example (scene detection requires GRAY8, so the properties must be copied back):
def copy_sc(n, f):
fout = f[0].copy()
fout.props._SceneChange = f[1].props._SceneChange[0]
return fout
tmp = core.resize.Point(clip, format=vs.GRAY8)
tmp = core.scd.Detect(tmp, thresh=20)
clip = core.std.ModifyFrame([clip, tmp], copy_sc)
clip = core.focus2.TemporalSoften2(clip)
Python wrapper
The TemporalSoften class in the scenechange package collapses the boilerplate above into a
single call and handles the RGB property-copy step automatically:
import vapoursynth as vs
from scenechange import TemporalSoften
core = vs.core
clip = TemporalSoften(core).soften(clip, luma_threshold=4)
Credits
Original plugins by Oka Motofumi (chikuzen.mo at gmail dot com).