HootVID Bible

This document mainly serves as our internal resource for HootVID’s project expressions (as well as their function and current application) and general project structure. The document’s primary author is Shane Starnes.

Directory:

  • Backend vs Frontend

  • Layer Names

  • Bodymovin

  • KBar

  • Common Expressions


“Backend” vs “Frontend”

Backend

The AE file that contains the “final product” - how the final render appears. When a user selects “Save and Render”, Hoot sends an order to Nexrender, which then calls upon aerender.exe and renders the file, just like we would in Media Encoder or After Effects’ Render Queue. The video is then uploaded to the VAST tag and our online previewer, along with a spot in our shared network drive. 

When building your AE project, start with the “Template Template” project located in the shared network drive. We’ve built it specifically for Hoot Projects and consequently, much of this guide requires the user to start each project with the Template Template. Best practice is to open the project, navigate to File > Dependencies… > Collect Files… and save to your local PC before beginning work on a new template.

When building a new template from scratch, we employ the “build everything first, make it work later” ideology. Start big, make something crazy, and we can scale back when the time comes.

Frontend

When the user needs to preview the changes and ensure that their data is connected correctly, we show them a Lottie representation. In short, a Lottie is a lightweight animation built for web browsers. Users can see their changes in real-time in a very simple representation of the final product. When making a Lottie animation, it needs to follow a few basic rules (and generally, not include much more than what’s listed):

  • Shape layers that have a “Fill” property applied. This is how the user selects colors for elements. Any bezier shape works, as long as it has a fill property below it in AE

  • Paragraph text layers for anything the user may want to change. Any custom text or dynamic text fields should be paragraph text layers, with a bounding box that accurately represents the allotted size for that text layer.

  • Simple images are fine, and will be compressed + rasterized in the final export.

  • All dynamic or custom layers from the backend file and frontend Lottie should be included have the same exact layer names (caps and spaces included).

  • Layers can be ordered in a different manner than the backend, but the layer order determines the field order that users see.

Frontend preview animations (Lotties) take the form of .json files. To a human, it looks like random lines of code. To a web browser, it reads our animation. In the next section, we will go over how to export Lottie files.

Lotties should be built in the simplest manner possible. A small amount of animation and extra details usually won’t hurt, but our goal here is accurate and lightweight


Layer Names

#step_[number]

All layer names for dynamic and custom layers (text, video, images, colors) will start with #step_[number].

The # symbol works alongside our Lottie preview. 

The word “step” was chosen by our dev team, but is crucial. 

The [number] corresponds to our scene number. If we have a template that should be built by the user in multiple scenes, we separate our FRONTEND file into multiple compositions to reflect that. Generally, we use this to separate intro, main, and outro scenes, but the usage may vary. When in doubt, everything is #step_1_…

#step_[number]_[text/image/effects]_

[text/image/effect] tells the webpage and Nexrender what type of layer we’re working with. 

Text layers will tell the webpage to display a text box, and tell Nexrender to fill in the Source Text, Text Fill Color, and Font properties. 

Image layers refer to any video or image - logos, product images, video assets, video backgrounds, etc. Remember, only custom/dynamic layers need these naming conventions applied.

As of writing this, “effects” only apply to color layers. If a shape layer has the ability to change colors, we’ll use #step_1_effects_

#step_[number]_[text/image/effects]_[field name]

We give each of our fields specific names. For example, in the text field we’d like the user to place the “Main Headline”, we’d put #step_1_text_main-headline. Our system prefers lower case and will not work with spaces, but will fill in the blanks for us. main-headline converts to Main Headline on the template edit page. Add _dynamic to the end if you’re naming a Dynamic element. 


Bodymovin

Bodymovin is the quintessential Lottie exporter. It converts the animation into a web-friendly json format. We use specific settings (very simple) to export our json’s effectively:

  • Turn off “Glyphs”

  • Under Assets, select “Export Rasterized Images”.

  • Set compression to 100.

  • Everything else can be turned off.

  • When exporting, you’ll get a pop-up asking how to handle the fonts. Select Google Fonts for each font.

  • DONE! 

