Advanced Scripting Tips
Contents |
An example of using recursion
messageclip(string(factorial(5))) function factorial(int n) { n<2?1:factorial(n-1)*n }
How this works: let's use factorial(2) as an example. First we test
n<2?
and in this case, it's not, so we go on to the next part:
factorial(n-1)*n
in which case we call factorial again, but with n-1. In this case, our "current" n is 2, so we call factorial(1). We do the test again:
n<2?1
and therefore return 1 as our answer. Now, go back to our "current" discussion, we get factorial(n-1)=1. So we find factorial(n-1)*2 or 1*2 and return the answer.
Here is an example recursive function that works with video.
stackverticaln(colorbars(height=240),2) function stackverticaln(clip v, int n) { n<2?v:stackvertical(v,stackverticaln(v,n-1)) }
In this example we follow the same pattern: we return the argument unchanged if n<2, but perform an operation involving "current" and the n-1 version of our function.
How to think about recursion
In the second example, you can think of what it's doing this way:
#n=1 v #n=2 stackvertical(last,v) #n=3 stackvertical(last,v)
You can also think of it this way:
#n=3 stackvertical(v,stackvertical(v,v))
There is in fact a way to generalize this:
#Shows how a for-next loop can be emulated with generalrecursion demo=0 #try also demo 1,2 select (demo, stackverticaln(colorbars(height=240),2), \ messageclip(string(factorial(5))), \ messageclip(string(sumofn(5))) \ ) function generalrecursion(val start, val current, int n, string func) { s="n<2?start:"+func+"("+current+",generalrecursion(start,current,n-1,func))" #return messageclip(s) eval(s) } function mult(int x, int y) { x*y } function add(int x, int y) { x+y } function factorial(n) { generalrecursion(1,"n",n,"mult") } function sumofn(n) { generalrecursion(1,"n",n,"add") } function stackverticaln(clip v, int n) { generalrecursion(v,"start",n,"stackvertical") }
Introducting GScript
There is a plugin called GScript which adds new capabilities to the Avisynth language. This can be an alternative to using recursive functions. Here is an example of the factorial function mentioned under Recursion, but written in GScript:
messageclip(string(factorial(5))) function factorial(n) { GScript(""" f=1 for (i=1,n) { f=f*i } """) f }
Weave 3 clips and the Pointresize trick (and issues)
colorbars(width=640/3) r=RGBAdjust(g=0,b=0) g=RGBAdjust(r=0,b=0) b=RGBAdjust(r=0,g=0) weave3(r.turnright,g.turnright,b.turnright).turnleft #Makes a CRT monitor special effect #vertically interleave 3 clips function weave3(clip a, clip b, clip c) { x=a #what is x? It's the line which will get deleted. #It doesn't matter what you set here, it could be any of the inputs a.isrgb?interleave(x,b,a,c):interleave(a,c,b,x) #0 1 2 3->01 23->0213->213 or abc in rgb; 021 or xab in yuv #x,b,a,c for rgb; a,c,b,x for yv12 assumefieldbased.assumetff.weave assumefieldbased.assumetff.weave #We've now weaved 4 times, so delete the 4th line next pointresize(width,height*3/4) #Deletes every 4th line offset 0 in rgb; #deletes every 4th line offset 3 in yuv. #This is the part that's colorspace dependant. }
How it works: Weave is normally used to put back together a progressive clip which has been split into even and odd lines by SeparateFields. If you think about what it does in general, it takes every pair of frames and weaves them line by line. You might call this "thinking outside of the box", that is, you have to start thinking of what a function actually does as opposed to how you would normally use it. Anyhow, it should be obvious that we can call weave twice to weave the lines of 4 frames together into one. It seems impossible to weave only 3 times, however. That's true - you can't produce a 3x weave, but you can use a PointResize trick to delete lines in a specific way. Let's look more closely at how PointResize works.
If you have lines numbered 0,1,2,3,4,5,6,7... and you PointResize(Width,Height/2), you will be selecting every second line, thus 0,2,4,... If you PointResize(Width,Height*3/4) you will select 3 of every 4 lines, thus 1,2,3, 5,6,7,... It is this last case we are interested in, because we can in effect "delete" 1 of every 4 lines, thus eliminating one of our weaves. We now know we want a final weave in this order: x,a,b,c, where x will get deleted and can be set to anything.
We also have to look more closely at how weave works. Let's say our frames are a,b,c,d,e,f,g,h... then weave will do put our lines together as a,b;c,d;e,f;g,h...:
Before Weave
frame a frame b frame c frame d a0 b0 c0 d0 a1 b1 c1 d1 a2 b2 c2 d2
After Weave
weaved 0 weaved 1 a0 c0 b0 d0 a1 c1 b1 d1
and if you weave again you get a,c;b,d;e,g;f,h... thus you have to be aware of the order that your frames will get weaved when you weave more than once. How can we rearrange the input to interleave to produce the order x,a,b,c when it will display as a,c,b,d? The first and final lines in the group of 4 don't change, but the middle two do, thus the answer is x,b,a,c.
Putting it together
After understanding how two weaves work, we deduce that our clips must be interleaved in the order x,b,a,c and PointResize'd to 3/4 height. When we write our script it works fine, but we notice that in YUV modes it doesn't work.
The PointResize Issue
In RGB, PointResize deletes the first of each group of 4 lines. However, in YUV modes, it deletes the last of each group of 4 lines. For this reason we had to test if the clip is RGB and adjust the interleave order as necessary. This difference exists in 2.58; and is undocumented in the current documentation which comes with the program. PointResize will still produce an image that looks fine and be the proper size, but it's still inconsistent in how it does that in various colorspaces.
In Summary
You can use PointResize, with care, to delete specific lines in an image
You can perform geometric operations like Weave in multiples of 4 and yet operate on multiples of 3 by simply deleting the extra result
Using Runtime Variables at Compile Time
Please review the section on The_script_execution_model. Most scripts that you write are executed during "compile time", while runtime scripts can only be executed in a ScriptClip or other runtime function. Sometimes it's useful to access the runtime special variables in your normal script. Note that they can only have a single, unchanging value in this case.
current_frame=0 blankclip(pixel_type="YV12",color_yuv=$408080) avgluma=AverageLuma messageclip(string(avgluma))
Will result in returning a float value of 64. Note that this only works for once, but you can find the runtime value for any frame. One potential use for this would be to calibrate recorded colorbars; extract one frame from your recording, then use crop statements to isolate each of the 7 colors bars, and find averageluma for each. The script can then report if you need to increase brightness or contrast during recording. This isn't a supported feature, but will probably remain. Tested in 2.58.
Casting/Promotion and Floating Point Traps
The float conversion of large numbers is imprecise. Example:
n1=16777475 n2=float(n1) errmsg=n1<>n2?"Error: n1<>n2":"n1=n2" messageclip(string(n1)+" "+string(n2)+" "+errmsg)
Results in "16777475 16777476.000000 n1=n2"
The comparison can't recognize that they're different numbers. If you do int(float(n1)), then it gets recognized as 16777476.
The reason is that floats in Avisynth are 32bit numbers with 24bit mantissa, which means they can hold numbers only up to +-16777216 accurately. If you try to store 16777216+1 as a float, it will be imprecise. The comparison operator returns true even though the numbers are different because, the expression is temporarily converted to float, which is less precise than int, and lower bits of the numbers are truncated. If there is a float in an expression, both sides are converted to float first. This is called promotion, and uses C language rules. This is explained further at wikipedia: Type conversion