Avisynth scripting language tutorial

Return to the Avisynth home page

Getting started: linear editing

In the first part of the tutorial I'm going to concentrate on linear editing of the sort you can do in VirtualDub. I tried to design Avisynth's scripting language so that linear-editing scripts are easy to write: you don't have to worry about variables and complicated expressions if you don't want to.

Once you've installed Avisynth, use a text editor to create a file called version.avs (or any name you like, as long as it ends in .avs) and put the following single line of text in it:

Version

Now, run Windows Media Player and use it to open your file. If all goes well, you should see a ten-second video clip showing Avisynth's version number and a copyright notice in orange text. (If you get an error saying "the format is not supported," make sure you've followed all the installation instructions.)

Version is what's called a "source filter," meaning that it generates a clip instead of modifying one. The first command in an Avisynth script file will always be a source filter.

Now add a second line to the script file, so that it reads like this:

Version
ReduceBy2

Reopen the file in Media Player. You should see the copyright notice again, but now half as large as before. ReduceBy2 is a "transformation filter," meaning that it takes the previous clip and modifies it in some way. You can chain together lots of transformation filters, just as in VirtualDub. Let's add another one to make the video fade to black at the end. Add another line to the script file so that it reads:

Version
ReduceBy2
FadeOut(15)

Now reopen the file. The clip should be the same for the first 9 seconds, and then in the last second it should fade smoothly to black. The FadeOut filter takes a numerical argument, which indicates the number of frames to fade. The video generated by Version happens to run at 15fps, so an argument of 15 to FadeOut makes the fade start one second from the end.

It takes a long time before the fade starts, so let's trim the beginning of the clip to reduce the wait. The clip produced by Version is 150 frames long (15 fps times 10 sec), and Avisynth starts numbering frames from 0, so the frames are 0 through 149. FadeOut happens to add one extra frame, so at the end of this script so far, the frames run from 0 to 150. Let's discard the first 120 of them:

Version
ReduceBy2
FadeOut(15)
Trim(120,150)     # chop off the first eight seconds

In this example I used a comment for the first time. Comments start with the # character and continue to the end of the line, and are ignored completely by Avisynth.

The Trim filter takes two arguments, separated by a comma: the first and the last frame to keep from the clip. If you put 0 for the last frame, it's the same as "end of clip," so this trim command could just as easily have been written Trim(120,0).

Keeping track of frame numbers this way is a chore. It's much easier to open a partially-completed script in an application like VirtualDub which will display the frame numbers for you. You can also use the ShowFrameNumber filter, which prints each frame's number onto the frame itself.

There are many other filters for generalized resizing, blurring/sharpening, noise reduction, and other things. Read the scripting language reference guide for all the gory details. In practice a much more useful source filter than Version is AVISource, which reads in an AVI file (or one of several other types of files) from disk. If you have an AVI file handy, you can try applying these same filters to your file:

AVISource("d:\capture.avi")     # or whatever the actual pathname is
ReduceBy2
FadeOut(15)
Trim(120,0)

Even a single-line script containing only the AVISource command can be useful for adding support for >2GB AVI files to applications which only support <2GB ones.

Non-linear editing

Now we're getting to the fun part. Make an AVS file with the following script in it:

StackVertical(Version, Version)

Now open it. Result: An output video with two identical lines of version information, one on top of the other.

Instead of taking numbers or strings as arguments, StackVertical takes video clips as arguments. In this script, the Version filter is being called twice. Each time, it returns a copy of the version clip. These two clips are then given to StackVertical, which joins them together (without knowing where they came from).

One of the most useful filters of this type is UnalignedSplice, which joins video clips end-to-end. Here's a script which loads three AVI files (such as might be produced by AVI_IO) and concatenates them together.

UnalignedSplice(AVISource("d:\capture.00.avi"), AVISource("d:\capture.01.avi"), AVISource("d:\capture.02.avi"))

(The SegmentedAVISource filter provides a better way to do this. See the reference guide for details.)

Both StackVertical and UnalignedSplice can take as few as two arguments or as many as sixty.

You can use the + operator as a shorthand for UnalignedSplice. For example, this script does the same thing as the previous example:

