Last active
June 21, 2024 09:25
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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 ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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 | |
] | |
] | |
} | |
} |
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
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.