{"id":331443,"date":"2020-12-30T07:56:11","date_gmt":"2020-12-30T15:56:11","guid":{"rendered":"https:\/\/css-tricks.com\/?p=331443"},"modified":"2020-12-30T07:56:14","modified_gmt":"2020-12-30T15:56:14","slug":"cloudinary-tricks-for-video","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/cloudinary-tricks-for-video\/","title":{"rendered":"Cloudinary Tricks for Video"},"content":{"rendered":"\n

Creating video is time consuming. A well-made 5-minute video can take hours to plan, record, and edit \u2014 and that’s before<\/em> we start talking about making that video consistent with all the other videos on your site.<\/p>\n\n\n\n

When we took on the Jamstack Explorers project<\/a> (a video-driven educational resource for web developers), we wanted to find the right balance of quality and shipping: what could we automate in our video production process to reduce the time and number of steps required to create video content without sacrificing quality?<\/p>\n\n\n\n

With the help of Cloudinary<\/a>, we were able to deliver a consistent branding approach in all our video content without adding a bunch of extra editing tasks for folks creating videos. And, as a bonus, if we update our branding in the future, we can update all the video branding across the whole site at once \u2014 no video editing required!<\/p>\n\n\n\n\n\n\n

What does \u201cvideo branding\u201d mean?<\/h3>\n\n\n

To make every video on the Explorers site feel like it all fits together, we include a few common pieces in each video:<\/p>\n\n\n\n

  1. A title scene<\/li>
  2. A short intro bumper (video clip) that shows the Jamstack Explorers branding<\/li>
  3. A short outro bumper that either counts down to the next video or shows a “mission accomplished” if this is the last video in the mission<\/li><\/ol>\n\n\n

    Skip to the end: here\u2019s how a branded video looks<\/h3>\n\n\n

    To show the impact of adding the branding, here’s one of the videos from Jamstack Explorers without<\/em> any branding:<\/p>\n\n\n\n

    This video (and this Vue mission from Ben Hong<\/a>) is legitimately outstanding! However, it starts and ends a little abruptly, and we don’t have a sense of where this video lives.<\/p>\n\n\n\n

    We worked with Adam Hald to create branded video assets that help give each video a sense of place. Check out the same video with all the Explorers branding applied:<\/p>\n\n\n\n

    How does Cloudinary make this possible?<\/h3>\n\n\n

    Cloudinary is a cloud-based asset delivery network that gives us a powerful, URL-based API to manipulate and transform media. It supports all sorts of asset types, but where it really shines is with images and video.<\/p>\n\n\n\n

    To use Cloudinary, you create a free account<\/a>, then upload your asset. This asset then becomes available at a Cloudinary URL:<\/p>\n\n\n\n

    https:\/\/res.cloudinary.com\/netlify\/image\/upload\/v1605632851\/explorers\/avatar.jpg\n                           ^^^^^^^             ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^\n                              |                      |                |\n                              V                      V                V\n                      cloud (account) name    version (optional)  file name<\/code><\/pre>\n\n\n\n

    This URL points to the original image and can be used in <img \/><\/code> tags and other markup.<\/p>\n\n\n\n

    \"\"
    The original image size is 97.6kB.<\/figcaption><\/figure>\n\n\n

    Dynamically adjust file format and quality to reduce file sizes<\/h3>\n\n\n

    If we’re using this image on a website and want to improve our site performance, we may decide to reduce the size of this image by using next-generation formats like WebP, AVIF, and so on. These new formats are much smaller, but aren’t supported by all browsers, which would usually mean using a tool to generate multiple versions of this image in different formats, then using a <picture><\/code> element or other specialized markup to provide modern options with the JPG fallback for older browsers.<\/p>\n\n\n\n

    With Cloudinary, all we have to do is add a transformation to the URL:<\/p>\n\n\n\n

    https:\/\/res.cloudinary.com\/netlify\/image\/upload\/q_auto,f_auto\/v1605632851\/explorers\/avatar.jpg\n                                                ^^^^^^^^^^^^\n                                                      |\n                                                      V\n                                    automatic quality & format transformations\n<\/code><\/pre>\n\n\n\n

    What we see in the browser is visually identical:<\/p>\n\n\n\n

    \"\"
    The transformed image is 15.4kB.<\/figcaption><\/figure>\n\n\n\n

    By setting the file format and quality settings to automatic (f_auto,q_auto<\/code>), Cloudinary is able to detect which formats are supported by the client and serves the most efficient format at a reasonable quality level. In Chrome, for example, this image transforms from a 97.6kB JPG to a 15.4kB WebP, and all we had to do was add a couple of things to the URL!<\/p>\n\n\n

    We can transform our images in lots of different ways!<\/h3>\n\n\n

    We can go further with other transformations, including resizing (w_150<\/code> for “resize to 150px wide”) and color effects (e_grayscale<\/code> for “apply the grayscale effect”):<\/p>\n\n\n\n

    https:\/\/res.cloudinary.com\/netlify\/image\/upload\/q_auto,f_auto,w_150,e_grayscale\/v1605632851\/explorers\/avatar.jpg<\/code><\/pre>\n\n\n\n
    \"\"
    The same image after adding grayscale effects and resizing.<\/figcaption><\/figure>\n\n\n\n

    This is only a tiny taste of what’s possible \u2014 make sure to check out the Cloudinary docs for more examples<\/a>!<\/p>\n\n\n

    There’s a Node SDK to make this a little more human-readable<\/h3>\n\n\n

    For more advanced transformations like what we’re going to get into, writing the URLs by hand can get a little hard to read. We ended up using the Cloudinary Node SDK<\/a> to give us the ability to add comments and explain what each transformation was doing, and that’s been extremely helpful as we maintain and evolve the platform.<\/p>\n\n\n\n

    To install it, get your Cloudinary API key and secret from your console<\/a>, then install the SDK using npm:<\/p>\n\n\n\n

    # create a new directory\nmkdir cloudinary-video\n\n# move into the new directory\ncd cloudinary-video\/\n\n# initialize a new Node project\nnpm init -y\n\n# install the Cloudinary Node SDK\nnpm install cloudinary<\/code><\/pre>\n\n\n\n

    Next, create a new file called index.js<\/code> and initialize the SDK with your cloud_name<\/code> and API credentials:<\/p>\n\n\n\n

    const cloudinary = require('cloudinary').v2;\n\n\/\/ TODO replace these values with your own Cloudinary credentials\ncloudinary.config({\n  cloud_name: 'your_cloud_name',\n  api_key: 'your_api_key',\n  api_secret: 'your_api_secret',\n});<\/code><\/pre>\n\n\n\n

    Don’t commit your API credentials to GitHub or share them anywhere. Use environment variables to keep them safe! If you’re unfamiliar with environment variables, Colby Fayock has written a great introduction to using environment variables<\/a>.<\/p>\n\n\n\n

    Next, we can create the same transformation as before using slightly more human-readable configuration settings:<\/p>\n\n\n\n

    cloudinary.uploader\n  \/\/ the first argument should be the public ID (including folders!) of the\n  \/\/ image we want to transform\n  .explicit('explorers\/avatar', {\n    \/\/ these two properties match the beginning of the URL:\n    \/\/ https:\/\/res.cloudinary.com\/netlify\/image\/upload\/...\n    \/\/                                    ^^^^^^^^^^^^\n    resource_type: 'image',\n    type: 'upload',\n\n    \/\/ \"eager\" means we want to run these transformations ahead of time to avoid\n    \/\/ a slow first load time\n    eager: [\n      {\n        fetch_format: 'auto',\n        quality: 'auto',\n        width: 150,\n        effect: 'grayscale',\n      },\n    ],\n\n    \/\/ allow this transformed image to be cached to avoid re-running the same\n    \/\/ transformations over and over again\n    overwrite: false,\n  })\n  .then((result) => {\n    console.log(result);\n  });<\/code><\/pre>\n\n\n\n

    Let’s run this code by typing node index.js<\/code> in our terminal. The output will look something like this:<\/p>\n\n\n\n

    {\n  asset_id: 'fca4abba96ffdf70ef89498aa340ae4e',\n  public_id: 'explorers\/avatar',\n  version: 1605632851,\n  version_id: 'b8a923931af20404e89d03852ff1bff1',\n  signature: 'e7201c9ab36cb5b6a0545cee4f5f8ee27fb7f99f',\n  width: 300,\n  height: 300,\n  format: 'jpg',\n  resource_type: 'image',\n  created_at: '2020-11-17T17:07:31Z',\n  bytes: 97633,\n  type: 'upload',\n  url: 'http:\/\/res.cloudinary.com\/netlify\/image\/upload\/v1605632851\/explorers\/avatar.jpg',\n  secure_url: 'https:\/\/res.cloudinary.com\/netlify\/image\/upload\/v1605632851\/explorers\/avatar.jpg',\n  access_mode: 'public',\n  eager: [\n    {\n      transformation: 'e_grayscale,f_auto,q_auto,w_150',\n      width: 150,\n      height: 150,\n      bytes: 6192,\n      format: 'jpg',\n      url: 'http:\/\/res.cloudinary.com\/netlify\/image\/upload\/e_grayscale,f_auto,q_auto,w_150\/v1605632851\/explorers\/avatar.jpg',\n      secure_url: 'https:\/\/res.cloudinary.com\/netlify\/image\/upload\/e_grayscale,f_auto,q_auto,w_150\/v1605632851\/explorers\/avatar.jpg'\n    }\n  ]\n}<\/code><\/pre>\n\n\n\n

    Under the eager<\/code> property, our transformations are shown along with the full URL to view the transformed image.<\/p>\n\n\n\n

    While the Node SDK is probably overkill for a straightforward transformation like this one, it becomes really<\/em> handy when we start looking at the complex transformations required to add video branding.<\/p>\n\n\n

    Transforming videos with Cloudinary<\/h3>\n\n\n

    To transform our videos in Jamstack Explorers, we follow the same approach: each video is uploaded to Cloudinary, and then we modify the URLs to resize, adjust quality, and insert the title card and bumpers.<\/p>\n\n\n\n

    There are a few major categories of transformation that we’ll be tackling to add the branding:<\/p>\n\n\n\n

    1. Overlays<\/li>
    2. Transitions<\/li>
    3. Text overlays<\/li>
    4. Splicing<\/li><\/ol>\n\n\n\n

      Let’s look at each of these categories and see if we can’t reimplement the Jamstack Explorers branding on Ben’s video! Let’s get set up by setting up index.js<\/code> to transform our base video:<\/p>\n\n\n\n

      cloudinary.uploader\n  .explicit('explorers\/bumper', {\n    \/\/ these two properties match the beginning of the URL:\n    \/\/ https:\/\/res.cloudinary.com\/netlify\/image\/upload\/...\n    \/\/                                    ^^^^^^^^^^^^\n    resource_type: 'video',\n   type: 'upload',\n\n    \/\/ \"eager\" means we want to run these transformations ahead of time to avoid\n    \/\/ a slow first load time\n    eager: [\n      {\n        fetch_format: 'auto',\n        quality: 'auto',\n        height: 360,\n        width: 640,\n        crop: 'fill', \/\/ avoid letterboxing if videos are different sizes\n      },\n    ],\n\n    \/\/ allow this transformed image to be cached to avoid re-running the same\n    \/\/ transformations over and over again\n    overwrite: false,\n  })\n  .then((result) => {\n    console.log(result);\n  });<\/code><\/pre>\n\n\n\n

      You may have noticed that we’re using a video called “bumper” instead of Ben’s original video. This is due to the way Cloudinary orders videos as we add them together. We’ll add Ben’s video in the next section!<\/p>\n\n\n

      Combine two videos with a custom transition using Cloudinary<\/h3>\n\n\n

      To add our bumpers, we need to add a second transformation “layer” to the eager<\/code> array that adds a second video as an overlay.<\/p>\n\n\n\n

      To do this, we use the overlay<\/code> transformation and set it to video:publicID<\/code>, where publicID<\/code> is the Cloudinary public ID of the asset with any slashes (\/<\/code>) transformed to colons (:<\/code>).<\/p>\n\n\n\n

      We also need to tell Cloudinary how to transition between the two videos, which we do using a special kind of video called a luma matte<\/em> that lets us mask one video with the black area of the video, and a second video with the white area. This results in a stylized cross-fade.<\/p>\n\n\n\n

      Here’s what the luma matte looks like on its own:<\/p>\n\n\n\n

      const videoBaseTransformations = {\n  fetch_format: 'auto',\n  quality: 'auto',\n  height: 360,\n  width: 600,\n  crop: 'fill',\n}\n\ncloudinary.uploader\n  .explicit('explorers\/bumper', {\n    \/\/ these two properties match the beginning of the URL:\n    \/\/ <https:\/\/res.cloudinary.com\/netlify\/image\/upload\/>...\n    \/\/\n    resource_type: 'video',\n    type: 'upload',\n\n    \/\/ \"eager\" means we want to run these transformations ahead of time to avoid\n    \/\/ a slow first load time\n    eager: [\n      videoBaseTransformations,\n      {\n        overlay: 'video:explorers:LCA-07-lifecycle-hooks',\n        ...videoBaseTransformations,\n      },\n      {\n        overlay: 'video:explorers:transition',\n        effect: 'transition',\n      },\n      { flags: 'layer_apply' }, \/\/ <= apply the transformation\n      { flags: 'layer_apply' }, \/\/ <= apply the actual video\n    ],\n\n    \/\/ allow this transformed image to be cached to avoid re-running the same\n    \/\/ transformations over and over again\n    overwrite: false,\n  })\n  .then((result) => {\n    console.log(result);\n  });<\/code><\/pre>\n\n\n\n

      We need the same format, quality, and sizing transformations on all videos, so we pulled those out into a variable called videoBaseTransformations<\/code>, then added a second object to contain the overlay.<\/p>\n\n\n\n

      If we run this with node index.js<\/code>, the video we get back looks like this:<\/p>\n\n\n\n