Difference between revisions of "Afx Javascript"

From bernie's
Jump to navigation Jump to search
(No difference)

Revision as of 20:03, 1 April 2021

Scripts

Copy footage to local folder as proxy

oZ24pac.png

/* 
 * simpleLocalProxy.jsx v1.0
 *
 * Copies footage from its current location to one chosen by the user (defaults to /tmp/) and allows to
 * switch original-proxy with a button
 * To be used when file i/o is slow on the network and you want file on your local SSD. Less black-box version of After Effects' file cache on scratch disks ('conformed media')
 *
 *
 * https://github.com/berniebernie/after-effects-scripts
 *    
 * Copyright 2015, bernie@berniebernie.fr
 *    
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT  
 *
 * This script embeds js-md5 from github: https://github.com/emn178/js-md5 for practical reasons
 *
 * Script that can be launched or put in the scriptui folder of After Effects to be used as a panel
 *  
 *
 *
 */



/*
 * js-md5 v0.1.2
 * https://github.com/emn178/js-md5
 *
 * Copyright 2014, emn178@gmail.com
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 */



/************************************************************************************************************
 *
 *
 *
 *      js-md5.js
 *
 *
 *
 ************************************************************************************************************/


//(function(root, undefined){
  //'use strict';

  var HEX_CHARS = "0123456789abcdef";
  var HEX_TABLE = {
    '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
    'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,
    'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15
  };

  var R = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
           5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20,
           4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
           6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21];

  var K = [0XD76AA478, 0XE8C7B756, 0X242070DB, 0XC1BDCEEE,
           0XF57C0FAF, 0X4787C62A, 0XA8304613, 0XFD469501,
           0X698098D8, 0X8B44F7AF, 0XFFFF5BB1, 0X895CD7BE,
           0X6B901122, 0XFD987193, 0XA679438E, 0X49B40821,
           0XF61E2562, 0XC040B340, 0X265E5A51, 0XE9B6C7AA,
           0XD62F105D, 0X02441453, 0XD8A1E681, 0XE7D3FBC8,
           0X21E1CDE6, 0XC33707D6, 0XF4D50D87, 0X455A14ED,
           0XA9E3E905, 0XFCEFA3F8, 0X676F02D9, 0X8D2A4C8A,
           0XFFFA3942, 0X8771F681, 0X6D9D6122, 0XFDE5380C,
           0XA4BEEA44, 0X4BDECFA9, 0XF6BB4B60, 0XBEBFBC70,
           0X289B7EC6, 0XEAA127FA, 0XD4EF3085, 0X04881D05,
           0XD9D4D039, 0XE6DB99E5, 0X1FA27CF8, 0XC4AC5665,
           0XF4292244, 0X432AFF97, 0XAB9423A7, 0XFC93A039,
           0X655B59C3, 0X8F0CCC92, 0XFFEFF47D, 0X85845DD1,
           0X6FA87E4F, 0XFE2CE6E0, 0XA3014314, 0X4E0811A1,
           0XF7537E82, 0XBD3AF235, 0X2AD7D2BB, 0XEB86D391];

  var jsmd5 = function(message) {
    var blocks = hasUTF8(message) ? UTF8toBlocks(message) : ASCIItoBlocks(message);
    var h0 = 0x67452301;
    var h1 = 0xEFCDAB89;
    var h2 = 0x98BADCFE;
    var h3 = 0x10325476;

    for(var i = 0, length = blocks.length;i < length;i += 16)
    {
      var a = h0;
      var b = h1;
      var c = h2;
      var d = h3;
      var f, g, tmp, x, y;

      for(var j = 0;j < 64;++j)
      {
        if(j < 16)
        {
          // f = (b & c) | ((~b) & d);
          f = d ^ (b & (c ^ d));
          g = j;
        }
        else if(j < 32)
        {
          // f = (d & b) | ((~d) & c);
          f = c ^ (d & (b ^ c));
          g = (5 * j + 1) % 16;
        }
        else if(j < 48)
        {
          f = b ^ c ^ d;
          g = (3 * j + 5) % 16;
        }
        else
        {
          f = c ^ (b | (~d));
          g = (7 * j) % 16;
        }

        tmp = d;
        d = c
        c = b

        // leftrotate
        x = (a + f + K[j] + blocks[i + g]);
        y = R[j];
        b += (x << y) | (x >>> (32 - y));
        a = tmp;
      }
      h0 = (h0 + a) | 0;
      h1 = (h1 + b) | 0;
      h2 = (h2 + c) | 0;
      h3 = (h3 + d) | 0;
    }
    return toHexString(h0) + toHexString(h1) + toHexString(h2) + toHexString(h3);
  };

  var toHexString = function(num) {
    var hex = "";
    for(var i = 0; i < 4; i++)
    {
      var offset = i << 3;
      hex += HEX_CHARS.charAt((num >> (offset + 4)) & 0x0F) + HEX_CHARS.charAt((num >> offset) & 0x0F);
    }
    return hex;
  };

  var hasUTF8 = function(message) {
    var i = message.length;
    while(i--)
      if(message.charCodeAt(i) > 127)
        return true;
    return false;
  };

  var ASCIItoBlocks = function(message) {
    // a block is 32 bits(4 bytes), a chunk is 512 bits(64 bytes)
    var length = message.length;
    var chunkCount = ((length + 8) >> 6) + 1;
    var blockCount = chunkCount << 4; // chunkCount * 16
    var blocks = [];
    var i;
    for(i = 0;i < blockCount;++i)
      blocks[i] = 0;
    for(i = 0;i < length;++i)
      blocks[i >> 2] |= message.charCodeAt(i) << ((i % 4) << 3);
    blocks[i >> 2] |= 0x80 << ((i % 4) << 3);
    blocks[blockCount - 2] = length << 3; // length * 8
    return blocks;
  };

  var UTF8toBlocks = function(message) {
    var uri = encodeURIComponent(message);
    var blocks = [];
    for(var i = 0, bytes = 0, length = uri.length;i < length;++i)
    {
      var c = uri.charCodeAt(i);
      if(c == 37) // %
        blocks[bytes >> 2] |= ((HEX_TABLE[uri.charAt(++i)] << 4) | HEX_TABLE[uri.charAt(++i)]) << ((bytes % 4) << 3);
      else
        blocks[bytes >> 2] |= c << ((bytes % 4) << 3);
      ++bytes;
    }
    var chunkCount = ((bytes + 8) >> 6) + 1;
    var blockCount = chunkCount << 4; // chunkCount * 16
    var index = bytes >> 2;
    blocks[index] |= 0x80 << ((bytes % 4) << 3);
    for(var i = index + 1;i < blockCount;++i)
      blocks[i] = 0;
    blocks[blockCount - 2] = bytes << 3; // bytes * 8
    return blocks;
  };

  /*if(typeof(module) != 'undefined')
    module.exports = jsmd5;
  else if(root)
    root.jsmd5 = jsmd5;*/
//}(this));


/************************************************************************************************************
 *
 *
 *
 *      simpleLocalProxy.jsx 
 *
 *
 *
 ************************************************************************************************************/


function e(str){
    //uncomment to allow debugging
    //$.writeln(str);
    
}

function pathToLocalizedPath(path){
        f = new File(path);
        return f.fsName.toString();
}

function sequenceFilesWildcard(path){ 
    //returns false or an array with everything before a sequence's image number, and the extension: /c/path/file.0555.exr > { /c/pathfile. ; .exr }
    var myRegexp = /(.*[\.\-_a-z])[\d]{1,}(\.[a-zA-Z]*)$/g; 
    var match = myRegexp.exec(path);
    if(!match){
        return false;
    }else{
        return match;
    }
}
function grabPaths(footage){
    //returns false or the path of the given footage, if it's a file sequence, returns the path with a wildcard for the current frame number: /c/path/file.0555.exr > /c/path/file.*.exr
    var f = footage;
    returnpath = false;
    if(f instanceof FootageItem && f.file != null){       
        var source = f.mainSource.file.toString();
        if(!f.mainSource.isStill){
            var pathFromRegex = sequenceFilesWildcard(source);
            if(pathFromRegex){
                returnpath = pathFromRegex[1]+"*"+pathFromRegex[2];
            }else{
                returnpath = source;
            }
        }else{
            returnpath = source;
        }
    }
    return returnpath;
}

function grabFootagePathsAndCopyToLocal(){
    //main worker function
    
    var sel = app.project.selection;
    if(sel.length < 1 || !(sel[0] instanceof FootageItem)){
        alert("Select footage(s) and try again");    
    }else{
        
        var debugcheck = (getPref("debug","false")=="true")?true:false;
        var useforcecopy = (getPref("forcecopy","false")=="true")?true:false;
        
        var isMacintosh = ($.os.toLowerCase().indexOf("windows")==-1);
        var localSaveDir = getPref("localSaveDir",Folder.temp.toString());
        
        var batchFile = (isMacintosh)?"# bash file used to copy After Effects footage to local storage":"@echo off\nREM batch file to copy After Effects footage to local storage";
        for(i=0;i<app.project.selection.length;i++){
            if(!sel[i].useProxy){
                //grab path from current selection item
                var curPath = grabPaths(sel[i]);
                var fileName = curPath.split('/').pop();
                var dir = curPath.substring(0,curPath.lastIndexOf('/')+1);
                
                var outputDir = "";
                if(getPref("usemd5",true)=="true"){
                    outputDir = localSaveDir + "/"+ jsmd5(dir);
                }else{
                    outputDir = localSaveDir + dir;
                }
            
                if(isMacintosh){
                    //macos uses rsync to copy files
                    
                    batchFile += "\nrsync -v -a ";
                    batchFile += ((useforcecopy)?"-I ":"");
                    batchFile += dir+fileName;
                    batchFile += " "+outputDir;
                    
                }else{
                    
                    //windows uses robocopy
                    sourcePath = pathToLocalizedPath(dir);
                    destinationPath = pathToLocalizedPath(outputDir);
                    filename = fileName.replace(/%20/g, " ");
                    
                    batchFile += "\nrobocopy ";
                    batchFile += "\"";
                    batchFile += sourcePath;
                    batchFile += "\"";
                    batchFile += " ";
                    //robocopy filename requires a wildcard, even if it's a single file
                    batchFile += "\"";
                    batchFile += destinationPath ;
                    batchFile += "\"";                    
                    batchFile += " ";
                    batchFile += "\"";
                    batchFile += filename ;
                    batchFile += "*\"";
                    batchFile += ((useforcecopy)?" /XO":"")+" /FFT"+((debugcheck)?"":" /NJH /NJS");
                    e(batchFile);
                }
            }
        }
        batchFile += ((debugcheck)?(isMacintosh?"\nread a":"\npause"):""); //nested tertiary operators, sue me
        
        var txtFile;
        if(isMacintosh){
            txtFile = new File(localSaveDir+"/AFX_footage_copy.command");
        }else{
            txtFile = new File(localSaveDir+"/AFX_footage_copy.bat");
        }
        if(txtFile.open("w","TEXT","????") == true){

            txtFile.write(batchFile);
            txtFile.close();
	    if(isMacintosh){

            		system.callSystem("chmod +x " + txtFile.toString() +"");
system.callSystem(txtFile.toString());
		}else{
            txtFile.execute();
}
        }else{
            alert("Write permission denied on\n"+localSaveDir);
        }
        //txtFile.remove();
    }
}
function grabFootagePathsAndSwitchProxy(){
    var sel = app.project.selection;
    if(sel.length < 1){
        alert("Select footage first");    
    }else{
        for(i=0;i<app.project.selection.length;i++){
            item = sel[i];
            if(item.useProxy){
                item.useProxy = false;
            }else{
                var curPath = grabPaths(item);
                var fileName = item.mainSource.file.toString().split('/').pop();
                var dir = curPath.substring(0,curPath.lastIndexOf('/')+1);
                var localSaveDir = getPref("localSaveDir",Folder.temp.toString());
                var outputDir = "";
                if(getPref("usemd5",true)=="true"){
                    outputDir = localSaveDir + "/"+ jsmd5(dir);
                }else{
                    outputDir = localSaveDir + dir;
                }
                var proxyFilePath = outputDir+"/"+fileName;
                var proxyFile = new File(proxyFilePath);
                e(proxyFilePath);
                if(proxyFile.exists){
                    if(item.mainSource.isStill){
                        item.setProxy(proxyFile);
                    }else{
                        //dirty workaround if the file is a movie file (.mpg, qucktime etc...)
                        try {
                            item.setProxyWithSequence(proxyFile,false);
                        }
                        catch(err) {
                            item.setProxy(proxyFile);
                        }   
                    }
                    item.proxySource.alphaMode = item.mainSource.alphaMode;
                    item.proxySource.premulColor = item.mainSource.premulColor;
                    item.proxySource.invertAlpha= item.mainSource.invertAlpha;
                }else{
                    if(getPref("debug",false)=="true"){
                        alert(">>> NO PROXY FOUND\n"+proxyFilePath);
                    }
                }
            }
        }
    }
}

