Log in

View Full Version : Presenting VapourSynth.nim


josemaria.alkala
28th March 2020, 15:26
I have wrapped VapourSynth in Nim programming language. Nim is to C like CoffeScript is to JavaScript.

The main advantages I see are:

1. Writting a pipeline is very easy. It feels even easier than python. For instance, reading the first 100 frames, transposing and piping to stdout:

import vapoursynth
Source("video.mkv")[0..100].Transpose.Pipey4m

2. The code is compiled, so you end up with a binary file.
3. It should be as fast as C. (But I haven't performed any serious benchmark yet)
4. You can even perform filters like in C. mycrop.nim (https://github.com/mantielero/VapourSynth.nim/blob/a022a045694c2bf1e93d821ff87f6a0a8916f098/test/mycrop.nim) is an implementation similar (not complete) to vapoursynth's simplefilter.c version (https://github.com/vapoursynth/vapoursynth/blob/R48/src/core/simplefilters.c#L251).
5. Nim has metaprogramming superpowers. I have created a macro to make easier the development of filters. This way, the creation of a filter becomes:

import ../vapoursynth
import mymacro
import options
import strformat

newFilter("MyCropRel"):
parameters:
inClip clip # Input video (mandatory)
optional:
left Natural
right Natural
top Natural
bottom Natural

validation:
# We assign the destination's frame size
data.width = data.vi.width.Natural - data.left - data.right
data.height = data.vi.height.Natural - data.top - data.bottom

# Avoid too much cropping
assert( (data.top + data.bottom) < data.vi.height, "vertical cropping too big" )
assert( (data.left + data.right) < data.vi.width, "horizontal cropping too big" )

# make sure the given values will work with subSampling
assert( (data.left mod divHorizontal) == 0, &"subSampling: \"left\"={data.left} not divisible by {divHorizontal}" )
assert( (data.top mod divVertical) == 0, &"subSampling: \"top\"={data.top} not divisible by {divVertical}" )
assert( (data.width mod divHorizontal) == 0, &"subSampling: new \"width\"={data.width} not divisible by {divHorizontal}" )
assert( (data.height mod divVertical) == 0, &"subSampling: new \"height\"={data.height} not divisible by {divVertical}")

if (data.left == 0 and data.right == 0 and data.top == 0 and data.bottom == 0):
passTrough()

processing:
let dst = src.newVideoFrame(data.width, data.height)
#echo "Frame: ", n
for i in 0..<srcNumPlanes:
var srcPlane = src.getPlane(i)
var dstPlane = dst.getPlane(i)
srcPlane.goto(row=data.top, col=data.left)

if data.height > 0:
srcPlane.copy(dstPlane, rows=data.height, cols=data.width)

As you can see it has three areas:
- parameters: The function parameters, where left, right, top, bottom has Natural type (>=0)
- validation: contains all the checks needed for the inputs
- processing: which is pretty human friendly code. Creates a new frame, iterates over the planes, goes to a position in the plane and copiesss a number of rows up to certain column. It does so bearing in mind the subsampling.

This is about 40 lines for a working filter. You can find it here (https://github.com/mantielero/VapourSynth.nim/blob/447f3cda3185cacca593f760d38c298fea1f6b2e/src/filters/croprel.nim).

In order to use this filter you would do:

import ../vapoursynth
import options
import croprel

# Reads the file, applies the Simple filter and saves the result in a file
Source("../../test/2sec.mkv").MyCropRel(top=some(150.Natural),bottom=some(150.Natural)).Savey4m("original.y4m")


How much time does it take to compile above's code? Less than two seconds

$ time nim c modifyframe
real 0m1,284s
user 0m1,361s
sys 0m0,136s


How much time does it take to perform the cropping to the two seconds video?

$ time ./modifyframe
real 0m0,176s
user 0m0,141s
sys 0m0,038s


How much time does it take the original filter in pure C?

$ time ./modifyframe2
real 0m0,186s
user 0m0,148s
sys 0m0,037s

So pretty similar.

I hope you find it useful. There is lot of work to do ahead nonetheless. I am not a pro-developer, so my code is pretty ugly. Bear in mind that this has been my toy project to learn Nim.

feisty2
28th March 2020, 16:11
interesting, is there an example that actually applies some filtering, like this one (https://github.com/IFeelBloated/vsFilterScript/blob/master/GaussBlur.hxx#L23)?

josemaria.alkala
28th March 2020, 20:56
I have done one with a naive implementation. It is not fast, but there is a lot of margin for improvement.

The filter would look like this in its current state:

import ../vapoursynth
import mymacro
import options
import strformat

newFilter("DrawFrame"):
parameters:
inClip clip # Input video (mandatory)

validation:
# We assign the destination's frame size
data.width = data.vi.width.Natural
data.height = data.vi.height.Natural

processing:
let dst = src.newVideoFrame(data.width, data.height)
for i in 0..<srcNumPlanes:
var srcPlane = src.getPlane(i)
var dstPlane = dst.getPlane(i)
for row in 0..<srcPlane.height:
for col in 0..<srcPlane.width:
var value:int = ( srcPlane.get(row-1,col-1) +
srcPlane.get(row-1,col) * 2 +
srcPlane.get(row-1,col+1) +
srcPlane.get(row,col-1) * 2 +
srcPlane.get(row,col) * 4 +
srcPlane.get(row,col+1) * 2 +
srcPlane.get(row+1,col-1) +
srcPlane.get(row+1,col) * 2 +
srcPlane.get(row+1,col+1) ).int
dstPlane.set(row, col , (value / 16).uint8)


With a bit of metaprogramming could be done easier to the eyes and much faster.

josemaria.alkala
28th March 2020, 21:04
I have just checked that compiling it like:

nim c -d:release -d:danger modifyframe

It takes right now:

$ time modifyframe
real 0m1,624s
user 0m1,393s
sys 0m0,091s