# jdl-range.avsi -- Functions that operate on ranges in a clip. # # Last modified: 2008-09-13 # # Written by James D. Lin (stickboy) and assigned to the public domain. # # The latest version of this file can be downloaded from: # # JDL_ApplyRange # # Applies a filter to the specified range of frames in a clip. # # This function is intended primarily for use with AviSynth 2.0x (an # internal ApplyRange function was added in AviSynth 2.51). However, # this function offers the following advantages over the internal # version: # * directly supports filters that take no arguments # (fixed in AviSynth 2.55) # * supports named filter parameters # * allows the range to be of length 1 (fixed in AviSynth 2.52) # * allows audio modification (fixed in AviSynth 2.52) # # NOTE: # Do not use this with filters that modify the frame count. # # PARAMETERS: # start, end : The filter will be applied to all frames in the range # [start, end]. # thunk : The action to perform on the specified frames. # # USAGE: # JDL_ApplyRange(123, 456, "Tweak(sat=1.1)") # # REQUIRES: # jdl-util.avsi (Trim2) # function JDL_ApplyRange(clip c, int start, int end, string thunk) { Assert(start >= 0 && start < c.FrameCount(), \ "JDL_ApplyRange: start frame out of bounds: " + String(start)) Assert(end >= start && end < c.FrameCount(), \ "JDL_ApplyRange: end frame out of bounds: " + String(end)) c filtered = Eval(thunk) Assert(c.FrameCount() == filtered.FrameCount(), \ "JDL_ApplyRange: should not be used with filters that modify the frame count. " \ + "Use JDL_ApplyRangeOld instead.") seg1 = c.Trim2(0, start) seg2 = filtered.Trim2(start, end + 1) seg3 = c.Trim2(end + 1) return seg1 + seg2 + seg3 } # JDL_ApplyRangeOld # # An old version of JDL_ApplyRange. This differs from JDL_ApplyRange by # splicing before filtering. This change affects the behavior of: # * temporal filters # * filters that operate on frame numbers # * filters that affect the frame count # # REQUIRES: # jdl-util.avsi (Trim2, Trim3) # function JDL_ApplyRangeOld(clip c, int start, int end, string thunk) { Assert(start >= 0 && start < c.FrameCount(), \ "JDL_ApplyRangeOld: start frame out of bounds: " + String(start)) Assert(end >= start && end < c.FrameCount(), \ "JDL_ApplyRangeOld: end frame out of bounds: " + String(end)) seg1 = c.Trim2(0, start) seg2 = c.Trim3(start, end + 1) seg3 = c.Trim2(end + 1) return seg1 + Eval("seg2." + thunk) + seg3 } # JDL_ApplyFrame # # Applies a filter to a specific frame in a clip. # # PARAMETERS: # frame : The index of the desired frame. # thunk : The action to perform on the specified frame. # # USAGE: # ApplyFrame(123, "Tweak(sat=1.1)") # function JDL_ApplyFrame(clip c, int frame, string thunk) { return JDL_ApplyRange(c, frame, frame, thunk) } # JDL_ApplyEvery # # Applies a filter at regular intervals in a clip. # # DEPRECATED: # Please use my ApplyEvery plug-in instead. # # # NOTE: # If the total number of frames is not a multiple of , then # silent, blank frames will be appended to the end of the output clip. # It is the caller's responsibility to remove these afterward as # necessary. (See Usage, below, for an example of removing these extra # frames.) # # This filter is recursive, and since AviSynth currently does not # properly optimize away tail-recursive calls, it can crash AviSynth (and # the host program) if there are too many iterations. # # PARAMETERS: # interval : The interval length, in frames. # thunk : The action to perform on the frames in the interval. # # USAGE: # # Deletes the 0th frame of every 100. # JDL_ApplyEvery(100, "DeleteFrame(0)") # # # Inverts the first two frames of every 5. # JDL_ApplyEvery(5, """ApplyRange(0, 1, "Levels", 0, 1.0, 255, 255, 0)""") # # # Reverses every group of 4 frames. # JDL_ApplyEvery(4, "Reverse()") # # # How to correct the clip length. # # (assumes that does not itself modify the frame-count) # n = FrameCount() # JDL_ApplyEvery(...) # Trim(0, -n) # # REQUIRES: # ApplyEvery # jdl-util.avsi (JDL_LengthenClip, NullClip, Trim2) # function JDL_ApplyEvery(clip c, int interval, string thunk) { Assert(interval > 0, "JDL_ApplyEvery: interval must be > 0") try { # Use the plug-in version if available. return c.ApplyEvery(interval, thunk) } catch (msg) { return JDL_ApplyEveryHelper(c.JDL_LengthenClip(Int(Ceil(c.FrameCount() / Float(interval)) \ * interval)), \ c.NullClip(), interval, thunk, 0) } } # Helper function to JDL_ApplyEvery; do not call this yourself. function JDL_ApplyEveryHelper(clip originalClip, clip newClip, \ int interval, string thunk, \ int curFrame) { originalClip.Trim2(curFrame, length=interval) Eval(thunk) newClip = newClip + last curFrame = curFrame + interval return (curFrame >= originalClip.FrameCount()) \ ? newClip \ : JDL_ApplyEveryHelper(originalClip, newClip, interval, thunk, curFrame) } # JDL_SimpleApplyEvery # # Applies a filter at regular intervals in a clip. # # Unlike ApplyEvery, JDL_SimpleApplyEvery applies the filter only at # specific frame offsets within each interval. # # For these sorts of cases, JDL_SimpleApplyEvery should be much more # efficient than ApplyEvery. # # PARAMETERS: # thunk : The action to perform on the specified frames. Must not # add, remove, or re-order frames. Must not affect the # frame-rate. # period : The interval length, in frames. # "offsets" : A comma-separated list of the offsets of each frame to # filter (see Usage, below). # Each offset must be in the range [0, period). # # USAGE: # # Converts frames 1, 3, 11, 13, 21, 23, 31, 33, ... to greyscale. # JDL_SimpleApplyEvery("Greyscale()", 10, "1, 3") # # REQUIRES: # ApplyEvery (DeleteEvery, InterleaveEvery) # function JDL_SimpleApplyEvery(clip c, string thunk, int period, string "offsets") { Assert(period > 0, "JDL_SimpleApplyEvery: period must be > 0") # A workaround to not being able to pass a variable number arguments # to a user-defined function is to package them all up in a string. # From there, we can use Eval to pass them along to built-in functions # or to plug-ins. offsets = Default(offsets, "") args = String(period) + ", " + offsets c Eval(thunk) filtered = last Eval("filtered.SelectEvery(" + args + ")") # Pull out the frames we want. subClip = last Eval("c.DeleteEvery(" + args + ")") # Inject the filtered frames back in. Eval("InterleaveEvery(last, subClip, " + args + ")") # SelectEvery/DeleteEvery/InterleaveEvery might have introduced # rounding error into the frame-rate, so set it back to its original # value. Assert(FrameCount() == c.FrameCount(), String(FrameCount()) + " != " + String(c.FrameCount())) return AssumeFPS(c) } # JDL_DeleteRange # # Deletes a range of frames from a clip. # # PARAMETERS: # start, end : All frames in the range [start, end] will be deleted. # function JDL_DeleteRange(clip c, int start, int end) { Assert(start >= 0 && start < c.FrameCount(), \ "JDL_DeleteRange: start frame out of bounds: " + String(start)) Assert(end >= start && end < c.FrameCount(), \ "JDL_DeleteRange: end frame out of bounds: " + String(end)) return c.Trim2(0, start) ++ c.Trim2(end + 1) } # JDL_SingleFrame # # Returns a single frame from a clip. # # PARAMETERS: # "frame" : The index of the desired frame. # (Default: 0) # # REQUIRES: # jdl-util.avsi (Trim3) # function JDL_SingleFrame(clip c, int "frame") { frame = Default(frame, 0) return c.Trim3(frame, length=1) } # JDL_ReplaceRange # # Replaces a range of frames from a clip with another clip. # # PARAMETERS: # baseClip : The target clip. # newClip : The clip containing the replacement frames. # start, "end" : All frames from in the range [start, end] # will be replaced by . # If is not specified, the number of frames replaced # in will correspond to newClip.FrameCount() # # REQUIRES: # jdl-util.avsi (Trim2) # function JDL_ReplaceRange(clip baseClip, clip newClip, int start, int "end") { Assert(start >= 0 && start < baseClip.FrameCount(), \ "JDL_ReplaceRange: start frame out of bounds: " + String(start)) end = Default(end, start + newClip.FrameCount() - 1) length = end - start + 1 Assert(end >= start && end < baseClip.FrameCount() && length <= newClip.FrameCount(), \ "JDL_ReplaceRange: end frame out of bounds: " + String(end)) c = baseClip.Trim2(0, start) ++ newClip.Trim3(0, length=length) ++ baseClip.Trim2(end + 1) Assert(c.FrameCount() == baseClip.FrameCount(), "JDL_ReplaceRange: length mismatch") return c } # JDL_FreezeRegion # # Freezes a region for the specified range of frames. Does not affect # the audio track. # # PARAMETERS: # x, y : The coordinates of the upper-left corner of the region # to freeze. # w, h : The width and height of the region to freeze. # start, end : All frames in the range [start, end] will be frozen. # "sourceFrame" : The index of the frame containing the region to copy. # (Default: start) # # COLORSPACES: # [YUY2, RGB32] # # REQUIRES: # jdl-util.avsi (Trim2) # function JDL_FreezeRegion(clip c, int x, int y, int w, int h, int start, int end, int "sourceFrame") { Assert(start >= 0 && start < c.FrameCount(), \ "JDL_FreezeRegion: start frame out of bounds: " + String(start)) Assert(end >= start && end < c.FrameCount(), \ "JDL_FreezeRegion: end frame out of bounds: " + String(end)) sourceFrame = Default(sourceFrame, start) Assert(sourceFrame >= 0 && sourceFrame < c.Framecount(), \ "JDL_FreezeRegion: source frame out of bounds: " + String(sourceFrame)) length = end - start + 1 copy = c.JDL_SingleFrame(sourceFrame).Crop(x, y, w, h).Loop(length) replaced = Layer(c.Trim2(start, length=length), \ copy.IsRGB32() ? copy.ResetMask() : copy, \ "add", 255, x, y) return c.Trim2(0, start) + replaced + c.Trim2(end + 1) } # JDL_FreezeLoop # # Like FreezeFrame, except repeats a cycle of frames instead of a single # frame. # # Does not affect the audio track. # # See Usage, below. # # PARAMETERS: # start, end : All frames in the range [start, end] will be # replaced with the looped frames. # loopStart, loopEnd : All frames in the range [loopStart, loopEnd] will # be looped. # # USAGE: # # Create the following output sequence: # # 0 1 2 3 0 1 2 0 1 2 0 1 2 0 1 15 16 17 ... # # ^-------------------^ # # Frames [4, 14] are replaced by looping frames [0, 2]. # # # JDL_FreezeLoop(4, 14, 0, 2) # # REQUIRES: # jdl-util.avsi (Trim2) # function JDL_FreezeLoop(clip c, int start, int end, int loopStart, int loopEnd) { Assert(start >= 0 && start < c.FrameCount(), \ "JDL_FreezeLoop: start frame out of bounds: " + String(start)) Assert(loopStart >= 0 && loopStart < c.FrameCount(), \ "JDL_FreezeLoop: loop start out of bounds: " + String(loopStart)) Assert(end >= start && end < c.FrameCount(), \ "JDL_FreezeLoop: end frame out of bounds: " + String(end)) Assert(loopEnd >= loopStart && loopEnd < c.FrameCount(), \ "JDL_FreezeLoop: loop end out of bounds: " + String(loopEnd)) span = end - start + 1 newClip = c.Trim2(loopStart, loopEnd + 1) newClip = newClip.Loop(span / newClip.FrameCount() + 1) return AudioDub(JDL_ReplaceRange(c, newClip, start, end), c) }