function setPref(pref,value){
    app.settings.saveSetting("simpleLocalProxy", pref, value);
}
function getPref(pref,defaultValue){
    prefsVar = "simpleLocalProxy";
    if(app.settings.haveSetting(prefsVar, pref)){
        return app.settings.getSetting(prefsVar, pref);
    }else{
        app.settings.saveSetting(prefsVar, pref, defaultValue);
        setPref(pref,defaultValue)
        return defaultValue;
    }
}

function simpleLocalProxy(thisObj) {
    pan = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Simple Local Proxy", [100, 100, 300, 300]);
    
    var securitySetting = app.preferences.getPrefAsLong("Main Pref Section", "Pref_SCRIPTING_FILE_NETWORK_SECURITY");
    if (securitySetting != 1) {
        pan.add("statictext",[15,15,300,45],"Set prefs and re-launch");
        alert("You need to check \"Allow Scripts to Write Files and Access Network\" in your preferences for this script to work");
    }else{
        var localFolder = getPref("localSaveDir",Folder.temp.toString());
        localFolder = pathToLocalizedPath(localFolder).replace(/\\/g,"\\\\");
        
        // UI DESCRIPTION
        
        res = "group { alignment: ['fill','fill'], alignChildren: ['fill','top'], orientation: 'column', \
                        cols: Group {orientation:'row',align:'left', alignChildren:['fill','top'],\
                            col1: Group {orientation:'column',align:'left', alignChildren:['fill','center'],\
                                saveDirText: StaticText {text: 'Proxy folder setup'},\
                                saveDirOptions: Group {orientation:'column',align:'left', alignChildren:['fill','center'],\
                                    usemd5rbox: RadioButton {text: ' Simple',helpTip:'Uses a unique folder name per footage; no subfolders (32 character md5 hashes of filenames)',value:true},\
                                    usepathrbox: RadioButton {text: ' Copy Folder Structure',helpTip:'Copies the target folder structure inside the proxy folder; more folders'}}},\
                            col2: Group {orientation:'column',align:'left', alignChildren:['fill','center'],\
                                optionsText: StaticText {text: 'Options: '},\
                                optionsGrp: Group {orientation:'column',align:'left', alignChildren:['fill','center'],\
                                    forcecopyChkbox: Checkbox {text: ' Force copy',helpTip:'Overwrite files (otherwise skips existing files)'},\
                                    debugChkbox: Checkbox {text: ' Debug',helpTip:'Show full batch process and pause at end of copies'}}}},\
                        cols2: Group {orientation:'row',align:'left', alignChildren:['fill','top'],\
                            localSaveDirBut: Button {text: ' Choose Proxy Folder ', helpTip:'choose folder to copy files to',preferredSize:[-1,30]} , \
                            browseBut: Button {text: ' Browse ' , preferredSize:[-1,30]}} , \
                        curentDirTxt: EditText {text: '" + localFolder +"',enabled:false},\
                        copyFootageBut: Button {text: ' Copy Footage(s) to Proxy Folder ' ,helpTip:'Launches a background batch copy of selected footage',preferredSize:[-1,30]} , \
                        switchproxyBut: Button {text: ' Switch Proxy/Original ',helpTip:'Switches from original to proxy path and back,  warning if no local copy has been found' ,preferredSize:[-1,30]} , \
                    }";

        //UI DRAW
        
        pan.grp = pan.add(res); 
        pan.layout.layout(true);
        
        //radio buttons and checkboxes prefs
          

        var usemd5 = getPref("usemd5",true);
        pan.grp.cols.col1.saveDirOptions.usemd5rbox.value = (usemd5=="true")?true:false;
        pan.grp.cols.col1.saveDirOptions.usepathrbox.value = (usemd5=="true")?false:true;

        var useforcecopy = getPref("forcecopy","false");
        pan.grp.cols.col2.optionsGrp.forcecopyChkbox.value = (useforcecopy=="true")?true:false;
        var debugcheck = getPref("debug","false");
        pan.grp.cols.col2.optionsGrp.debugChkbox.value = (debugcheck=="true")?true:false;

        pan.layout.resize();
        pan.onResizing = pan.onResize = function () {this.layout.resize();}

        // UI ACTIONS
        
        //browse button
        pan.grp.cols2.browseBut.onClick = function(){
                localSaveDir = new Folder(getPref("localSaveDir",Folder.temp.toString()));
                localSaveDir.execute();
        }
        //choose local folder button
        pan.grp.cols2.localSaveDirBut.onClick = function(){
            localSaveDir = new Folder(getPref("localSaveDir",Folder.temp.toString()));
            o = localSaveDir.selectDlg("Choose folder to copy footage to");
            if(o!=null){
                    setPref("localSaveDir",o.toString());
                    pan.grp.curentDirTxt.text = pathToLocalizedPath(o.toString());//.replace(/\\/g,"\\\\");
            }
        }
    
        pan.grp.copyFootageBut.onClick = function(){
            //copy footages button (launches the 'guts' of this script)
            
            //save prefs
            var usemd5 = pan.grp.cols.col1.saveDirOptions.usemd5rbox.value;
            var useforcecopy = pan.grp.cols.col2.optionsGrp.forcecopyChkbox.value;
            var debugcheck = pan.grp.cols.col2.optionsGrp.debugChkbox.value;
            setPref("usemd5",usemd5);
            setPref("forcecopy",useforcecopy);
            setPref("debug",debugcheck);
            
            grabFootagePathsAndCopyToLocal();
        }
        
        pan.grp.switchproxyBut.onClick = function(){
            //checks for a local copy of the footage, and switches to a proxy accordingly 
            
            //save prefs
            var usemd5 = pan.grp.cols.col1.saveDirOptions.usemd5rbox.value;
            var useforcecopy = pan.grp.cols.col2.optionsGrp.forcecopyChkbox.value;
            var debugcheck = pan.grp.cols.col2.optionsGrp.debugChkbox.value;
            
            setPref("usemd5",usemd5);
            setPref("forcecopy",useforcecopy);
            setPref("debug",debugcheck);

            grabFootagePathsAndSwitchProxy();
        }
    }
    if (pan instanceof Window) pan.show() ;
}
simpleLocalProxy(this);

Auto Expose Animation

Adds 'hold' keyframes to your animation if nothing is moving between frames (ie detects animation) -- better tutorial video TBD


//save as AutoExpose.jsx in your program files/after effects/scripts/ScriptUI folder
{
    
var currentSlider = "none";


 function watchFolderUI(thisObj){
    pan = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Auto Expose", [100, 100, 300, 300]);
    var res = 
    "group { \
                alignment: ['fill','fill'], \
                alignChildren: ['fill','top'], \
                orientation: 'column', \
                    setupDetector: Button {text: 'Step 1: setup detector on layer' ,preferredSize:[-1,30]} , \
                    txt1: StaticText {text: 'Increase resolution slider until Detector slider picks up changes (> 0)',properties:{multiline:true}} , \
                    bakeKeys: Button {text: 'Step 2: bake to keys' ,preferredSize:[-1,30],enabled:true} , \
                    txt2: StaticText {text: 'Bake to hold keys (0 and 1s) and check if they correspond to animation, otherwise increase resolution and run again',properties:{multiline:true}} , \
                    applyKeys: Button {text: 'Step 3: apply as time remapping on selected layers' ,preferredSize:[-1,30]} , \
                    txt3: StaticText {text: 'Select layers on which to apply time remapping (\"exposed\" keys). If you want sequential keys instead, turn expression on the time remapping',properties:{multiline:true}} , \
            }";	
    pan.grp = pan.add(res);        
    pan.grp.setupDetector.onClick = function () {
        //pan.grp.bakeKeys.enabled = true;        
        setupDetector();
        
    }
    pan.grp.bakeKeys.onClick = function () {bakeKeys();}
    pan.grp.applyKeys.onClick = function () {applyKeys();}

    pan.layout.layout(true);
    pan.layout.resize();
    pan.onResizing = pan.onResize = function () {this.layout.resize();}
    return pan;
    }
watchFolderUI(this) ;



function e(s){
    $.writeln(s);
}



function setupDetector(){
    app.beginUndoGroup("Auto expose setup Detector");
    var layers = app.project.activeItem.selectedLayers;
    curlayer = layers[0];

    var duplicatelayer = curlayer.duplicate();
    curlayer.moveBefore(duplicatelayer);
    var futureprecompindex = duplicatelayer.index;
    var precomp = app.project.activeItem.layers.precompose([duplicatelayer.index],duplicatelayer.name+"_anim_detection",true);
    var precomplayer =  app.project.activeItem.layer(futureprecompindex);
    precomplayer.guideLayer = true;

    var allLayers = app.project.activeItem.layers;
    for(i=1;i<=allLayers.length;i++){
        if(allLayers[i].enabled){
            allLayers[i].solo = false;
        }
    }
    precomplayer.enabled = true;    
    precomplayer.solo = true;

    var toplayer = precomp.layers[1];
    var props = toplayer.property("ADBE Effect Parade");
    while(props.numProperties>0){
        props.property(1).remove();
    }
    var newlayer = toplayer.duplicate(); 

    newlayer.blendingMode = BlendingMode.CLASSIC_DIFFERENCE;
    newlayer.startTime += app.project.activeItem.frameDuration;
    newlayer.timeRemapEnabled = true;
    newlayer.inPoint -= app.project.activeItem.frameDuration;

    var explainer = new MarkerValue("1 frame shift + difference blendmode = highlight pixel changes");
    newlayer.property("Marker").setValueAtTime(.5, explainer);

    var blackSolid = precomp.layers.addSolid([0,0,0], "Black", precomp.width, precomp.height, 1);
    blackSolid.moveToEnd();


    var sliderctrl = precomplayer.Effects.addProperty("ADBE Slider Control");
    sliderctrl.name = "Resolution";
    var resolutionslider = sliderctrl.property("ADBE Slider Control-0001");
   
    resolutionslider.setValue(3);

    var detectorctrl = precomplayer.Effects.addProperty("ADBE Slider Control");
    detectorctrl.name = "Detector";
    var detectorslider = detectorctrl.property("ADBE Slider Control-0001");
    detectorexpression = "\
    resolution = effect(\"Resolution\")(\"ADBE Slider Control-0001\");\
    resolution = (resolution<1)?1:resolution;\
    a = [0,0,0,0];\
    for(i=0;i<resolution;i++){\
        for(j=0;j<resolution;j++){\
            center = [thisComp.width/resolution/2+thisComp.width/resolution * j ,thisComp.height/resolution/2+thisComp.height/resolution * i ];\
            sampledistance = [thisComp.width/resolution/2,thisComp.height/resolution/2];\
            a+= sampleImage(center, sampledistance , postEffect = true, t = time);\
        }\
    }\
    \
    (a[0]+a[1]+a[2]+a[3])/(resolution*resolution)*10000-10000;\
    ";

    detectorslider.expression = detectorexpression;
    currentSlider = detectorslider;
    app.endUndoGroup();
 }

function bakeKeys(){
    
    // bake slider keys, apply expression to figure out which frames have movement, then bake again to 'clean' expression
    bakeCommand = app.findMenuCommandId("Convert Expression to Keyframes");
    currentSlider.selected = true;
    app.executeCommand( bakeCommand );
    detectorexpression = "f = effect('Detector')('ADBE Slider Control-0001');\nf>0?1:0;";
    currentSlider.expression = detectorexpression;
    currentSlider.expressionEnabled = true;
    currentSlider.selected = true;
    app.executeCommand( bakeCommand );
    

    // travel backwards through keys and remove keys that are == 0
    // set the the value of kept frames to be that of the time they're on
    
    for(i=currentSlider.numKeys;i>0;i--){
        if(currentSlider.keyValue(i) > 0){
            currentSlider.setValueAtKey(i, currentSlider.keyTime(i));
            currentSlider.setInterpolationTypeAtKey(i,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD);
        }else{
            currentSlider.removeKey(i);
        }
    }

    // add '0' key on first frame
    
    currentSlider.addKey(0);
    currentSlider.setValueAtKey(1, 0);
    currentSlider.setInterpolationTypeAtKey(1,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD);

}
function applyKeys(){
    var layers = app.project.activeItem.selectedLayers;
    for(i = 0;i<layers.length;i++){
        layers[i].timeRemapEnabled = true;
        var remap = layers[i].property("Time Remap");
        for(j=1;j<=currentSlider.numKeys;j++){
            
            v = currentSlider.keyValue(j);
            t = currentSlider.keyTime(j);
            remap.addKey(t);
            remap.setValueAtKey(j, v);
            remap.setInterpolationTypeAtKey(j,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD);
        }
        remap.expression = "\
        //toggle this on to set sequential time remap\
        a = timeRemap;\
        nk = a.nearestKey(time);\
        curframe = 0;\
        if(nk.time > time){\
            curframe = nk.index-1;\
        }else{\
            curframe = nk.index;\
        }\
        curframe*thisComp.frameDuration;";
        remap.expressionEnabled = false;
    }
}


}

