Afx Javascript Temp

From bernie's
Jump to: navigation, search

Local Proxy

/* 
 * simpleLocalProxy.jsx v1.0
 * 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
 *  
 * 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
 *
 *
 */



/*
 * 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){
    $.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
                    
                    batchFile += "\nrobocopy ";
                    batchFile += pathToLocalizedPath(dir);
                    //robocopy filename requires a wildcard, even if it's a single file
                    batchFile += " \""+fileName.replace(/%20/g, " ")+"*\" ";
                    batchFile += pathToLocalizedPath(outputDir);
                    batchFile += ((useforcecopy)?" /XO":"")+" /FFT"+((debugcheck)?"":" /NJH /NJS");
                }
            }
        }
        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: 'Local Copy folder setup: '},\
                                saveDirOptions: Group {orientation:'column',align:'left', alignChildren:['fill','center'],\
                                    usemd5rbox: RadioButton {text: ' Simple',helpTip:'Uses a unique folder name per footage, requires md5.js',value:true},\
                                    usepathrbox: RadioButton {text: ' Copy Folder Structure',helpTip:'Copies the target folder structure inside the local folder'}}},\
                            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 local files (if your render has changed for instance)'},\
                                    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 Local Folder ' ,preferredSize:[-1,30]} , \
                            browseBut: Button {text: ' Browse ' , preferredSize:[-1,30]}} , \
                        curentDirTxt: EditText {text: '" + localFolder +"',enabled:false},\
                        copyFootageBut: Button {text: ' Copy Footage(s) to Local Folder ' ,helpTip:'Launches a background batch copy of selected footage',preferredSize:[-1,30]} , \
                        switchproxyBut: Button {text: ' Switch Local 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);

needs (optional) md5.js from https://github.com/blueimp/JavaScript-MD5/blob/master/js/md5.js next to the current file



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


function pathToWinPath(path){
        /*var str = path.toString().replace(/\//, "");
        str = str.replace(/\//, ":/");
        str = str.replace(/%20/g, " ");
        str = str.replace(/\//g, "\\");*/
        f = new File(path);
        return f.fsName.toString();
}
function ismd5available(){
    var currentScript = new File($.fileName);
    var md5Script = new File(currentScript.path.toString()+"/md5.js");
    //if(md5Script.exists){
    //    $.evalFile(md5Script);
        //e(calcMD5("hello bobby"));
    //}
    if(md5Script.exists){
        return (md5Script.toString());
    }else{
        return false;
    }
}
function sequenceFilesWildcard(path){ 
    //returns everything before a sequence's image number, and the extesion: /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){
    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];
                //e("sequence: "+pathFromRegex[1]+"*"+pathFromRegex[2]);
            }else{
                returnpath = source;
                //e("movie: "+source);
            }
        }else{
            returnpath = source;
            //e("still: "+source);
        }
    }
    return returnpath;
}

function grabFootagePathsAndCopyToLocal(){
    //main worker function
    
    var sel = app.project.selection;
    if(sel.length < 1){
        alert("Select footage first");    
    }else{
        
        var debugcheck = (getPref("debug","false")=="true")?true:false;
        var useforcecopy = (getPref("forcecopy","false")=="true")?true:false;
        
        var batchFile = "@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 localSaveDir = getPref("localSaveDir",Folder.temp.toString());
                var outputDir = "";
                if(getPref("usemd5",true)=="true"){
                    var md5Script = new File(ismd5available());
                    $.evalFile(md5Script);
                    outputDir =localSaveDir + "/"+ md5(dir);
                }else{
                    outputDir =localSaveDir + dir;
                }
                //windows only for now
                
                batchFile += "\nrobocopy ";
                batchFile += pathToWinPath(dir);
                //robocopy filename requires a wildcard, even if it's a single file
                e(fileName);
                batchFile += " \""+fileName.replace(/%20/g, " ")+"*\" ";
                batchFile += pathToWinPath(outputDir);
                batchFile += ((useforcecopy)?" /XO":"")+" /FFT"+((debugcheck)?"":" /NJH /NJS");
                //e(batchFile);
            }
        }
        batchFile += ((debugcheck)?"\npause":"");
        var txtFile = new File(localSaveDir+"/AFX footage copy.bat");
        txtFile.open("w","TEXT","????");
        txtFile.write(batchFile);
        txtFile.close();
        txtFile.execute();
        //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"){
                    var md5Script = new File(ismd5available());
                    $.evalFile(md5Script);
                    outputDir =localSaveDir + "/"+ md5(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 = pathToWinPath(localFolder).replace(/\\/g,"\\\\");
        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: 'Local Copy folder setup: '},\
                                saveDirOptions: Group {orientation:'column',align:'left', alignChildren:['fill','center'],\
                                    usemd5rbox: RadioButton {text: ' Simple',helpTip:'Uses a unique folder name per footage, requires md5.js',enabled:false,active:false,value:true},\
                                    usepathrbox: RadioButton {text: ' Copy Folder Structure',helpTip:'Copies the target folder structure inside the local folder'}}},\
                            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 local files (if your render has changed for instance)'},\
                                    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 Local Folder ' ,preferredSize:[-1,30]} , \
                            browseBut: Button {text: ' Browse ' , preferredSize:[-1,30]}} , \
                        curentDirTxt: EditText {text: '" + localFolder +"',enabled:false},\
                        copyFootageBut: Button {text: ' Copy Footage(s) to Local Folder ' ,helpTip:'Launches a background batch copy of selected footage',preferredSize:[-1,30]} , \
                        switchproxyBut: Button {text: ' Switch Local 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
          
            if(ismd5available()){
                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;
                pan.grp.cols.col1.saveDirOptions.usemd5rbox.enabled = true;
            }else{
                setPref("usemd5",false);
                pan.grp.cols.col1.saveDirOptions.usemd5rbox.value = false;
                pan.grp.cols.col1.saveDirOptions.usepathrbox.value = true;
                pan.grp.cols.col1.saveDirOptions.usemd5rbox.enabled = false;
            }
            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 = pathToWinPath(o.toString());//.replace(/\\/g,"\\\\");
            }
        }
    
        //copy footages button (launches the 'guts' of this script)
        pan.grp.copyFootageBut.onClick = function(){
            //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);
             

             
            //this.text = md5("opying");
            grabFootagePathsAndCopyToLocal();
        }
        //checks for a local copy of the footage, and switches to a proxy accordingly 
        pan.grp.switchproxyBut.onClick = function(){
            //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);
             

             
            //this.text = md5("opying");
            grabFootagePathsAndSwitchProxy();
        }


        
        

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