Select the 3 dots to the right of the “Comp” option and find the appropriate file location. Name the file “1” for Scene 1, “2” for Scene 2, and so on. This file will not need to call to any image assets, as they’ll be baked into the file.


KBar

We’ve created a tool using KBAR, an extension for AE that allows the user to create toolbars with customizable buttons to apply animation presets and run scripts with a single click. Specifically, we’ve developed a toolbar that specializes in configuring AE projects to HootVid’s Template requirements.

Screenshot of HootVid’s KBAR setup.

Hovering over each button reveals the button’s function. A few notable buttons include:

Text Settings: When a text layer is selected, applies each of the commonly used expressions for text layers and applies easy-to-use controls for layout - max scale, anchor point position, max characters etc. Will only work with the Template Template mentioned at the beginning of this guide.

Pre-Compose and Stagger: Pre-composes each selected layer into a single pre-comp, shortens them to one frame each, and staggers by the same amount. Extremely important for optimizing render times. Use this in conjunction with the freeze-frame button directly next to it.

Write All Layer Names to File: Because both the frontend and backend project files need to have the exact same dynamic layer names, use this to check between the two projects before attempting to upload the template.

These are only a few examples. Explore the KBAR’s buttons further and experiment with combinations. Our KBAR is constantly changing and new buttons will come as we find new ways to standardize and optimize our workflow.


Optimizing Render Speeds

Our render speed benchmarks are quite low compared to most AE compositions. While this is generally restrictive, we have some ways to bend the rules a bit to still get great quality and effective render times. As a general rule, we avoid any render speeds that are longer than 5 minutes on our personal PC using Media Encoder’s default settings. In fact, most of our projects aim for sub-3 minute renders. Because our templates are rendered 8 times per ad created, 3 minutes can easily turn into 30. Any renders over 1.5hrs on our render server will result in a timeout.

To remedy long render times, we employ a few of the following procedures:

  • Pre-render non-dynamic content (particle and glow effects, background video elements, etc) using Apple ProRes 422 codec. NEVER use PNG sequences.

  • Pre-Compose and Freeze-Frame layers that are dynamic, but remain flat throughout their time on screen. Keep in mind, this doesn’t limit us to non-moving elements. This is essentially required for all text layers with the Text Wrap settings applied. The “Pre-Compose and Stagger” K-Bar button will make this process easy.

  • Add posterizeTime(0.1) to the beginning of all expressions that don’t change over time. This only has a small impact on the small scale, but for large projects, can make a considerable difference.

These are the most popular methods of optimization we use, but each template has it’s own challenges. Optimization is nearly as important as configuration, so keep it top-of-mind when building projects.


Common Expressions

Important note: These expressions are included in the K-Bar, and constantly being updated. The expressions below may have been adjusted, upgraded, or changed entirely from what’s listed. If unsure, please stick to using the K-Bar.

Auto-scale Image or Video to fit entire Comp (best used in a separate pre-comp) SCALE

R = sourceRectAtTime(sourceTime(time), true);
w = thisComp.width / R.width * 100;
h = thisComp.height / R.height * 100;
s = Math.min(w, h);
[s,s]

Auto-scale text to a specified height and width SCALE

maxW = 550;
maxH = 100;
x = sourceRectAtTime();
x2 = sourceRectAtTime(31);
w = x.width;
h = x2.height;
y = w/h > maxW/maxH ? maxW/w: maxH/h;
[100,100]*y;

Align Anchor Point (Variations) Anchor Point

rect = sourceRectAtTime();
rect2 = sourceRectAtTime(31);
x = rect.left + rect.width / 2;
y = rect2.top + rect2.height / 2;
[x,y]  

Text WordWrap Source Text