Create Null controllers on Puppet pins

HdFjaYZ.jpg

//nulls created from puppet pins can be parented like normal layers
{
    app.beginUndoGroup("Create Null Controls on Puppet Pinsv");

    function getLayerFromProperty(prop){
        return prop.propertyGroup(prop.propertyDepth)
    }


    var c = app.project.activeItem;
    if( c != null && c.selectedProperties != null){
        var props = c.selectedProperties;
        j = 0;
        for(i = 0;i<props.length;i++){
            if(props[i].matchName == "ADBE FreePin3 PosPin Atom"){
                j++;
                child = props[i].property("ADBE FreePin3 PosPin Position");
                pos = [child.value[0],child.value[1]];
                nullLayer = app.project.activeItem.layers.addNull();
                nullLayer.name = "puppetCtrl"+j;
                nullLayer.position.setValue(pos);
                var expr = "thisComp.layer(\""+nullLayer.name+"\").toWorld(thisComp.layer(\""+nullLayer.name+"\").transform.anchorPoint)";
                child.expression = expr;
            }
        }
    }
    app.endUndoGroup();
}

Batch replace file locations with text file

88QHvlQ.jpg

//now works with sequences or still images, windows only
//edit nov2017 so we get nicer paths (windows-like c:/file path instead of \c\file%20path
function URIToWinPath(path){
	str = path.replace(/\//, "");
	str = str.replace(/\//, ":/");
	str = str.replace(/%20/g, " ");
	str = str.replace(/\//g, "\\");
	return str;
}
function WinPathtoURI(path){
    //windows, for now, the only one available!
    str = "/"+path.replace(":\\", "/");
    str = str.replace(/\\/g, "/");
    str = str.replace(/ /g, "%20");
    //str = str.substring(0,str.lastIndexOf("/"));
    return str;
}
{
    app.beginUndoGroup("Change File Locations");
    
    var txtFile = new File("~/Desktop/tempAE.txt");
    txtFile.open("w","TEXT","????");
    txt = "";
    sel = app.project.selection;
    var isSequence = new Array();
    if(sel.length == 0){
       alert("Select footage items.");
    }else{
        for(i=0;i<app.project.selection.length;i++){
            isSequence[i] = !sel[i].mainSource.isStill;
            txt += URIToWinPath(sel[i].mainSource.file.toString())+"\n";
        }
        txtFile.write("*** Paths are written in Unix style, once edited simply _SAVE_ and press Yes in the AE prompt.Undo available if you screw up.***\n\n");
        txtFile.write(txt);
        txtFile.close();
        txtFile.execute();
        isOk = confirm("Change file paths ?\n\nReloading might take a while!");
        if(isOk){
           txtFile.open("r","TEXT","????"); 
           contents = txtFile.read();
           arrayContents = contents.split("\n");

           for(i=0;i<app.project.selection.length;i++){
                var tmpFile =  new File(WinPathtoURI(arrayContents[i+2]));
                //alert(tmpFile);
                if(isSequence[i]){
                    sel[i].replaceWithSequence(tmpFile,0);
                }else{
                    sel[i].replace(tmpFile);
                }
                
                writeLn(Math.round((i+1)/app.project.selection.length*100)+"%");
           }
        }
    }
    app.endUndoGroup();
}

Loop Selected Layers

82c30ae9dd47a979c727a3b4f30ad617.gif

No hassle looping

app.beginUndoGroup("Set loops");
var layersList = app.project.activeItem.selectedLayers;
var frameD = app.project.activeItem.frameDuration;
for (i=0;i<layersList.length;i++){
    if(!layersList[i].timeRemapEnabled){
        var outP = layersList[i].outPoint;
        layersList[i].timeRemapEnabled = true;
        layersList[i].timeRemap.setValueAtTime(outP-frameD,outP-frameD);
        layersList[i].timeRemap.setValueAtTime(outP,0);
        layersList[i].timeRemap.expressionEnabled = true;
        layersList[i].timeRemap.expression = "loopOut()";
        layersList[i].outPoint = app.project.activeItem.workAreaStart+app.project.activeItem.workAreaDuration;
    }
}
app.endUndoGroup();

AFX Shelf

Find Next Text Layer

for(var i = 1; i <= app.project.numItems; i++){
    a = true;
                if(!a){
                break;
                }
    if(app.project.item(i) instanceof CompItem){
        var comp = app.project.item(i);
        for(j = 1; j <=  comp.layers.length;j++){
            a = true;
            comp.layer(j).selected = false;
            if(comp.layer(j) instanceof TextLayer){
                comp.layer(j).selected = true;
                $.writeln(comp.name);
                a = confirm("Continue selecting text layers",true);
            }
            if(!a){
                break;
            }else{
                comp.layer(j).selected = false;
            }
        }
    }
}
if(a){
    alert("No (more) text layers found");
}

Simple Watchfolder

poTWO.png

I've been told that media encorder kind of does the same thing but I've never looked into scripting it

/*
Watchfolder.js v1.0
--------------------
By bernie @ berniebernie.fr

This script is a simple palette to automatically send your after effects file to be processed by the watchfolder
In essence it's a simplified Collect Files > Project only.

To be added:
- A check for missing footage
- Some way to parse logs to figure what has rendered and what hasn't
- Some UIs to turn AFX into a render slave & set some WF options
*/
{                 
watchfolderLocation = "none";
watchfolderLocation = (app.settings.haveSetting("watchfolderPrefs", "watchfolderLocation"))?(app.settings.getSetting("watchfolderPrefs", "watchfolderLocation")):watchfolderLocation;

function sendToWF(wf){
        var c = confirm(app.project.file.name+" needs to be saved first. Save ?",false,"Save Project");
        var saved = app.project.file.toString();
        if(c){
            app.project.save();
            var curFile = app.project.file.name;
            curFile = curFile.substring(0,curFile.length-4);
            var myFolder = new Folder(wf+"/"+curFile+"_watch/");
            myFolder.create();
            curFile += "_watchfolder";
            var mySaveFile = new File(myFolder.toString()+"/"+curFile+".aep");        
            app.project.save(mySaveFile);
            var myTextFile = new File(myFolder.toString()+"/"+curFile.substring(0,22)+"_RCF.txt");    
            myTextFile.open("w","TEXT","????");
            var text = "After Effects 10.0v1 Render Control File\nmax_machines=5\nnum_machines=0\ninit=0\nhtml_init=0\nhtml_name=\"\"\n" ;
            myTextFile.write(text);
            myTextFile.close();
            app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES);
            var opFile = new File(saved);
            if(opFile){
                app.open(opFile);
            }            
            writeln("Sent to watchfolder...");            
    }
}
function setWatchFolder(){
        var y = confirm("Watchfolder is currently set to \n\n\""+watchfolderLocation+"\"\n\nChange it ?");
        if(y){
            var v = Folder.selectDialog ("New watchfolder location").toString();
            if(v!=null && v!="undefined"){
                app.settings.saveSetting("watchfolderPrefs", "watchfolderLocation",v);     
                watchfolderLocation = v;
            }
        }
    }
 function watchFolderUI(thisObj){
    pan = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Watchfolder", [100, 100, 300, 300]);
    var securitySetting = app.preferences.getPrefAsLong("Main Pref Section", "Pref_SCRIPTING_FILE_NETWORK_SECURITY");
    if (securitySetting != 1) {
        pan.add("statictext",[15,15,300,45],"Set prefs and re-launch");
        alert("You need to check \"Allow Scripts to Write Files and Access Network\" in your preferences for this script to work");
     }else{
                var res = 
            "group { \
                        alignment: ['fill','fill'], \
                        alignChildren: ['fill','top'], \
                        orientation: 'column', \
                            setWF: Button {text: 'Set watchfolder' ,preferredSize:[-1,30]} , \
                            sendWF: Button {text: 'Send To Watchfolder' ,preferredSize:[-1,30]} , \
                    }";	
            pan.grp = pan.add(res);        
            pan.grp.setWF.onClick = function () {
                    setWatchFolder();
                    }
            pan.grp.sendWF.onClick = function () {
                if(watchfolderLocation=="none"){
                    setWatchFolder();
                    }else{
                        
                    sendToWF(watchfolderLocation);
                }}
            pan.layout.layout(true);
            pan.layout.resize();
            pan.onResizing = pan.onResize = function () {this.layout.resize();}
            return pan;
    }
}
watchFolderUI(this) ;
}

Show original source location (WIN only)

04O3r.png

{
    function pathToWinPath(path){
        var str = path.toString().replace(/\//, "");
        str = str.replace(/\//, ":/");
        str = str.replace(/%20/g, " ");
        str = str.replace(/\//g, "\\");
        return str;
    }

    sel = app.project.selection;
    if(sel.length == 0){
        alert("You need to select source(s) in the project panel");
    }else{
        for(i=0;i<app.project.selection.length;i++){
            a = prompt("'OK' continues, 'cancel' stops displaying original sources\n\n[ "+sel[i].name+" ]",pathToWinPath(sel[i].mainSource.file.path.toString()));    
            if(!a){break}
        }
    }
}



List selected layers effects and their properties matchNames

//windows only
if(app.preferences.getPrefAsLong("Main Pref Section","Pref_SCRIPTING_FILE_NETWORK_SECURITY")){
    var txtFile = new File("~/Desktop/effectList.txt");
    txtFile.open("w","TEXT","????");
    var col = app.project.activeItem.selectedLayers;
    for (i=0;i<col.length;i++){
        effs = col[i].property("ADBE Effect Parade");
            for(j=1;j<=effs.numProperties;j++){
                txtFile.write("\n( "+effs.property(j).matchName+" ) "+effs.property(j).name+"\n------------------------------------------\n");
                for(k=1;k<=effs.property(j).numProperties;k++){
                    txtFile.write(effs.property(j).property(k).matchName+" --> "+effs.property(j).property(k).name+"\n");

                    }
            }
    }

    txtFile.write("\n\n\nReminder (add effect and set property):\n\ns = app.project.activeItem.selectedLayers[0];\n");
    txtFile.write("v = s.Effects.addProperty(\"CC RepeTile\");\n");
    txtFile.write("v.property(\"CC RepeTile-0001\").setValue(10);");    
    txtFile.close();
    txtFile.execute();
}else{
    alert("Set scripting Prefs to enable to write to disk");
}

IEUwe.gif


List effects

 function projEffects(){
 	var effects = new Array();
 	var effects2 = new Array();
 	for(var i = 1; i <= app.project.numItems; i++){
 		if(app.project.item(i) instanceof CompItem){
 			   var comp = app.project.item(i);
 				for(j = 1; j <=  comp.layers.length;j++){
 					effs = comp.layer(j).property("ADBE Effect Parade");
 					for(k=1;k<=effs.numProperties;k++){
 						keyName = effs.property(k).matchName;
 						effects[keyName]  = 1;
 						}
 					   
 					}
 			}
 	}
 	for(a in effects){
 		effects2[effects2.length] = a;
 	}
 	effects2.sort();
 	return effects2;
 }
 alert(projEffects().join("\n"))

hFNiQ.jpg

Multiple sequences import (Windows)

// Multiple sequences import (windows)
// ----------------------------------------------
//  bernie@berniebernie.fr
//  This script takes an image as an input and loads the sequences it finds in the same folder using a batch (.bat) file
//  Access to files network required --- no error checking.
//   > only works on windows so far
//   > only finds exrs/pngs/jpgs
//   > probably fails if your sequences dont have the same start frame
//   > will work with /path/image.####.jpg or similar



function uniq_fast(a) {
    var seen = {};
    var out = [];
    var len = a.length;
    var j = 0;
    for(var i = 0; i < len; i++) {
         var item = a[i];
         if(seen[item] !== 1) {
               seen[item] = 1;
               out[j++] = item;
         }
    }
    return out;
}


function e(s){
    $.writeln(s);
}
curScript = new File($.fileName);

startT = Date.now();

sourceFolder = app.project.selection[0].mainSource.file.parent;
tmpFolder = Folder.temp;
tmpBat = Folder.temp.toString()+"/ae_dirlist.bat";
tmpFilesList = Folder.temp.toString()+"/ae_imageslist.txt";
var listFile = new File(tmpFilesList);



var batFile = new File(tmpBat);
batFile.open("w","TEXT","????");
batFile.write("REM Auto generated by this after effects script file: "+curScript.fsName);
batFile.write("\npushd \""+sourceFolder.fsName+"\"");
batFile.write("\ndir /b /on *.exr *.png *.jpg *.jpeg > \""+listFile.fsName+"\"");
batFile.close();
system.callSystem(batFile.fsName);


listFile.open("r","TEXT","????"); 
contents = listFile.read();
arrayContents = contents.split("\n");
listFile.close();
timer = (Date.now()-startT)/1000;
e("via batch: "+arrayContents.length+" files found in "+timer+"s ");


var myRe = new RegExp("[0-9]{2,}(\)){0,1}\.[a-z]+$");
var myArray = myRe.exec(arrayContents[0]);
endlength = myArray[0].length;

for(i = 0;i<arrayContents.length;i++){
    arrayContents[i] = arrayContents[i].slice(0, -endlength);
}
unique = uniq_fast(arrayContents);

dialog = confirm(unique.length+" sequences found. Load them ? \nIt might take a long time !",false,"Confirm Loading");

if(dialog){      
    for(i = 0;i<unique.length;i++){
        if(unique[i].length>0){
            sequenceStartFile = new File(sourceFolder.toString()+"/"+unique[i]+myArray[0]);
            
            if (sequenceStartFile) {
            writeLn("Loading "+(i+1)+"/"+unique.length);
                try {
                    // Create a variable containing ImportOptions.
                    var importOptions = new ImportOptions(sequenceStartFile);
                    importOptions.sequence = true;
                    try { 

                        app.project.importFile(importOptions);
                    } catch (error) {
                       e(error.toString());
                    }
                } catch (error) {
                    e(error.toString());
                }
            }
        }
    }
}else{
    writeLn("Canceled");
}
timer = (Date.now()-startT)/1000;
e("after regext: "+timer);

Split to Renderqueue

0f9dcb815b75fef03af6c16d340614b6.gif

// Split Layers to Renderqueue v2.0
// ----------------------------------------------
//
//  This script takes the one item from the renderqueue, and creates a file for each layer in the associated comp
//  using the in and out points. I use this along with 'Magnum' the edit detector to split & render sequences.
//
//  v2.0 added the index of the layer in the filename to prevent duplicates, alos ignore the 'base' renderqueue item that we derive everything from

function pad(n, width, z) {
  z = z || '0';
  n = n + '';
  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}

app.beginUndoGroup("Split Layers to Renderqueue");

if(app.project.renderQueue.items.length == 1){
    FE = app.project.renderQueue.items[1];
    ai = FE.comp;
    path = FE.outputModule(1).file.path;
    for(i=1;i<=ai.layers.length;i++){

        RI = app.project.renderQueue.items[1].duplicate();
        RI.timeSpanStart = ai.layers[i].inPoint;
        RI.timeSpanDuration = ai.layers[i].outPoint-ai.layers[i].inPoint;
        $.writeln(ai.workAreaDuration+" "+ai.layers[i].outPoint);
        RI.outputModule(1).file =  new File(path+"/"+pad(i,2,"0")+"_"+ai.layers[i].name);
    }
    app.project.renderQueue.items[1].render= false;
}else{
    alert("Only 1 element should be in renderqueue");
}
app.endUndoGroup;

Docked Panel SNIP

//script panel
{
	var nested_file = new File("U:\Matthieu Bernadat\afxscripts\Dandy_script.jsx");
	nested_file.open("r");
	eval(nested_file.read());
	nested_file.close();
}
//called file
function createUI(thisObj) {
	var myPanel = ( thisObj instanceof Panel) ? thisObj : new Window("palette", "Maya Track",[100, 100, 300, 300]);
	impButton = myPanel.add("button", [10, 10, 100, 30], "Import");
	//impButton.onClick = openfile;
	return myPanel;
}
var myToolsPanel = createUI(this);

Watchfolder watcher (WIP)

lhO3k.gif

Needs in the ScriptUI folder Nfw8h.png rgPGl.png N93S7.png VTGZl.png PjAkj.png (flag_orange.png, flag_green.png, flag_red.png,flag_graygreen.png)

//  watchwatchfolder: a tool to look at what's in the watchfolder
//  v1.0 by bernie - mbernadat@gmail.com
//
//
// icons by Mark Jame - http://www.famfamfam.com/lab/icons/silk/ licensed under a Creative Commons Attribution 2.5 License. 
//
//  known limitations (v1.0), to be fixed:
//  -windows only for now
//  -only looks at 1 renderqueue element per AEP file.
//
//
// I coded this like a dirty monkey. I feel sorry if you have to look at this.

/*
todo: check if icons are here
*/

{
//var watchfolderLocation = "/w/09_Dandelions_COMP-SHOWS/__WATCHFOLDER__";
var wf = app.settings.haveSetting("watchwatchfolder", "wfloc")?new Folder(app.settings.getSetting("watchwatchfolder", "wfloc")):null;
var scriptFile = new File($.fileName);
var scriptFolder = scriptFile.parent; //png icons should be here
b=0; //global string that will store the 
var cancelTaskID;
var pal;
var timer = 0;
var refreshRate = 5;  //seconds
var reloadEditTxt;
var firstTime = true;
var shotinfos = new Array();

//global ui names
var refreshBtn;
var list;
var wfStText;
var wfPbar;
var chckBox0;
var chckBox1;
var chckBox2;
var chckBox3;


function getLogonly(file){
    if(file.name.indexOf("Logs)") != -1){
        return true;
    }
        return false;
 }
function pathToWinPath(path){
	str = path.replace(/\//, "");
	str = str.replace(/\//, ":/");
	str = str.replace(/%20/g, " ");
	str = str.replace(/\//g, "\\");
	return str;
}
function localToRessource(path){
    //windows, for now, the only one available!
    str = "/"+path.replace(":\\", "/");
    str = str.replace(/\\/g, "/");
    str = str.replace(" ", "%20");
    str = str.substring(0,str.lastIndexOf("/"));
    return str;
}
function returnFolderArray(location){
    fold = new Folder(location.toString());
    foldArray = fold.getFiles()
    folderOnly = new Array();
    for(i=0;i<foldArray.length;i++){
        if(foldArray[i] instanceof Folder){
            folderOnly[folderOnly.length] = foldArray[i];
            }
        }
    return folderOnly;
}
function logInfo(logFolderLocation){
    
    curFold = new Folder(logFolderLocation.toString());
    folds = returnFolderArray(logFolderLocation);
    txtfiles = fold.getFiles("*RCF.txt"); //there should only be one
    htmlfiles =  fold.getFiles("*.htm");
    var htmlfile;
    if(htmlfiles[0]){
        htmlfile = htmlfiles[0].toString();
    }
  // $.writeln("file >>> "+txtfiles[0].toString());
  var info = [logFolderLocation.name.toString(),1,logFolderLocation.toString(),logFolderLocation.name.toString()+"rrr"];
    var fcontents = "";
    if(txtfiles[0]){
        f = txtfiles[0].toString();
        f = new File(f);
        f.open("r","TEXT","????");
        fcontents = f.read();
        f.close();
        var findItems = new RegExp("^(item)[0-9]{1,}(=\()(.*)(\))$","mi");
        v=findItems.exec(fcontents);
        if(v){
            if(v[4].indexOf("Stopped") != -1){
                info=["name",1,v[4].substring(1,v[4].indexOf(","))];  
            }else if(v[4].indexOf("In Progress") != -1){
                info=["name",2,"Rendering"];  
            }else if(v[4].indexOf("Finished") != -1){
                info=["name",0,""];  
            }

        }else{
           // $.writeln("Buggy file");
            info=[logFolderLocation.name.toString(),1,"--Bug--",logFolderLocation.toString()+"eeee"];  
        }
        fold = new Folder(folds[0].toString());
        files = fold.getFiles("*.txt"); 
        firstFile = files[files.length-1]; //used to be first file, works better with last text file
        if(firstFile == undefined || firstFile == null){
              info=[logFolderLocation.name.toString(),1,"Error",htmlfile]; 
        }else{
                //info = ["debug",logFolderLocation.toString(),logFolderLocation.name.toString()];
                //info = [logFolderLocation.name.toString(),(info[1]==4)?1:info[1],,logFolderLocation.toString(),0];
               
                firstFile.open("r","TEXT","????");
                contents = firstFile.read();
                firstFile.close();
                lines = contents.split("\n");
              //  $.writeln(logFolderLocation.toString()+" <<< ")
               info[3] = logFolderLocation.toString();
               info[0] = logFolderLocation.name.toString();
                for(i=0;i<lines.length;i++){
                    //$.writeln("Doing shit");
                    phrase ="Rendering started on";
                    info[4] = "n/a";
                    if(s = lines[i].indexOf(phrase) != -1){
                        info[4] = lines[i].substring(s+phrase.length);        
                    }
                    phrase ="Output To: ";
                   
                   // info[2] = "n/a+";
//                    info[1] = 1;
                    
                    if(info[1]==0 ||info[1]==2){
                       // $.writeln( lines[i].indexOf(phrase));
                            if(s = lines[i].indexOf(phrase) != -1){
                                s = lines[i].indexOf(phrase);
                                v = lines[i].substring(s+phrase.length);
                                wfold = v.substring(0,v.lastIndexOf("\\"));
                               //$.writeln(wfold);
                                f =  wfold.substring(wfold.lastIndexOf("\\")+1,wfold.length);
                                ///info[3] = pathToWinPath(wfold);
                                 info[3] =localToRessource(wfold);
                                 $.writeln(info[3])
                                info[0] = f;
                               // info[2] = " ";
                            }
                        }
                   // info[1] = ;
                
                }
              //  $.writeln(info);
               // $.writeln(contents);
        }
   
    }
  $.writeln(info);
    return info;
            
}
function hasStarted(watchedFolderLocation){
    fold = new Folder(watchedFolderLocation.toString());
    files = fold.getFiles(getLogonly);
    //$.writeln(files.length);
    if(files.length>0){
        return true;
    }else{
        return false;
    }
}

function getFilePath(watchedFolderLocation){
    fold = new Folder(watchedFolderLocation.toString());
    files = fold.getFiles(getLogonly);
    alert(files[0].name);
    return files[0].name;
}
/*
function getPercentage(folder){
    myFolder = new Folder(folder);
    files = myFolder.getFiles("*DandyWatch.txt");
    if(files.length != 1){
        return false;
    }
    myTextFile = files[0];    
    myTextFile.open("r","TEXT","????");
    contents = myTextFile.read();
    myTextFile.close();
    lines = contents.split("\n");
    outPutFolder = new Folder(lines[0]);
    files = outPutFolder.getFiles();
    f = files.length;
    writeLn(f+"/"+(lines[1]+1));
    return parseFloat(files.length/(lines[1]+1));
    //var percentage = new Array();
}
//getPercentage(watchfolderLocation+"/"+shot);
*/

function explore(location){
     fold = new Folder(location.toString());
     return fold.execute();     
}
function retrieveArray(){
    var state = 4; //queued, default state
     var vArray = new Array();
    l =0;
    counter=0;
    counter2=0;
    folders = returnFolderArray(wf);
    for(folder in folders){
    l++;
        var out = "";
        if(folders[folder].name != "anonymous"){ //??
            counter2++;
            if(hasStarted(folders[folder])){
                state = 0;
                out = logInfo(folders[folder]);
                //$.writeln(out);
                //out[3] = localToRessource(out[3]);
                                   
            }else{
                state = 4;
                out = [folders[folder].name,4,"",folders[folder].toString()];
            }
            //$.writeln(">>> "+state+" - "+out+" - "+folders[folder].name);
            
           // $.writeln(out);
            vArray[counter2] =out;
        }
      // $.writeln(vArray[counter2]);
        wfPbar.value = l/folders.length*100;

        if (pal instanceof Window){
            pal.update();
        }else{
            if(counter < Math.round(wfPbar.value/5,0)*5){
                counter +=5;
                clearOutput();
                writeLn(counter+"%");
            }else{
                write(".");
            }
        }
    }
    return vArray;
}
function gather(){
                refreshBtn.text = "refreshing...";
                refreshBtn.enabled = false;
                wfStText.visible = false;
                wfPbar.visible = true;
                shotinfos = retrieveArray();
                list.removeAll();
                for(i=1;i<shotinfos.length;i++){
                    var p = shotinfos[i];
                                      //  $.writeln("Debug");$.writeln(p);
                    addItem(p[0],p[1],p[2]);

                }
                writeLn("Done");
                wfStText.visible = true;
                wfPbar.visible = false;
                
                //set (or not) the timer
                refreshBtn.enabled = true;
                if(parseInt(reloadEditTxt.text) > 0){
                    timer = parseInt(reloadEditTxt.text)/refreshRate;

                    refreshBtn.text = "Click to stop ("+(timer*5)+"s)";
                    cancelTaskID = app.scheduleTask("loop()",refreshRate*1000,1);                
                }else{
                    refreshBtn.text = "Refresh";
                }
}
function watchWatchFolder(thisObj){

        //UI Design
        {
            pal = (thisObj instanceof Panel) ? thisObj : new Window("palette", "WatchFolder", undefined, {resizeable:true});
            var winGfx = pal.graphics;
            var darkColorBrush = winGfx.newPen(winGfx.BrushType.SOLID_COLOR, [0,0,0], 1);
            pal.bounds = [300,200,600,600];        
            var wfLocBtn = pal.add("button", [10,10,105,35],"Set Watchfolder");
            wfStTextContents = ( wf != null)?app.settings.getSetting("watchwatchfolder", "wfloc"):"(not set...)";
            wfStText = pal.add("edittext",[115,12,285,33], wfStTextContents);
                wfStText.active = false;
                wfStText.enabled = false;
            wfPbar = pal.add("progressbar",[115,12,285,33],0,100);
                wfPbar.visible = false;
            var sortStText = pal.add("statictext",[17,42,80,60], "Sort by: ");
            var sortByName = pal.add('radiobutton',[62,40,120,57], 'name');
            var sortByStatus = pal.add('radiobutton',[115,40,170,57], 'status');


            
                sortByName.value = true;
            var bottomElements = pal.add("panel",[10,310,260,360],undefined,{borderStyle:"none"});
            
            bl = 0;
            var arrayV = new Array("green","red","orange","graygreen","gray");      
            
            bottomElements.add('image',[5,bl,20,bl+14],scriptFolder.toString()+"/flag_"+arrayV[0]+".png");
            chckBox0 = bottomElements.add('checkbox',[22,bl,39,bl+16], '');
            chckBox0.value = true;
            chckBox0.helpTip = "Show finished renders";
            
            bottomElements.add('image',[45,bl,60,bl+14],scriptFolder.toString()+"/flag_"+arrayV[1]+".png");
            chckBox1 = bottomElements.add('checkbox',[62,bl,79,bl+16], '');
            chckBox1.value = true;
            chckBox1.helpTip = "Show errors";
            
            bottomElements.add('image',[85,bl,100,bl+14],scriptFolder.toString()+"/flag_"+arrayV[2]+".png");
            chckBox2 = bottomElements.add('checkbox',[103,bl,120,bl+16], '');
            chckBox2.value = true;
            chckBox2.helpTip = "Show rendering";
           
            bottomElements.add('image',[125,bl,140,bl+14],scriptFolder.toString()+"/flag_"+arrayV[4]+".png");
            chckBox3 = bottomElements.add('checkbox',[143,bl,160,bl+16], '');
            chckBox3.value = true;
            chckBox3.helpTip = "Show queued";
            
            bl = 25;
            var reloadStTxt =  bottomElements.add("statictext",[5,bl+2,50,bl+21], "Update: ");
            reloadEditTxt =  bottomElements.add("edittext",[55,bl,90,bl+20], 600);
            reloadEditTxt.enabled=false;
            refreshBtn =  bottomElements.add("button",[100,bl,260,bl+20], "Start");
            reloadEditTxt.helpTip = "in seconds, 0 for manual refresh only.";
            list = pal.add ("ListBox", [10, 65, 260,320], "desc",{numberOfColumns: 2,showHeaders: true});        
            
            
            //list.columnTitles = Array("First Name", "Last"); doesn't work in CS5 apparently
            
         }
     
     
        //UI Callbacks
        {
            wfLocBtn.onClick = function(){
                wfLoc = Folder.selectDialog("Select watchfolder");
                if(wfLoc != null){
                    app.settings.saveSetting("watchwatchfolder", "wfloc",wfLoc.toString());
                    wf = new Folder(wfLoc.toString());
                    if(wf){
                        wfStText.text = wfLoc.toString();
                    }
                }
            }    
            pal.onResize = function(){
                //because using layouts is too confusing for me
                list.bounds = [list.bounds[0],list.bounds[1],pal.bounds[2]-pal.bounds[0]-10,pal.bounds[3]-pal.bounds[1]-100];
                wfStText.bounds = [wfStText.bounds[0],wfStText.bounds[1],pal.bounds[2]-pal.bounds[0]-10,wfStText.bounds[3]];
                wfPbar.bounds = wfStText.bounds;
                bottomElements.bounds = [list.bounds[0],list.bounds[3]+10,list.bounds[2],list.bounds[3]+60];
            }
            list.onDoubleClick = function(){//onChange
                sel = list.selection;
               
                if(list.selection != null){
                    // $.writeln(shotinfos.join("\n"));
                    sL=shotinfos.length;
                    //$.writeln(sL);
                    for(i=1;i<sL;i++){
                       a=shotinfos[i];
                      //$.writeln(a);
                        //$.writeln(shotinfos[i][1]+" "+list.selection.toString());
                        if(a[0] == list.selection.toString()){
                           // $.writeln("ww"+);
                       //  $.writeln(a);
                            //explore(a[3]+((a[1]==2)?"/"+a[0]:""));
                            explore(a[3]);
                            break;
                            }
                        }
                    //$.writeln(sel[0].subItems[0].text);
                }


            }        
            
            
            refreshBtn.onClick = function(){
                if((timer>0 && !firstTime) || !pal.visible){
                        timer = 0;
                       // $.writeln("stopped");
                        refreshBtn.text = "Start";
                        firstTime = true;
                        cancelTask(cancelTaskID);
                }else{
                    if(reloadEditTxt.text == 0 || firstTime){
                            gather();
                            firstTime = false;
                    }
                }
                
                ///timer = parseInt(reloadEditTxt.text)/refreshRate;
                //refreshBtn.text = "Click to stop ("+(timer*5)+"s)";
                //cancelTaskID = app.scheduleTask("loop()",refreshRate*1000,1);
            }
            reloadEditTxt.onClick = function(){reloadEditTxt.enabled=true};
            reloadEditTxt.onChange = function(){
                if(reloadEditTxt.text < 10 || (parseInt(reloadEditTxt.text) != reloadEditTxt.text)){
                    timer = 0;
                    reloadEditTxt.text = 0;
                    refreshBtn.text = "Refresh";
                //}else if(reloadEditTxt.text >=10){
                }else{
                    if(timer <= 0){
                        refreshBtn.text = "Start (~ "+eggTimer(reloadEditTxt.text)+")";
                    }
                }
            reloadEditTxt.enabled=false;                
            }
        }
    
    
        //Initialize the whole shebang
            if (pal instanceof Window){
                pal.center();
                pal.show();
            }
	   
        
      return pal;
     }
//reloadEditTxt.notify("onChange");
ui = watchWatchFolder(this);
}

function eggTimer(time){
    if(time<=59){
        time = time+"s";
    }else if(time<=60*4){
        time = Math.floor(time/60)+"m "+(time%60)+"s";
    }else if(time<60*60){
        time = Math.round(time/60,0)+"m";
    }else{
        time = Math.round(time/(60*60),0)+"h";
    }
    return time;
    }
function loop(){
    timer--;
  //  $.writeln(timer+" pal.visible:"+pal.visible);
    refreshBtn.text = "Click to stop ("+eggTimer(timer*5)+")";
    if(timer<=0 || !pal.visible){

        cancelTask(cancelTaskID);
        if(reloadEditTxt.value == 0 || !pal.visible){
          
        }else{
            gather();
        }
    }
}
function cancelTask(id){
        app.cancelTask(id);
    }

function grow(palette,value){
    
}
function addItem(name,state,msg){
    //    alert(listItem.selection);
    var arrayV = new Array("green","red","orange","graygreen","gray");
    //var texted = new Array("...","error:","rendering","...","queued");
    var item = list.add ('item',name);
    //$.writeln(scriptFolder.toString()+"/flag_"+array[state]+".png");
    item.image = File(scriptFolder.toString()+"/flag_"+arrayV[state]+".png");
    //item.subItems[0].helpTip = texted(state);
    item.subItems[0].text =msg;

}
///////////////////////////////////////////
           /* if((logFolder =  getFilePath(watchfolderLocation+"/"+shot)) != undefined){
                $.writeln(logInfo(logFolder));
            }else{
                $.writeln("no log folder");
                }*/
           // $.writeln();
           // addItem(list,"G12_SC213_T1",2);
           
           
           /*
var item1 = list.add ('item', 'GB15_SC138_T1');
item1.image = File("~/Desktop/flag_gray.png");
item1.subItems[0].text = 'Queued...';
item1.enabled = false;
*/

            
            //alert(getPercentage(watchfolderLocation+"/"+shot));
          //  pBar.value = Math.round(getPercentage(watchfolderLocation+"/"+shot)*100);
          
                       // alert("test");
              //$.writeln(files[file].path+"/"+files[file].name);
                            //  a+=  getFilePath(files[file].path+"/"+files[file].name).name+"\n";
                            
/*                            
                            
                            {
                                
        wfLocBtn.onClick = function(){
            app.scheduleTask("loop()",2000,1);

           
            fold = new Folder( watchfolderLocation.toString());
            files = fold.getFiles();
                a= "";
            for(file in files){

              $.writeln(getFilePath(files[file].path+"/"+files[file].name));

            }
           alert(a);

            writeLn(pBar.value);
        }
*/

Import pos from maya

function createUI(thisObj) {
var myPanel = ( thisObj instanceof Panel) ? thisObj : new Window("palette", "Maya Track",
[100, 100, 300, 300]);
impButton = myPanel.add("button", [10, 10, 100, 30], "Import");
impButton.onClick = openfile;
return myPanel;
}
var myToolsPanel = createUI(this);
//myToolsPanel.show();


function openfile(){
	            var myFile = File.openDialog ("Select track file","*.txt"); 
            var fileOK = myFile.open("r","TEXT","????");
	//var fileD = OpenDlg ("Tracking Point File","*.txt",true);
	//txt = fileD.readln ()
	alert("txt"+readTxt(myFile));
}

function readTxt(myFile){
var myText = myFile.read();	
return myText;
	}

Dandelion/Amazing World Of Gumball Shotbuilder

xKJWB.png


For education purposes only, the script was run in production to helb build shots, it:

  • created a list of shots & shows possible given a folder structure ("GB##_SHOWNAME_SC##_T##")
  • opened the last .AEP file found in said shot folder
  • if no .AEP found, built a comp according to a given pre-cut animatic movie file found in previous folder
  • imported footage from the appropriate sources folder (and made sure not to import twice when you re-clicked the 'grab sources' button
  • automatically sent files to the watchfolder to render on a small-ish farm.

Other buttons allowed to check for missing footage, open the comp's folder, open the current shows' latest animatic, etc


//  
//  Dandelion Shot Builder for "the Amazing World Of Gumball" v0.7 by Bernie
//   last update 21/11/10
//
//  Known Bugs:
//      
//  -you can't have several sequences in a single folder and expect the script to pick up all the sequences
//  -this will not set color profiles
// //TODO
//  >>> CHECK IF WF FOLDER ALREADY EXISTS
// >>>> GET TAKE FROM N DRIVE, NOT OUT FOLDER
//  -v0.7 fixes
//        -new scene after WF works properly
//        -changed output folder location to N:
//  -v0.6 fixes
//       -cancel watchfolder cancels watchfolder
//        -fixed GB##_SC_###_T1
//       -added options
//       -can work on a new location
//  -v0.5 fixes
//      -shows allow for a letter in the comp now (ie GB##_SC###a_T#)
//      -will warn if there is missing footage before sending to WF
//      -removed set take, added 'missing' dialog.
//  -v0.4 fixes
//      -there shouldn't be a refresh problem on show change anymore
//  -v0.3 fixes
//      -changed watchfolder location to anthony's mac
//      -changed save as dialog
//      -removed unused buttons
//      -added WF (watchfolder) FE (for edit) XLS (comp chart) ANI (animatic)
//  -v0.2 fixes
//      -sequences weren't getting imported.
//      -fixed UI/little problems
//      -added folders, refresh button, save before watchfoldering
var version = "0.7";

//default locations
var watchfolderLocation = "/c/__WATCHFOLDER__";
var rootFolder = "/c/";
var forEditFolderLoaction = "//MAC0023DFDF5429/for%20edit";


rootFolder = ((app.settings.haveSetting("dandybuildPrefs", "rootfolder")))?(app.settings.getSetting("dandybuildPrefs", "rootfolder")):rootFolder;
watchfolderLocation = ((app.settings.haveSetting("dandybuildPrefs", "watchfolderLocation")))?(app.settings.getSetting("dandybuildPrefs", "watchfolderLocation")):watchfolderLocation;
forEditFolderLoaction = ((app.settings.haveSetting("dandybuildPrefs", "forEditFolderLoaction")))?(app.settings.getSetting("dandybuildPrefs", "forEditFolderLoaction")):forEditFolderLoaction;

          //dirty hack
            a = ((app.settings.haveSetting("dandybuildPrefs", "optMissingFootage")))?(app.settings.getSetting("dandybuildPrefs", "optMissingFootage")):true;
            b = ((app.settings.haveSetting("dandybuildPrefs", "optNuComp")))?(app.settings.getSetting("dandybuildPrefs", "optNuComp")):false;
            c = ((app.settings.haveSetting("dandybuildPrefs", "optSaveComp")))?(app.settings.getSetting("dandybuildPrefs", "optSaveComp")):true;
            if(a=="false") a = false;
            if(a=="true") a = true;
            if(b=="false") b = false;
            if(b=="true") b = true;
            if(c=="false") c = false;
            if(c=="true") c = true;
            
          


//var watchfolderLocation = "/w/09_Dandelions_COMP-SHOWS/_DUMP_Watchfolder_";
var loadFile = ((app.settings.haveSetting("dandybuildPrefs", "loadscene")))?(app.settings.getSetting("dandybuildPrefs", "loadscene")):"";
checkOutputSettings();

Array.prototype.has = function(value) {
    var i;
    for (i=0;i<this.length;i++) {
        if (this[i] == value) {
            return true;
        }
    }
    return false;
}


function addShotDialog(sel){
    alert(sel);
    }

function pathToWinPath(path){
    path = path.toString();
	str = path.replace(/\//, "");
	str = str.replace(/\//, ":/");
	str = str.replace(/%20/g, " ");
	str = str.replace(/\//g, "\\");
	return str;
}

function returnFolderArray(location){
     fold = new Folder(location.toString());
    foldArray = fold.getFiles()
    folderOnly = new Array();
    for(i=0;i<foldArray.length;i++){
        if(foldArray[i] instanceof Folder){
            folderOnly[folderOnly.length] = foldArray[i];
            }
        }
    return folderOnly;
}

function sendToWatchFolder(watchfolderlocation,compname){
    ocn = compname;
    if(app.settings.getSetting("dandybuildPrefs", "optSaveComp") == "true"){
        c = confirm("Save "+app.project.file.name+" ?",false,"Save Project");
        if(c){
            app.project.save();
        }
    }
    curFile = app.project.file;
    myFolder = new Folder(watchfolderlocation.toString()+"/"+compname+"_watch/");
    compname += "_WF";
    myFolder.create();
    mySaveFile = new File(myFolder.toString()+"/"+compname+".aep");        
    app.project.save(mySaveFile);
    myTextFile = new File(myFolder.toString()+"/"+compname.substring(0,22)+"_RCF.txt");    
    myTextFile.open("w","TEXT","????");
    text = "After Effects 10.0v1 Render Control File\nmax_machines=5\nnum_machines=0\ninit=0\nhtml_init=0\nhtml_name=\"\"\n" ;
    myTextFile.write(text);
    myTextFile.close();
    writeInfo(watchfolderlocation,ocn);
    if(app.settings.getSetting("dandybuildPrefs", "optNuComp") == "false"){
        opFile = new File(curFile);
        if(opFile){
            app.open(opFile);
        }
    }else{
        app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES);
        app.project.new();
    }
    
    
    //explore(watchfolderlocation);
}

function writeInfo(watchfolderlocation,compname){
    myFolder = new Folder(watchfolderlocation.toString()+"/"+compname+"_watch/");

    mySaveFile = new File(myFolder.toString()+"/"+compname+"_DandyWatch.txt");    
    mySaveFile.open("w","TEXT","????");
    rq = app.project.renderQueue;
    text ="";    
    for(i=1;i<=rq.numItems;i++){    
        if(rq.items[i].render){
            text += rq.items[i].outputModules[1].file.path+"\n";
            text += (Math.round(rq.items[i].timeSpanDuration*25)+"\n");
            text += system.callSystem("hostname");
        }
    }
    mySaveFile.write(text);
    mySaveFile.close();
}


function explore(location){
     fold = new Folder(location.toString());
     return fold.execute();     
}

function checkForMissingFootage(dialogFlag){
        var dialog = dialogFlag;
        var missing = false;
        for(i=1;i<=app.project.numItems;i++){
            app.project.items[i].selected = false;
            if(app.project.items[i] instanceof FootageItem && app.project.items[i].file != null){
                            if(app.project.items[i].footageMissing){
                                app.project.items[i].selected = true;
                                missing = true;
                                if(dialog){
                                    dialog = prompt("Original folder of \""+(app.project.items[i].name)+"\" :",pathToWinPath(app.project.items[i].file),"Missing footage! (hit cancel to suppress further dialogs)");
                                }
                            }
            }
        }
        return missing;
}

function checkOutputSettings(){
    renderSettingsSet = ((app.settings.haveSetting("dandybuildPrefs", "tiffsettings")))?true:false;
   // renderSettingsSet = 1;
    if(!renderSettingsSet){
                alert("Tiff output being setup, this should happen only once. It needs to close the current comp.");
                opFile = new File(rootFolder+"base.aep");
                if(opFile.exists){
                     app.open(opFile);
                     app.project.renderQueue.items[1].outputModules[1].saveAsTemplate("TiffOutput");
                     app.settings.saveSetting("dandybuildPrefs", "tiffsettings","is there");
                     app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES);
                     app.newProject();
                 }else{
                     alert("Error:\n\nThere should be a file called\n\n\""+rootFolder+"base.aep"+"\"");
                }
    }
}

function checkOutFolder(tpath){
    foldFile = new Folder(tpath);
    //$.writeln(tpath);
    if(foldFile.exists){
        files = foldFile.getFiles();  
        if(files.length > 1){
            return false;
        }else{
            return true;
        }
    }else{
      newF = foldFile.create();
        return newF;
    }
}
function getSetTake(force){
    show = app.settings.getSetting("dandybuildPrefs","show");// showDDL.selection.toString();
    shot = app.settings.getSetting("dandybuildPrefs","shot"); //shotDDL.selection.toString();
    projectTake = getTake(findMainShot().name);
//    paths  = rootFolder+show+"/"+shot+"/OUT/";
    paths  = "/n/01_OUT/"+show+"/"+shot+"/";
    lastFolder = getLastModified(paths);
    //alert(lastFolder.name+" -->"+paths);
    if(lastFolder){
        folderTake = getTake(lastFolder.name);
    }else{
        folderTake = false;
    }
    msgPt1 = (folderTake)? "The last take in OUT folder is take "+(folderTake)+".\n":"No take was found in the OUT folder.\n";
    msgPt2 = "The current project take is "+projectTake+". \n\nChoose the current take:";
    if(force || (folderTake != (projectTake-1))){
        recommended = (folderTake)?((folderTake)+1):projectTake;
        answer = prompt(msgPt1+msgPt2,recommended);
        //$.writeln("blabal "+answer);
        if(answer != null){
            return answer;
        }else{
             return false;
         }
    }
    return projectTake;
}



function setTake(value){
    comp = findMainShot();
    comp.name = comp.name.substring(0,(comp.name.lastIndexOf("_T")+2))+value;
}

function getTake(normalizedName){
    tmp = normalizedName.split("_T");
    return  parseFloat(tmp[tmp.length-1]);
}
	// Smart Import.jsx
	//adobe-shipped smart import with tweaks to not import files that are already here, and not screw up in some cases
    //the only problem is that you can still not have 2 sequences in a single folder
   
function SmartImport(targetFolder){
         writeLn("Importing files...");
         var importedFiles = 0;
		var scriptName = "Smart Import";
		var sourcePaths = getSourcePathsArray();
		// Ask the user for a folder whose contents are to be imported.
		//var targetFolder = Folder.selectDialog("Import items from folder...");
		if (targetFolder != null) {
			// If no project open, create a new project to import the files into.
			function processFile(theFile)
			{
				try {
					// Create a variable containing ImportOptions.
					var importOptions = new ImportOptions(theFile);
                    //alert();
                     if(theFile.name.toString().toLowerCase().lastIndexOf(".psd") != -1){
                        importOptions.importAs = ImportAsType.COMP;
                     }
                       importSafeWithError(importOptions);
				} catch (error) {
					// Ignore errors.
				}
			}
			
			function testForSequences(files)
			{
				var searcher = new RegExp("[0-9]{3,}$");
				var movieFileSearcher = new RegExp("(mov|avi|mpg)$", "i");
				var parseResults = new Array;
                  var isFolder = new Array;
				
				// Test that we have a sequence. Stop parsing after 10 files.
				for (x = 0; (x < files.length) & x < 10; x++) {
					var movieFileResult = movieFileSearcher.exec(files[x].name);
					if (!movieFileResult) {
//******                           splitName = files[x].name.split('.')[0];
                            splitName=files[x].name.substring(0,files[x].name.length-4);

                           //splitName[splitNameA.length] = 
                           //alert(splitName);	
                         //  alert(files[x].name+" >>> "+files[x].name.split('.')[0]);
						var currentResult = searcher.exec(splitName);
						// Regular expressions return null if no match was found.
						// Otherwise, they return an array with the following information:
						// array[0] = the matched string.
						// array[1..n] = the matched capturing parentheses.
                   				
                    if (currentResult) { // We have a match -- the string contains numbers.
                           
                            // The match of those numbers is stored in the array[1]
							// Take that number and save it into parseResults.
							parseResults[parseResults.length] = currentResult[0];
						} else {
							parseResults[parseResults.length] = null;
						}
                           if(files[x].name == splitName){
                                isFolder[isFolder.length] = true;
                               // alert("Folder "+files[x].name);
                           }else{
                                isFolder[isFolder.length] = false;
                           }
					} else {
						parseResults[parseResults.length] = null;
					}
				}
				
				// If all the files we just went through have a number in their file names, 
				// assume they are part of a sequence and return the first file.
				
				var result = null;
				for (i = 0; i < parseResults.length; ++i) {
                   				
                   if(!isFolder[i]){
                    if (parseResults[i]) {
						if (!result) {
                            
							result = files[i];		
						}
					} else {
                        
						// In this case, a file name did not contain a number.
						result = null;
						break;
					}
                   }
                }
				
				return result;
			}
        			
			
			function importSafeWithError(importOptions)
			{
              if(!(sourcePaths.has(importOptions.file.toString()))){
 				try { 
                  b = app.project.importFile(importOptions);
                    writeLn(importOptions.file.toString());
                 sfi = sourcesFolderItem();
                   b.parentFolder = sfi;
                    if(importOptions.importAs == ImportAsType.COMP){
                        for (i = 1; i <= app.project.numItems ; i++) {
                            if(app.project.items[i].typeName == "Folder" && app.project.items[i].name.lastIndexOf(" Layers") != -1){
                         app.project.items[i].parentFolder = sfi;
                            }
                        }
                    }
                        writeLn("Importing file ("+importedFiles+")");
                        importedFiles++;
				} catch (error) {
					alert(error.toString() + importOptions.file.fsName, scriptName);
				}
                // }else{
                //        alert("file already here");
                 }
			}
			
			
			function processFolder(theFolder)
			{
				// Get an array of files in the target folder.
				var files = theFolder.getFiles();
				//alert(files);
				// Test whether theFolder contains a sequence.
				var sequenceStartFile = testForSequences(files);
				
				// If it does contain a sequence, import the sequence,
				if (sequenceStartFile) {
					try {
						// Create a variable containing ImportOptions.
						var importOptions = new ImportOptions(sequenceStartFile);
						importOptions.sequence = true;
						// importOptions.forceAlphabetical = true;		// Un-comment this if you want to force alpha order by default.
						importSafeWithError(importOptions);
					} catch (error) {
					}
				}
				
				// Otherwise, import the files and recurse.
				
				for (index in files) { // Go through the array and set each element to singleFile, then run the following.
					if (files[index] instanceof File) {
						if (!sequenceStartFile) { // If file is already part of a sequence, don't import it individually.
							processFile(files[index]); // Calls the processFile function above.
						}
					}
					if (files[index] instanceof Folder) {
						processFolder(files[index]); // recursion
					}
				}
			}
			
			// Recursively examine that folder.
			processFolder(targetFolder);
		}
    clearOutput();	
    }
	
	
	
   
function sourcesFolderItem(){
    for (i = 1; i <= app.project.numItems ; i++) {
       // alert(app.project.items[i].name);
     if(app.project.items[i].typeName == "Folder" && app.project.items[i].name == "Sources"){
           return app.project.items[i];
      }
   }
    return app.project.items.addFolder("Sources");
    //else
    
}

function getSourcePathsArray(){
    var footageLocations = new Array();
    for (i = 1; i <= app.project.numItems ; i++) {
        if(app.project.item(i).typeName == "Footage"){
            footageLocations[footageLocations.length] = app.project.item(i).file;
        }    
    }
    return footageLocations;
}

function getLastModified(location,extension){
      myFolder = new Folder(location.toString());
      if (!myFolder instanceof Folder){
          return false;
     }
     listFiles = myFolder.getFiles(extension);
     listDates = new Array();
     maxId = 0;
     maxValue = 0;
     for(i=0;i<listFiles.length;i++){
         if (!(extension == "" && (listFiles[i] instanceof File))){
             d = listFiles[i].modified;
             formattedDate = d.getFullYear()+""+pad(d.getMonth()+1,2,0)+""+pad(d.getDate(),2,0)+""+pad(d.getHours(),2,0)+""+pad(d.getMinutes(),2,0)+""+pad(d.getSeconds(),2,0);
             listDates[i] = formattedDate;
             if(Math.max(maxValue,formattedDate) == formattedDate){
                 maxValue = formattedDate;
                 maxId = i;
             }
        }
     
     }
    if(listFiles[maxId] == "" ||listFiles[maxId] == "undefined"||listFiles[maxId] == null){
        return false;
    }
    return listFiles[maxId];     
}

function pad(number, length, character) {
    if (character == null) {
        character = "0";
    }
    var numberStr = "" + number;
    while (numberStr.length < length) {
        numberStr = character + numberStr;
    }
    return numberStr;
}
function grabAnimatic(folderLocation){
    newLocation = new Folder(folderLocation);
    animatic = newLocation.getFiles("*.mov");
    sfi = sourcesFolderItem();
    if(animatic[0]){
        try {
            var importOptions = new ImportOptions(animatic[0]);
            animatic = app.project.importFile(importOptions);
            for (i = 1; i <= app.project.numItems ; i++) {
                    if(app.project.item(i).typeName == "Footage"){
                   app.project.item(i).parentFolder = sfi;
                    }    
            }
            return animatic;
        }catch (error){
            return false;
        }
    }
}
function figureOutShotName(filepath){
    folders = filepath.split("/");
    showFolder = folders[folders.length-2];
    showFolder = showFolder.split("_")[0];
    shotFolder = folders[folders.length-1];
    var searcher = new RegExp("[0-9]{2,}$");
    var currentResult = searcher.exec(shotFolder);
    if (currentResult){
        shotFolder = currentResult[0];
    }
    return showFolder+"_SC"+shotFolder;
}

function createMainComp(path){
    grabAnimatic(path);
    shotName = figureOutShotName(path);
    workComp = app.project.items.addComp(shotName+"_T1", 1920, 1080,1.0 ,animatic.duration,25.0);
    animaticLayer = workComp.layers.add(animatic);
    animaticLayer.guideLayer = true;
    return workComp;
}

function returnList2(type){
     if(type == "show"){
        rArray = returnFolderArray(rootFolder);
        for(i=0;i<rArray.length;i++){
            rArray[i] = rArray[i].name;
            }
    }else if(type == "scene"){
        rArray = ["a","b"];
        alert(dPanel.shotLoad.showDDL.selection.toString());

    }
    return rArray;
}



function returnList(folder){
    rArray = returnFolderArray(folder);
    for(i=0;i<rArray.length;i++){
            rArray[i] = rArray[i].name;
    }
    return rArray;
}

function findMainShot(){
    //fix GB##_SC_###_T1
    var findShot = new RegExp("^(GB)[0-9]{2}(_SC_)[0-9]{1,3}[a-z]{0,}(_T)[0-9]{1,}$", "i");
    for(i=1;i<=app.project.numItems;i++){
        if(app.project.items[i] instanceof CompItem){
           var movieFileResult =  findShot.exec(app.project.items[i].name);
           if(movieFileResult){
               nuName = app.project.items[i].name;
               nuName = nuName.split("SC_");
               app.project.items[i].name = nuName[0]+"SC"+nuName[1];
            }
        }        
    }
    
    var findShot = new RegExp("^(GB)[0-9]{2}(_)(SC)[0-9]{1,3}[a-z]{0,}(_T)[0-9]{1,}$", "i");
    var shot = null;
    for(i=1;i<=app.project.numItems;i++){
        if(app.project.items[i] instanceof CompItem){
           var movieFileResult =  findShot.exec(app.project.items[i].name);
           if(movieFileResult){
               shot = app.project.items[i];
               break;
               }
        }
    }
    if(shot == null){
        alert("Could not find a comp named like \"GB##_SC###(a-z)_T#\"\n\nRename main comp accordingly, render again");
        return false;
    }
    return shot;
}


function dandyShotBuilderUI(thisObj){
    
    ////////////////////////////////////// UI ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
        dPanel = (thisObj instanceof Panel) ? thisObj : new Window("palette", "DandyShotBuilderFix", [100, 100, 300, 300]);
        var securitySetting = app.preferences.getPrefAsLong("Main Pref Section", "Pref_SCRIPTING_FILE_NETWORK_SECURITY");
        if (securitySetting != 1) {
           var msg = "This script requires the scripting security preference to be set. \nGo the \"General\" panel of your application preferences and make sure that \"Allow Scripts to Write Files and Access Network\" is checked.\n\n Restart AFX";
           var shotInfo = dPanel.add("edittext",[10,5,235,305],msg,{multiline:true}); 
         }else{
      var shotLoad = dPanel.add("panel",[10,5,235,175],"Load Shot");
         var shotLoadTxt1 = shotLoad.add("statictext",[15,24,52,44],"Show:");
         var showDDL = shotLoad.add ("dropdownlist", [56,19,210,44], returnList(rootFolder));
            if (app.settings.haveSetting("dandybuildPrefs", "show")){
                 showDDL.selection = showDDL.find(app.settings.getSetting("dandybuildPrefs","show"));
                 //parseFloat(app.settings.getSetting("dandybuildPrefs","show"));
                }else{
                 showDDL.selection = 0;
                 app.settings.saveSetting("dandybuildPrefs","show",showDDL.selection.toString());
                }
         var shotLoadTxt2 = shotLoad.add("statictext",[12,59,52,79],"Scene:");
         var shotDDL = shotLoad.add ("dropdownlist", [56,54,210,79], returnList(rootFolder+"/"+app.settings.getSetting("dandybuildPrefs", "show")));
            if (app.settings.haveSetting("dandybuildPrefs", "shot")){
                 shotDDL.selection = shotDDL.find(app.settings.getSetting("dandybuildPrefs","shot"));
                 //parseFloat(app.settings.getSetting("dandybuildPrefs","show"));
                }else{
                 shotDDL.selection = 0;
                }
         
          
         var shotStatusTxt = shotLoad.add("statictext",[25,95,52,115],"File:");
         var shotStatusEditTxt = shotLoad.add("edittext",[56,92,210,115],"...");
            shotStatusEditTxt.active = false;
            shotStatusEditTxt.enabled = false;
         var shotLoadBtn = shotLoad.add("button",[56,125,96,150],"Load");
            shotLoadBtn.enabled = false;
         var shotBuildBtn = shotLoad.add("button",[101,125,141,150],"Build");
            shotBuildBtn.enabled = false;
         var shotRefreshBtn = shotLoad.add("button",[146,125,168,150],"r");
         //var shotOptns = shotLoad.add("button",[174,125,210,150],"optns");
            //shotSetBtn.enabled = false;
        
       var shotTools = dPanel.add("panel",[10,185,235,300],"Misc");
            shotToolsBtn1 =  shotTools.add("button",[12,12,79,35],"Open Folder");
            shotToolsBtn1Xtra =  shotTools.add("button",[82,12,102,35],"N:");
            //shotToolsBtn2 =  shotTools.add("button",[107,12,197,35],"Flick Last Take");
            //shotToolsBtn2.enabled = false; 
            shotToolsBtnWF =  shotTools.add("button",[107,12,135,35],"WF");
            shotToolsBtnWF.onClick = function(){explore(watchfolderLocation)};
             shotToolsBtnFE =  shotTools.add("button",[139,12,167,35],"FE");
            shotToolsBtnFE.onClick = function(){explore(forEditFolderLoaction)};
            shotToolsBtnXLS =  shotTools.add("button",[172,12,197,35],"xls");
            //shotToolsBtnXLS.enabled = false;             
                
            shotToolsBtn3 =  shotTools.add("button",[12,40,102,63],"Grab Sources");
            shotToolsBtn4 =  shotTools.add("button",[107,40,135,63],"Miss");
            shotToolsBtnANI =  shotTools.add("button",[139,40,167,63],"ANI");
            shotToolsBtnOPT =  shotTools.add("button",[172,40,197,63],"Opt");
            
              //  shotToolsBtn4.enabled = false;            
            shotToolsBtn5 =  shotTools.add("button",[12,68,102,91],"Renderqueue");
            shotToolsBtn6 =  shotTools.add("button",[107,68,197,91],"To Watchfolder");
                //shotToolsBtn6.enabled = false; 
            }
       var optnsGrp = dPanel.add("panel",[10,5,235,300],"Options");
            optnsGrp.visible = false;
            mgL = 20;
            mgT=15;
            mgBet=3;
            i=0;
            w=210;
            h=23;
            version = optnsGrp.add("statictext",[mgL,mgT+(i*(h+mgBet)),w,mgT+((i+1)*(h+mgBet))],"version "+version);i++;
            optMissingFootage =  optnsGrp.add("checkbox",[mgL,mgT+(i*(h+mgBet)),w,mgT+((i+1)*(h+mgBet))],"Check for missing footage\rlonger, but safer");i++;
            //$.writeln("load missing footage pref set to"+(app.settings.haveSetting("dandybuildPrefs", "optMissingFootage"))?(app.settings.getSetting("dandybuildPrefs", "optMissingFootage")):true);
                optMissingFootage.value = a;
            optNuComp =  optnsGrp.add("checkbox",[mgL,mgT+(i*(h+mgBet)),w,mgT+((i+1)*(h+mgBet))],"New comp after sending to WF");i++;
                optNuComp.value = b;
            optSaveComp =  optnsGrp.add("checkbox",[mgL,mgT+(i*(h+mgBet)),w,mgT+((i+1)*(h+mgBet))],"Ask to save before sending to WF");i+=2;
                optSaveComp.value = c;
            rootFolderLoc =  optnsGrp.add("button",[mgL,mgT+(i*(h+mgBet)),w,mgT+((i+1)*(h+mgBet))],"Root folder location");i++;
            watchFolderLoc =  optnsGrp.add("button",[mgL,mgT+(i*(h+mgBet)),w,mgT+((i+1)*(h+mgBet))],"Watchfolder location");i+=2;
            okBtn =  optnsGrp.add("button",[mgL,mgT+(i*(h+mgBet)),w,mgT+((i+1)*(h+mgBet))],"OK");
            

       ///////////////////////////////// UI FUNCTION ///////////////////////////////////////////////////////////
       
        showDDL.onChange = function(){
            app.settings.saveSetting("dandybuildPrefs","show",showDDL.selection.toString());
           // shotLoad.remove(shotDDL);
          shotDDL.removeAll();

          items = returnList(rootFolder+"/"+showDDL.selection.toString());
          //  alert(items);
            for(i in items){
               shotDDL.add("item", items[i]);
            }
            //var shotDDL = shotLoad.add ("dropdownlist", [56,54,210,79], returnList(rootFolder+"/"+showDDL.selection.toString()));
            shotDDL.selection = 0;
            shotDDL.notify();
        }
        shotDDL.onChange = function(){
           
            app.settings.saveSetting("dandybuildPrefs","shot",shotDDL.selection.toString());
            //$.writeln("Changing");
            var lastAEP = getLastModified(rootFolder+"/"+showDDL.selection.toString()+"/"+shotDDL.selection.toString()+"/AEP","*.aep");
            if(!lastAEP){
                shotStatusEditTxt.text = "no shot found, build one!";
                   shotLoadBtn.enabled = false;
                   shotBuildBtn.enabled = true;
            
            }else{
                app.settings.saveSetting("dandybuildPrefs", "loadscene",lastAEP.toString());
                loadFile = lastAEP.toString();
                fullSplitPath = lastAEP.toString().split("/");
                fileName = fullSplitPath[fullSplitPath.length-1];
                if(fileName.length > 20){
                    shotStatusEditTxt.text = fileName.substr(0,12)+"[...]"+fileName.substr(fileName.length-8);
                }else{
                    shotStatusEditTxt.text = fileName;
                } 
                    shotLoadBtn.enabled = true;
                    shotBuildBtn.enabled = false;
            }
        }
        shotLoadBtn.onClick = function(){
            opFile = new File(loadFile);
            if(opFile){
             app.open(opFile);
            }else{
                alert("Can't open file");
            }        
        }
        shotRefreshBtn.onClick = function(){
            shotDDL.notify();
            writeLn("If it doesn't seem to refresh, close and launch again.");
            }
        shotBuildBtn.onClick = function(){
            opFile = new File(rootFolder+"base.aep");
            if(opFile.exists){
                app.open(opFile);
                for(i=1;i<=app.project.numItems;i++){                     
                    app.project.items[i].remove();
                }
            }else{
                alert("Error:\n\nThere should be a file called\n\n\""+rootFolder+"base.aep"+"\"");
            }
            targetFolder = rootFolder+"/"+showDDL.selection.toString()+"/"+shotDDL.selection.toString();
            createMainComp(targetFolder);
            app.project.bitsPerChannel = 16;
            app.project.items.addFolder("Precomps");
            importFolder = new Folder(targetFolder+"/Source");
            SmartImport(importFolder);
            saveConf = confirm("Save "+shotDDL.selection.toString()+" in the right folder ?",false,"Dandelion Shot Builder");
            if(saveConf){
                saveFile = new File(targetFolder+"/AEP/"+showDDL.selection.toString()+"_"+shotDDL.selection.toString()+"_comp01.aep");
                app.project.save(saveFile);
                shotDDL.notify();
                }
        }
        shotToolsBtn1.onClick = function(){
            explore(rootFolder+"/"+showDDL.selection.toString()+"/"+shotDDL.selection.toString());
        }
        shotToolsBtn1Xtra.onClick = function(){
            //$.writeln("button");
            p = "/n/01_OUT/"+showDDL.selection.toString()+"/"+shotDDL.selection.toString();
            checkOutFolder(p);
            explore(p);
        }
        shotToolsBtn3.onClick = function(){
            app.project.bitsPerChannel = 16;
            targetFolder = rootFolder+"/"+showDDL.selection.toString()+"/"+shotDDL.selection.toString();
            importFolder = new Folder(targetFolder+"/Source");
            SmartImport(importFolder);
        }
        shotToolsBtnXLS.onClick = function(){
            loc =  rootFolder+"/"+showDDL.selection.toString();
            fold = new Folder(loc.toString());
            files = fold.getFiles("*.xls*");
            if(files.length > 0){
                files[0].execute();
            }else{
                writeLn("No .xls shortcut found in in:");
                writeLn(showDDL.selection.toString());
            }
        }
       shotToolsBtnANI.onClick = function(){
            loc =  rootFolder+"/"+showDDL.selection.toString();
            fold = new Folder(loc.toString());
            files = fold.getFiles("*.mov*");
            if(files.length > 0){
                files[0].execute();
            }else{
                writeLn("No .mov shortcut found in in: ");
                writeLn(showDDL.selection.toString());
            }
        }
        shotToolsBtnOPT.onClick = function(){
            shotTools.visible = false;
            shotLoad.visible = false;
            optnsGrp.visible = true;
        }
    okBtn.onClick = function(){
            shotTools.visible = true;
            shotLoad.visible = true;
            optnsGrp.visible = false;
           
            app.settings.saveSetting("dandybuildPrefs", "optMissingFootage",optMissingFootage.value);
            app.settings.saveSetting("dandybuildPrefs", "optNuComp",optNuComp.value);
            app.settings.saveSetting("dandybuildPrefs", "optSaveComp",optSaveComp.value);
           // $.writeln("new comp checked: "+app.settings.getSetting("dandybuildPrefs", "optNuComp"));
    }
        
        //send to renderqueue
        //uses the comp name to figure out folder

                   
        shotToolsBtn5.onClick = function(){
        rflag = false;
        //$.writeln("missing fottage "+app.settings.getSetting("dandybuildPrefs", "optMissingFootage"));
        if(app.settings.getSetting("dandybuildPrefs", "optMissingFootage") == "true"){
            rflag = checkForMissingFootage(false);
        }
        if(!rflag){
            rcomp = findMainShot();
                if(rcomp){
                    shotNumber = getSetTake();
                    if(shotNumber != false){
                        //$.writeln(shotNumber);
                        setTake(shotNumber);
                        rq = app.project.renderQueue;
                        for(i=1;i<=rq.numItems;i++){    
                                rq.items[i].render = false;
                         }
                        rqitem = app.project.renderQueue.items.add(rcomp);
                        rqitem.outputModules[1].applyTemplate("TiffOutput");
                        rqitem.applyTemplate("Multi-Machine Settings");
                        //savePath = rootFolder+showDDL.selection.toString()+"/"+shotDDL.selection.toString()+"/OUT/"+rcomp.name;
                        savePath = "/n/01_OUT/"+showDDL.selection.toString()+"/"+shotDDL.selection.toString()+"/"+rcomp.name;
                        
                        
                        if(checkOutFolder(savePath)){
                           rqitem.outputModules[1].file = new File(savePath+"/"+rcomp.name+"_[#####].tif");
                        }else{
                           alert("Couldn't figure out an output folder for \""+(rcomp.name)+"\", select a folder manually.");
                          rqitem.outputModules[1].file = new File();
                        }
                    }
                }
            }else{
                alert("This project is missing source files and will not render. \nUse the 'miss' button to find out where the footage should be");
            }
        }
        
        shotToolsBtn4.onClick = function(){
            //shotNumber = getSetTake(true);
            //setTake(shotNumber);
            checkForMissingFootage(true);
        }
        
        shotToolsBtn6.onClick = function(){
           wf = new Folder(watchfolderLocation);
           if(wf.exists){
               sendToWatchFolder(wf,findMainShot().name);
               }else{
                   alert("Watchfolder error, sorry!");
               }
        }
            
    
    
    
    
    
    rootFolderLoc.onClick = function(){
        y = confirm("Root folder is currently set to \n\n\""+rootFolder+"\"\n\nChange it ?");
        if(y){
            //v = new Folder();
            v = Folder.selectDialog ("New root folder location").toString();
            if(v!=null && v!="undefined"){
                app.settings.saveSetting("dandybuildPrefs", "rootfolder",v);     
                rootFolder = v;
                          showDDL.removeAll();
                          items = returnList(rootFolder);
          //  alert(items);
            for(i in items){
               showDDL.add("item", items[i]);
            }
            //var shotDDL = shotLoad.add ("dropdownlist", [56,54,210,79], returnList(rootFolder+"/"+showDDL.selection.toString()));
            shotDDL.selection = 0;
            shotDDL.notify();
                showDDL.notify();
                shotDDL.notify();
                //$.writeln("notified");
            }
        }
    }
    watchFolderLoc.onClick = function(){
        y = confirm("Watchfolder is currently set to \n\n\""+watchfolderLocation+"\"\n\nChange it ?");
        if(y){
            //v = new Folder();
            v = Folder.selectDialog ("New watchfolder location").toString();
            if(v!=null && v!="undefined"){
                app.settings.saveSetting("dandybuildPrefs", "watchfolderLocation",v);     
                watchfolderLocation = v;
            }
            
        }
    }
    shotDDL.notify();
    }
    dandyShotBuilderUI(this) ;
   


layer Position to .txt

G5DX1T0.gif Old, might not work! (but should :)

// script created by mlk: mlkdesign@gmail.com Jan 2007 (my old internet handle !)
//
// The script will write to a text file the x & y values of the layer position for every frame comprised in
// a selection of keyframes, or for the whole comp duration if no keyframes are selected
//
//
function timeToFrameNum(myTime){
   return Math.floor(myTime) * app.project.activeItem.frameRate + (myTime - Math.floor(myTime)) / (1/app.project.activeItem.frameRate);
}
function framesToTime(myTime){
   return myTime/app.project.activeItem.frameRate;
}
function timeToTimeCode(myTime){
   var framesN = myTime * app.project.activeItem.frameRate;
   fr = addZero(Math.round((myTime - Math.floor(myTime))/(1/app.project.activeItem.frameRate)));
   ho = addZero(Math.floor(myTime/3600));
   mi = addZero(Math.floor(myTime/60)-ho*60);
   se = addZero(Math.floor(myTime)-mi*60-ho*3600);
   return ho+":"+mi+":"+se+":"+fr;
}
function addZero(val){
   if(val<10){
      val = "0"+val;
   }
   return val;
}
var myDisp = app.project.timecodeDisplayType;
app.project.timecodeDisplayType = TimecodeDisplayType.TIMECODE;
var pText = "Choose an output format using:\n%f (framenumber),%i (index, starts at 0) %t (SMTPE timecode), %x (x value), %y (y value), %l (linebreak) and any other character. Default output looks like '16: 230;22'";

if(app.project.activeItem != "null" && app.project.activeItem != null && app.project.activeItem != 0){
   if(app.project.activeItem.selectedLayers.length != 0){
      if(app.preferences.getPrefAsLong("Main Pref Section","Pref_SCRIPTING_FILE_NETWORK_SECURITY")){
         curLayer = app.project.activeItem.selectedLayers[0];
         var textName = "AEcoordinates.txt";
         var myTextFile = filePutDialog("Select a location to save your .txt file", textName, "TEXT txt");
         if(myTextFile == null){
            alert("You must choose a place to save the file");
         }else{
            var formatString = prompt(pText,"%i: %x;%y%l");
            myTextFile.open("w","TEXT","????");
            myKeys = curLayer.property("position").selectedKeys;
            if(myKeys.length != 0){
               strTime = curLayer.property("position").keyTime(myKeys[0]);
               endTime = curLayer.property("position").keyTime(myKeys[myKeys.length-1]);
            }else{
               strTime = app.project.activeItem.workAreaStart;
               endTime = app.project.activeItem.workAreaStart + app.project.activeItem.workAreaDuration;
            }
            var startLoop = timeToFrameNum(strTime);
            var endLoop = timeToFrameNum(endTime);
            for(i=startLoop;i<=endLoop;i++){
               var curTime = framesToTime(i);
               out = formatString.replace('%x',curLayer.property("position").valueAtTime(curTime,true)[0]);
               out = out.replace('%y',curLayer.property("position").valueAtTime(curTime,true)[1]);
               out = out.replace('%f',i);timeToTimeCode
               out = out.replace('%t',timeToTimeCode(curTime));
               out = out.replace('%i',i-startLoop);
               out = out.replace('%l','\n');
               myTextFile.write(out);
               clearOutput();
               write(Math.round((i-startLoop)/(endLoop-startLoop)*100,0)+"% done...");
            }
         clearOutput();
         writeLn(endLoop-startLoop+" positions saved to file");
         writeLn("script written by mlk =)");
         myTextFile.close();
         }
      } else {
         alert ("This script requires the scripting security preference to be set.\n" +
         "Go to the \"General\" panel of your application preferences,\n" +
         "and make sure that \"Allow Scripts to Write Files and Access Network\" is checked.");
      }

   }else{
      alert("Select a layer with 'position' keyframes");
   }
}else{
   alert("Select a composition and a layer with 'position' keyframes");
}
app.project.timecodeDisplayType = myDisp;

Other Scripts

Shelf

AFX Shelf

//to hijack admin and load shelf from my documents/shelf.jsx, this needs to be in the scriptUI Panel folder
{
    var nested_file = new File("~/shelf.jsx");
	nested_file.open("r");
	eval(nested_file.read());
	nested_file.close();
}