Log in

View Full Version : CutFrames: Opposite of Trim


mikeytown2
2nd March 2008, 00:29
# CutFrames() - March 9th, 2008
# Cut a range of frames from a single a/v clip.
#
# Parameters:
# int start - start of cut
# int end - end of cut
# int duration - duration of fade between cuts
# int fade_color - blank clip color (usually in Hex RGB) used at start or end of clip only
# string transition - pass a function name, if user wants to use something other then Dissolve
# val opt1 - pass a variable to the fade/transition function
# val opt2 - pass a variable to the fade/transition function
# val opt3 - pass a variable to the fade/transition function
#
# Preconditions:
# start >= 0 ; start can not be a negative value
# start-duration > 0
# start = 0 then a fade in is used
# end = 0 cut till the end of the clip and a fade out is used
# start != end ; mainly a condition to check for start == 0 and end == 0


function CutFrames(clip c, int start, int end, int "duration", int "fade_color", string "transition", val "opt1", val "opt2", val "opt3")
{
# Set Defaults
duration = default(duration, 0)
transition = default(transition, "Dissolve")
fade_color = default(fade_color,$000000)
opt1=(Defined(opt1) ? ", "+string(opt1): "")
opt2=(Defined(opt2) ? ", "+string(opt2): "")
opt3=(Defined(opt3) ? ", "+string(opt3): "")
opts = opt1 + opt2 + opt3
end = c.Framecount()-1 == end ? 0: end
d = c.BlankClip(duration, color=fade_color)

# Check For Any Unreasonable Inputs
Assert(start >= 0, "CutFrames: Starting Frame[" + String(start) + "] must be a positive value")
Assert((start == 0) || (start-duration >= 0), "CutFrames: Starting Frame[" + String(start) + "] must be greater then or equal to" + Chr(10) + " inputed duration[" + String(duration) + "]")
Assert(start <> end, "CutFrames: Start and End cannot both be the same")
Assert(end <= c.Framecount()-1, "CutFrames: End[" + String(end) + "] is greater then " + Chr(10) + "clip length[" + String(c.Framecount()-1) + "]")
Assert((end == 0) || (end+duration <= c.Framecount()-1), "CutFrames: Ending Frame[" + String(end) + "] must be less then or equal to" + Chr(10) + " last frame - duration [" + String(c.Framecount()-1-duration) + "]")

# Select uncut frames (or blanks) as transition components:
clip1 = (start == 0) ? d : c.trim(0, -start)
clip2 = (end == 0) ? d : c.trim(end+1, 0)

# Perform transition:
Eval(transition + "(clip1, clip2," + string(duration) + opts + ")")
}

General Examples: - Assuming clip is 1000 frames in length
CutFrames(200, 400) - Cut out frames 200-400 from the video. Resulting length: 799
CutFrames(200, 400, 20) - Cut out frames 200-400 using Dissolve() for a 20 frame fade. Transition starts on frame 180; Resulting length: 779
CutFrames(210, 390, 20) - Cut out frames 210-390 using Dissolve() for a 20 frame fade. Transition starts on frame 190; Resulting length: 799
CutFrames(400, 200, 20) - Replay frames 200-400 using Dissolve() with a 20 frame fade. Transition starts on frame 380; Resulting length: 1179
CutFrames(200, 400, 20, $000000, "TransSwing", false, 1, 1) - Cut out frames 200-400 using TransSwing() (http://avisynth.org/vcmohan/TransAll/docs/TransSwing.html) for a 20 frame fade, passing 3 additional arguments to TransSwing(). Transition starts on frame 180; Resulting length: 779
CutFrames(0, 400) - Cut out frames 0-400 from the video. Resulting length: 599
CutFrames(0, 400, 20) - Cut out frames 0-400 from the video using Dissolve() for a 20 frame fade from black (FadeIn). Resulting length: 599
CutFrames(200, 0) - Cut out frames 200-1000 from the video. Resulting length: 199
CutFrames(200, 0, 20) - Cut out frames 200-1000 from the video using Dissolve() for a 20 frame fade into black (FadeOut). Resulting length: 199
CutFrames(0, 400, 20, $FF0000) - Cut out frames 0-400 from the video using Dissolve() for a 20 frame fade from red (FadeIn). Resulting length: 599

Tips:

When using this multiple times, work backwards. Cut frames from the end of the clip first. By doing this, your cuts will be independent from each other.
Example:
CutFrames(900, 950)
CutFrames(800, 850)
CutFrames(650, 700)

Inserting ShowFrameNumber(x=10, y=30) before any cutframes() will show what frame you're currently on.

If your looking for audio cuts add something like this before CutFrames()
Histogram("stereo")
AddBorders(300, 0, 0, 0)
Histogram("Audiolevels")
ShowFrameNumber(x=width()-100, y=30)
ShowSMPTE()
Or a better way using AudioGraph() (http://avisynth.org.ru/docs/english/externalfilters/audiograph.htm). Stickboy made a nice wrapper (http://avisynth.org/stickboy/jdl-wrappers.avsi) for stereo output. http://forum.doom9.org/showthread.php?s=&threadid=59412
Grayscale()
ConvertToRGB()
AudioGraph(Width()/96)
ShowFrameNumber(x=10, y=30)
ShowSMPTE()
You can place these after all your CutFrames() to see the resulting sound/waveform.

Place your CutFrames() inside a function. This allows you to comment out all your cuts at once.

cut()
Function Cut(clip c)
{
c
CutFrames(254700, 0, 15)
CutFrames(194974, 254027, 15)
CutFrames(0, 189730, 15)
}
Taking this one step further, you can create multiple segments and order them in any way; selectively applying filters as well.
original = last
segmentA(original)
last + Grayscale(segmentB(original))
function segmentA(clip c)
{
c
CutFrames(254700, 0, 15)
CutFrames(194974, 254027, 15)
CutFrames(0, 189730, 15)
}
function segmentB(clip c)
{
c
CutFrames(3390, 0, 15)
CutFrames(580, 2700, 15)
CutFrames(0, 460, 15)
}



Footnotes:
Loop() (http://avisynth.org/mediawiki/Loop) can delete frames as well. Second to last example on page.

Future Ideas:
This is a RFC (Request For Comments), your input is greatly appreciated.

Edits:
March 4th, 2008: Got rid of ugly logic - Thanks Gavino
March 7th, 2008: Added error checking and FadeIn/FadeOut - Thanks stickboy for pointing out how this can fail and for header suggestions
March 8th, 2008: Improved End error checking
March 8th, 2008: Fixed cut points, now they are excluded - Thanks Gavino. Examples changed as a result.
March 9th, 2008: More code cleanup, added color option to fadeI/O, fixed end boundary condition - Thanks Gavino.

stickboy
2nd March 2008, 12:54
Even your simple version is problematic. Be careful when using Trim with variable arguments. (http://forum.doom9.org/showthread.php?s=&threadid=58358)

mikeytown2
2nd March 2008, 15:02
Thanks for pointing this out! After looking at your functions and reading the headers, i think i will use Trim3.
http://avisynth.org/stickboy/jdl-util.avsi :thanks:

You really like Assert, i had to go look it up.
http://avisynth.org/mediawiki/Internal_functions/Control_functions


So reworking my first example it should be something like this
function CutFrames(clip c, int start, int end)
{
return c.trim3(0, start) + c.trim3(end, c.Framecount())
}

Gavino
2nd March 2008, 21:16
This is a very simple script, but i would like it to be smart.function CutFrames(clip c, int start, int end)
{
return c.trim(0, start) + c.trim(end, c.Framecount())
}
I would like to make it aware of the other times it is called. So when i edit a video and call this function, multiple times, it knows. So if i remove the very first call to CutFrames i would not have to change all the other ones.

The solution to your problem is much easier: simply apply your cuts in reverse order in your script, eg

CutFrames(4444, 5555)
CutFrames(2222, 3333)
...

Then if you decide to remove any of the cuts, the frame numbers in the other ones will still be valid.

Gavino

Ebobtron
3rd March 2008, 08:08
Hello,
Lots of ways to skin this cat, here is one I use often, it is easier for me to understand weeks, maybe months, into something.


some source filter or filters
# source filters return a clip "last"
# which is equivalent to " last = source filter "
# unless assigned to a clip variable,
# (for example " source = source filter ")

trim(framestart, frameend) + trim(fs, fe) + trim(fs, fe) + trim(fs, fe)
# yep that sucks, and if there are a lot of them it really sucks

aclip = trim(fs, fe)
# aclip is assigned the clip returned by " trim(last, fs, fe) "
aclip = aclip + trim(fs, fe)
# The just keep doing it
aclip = aclip + trim(fs, fe)
aclip = aclip + trim(fs, fe)
aclip = aclip + trim(fs, fe)
aclip = aclip + trim(fs, fe)
aclip = aclip + trim(fs, fe)
aclip = aclip + trim(fs, fe)

# want to comment one out
#aclip = aclip + trim(fs, fe)

aclip = aclip + trim(fs, fe)

# at this point the script's output is still equal to " last '
#

aclip
# which is the same as last = aclip


# add dissolves between all clips
aclip = trim(fs, fe)
aclip = Dissolve(aclip, trim(fs, fe), 60)
aclip = Dissolve(aclip, trim(fs, fe), 60)
aclip = Dissolve(aclip, trim(fs, fe), 60)
aclip = Dissolve(aclip, trim(fs, fe), 60)

aclip


have fun ;)

mikeytown2
3rd March 2008, 19:30
@Gavino Thank you for the reverse order hint, that makes this a lot simpler to implement.

@Ebobtron The option to dissolve/blend the clips would be a good thing. [http://avisynth.org/mediawiki/Dissolve] In the long run, i would like to be able to support any transition. [http://avisynth.org/vcmohan/TransAll/docs/index.html] I think i would have to use an eval statement in order to do it. [http://avisynth.org/mediawiki/Internal_functions/Control_functions]

In terms of the structure of the AddCutPoint function, i think it would look something like this
AddCutPoint(clip c, int start, int end, string transition, int duration)

where clip c is the a/v stream to cut up. int start is the starting frame to cut, int end would be the last or ending frame to cut. String transition would be the function name, and int duration is how long the transition will last. This function call would then store these 5 values into a 5 dim array. the only question i have is about storing a clip into an array; is this a bad idea? Int's and strings shouldn't be an issue but i'm not sure about the clip variable. The reason for storing the clip variable would be in the case of multiple a/v clips in a single script.


Anyway here is the simple script with transitions addedfunction CutFrames(clip c, int start, int end, string "transition", int "duration")
{
duration = default(duration, 0)
return Defined(transition) ? Eval(transition + "(c.trim(0, " + string(start) + "), c.trim(" + string(end) + ", c.Framecount()), " + string(duration) + ")") : c.trim(0, start) + c.trim(end, c.Framecount())
}


CutFrames(200, 800, "Dissolve", 60)


EDIT
I think i like this function better
function CutFrames(clip c, int start, int end, int "duration", string "transition")
{
duration = default(duration, 0)
transition = default(transition, "Dissolve")
return Eval(transition + "(c.trim(0, " + string(start) + "), c.trim(" + string(end) + ", c.Framecount()), " + string(duration) + ")")
}


CutFrames(200, 800, 60)

Gavino
4th March 2008, 01:30
@Gavino Thank you for the reverse order hint, that makes this a lot simpler to implement.

...

In terms of the structure of the AddCutPoint function, i think it would look something like this
AddCutPoint(clip c, int start, int end, string transition, int duration)


My point was that if you adopt the 'reverse order' strategy (ie put the cuts in reverse chronological order), you no longer need the AddCutPoint function. This approach will still work with your extended CutFrames function which supports transitions.

Gavino

mikeytown2
4th March 2008, 03:31
@Gavion I was planing on using a sort array method, to order the array from largest to smallest, inside the ProcessCutPoints() function. Without your helpful input, i probably would have done it another way, with a lot of math calc involved.

After looking at [http://avisynth.org/mediawiki/Arrays] and [http://avslib.sourceforge.net/functions/a/arraycreate.html], i have decided to drop the idea of processing multiple clips simultaneously. The ProcessCutPoints() function should clear the arrays, so that it can still do cuts on multiple clips; just not all at the same time. Speaking of arrays i think 4 single dim arrays would be the easiest to do with the avisynth language. I just need a way to sort this, otherwise as Gavion has pointed out, my simple method is quite adequate.

Any input would greatly be appreciated, should i go for the 2 step process or should i leave this as is?


EDIT
Multiple options can be passed to the blending function now
function CutFrames(clip c, int start, int end, int "duration", string "transition", string "opt1", string "opt2", string "opt3")
{
duration = default(duration, 0)
transition = default(transition, "Dissolve")
return
\ Defined(opt3) ? Eval(transition + "(c.trim(0, " + string(start) + "), c.trim(" + string(end) + ", c.Framecount()), " + string(duration) + ", " + String(opt1) + ", " + String(opt2) + ", " + String(opt3) + ")") :
\ Defined(opt2) ? Eval(transition + "(c.trim(0, " + string(start) + "), c.trim(" + string(end) + ", c.Framecount()), " + string(duration) + ", " + String(opt1) + ", " + String(opt2) + ")") :
\ Defined(opt1) ? Eval(transition + "(c.trim(0, " + string(start) + "), c.trim(" + string(end) + ", c.Framecount()), " + string(duration) + ", " + String(opt1) + ")") :
\ Eval(transition + "(c.trim(0, " + string(start) + "), c.trim(" + string(end) + ", c.Framecount()), " + string(duration) + ")")
}


CutFrames(200, 800, 60, "TransSwing", "false", "1", "1")
http://avisynth.org/vcmohan/TransAll/docs/TransSwing.html


You can also use this as a quick, apply effect to range function, that takes 2 clips as input
CutFrames(200, 150, 50, "EffectName")

Or instant replay
CutFrames(300, 150)

Or "Drunk Effect"
CutFrames(800, 100, 695)

Gavino
4th March 2008, 13:15
Any input would greatly be appreciated, should i go for the 2 step process or should i leave this as is?

It's a matter of taste, but I think the introduction of AddCutPoint and ProcessCutPoints (if that's what you mean by the 2 step process) is overkill, and the CutFrames function is all you need. The 'reverse order' approach solves the problem you initially identified, of each cut potentially changing the frame numbers to be used in the others.

Of course YMMV, and your time and effort are yours to spend as you wish. :)

Incidentally, I suggest a modification to your enhanced CutFrames function: change the types of opt1, opt2 and opt3 from 'string' to 'val'. This would allow you to pass numerical parameters without quotes.
Also (and again, it's a matter of personal taste), I would tidy up the last part as follows:

opt1=(Defined(opt1) ? ", "+string(opt1): "")
opt2=(Defined(opt2) ? ", "+string(opt2): "")
opt3=(Defined(opt3) ? ", "+string(opt3): "")
return Eval(transition + "(c.trim(0, " + string(start) + "), c.trim(" + string(end) + ", 0), " + string(duration) + opt1 + opt2 + opt3 + ")")

mikeytown2
4th March 2008, 22:50
Updated first post with latest function and put up examples as well as a very helpful tip.

Thanks to Gavino for your help in cleaning up the code and the tip.
Thanks to Ebobtron for pointing out transitions.
Thanks to stickboy for directing me towards his other trim functions; i didn't use them because of dependency issues though.

stickboy
5th March 2008, 20:37
Updated first post with latest Thanks to stickboy for directing me towards his other trim functions; i didn't use them because of dependency issues though.So you'd rather have functions that silently do the wrong thing when given certain inputs? You don't even bother using Assert() to validate them, nor do you describe in the comments which values to avoid.

It's not as if they're unlikely inputs. You only need to call CutFrames(c, 0, ...) for it to be wrong.

I've made my functions public domain for a reason. If you don't want to tell people to go download my scripts (and I don't think anyone actually has any problem with that), you're free to simply copy and paste the parts you need.

mikeytown2
7th March 2008, 22:54
So you'd rather have functions that silently do the wrong thing when given certain inputs? You don't even bother using Assert() to validate them, nor do you describe in the comments which values to avoid.

It's not as if they're unlikely inputs. You only need to call CutFrames(c, 0, ...) for it to be wrong.

I've made my functions public domain for a reason. If you don't want to tell people to go download my scripts (and I don't think anyone actually has any problem with that), you're free to simply copy and paste the parts you need.

It's nothing against your scripts, just me personally, any time i have to download multiple files to get a function working, i find that annoying; trying to avoid that if i can. Thank you for the option to paste your script into this.

I never considered 0 as an input for this function (was thinking they would use trim), now i think CutFrames handles this quite nicely.

I also added Preconditions to the header.



Where should i pass the BlankClip Color property into this function? i was thinking after int "duration".

Gavino
9th March 2008, 02:37
@mikeytown2: Looking at this again, I've just spotted something ...

General Examples: - Assuming clip is 1000 frames in length
CutFrames(200, 400) - Cut out frames 200-400 from the video. Resulting length: 800
...


In fact, it cuts out frames 201-399, for a resulting length of 801.
(and even if it did cut out 200-400, the length would then be 799)

As the function is written, frames 'start' and 'end' are excluded from the cut, ie they appear in the output clip. I assume this is an oversight and you intended it to work as described in the example.

Gavino

mikeytown2
9th March 2008, 03:24
@mikeytown2: Looking at this again, I've just spotted something ...


In fact, it cuts out frames 201-399, for a resulting length of 801.
(and even if it did cut out 200-400, the length would then be 799)

As the function is written, frames 'start' and 'end' are excluded from the cut, ie they appear in the output clip. I assume this is an oversight and you intended it to work as described in the example.

Gavino

Thanks for pointing this out. I think i have it fixed now. Trim acts kinda odd when you just want the first frame [http://avisynth.org/mediawiki/Trim].

Gavino
9th March 2008, 17:05
I think i have it fixed now.
Almost, but I think you've still got one of the boundary conditions wrong.
Since the last frame of a clip is actually framecount-1, this value (rather than framecount) should be the max allowed for end (and mapped internally to 0).

Also, it might be clearer to replace:

#Funkey Conditions
start = (start == 0) ? -1 : start
start = (start == 1) ? 0 : start

#Perform Cut
return start == -1 ?
\Eval(transition + "(d, c.trim(" + string(end+1) + ", 0), " + string(duration) + opts + ")"):
\end == 0 ?
\Eval(transition + "(c.trim(0, " + string(start-1) + "), d, " + string(duration) + opts + ")"):
\Eval(transition + "(c.trim(0, " + string(start-1) + "), c.trim(" + string(end+1) + ", 0), " + string(duration) + opts + ")")

by

# Select uncut frames (or blanks) as transition components:
clip1 = (start == 0) ? d : c.trim(0, -start)
clip2 = (end == 0) ? d : c.trim(end+1, 0)

# Perform transition:
Eval(transition + "(clip1, clip2," + string(duration) + opts + ")")

The effect is the same, but this way it seems clearer what the special cases are and how they are handled. (And by always using the negative Trim argument to select the first start frames, start=1 is no longer a special case)

Gavino

mikeytown2
9th March 2008, 23:25
Gavino thank you for improving this function, it would be a lot uglier without your help!


I was thinking about the fade color option, is there any way to test if an int is hex ($000000), if it has a "$" in it? If there is, then an idea i have is to get rid of the fade_color variable and use the start and end variables instead. Because the only time the blank clip is used is when start or end = 0; then if start or end is a color it would set the color with that value and then set start or end to 0.
Example: CutFrames(0, 400, 20, $FF0000) would be CutFrames($FF0000, 400, 20) and CutFrames(400, 0, 20, $FF0000) would be CutFrames(400, $FF0000, 20).
This would eliminate the unnecessary color variable in this example: CutFrames(200, 400, 20, $000000, "TransSwing", false, 1, 1)

Good/Bad, is it even possible idea?

Gavino
10th March 2008, 00:57
I was thinking about the fade color option, is there any way to test if an int is hex ($000000), if it has a "$" in it? If there is, then an idea i have is to get rid of the fade_color variable and use the start and end variables instead.
It's not possible to distinguish an int passed in hex from one passed in decimal; once you get your hands on it it's just an int value.

If passing an unnecessary color parameter bothers you, you could move fade_color to the end of the function parameter list, then pass it by name on the infrequent occasions that a non-default value is wanted.

There seems to be a typo in your latest edit:
bg_color = default(fade_color,$000000)
should be
fade_color = default(fade_color,$000000)

(or maybe you meant to use bg_color as the parameter to BlankClip later?)

mikeytown2
10th March 2008, 01:05
Thanks for the catching the typo! I originally called it background color, then i thought fade described it better. Thanks for looking into the int/hex issue, it's not a major issue, just thought it might be better if it was possible. I'll leave fade_color where it is.