Keyframes and Layers counter

wip, doesn't work with animated shapes for now


layersCount = 0;
keyframesCount = 0;
collection = app.project.items;
for(i = 1;i<=collection.length;i++){
    curItem = collection[i];
    if(curItem instanceof CompItem){
        len = curItem.layers.length;
        layersCount += len;
        for(j = 1;j<=len;j++){
            curLayer = curItem.layers[j];
            for(k = 1;k <= curLayer.numProperties;k++){
                for(l = 1;l <= curLayer.property(k).numProperties; l++){
                    //$.writeln(keyframesCount);
                    if(curLayer.property(k).property(l).numProperties>0){
                         for(m = 1;m <= curLayer.property(k).property(l).numProperties; m++){
                            nKeys = curLayer.property(k).property(l).property(m).numKeys;
                            if(nKeys > 0){
                                 keyframesCount += nKeys;
                            }
                            //ugh i wish i was better at recursiveness
                            //$.writeln(curLayer.property(k).property(l).property(m).matchName);
                            //$.writeln(curLayer.property(k).property(l).property(m).numKeys);     
                        }
                    }else{
                            nkeys = curLayer.property(k).property(l).numKeys;
                            if(nKeys > 0){
                                keyframesCount += nKeys;
                            }
                            //keyframesCount += curLayer.property(k).property(l).numKeys;
                            //$.writeln(curLayer.property(k).property(l).matchName);
                            //$.writeln(curLayer.property(k).property(l).numKeys);     
                    }
                }  
            }
        }
    }
}
alert(keyframesCount+" keyframes found and\n"+layersCount+" layers counted in the project");
    
    
function iterateProperties(prop){
           // $.writeln(prop.numProperties);    
    if(prop.numProperties > 0){
        for(i = 1;i<=prop.numProperties;i++){
            iterateProperties(prop.property(i));
        }
    }else{
    //    $.writeln(prop.matchName);
    }
    //$.writeln(prop.matchName);
}
//alert(app.project.items[2])

Auto Expose

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

app.beginUndoGroup("Analyze animation");
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 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";
sliderctrl.property("ADBE Slider Control-0001").setValue(3);

var detectorctrl = precomplayer.Effects.addProperty("ADBE Slider Control");
detectorctrl.name = "Detector";

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;\
";
detectorctrl.property("ADBE Slider Control-0001").expression = detectorexpression;


detectorctrl.property("ADBE Slider Control-0001").selected = true;
app.executeCommand(app.findMenuCommandId("Convert Expression to Keyframes")); 
detectorctrl.property("ADBE Slider Control-0001").selected = false;


app.endUndoGroup();
var rez = parseFloat(prompt("Resolution to detect movement. 10 is a good start, don't go above 50",10));

//step 1: iterate through sampleImage, add more rez if no change is detected, bake keys at end -- the heavy bit

//rez = 10;
exp = "steps = "+rez+";\ntot = 0;\nfor(j=height/steps/2;j<height;j = j + height/steps){\n\t";
exp += "for(i= width/steps/2;i<width;i = i + width/steps){\n\t\t";
exp += "s = sampleImage([i,j], [width/steps/2,height/steps/2], postEffect = true, t = time);\n\t\ttot += (s[0]+s[1]+s[2]+s[3]);\n";
exp += "\t}\n}\ntot";

s = app.project.activeItem.selectedLayers[0];
v = s.Effects.addProperty("ADBE Slider Control");
slider = v.property("ADBE Slider Control-0001");
slider.expression = exp;
slider.selected = true;
app.executeCommand(2639);

//step 2: if current value is similar to the previous' frame value, write 0 else, write 1, bake result

exp = "f = effect('"+v.name+"')('ADBE Slider Control-0001');\n";
expB = exp + "f-f.valueAtTime(time-thisComp.frameDuration)==0?0:1;";
slider.expression = expB;
app.executeCommand(2639);

mainKeys = [];
for(i=1;i<=slider.numKeys;i++){
    mainKeys[mainKeys.length] = slider.keyValue(i);
}

//step 3: iterate through every frame and add which frame it corresponds to

