Improving the quality from a large collection of clips from the Adobe Premiere project manager by using the power of After Effects and its scripting feature. The basic idea behind this is that when working in a multicamera environment, each footage should undergo its own specific processing, depending from which camera it comes. (These differences have to do with CCD-noise levels and de-interlacing settings, depending on noise and movement in the recorded material.) This will reduce the differences between the footage from each camera. It's like giving each camera it's own profile.
Adobe Premiere
After the project has been optimised by its project manager, you are stuck with a tremendous amount of seperated clips. Making projects and compositions by hand with all these clips in After Effects, is too painstaking. So here comes scripting!
After Effects Processing
If you have DV-files, you could use the Fieldskit Deinterlacer and the (build in) Remove Grain effects in order to get proper deinterlacing and film-like footage. But if you have hundreds of clips would you like to do the following steps by hand?
1.Interpret footage setting: Fields OFF
2.Create Comp, add footage to it; length, size and framerate will be same as footage
3.*User interaction: select which Fieldskit deinterlace effect preset should be used
4.*User interaction: select which remove grain preset should be used
5.Apply fieldskit effect with the selected preset/template
6.Pre-compose
7.Apply remove-grain effect with selected preset/template
8.Add a mask which will be the TV 'safe area'
9.Add comp to render queue with correct settings by using two templates.
For creating the presets, a small utility is available. It extracts from a composition which contains a layer with some selected effect its settings and writes that to a file. See the second post for that.
How to use the footageToFilm script
(In combination with Adobe Premiere, once your Project is finished do the following:
~1.In Adobe Premiere go to Project->Project Manager
~2.Select the following (default settings):
~-----Create New Trimmed Project
~-----Exclude Unused Clips
~-----Include Handles 25 Frames
~-----Rename Media Files to Match Clip Names
~-----Set a new folder name for your Project Destination
~3.Click OK
Premiere analyses your project, copies only those parts of your footage which are being displayed.
~4. Once done, close Premiere, open the .prproj Premiere project with After Effects, continue with step 3. below)
1. Make a project in After Effects
2. Add footage
3. Select the footage which should undergo these 9 steps
4. Start script, follow it's instruction
The rendered output files will be written in the same folder as the source footage files with a preceding 'R_Fx_'. To change this see function myDataObj. If an alternativ location should be specified, you should change the function add2RenderQ. Please note that if the footage origin is coming from a network source, the filename for the rendered output cannot not (yet) be created properly. You should correct this in the Render Queue manually.
FOR THE RENDER QUEUE FOLLOWING IS VERY IMPORTANT!
In order to create proper output in the Render Queue, you must create a template with your best settings, called: "BestSettings"
And an output module with your preferred codec, called: "LosslessCodec"
Code: Select all
//###############################################################################
//# FootageToFilm script
//# What it does basically:
//# Creates from footage which is in the project window automatically a composition
//# to which basically the Fieldskit Deinterlacer and the Remove Grain effects
//# will be applied.
//# The settings for these two effects are taken from a template file, for which
//# file dialog windows appear in which these files can be selected.
//#
//# What is it for?
//# Improving quality of clips from a Premiere project. After the project has been
//# optimized by its project manager, you are stuck with a tremendous amount of
//# separated clips. Making a project with all these clips in After Effects is
//# too painstaking. So here comes scripting!
//#
//# What it does in steps: (See function performAction() below)
//# For a group of selected footage files in the project, each file will be treated as follows:
//# 1.interpret footage setting: Fields OFF
//# 2.Create Comp, add footage to it; length, size and frame rate will be same as footage
//# 3.*User interaction: select which fieldskit effect preset should be used
//# 4.*User interaction: select which remove grain preset should be used
//# 5.apply fieldskit effect with the selected preset/template
//# 6.pre-compose
//# 7.apply remove-grain effect with selected preset/template
//# 8.Add a mask which will be the TV 'safe area'
//# 9.add comp to render queue with correct settings by using two templates.:
//#
//# The two templates for the Render Queue need exactly the following names
//# 1. BestSettings
//# This has all details concerning the Render Settings.
//# 2. LosslessCodec
//# This has all details concerning the Output Module.
//#
//# Written in June-September 2005, by Marc Nijdam
//###############################################################################
{
//###############################################################################
//# \/ \/ \/ \/ Program Entry point \/ \/ \/ \/
//###############################################################################
//# declare and initialise variables and some objects
//###############################################################################
var proj = app.project; // Set project
String.prototype.trimExt=trimExtension; // String method usage: .trimExt() if present, removes file extension
var Fn = new Array(); // This Array will hold objects with effectdata and functionality
Fn[0] = new EffectsObj; // Fn[0] is an object from EffectsObject; Create object, initialise and do the constructor
Fn[1] = new EffectsObj;
myConst = new myDataObj; // This Object Stores Data about a.o Final Mask (So called 'Safe Area') and other constants
//###############################################################################
//# Check validity of project
//# Load effect array with objects for each effect
//# Iterate through each selected footage file
//###############################################################################
var selectedFootage=examineProject(proj);
if (typeof selectedFootage != 'string') // Alert error if a string is being returned, it contains a guru notice.
{
// **** Prepare Data ****
Fn=getEffectData(proj,Fn); // Stuff array with objects which hold data about the to be applied effects
if (validEffectData(Fn)) // Check whether user pressed cancel in one of the dialogs
{
// **** Perform Action; iterate through each selected Footage in the Project window ****
proj.renderQueue.showWindow(true); // Show progress in the Render Queue window
app.beginUndoGroup("Footage to Film"); // Create undo group
for (var i = 0; i <selectedFootage> projClt ;project object (=app.project)
//# --> curItem ;this is the avi or mov file
//# <created>= cNMyData.nameMaxLength) // abbreviate long names, by chopping off part of the middle. Long names will throw an error!
{
cN=cN.substring(0, cNMyData.nameStartLength)+cNMyData.nameAbbTxt+cN.substring(cN.length-(cNMyData.nameStartLength+cNMyData.nameAbbTxt.length+cNMyData.comp2Name.length)-1, cN.length);
}
return crProj.items.addComp(cNMyData.comp2Name + cN,crCurItem.width,crCurItem.height,crCurItem.pixelAspect,crCurItem.duration-.005,crCurItem.frameRate);
}
//###############################################################################
//#
//# precompose a specific layer
//#
//# --> fproj ;project object (=app.project)
//# --> fprojComp ;comp with Fieldskit applied (typeName=Composition)
//# --> fprcnm ;The name for the precomp
//# <precomp> 24) flayerName = flayerName.substring(0, 23);
fnCompName += flayerName;
return fprojComp.layers.precompose([1], fnCompName, true );
}
//###############################################################################
//#
//# myDataObj object; this objects holds some constants, which are collected here all together.
//#
//###############################################################################
function myDataObj() // // ***** Object + constructor
{
this.maskTop = 10;
this.maskBottom = 10;
this.maskLeft = 33;
this.maskRight = 33;
this.nameMaxLength = 27; // a long filename will be abbreviated
this.nameStartLength = 8; // 1234567890123456789012345678901234
this.nameAbbTxt = "...";// Fx_filename...tail_of_name_001
this.comp1Name = "~Fx_";
this.comp2Name = "Fx_";
this.renderNameHead ="R_";
this.templateSetting ="BestSettings";
this.outputModule ="LosslessCodec";
}
//###############################################################################
//#
//# addMask
//# adds a mask to a specified layer
//# ---> composition
//# ---> maskdata
//#
//###############################################################################
function addMask(aMprojComp,aMdata)
{
var maskGroup = aMprojComp.layer(1).property("Masks");
var mask = maskGroup.addProperty("Mask"); // Create a new mask
var atC =aMdata.maskTop;
var abC =aMprojComp.height-aMdata.maskBottom;
var arC =aMdata.maskLeft;
var alC =aMprojComp.width-aMdata.maskRight;
var mskShape = new Shape();
mskShape.vertices = [ [alC,atC], [arC,atC], [arC,abC], [alC,abC] ];
mskShape.closed = true;
maskShape = mask.property("maskShape"); // Get the Mask Shape property for the mask
maskShape.setValue(mskShape); // Change the mask shape (not keyframed)
return;
}
//###############################################################################
//#
//# EffectsObj object
//#
//###############################################################################
function EffectsObj() // // ***** Object + constructor
{
this.EFFSTR = "ADOBE_Property" + "_";
this.EFFNAME = "ADOBE_EffectName";
this.succes = false;
this.guru = ""; //this string might hold error after processing
this.Apply = FFApply; // call to function FFApply();
String.prototype.trim = trimSpaces; // method usage: .trim() removes leading and trailing spaces
String.prototype.parse = parseLine; // method usage: .parse(_character_) beginning at position 0, search for = character, return what's after = character
String.prototype.isFfPrp = isEffectProperty; // method usage: .isFfPrp() returns true if this line has = sign, false if it hasn't
String.prototype.cntFfDat = countData; // method usage: .cntFfDat() returns the amount of numbers on this line. (e.g. "[1.0,5.4]".cntFfDat()=2)
String.prototype.getFfIDat = getIndexedData; // method usage: .getIndexedData(_index_) returns the n-th number on this line. (e.g. "[1.0,5.4]".getFfIDat(2)=5.4)
String.prototype.isFfComment = isComment; // method usage: .isFfComment() true if this line is regarded as comment (starts with ;)
String.prototype.getFfPrpNr = getPropertyNumber; //method usage: .getFfPrpNr() returns the number, which follows after EFFSTR (e.g. ADOBE_Property_2=Point B will give 2)
String.prototype.getFf2Array = getLine2Array; // method usage: .getFf2Array() returns array from a given string (e.g. [1,2,3,4,5] -> [0] = 1, [1]=2,.., [4]=5)
Array.prototype.reOrder = reOrderArray; // method usage: .reOrder() returns reordered array, taking into account dependencies defined by a specific effect (first item in array says which effect)
}
function FFApply()
{
var FFename;
var FFedata;
var FFvalue;
var FFbase;
var FFi=0;
var FFj;
var FF1;
this.FFArray.reOrder(); // optimize array, remove comment and properties without values; This line may be removed for compatibility options.
while ( ( this.FFArray[FFi].indexOf(this.EFFNAME) == -1) && (FFi < this.FFArray.length-1) ) FFi++; // search for occurrence of string EFFNAME
if (FFi <this>testfile)
{
var tEstr=this; // 'this' is only readable...
var tEi=tEstr.length;
while ( (tEstr.charAt(tEi) != '.') && (tEi > 0) ) tEi--;
if (tEi != 0) return tEstr.substring(0,tEi);
return tEstr; }
function parseLine(pLq) // extract part after character pLq
{
var pLstr=this; // 'this' is only readable...
while ( (pLstr.charAt(0) != pLq) && (pLstr.length>0)) pLstr = pLstr.substring(1);
return pLstr.substring(1);
}
function isEffectProperty() // does it start with EFFSTR and has this line the '=' character? if so, return true, else false
{
if ( this.indexOf("=") == -1) return false; // first check for presence of = sign;
iEP= new EffectsObj; // need temporarily EFFSTR
if ( this.substring(0,iEP.EFFSTR.length) == iEP.EFFSTR )
{
iEP=null;
return true;
}
iEP=null;
return false;
}
function countData() // count the amount of numbers
{
if (this.length==0) return 0;
var cDi;
var cDNaN=false;
var cDBrB=0;
var cDBrE=0;
var cDcnt=1;
for (cDi=0; cDi<this>1) || (cDBrE>1) || (cDBrB != cDBrE) ) return 0;
return cDcnt;
}
function getIndexedData(gID) // will return the n-th number on this line
{
if (gID==0) return -1; // index =0 is not valid
if (gID>this.cntFfDat()) return -1; // out of range
if (this.cntFfDat()==1)
{
if ( (this.trim().charAt(0) != "[") || (this.trim().charAt(this.trim().length-1) != "]") ) return parseFloat(this.trim()); // there's only one number
}
if ( this.trim().length<=2) return 0; // or write 0.0? anyway, check length
if ( (this.trim().charAt(0) != "[") || (this.trim().charAt(this.trim().length-1) != "]") ) return -1; // wrong array notation; string should be like [1,2,..,n]
var gIDstr=this.trim().substring(1, this.trim().length-1).trim(); // remove [ and ]
if (gIDstr.cntFfDat()==1) return parseFloat(gIDstr); // someone put a single number in an array notation e.g. [20.5]
for (var gIDi=1; gIDi<gID>= 1) return parseFloat( gIDstr.substr( 0 , gIDstr.indexOf(",") ) ); // trim the rest of the numbers on this line
return parseFloat(gIDstr); // there's only one number
}
function isComment()
{
if (this.trim().charAt(0)==";") return true;
return false;
}
function getPropertyNumber() // will return number between second '_' character and '=' character
{
if (this.length==0) return -1;
var gPNi;
var gPNEqs=0;
var gPNUsc=0;
for (gPNi=0; gPNi<this>= 1) && (gPNEqs == 1) )
{
if ( this.trim().substring(this.trim().lastIndexOf("_")+1,this.trim().indexOf("=") ).trim().cntFfDat() == 1 ) return parseInt(this.trim().substring(this.trim().lastIndexOf("_")+1,this.trim().indexOf("=") ).trim());
}
return -1;
}
function getLine2Array() // will return an array with all data from a string
{
if (this.length == 0) return [-1];
if (this.cntFfDat() == -1) return [-1];
var gL2A = new Array( this.cntFfDat() );
var gL2Ai;
for (gL2Ai=0; gL2Ai<=this.cntFfDat()-1; gL2Ai++)
{
gL2A[gL2Ai]=this.toString().getFfIDat(gL2Ai+1);
}
return gL2A;
}
function reOrderArray()
{
if (this.length==0) return this;
var FFrOA=this;
var FFrOi;
var FFrOstr="";
var FFrOj=0;
for (FFrOi = 0; FFrOi < this.length; FFrOi++) // remove all lines with comment
{
if ( this[FFrOi].toString().isFfComment()) ;
else FFrOA[FFrOj++]=this[FFrOi];
}
FFrOA.length=FFrOj; // correct now the Array length
FFrOEP= new EffectsObj; // need temporarily EFFSTR
if ( FFrOA[0].indexOf(FFrOEP.EFFNAME) == -1) return FFrOA; // First line should contain the string EFFNAME
FFrOEP=null; // discard object
if (FFrOj==1) return FFrOA; // The current Array holds only item; the line with string EFFNAME, but no further property or data; so finished for now!
FFrOj=1;
var FFrOprOrd = new Array();
for (FFrOi = 1; FFrOi <FFrOA> Project
//# ---> Array
//# <Array> Array
//# <Array> app.project
//# ---> composition
//#
//###############################################################################
function add2RenderQ(a2rProj,a2rComp,a2rConst)
{
a2rProj.renderQueue.items.add(a2rComp);
var a2rQ = a2rProj.renderQueue.item(a2rProj.renderQueue.numItems); // added rendertask will be the last in the stack
a2rQ.applyTemplate(a2rConst.templateSetting);
a2rQ.outputModules[1].applyTemplate(a2rConst.outputModule);
var a2rOldLocation=a2rQ.outputModule(1).file;
a2rQ.outputModule(1).file=new File(a2rProj.file.path + "/" + "R_"+a2rOldLocation.name);
return;
}
//###############################################################################
//#
//# performAction
//#
//# perform actions to a selected footage file in this project
//# ---> proj,selection,EffectsArray
//#
//###############################################################################
function performAction(exProj,exSelection,exFn)
{
var newComp; //holds a composition
var newLayr; //holds a layer
var newPrcp; //holds a precomposition
if ( (exSelection.hasVideo) && (exSelection.typeName == "Footage") ) //check if item is a video file
{
// change interpret footage by setting separate fields
exSelection.mainSource.fieldSeparationType=FieldSeparationType.OFF;
// Create new comp with length, size and framerate. Name will be same as source, with preceding Fx and omitted extension
newComp=createNcomp(exProj,exSelection,myConst);
// Add Footage into this comp
newLayr = newComp.layers.add(exSelection); //variable for collection of layer objects in logoComp
// Apply Fieldskit effect
exFn[0].layr=newLayr; //set layer to which effect must be applied
exFn[0].Apply(); //apply effect
if (exFn[0].guru) { alert(exFn[0].guru); return; } //anounce any errors
// Precompose previous layer
newPrcp=fprecomp(exProj,newComp,myConst);
// Apply Remove Grain
exFn[1].layr=newComp.layers[1];
exFn[1].Apply();
if (exFn[1].guru) { alert(exFn[1].guru); return; } //anounce any errors
// Add Mask
addMask(newComp,myConst); // myConst holds data about the mask borders
// Add to Render Queue
add2RenderQ(exProj,newComp,myConst);
}
return;
}
}