A fork of Mmaker's screenflip.jsx. With his help, I've made it a bit more confortable to work with, if you already know how to use his version this new one shouldn't require any tutorial
Last active
November 6, 2020 20:27
-
-
Save CHFR-wide/ab4b4e206f6f399ba23e9227adfffaca to your computer and use it in GitHub Desktop.
ScreenFlip - Audio-based YTPMV/otoMAD style screenflipping for After Effects
This file contains hidden or 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
/* | |
--ScreenFlip release v7-- | |
YTPMV/otoMAD screen flip script by MMaker @stysmmaker, reworked on by CH FR as practice. | |
Use however you like. Go crazy and experiment to come up with something unique by using this. | |
Place script file in <After Effects Install Location>/Support Files/Scripts/ScriptUI Panels/ | |
Access via the Window menu in After Effects | |
beat.wav can be found at https://mmaker.pw/junk/beat.wav | |
Changelog can be found at https://gist.github.com/stysmmaker | |
--USAGE-- | |
Create a 'beat' audio track using beat.wav by replacing samples or just pasting it where you want it to go. | |
Export the audio track as it's own separate file (make sure the timing matches your original audio!) | |
Select video and audio beat track to use in the current comp. | |
Run script. | |
Done! | |
--CHANGELOG-- | |
v7 (CHFR here, I've added and fixed a few things in the script with the help of mmaker to fit it more to my likings) | |
-Added auto renaming, script can now be executed more than once per comp | |
-Selected layers can now be changed without closing the script | |
-Flips video layer is now automatically muted | |
-Re-enabled AudioAmp null shying, disabled automatic pressing of hideShyLayers | |
-Added checkbox to clean up the beat.wav layers after execution | |
-Work Area preservation | |
-Disabled Reverse w/ stretching for being too unreliable (re-enable it line 60 if you want to) | |
v6 | |
-Refactored code, now requires no keyframes! | |
-Now powered by expressions^tm. | |
v5 | |
-Added 'Reverse w/ stretching' option | |
v4 | |
-Modified calculation of time remap keyframes (properly sets the time now, for both short and long video!) | |
-Refactored time remap code | |
-Updated undo group name to be more informative (now shows video and audio layers used) | |
v3 | |
-Implemented GUI | |
-Added option to select flip type (default, no flip) | |
v2 | |
-Fixed short video clips not working (now pauses on last frame when distance between beat is longer than video clip) | |
-Published to GitHub | |
v1 | |
-Initial release, supports images/videos and automatically flips based on audio | |
--TODO-- | |
More features (delay, stretching, better controls) | |
Interface for quicker access and more options | |
Better error handling, better beat sample, and other general optimizations | |
*/ | |
(function () { | |
function createUI(thisObj) { | |
var w = 196; | |
var h = 150; | |
var pad = 16 | |
var win = (thisObj instanceof Panel) ? thisObj : new Window("palette", "ScreenFlip", undefined); | |
var radio_type_group = win.add("panel"); | |
radio_type_group.alignChildren = "left"; | |
radio_type_group.add("radiobutton",undefined,"Default"); | |
radio_type_group.add("radiobutton",undefined,"No flip"); | |
//radio_type_group.add("radiobutton",undefined,"Reverse w/ stretching"); | |
var checkbox_type_group = win.add("panel"); | |
var check1 = checkbox_type_group.add("checkbox",undefined,"Clean audio layers"); | |
check1.value = true; | |
radio_type_group.children[0].value = true; | |
var run = win.add("button", undefined, "Go!"); | |
function selected_rbutton(rbuttons) | |
{ | |
for (var i = 0; i < rbuttons.children.length; i++) | |
if (rbuttons.children[i].value == true) | |
return i+1; | |
} | |
run.onClick = function () {main(selected_rbutton(radio_type_group), check1);} | |
return win; | |
} | |
var ytpmvpanel = createUI(this); | |
ytpmvpanel.center(); | |
ytpmvpanel.show(); | |
// Helper variables | |
var proj = app.project; // active project | |
var comp = proj.activeItem; // active comp | |
//var layer = comp.selectedLayers; //no, has to be declared when we click on the button | |
var numLayers = comp.numLayers; // count of comp layers | |
var flip = 100; // keep track of flip value. multiplied by -1 to flip stuffs. | |
var minVol = 3; // minimum keyframe/volume value to count as a beat. | |
var cooldownFrames = 2; // max keyframe distance between flips | |
var keyInterpolate = { // easier access to keyframe interpolation types | |
'linear':KeyframeInterpolationType.LINEAR, | |
'hold':KeyframeInterpolationType.HOLD}; | |
//-------------------------------------------------------------- | |
function generateNullName(comp){ | |
nullName = "audioAmp "; | |
nullIndex = 0 | |
for (i=1; i <= numLayers; i++){ | |
if (comp.layer(i).name.split("audioAmp ")[0] == ''){ | |
if (parseInt (comp.layer(i).name.split("audioAmp ")[1]) > nullIndex){ | |
nullIndex = parseInt(comp.layer(i).name.split("audioAmp ")[1]); | |
} | |
} | |
} | |
nullIndex += 1 | |
return nullName+=nullIndex.toString(); | |
} | |
function main(fliptype, checkboxVal) { // Core of the function, stuff happens here | |
var layerAudioReset = []; // for storing indexes of currently audio-enabled layers not used | |
function setDesignatedLayers() // Automatically sets video and audio layer based on user selection (also handles different selection order) | |
{ | |
var layer = comp.selectedLayers; // selected layers | |
if (layer.length == 2 && ( (layer[0].hasVideo && (!layer[1].hasVideo && layer[1].hasAudio)) || (layer[1].hasVideo && (!layer[0].hasVideo && layer[0].hasAudio)) ) ) | |
{ | |
audioLayer = (!layer[1].hasVideo && layer[1].hasAudio) ? layer[1] : layer[0]; | |
videoLayer = (!layer[1].hasVideo && layer[1].hasAudio) ? layer[0] : layer[1]; | |
} | |
else | |
{ | |
alert("Invalid selection."); | |
return; | |
} | |
} | |
setDesignatedLayers(); | |
app.beginUndoGroup("ScreenFlip - V: {" + videoLayer.name + '} A: {' + audioLayer.name + '}'); | |
var formerWAS = comp.workAreaStart; | |
var formerWAD = comp.workAreaDuration; | |
comp.workAreaStart=0; | |
comp.workAreaDuration=comp.duration; | |
function setLayerAudioStateOff() // Keeps track of audio layers NOT used in the script, and mutes then during audiotokeyframe runtime. These layers are later unmuted. | |
{ | |
for (var i = 1; i-1 < numLayers; i++) // AE stores indexes of layers starting at 1 | |
{ | |
if (comp.layer(i).hasAudio == true) | |
{ | |
if (comp.layer(i) != audioLayer) | |
{ | |
if (comp.layer(i).audioEnabled == true) | |
{ | |
layerAudioReset.push(i); // store index so audio can be re-enabled after running audioToKey | |
} | |
comp.layer(i).audioEnabled = false; | |
} | |
else | |
{ | |
comp.layer(i).audioEnabled = true; | |
} | |
} | |
} | |
} | |
function setLayerAudioStateOn() // Unmutes layers previously muted by setLayerAudioStateOff() | |
{ | |
for (var i = 0; i < layerAudioReset.length; i++) | |
{ | |
comp.layer(layerAudioReset[i]+1).audioEnabled = !comp.layer(layerAudioReset[i]+1).audioEnabled; // re-enable the rest of previously enabled audio tracks | |
} | |
} | |
setDesignatedLayers(); | |
setLayerAudioStateOff(); | |
app.executeCommand(app.findMenuCommandId("Convert Audio to Keyframes")); | |
audioLayer.audioEnabled = false; // Disable beat audio automatically after running audiotokeyframes | |
setLayerAudioStateOn(); | |
audioAmp = comp.layer(1) // audio to keyframes automatically becomes highest layer in comp | |
audioAmp.name = generateNullName(comp); | |
audioAmp.shy = true; | |
audioAmpVal = audioAmp.property("Effects").property(3).property(1); // property 3 is "Both Channels", property 1 is "Slider" (the actual audio amp) | |
flipProp = audioAmp.property("Marker"); | |
var timeOffset = videoLayer.Effects.addProperty("ADBE Slider Control"); | |
timeOffset.name ="Time offset (seconds)"; | |
timeOffset.property(1).expression ='value/10'; | |
if (audioAmpVal.numKeys > 0) //markers creation | |
{ | |
var k = 0; // for cooldown | |
videoLayer.Effects.addProperty("Transform"); | |
videoLayer.property("Effects").property("Transform").property(3).setValue(false); // property 3 is "Uniform Scale", gotta do this because using the property name messes stuff up. | |
for (var i = 1; i < audioAmpVal.numKeys; i++) | |
{ | |
if (k > 0) | |
{ | |
k=k-1 | |
} | |
if (audioAmpVal.keyValue(i) > minVol && k == 0) | |
{ | |
k = cooldownFrames; | |
audioAmp.property("Marker").setValueAtTime(audioAmpVal.keyTime(i), new MarkerValue("")); | |
} | |
} | |
} | |
expressionList = { //list for storing expressions | |
1: { | |
"timeRemap": "a = thisComp.layer('"+audioAmp.name+"').marker; \ | |
n = 0; \ | |
if (a.numKeys > 0){ \ | |
n = a.nearestKey(time).index; \ | |
if (a.key(n).time > time) n--; \ | |
} \ | |
if (n > 0) \ | |
d = time - a.key(n).time + effect(\"Time offset (seconds)\")(\"Slider\"); \ | |
else \ | |
d = 0;", | |
"flip": "a = thisComp.layer('"+audioAmp.name+"').marker; \ | |
n = 0; \ | |
if (a.numKeys > 0){ \ | |
n = a.nearestKey(time).index; \ | |
if (a.key(n).time > time) n--; \ | |
} \ | |
\ | |
if (n % 2 != 0) \ | |
{ \ | |
100 \ | |
} \ | |
else \ | |
{ \ | |
-100 \ | |
}" | |
}, | |
2: { | |
"timeRemap": "a = thisComp.layer('"+audioAmp.name+"').marker; \ | |
n = 0; \ | |
if (a.numKeys > 0){ \ | |
n = a.nearestKey(time).index; \ | |
if (a.key(n).time > time) n--; \ | |
} \ | |
if (n > 0) \ | |
d = time - a.key(n).time + effect(\"Time offset (seconds)\")(\"Slider\")\ | |
else \ | |
d = 0;", | |
"flip": "" | |
}, | |
3: { | |
"timeRemap":"a = thisComp.layer('"+audioAmp.name+"').marker; \ | |
n = 0; \ | |
if (a.numKeys > 0){ \ | |
n = a.nearestKey(time).index; \ | |
if (a.key(n).time > time) n--; \ | |
\ | |
} \ | |
\ | |
try \ | |
{ \ | |
d = Math.abs(a.key(n).time - a.key(n+1).time) \ | |
} \ | |
catch(e) \ | |
{ \ | |
d = thisLayer.source.duration \ | |
} \ | |
\ | |
if (n % 2 == 0) \ | |
{ \ | |
b = thisLayer.source.duration; \ | |
c = 0; \ | |
} \ | |
else \ | |
{ \ | |
b = 0; \ | |
c = thisLayer.source.duration; \ | |
} \ | |
\ | |
linear(time-a.key(n).time, 0, d, b, c)", | |
"flip": "" | |
} | |
} | |
if (videoLayer.canSetTimeRemapEnabled == true) // The video layer can either be an image or video, so this handles for that case. | |
{ | |
videoLayer.timeRemapEnabled = true; | |
videoLayer.inPoint = flipProp.keyTime(1); // set video to appear at first beat | |
videoLayer.outPoint = comp.duration; // set video to end at end of the comp | |
var videoLayerTimeRemap = videoLayer.property("Time Remap"); | |
videoLayerTimeRemap.expression = expressionList[fliptype]["timeRemap"] | |
videoLayer.property("Effects").property("Transform").property(5).expression = expressionList[fliptype]["flip"] | |
} | |
//comp.hideShyLayers = true; // hide the audio to keyframes layer | |
//alert(checkboxVal.value); | |
if (checkboxVal.value == true){ | |
audioLayer.remove(); | |
} | |
comp.workAreaStart=formerWAS; | |
comp.workAreaDuration=formerWAD; | |
videoLayer.audioEnabled = false; | |
app.endUndoGroup(); | |
} | |
}) (); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment