Copy link to clipboard
Copied
Hi everyone!
I'm trying to animate captions using sourceText keyframes for each sentence, which I want to animate on a per-word basis using layer markers as the triggers, so that the text is synced up with the audio.
What I've found so far is using the expression below (thanks ukramedia) on a text animators expression selector, which works beautifully until the next sourceText keyframe, then it resets.
anim = thisProperty;
delayDur = framesToTime(effect("Delay Duration")("Slider").value);
numMarkers = marker.numKeys;
delay = (textIndex - 1)*delayDur;
if (numMarkers > 0)
{delay = textIndex <= numMarkers ? marker.key(textIndex).time : marker.key(numMarkers).time;}
anim.valueAtTime(time - delay);
Now, my idea is to reset the text animation every time there's a sourceText keyframe, but I just cannot wrap my head around on how to integrate this into an if/else expression.
Do you have any ideas how to approach this? Your help would be highly encouraging ☺
Best
Julian
Hey again, I just got help and this is the final code. It works flawlessly.
anim = thisProperty;
delayDur = framesToTime(effect("Delay Duration")("Slider").value);
numMarkers = marker.numKeys;
txt = text.sourceText;
delay = (textIndex - 1) * delayDur;
for (i = 0;
(i < txt.numKeys && txt.key(i + 1).time <= time); i++) {
// dummy loop, i counts # of sourceText Keys that we have passed
};
for (j = 0;
(i > 0 && j < marker.numKeys && marker.key(j + 1).time < txt.key(i).time); j++) {
...
Copy link to clipboard
Copied
You need to run it in a loop to actually check each marker. The expression you are using simply assumes a fixed delay and will simply snap to the next keyframe or the last one it encounters. There's no actual logic here that compares the keyframes and markers. You need to check which key and marker are nearest to your current time and then use that as the trigger. Refer to Dan Ebberts' classic code to get an idea:
http://www.motionscript.com/design-guide/marker-sync.html
Mylenium
Copy link to clipboard
Copied
Thanks for your kind input, Mylenium.
I should've said that I'm a total beginner to expressions and that I've hit a dead end after trying to figure this out for the last couple of days. I've actually already tried some code by Dan, but only got it to work on range selectors. This is the code:
m = text.sourceText;
if (m.numKeys > 0){
n = m.nearestKey(time).index;
if (m.key(n).time > time) n--;
}
if (n > 0){
t1 = m.key(n).time;
if (n < m.numKeys){
t2 = m.key(n+1).time;
}else{
t2 = t1 + .5;
}
ease (time,t1,t2,0,100);
}else
0
This works beautifully, but since I'd love to be able to sync up the words with the audio, I need to migrate this into the other expression based on markers. Can you further assist me with this? Would love to understand this better.
Thanks again
Julian
Copy link to clipboard
Copied
Looking at your original source image I think I misunderstood your request, at least in part. The delay from the Dan Ebberts example seems irrelevant, but the logic for the keyframes and markers still applies. In your case you would basically use the methodology twice in a nested construct. The outside loop would be the keyframes and the inside the markers where you substitute the normal key with markerKey and create another variable for marker.numKey that equates the m variable. I don't have time to sit down right now to test this, but perhaps you can figure it out based on my crude advice.
Mylenium
Copy link to clipboard
Copied
Appreciate the effort, thanks for taking your time. I'm trying to implement your advise, but can't seem to crack the code.
Copy link to clipboard
Copied
Hey again, I just got help and this is the final code. It works flawlessly.
anim = thisProperty;
delayDur = framesToTime(effect("Delay Duration")("Slider").value);
numMarkers = marker.numKeys;
txt = text.sourceText;
delay = (textIndex - 1) * delayDur;
for (i = 0;
(i < txt.numKeys && txt.key(i + 1).time <= time); i++) {
// dummy loop, i counts # of sourceText Keys that we have passed
};
for (j = 0;
(i > 0 && j < marker.numKeys && marker.key(j + 1).time < txt.key(i).time); j++) {
// dummy loop, j counts # of markers that have occured before the current sourceText Keyframe.
}
textIndex += j; // offset the textIndex so we relatively start counting from 1 after each txt keyFrame
if (numMarkers > 0) {
delay = textIndex <= numMarkers ? marker.key(textIndex).time : marker.key(numMarkers).time;
}
anim.valueAtTime(time - delay);
Super stoked this is possible. Thanks again for the guidance!
Best
Julian
Get ready! An upgraded Adobe Community experience is coming in January.
Learn more