AVISource("d:\capture.00.avi") + AVISource("d:\capture.01.avi") + AVISource("d:\capture.02.avi")

Now let's suppose you're capturing with the patched version of VIDCAP32. This application also saves the video in multiple AVI segments, but it puts the audio in a separate WAV file. Can we recombine everything? You bet:

AudioDub(AVISource("d:\capture.00.avi")+AVISource("d:\capture.01.avi")+AVISource("d:\capture.02.avi"), WAVSource("d:\audio.wav"))

All the ordinary transformation filters I used back in the linear-editing section accept a video clip as their final argument. So you can write, for example:

FadeOut(15, ReduceBy2(Version))

instead of:

Version
ReduceBy2
FadeOut(15)

Variables

You might have noticed that that AudioDub example was a little ungainly, and if we had been joining more than three files it would have been even worse. You can simplify your expressions by using variables. Here's another version of the last example that's more manageable and easier to understand:

a = AVISource("d:\capture.00.avi")
b = AVISource("d:\capture.01.avi")
c = AVISource("d:\capture.02.avi")
sound_track = WAVSource("d:\audio.wav")

AudioDub(a+b+c, sound_track)

Variable names can be up to fifty characters long and can contain letters, numbers, and underscores (_). You can assign the result of an expression to a variable by putting the variable's name at the beginning of a line followed by = and then the expression. After a video clip has been assigned to a variable, you can use the variable later any time a clip is expected.

Variables are never modified unless you assign to them. For example, you can write ReduceBy2(myvideo), but it won't change the variable "myvideo" unless you assign the result back there by writing myvideo=ReduceBy2(myvideo).

Implicit `last'

What if you don't assign the result of an expression to a variable? The answer is that the result gets assigned to a special variable named "last," as though the line had begun "last =". Last is used implicitly in two other situations as well: first, if you use a filter, and the filter takes a single clip as its last argument, and you don't supply that argument, then the value in last will be used; and second, when the script ends, the video clip in last is taken as the "return value" of the script--that is, the video clip that the application opening the script will see.

These three rules, taken together, are the reason that the linear-editing examples in the first part of this tutorial were able to work without any explicit variables. Here's one of the early examples, rewritten with everything explicit:

last = Version
last = ReduceBy2(last)
last = FadeOut(15, last)
return last

The return command used here should be self-explanatory, but if you'd like an explanation see the reference guide.

The implicit use of last can be useful even in non-linear editing. For example, if we want to load an AVI file, extract two scenes from it, and join them together with a visual dissolve in between, we could do something like this:

AVISource("capture.avi")
Dissolve(10, Trim(123,456), Trim(789,1000))

Here the two Trim filters each used the clip which had last been assigned to last--which in this case was the result of AVISource.

OOP notation

I already mentioned that the three-line Version/ReduceBy2/FadeOut example could be written in one line like this:

FadeOut(15, ReduceBy2(Version))

This works, but I find it rather confusing. For one thing, the order in which you type the functions is the opposite of the order in which they're applied (which is a problem that has been bothering mathematicians for ages). For another thing, as you add more functions you get more and more nested parentheses, and it's hard to tell how many to type at the end.

Avisynth supports another function-calling notation, called "OOP notation," which I like better than the function-style notation in many situations. Using OOP notation you can write the script above like this:

Version.ReduceBy2.FadeOut(15)

This is reminiscent of the method-call syntax used in many object-oriented programming (OOP) languages, like C++ and Java. Here's another example:

myvideo = AVISource("capture.avi")
return Dissolve(10, myvideo.Trim(123,456), myvideo.Trim(789,1000))

The disadvantage of the OOP notation is that it can only be used with filters like Trim which take a single video-clip argument, not with filters like Dissolve which take several.

It's still true that variables aren't modified unless assigned to, so if you write

myvideo.ConvertToRGB()

it will put the converted version of the clip in last and leave myvideo unchanged.

Where to go from here

This concludes the tutorial portion of our program. There are a lot more code samples in the scripting language reference guide, along with descriptions of filters which didn't make it into this tutorial. I recommend you visit there next.


Ben Rudiak-Gould