# jdl-util.avsi -- General-purpose, utility functions.
#
# Last modified: 2008-10-16
#
# Written by James D. Lin (stickboy) and assigned to the public domain.
#
# The latest version of this file can be downloaded from:
#
# These functions are superceded by the more general functions in my
# Min/Max plug-in. Luckily, AviSynth supports overloaded functions, so
# the plug-in and these functions can co-exist peacefully. Yay!
#
function Min(val a, val b) { return (a < b) ? a : b }
function Max(val a, val b) { return (a > b) ? a : b }
function Clamp(val n, val low, val high) { return Max(low, Min(n, high)) }
# Returns a double-quote (") character. I made a function for this because
# I can't (and don't want to) remember the ASCII value.
#
# (See for an
# even easier way to put double-quotes within strings.)
#
function Quote() { return Chr(34) }
# Returns a newline character. Again, not something I care to remember.
#
function Endl() { return Chr(10) }
# AssertEval is mentioned in the AviSynth documentation but doesn't exist
# as of 2.52.
#
function AssertEval(string s)
{
Assert(s)
}
# Throw
#
# Throws an error.
#
# PARAMETERS:
# "msg" : The error message.
# (Default: AviSynth's default assertion string.)
#
function Throw(string "msg")
{
Assert(false, msg)
}
# Undefined
#
# Generates an "undefined" variable value. This function can be used to
# set an already-defined variable to an undefined state.
#
# USAGE:
# x = Undefined() # Subsequent calls to Defined(x) will return false.
#
function Undefined(val "dummyArg")
{
Assert(!Defined(dummyArg), "Undefined: Cannot be called with any arguments.")
return dummyArg
}
# NullClip
#
# Generates a null (0 frame) clip using the input clip as a template.
# Useful to use as a placeholder when conditionally splicing.
#
function NullClip(clip "template")
{
return BlankClip(template, length=0)
}
# JDL_ImageSource
#
# A wrapper function to ImageReader. The syntax and behavior of this
# function should be a little bit more intuitive than ImageReader's.
#
# PARAMETERS:
# filenameTemplate : The template string used to generate filenames from
# frame numbers.
# Should include the full, absolute path.
# "start", "end" : The start and end frame numbers (inclusive).
# (Default: If not specified, assumes that you want to
# load only a single image file instead of an image
# sequence.)
# "fps" : The desired frame-rate.
# (Default: 29.97)
#
function JDL_ImageSource(string filenameTemplate, int "start", int "end", float "fps")
{
Assert(Defined(start) == Defined(end),
\ "JDL_ImageSource: and parameters must be used together")
start = Default(start, 0)
end = Default(end, start)
Assert(start >= 0, "JDL_ImageSource: invalid start frame: " + String(start))
Assert(end >= start, "JDL_ImageSource: invalid end frame: " + String(end))
Assert(filenameTemplate != "", "JDL_ImageSource: invalid filename template")
fps = Default(fps, 29.97)
try
{
ImageSource(filenameTemplate, start, end, 1, true, pixel_type="rgb32")
}
catch (msg)
{
try
{
# We're using a version of AviSynth that doesn't support RGB32
# for ImageSource. Fall back.
ImageSource(filenameTemplate, start, end, 1, true)
}
catch (msg)
{
# ImageReader used to have a bug that generated upside-down
# images. This bug was fixed at the same time that the
# ImageSource function was added. If we got this far, that
# implies that ImageSource doesn't exist and that we're still
# using the version of ImageReader with the upside-down bug.
ImageReader(filenameTemplate, start, end, 1, true)
FlipVertical()
}
}
Trim3(start, length=(end - start + 1))
AssumeFPS(fps)
return last
}
# Trim2
#
# A version of Trim with a different syntax. This version is more
# suitable than Trim in reusable functions.
#
# See:
#
# Trims the video clip to include frames in the range [start, end).
#
# If is negative, indicates the number of frames to trim from the
# end of the clip. See Usage, below.
#
# If is omitted, all frames to the end of the clip are included.
# Equivalent to Trim2(c, start, c.FrameCount()).
#
# may be used intead of . must be non-negative.
#
# and cannot be used together.
#
# Allows generation of null (0 frame) clips.
#
# PARAMETERS:
# start, "end"
# "length"
#
# PRECONDITIONS:
# 0 <= start
# start <= end || end < 0
# length >= 0
#
# USAGE:
# Trim2(0, -1) # removes the last frame
#
# Trim2(500, 600) # same as Trim2(500, length=100)
#
function Trim2(clip c, int start, int "end", int "length")
{
Assert(!(Defined(end) && Defined(length)),
\ "Trim2: and parameters cannot be used together")
Assert(!Defined(length) || length >= 0,
\ "Trim2: invalid length: " + String(length))
end = Defined(end)
\ ? ((end < 0) ? (c.FrameCount() + end) : end)
\ : Max(start, c.FrameCount())
end = Defined(length) ? (start + length) : end
Assert(start >= 0, "Trim2: start frame out of bounds: " + String(start))
Assert(end >= start, "Trim2: end frame out of bounds: " + String(end))
start = Min(start, c.FrameCount())
end = Min(end, c.FrameCount())
# We can't use Trim(start, end - 1) in case end == 1
return (start == end)
\ ? c.NullClip()
\ : c.Trim(start, -(end - start))
}
# Trim3
#
# A version of Trim2 with stricter bounds. Disallows bounds beyond the
# end of the clip. Disallows generation of null (0 frame) clips.
#
# PARAMETERS:
# start, "end"
# "length"
#
# PRECONDITIONS:
# 0 <= start
# start < end <= c.FrameCount() || end < 0
# length > 0
#
function Trim3(clip c, int start, int "end", int "length")
{
Assert(!(Defined(end) && Defined(length)),
\ "Trim3: and parameters cannot be used together")
Assert(!Defined(length) || length > 0,
\ "Trim3: invalid length: " + String(length))
end = Defined(length) ? (start + length) : Default(end, c.FrameCount())
end = (end < 0) ? (c.FrameCount() + end) : end
Assert(start >= 0 && start < c.FrameCount(),
\ "Trim3: start frame out of bounds: " + String(start))
Assert(end > start && end <= c.FrameCount(),
\ "Trim3: end frame out of bounds: " + String(end))
return c.Trim2(start, end)
}
# JDL_LengthenClip
#
# Appends frames to a clip to give it the specified length.
#
# DEPRECATED:
# Please use LengthenClip from my ApplyEvery plug-in instead.
#
#
# PARAMETERS:
# minLength : The minimum length desired for the clip.
# "copy" : If true, the clip will be lengthened by duplicating
# its last frame.
# If false, the clip will be lengthened by appending
# blank frames to the end.
# (Default: false)
# "color" : The RGB color to use for appended blank frames.
# (default: $000000 (black))
#
function JDL_LengthenClip(clip c, int minLength, bool "copy", int "color")
{
copy = Default(copy, false)
color = Default(color, $000000)
try
{
# Use the plug-in version if available.
return c.LengthenClip(minLength, copy, color)
}
catch (msg)
{
numPadFrames = minLength - c.FrameCount()
return (numPadFrames > 0)
\ ? (c ++ (copy
\ ? c.Trim3(c.FrameCount() - 1).Loop(numPadFrames).Amplify(0.0)
\ : c.BlankClip(length=numPadFrames, color=color)))
\ : c
}
}
# SetParity
#
# Sets the parity of a clip.
#
# PARAMETERS:
# parity : true for TFF, false for BFF.
# These values correspond to those returned by the internal
# GetParity function.
# (I use the mnemonic "T" = "T"op = "T"rue to remember.)
#
function SetParity(clip c, bool parity)
{
return parity ? c.AssumeTFF() : c.AssumeBFF()
}
# AssumeScaledFPS
#
# Scales a clip's frame-rate by the specified factor. Attempts to
# perform an exact calculation without overflow if possible. Does not
# add or remove frames nor affect the audio track.
#
# DEPRECATED:
# AviSynth 2.5.6 has its own internal version of this function.
#
# PARAMETERS:
# numerator, denominator : The scaling factor.
#
# USAGE:
# # Sets the clip's frame-rate to 80% (4/5) of its original value.
# AssumeScaledFPS(4, 5)
#
function AssumeScaledFPS(clip c, int numerator, int denominator)
{
Assert(denominator != 0, "AssumeScaledFPS: denominator cannot be 0")
# We need to cancel as many common factors as possible to minimize
# the chance for overflow. Unfortunately, AviSynth doesn't provide a
# gcd function, and writing our own recursively won't be as efficient
# as we'd like.
#
# Instead, we readily abuse the fact that AviSynth internally reduces
# frame-rates to make the numerator and denominator relatively prime.
n0 = c.FrameRateNumerator()
d0 = c.FrameRateDenominator()
# Make sure that the numerator and denominator are reduced.
scale = c.AssumeFPS(numerator, denominator)
n = scale.FrameRateNumerator()
d = scale.FrameRateDenominator()
# Cancel any common factors with the original frame-rate.
x = c.AssumeFPS(n0, d)
y = c.AssumeFPS(n, d0)
nx = x.FrameRateNumerator()
dx = x.FrameRateDenominator()
ny = y.FrameRateNumerator()
dy = y.FrameRateDenominator()
# We've cancelled as many factors as we could. Multiply the reduced
# frame-rates. (Assuming overflow doesn't occur, newN and newD will be
# relatively prime.)
newN = nx * ny
newD = dx * dy
# If we didn't overflow, use newN and newD directly. Otherwise,
# resort to floating-point arithmetic.
#
# Unfortunately, checking for overflow with (a * b) / a != b produces
# false positives because AviSynth scripts use only signed integers,
# and frame-rates internally use unsigned values.
#
# Therefore, as a secondary heuristic, check if the integer calculation
# is equal to the floating-point calculation within some tolerance.
approximate = (c.FrameRate() * n) / d
exactClip = (newD == 0) ? c : c.AssumeFPS(newN, newD)
Assert(approximate <= (Float($7FFFFFFF) * 2),
\ "AssumeScaledFPS: frame-rate is too high.")
return ( newD != 0
\ && ( ( (nx == 0 || newN / nx == ny)
\ && newD / dx == dy)
\ || (abs(exactClip.FrameRate() - approximate) <= (exactClip.FrameRate() * 0.0001)) ))
\ ? exactClip
\ : c.AssumeFPS(approximate)
}
# JDL_ShiftFrame
#
# Shifts a frame by the specified offset.
#
# PARAMETERS:
# "dx", "dy" : The offset amount.
# Positive values shift to the right or downward.
# Negative values shift to the left or upward.
# These values are subject to colorspace constraints
# (see the documentation to the internal Crop function
# for details).
# (dx default: 0)
# (dy default: 0)
# "color" : The RGB color to use to fill in the vacated edges.
# (Default: $000000 (black))
#
function JDL_ShiftFrame(clip c, int "dx", int "dy", int "color")
{
dx = Default(dx, 0)
dy = Default(dy, 0)
color = Default(color, $000000)
w = c.Width() - Abs(dx)
h = c.Height() - Abs(dy)
Assert(w >= 0 && h >= 0,
\ "JDL_ShiftFrame: the magnitudes of and cannot be larger than the frame size")
c = (dx == 0)
\ ? c
\ : ((dx < 0)
\ ? c.Crop(-dx, 0, w, c.Height()).AddBorders( 0, 0, -dx, 0, color)
\ : c.Crop( 0, 0, w, c.Height()).AddBorders(dx, 0, 0, 0, color))
c = (dy == 0)
\ ? c
\ : ((dy < 0)
\ ? c.Crop(0, -dy, c.Width(), h).AddBorders(0, 0, 0, -dy, color)
\ : c.Crop(0, 0, c.Width(), h).AddBorders(0, dy, 0, 0, color))
return c
}
# NoArgFunctionWrapper
#
# A wrapper for filters that take no additional arguments other than a
# clip. Useful for using such filters in functions such as Animate or
# ApplyRange.
#
# DEPRECATED:
# Starting with AviSynth 2.5.5, this function is no longer necessary.
#
# PARAMETERS:
# filter : The name of the filter to apply.
#
# USAGE:
# ApplyRange(100, 200, "NoArgFunctionWrapper", "Greyscale")
#
function NoArgFunctionWrapper(clip c, string filter)
{
c
return Eval(filter + "()")
}
# JDL_FunctionDefined
#
# Determines whether a function is defined.
#
# PARAMETERS:
# functionName : The name of the function.
# Can be the name of a built-in function, a plug-in
# function, or a user-defined function.
# "err_msg" : If specified, JDL_FunctionDefined will throw an
# exception with that error message if the function is
# not defined.
# If not specified, JDL_FunctionDefined will return true
# if the function is defined, false otherwise.
#
# USAGE:
# # JDL_FunctionDefined can be used to check for script dependencies. If
# # a script requires a plug-in or function that isn't found, you can
# # present a meaningful error message explaining where to download the
# # necessary files.
# #
# JDL_FunctionDefined("Trim2",
# \ "This script requires Trim2. Download it from ...")
#
function JDL_FunctionDefined(string functionName, string "err_msg")
{
try
{
Eval(functionName + "()")
# If we made it this far, we called the function without any
# problem; therefore it exists.
return true
}
catch (s)
{
# Dependent on AviSynth generating an exception with the message:
# "Script error: there is no function named ..."
# Basic idea from mf.
isDefined = FindStr(s, "there is no function named "
\ + Quote() + functionName + Quote())
\ == 0
Assert(isDefined || !Defined(err_msg), err_msg)
return isDefined
}
}
# HasVideo
#
# Returns true if the specified clip has a video track, false otherwise.
#
# DEPRECATED:
# AviSynth 2.5.6 has its own internal version of this function.
#
function HasVideo(clip c)
{
return c.Width() != 0
}
# HasAudio
#
# Returns true if the specified clip has an audio track, false otherwise.
#
# DEPRECATED:
# AviSynth 2.5.6 has its own internal version of this function.
#
function HasAudio(clip c)
{
return c.AudioRate() != 0
}