Skip to content

Instantly share code, notes, and snippets.

@nicoptere
Last active December 19, 2023 13:39
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicoptere/6198923eb1a8803ae7cea45cd4145219 to your computer and use it in GitHub Desktop.
Save nicoptere/6198923eb1a8803ae7cea45cd4145219 to your computer and use it in GitHub Desktop.
converts after effects layers' keyframes to JSON and saves file on hard drive.
//JSON object
"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(t){return 10>t?"0"+t:t}function this_value(){return this.valueOf()}function quote(t){return rx_escapable.lastIndex=0,rx_escapable.test(t)?'"'+t.replace(rx_escapable,function(t){var e=meta[t];return"string"==typeof e?e:"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+t+'"'}function str(t,e){var r,n,o,u,f,a=gap,i=e[t];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(t)),"function"==typeof rep&&(i=rep.call(e,t,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?i+"":"null";case"boolean":case"null":return i+"";case"object":if(!i)return"null";if(gap+=indent,f=[],"[object Array]"===Object.prototype.toString.apply(i)){for(u=i.length,r=0;u>r;r+=1)f[r]=str(r,i)||"null";return o=0===f.length?"[]":gap?"[\n"+gap+f.join(",\n"+gap)+"\n"+a+"]":"["+f.join(",")+"]",gap=a,o}if(rep&&"object"==typeof rep)for(u=rep.length,r=0;u>r;r+=1)"string"==typeof rep[r]&&(n=rep[r],o=str(n,i),o&&f.push(quote(n)+(gap?": ":":")+o));else for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(o=str(n,i),o&&f.push(quote(n)+(gap?": ":":")+o));return o=0===f.length?"{}":gap?"{\n"+gap+f.join(",\n"+gap)+"\n"+a+"}":"{"+f.join(",")+"}",gap=a,o}}var rx_one=/^[\],:{}\s]*$/,rx_two=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,rx_three=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,rx_four=/(?:^|:|,)(?:\s*\[)+/g,rx_escapable=/[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,rx_dangerous=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},Boolean.prototype.toJSON=this_value,Number.prototype.toJSON=this_value,String.prototype.toJSON=this_value);var gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(t,e,r){var n;if(gap="",indent="","number"==typeof r)for(n=0;r>n;n+=1)indent+=" ";else"string"==typeof r&&(indent=r);if(rep=e,e&&"function"!=typeof e&&("object"!=typeof e||"number"!=typeof e.length))throw Error("JSON.stringify");return str("",{"":t})}),"function"!=typeof JSON.parse&&(JSON.parse=function(text,reviver){function walk(t,e){var r,n,o=t[e];if(o&&"object"==typeof o)for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(n=walk(o,r),void 0!==n?o[r]=n:delete o[r]);return reviver.call(t,e,o)}var j;if(text+="",rx_dangerous.lastIndex=0,rx_dangerous.test(text)&&(text=text.replace(rx_dangerous,function(t){return"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})),rx_one.test(text.replace(rx_two,"@").replace(rx_three,"]").replace(rx_four,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
//log wrapper
function log( arg ){$.writeln( arg );}
//gets a URL based on the file path and the name
function getUrl( name ){
var projectName = app.project.file.name.replace(".aep", '');
var compName = app.project.activeItem.name;
var fileName = name || projectName + "_"+ compName + ".json";
fileName = fileName.replace(/\s/g, '');
var path = app.project.file.parent.absoluteURI + "/";
return path + fileName;
}
// you need to allow AE to write files to your hard drive:
// go to: Edit > Preferences > General > and check on "Allow Scripts to Write Files and Access Network"
//
//write the output to disk:
function saveFile( obj, fileName ){
var output = new File( getUrl( fileName + ".json" ) );
if( output.open( "w" ) ){
output.encoding = "UTF-8";
var content =JSON.stringify( out, undefined, 2 );
output.write( content );
output.close();
}
}
//recursive method to get all values from all layers
function scanLayers( obj, id, props ){
if( id > comp.layers.length )return;
//for each layer
var layer = comp.layer(id);
if( layer == null ) return;
//stores all layer's values in an object
var tmp = {};
tmp.times = [];
//for each property passed as an argument
for( var p =0; p< props.length; p++ ){
var property = layer[ props[ p ] ];
//gets the keyframe count
var numKeys = property.numKeys;
//default value if property is not animated
if( numKeys <= 0 ){
tmp[ props[ p ] ] = null;
continue;
}
//if the property is animated
var values = [];
if (numKeys > 0){
var time, value;
for( var i = 1; i <= numKeys; i++ ){
//gets the time and property values at this keyFrame
time = property.keyTime( i );
value = property.keyValue( i );
//stores them into an array which first value is the time stamp
var keyframe = [time];
if( !isNaN( value ) ){
// stores rotations in RADIANS
if( props[ p ] == "rotation" )
{
keyframe.push( value * Math.PI / 180 );
}
else
{
keyframe.push( value );
}
}
else
{
for( var j = 0; j < value.length; j++ ){
keyframe.push( value[ j ] );
}
}
//store this keyframe in the layer object
values.push( keyframe );
//if this timestamp was not yet present in the layer's list of timestamps
var addTime = true;
for( var j = 0; j < tmp.times.length; j++ ){
if( tmp.times[ j ] == time ) addTime = false;
}
//add it
if( addTime ) tmp.times.push( time );
}
}
//stores the property's values for this layer and got ot next property
tmp[ props[ p ] ] = values;
}
// add this layer to the final object
obj[ layer.name.replace(/\s/g, '_' ).toLowerCase() ] = tmp;
return scanLayers (obj, ++id, props);
}
////////////////////////////////////////////////
// SETUP WINDOW
////////////////////////////////////////////////
var win = new Window('dialog', 'Alert Box Builder');
win.options = win.add('panel', undefined, 'Build it');
var prs = [
"position",
"rotation",
"scale"
];
for( var i = 0; i < prs.length; i++ )
{
win[ prs[ i ] ] = win.options.add('checkbox', undefined, prs[ i ] );
win[ prs[ i ] ].value = true;
}
win.options.fileNameLabel = win.options.add('statictext', undefined, 'json file name:');
win.options.fileName = win.options.add('edittext', undefined, 'fileName' );
win.options.buildBtn = win.options.add('button', undefined, 'Build', {name:'ok'});
win.show();
//specify properties to export
var properties = [];
for( var i = 0; i < prs.length; i++ ){
log( prs[i] + " -> " + win[ prs[ i ] ].value );
if( win[ prs[ i ] ].value ) properties.push( prs[ i ] );
}
//specify file name or fall back to file name + .json
var fileName = win.options.fileName.text;
if( fileName == "fileName" ) fileName = app.project.file.name.replace(".aep", '');
////////////////////////////////////////////////
// GO !
////////////////////////////////////////////////
var proj = app.project;
var comp = proj.activeItem;
var out = {};
saveFile( scanLayers( out, 1, properties ), fileName );
{
"bla": {
"times": [
0,
38.0046713380047,
50.6172839506173,
37.7711044377711
],
"position": [
[
0,
430,
450,
0
],
[
38.0046713380047,
474,
96,
0
],
[
50.6172839506173,
394,
156,
0
]
],
"rotation": null,
"scale": [
[
0,
100,
100,
100
],
[
37.7711044377711,
100,
-14122.2222222222,
100
],
[
38.0046713380047,
100,
99.9999999999982,
100
]
]
},
"red_solid_1": {
"times": [
38.0046713380047,
43.1431431431431,
46.6466466466466
],
"position": null,
"rotation": null,
"scale": [
[
38.0046713380047,
100,
100,
100
],
[
43.1431431431431,
244,
244,
100
],
[
46.6466466466466,
100,
100,
100
]
]
},
"rect": {
"times": [
40.5739072405739,
54.5879212545879,
38.0046713380047,
50.6172839506173,
59.693026359693
],
"position": null,
"rotation": [
[
40.5739072405739,
1.81828270071446
],
[
54.5879212545879,
0
]
],
"scale": [
[
38.0046713380047,
8.203125,
7.8125,
100
],
[
40.5739072405739,
26.0121527777778,
27.3032407407407,
100
],
[
50.6172839506173,
76.453125,
72.8125,
100
],
[
59.693026359693,
8.203125,
7.8125,
100
]
]
}
}
@nicoptere
Copy link
Author

the times are set in an array per layer to store all the times when a property changes.
each property's array stores arrays of all changes, they starts with the timestamp followed by the property's values at this timestamp.

@aguynamededward
Copy link

aguynamededward commented Mar 30, 2020

How does it handle it if the keyframes for different properties are at different times? Eg, scale settings every second, but position settings every two seconds? I'm guessing it has a time entry for every key frame, and if the property doesn't have a specific value at that time, it interpolates it?

EDIT: My apologies, I misread the code. Looks like it embeds the time stamp at the start of each one. :) Great stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment