# AVSLib :: 'filters' package :: 'edit' module
# Copyright (c) 2005 George Zarkadas (gzarkadas@users.sourceforge.net)

# This program is free software; you can redistribute it and/or modify it under the terms of 
# the GNU General Public License as published by the Free Software Foundation; either 
# version 2 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this program; 
# if not, write to the "Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
# MA  02111-1307  USA"

__lib__ = "avslib" #$$ [__lib__ = "avslib-debug"]

DefineModule(__lib__, "filters", "edit")

global __load__ = Eval("""
	LoadModule(__lib__, "base", "core")
	LoadModule(__lib__, "numeric", "core")
	LoadModule(__lib__, "string", "core")
	""")

# Edit...xxx operation constants specifying 
# how to join resulting clip parts after editing 

global EDOP_ADD = 0			# using UnallignedSplice (+)
global EDOP_ALGN = 1		# using AllignedSplice (++)
global EDOP_DISS = 2		# using Dissolve(...parts... extra_info)
global EDOP_USER = 3		# using user-defined func [extra_info(...parts...)]


# Trims c in the range [fstart..fend) or [fstart..fstart+fcount) depending 
# of which argument is specified.
# 'fstart', 'fend' can be negative in which case c.Framecount is added to 
# them (this allow specifying offsets from the end of clip)
# If fend<=fstart or fcount==0 or fstart >= c.Framecount a null clip is 
# returned.
# If neither 'fend' nor 'fcount' are specified, an [fstart..c.Framecount) 
# clip is returned.

	Function __edit_trim_fend(clip base, int fstart, int fend)
	{
		fc = base.Framecount
		fstart = fstart < 0 ? fstart + fc : fstart
		fend = fend < 0 ? fend + fc : fend
		fstart = Clamp(fstart, 0, fc)
		fend = Clamp(fend, 0, fc)
		frames = fend - fstart
		return (fstart < fc && frames > 0) \
			? base.Trim(fstart, -frames) \
			: Null(base)
	}
	
	Function __edit_trim_fcount(clip base, int fstart, int fcount)
	{
		fc = base.Framecount
		fstart = fstart < 0 ? fstart + fc : fstart
		fend = fstart + fcount # do this step BEFORE clamping
		fstart = Clamp(fstart, 0, fc)
		fend = Clamp(fend, 0, fc)
		return (fstart < fc && fcount > 0) \
			? base.Trim(fstart, -fcount) \
			: Null(base)
	}
	
# Allow fstart, fend to go out of [0..c.Framecount) bounds to aid coding 
# (else an explicit check is required in user code to avoid error dialog
# box). Return only the clip part inside bounds or Null if no frame is 
# inside [fstart..fend).

Function EditTrim(clip base, int fstart, int "fend", int "fcount")
{
	bend = Defined(fend)
	bcount = Defined(fcount)
	Assert(!(bcount && bend), \
		"EditTrim: only one of 'fend' and 'fcount' arguments is allowed")
	# fend must be defaulted if we want 
	# calls of the type EditTrim(c, start)
	fend = Default(fend, base.Framecount)
	fcount = Default(fcount, 0)
	Assert(fcount >= 0, \
		"EditTrim: negative values for 'fcount' are not allowed")
	return (bcount) \
		? __edit_trim_fcount(base, fstart, fcount) \
		: __edit_trim_fend(base, fstart, fend)
}

