# # Various AviSynth script functions # # Copyright (c) 2010, Daniel Potter # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # 3. Neither the name of the author nor the names of any contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # (Note: "binary form" does *not* mean resulting videos. It would only apply to # a compiled form of an AviSynth script that uses this code.) # # Utility function to check that frame counts match. # Useful for doing a check when adding some overlay to a clip to ensure that the # resulting clip is exactly the same length as the original. # # expected the original clip, containing the expected number of frames # actual the new clip, containing the actual number of frames generated # what (optional) a string describing what made the change, defaults to # "Frame count has been modified!" function AssertFramecounts(clip expected, clip actual, string "what") { Assert(expected.FrameCount() == actual.FrameCount(), \ Default(what, "Frame count has been modified!") + \ " (Wanted " + \ String(expected.FrameCount()) + ", have " + \ String(actual.FrameCount()) + "!)") } # Utility function to create an image clip that contains silent audio and a # frame rate that matches the given clip. # # path path to the image to use as a clip # length length of the clip to generate in frames # base the clip to use to duplicate audio and FPS settings # pixel_type (optional) the pixel type of the image to return, defaults to # "RGB32" function CreateImageClip(string path, int length, clip base, string "pixel_type") { return AudioDub( \ ImageSource(path, start=1, end=length, fps=base.FrameRate(), \ pixel_type=Default(pixel_type, "RGB32")), \ BlankClip(base, length=length)) } # Internal function used for the animation generated by TransitionZoomBottom. function AnimZoomBottom(clip first, clip second, float opacity, float zoom) { w = Floor(second.Width() * zoom) h = Floor(second.Height() * zoom) x = Floor((second.Width() - w) / 2) y = first.Height() - h show = w > 0 && h > 0 scaled = show ? BilinearResize(second, w, h) : first return show ? Overlay(first, scaled, x, y, opacity=opacity, mask=scaled.ShowAlpha()) : first } # Transistion that causes the second clip to zoom in from the bottom over the # first clip. # # first the original clip # second the clip to transition to # overlap the number of frames over which to show the transition function TransitionZoomBottom(clip first, clip second, int overlap) { first_over = Trim(first, first.FrameCount()-overlap, 0) second_over = Trim(second, 0, overlap-1) result = Animate(0, overlap-1, "AnimZoomBottom", first_over,second_over,0.0,0.0, first_over,second_over,1.0,1.0) result = first.FrameCount() > overlap ? Trim(first, 0, first.FrameCount()-overlap-1) + result : result result = second.FrameCount() > overlap ? result + Trim(second, overlap, 0) : result Assert(result.FrameCount() == first.FrameCount() + second.FrameCount() - overlap, \ "Zoom transition has wrong frame count! (Wanted " + \ String(first.FrameCount() + second.FrameCount() - overlap) + ", created " + \ String(result.FrameCount()) + "!)") return result } # Internal function used for the animation generated by TransitionZoomLeftRight. function AnimZoomLeftRight(clip first, clip second, clip back, float opacity, float zoom) { zoom1 = 1.0 - zoom w1 = Floor(first.Width() * zoom1) h1 = Floor(first.Height() * zoom1) # Are we even showing the first clip? op1 = 1.0 - opacity show1 = w1 > 0 && h1 > 0 # If we are, overlay it onto the background scaled1 = show1 ? BilinearResize(first, w1, h1) : first back = show1 ? Overlay(back, scaled1, 0, Floor((back.Height() - h1) / 2), opacity=op1, mask=scaled1.ShowAlpha()) : back # Now for the second clip w2 = Floor(second.Width() * zoom) h2 = Floor(second.Height() * zoom) show2 = w2 > 0 && h2 > 0 scaled2 = show2 ? BilinearResize(second, w2, h2) : back return show2 ? Overlay(back, scaled2, back.Width()-w2, Floor((back.Height() - h2) / 2), opacity=opacity, mask=scaled2.ShowAlpha()) : back } # Transistion that causes the first clip to shrink over to the left while the # second clip expands from the right, over the given backdrop # # first the original clip # second the clip to transition to # background the background over which the transition plays, should be at # least overlap frames long # overlap the number of frames over which to show the transition function TransitionZoomLeftRight(clip first, clip second, clip background, int overlap) { first_over = Trim(first, first.FrameCount()-overlap, 0) second_over = Trim(second, 0, overlap-1) result = Animate(0, overlap-1, "AnimZoomLeftRight", first_over,second_over,background,0.0,0.0, first_over,second_over,background,1.0,1.0) result = first.FrameCount() > overlap ? Trim(first, 0, first.FrameCount()-overlap-1) + result : result result = second.FrameCount() > overlap ? result + Trim(second, overlap, 0) : result Assert(result.FrameCount() == first.FrameCount() + second.FrameCount() - overlap, \ "Zoom left/right transition has wrong frame count! (Wanted " + \ String(first.FrameCount() + second.FrameCount() - overlap) + ", created " + \ String(result.FrameCount()) + "!)") return result } # Fast Forward effect that removes frames to generate the fast forward. # # c the clip to speed up # times how many times faster to make the clip # audio (optional) audio to replace the clip with; if not given, this # does a "time stretch" effect to maintain pitch while speeding up # the audio # allowSkew (optional) if true, do not check to make sure exactly enough # frames are available to do the speedup without introducing # audio skew # # Fails if: # * The number of frames in the given clip is not a multiple of times function FastForward(clip c, int times, clip "audio", boolean "allowSkew") { Assert(times != 1, "Fast foward 1X is kinda dumb and pointless.") Assert(times >= 2, "Times must be at least 2 (was " + String(times) + \ ")") over = c.FrameCount() % times Assert(over == 0 || Default(allowSkew, false), "FFx" + String(times) + \ " requires a clip with a frame count that is a multiple of " + \ String(times) + " (trim " + String(over) + (over == 1 ? \ " frame.)" : " frames.)")) # Fast forward the clip... ff = ChangeFPS(AssumeScaledFPS(c, times), c) # Then either dub or pitch bend return Defined(audio) ? \ AudioDub(ff, audio) \ : TimeStretch(ff, tempo=(100*times), Sequence=41, Overlap=10) # Removed: this was useful when there was an optional setting to allow # the audio to be sped up instead of pitch bent. # And convert back to the original #return SSRC(ff, c.AudioRate()) } # Fast Forwards the given clip to 2X. Unlike FastForward, this blends frames # together, creating a blurry effect. # # c the clip to speed up # audio (optional) audio to replace the clip with; if not given, this # does a "time stretch" effect to maintain pitch while speeding up # the audio # Fails if: # * The number of frames in the given clip is not a multiple of 2 function FastForward2X(clip c, clip "audio") { over = c.FrameCount() % 2 Assert(over == 0, "2X FF requires a clip with a frame count that is a multiple of 2 (trim " + String(over) + " frame)") # We ignore the audio for the time being... # Step 1: half frame rate and blend frames together... c2 = Overlay(SelectEven(c), SelectOdd(c), opacity=0.5) # Step 2: Restore framerate c3 = AssumeFPS(c2, c2.FrameRate() * 2) # Step 3: Pitch bend or alter sample rate c4 = Defined(audio) ? \ AudioDub(c3, audio) \ : TimeStretch(c3, tempo=200, Sequence=41, Overlap=10) # Step 4: Restore sample rate return SSRC(c4, c.AudioRate()) } # Fast Forwards the given clip to 4X. Unlike FastForward, this blends frames # together, creating a blurry effect. # # c the clip to speed up # audio (optional) audio to replace the clip with; if not given, this # does a "time stretch" effect to maintain pitch while speeding up # the audio # Fails if: # * The number of frames in the given clip is not a multiple of 4 function FastForward4X(clip c, clip "audio") { over = c.FrameCount() % 4 Assert(over == 0, "4X FF requires a clip with a frame count that is a multiple of 4 (trim " + String(over) + " frames)") # We ignore the audio for the time being... # Step 1: half frame rate and blend frames together... c2 = Overlay(SelectEven(c), SelectOdd(c), opacity=0.5) # Step 2: repeat c3 = Overlay(SelectEven(c2), SelectOdd(c2), opacity=0.5) # Step 3: Restore framerate c4 = AssumeFPS(c3, c3.FrameRate() * 4) # Step 4: Pitch bend or alter sample rate c5 = Defined(audio) ? \ AudioDub(c4, audio) \ : TimeStretch(c4, tempo=400, Sequence=41, Overlap=10) # Step 5: Restore sample rate return SSRC(c5, c.AudioRate()) } # Fast Forwards the given clip to 8X. Unlike FastForward, this blends frames # together, creating a blurry effect. # # c the clip to speed up # audio (optional) audio to replace the clip with; if not given, this # does a "time stretch" effect to maintain pitch while speeding up # the audio # Fails if: # * The number of frames in the given clip is not a multiple of 8 function FastForward8X(clip c, clip "audio") { over = c.FrameCount() % 8 Assert(over == 0, "8X FF requires a clip with a frame count that is a multiple of 8 (trim " + String(over) + " frames)") # We ignore the audio for the time being... # Step 1: half frame rate and blend frames together... (x2) c2 = Overlay(SelectEven(c), SelectOdd(c), opacity=0.5) # Step 2: repeat (x4) c3 = Overlay(SelectEven(c2), SelectOdd(c2), opacity=0.5) # Step 3: One last time (x8) c4 = Overlay(SelectEven(c3), SelectOdd(c3), opacity=0.5) # Step 4: Restore framerate c4 = AssumeFPS(c4, c.FrameRate()) # Step 5: Pitch bend or replace audio return Defined(audio) ? \ AudioDub(c4, audio) \ : SSRC(TimeStretch(c4, tempo=800, Sequence=41, Overlap=10), c.AudioRate()) } # Fast Forwards the given clip to 16X. Unlike FastForward, this blends frames # together, creating a blurry effect. # # c the clip to speed up # audio (optional) audio to replace the clip with; if not given, this # does a "time stretch" effect to maintain pitch while speeding up # the audio # Fails if: # * The number of frames in the given clip is not a multiple of 16 function FastForward16X(clip c, clip "audio") { over = c.FrameCount() % 16 Assert(over == 0, "16X FF requires a clip with a frame count that is a multiple of 16 (trim " + String(over) + " frames)") # We ignore the audio for the time being... # Step 1: half frame rate and blend frames together... (x2) c2 = Overlay(SelectEven(c), SelectOdd(c), opacity=0.5) # Step 2: repeat (x4) c3 = Overlay(SelectEven(c2), SelectOdd(c2), opacity=0.5) # Step 3: Again (x8) c4 = Overlay(SelectEven(c3), SelectOdd(c3), opacity=0.5) # Step 4: And done (x16) c4 = Overlay(SelectEven(c4), SelectOdd(c4), opacity=0.5) # Step 5: Restore framerate c4 = AssumeFPS(c4, c.FrameRate()) # Step 5: Pitch bend or replace audio return Defined(audio) ? \ AudioDub(c4, audio) \ : SSRC(TimeStretch(c4, tempo=1600, Sequence=41, Overlap=10), c.AudioRate()) } # Halves the input clip framerate by blurring adjacent frames together. # # c the clip to half the frame rate of function HalfFrameRate(clip c) { #Assert(c.FrameCount() % 2 == 0, "Odd number of frames.") Overlay(SelectEven(c), SelectOdd(c), opacity=0.5) } # Display four clips at once. # The clips are arranged: # c1 | c2 # ------- # c3 | c4 # # c1 the clip in the upper left-hand corner # c2 the clip in the upper right-hand corner # c3 the clip in the lower left-hand corner # c4 the clip in the lower right-hand corner function FourSquare(clip c1, clip c2, clip c3, clip c4) { width = Max(c1.Width() + c2.Width(), c3.Width() + c4.Width()) half_width = width / 2 height = Max(c1.Height() + c3.Height(), c2.Height() + c4.Height()) half_height = height / 2 length = Min(c1.FrameCount(), c2.FrameCount(), c3.FrameCount(), c4.FrameCount()) # Create the backdrop clip back = BlankClip(c1, length=length, width=width, height=height) back = Overlay(back, c1, x=(half_width-c1.Width())/2, y=(half_height-c1.Height())/2) back = Overlay(back, c2, x=half_width+(half_width-c2.Width())/2, y=(half_height-c2.Height())/2) back = Overlay(back, c3, x=(half_width-c3.Width())/2, y=half_height+(half_height-c3.Height())/2) back = Overlay(back, c4, x=half_width+(half_width-c4.Width())/2, y=half_height+(half_height-c4.Height())/2) return back } # Replaces the audio of the second clip such that audio format matches that of # the original clip. (Useful for images, see CreateImageClip.) # # c the clip having its audio replaced # to the clip to use to determine the audio settings function MatchAudio(clip c, clip to) { AudioDub(c, BlankClip(to, length=c.FrameCount())) } # Internal function used to generate the actual PIP overlay function PiPZoomOverlay(clip back, clip original, clip inset, int o_w, int o_h, int i_w, int i_h) { Overlay(Overlay(back, \ BilinearResize(original, o_w, o_h), 0, 0), \ BilinearResize(inset, i_w, i_h), back.Width() - i_w, back.Height() - i_h) } # Internal function used to do the animation to/from PIP. function PiPEffect(clip back, clip original, clip inset, int overlap, int orig_w, int orig_h, int inset_w, int inset_h, bool start) { # Figure out the inset starting width/height since it needs to maintain # the aspect ratio as best as possible ar = inset_w > inset_h ? Float(inset_w) / Float(inset_h) : Float(inset_h) / Float(inset_w) start_width = inset_w > inset_h ? Floor(4 * ar) : 4 start_height = inset_w > inset_h ? 4 : Floor(4 * ar) start ? \ Animate(0, overlap-1, "PiPZoomOverlay",\ back,original,inset,original.Width(),original.Height(),start_width,start_height, \ back,original,inset,orig_w,orig_h,inset_w,inset_h) \ : Animate(0, overlap-1, "PiPZoomOverlay",\ back,original,inset,orig_w,orig_h,inset_w,inset_h, \ back,original,inset,original.Width(),original.Height(),start_width,start_height) \ } # Creates a PIP effect. # # original the clip to overlay on top of # inset the clip to show inside the other # start the first frame in the original to display the inset # overlap the amount of time to do the animation between clips # original_width (optional) the width to display the original clip after the # animation, defaults to 65% of the original width # original_height (optional) same as original_width, except for the height # inset_width (optional) the width to display the inset clip in after the # animation, defaults to 40% of the original width # inset_height (optional) same as inset_width, except for the height # background (optional) clip to display behind the two clips, defaults to a # blank black clip. (The audio won't be used.) # audio_factor (optional) percentage of audio of the inset clip - the resulting # audio during the PIP effect will be mixed so that the original # audio is 1-audio_factor and the PIP audio is audio_factor. The # default is 0.4. # # Fails if: # * The original is shorter than the inset # * The inset is shorter than the time given to animate in/out of the clip function PiPZoom(clip original, clip inset, int start, int overlap, \ int "original_width", int "original_height", \ int "inset_width", int "inset_height", clip "background", \ float "audio_factor") { Assert(original.FrameCount() >= inset.FrameCount(), \ "Original is shorter than the inset!") Assert(inset.FrameCount() > overlap*2, \ "Inset is shorter than the animation time given!") original_width = Default(original_width, original.Width() * 13 / 20) original_height = Default(original_height, original.Height() * 13 / 20) inset_width = Default(inset_width, inset.Width() * 4 / 10) inset_height = Default(inset_height, inset.Height() * 4 / 10) audio_factor = Default(audio_factor, 0.4) back = Default(background, BlankClip(original, length=(inset.FrameCount()))) back = back.HasAudio() ? back : \ AudioDub(back, BlankClip(original, length=back.FrameCount())) result = Trim(original, 0, start-1) + \ PiPEffect(Trim(back,0,overlap-1), \ Trim(original, start, start+overlap-1), \ Trim(inset, 0, overlap-1), overlap, \ original_width, original_height, \ inset_width, inset_height, true) + \ PiPZoomOverlay(Trim(back, overlap, inset.FrameCount() - overlap - 1), \ Trim(original, start+overlap, start + inset.FrameCount() - overlap - 1), \ Trim(inset, overlap, inset.FrameCount() - overlap - 1), \ original_width, original_height, inset_width, inset_height) + \ PiPEffect(Trim(back, inset.FrameCount() - overlap, inset.FrameCount()-1), \ Trim(original, start + inset.FrameCount() - overlap, start + inset.FrameCount()-1), \ Trim(inset, inset.FrameCount() - overlap, inset.FrameCount()-1), \ overlap, original_width, original_height, \ inset_width, inset_height, false) + \ Trim(original, start + inset.FrameCount(), 0) AssertFrameCounts(original, result) # With that done, we now need to play with the audio. We use Dissolve # to create the result since it does the ramp up/ramp down effect. audio = Dissolve(Trim(original, 0, start-overlap-1), \ MixAudio(Trim(original, start - overlap, start + inset.FrameCount() + overlap - 1), \ inset, audio_factor), \ Trim(original, start + inset.FrameCount() - overlap, 0), overlap) AssertFrameCounts(original, audio) return AudioDub(result, audio) }