exp += "a = 0;\nfor(i=0;i<=time*(1/thisComp.frameDuration);i++){\n\t";
exp += "if(f.valueAtTime(i*thisComp.frameDuration)==1){\n\t\ta = i*thisComp.frameDuration;\n\t}\n}\na;";
slider.expression = exp;
app.executeCommand(2639);
slider.expression = "";
slider.expressionEnabled = false;


//step 4: clean up keys
for(i=mainKeys.length-1;i>0;i--){
    if(mainKeys[i]==0){
        slider.removeKey(i+1);
    }else{
        slider.setInterpolationTypeAtKey(i+1,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD);
    }
}
slider.setInterpolationTypeAtKey(1,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD); 

Batch render project items

Select project footage and it will render them with the chosen rq preset

template = "QTJPG2000"; // chosen by hand, for now

selectedItems = app.project.selection;
rq = app.project.renderQueue;

for(i=0;i<selectedItems.length;i++){
    
    item = selectedItems[i];
    path = item.mainSource.file.path.toString()+'/'+item.name;
    p = app.project.items.addComp(item.name,item.width,item.height,1.0,item.duration,item.frameRate);
    p.layers.add(item);
    rqitem = rq.items.add(p);
	rqitem.outputModules[1].file = new File(path);
	rqitem.outputModules[1].applyTemplate(template);
    
}

Auto Comp to Renderqueue with Template

//todo: be able to select existing RQ items instead of simply comps, and edit their existing paths


function e(s){$.writeln(s)} //debug fn

var win;

function renderAll(){
	//prefTemplate = (app.settings.haveSetting("renderCompWithTemplate", "template"))?(app.settings.getSetting("renderCompWithTemplate", "template")):false;
	
	sel = app.project.selection;
	
	if(sel.length>0){

		
		//needs a comp to create a render item to get templates

		getTemplateUI(sel[0]);
		
		
		//if ui worked fine, there should be a template setting string, if there's a bug or user cancelled, fuck it.

		prefTemplate = (app.settings.haveSetting("renderCompWithTemplate", "template"))?(app.settings.getSetting("renderCompWithTemplate", "template")):false;

		if(prefTemplate != false && prefTemplate != "null" ){
			
			for(i=0;i<sel.length;i++){  

				renderCompWithTemplate(sel[i],prefTemplate) 

			}

		}else{


			writeLn("Send to renderqueue cancelled.");

		}
	}else{
		alert("Select at least one comp or render queue element");
	}
}

