Afx Javascript Temp

From bernie's
Jump to navigation Jump to search

Auto-expose rewrite/debug

// save as AutoExpose.jsx in your program files/after effects/scripts/ScriptUI folder or install from the AE ui
// v1.1 - made the 8bit switch w/o user input, fixed bugs


{
// currentSlider holds a reference to the Detector property after setupDetector runs,
// making it accessible to bakeKeys and applyKeys.
var currentSlider = "none";
var savedBitDepth = app.project.bitsPerChannel;


 function watchFolderUI(thisObj){
    // Determine if the script is running as a dockable panel or a floating window
    pan = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Auto Expose", [100, 100, 300, 100]);
    var res = 
    "group { \
                alignment: ['fill','fill'], \
                alignChildren: ['fill','top'], \
                orientation: 'column', \
                    txt1: StaticText {text: 'Step1: Select a footage or layer in a comp and move to a frame where there is a change in animation.  Click setup button and increase resolution slider until the (red) slider value called \\'Detector\\' picks up a change (a value > 0)',properties:{multiline:true}} , \
                    setupDetector: Button {text: 'Setup detector guide layer' } , \
                    txt2: StaticText {text: 'Step2: Bake the detected animation as time remapped keys, this can take some time, keep an eye on your \\\"Info\\\" panel. If some animation is not detected, change resolution slider and run again',properties:{multiline:true}} , \
                    bakeKeys: Button {text: 'Bake to Keys'} , \
                    txt3: StaticText {text: 'Step3: 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}} , \
					applyKeys: Button {text: 'Apply as time remapping'} , \
            }";	
    pan.grp = pan.add(res);        
    
    // Assign click handlers
    pan.grp.setupDetector.onClick = function () {
        setupDetector();
    }
    pan.grp.bakeKeys.onClick = function () {bakeKeys();}
    pan.grp.applyKeys.onClick = function () {applyKeys();}

    // Layout and resizing management for the UI
    pan.layout.layout(true);
    pan.layout.resize();
    pan.onResizing = pan.onResize = function () {this.layout.resize();}
    return pan;
    }
    
// Execute the UI function
watchFolderUI(this) ;


// Helper function for writing debug messages
function e(s){
    $.writeln(s);
}



function setupDetector(){
    // Check if a composition is active
    if (!(app.project.activeItem instanceof CompItem)) {
        alert("Please select a Composition and one layer.");
        return;
    }
    
    // Check if one layer is selected
    var selectedLayers = app.project.activeItem.selectedLayers;
    if (selectedLayers.length !== 1) {
        alert("Please select exactly one layer to set up the detector on.");
        return;
    }


	
    app.beginUndoGroup("Auto expose setup Detector");
	app.project.bitsPerChannel = 8;
	
	//move playhead to frame 1 instead of 0 if user hasn't moved, so that the script shows a change (should be a fully black frame if no animation is detected)
	if(app.project.activeItem.time == 0){
		app.project.activeItem.time = app.project.activeItem.frameDuration;
	}
	
	
    var curlayer = selectedLayers[0];

    // 1. Duplicate the layer and precompose the duplicate
    var duplicatelayer = curlayer.duplicate();
    // Move the original layer before the duplicate for consistent indexing
    curlayer.moveBefore(duplicatelayer); 
    
    // Get the index of the duplicate layer before precomposing
    var futureprecompindex = duplicatelayer.index; 
    
    // Precompose the duplicate layer
    var precomp = app.project.activeItem.layers.precompose([duplicatelayer.index], duplicatelayer.name + "_anim_detection", true);
    
    // Get the layer *in the active comp* that references the new precomp
    var precomplayer = app.project.activeItem.layer(futureprecompindex);
    precomplayer.guideLayer = true;

    // 2. Solo the detector precomp layer
    var allLayers = app.project.activeItem.layers;
    for(i=1;i<=allLayers.length;i++){
        if(allLayers[i].enabled && allLayers[i].index !== precomplayer.index){
            allLayers[i].solo = false;
        }
    }
    precomplayer.enabled = true;    
    precomplayer.solo = true;

    // 3. Inside the Precomposition (precomp): setup the difference layer
    var toplayer = precomp.layers[1]; // The duplicated layer that was precomposed

    // Remove existing effects from the top layer inside the precomp
    var props = toplayer.property("ADBE Effect Parade");
    while(props.numProperties>0){
        props.property(1).remove();
    }
    
    // Duplicate the top layer inside the precomp
    var newlayer = toplayer.duplicate(); 

    // 1 frame shift + difference blendmode = highlight pixel changes
    newlayer.blendingMode = BlendingMode.CLASSIC_DIFFERENCE;
    newlayer.timeRemapEnabled = true; // Enable Time Remapping
    // Shift the duplicated layer's content by one frame
    newlayer.startTime += app.project.activeItem.frameDuration; 
    // Shift the layer's inPoint to compensate
    newlayer.inPoint = precomp.frameDuration; 

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

    // Add black solid at the bottom to ensure correct difference result
    var blackSolid = precomp.layers.addSolid([0,0,0], "Black", precomp.width, precomp.height, 1);
    blackSolid.moveToEnd();


    // 4. Add Slider Controls to the Precomp Layer in the main comp (precomplayer)
    var effectsGroup = precomplayer.property("ADBE Effect Parade");
    
    // Resolution Slider
    var sliderctrl = effectsGroup.addProperty("ADBE Slider Control"); 
    sliderctrl.name = "Resolution"; 
    var resolutionslider = sliderctrl.property("ADBE Slider Control-0001");
    resolutionslider.setValue(3);

    // Detector Slider
    var detectorctrl = effectsGroup.addProperty("ADBE Slider Control");
    detectorctrl.name = "Detector";
    var detectorslider = detectorctrl.property("ADBE Slider Control-0001");
    
    // Expression to sample the difference precomp. It samples the previous frame's result 
    // because the precomp content itself is already a difference calculation between the current 
    // and previous frame (due to the -1 frame shift on the layer inside the precomp).
    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];\
            // Sample image at time-1 frameDuration because the comp content itself is the difference.\
            a+= sampleImage(center, sampledistance , postEffect = true, t = time-thisComp.frameDuration);\
        }\
    }\
    \
    // Normalize to 0-1, scale up for sensitivity, then subtract to baseline 0 for no change\
    (a[0]+a[1]+a[2]+a[3])/(resolution*resolution)*10000-10000;\
    ";
	detectorexpression = "\
	var resolution = effect('Resolution')('ADBE Slider Control-0001');\
resolution = (resolution<1)?1:resolution;\
var cw = thisComp.width/resolution;\
var ch = thisComp.height/resolution;\
var a = [0,0,0,0];\
for(i=0;i<resolution;i++){\
	for(j=0;j<resolution;j++){\
		center = [cw/resolution/2+cw * j ,ch/resolution/2+ch/resolution * i ];\
		sampledistance = [cw/2,ch/2];\
		a+= sampleImage(center, sampledistance , postEffect = true, t = time);\
	}\
}\
// show accumulated delta\
(a[0]+a[1]+a[2])*1000;\
";
    

    detectorslider.expression = detectorexpression;
    currentSlider = detectorslider; // Assign to the global variable
    app.endUndoGroup();
 }