# Replaces 'rep_frames' frames of 'base' clip with 'ins_frames' frames of 'inclip' clip,
# starting at frame position 'frame'.
# The operation is the sum of a Trim and an Insert and thus the length of the resulting
# clip may be different from 'base' clip's length.
# * 'frame' is the frame-number to start replacing from. 'frame' is included in the replace,
# so that result.Trim(frame, -1) is the first frame of 'inclip'. The value of 'frame' is
# always clamped between zero and base.Framecount. Thus values <= 0 result in replacement
# starting from the start of 'base' clip and values >= base.Framecount in addition of 
# 'inclip' (trimmed to 'ins_frames' length) to the end of base.
# * 'ins_frames' is the number of frames to insert from 'inclip' (always starting from
# the begining of it). If 'ins_frames' = 0 or not supplied, the entire 'inclip' will be
# inserted (the same as supplying inclip.Framecount). The value of 'ins_frames' is always
# clamped between zero and inclip.Framecount. 
# * 'rep_frames' is the number of frames to delete from 'base', starting from 'frame'
# (always starting from the beginning of it). If 'rep_frames' is not supplied, up to 
# 'ins_frames' will be replaced, depending on the number of available frames up to the
# end of the 'base' clip. If 'rep_frames' = 0 the operation is same as inserting 'inclip'
# (trimmed to 'ins_frames' length) at position 'frame'. The value of 'rep_frames' is
# always clamped between zero and Max(0, base.Framecount - frame), i.e. maximum
# available frames after frame. 
# Note that 
# 1) 'ins_frames' frames of 'inclip' (after clamping) will be inserted in any case,
#    regardless of the value of frame.
# 2) As a result of clamping, a positive 'rep_frames' argument specifies the maximum
# requested 'base' frames for replacement. Less frames may actually be replaced if 
# 'frame' is near the end of 'base' clip and thus frame + rep_frames is greater than
# base.Framecount. 
# * 'op' is the operation to perform for joining the parts of new clip, ie: 
#   base[0...frame), inclip[0...ins_frames) and base[frame+rep_frames...base.Framecount)
# Use one of EDIT_OP_xxx constants for this argument.
# * 'extra_info' is used if 'op' is 2 or 3. In the first case it must be an int 
# specifying the overlap frames for the call to Dissolve; in the later case it must 
# be the name of a function that can accept 0-3 clip arguments (make all args optional)
# and returns the user-specified combination of them.
#
Function EditReplace(clip base, clip inclip, int frame, int "ins_frames", \
	int "rep_frames", int "op", string "extra_info") 
{
	# Arguments checks and 'normalisation'.
	op = Default(op, EDOP_ADD)
	extra_info = Default(extra_info, "")
	Assert(op >= 0 && op <= 3, \
		"Edit: invalid value of 'op' argument")
	flag = (op == EDOP_DISS) \
		? (Int(Value(extra_info)) > 0 ? true : false) \
		: true
	Assert(flag, \
		"Edit: EDOP_DISS requires a positive integer 'extra_info' argument")
	flag = (op == EDOP_USER) \
		? (extra_info != "" ? true : false) \
		: true
	Assert(flag, \
		"Edit: EDOP_USER requires to supply the name of " \
		+ "a function in the 'extra_info' argument")
	# Calculation of frames
	bfc = base.Framecount
	ifc = inclip.Framecount
	frame = frame < 0 ? frame + bfc : frame
	frame = Clamp(frame, 0, bfc)
	ins_frames = Defined(ins_frames) \
		? Clamp(ins_frames, 0, ifc) \
		: ifc
	ins_frames = ins_frames == 0 \
		? ifc \
		: ins_frames	# treat specially the zero case
	rep_frames = Defined(rep_frames) \
		? Clamp(rep_frames, 0, __max2(0, bfc - frame)) \
		: ins_frames
	# Performance of edit operations
	intemp = inclip.EditTrim(0, ins_frames)
	# first part of base clip
	b1temp = base.EditTrim(0, frame)
	# second part of base clip (with frames to replace trimmed off)
	b2temp = base.EditTrim(frame + rep_frames)
	# Statement string construction
	s1str = (b1temp.Framecount > 0) ? "b1temp" : ""
	s2str = (b2temp.Framecount > 0) ? "b2temp" : ""
	icstr = (intemp.Framecount > 0) ? "intemp" : ""
	_nul = ""
	_cma = ","
	startstr  = Select(op, _nul, _nul, "Dissolve(", extra_info+"(" ) 
	middlestr = Select(op, "+" , "++", _cma , _cma )
	endstr    = Select(op, _nul, _nul, _cma+extra_info+")", ")" )
	var_str = \
		((s1str == "") ? "" : s1str + middlestr) + \
		((icstr == "") ? "" : icstr + middlestr) + \
		((s2str == "") ? "" : s2str + middlestr)
	# Trim last middle string if there is at least one non-null term
	var_str = (var_str == "") \
		? var_str \
		: StrLeft(var_str, StrLen(var_str) - StrLen(middlestr))
	# Check if all strings are null 
	# (only happens if null clips are passed as argument values)
	return (var_str != "") \
		? Eval(startstr + var_str + endstr) \
		: Null(base)
} 

# Special case of EditReplace where nothing from the 'base' clip is replaced.
#
Function EditInsert(clip base, clip inclip, int frame, int "ins_frames", \
	int "op", string "extra_info")
{
	return EditReplace(base, inclip, frame, ins_frames, 0, op, extra_info)
}

# Special case of EditReplace where nothing from the 'inclip' clip is inserted.
#
Function EditDelete(clip base, int frame, int "del_frames", int "op", \
	string "extra_info")
{
	del_frames = Default(del_frames, base.Framecount)
	return EditReplace(base, Null(base), frame, 0, del_frames, op, extra_info)
}