function getTemplateUI(nullComp){

	
	// get previous render settings, so we can use it as default selection later

	prefTemplate = (app.settings.haveSetting("renderCompWithTemplate", "template"))?(app.settings.getSetting("renderCompWithTemplate", "template")):false;

	// void render settings if user clicks cancel, to be changed

	app.settings.saveSetting("renderCompWithTemplate", "template","null");

	ai = app.project.activeItem;
	

	// must have at least one comp selected, and the project saved to know where to place the renders
	// eventually the user shouldn't have to save his AEP, but have it appear as a warning somewhere that he should

	if( nullComp != null && nullComp instanceof CompItem && app.project.file != null ){
		
		rq = app.project.renderQueue;

		//create null render comp to fetch templates, delete it right afterwards

		rqitem = rq.items.add( nullComp );
		rqtemplates = rqitem.outputModules[1].templates;
		rqitem.remove();

		// dockable window to do
		//res = "dialog { properties:{ resizeable:true }, preferredSize: [690, 20],  alignChildren: 'fill',orientation:'column',\
		res = "dialog {  \
				properties:{ resizeable:true }, alignChildren: 'fill',orientation:'column',\
				templatesPnl: Panel { \
					orientation:'column', alignChildren:['left', 'top'],text: 'Choose a rendering template',\
					templates: DropDownList {}, \
				}, \
				filePnl: Panel { \
					orientation:'column', alignChildren:['left', 'top'],text: 'Display Paths',\
					pathRbs: Group { \
						orientation:'row',  alignment: 'left', alignChildren:['left', 'bottom'] \
						txt: StaticText { alignment:'left', text:'File path style:' }, \
						unixStylePathRb: RadioButton { text:'Unix (mac)' }, \
						winStylePathRb: RadioButton { text:'Windows', value:true } \
					}, \
					showPathText: StaticText {alignment: 'fill'},  \
					edithPathGrp: Group { \
						orientation:'row', spacing:2, alignment: 'left', \
						editPathBox: EditText { alignment: ['fill','center'],  margins: 8, text:'', properties:{borderless:true}},  \
						testBtn: Button { alignement:[center','center'], text:'Test', properties:{name:'test'} } ,\
					}, \
					explainPathText: StaticText {alignment: 'fill', properties:{multiline: true } }, \
					explainPathTextGrp: Group { \
						orientation:'row',  alignment: 'left', alignChildren:['left', 'center'] \
						c1: StaticText {alignment: 'fill', properties:{multiline: true } },  \
						c2: StaticText {alignment: 'fill', properties:{multiline: true } } \
					}  \
				}, \
				buttons: Group { orientation: 'row', alignment: 'right', \
					okBtn: Button { text:'OK', properties:{name:'ok'} }, \
					cancelBtn: Button { text:'Cancel', properties:{name:'cancel'} }, \
				}, \
			}";

		
		// get/set the display path preference (it will always be unix style internally) 1=windows 0=mac/unix


		
	  
		//input base text 

		explanationText = "You can use the following variables ( and can't use * ? \" < > or | ): \n";
		/*explanationText += "\t{compname}\t\t{compfolder}\n";
		explanationText += "\t{projpath}\t\t\t{compid}\n";
		explanationText += "\t{projname}\t\t\t{date}\n";
		explanationText += "\t[#], [##], [####]... \t(padding)\n";*/

		// create window resource

		win = new Window (res);
        
        //set the preferred template

		preferredItem = 0;

		for(i=0;i<rqtemplates.length;i++){

			//skip hidden templates
			if(rqtemplates[i].indexOf('_HIDDEN') != 0 ){
				item = win.templatesPnl.templates.add ('item',rqtemplates[i]);
				if(rqtemplates[i] == prefTemplate){
					preferredItem = i;
				}
			}
		}
		win.templatesPnl.templates.selection = win.templatesPnl.templates.items[preferredItem];

		// can't add newline \n character in res, so fill in text now

		win.filePnl.explainPathText.text = explanationText;
		
		// set os choice radio button, figure out if it's in prefs 
		// eventually users shouldn't see this, the script should be tailored for mac or windows

		os = $.os.toLowerCase().indexOf("windows") != -1;

		if( app.settings.haveSetting("renderCompWithTemplate", "oschoice") ){
			os = ( app.settings.getSetting("renderCompWithTemplate", "oschoice") == 1 ) ? 1 : 0 ;
		}else{
			app.settings.saveSetting("renderCompWithTemplate", "oschoice", os);
		}

		win.filePnl.pathRbs.unixStylePathRb.value = !os;

		// set path template box, if it doesn't have one, use a default;

		inputBox = win.filePnl.edithPathGrp.editPathBox;
		outPutBox = win.filePnl.showPathText;

		var pathTemplate = "";

		if( app.settings.haveSetting("renderCompWithTemplate", "pathtemplate") ){
			pathTemplate = app.settings.getSetting("renderCompWithTemplate", "pathtemplate");
		}else{
			pathTemplate = "{projpath}/RENDER/{date:ymd}_{compname}/{compname}.[####]";
			app.settings.saveSetting("renderCompWithTemplate", "pathtemplate", pathTemplate);
		}

		inputBox.text = pathTemplate;

		//add ui callbacks

		//call once, to fill the result text box
		
		updatePath(inputBox, outPutBox);

		inputBox.onChange = inputBox.onChanging = function() { updatePath(inputBox, outPutBox) };

		/*-----TO BE CUT---------*/
		win.filePnl.pathRbs.unixStylePathRb.onClick = win.filePnl.pathRbs.winStylePathRb.onClick = function() {
			//save preferences

			app.settings.saveSetting("renderCompWithTemplate", "oschoice", (!win.filePnl.pathRbs.unixStylePathRb.value)?1:0);
			updatePath(inputBox, outPutBox);
		}
		/*-----END CUT-----------*/


		win.filePnl.edithPathGrp.testBtn.onClick = function(){
			fp = descriptionToFilePath("",1,inputBox.text, 0);
			fpFolder = new Folder(fp);
			e(fpFolder.absoluteURI);
		}

		win.buttons.okBtn.onClick = function(){
			//save preferences
			prefTemplate = win.templatesPnl.templates.selection;
			app.settings.saveSetting("renderCompWithTemplate", "pathtemplate", inputBox.text);	
			//app.settings.saveSetting("renderCompWithTemplate", "oschoice", (!win.filePnl.pathRbs.unixStylePathRb.value)?1:0);
			app.settings.saveSetting("renderCompWithTemplate", "template",prefTemplate);
			win.close();
		}

		win.layout.layout(true);

		win.onResizing = win.onResize = function () {this.layout.resize()};
	
	
		
		win.center();
		win.show();

	}else{

		//alert dialogs suck, but they are efficient

		alert("Select at least one comp to render, and make sure your After Effects file is saved to disk.");
	}
}

function resizeUI(windowObj){
	//nasty function which creates a new textbox to figure out how big the editbox is, so no text is clipped

	var textbox = windowObj.filePnl.edithPathGrp.editPathBox;
	//var testbutton = windowObj.filePnl.edithPathGrp.testBtn;

	g = windowObj.filePnl.add("group", [0,0,0,0]);
	g.enabled = g.visible = false;
	tmp = g.add("edittext", undefined, textbox.text, {enabled:false, visible:false});

	var preferredWidth = tmp.preferredSize[0];

	//e("newpref: "+r.preferredSize[0]);
	windowObj.filePnl.remove(g);

	textbox.size = [preferredWidth, textbox.preferredSize[1] ];
	//testbutton.size = testbutton.preferredSize;

	windowObj.layout.layout(true);

}

function updatePath( inputRes, outputRes){
	
	os = ( app.settings.getSetting("renderCompWithTemplate", "oschoice") == 1 ) ? 1 : 0 ;
	
	input = inputRes.text;

	input = descriptionToFilePath("",1,input, 1);
	input = setPathStyle(input, !os) ;  //backslashes to slashes
	outputRes.text = input;

	resizeUI(inputRes.parent.parent.parent);

	//windowObj.layout.layout(true);

}

