Understanding editing filters
Introduction
Editing filters provide a standardized interface for common editing operations with clips, such as trimming, replacing parts of a clip with another, inserting and deleting parts and sequentially joining clips.
Currently there are six editing filters, three of which operate on single clips, two on pairs of clips and one on groups of clips (see the corresponding filter documentation for usage examples):
- EditDelete : Deletes a part of the clip.
- EditInsert : Inserts a part of a clip inside another.
- EditJoin : Joins a group of clips sequentially (one after the other).
- EditReplace : Replaces a part of a clip with a part of another clip
- EditTrim : Returns a portion of a clip, like the Trim standard Avisynth function but with a different set of parameters.
- EditTrimRange : Returns portions of a clip, typically disjoint, with the same or varying length. The ranges are joined together in one clip, using EditJoin.
The main advantage of using the editing filters (except of course the standardization and the error checking performed by them, which are already good reasons to adopt their use) is the ability to specify post-processing operations on the parts that compose the final result of editing. We will examine this subject in detail at a following section.
The editing model
All editing filters treat clips as being an array of frames. To promote this notion of an array, frame numbers behave like array incides. More specifically:
- Valid frame numbers are in the range [0..clip.Framecount)
- When selecting subranges, starting frame number is included in the subrange while ending frame number is not included.
- Negative frame numbers are supported also, meaning offset from the end of clip (the length of clip ie clip.Framecount is added to a negative frame number inside the editing functions)[1].
Internally all editing filters use calls to the Trim() standard Avisynth filter to select the requested subranges of frames and then, as a final step, they assemble the selected subranges and form the final result of the editing operation.
Depending of the filter called and the values of the parameters, the number of subranges created for each clip or clip pair may vary from one to three. Because this is of interest to user-supplied functions the following table presents the possible different cases. For EditTrimRange the discussion for EditJoin applies.
Function | Place of operation | Parts | Comments |
---|---|---|---|
At the middle of the clip (frame > 0 and not all frames up to the end are replaced) | 3 | 1st and 3rd parts belong to clip #1, 2nd part belongs to clip #2 at the function's argument list | |
At the start of the clip (frame = 0 and not all frames up to the end are replaced) | 2 | 1st part belongs to clip #2, 2nd part belongs to clip #1 at the function's argument list | |
At the end of the clip (frame > 0 and all frames up to the end are replaced) | 2 | 1st part belongs to clip #1, 2nd part belongs to clip #2 at the function's argument list | |
At the middle of the clip (frame > 0 and not all frames up to the end are deleted) | 2 | Both parts belong to the clip. The start of the original clip belongs to the 1st part | |
At the start or end of the clip | 1 |
| |
At the end of the clip (this is the only option) | 2 | The 1st part is the entire left clip, the 2nd is the entire right clip1 |
1 EditJoin may take up to 50 clip arguments but it joins them incrementally and thus in each join only two clips are participating. The left clip is the combined join of all clips that have been joined so far (ie at the first step it is the clip #1 at the function's argument clip (the right operand is clip #2), at the second step it is the result of the combination of clip #1 and clip #2 (the right operand is now clip #3) and so on..
The following figures demonstrate the above.
Figure 1: Clip parts resulting from EditReplace and EditInsert filters' action
Figure 2: Clip parts resulting from EditDelete and EditJoin filters' action
Avisynth provides a number of ways to assemble clips together, namely UnallignedSplice, AllignedSplice and Dissolve. Instead of having separate versions of each editing filter for each one of these ways, an optional parameter "op" is declared at the argument list of the filters, the value of which controls the way that final result's subranges will be assembled together. One more option added by AVSLib is to process the clips by an arbitrary user-supplied function.
We will examine in more detail this final step of the editing operation at the following section
Assembling the parts of the final result
The editing filters provide four ways to perform the assembly of the parts consisting the final result, controlled by the value of the op[2] optional parameter:
- By using the UnallignedSplice standard Avisynth filter ie the "+" operator. This is the default operation when op is ommited.
- By using the AllignedSplice standard Avisynth filter ie the "++" operator.
- By using the Dissolve standard Avisynth filter.
- By using a custom, user supplied function.
For the last two options a second optional parameter, extra_info, is provided in order to supply the necessary information to the editing function for performing the operation (the overlap in the case of Dissolve and the name of the user function to call in the last case). In both cases the information must be supplied as a string.
Which selection will be used is application-dependent and we will not analyse this issue in depth. UnallignedSplice appears to be the most common type of assembling operation and thus it was chosen as the default, implicit case. Dissolve may be more appropriate when replacing a scene with a similar one from a second source or when cutting scenes out. See the corresponding filter's documentation for usage examples.
The fourth option (user supplied functions) will be presented in detail since it is a new concept and provides a rich set of options (transitions, conditional inclusion, amendment of front-cover clips and logos, special effects, etc.) not found in the other three.
User-supplied functions
User supplied functions provide a generic and versatile mechanism for post-editing clips. We use the term "post-editing" since the call to the user function is the last step of the editing operation; the final result will be whatever function(part1, part2, ...) returns.
As a matter of fact, the user function is free to return anything, even a non-clip value (for example an integer). The only exceptions are EditTrim, where the whole conversation does not apply anyway, and EditJoin when more than two clips are supplied to it (the user function still can return anything, but it must be a clip).
Of course returning something completely irrelevant to the original clips passed to the editing filter or a non-clip value is bad, since it breaks the semantics associated with the names of the filters and in general it results in hard to understand code. Nevertheless, it may be of use in certain situations (for example when you want to insert a clip only if it meets certain requirements that are associated with the final parts of the editing and not the original clips, or when an error condition needs to be flagged). Thus, it is left up to the end-user of the library.
Most of the time however, the user function will return something that has direct relation with the supplied clips. There are many possibilities as pointed out earlier but first let us look at how this user function must be defined.
User supplied functions can expect to be provided with a minimum of one and a maximum of three clips (see the figures above for a visual presentation). The actual number depends on the type of operation and the frame number(s) where the operation was requested. The same is true regarding the initial clip from which the part originates.
Thus, user functions must in general have a parameter list with one required and two optional clips in the first three argument slots. Additional optional arguments do not harm, but they will never be supplied by the caller (ie the editing filter). It is however possible to use a function with less clip slots in the argument list if you know beforehand the number of parts that the editing filter will produce, since the check on arguments' number is done by Avisynth at runtime.
What the user function does with its parameters (ie the processing of the clip parts) is up to the user, so we will not go any further. A limited, indicative list of possible applications is presented below:
- Apply a transition effect between the parts.
- Insert a joining clip to provide continuity of the story (such as a "two minutes later..." logo).
- Insert a front-cover clip in front of every clip (most useful when joining clips with EditJoin, say to construct a movie with a series of episodes.
- Replace a part that does not meet some specifications (for example it is too short) with another or remove it completely from the final output.
- Insert another clip between the parts, for example a commercial, a diagram (if you are constructing educational videos), etc.
- Overlay another clip on top of the final result (say a parallel story inside a window, etc.).
- Apply different sets of filters to each part and the overall result.
At the next section a generic example of a user-supplied function is presented to help clarify the above conversation.
A generic example of a user-supplied function
The following code snippet presents a generic example of a user-supplied function that can be used will all editing filters and all possible values of their arguments.
Function user_function_template(clip c1, clip "c2", clip "c3") { # get number of clips supplied and a reasonable default for all arguments nparts = 1 + (Defined(c2) ? 1 : 0) + (Defined(c3) ? 1 : 0) c2 = Default(c2, Null(c1)) c3 = Default(c3, Null(c1)) # now perform whatever custom processing the user function is designed for ... }
The first line stores the number of parts supplied to the user function, while the next ones assure that all arguments are initialised to clip-type variables. If an argument is not supplied, the Null() function assigns a zero-length clip to it.
The next lines should perform the intended operations on the clip parts, possibly using the information stored at the nparts local variable; if you not need it you may remove the corresponding line. A final clip should be returned at the end.
It should be noted that there is no generic way for the user function to figure out in which of the original clips a specific part belongs in all cases. While this is an issue only for the cases of EditReplace and EditInsert filters when only two parts are supplied (see the table at the section "The editing model" above), it is nevertheless considered a limitation and will be removed in one of the next versions of AVSLib. For the time being, this knowledge should be derived by the kind of operations that the user performs to the edited clips.
You need not however always use the template above in your scripts, since you know what your script does. This generic template is actually needed only if you are constructing general-case functions to make a post-editing library. Most of the time it will be enough to construct simpler variants of the above template such as
- Function user_function_3c(clip c1, clip c2, clip c3) { ... }
- Function user_function_2c(clip c1, clip c2) { ... }
- Function user_function_1c(clip c1) { ... }
Of course you must then assure that the correct variant is used in every case, but this is not difficult.
[1]: See the section Implementation selections at the backgrounder "Understanding containers" for more details.
[2]: The allowed values for the op parameter are coded into a global set of constants ("EDOP_...") by the filters :: edit module.