# Joins linearly up to 50 clips, performing on each join the operation 
# and additional parameters supplied by 'op' and 'extra_info' arguments
#(see EditReplace).

	Function __ed_join2(clip c1, clip "c2", int "op", string "extra_info")
	{
		return Defined(c2) \
			? c1.EditInsert(c2, c1.Framecount, 0, op, extra_info) \
			: c1
	}

Function EditJoin(clip c1, clip c2, clip "c3", clip "c4", clip "c5", clip "c6", \
	clip "c7", clip "c8", clip "c9", clip "c10", clip "c11", clip "c12", \
	clip "c13", clip "c14", clip "c15", clip "c16", clip "c17", clip "c18", \
	clip "c19", clip "c20", clip "c21", clip "c22", clip "c23", clip "c24", \
	clip "c25", clip "c26", clip "c27", clip "c28", clip "c29", clip "c30", \
	clip "c31", clip "c32", clip "c33", clip "c34", clip "c35", clip "c36", \
	clip "c37", clip "c38", clip "c39", clip "c40", clip "c41", clip "c42", \
	clip "c43", clip "c44", clip "c45", clip "c46", clip "c47", clip "c48", \
	clip "c49", clip "c50", int "op", string "extra_info")
{
	_ret = __ed_join2(  c1,  c2, op, extra_info)
	_ret = __ed_join2(_ret,  c3, op, extra_info)
	_ret = __ed_join2(_ret,  c4, op, extra_info)
	_ret = __ed_join2(_ret,  c5, op, extra_info)
	_ret = __ed_join2(_ret,  c6, op, extra_info)
	_ret = __ed_join2(_ret,  c7, op, extra_info)
	_ret = __ed_join2(_ret,  c8, op, extra_info)
	_ret = __ed_join2(_ret,  c9, op, extra_info)
	_ret = __ed_join2(_ret, c10, op, extra_info)

	_ret = __ed_join2(_ret, c11, op, extra_info)
	_ret = __ed_join2(_ret, c12, op, extra_info)
	_ret = __ed_join2(_ret, c13, op, extra_info)
	_ret = __ed_join2(_ret, c14, op, extra_info)
	_ret = __ed_join2(_ret, c15, op, extra_info)
	_ret = __ed_join2(_ret, c16, op, extra_info)
	_ret = __ed_join2(_ret, c17, op, extra_info)
	_ret = __ed_join2(_ret, c18, op, extra_info)
	_ret = __ed_join2(_ret, c19, op, extra_info)
	_ret = __ed_join2(_ret, c20, op, extra_info)

	_ret = __ed_join2(_ret, c21, op, extra_info)
	_ret = __ed_join2(_ret, c22, op, extra_info)
	_ret = __ed_join2(_ret, c23, op, extra_info)
	_ret = __ed_join2(_ret, c24, op, extra_info)
	_ret = __ed_join2(_ret, c25, op, extra_info)
	_ret = __ed_join2(_ret, c26, op, extra_info)
	_ret = __ed_join2(_ret, c27, op, extra_info)
	_ret = __ed_join2(_ret, c28, op, extra_info)
	_ret = __ed_join2(_ret, c29, op, extra_info)
	_ret = __ed_join2(_ret, c30, op, extra_info)

	_ret = __ed_join2(_ret, c31, op, extra_info)
	_ret = __ed_join2(_ret, c32, op, extra_info)
	_ret = __ed_join2(_ret, c33, op, extra_info)
	_ret = __ed_join2(_ret, c34, op, extra_info)
	_ret = __ed_join2(_ret, c35, op, extra_info)
	_ret = __ed_join2(_ret, c36, op, extra_info)
	_ret = __ed_join2(_ret, c37, op, extra_info)
	_ret = __ed_join2(_ret, c38, op, extra_info)
	_ret = __ed_join2(_ret, c39, op, extra_info)
	_ret = __ed_join2(_ret, c40, op, extra_info)

	_ret = __ed_join2(_ret, c41, op, extra_info)
	_ret = __ed_join2(_ret, c42, op, extra_info)
	_ret = __ed_join2(_ret, c43, op, extra_info)
	_ret = __ed_join2(_ret, c44, op, extra_info)
	_ret = __ed_join2(_ret, c45, op, extra_info)
	_ret = __ed_join2(_ret, c46, op, extra_info)
	_ret = __ed_join2(_ret, c47, op, extra_info)
	_ret = __ed_join2(_ret, c48, op, extra_info)
	_ret = __ed_join2(_ret, c49, op, extra_info)
	_ret = __ed_join2(_ret, c50, op, extra_info)

	return _ret
}