function descriptionToFilePath(comp, rqid, descriptionString,forDisplay){

	// takes a description like {projpath}/{projname}_{compname}/{compname}_[#####] and transforms it into a usable unix path
	// if there is no comp fed, use the first in the selection.
	// 'forDisplay' int decides if it's for display (add frame count and turn %20s to spaces) or actual path to be used
	// 'rqid' is the number of the comp in the renderqueue, default is 1

	var str = descriptionString;

	if(comp == "" || comp == null){
		comp = app.project.selection[0];
	}
	var projName = app.project.file.name.toString();

	//standard stuff, comp path name etc....

	str = str.replace(/\{projpath\}/gi, app.project.file.path.toString());
	projName = projName.substr(0, projName.lastIndexOf('.')) || projName;
	str = str.replace(/\{projname\}/gi, projName);
	str = str.replace(/\{compname\}/gi, comp.name);
	var parentFolder = (comp.parentFolder.name == "Root") ? "" : comp.parentFolder.name ;
	str = str.replace(/\{compfolder\}/gi, parentFolder);

	//if we use {id}, make sure it's padded 

	var rq = app.project.renderQueue;
	str = str.replace(/\{id\}/gi, pad(rq.items.length.toString().length, rqid.toString(),"0") );


	//figure out padding, if it's forDisplay, show it to the user

	startPad = str.indexOf("[#");
	endPad = str.lastIndexOf("#]");

	if(startPad > 0  && endPad > 0 && endPad > startPad && forDisplay){
		startFrame = comp.workAreaStart/comp.frameDuration;
		str = str.substring(0,startPad)+pad(endPad-startPad, startFrame.toString(), "0")+str.substring(endPad+2,str.length);
	}

	//date

	var datesArray = str.split("{date:");
	var tempstr = "";

	if(datesArray.length > 0){

		var today = new Date();
		var dd = today.getDate().toString();
		var mm = (today.getMonth()+1).toString();
		var yyyy = today.getFullYear();

		tempstr = datesArray[0];

		for(i = 1 ; i < datesArray.length; i++ ){
			endBracketPos = datesArray[i].indexOf("}");
			nextBracketPos =  datesArray[i].indexOf("{");
			nextBracketPos = (nextBracketPos == -1)? 9999 : nextBracketPos; //case if date is the last used tag
			if(endBracketPos > -1 && endBracketPos < nextBracketPos){
				dateString = datesArray[i].substring(0, endBracketPos );
				dateString = dateString.replace(/y/gi, yyyy);
				dateString = dateString.replace(/d/gi, pad(2, dd, "0"));
				dateString = dateString.replace(/m/gi, pad(2, mm, "0"));
				tempstr += dateString + datesArray[i].substring(endBracketPos+1,datesArray[i].length);
			}else{
				tempstr = str;
			}
		}
		str = tempstr;
	}

	if(forDisplay){
		str += ".ext";
	}

	return str;
}

function renderCompWithTemplate(comp,template){
	//sends the comp to the renderqueue with the selected file path, creating folders and subfolders if needed

	rq = app.project.renderQueue;
	rqitem = rq.items.add(comp);
	rqtemplates = rqitem.outputModules[1].templates;

	filename = app.project.file.name;
	filepath = app.project.file.toString();
	projpath = filepath.slice(0,filepath.length-filename.length);

	savePath = projpath+comp.name+"/"+filename.slice(0,filename.length-4)+"/";
	saveFile = comp.name+".[####].tif";
	saveFolder = new Folder(savePath);
	saveFolder.create();
	rqitem.outputModules[1].file = new File(savePath+"/"+saveFile);
	rqitem.outputModules[1].applyTemplate(template);

}

function setPathStyle(path, toUnixPath){
	// takes a string and creates either a unix path like /c/my%20folder/ if toUnixPath is set to true
	// or a windows path like c:\my folder\. Returns false if there's a problem (tbd)
	if(!toUnixPath){
		//unix -> win
		path = path.replace(/\//, "");
		path = path.replace(/\//, ":/");
		path = path.replace(/%20/g, " ");
		path = path.replace(/\//g, "\\");
	}else{
		path = "/"+path.replace(":\\", "/");
		path = path.replace(/\\/g, "/");
		path = path.replace(" ", "%20");
		//path = path.substring(0,path.lastIndexOf("/"));
	}
	return path;
}

function pad(width, string, padding) { 
  return (width <= string.length) ? string : pad(width, padding + string, padding);
}

renderAll();


create effect creator

ui

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

/*

var windowObj;
function ui(thisObj) {
	windowObj = (thisObj instanceof Panel) ? thisObj : new Window("palette", "ui_test", [100, 100, 300, 300]);
	addButton("flip",windowObj);
	return windowObj;
}*/


function addButton(buttonname,ui){
	//ui.add("button", [0, 0, 20, 20], But_01[0]);
	return ui.add("button", undefined, buttonname);
}





function ui(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: 'add btn' ,preferredSize:[-1,30]} , \
                            sendWF: Button {text: 'Send To Watchfolder' ,preferredSize:[-1,30]} , \
                    }";	
            pan.grp = pan.add(res);        
            pan.grp.setWF.onClick = function () {
            	addButton("joe",pan.grp);
            	resfreshUI(pan);
                    }
            pan.grp.sendWF.onClick = function () {
                }
            resfreshUI(pan);    
            /*
            pan.layout.layout(true);
            pan.layout.resize();
            pan.onResizing = pan.onResize = function () {this.layout.resize();}*/
            return pan;
    }
}

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

}

ui(this);


fn

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

// to be done:
// 		save keys
// 		save masks
//		save text if text layer, camera zoom if camera etc
//		use FFX as custom values