function bakeKeys(){
    
    // Guardrail: check if setupDetector has been run
    if (currentSlider === "none" || !(currentSlider instanceof Property)) {
        alert("Please run 'Step 1: setup detector on layer' first.");
        return;
    }
    
    app.beginUndoGroup("Auto expose bake to keys");

    // 1. Bake slider keys (initial movement detection)
    bakeCommand = app.findMenuCommandId("Convert Expression to Keyframes");
    currentSlider.selected = true;
    app.executeCommand( bakeCommand );
    
    // 2. Apply secondary expression to convert movement value (>0) to 1, or 0
    detectorexpression = "f = effect('Detector')('ADBE Slider Control-0001');\nf>0?1:0;";
    currentSlider.expression = detectorexpression;
    currentSlider.expressionEnabled = true;
    
    // 3. Bake the 0 or 1 expression (clean expression)
    currentSlider.selected = true;
    app.executeCommand( bakeCommand );
    
    // 4. Clean up keys and set the correct key values
    // 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 the value is 1 (movement detected)
        if(currentSlider.keyValue(i) > 0){
            // Set the value to the key's time, this is the desired exposed time
            currentSlider.setValueAtKey(i, currentSlider.keyTime(i));
            currentSlider.setInterpolationTypeAtKey(i,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD);
        }else{
            // Remove keys where no movement was detected (value == 0)
            currentSlider.removeKey(i);
        }
    }

    // 5. Add '0' key on first frame (Time 0)
    var compStartTime = app.project.activeItem.time; 
    
    // Add key at the comp's start time (usually 0)
    currentSlider.addKey(compStartTime); 
    
    var keyIndex = 1; 
    
    if (currentSlider.numKeys > 0) {
        // If the first key is not at compStartTime, insert the new key at index 1
        if (currentSlider.keyTime(1) !== compStartTime) { 
            currentSlider.setValueAtTime(compStartTime, 0); // Adds key, returns index
            keyIndex = currentSlider.nearestKeyIndex(compStartTime);
        }
    } else {
        // No keys left, add the first key
        currentSlider.setValueAtTime(compStartTime, 0);
        keyIndex = 1;
    }
    
    currentSlider.setValueAtKey(keyIndex, 0);
    currentSlider.setInterpolationTypeAtKey(keyIndex,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD);

    app.project.bitsPerChannel = savedBitDepth;
	app.endUndoGroup();
	
}