var str = text.sourceText;
var truncateLength = 35;
if (str.length > truncateLength) {
  var lastChar = str.substr(truncateLength-4, 1);
  if (lastChar === " ") {
    str = str.substr(0, truncateLength - 4).trim() + "...";
  } else {
    str = str.substr(0, truncateLength - 3) + "...";
  }
} else {
  str = str;
}
text.sourceText = str;
var lines = str.split('\n');
var maxW = 20;
str = wordWrap(str, maxW);
var lineCount = str.split('\n').length;
function wordWrap(str, maxW) {
    var newLineStr = "\n";
    done = false;
    res = '';
    while (str.length > maxW) {                
        found = false;
              for (i = maxW - 1; i >= 0; i--) {
            if (testWhite(str.charAt(i))) {
                res = res + [str.slice(0, i), newLineStr].join('');
                str = str.slice(i + 1);
                found = true;
                break;
            }
        }
                if (!found) {
            res += [str.slice(0, maxW), newLineStr].join('');
            str = str.slice(maxW);
        }
    }
    return res + str;
}

function testWhite(x) {
    var white = new RegExp(/^\s$/);
    return white.test(x.charAt(0));
};
finalText = time < 0 ? lineCount : str
time > 30 ? finalText + "abcdefghijklmnopqrstuvwxyz|" : finalText

A few very important values are highlighted:

  • truncateLength = [number]: This number is the max number of characters added to the text before truncates, and adds “...” automatically. It will have no effect on the fields in the frontend and is near-impossible to represent accurately in the Lottie preview.

  • maxW = [number]: This number is the maximum number of characters per line.

  • finalText = time < 0 ? lineCount : str: We’re creating a variable called “finalText” and assigning it 2 different values, which change over time. If the current time is less than 0 (impossible to see, but still calculable), the sourceText is the number of lines in the final text. We use this value to adjust the layout of the comp based on the number of lines. It can be called to in any other property on any other layer.

    • We can then call to this value in any other property within our AE project. To assign it to a variable, just use:

[variableName] = parseInt([compName].[layerName].text.sourceText.valueAtTime(-1)

  • time > 30 ? finalText + ""abcdefghijklmnopqrstuvwxyz|" : finalText: We are assigning the sourceText a value based on the current time. If the time is greater than 30 (none of our compositions ever are), then add the entire alphabet to it plus a pipe character. Otherwise, display the finalText, which is the intended text to be displayed in the final render.

    • When doing layout with text, letters, and characters that descend beyond the baseline (descenders) can cause layout issues when using sourceRectAtTime(). By using sourceRectAtTime(31), we’re able to grab the sourceRectAtTime of text that includes descenders, and we’re able to make more accurate layout predictions and concessions for all possibilities of text.

Conditional Statements ANY PROPERTY

There are two forms of conditional statements, and most can be used interchangeably. Here is the one used more frequently:

[variable] = [condition] ? [intended value] : [other intended value]

Declare your variable, then check a condition. For example, is 2 > 1 ? After the question mark, we place an intended result if the condition is true. If the condition is false, the variable will take on the [other intended value]

The other, used in some other cases but is a bit slower:

if (condition) {
do this
}else{
do this instead
}

We can check a million things, but a good use case is checking the length of another layer’s sourceText to see if there’s even any text there at all. A use case that we use often is as follows on the Y Position property:

thisComp.layer(index+1).text.sourceText.length > 0 ? 600 : 700

Does the layer below me have text filled in? If so, Y Position is 600. If not, it is 700.

Animate Smoothly with Expressions

Place two keyframes for the in and out point of your animation

function expoOut(curT,t1,t2,val1,val2){
  if(curT <= t1)return val1;
  if(curT >= t2)return val2;
  dtEase = t2 - t1;
  dvEase = val2 - val1;
  tEase = (curT-t1)/dtEase;
  return val1 + dvEase*(1-Math.exp(-10*tEase));
}

if(numKeys > 1){
  t1 = key(1).time;
  t2 = key(2).time;

  v1 = 80;
  v2 = 400;

  expoOut(time,t1,t2,v1,v2);

}else value