function grabEffects(layers){
	
	var buffer = "";
	var chosenLayerName = "curLayer";

	for (i=0;i<layers.length;i++){

		effs = layers[i].property("ADBE Effect Parade");

		for(j=1;j<=effs.numProperties;j++){

			//create the effects, no matter if properties below have values

			buffer += "\n\tprop = "+ chosenLayerName +".Effects.addProperty(\""+ effs.property(j).matchName +"\");\n";
			buffer += "\tprop.name = \""+ effs.property(j).name +"\";\n";
			buffer += "\tprop.enabled = "+ effs.property(j).enabled +";\n";

			for(k=1;k<=effs.property(j).numProperties;k++){

				curProp = effs.property(j).property(k);
				
				//only add value if it's non-default

				if(curProp.isModified){

					curPropValue = propType(curProp);

					// if it returns -1, it's (probably) a useless 'parent' property, skip

					if( curPropValue != -1){
						
						// /!\ if it'sa  custom value type, there's little to no way of using it in a script, so we can comment it out and warn the user -- there are also false positives, ^but they should be ignored with '-1' as curpropvalue

						buffer += "\t\t// "+curProp.name+((curPropValue===false)?" /!\\ Can't use this custom data! could be a false negative, but unlikely ":"")+"\n";                                        
						buffer += "\t\t"+((curPropValue===false)?"//":"") + "prop.property(\""+curProp.matchName+"\").setValue(" + curPropValue + ");\n";
						
						// if there is an expression and it's enabled, use it

						if(curProp.expression != "" && curProp.expressionEnabled){
							buffer += "\t\t"+((curPropValue===false)?"//":"") + "prop.property(\""+curProp.matchName+"\").expression = \""+curProp.expression+"\";\n";
						}
					}
				}
			}
		}
	}

	return buffer;
}

function outputCode(){
	
	effectsLayers = app.project.activeItem.selectedLayers; //use effects from all selected layers

	buffer = "";
	if(effectsLayers.length>0){
		buffer = "var selectedLayers = app.project.activeItem.selectedLayers;\n";
		buffer += "for (i=0;i<selectedLayers.length;i++){\n";
		buffer += "\n\tcurLayer = selectedLayers[i];\n"; 
		buffer += grabEffects(effectsLayers) + "\n}";
		//e(buffer);
	}
	return buffer;
}


e(outputCode());
e("\n------------------------------------------------------");


 function propType(property){

	returnvalue = false;
	
	e("--->"+property.name+" "+property.propertyValueType+"\n"); //debug
	//e("--->"+property.name+" "+property.canVaryOverTime+"\n"); //debug		
	
	switch(property.propertyValueType){

		case PropertyValueType.ThreeD_SPATIAL:
			returnvalue = "["+property.value[0]+","+property.value[1]+","+property.value[2]+"]";
			break;
		case PropertyValueType.ThreeD:
			returnvalue = "["+property.value[0]+","+property.value[1]+","+property.value[2]+"]";
			break;

		case PropertyValueType.TwoD_SPATIAL:
			returnvalue = "["+property.value[0]+","+property.value[1]+"]";
			break;
		case PropertyValueType.TwoD:
			returnvalue = "["+property.value[0]+","+property.value[1]+"]";
			break;			

		case PropertyValueType.OneD:
			returnvalue = property.value;
			break;		

		case PropertyValueType.COLOR:
			returnvalue = "["+property.value[0]+","+property.value[1]+","+property.value[2]+","+property.value[3]+"]";
			break;

		case PropertyValueType.LAYER_INDEX:
			returnvalue = property.value;
			break;

		case PropertyValueType.MASK_INDEX:
			returnvalue = property.value;
			break;			

		//no way to store custom value but FFX, tbd
		case PropertyValueType.CUSTOM_VALUE:
			returnvalue = false;
			break;

		// if we land on default, it is _most likely_ a property 'parent' with no real use
		default:
			//e("using default: --->"+property.name+" "+property.propertyValueType+"\n")
			returnvalue = -1;
			break;
	}
	return (returnvalue);
 }

left to right

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


function replaceLR(s){
    
    s = s.replace(/left/g, "right");
    s = s.replace(/Left/g, "Right");
    s = s.replace(/LEFT/, "RIGHT");
    return s;
}


function checkImportLeftRight(layer){

	// is it a footage layer (includes solids etc.. not Comps)
	if(layer.source instanceof FootageItem){
		
		//is it a footage item (.exr)
		if(layer.source.file != null){
			path = layer.source.mainSource.file.toString();
	
			 //check path, if it has 'left' in it, grab 'right' and import and replace footage
			if(path.toLowerCase().indexOf("left") > -1){

				rightPath = replaceLR(path);
				rightFile = new File(replaceLR(path));

				//if file exists, import right file, replace layer with it
				if(rightFile.exists){
					
					
					var io = new ImportOptions(rightFile);
					if(io.canImportAs(ImportAsType.FOOTAGE)){
					
						io.importAs = ImportAsType.FOOTAGE;
						io.sequence = true;
						src = app.project.importFile(io);
						
						return(layer.replaceSource(src,1));
						
					}else{
						return false
					}



				}else{
					alert("file not found! "+rightPath);
					return false
				}
			}
	
		}
	}
}