function applyKeys(){
    
    // Guardrail: check if detection and baking has been run
    if (currentSlider === "none" || !(currentSlider instanceof Property)) {
        alert("Please run 'Step 1: setup detector on layer' first, then 'Step 2: bake to keys'.");
        return;
    }

    var comp = app.project.activeItem;
    if (!(comp instanceof CompItem)) {
        alert("Please select a Composition and layers to apply keys to.");
        return;
    }
    
    var layers = comp.selectedLayers;
    if (layers.length === 0) {
        alert("Please select layers to apply keys to.");
        return;
    }
    
    app.beginUndoGroup("Auto expose apply keys");

    for(var i = 0; i < layers.length; i++){ 
        layers[i].timeRemapEnabled = true;
        // Use the match name for Time Remap property
        var remap = layers[i].property("ADBE Time Remapping"); 
        
        // Remove existing keys first for a clean application
        while (remap.numKeys > 0) {
            remap.removeKey(1);
        }
        
        // Copy the baked keys from the detector slider to the layer's Time Remap
        for(var j=1;j<=currentSlider.numKeys;j++){ 
            
            var v = currentSlider.keyValue(j); // Value (the frame time)
            var t = currentSlider.keyTime(j); // Time (the moment of exposure)
            
            remap.addKey(t); 
            // Setting value at time is the robust way to set keys
            remap.setValueAtTime(t, v); 
            
            // Find the index of the key we just added
            var keyIndex = remap.nearestKeyIndex(t); 

            remap.setInterpolationTypeAtKey(keyIndex,KeyframeInterpolationType.HOLD,KeyframeInterpolationType.HOLD);
        }
        
        // Expression for sequential exposure (if enabled by user):
        remap.expression = "\
        //toggle this on to set sequential time remap\n\
        a = timeRemap;\n\
        nk = a.nearestKey(time);\n\
        curframe = 0;\n\
        if(nk.time > time){\n\
            // nearest key is in the future, use the previous key index\n\
            curframe = nk.index-1;\n\
        }else{\n\
            // nearest key is now or in the past, use its index\n\
            curframe = nk.index;\n\
        }\n\
        // Get the value of the key (which is the actual time) and hold it\n\
        if (curframe > 0) {\n\
            a.keyValue(curframe);\n\
        } else {\n\
            // Before the first key, hold the time 0.\n\
            0;\n\
        }\n\
        ";

        remap.expressionEnabled = false; // Start with keyframes active, expression disabled
    }
    app.endUndoGroup();
}


}

PNG to image source


var path = '/c/temp/temp2/';
items = ['blue','red','red_o','green','green_o','gray','gray_o'];


var myFile = new File("/c/temp/temp2/pngs.txt");
myFile.open("w");
myFile.encoding = "UTF-8";

var text = "";

for (var i = 0, len = items.length; i < len; i++) {
item = items[i];


    var f = File(path+item+'.png');
    f.encoding = 'BINARY';
    f.open('e');

    var binary;
    binary = f.read().toSource();

    //var myFile = new File("/c/temp/temp2/"+item+".txt");
//            myFile.open("w");
//            myFile.encoding = "UTF-8";
text += item + " = ";

temp = binary.toString();
temp = temp.replace('(new String(','');
temp = temp.replace('))',';');


text += temp;
text += '\n';
            //myFile.write(binary.toString());
            //myFile.write('\n\n');
//            myFile.close();

    //$.writeln(binary);

    f.close();
}


myFile.write(text);

myFile.close();

Watchfolder watcher

tb integrated

//  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.

// v1.1 updated this 15 year old script for a job, surprised that it still worked-ish!
// 