/*
a = app.project.activeItem;

for(i=1;i<=a.layers.length;i++){
 //e(a.layers[i].name);
 e(checkImportLeftRight(a.layers[i]));
    //if it's footage

}

for(i = 1;i<app.project.numItems;i++){
	e(i+" "+app.project.items[i].name); 
}
// 3 and 4
*/
function new3dComp(leftcomp,rightcomp){
	threedcomp = leftcomp.name.replace(/left/g,"3D");
	w = leftcomp.width;
	h = leftcomp.height;
	duration = leftcomp.duration;
	frameRate = leftcomp.frameRate;
	newComp = app.project.items.addComp(threedcomp, w , h , 1.0, duration, frameRate);


	
	newComp.layers.add(rightcomp);
	newComp.layers.add(leftcomp);
	
	threed = newComp.layers.addSolid([1,1,1],"3D Glasses",w,h,1.0);
	threed.adjustmentLayer = true;
	
	threedglasses = threed.Effects.addProperty("ADBE 3D Glasses2");
	threedglasses.property("Left View").setValue(2);
	threedglasses.property("Right View").setValue(3);
	threedglasses.property("3D View").setValue(12);

}
new3dComp(app.project.items[3],app.project.items[4]);



//e(app.project.activeItem.name+" --- ")

Output .SRT from layer markers

function pad10(n){
    return (n<10)?"0"+n:n;
}
function pad100(n){
    return (n<100)?"0"+pad10(n):n;
}

function formatTime(time){
    time =~~ (time*1000)/1000;

    var hrs = ~~(time / 3600);
    var mins = ~~((time % 3600) / 60);
    var secs =  ~~(time % 60);    
    var ms = ~~((time-Math.floor(time))*1000);
    
    time = pad10(hrs)+":"+pad10(mins)+":"+pad10(secs)+","+pad100(ms);
    return time;
}

data = "";
var ai = app.project.activeItem;
if ( ai instanceof CompItem && ai.selectedLayers.length == 1) {
    var m = ai.selectedLayers[0].marker;
    for(i=1;i<=m.numKeys;i++){
        data += i;
        data += "\n"+ formatTime(m.keyTime(i))+" --> "+ formatTime(m.keyTime(i)+m.keyValue(i).duration);
        data += "\n"+m.keyValue(i).comment;
        data += "\n";
       }
}


alert(data)

//result:
//
//1
//00:03:16,945 --> 00:03:17,364
//testing comment 1
//
//2
//01:59:47,228 --> 01:59:49,396
//second subtitle at 2 hours in
//

Shift keys

function e(str){
      $.writeln(str);
}
function getLayerFromProperty(prop){
    return prop.propertyGroup(prop.propertyDepth)
}
var c = app.project.activeItem;
if( c != null){
    var props = c.selectedProperties;

    //e(props[0].matchName);
    for(i = 0;i<props.length;i++){
        e("\n---------------------------\n"+props[i].matchName+" / "+getLayerFromProperty(props[i]).name);
        k = props[i].selectedKeys;
        for(keyVar in k){
            //k[keyVar]
            
            
           // e( ); 
           e(props[i].keyTime(k[keyVar])); 
           //e(k[keyVar]); 
        }
    //CANT FUCKING NUDGE KEYFRAMES
      //  e(props[i].selectedKeys);
    }
}



Selection tool palette

{
//tmpdir = $.getenv("tmp");
    function toolWindow(thisObj){
        function drawUI(){
            var my_palette = new Window("palette","Selection Tool");
            my_palette.bounds = [300,200,300,285];
            /*var button1 = addScriptButton(my_palette,[l_button_left,   5, l_button_right, 25], 
                    "Find and Replace Text",    demosDirectory, "Find and Replace Text.jsx");
            var button3 = addScriptButton(my_palette,[l_button_left,  30, l_button_right, 50], 
                    "Scale Composition", 		demosDirectory, "Scale Composition.jsx");
            var button4 = addScriptButton(my_palette,[l_button_left,  55, l_button_right, 75], 
                    "Scale Selected Layers", demosDirectory, "Scale Selected Layers.jsx");

            var button6 = addScriptButton(my_palette,[r_button_left,   5, r_button_right, 25], 
                    "Sort Layers by In Point",     demosDirectory, "Sort Layers by In Point.jsx");
            var button8 = addScriptButton(my_palette,[r_button_left,  30, r_button_right, 50], 
                    "Render and Email",    myDirectory,    "Render and Email.jsx");

            var button12 = addHelpButton(my_palette,[r_button_left,  55, r_button_right, 75]);*/

            my_palette.show();
        }
    drawUI();
    }
 toolWindow(this);
}

Make .bat file (WIP)

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


function createBat(){
    d = new Date();
    m = d.getMonth()+1;
    j = d.getDate();
    if(m<10){
        m="0"+m;
    }
    if(j<10){
        j="0"+j;
    }

    var txtFile = new File("~/Desktop/ae_render"+m+"_"+j+".bat");
    alert(pathToWinPath(app.project.file));
    txtFile.open("w","TEXT","????");
    txtFile.close();
}

res =
"dialog { \
    allGroups: Panel {\
        orientation:'column',\
        alignChildren:'fill',\
        text:'Options', \
        chckOff: Checkbox {text:'Shutdown PC when finished'}, \
        chckAppend: Checkbox {text:'Append (instead of overwriting)'},\
        aePath: Group {orientation:'row',align:'fill', alignChildren:['fill','center'],\
            aeLocBtn: Button {text:' aerender.exe location : '}, \
            aeLocTst: StaticText {text:'C:\\Program fil...'}} \
        okCancel: Group {orientation:'row',align:'fill', alignChildren:'center',\
            okBtn: Button { text:'OK', properties:{name:'ok'}} , \
            cancelBtn: Button { text:'Cancel', properties:{name:'cancel'}},\
        }\
    }\
}";

win = new Window (res);
win.allGroups.chckAppend.value = true;
win.center();
win.show();

Carnet De Voyage rangement

//rangement pour carnet de voyage
app.beginUndoGroup("CDV Rangement");
boolVal = false;
sel = app.project.selection;
if(sel.length == 1){
    shot = sel[0];
    allItems = app.project.items;
    sortItems = new Array();
    for(i=1;i<=allItems.length;i++){
        if(!(allItems[i] instanceof FolderItem)){
            if(allItems[i].parentFolder.name == "Root"){
                sortItems[sortItems.length] = allItems[i];
            }
        }else{
            if(allItems[i].parentFolder.name == "Root"){
                    sortItems[sortItems.length] = allItems[i];
            }        
        }
    }
    t = app.project.items.addFolder("ELMTS");
    for(i=0;i<=sortItems.length-1;i++){
        if(sortItems[i] != shot){
        sortItems[i].parentFolder = t;
        }
    }
}
if(app.project.renderQueue.item(1).outputModules.length == 2){ //copie d'un output module à la main nécéssaire
    fileP = app.project.renderQueue.item(1).outputModules[1].file.toString();
    fileP = fileP.replace("/m/EPISODE","/b/EPISODE");
    fileP = new File(fileP);
    app.project.renderQueue.item(1).outputModules[2].file = fileP;
    app.project.renderQueue.item(1).outputModules[2].applyTemplate("PNGSeq");
    var curFile = app.project.file.name;
    curFile = curFile.substring(0,curFile.length-4);
    var pth = app.project.file.path+"/"+curFile+"_MOV+PNG.aep";
    var mySaveFile = new File(pth);        
    app.project.save(mySaveFile);
}else{
    writeLn("Duplicate output modules first");
}
app.endUndoGroup();

Chat

JSX

var myPalette = buildUI(this);

    if (myPalette != null && myPalette instanceof Window) {
        myPalette.show()
        }

    function buildUI (thisObject) {

    if (thisObject instanceof Panel) {
        var myWindow = thisObject;
        } else { 
        var myWindow = new Window ("palette", "My Window");
        }

g = myWindow.add("group");
g.orientation = "row";
g.alignChildren = "left";
//g.alignement = "top";
bt1 = g.add("button",undefined,"test");
bt1.onClick = bob;
//myWindow.myPanel.titleText = myWindow.myPanel.add("staticText");
//myWindow.myPanel.titleText.text =  "Move After Render v1.0";  

myWindow.layout.layout(true);
//myWindow.layout = new AutoLayoutManager(myWindow);
//myWindow.layout.resize();
return myWindow;
} 
h = 0;
function bob(){
//    alert();
    w = this.parent.parent;
    p = this.parent;
    h++;
    bt2 = p.add("button",undefined,"test"+h);  
      bt2.alignement = ["left","top"];
    if(h%3==0){

       
        bt2.helpTip = "TTTTT";
        }
    bt2.onClick = flup;
   //w.update();
   w.layout.layout(1); 
  // w.layout.resize();

//    w.show();
    }

function flup(){
    f = new Folder($.fileName);
    f = new Folder(f.parent+"/shelves/");
   if(!f.exists){f.create()};
    cfg = new File(f.parent+"/shelves/shelves.cfg");
    cfg.open('e');
    cfg.write(this.onClick);
    cfg.close();
    cfg.execute();
}
function pop(){
    f = new Folder($.fileName);
    fi = new File(f.parent+"/shelves/testShelf.jsx");
    fi.open('e');
    eval(fi.read());
    }
----------------
function grab(){
    var reply = "";
    c = new Socket;
    if (c.open ("berniebernie.fr:80")) {
        if(c.writeln ("GET /dump/afx/chat.php  HTTP/1.0\nHost: berniebernie.fr\n")){
            reply = decodeURIComponent(c.read(1000));
            reply = reply.split("--afx chat file log--");
            reply = reply[1];
        }else{
             return 0;
        }
        c.close();
    }else{
             return 0;
    }
    return reply;
}
function talk(str){
    var reply = "";
    c = new Socket;
    if (c.open ("berniebernie.fr:80")) {
        if(c.writeln ("GET /dump/afx/chat.php?msg="+str+"  HTTP/1.0\nHost: berniebernie.fr\n")){
            reply = decodeURIComponent(c.read(1000));
            reply = reply.split("--afx chat file log--");
            reply = reply[1];
        }else{
             return 0;
        }
        c.close();
    }else{
             return 0;
    }
    return reply;
}
//alert(grab());
alert(talk("flipflap"));

////////////////
app.preferences.getPrefAsLong("Main Pref Section","Pref_SCRIPTING_FILE_NETWORK_SECURITY"

Php

<?php
$myFile = "testFile.txt";
if(isset($_GET['msg']) && $_GET['msg'] != ""){
    $fh = fopen($myFile, 'a') or die("can't open file");
    fwrite($fh, ($_GET['msg']."\n"));
    fclose($fh);    
}else{
    //$fh = fopen($myFile, 'r') or die("can't open file");
    //include$str = $str, true);
    //header("Content-Type: plain/text"); 
    echo encodeURIComponent(file_get_contents("testFile.txt"));
    //echo nl2br(htmlentities(file_get_contents("testFile.txt")));
}

?>