{
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;


//states
// 0 - queued, paused           -> gray_o
// 1 - queued, ready to start   -> green_o
// 1 - done, no errors          -> green
// 2 - canceled, errors         -> red
// 3 - paused (?)               -> gray
// 4 - in progress              -> blue



//ui pngs


blue = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\t\u00D7\x00\x00\t\u00D7\x01\u00B1n\x17\u00B7\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00DCIDAT8\u008D\u00A5\u0093\u00B1N\x02A\x14E\u00CF{\u00BB\u0089T\x04$F\u00D8\u0086\u00CE\u00BF\u00D0\u00DE\u00D2X\u0091\u00F8!\u00FE\x0B\u008D\u0095%\r\u00FE\u0085\u00DD~\x01\x1D\x18-\u008C\x15[8\u00D7\u008250\u0082\u00D9\u00D9\u00EC\u00ADf\u0092w\u00CF\u00DC\u00FB\u00921It\u0091wr\x03\u00F9\u00E1\u00C5\x16\u00EF7`s\u00CC\u00F3\u00FF\f;iK\u00F0\u0099\u00EE\u0087e<\u0098e\x0F\x04]AS-Ux\u00B8\x06\u00CA\u00B8\u00824M\u00CAmV!{\u0083\u00BF;\u0090M\u0092\x00\u00B2@\u00F6}\x02\u0080.\u0092\x00(#\u009CL\u00C0 \u00CD\u00CF\x19=\u00DFD\x00{Z\u00F50\x1A\u00B6_\u00CBq\u00DD\u009E\x7F\u00C5\t\u00FA\u0083K\u00D4\u00B8\u00FE\u009D\x02\x1F\u00BF\u00C7\u0083\x17\u00C3\x04c\x03<7\u00D8\x05\u00FEr\f0\x15\u00C0\u00AB\u00EEF\u008FI)j\u00ED+\x18\x05\u00B0nc\u008E\x01b\\Wh\u00A5}\x05g\u0089\u00E9\u00B3-\u00C0\u00BA~\u00E7\x1F\u0090\u00E3<q\u00B2\\\x19\u00D4\x00\x00\x00\x00IEND\u00AEB`\u0082";
red = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\t\u00D7\x00\x00\t\u00D7\x01\u00B1n\x17\u00B7\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00E5IDAT8\u008D\u00A5\u0093;N\x031\x14E\u00CF\u00F5D\u0090\u0092\x06\u0085\u00A4\u00A1c\x01\x14tD\u00CA&\u0090\"X\b{\u00A1\u00A1\u00A2\u00A0%\u008B\x18\th\u00B2\t\x06)\x05P\u0091(\u00E3KE\x14gD\u00F0h^eK>\u00E7}l\u00CB6]\"t\u00A2\u0081\u00DE\u00F6\u00E6\u00F1\u00E2|l\u00B8\u00B3\u00D4\u00FB\x0B\x00\x10|G1\u009D\u0096\u00AF\u00F3\u00F4`\b7\u00D8g\u00FA'\u00AB\u00F0R\u00E8\x12\u0098'-\u00D8\u009C\u00E6\u0094m\u00C2\x12\u00FC\x0E\u00BB3\u00B0\u00879\x02\u00E1\u0088\u00D5\x14H:\u00CE\x11\u0080\nQ7\x05\u00C6G9\u00B8\u00F1\u00E1Z\u00FD*\x11\u00DCO&}vneO\u0084\u00EB\u00B2\u00FCJ\x04\x07\u00AB\u00CF\x01\u0090\u00FB\u00AA\x16\u00BF\u008BM\u00C6\u00A2\u00D6\x10\u00A8\u0084\x1E\u00F6\u0091V\u00B4\u00EAb\u00D6\x10\x10\x19\x11x\u00BE*_n3\u00AB\x00\u00B6\u0087(F\u008A\u00BC\u00B5\u0081S\x01\u009C\u0080\u00AB\u00B6\u0082M\x0B\x0Ez\n\u00EB\u00F0\u00D1V\u00A0\u00AE\u00DF\u00F9\x07\x1F\u0090C\u00FA\u00D1U\r\x03\x00\x00\x00\x00IEND\u00AEB`\u0082";
red_o = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\t\u00D7\x00\x00\t\u00D7\x01\u00B1n\x17\u00B7\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00FEIDAT8\u008D\u00A5\u00931N\x02A\x18\u0085\u00BF7\x18C,L\u00AC\x168\u00C6\u00C6@\u00A5\u00E1\x10\u00C6hg\u00EB\t\u00F0\f^\u00C2\x16\u00B0\u00E4\x02\u0094\u009B(\x16\u009E\u00C1D\u00E9\u00A4\u00D1\u00A8\u00EC\u00FC6\u00EE\u00C2\u00AC\u00BA,\u00D9\u0097L\u00F16\u00EF\u00FFf\u00E7MFfF\x1D\u00B9Z\u00D3\u00C0\u00CE\u00BA\x19w\u00E3c\u0093\u00AE\u008B\u00DF\u00FF\u00D0\u0087\x17\u0097\u00E7\u00C9\u00EC1\b\u009A\u00DC\u0089`*\u00D9m\u00D9\u00B47\x06\u0082# \x04\b\u00DA^~t\u0096<\u00CC\u00CA\x00\u00E3\u00DE\u00A1\f?\u0087B\x07\u0086E\u0098\u00E6\x1B~?\u00C8\x15Kl\u0089t#`=W\x04DK5_*\x00V93\u00C3\u00CC\u00B8\u00E9\u00F7\u009B\u00C3n\u00FC\u0096\u00F9\u00FF\u00D6O\u00EE=\u00F3y\u0089\u00BB\u009F\u008B\b\u00F45\u00EC\u00C5\u0083\u00B2\u00AD\u00F7\u00D0\u00BE\u00C1S\u00E6s@#U\x1BX8\u00DCA\x19\u00C0\u00E4Mi\u00E3\u00E2\x17\x00O\x07\u00C7\u00DDir\x7FU\u00A1\u0083\\\u00AB\x12EG\u009E\u00E7m\u0086C\x00\u00B4\u00C0\u00AA\u00DC@\u00A0\u00FC\b\u00E64qK\u00F7\u00BA-@u\u009F\u00F37\u0085Ir\x0E\u00B9\u008B\u00D4\u0082\x00\x00\x00\x00IEND\u00AEB`\u0082";
green = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\t\u00D7\x00\x00\t\u00D7\x01\u00B1n\x17\u00B7\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00EFIDAT8\u008D\u00A5\u00931J\x03Q\x14E\u00CF}3hJ\x1B\u00D1\u00A4\u00B1s\x01\u00F6\n\u00AEA\x10\u00826\u00EE\"{\u00B1\u00B1\u00B2\u0090t\u008Ak\x10\u00D4&\u009Bp\x04\x0BM\u0095\u0080\u00F97\u00C5\u00A0f\u008C&3\u00CC\u00AB\u00FE\u0087\x7F\u00CF\u00BD\u00EF=\u00BEl\u00D3\u00A6\u00A2\u0095\x1A\u00C8\x17/\x077'G\u00D8\u0097\u00B2\u00F2\u00FF\x04\x00\u00C8\x13R\u00F4\u009F\u00FB\u00C3Q\u00E5a\u00A0s\u00C3>Z\u00EDji*\u00A5C`Tm\u00C1\u00EC\u00D5\u0089\x1D\u00F6\u00D4\u00E8\u00B54\u00AD\u00E8S\u00B7\x0E\u00C0R\x12\u00B1\f\u0090\u00B4]\x07 \u00C8f\u009A\u00FD\u0091\u00C0l\u00C1\u00FA\u00B5\u00DAlv>'E\x05p|u\u00D1\x01r\u00D6M\u00B0D\u00C4\u00C3\u00D9\u00FD\u00B8\x02\u00F8\u00D8\x18\u00EFP\u00C7\x1E\x00\u00BD}\u009D\u00BE\u00D7\u00A8\u008C.PH\u00BA^%MN\u00CE\u00A4\u00BB%\x00\u00B8\x07\u00F1\u00F8t:\x1C\u00D4KQ\u00D6\u00C2\x10\u00A3\u0087xi\"\u00FE\x05H\u00BB\u00C6ES\u00C0\u00CF\f\x1C\u00B7\x11\u00F1\u00DE\x14\u00A0\u00B6\u00DFy\x0E\u00F4\x1CG\u00E7\u00C3S\u00DC\u00E7\x00\x00\x00\x00IEND\u00AEB`\u0082";
green_o = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\t\u00D7\x00\x00\t\u00D7\x01\u00B1n\x17\u00B7\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00FFIDAT8\u008D\u00A5\u0093\u00B1J\x03a\x10\u0084g\u00F6b\b\x16\u0082\u00D5%y\u008C`\u00AB\u00E0+\x18\x11\u00C5FK\u009F >\u0083Oa\u00A91`!X[\n\x1A\x0B\u009FA\u00D0t&\u0095\u00A2\u00B7c\u00A1\u00B9\u00DC\u00FD\u00E2\x1F\u00C3M\u00B70\u00F3\u00B1\u00CC\u00B2\u0094\u0084*\u00B2Ji\x00\u00B5\u00E2\u00D0\u00E9w7(\u009C\x00\u00AC\u00FD\x15\u00F8\u0096\u00DE\u00E1<\x1A\u00EE\r\x1EKF\x13\u00B6A\u00DE\u0088\u00D9E4\u00EFI\x0F\u00F4u\x00e\x00\u0088\u0096\u00BB\u00CE\x1Fv/\u0087\u00B1\u00FCZ\u00BFK\x17G@\u00D0\u0081\u00A4\u0094\u00B0Q|\u00FD\u00B2/(\u0091\u00CD\u008C\u00D9\\@\u00D1\x17\x00\u00946>\u00DF^\u00E6\x03f\u00BE\x1C\u00B0yz\u00D8\x00\u00B8t\u00BB\x7F=\u0089E\x7F|\u00F5\u00A9//q\\\u009F\u00A4\u0084>:g[\u00BD\u00E8\u00F2\u00CB\\\u0081\u00F04\u009Ds\x00\x13\u00B4\x00\u008E\u00CD\u00B8\x1A\x03\u00B8\\\ty\u00F0\x0B\x00\u00A8\r\u00D8\u00DD\u00FD\u00CE\u00E08\x06\bU(\u00D1\u00DA \u009E\x17\t\x07\x00o\n\u00FA\u00C7\x05\u00CA\u009Au \u00BB2\u00B3\u00D7E\x01\u00AC\u00FA\u00CE_\u00A28Y\x00\x13L\u00B1)\x00\x00\x00\x00IEND\u00AEB`\u0082";
gray = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\t\u00D7\x00\x00\t\u00D7\x01\u00B1n\x17\u00B7\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00EEIDAT8\u008D\u00A5\u00931N\u00C3@\x10E\u00DF\x1F[@\x15\u00D1 H\x1A:n\x01\u0097\b\r2\u00CA9\u00B8\x0B\u008Di((\u00D2\u00C0-\u00E8r\t\x1C\u0089\x02\u00D1@\u0080\u00EC\u00A7!\x01;\"\u00B1\u0095\u00A9f\u00A5\u00FDo\u00FF\x1F\u00ED\u00C86\u00DBTl\u00A5\x06\u00F2\u00BF\u0087\u00F1\u00DD\u00CD\x19\u00E8\x1A)\u00FFO\x00 \u00D2{\n]\f\u0087\u00A3I\u00ED\u00A2B\u00976'\u00B0>\u0096a\x16s\u009D\x02\u0093\u00E6K\u00C7ml\u00DB\u009A!O\u00A19\x03\u00BB\u00DF\x06\x10rJ\u008AU\u0080\u00D1A+\x07(\u00CB\u00E6i\n\u008D!\x1A\u00F6\x01\u00B4\x11\u00C0\u00EE\u00DBWV\u00D5\x1C\u0094e\u00B9'\u00C87\u0089\x7FDQ\x14\u00C5k\r\u00D0\u00EB\u00ED\x1C\u009Av\u00BF\u00CA\u00F0\u00BC\u00E8\x7F#|~\u00F4\x15Q!n\u00D7\u0089el\u00FBa\x05\u00A0,\x1B$\u00FBqx>\u00BAj\u00E3bQ\u00CB\b\u00C9\x1E ?u\x11\u00D7\x00\u0081\u008F\x04UW\u00C02B\u008A\u00B8OI/]\x01\u00DAv\u009D\u00BF\x01\u00E7\u008FN\x13\u00B9\u00E9m\u0087\x00\x00\x00\x00IEND\u00AEB`\u0082";
gray_o = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\t\u00D7\x00\x00\t\u00D7\x01\u00B1n\x17\u00B7\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00FFIDAT8\u008D\u00A5\u00931N\u00C3@\x14Dg\u00BEC\u0094\x02Y\u00A2r\u0092\u00BB\u00903 9T\u0096\x02-'\bg\u00E0\x14&\x1D\u0089\x14\n.@\u008Fh8\x03R\u0092\x0Eh\x10$\u00DE\u00A1!N\u00BC\u00A0\u00B5\"O\u00F7wg\u009E\u00FE\u00FF\u00AB\u00A5$4\u00915J\x03h\u00ED\x17\u00F3\u00E9\u00ED\x00\u00E4\u008D\x7F\u00FE\u008F\u00BEd\u00B8J\u00D3\u00D1\u008Bg\u00E4\x10\u00C4\u00A3I\u00B3P\u00DA\u0081c+x\n\u00C0\x03\x10=\bwg\u00E7\x17\u00CF!\u00C0|6\u00A1\u00A8\x15\u00E0\u00ED\u0080P\u00E2h\u00AB\u009A\u00F6+\u00BE\n@`7*\\-`\u00DF\u00D7\u00AA^ \u00F9\u00DCD\u00CBz\u00C0\u00CEWv\u0090\u00E7y\u0087\u00C0Q\u0096e\x1F\u00A1\u00F0\u00AF\u00AF\u00BD\u00F5\u0095\x1D\u00C4q;Q\u00B1Y\u00DFO'\u00E3\x10 >\u00B6\x18\u00C2\u00EB\u00B6\u00DE\u008D\u00B0\u00FE\u00EE\u00D1\u00EC]\u0086\u0093\x10\u0080\u0082$]\u00FE\x010\u008A\u00FANzJ\u0087\u00A3\u00EB\x10\u00C0W\u00B9\x03'\u00F5A-\x0E\tW\x00\x06u\t\u00D4\u00BE\u0080\u00AFr\x04g\u00F6\u00E0\x1C\u00DF\x0E\x05\u00B0\u00E9w\u00FE\x01\u00C6z]\u00D4\u0097\x17j\u00EE\x00\x00\x00\x00IEND\u00AEB`\u0082";






var states = [gray_o,green_o,green, red, gray, blue];


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();
    }
  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{
          
                firstFile.open("r","TEXT","????");
                contents = firstFile.read();
                firstFile.close();
                lines = contents.split("\n");

               info[3] = logFolderLocation.toString();
               info[0] = logFolderLocation.name.toString();
                for(i=0;i<lines.length;i++){
                    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: ";
                   
                    
                    if(info[1]==0 ||info[1]==2){

                            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("\\"));

                                f =  wfold.substring(wfold.lastIndexOf("\\")+1,wfold.length);

                                 info[3] =localToRessource(wfold);
                                 $.writeln(info[3])
                                info[0] = f;

                            }
                        }
                
                }

        }
   
    }
  $.writeln(info);
    return info;
            
}
function hasStarted(watchedFolderLocation){
    fold = new Folder(watchedFolderLocation.toString());
    files = fold.getFiles(getLogonly);
    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()];
            }
            vArray[counter2] =out;
        }

        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];
                    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], 20);
            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){

                    sL=shotinfos.length;

                    for(i=1;i<sL;i++){
                       a=shotinfos[i];

                        if(a[0] == list.selection.toString()){
                            explore(a[3]);
                            break;
                            }
                        }

                }


            }        
            
            
            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;
                    }
                }
                

            }
            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(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 addItem(name,state,msg){

    //var arrayV = new Array(green,green_o,gray,gray_o,red,red_o);
    var item = list.add ('item',name);
    item.image =  File.decode(states[state]);
    //palette.add("image", undefined, File.decode(gray), {name: "image1"}, [10,10]);
    //item.image =  File(scriptFolder.toString()+"/flag_"+arrayV[state]+".png");
    item.subItems[0].text =msg;

}

SimpleWatchfolder WIP

some improvements

/*
WatchFolder.js v1.2
--------------------
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.

Windows only.


Was added:
- A check for missing footage
- Some UIs to turn AFX into a render worker, launch a worker or launch a simple .bat file instead

TBD
- Some way to parse logs to figure what has rendered and what hasn't, cause Adobe's .html file is crap TBH. This is the 'watchfolder watcher' script that I tried to do long time ago.
- set some WF options (flags)

*/
{
    watchfolderLocation = "none";
    watchfolderLocation = (app.settings.haveSetting("watchfolderPrefs", "watchfolderLocation")) ? (app.settings.getSetting("watchfolderPrefs", "watchfolderLocation")) : watchfolderLocation;



    function sendToWF(wf) {
	var m=0;
	var errorList=""
        for(i=1;i<=app.project.numItems;i++){
            if(app.project.items[i] instanceof FootageItem && app.project.items[i].file != null){
                            if(app.project.items[i].footageMissing){
                                m++;
				errorList += app.project.items[i].name + "\n"; // + " > " + app.project.items[i].mainSource.file.path.toString + "\n";
                            }
            }
        }
	if(m!=0){
		alert("There are "+m+" missing footage(s) in the project, the watchfolder will fail.\n\nYou can however batch locally using the aerender button\n\n"+errorList);
	}else{
		// the important part of this script. Creates the RCF (render control file), the undocumented file that will launch the watchfolder process.
		// the way it is created is kind of black-boxy, but this setup works for me.
        	var saved = app.project.file;
        	var curFile = app.project.file.name;
        	curFile = curFile.substring(0, curFile.length - 4);
        	var myFolder = new Folder(wf + "/" + curFile + "_wf/");
        	myFolder.create();
        	writeLn("Copying AEP to watchfolder.");
        	var mySaveFile = new File(myFolder.toString() + "/" + curFile + ".aep");
        	saved.copy(mySaveFile);
        	var myTextFile = new File(myFolder.toString() + "/" + curFile + "_RCF.txt");
        	myTextFile.open("w", "TEXT", "????");
        	var text = "After Effects 13.2v1 Render Control File\nmax_machines=10\nnum_machines=0\ninit=0\nhtml_init=0\nhtml_name=\"\"\n";
        	myTextFile.write(text);
	        myTextFile.close();
        	writeLn("Sent to watchfolder...");
	}
    }

    function setWatchFolder() {
        var tmpfile = new File(String(Folder.desktop) + "/save this temp file with any name in the watchfolder");
        var selectedFolder = tmpfile.saveDlg('Select Watchfolder Location');
        if (selectedFolder) {

            app.settings.saveSetting("watchfolderPrefs", "watchfolderLocation", selectedFolder.path);
            watchfolderLocation = selectedFolder.path;
            return true;
        } else {
            return false;
        }

    }

    function startWatchingFolder() {
        aerenderExe = '"' + Folder(Folder.decode(Folder.appPackage.absoluteURI)).fsName + '\\AfterFX.exe" -m -re -wf "' + Folder(watchfolderLocation).fsName + '"';
        batch = new File(Folder.desktop.toString() + "/launch_WatchFolder.bat");
        if (batch.open("w", "TEXT", "????") == true) {
            batch.write("@echo off\n");
	    // todo add start "" "C:\Program Files\Adobe\Adobe After Effects 2024\Support Files\AfterFX.exe" -noui -m -re -wf .......
            batch.write(aerenderExe);
            batch.close();
            batch.execute();
        } else {
            alert("unable to launch the AfterEffects worker");
        }

    }
    function startAERender() {
	//to add: multiframe rendering
        aerenderExe = '"' + Folder(Folder.decode(Folder.appPackage.absoluteURI)).fsName + '\\aerender.exe" -continueOnMissingFootage -project "' + File(app.project.file).fsName + '"';
        batch = new File(Folder.temp.toString() + "/launch_aerender.bat");
        if (batch.open("w", "TEXT", "????") == true) {
            batch.write("@echo off\n");
            batch.write(aerenderExe);
	    batch.write("\nPAUSE");
            batch.close();
            batch.execute();
        } else {
            alert("unable to launch the aerender.exe");
        }

    }

    function updateUI(dialog) {

    }

    function watchFolderUI(thisObj) {
        var securitySetting = app.preferences.getPrefAsLong("Main Pref Section", "Pref_SCRIPTING_FILE_NETWORK_SECURITY");
        if (securitySetting != 1) {
            alert("You need to check 'Allow Scripts to Write Files and Access Network' in your preferences for this script to work");
        } else {



            var panelGlobal = thisObj;


            /*
            Code for Import https://scriptui.joonas.me
            */

            // DIALOG
            // ======
            var dialog = (panelGlobal instanceof Panel) ? panelGlobal : new Window("palette", "Simple Watchfolder", [100, 100, 300, 300]);
            if (!(panelGlobal instanceof Panel)) dialog.text = "Simple Watchfolder";
            dialog.orientation = "column";

            dialog.alignChildren = ["fill", "top"];
            dialog.spacing = 10;
            dialog.margins = 16;

            var grp = dialog.add("group", undefined, {
                name: "group0"
            });
            grp.alignement = ["fill", "fill"];
            grp.alignChildren = ["fill", "top"];
            grp.orientation = "column";


            grp.add("statictext", undefined, "Simple UI to send the current queue to a watchfolder. Please save before sending to WF as it will copy the current .aep to the watchfolder", {
                multiline: true
            });



            var group1 = grp.add("group", undefined, {name: "group1"});
            group1.orientation = "row";
            group1.alignChildren = ["fill", "top"];
            group1.spacing = 10;
            group1.margins = 0;

            var setWF = group1.add("button", undefined, undefined, {name: "setWF"});
            setWF.text = "Set WatchFolder";

            var openWF = group1.add("button", undefined, undefined, {name: "openWF"});
	    openWF.enabled = (watchfolderLocation=="none")?false:true; 
            openWF.text = "Open WF";


            var curWF = grp.add("statictext", undefined, undefined, {name: "statictext2"});
            curWF.helpTip = "shows current watchfolder";
            curWF.text = "(choose watchfolder)";


            var sendWF = grp.add("button", undefined, undefined, {name: "sendWF"});
            sendWF.helpTip = "save file first!";
            sendWF.text = "Send .aep to Watchfolder";

            var sendAER = grp.add("button", undefined, undefined, {name: "sendAER"});
            sendAER.helpTip = "local cmdline render";
            sendAER.text = "render local aerender.exe batch";

            var group2 = grp.add("group", undefined, {name: "group2"});
            group2.orientation = "row";
            group2.alignChildren = ["fill", "top"];
            group2.spacing = 10;
            group2.margins = 0;

            var launchWorker = group2.add("button", undefined, undefined, {name: "launchWorker"});
            launchWorker.helpTip = "Saves a .bat file to your desktop that launches a worker";
            launchWorker.text = "Launch a worker";
	    launchWorker.enabled = (watchfolderLocation=="none")?false:true;


            var switchToWorker = group2.add("button", undefined, undefined, {name: "switchToWorker"});
            switchToWorker.helpTip = "Turns the current After Effects into a watchfolder worker";
            switchToWorker.text = "Set as worker";
            switchToWorker.enabled = (watchfolderLocation=="none")?false:true;



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


	     if (watchfolderLocation != "none") {
                curWF.text = "Folder: " + Folder(watchfolderLocation).fsName;
		}


            setWF.onClick = function() {
                swf = setWatchFolder();
                if (swf) {
                    curWF.text = "Folder: " + Folder(watchfolderLocation).fsName;
		    openWF.enabled = launchWorker.enabled = launchWorker.enabled = true;
                }
            }
            launchWorker.onClick = function() {
                startWatchingFolder();
            }
	    switchToWorker.onClick = function() {
		app.watchFolder(watchfolderLocation);
	    }
            sendAER.onClick = function() {
                startAERender();
            }

            openWF.onClick = function() {
        	var myFolder = new Folder(watchfolderLocation);
		if (ScriptUI.environment.keyboardState.shiftKey === true) {
		        alert("lol");
    		}
        	myFolder.execute();

                //watchfolderLocation
            }
            sendWF.onClick = function() {
                if (watchfolderLocation == "none") {
                    setWatchFolder();
                } else {
                    sendToWF(watchfolderLocation);
                }
            }

        }
    }
    watchFolderUI(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])

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

6iJkeH9.png

Very wip. Features and UI don't fully work.

I hate having to go and fill out the outputs of renderqueue items by hand. This would/will automate the creation of render outputs according to variables like the comp's name and folder, the name of the After Effects project, the current date etc...

It still needs work, which will happend if someone ever hires me for AE work again :)
//todo: be able to select existing RQ items instead of simply comps, and edit their existing paths

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

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 = "Here are variables you can use:\n";
		explanationText += "\t{compname} {compfolder} {projpath} {compid}\n";
		explanationText += "\t{projname} {date} (using date:y/m/d)";
		explanationText += "\t[#], [##], [####] (padding)\n";
		explanationText += "You can write '/../' to go 'up' one directory ";
		// 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, "\\");
		path = path.charAt(0).toUpperCase() + path.slice(1);
	} 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")));
}

?>