Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <h1 class="text-center">WOXO Videos API Playground</h1>
<h2 class="text-center">Feel free to dig into the JS code to see the API usage details.</h2>
<p class="text-center fs-1">🥳</p>
<!-- <p>Click any button and wait till done 🤞</p> -->
<section class="container mt-5" id="HelloWorld">
  <h2>👋 Example 1: Hello World</h2>
  <p class="mt-3">Basic example using the current *user time* and *browser* to make it more fun 😅.</p>

  <form class="text-center">
    <div class="mb-3 video-input">
      <label>Your Name</label>
      <input type="text" class="form-control" id="NameInput" placeholder="Elon">
    </div>

    <button class="btn btn-light" type="button" onclick="generateHelloWorld()">
      Generate
    </button>
  </form>
  <div class="video-render"></div>
</section>

<section class="container mt-5" id="WithCryptoCurrentPrices">
  <h2>💰 Example 2: Real-time Cryptocurrencies prices</h>
    <p class="mt-3">Bitcoin, Ethereum, Cardano, Tether, Binance Coin, XRP, Dogecoin</p>
    <form class="text-center">
      <button class="btn btn-light" type="button" onclick="generateWithCryptoCurrentPrices()">
        Generate
      </button>
    </form>
    <div class="video-render"></div>
</section>

<section class="container mt-5" id="WithMultipleResolutions">
  <h2>📺 Example 3: Multiple Video Resolutions</h>
    <p class="mt-3">Using the Video Shape parameter you can get videos in multiple resolutions.</p>

    <form class="text-center">
      <button class="btn btn-light" type="button" onclick="generateWithMultipleResolutions()">
        Generate
      </button>
    </form>
    <div class="video-render"></div>
</section>
              
            
!

CSS

              
                body {
  font-family: Open sans, sans-serif !important;
  background-image: -webkit-linear-gradient(180deg, #764dbd, #026cce);
  padding: 0.5rem;
}

h1,
h2,
p {
  color: #fff;
  text-align: center;
}

h2 {
  font-size: 1.5rem;
}
p {
  font-size: 1rem;
}

.video-input {
  background: white;
  color: black;
  padding: 20px;
  width: 25%;
  margin-left: 37%;
}
#btnBox {
  display: flex;
  justify-content: center;
}

.btn {
  font-size: 1rem;
  font-weight: 700;
  background: #fff;
  padding: 10px 30px;
  border-radius: 45px;
  border: none;
  cursor: pointer;
  margin-bottom: 1rem;
}

.loader {
  display: flex;
  justify-content: center;
  text-align: center;
  display: none;
}

.label {
  color: #fff;
  display: flex;
  justify-content: center;
  text-align: center;
  display: none;
}

.lds-ripple {
  display: inline-block;
  position: relative;
  width: 80px;
  height: 80px;
}

.lds-ripple div {
  position: absolute;
  border: 4px solid #fff;
  opacity: 1;
  border-radius: 50%;
  animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}

.lds-ripple div:nth-child(2) {
  animation-delay: -0.5s;
}

@keyframes lds-ripple {
  0% {
    top: 36px;
    left: 36px;
    width: 0;
    height: 0;
    opacity: 1;
  }

  100% {
    top: 0px;
    left: 0px;
    width: 72px;
    height: 72px;
    opacity: 0;
  }
}

#videos {
  display: flex;
  justify-content: center;
}

video {
  overflow: hidden;
  border-radius: 12px;
  margin-right: 15px;
}

              
            
!

JS

              
                // Find your teamId and token in the WOXO dashboard
// Please don't use this token ;)
// https://woxo.tech

var TEAM_ID = "5f52815e4c111600176f763b"; // copy here your team from woxo dashboard -> my-account
var TOKEN = "b4IcRI8lycfzaISRcTmA9JKXGCLOV5i8"; // copy here your token from woxo dashboard -> my-account

// Example 1
var generateHelloWorld = () => {
  // paste your data here
  var date = new Date();
  var humanDate = date.toLocaleTimeString();
  var name = document.getElementById("NameInput").value || "Elon";
  var videos = [
    {
      "Title 1": `Hello ${name}`,
      "Text 1": `What time is it?`,
      "Media 1": "bot /video",

      "Title 2": `It is ${humanDate}?`,
      "Text 2": `I have been working a lot...`,
      "Media 2": "bot /video",

      "Title 3": `${getBrowserName()} really?`,
      "Text 3": `It should be a better browser ;)`,
      "Media 3": "bot /video",

      "Title 4": "I'm WOXO",
      "Text 4": "The coolest bulk video maker",
      "Media 4": "bot /video",

      "Title 5": "It's very nice to meet you :)",
      "Text 5": "",
      "Media 5": "bot /video",
      TextPosition: "Top",
      TextColor: "#fff", // any Hex or RGBA color
      BackgroundColor: "#118ab2", // any Hex or RGBA color
      TextStyle: "Criss Cross" // Solid, Criss Cross, Basic, Step Up or Step Center.
    },
    {
      "Title 1": `Son las ${humanDate}?`,
      "Text 1": `Hola Mundo`,
      "Media 1": "cat /video",

      "Title 3": `Usando ${getBrowserName()}`,
      "Text 3": `Debe haber un mejor browser...`,
      "Media 3": "cat /video",

      "Title 2": "Soy WOXO",
      "Text 2": "The coolest bulk video maker",
      "Media 2": "cat /video",

      "Title 4": "Gusto en conocerte :)",
      "Text 4": "",
      "Media 4": "cat /video",
      TextPosition: "Top"
    },
    {
      "Title 1": `${humanDate}?`,
      "Text 1": `Bonjour le monde`,
      "Media 1": "funny /video",
      "Title 2": "Je suis WOXO",
      "Text 2": "The coolest bulk video maker",
      "Media 2": "funny /video",

      "Title 3": "je vois que tu utilises",
      "Text 3": `un navigateur **${getBrowserName()}**`,
      "Media 3": "funny /video",

      "Title 4": "C'est très agréable de vous rencontrer :)",
      "Text 4": "",
      "Media 4": "funny /video",
      TextPosition: "Top"
    }
  ];
  showLoader("HelloWorld");
  generateWOXOVideosAPI(videos)
    .then((data) => {
      renderVideoPlayers(data, "HelloWorld");
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
      hideLoader("HelloWorld");
    });
};

// Example 2
var generateWithCryptoCurrentPrices = () => {
  showLoader("WithCryptoCurrentPrices");
  getCryptoRealtimeDataAPI()
    .then((response) => response.json())
    .then((data) => {
      console.log(data);
      const event = new Date();
      const options = { year: "numeric", month: "short", day: "numeric" };
      var humanDate = event.toLocaleDateString(undefined, options);
      var videos = data.coins.map((coin) => {
        var roundedPrice = parseFloat(coin.price).toFixed(2);
        return {
          "Title 1": `${coin.name} prices today`,
          "Text 1": `${humanDate}`,
          "Media 1": "crypto /video",

          "Title 2": `${roundedPrice} USD`,
          "Text 2": `Real-time price $$$`,
          "Media 2":
            "https://player.vimeo.com/external/539028225.sd.mp4?s=a46007b4c5d633387a29f1f9499e69b25371f6c6&profile_id=165&oauth2_token_id=57447761",

          "Title 3": `1 Week Price Change?`,
          "Text 3": `${coin.priceChange1w} USD`,
          "Media 3":
            "https://player.vimeo.com/external/539036230.hd.mp4?s=c61e9cb8ee255673477195c524fe982f3f77ce31&profile_id=172&oauth2_token_id=57447761",

          "Title 4": "Stay tuned!",
          "Text 4": "cryptolovers.com",
          "Media 4":
            "https://player.vimeo.com/external/564155892.sd.mp4?s=cabd662d1bf561342f03ddd47c498c29ba43b29f&profile_id=165&oauth2_token_id=57447761",
          TextPosition: "Top",
          TextColor: "#fff", // any Hex or RGBA color
          BackgroundColor: "#ef476f", // any Hex or RGBA color
          TextStyle: "Criss Cross" // Solid, Criss Cross, Basic, Step Up or Step Center.
        };
      });
      return videos;
    })
    .then((videos) => {
      console.log(videos);
      return generateWOXOVideosAPI(videos);
    })
    .then((data) => {
      renderVideoPlayers(data, "WithCryptoCurrentPrices");
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
      hideLoader("WithCryptoCurrentPrices");
    })
    .catch((e) => {
      alert("Error getting information from the API");
    });
};

// Example 3
var generateWithMultipleResolutions = () => {
  // paste your data here
  var videos = [
    {
      "Title 1": "Resolution 9:16",
      "Text 1": `Good for TikTok, IG Reels, YT Shorts, etc...`,
      "Media 1": "social media /video",

      "Title 3": "Do you know who has more followers on TikTok?",
      "Text 3": ``,
      "Media 3": "tiktok",

      "Title 2": "Charli D'Amelio",
      "Text 2": "She has 123.5 million TikTok followers",
      "Media 2": "tiktok",

      "Title 4": "Have a nice day :)",
      "Text 4": "",
      "Media 4": "morning /video",
      TextPosition: "Top",
      "Video Shape": "9:16"
    },
    {
      "Title 1": "Resolution 1:1",
      "Text 1": `Square Video`,
      "Media 1": "square /video",

      "Title 2": "Do you like squares?",
      "Text 2": ``,
      "Media 2": "squares",

      "Title 3": "Square videos can be used in",
      "Text 3": "Linkedin, Pinterest, Facebook, etc",
      "Media 3": "linkedin",

      "Title 4": "Have a nice day :)",
      "Text 4": "",
      "Media 4": "morning /video",
      TextPosition: "Top",
      "Video Shape": "1:1"
    },
    {
      "Title 1": "Resolution 16:9",
      "Text 1": `Landscape Video`,
      "Media 1": "youtube /video",

      "Title 2": "Do you like squares?",
      "Text 2": ``,
      "Media 2": "squares",

      "Title 3": "Landscape videos can be used in",
      "Text 3": "Youtube",
      "Media 3": "youtube",

      "Title 4": "Have a nice day :)",
      "Text 4": "",
      "Media 4": "morning /video",
      TextPosition: "Top",
      "Video Shape": "16:9"
    },
    {
      "Title 1": "Resolution 4:5",
      "Text 1": `Landscape Video`,
      "Media 1": "youtube /video",

      "Title 2": "Do you like squares?",
      "Text 2": ``,
      "Media 2": "squares",

      "Title 3": "Landscape videos can be used in",
      "Text 3": "Youtube",
      "Media 3": "youtube",

      "Title 4": "Have a nice day :)",
      "Text 4": "",
      "Media 4": "morning /video",
      TextPosition: "Top",
      "Video Shape": "4:5"
    }
  ];
  showLoader("WithMultipleResolutions");
  generateWOXOVideosAPI(videos)
    .then((data) => {
      renderVideoPlayers(data, "WithMultipleResolutions");
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
      hideLoader("WithMultipleResolutions");
    });
};

// Use WOXO API for generating videos
function generateWOXOVideosAPI(videos, afterAllVideosAreGenerated) {
  var raw = JSON.stringify({
    data: videos
  });
  var requestOptions = {
    method: "POST",
    headers: {
      team: TEAM_ID,
      token: TOKEN,
      "Content-Type": "application/json"
    },
    body: raw,
    redirect: "follow"
  };
  const myPromise = new Promise((resolve, reject) => {
    // This request will send the video Job to WOXO API
    fetch("https://api.woxo.tech/video/native/generate", requestOptions)
      .then((response) => response.text())
      .then((result) => {
        var { native } = JSON.parse(result).success;
        var checkUrl = native.check;
        var previousTotalDone = 0;
        var videoResults = [];
        var intervalTimer = setInterval(() => {
          // This function will check is the Video Generation Job is Done
          checkVideoGenerationStatus(checkUrl)
            .then(({ data, totalFailed, totalDone, totalRunning }) => {
              if (previousTotalDone < totalDone) {
                videoResults = data.map((v) => {
                  if (v.status === "done") {
                    return {
                      url: v.videoEndpoint,
                      thumbnails: v.thumbnailsLink,
                      preview: v.previewLink,
                      id: v._id,
                      metadata: v.editSpec.metadata,
                      fileName: v.editSpec.fileName,
                      index: v.editSpec.index,
                      downloadLink: v.downloadLink,
                      resolution: {
                        width: v.editSpec.width,
                        height: v.editSpec.height
                      }
                    };
                  }
                });
                previousTotalDone = totalDone;
                if (onceSomeVideosAreGenerated) {
                  onceSomeVideosAreGenerated(_data);
                }
              }

              if (totalFailed + totalDone === data.length && data.length > 0) {
                console.log("finish generate");
                resolve(videoResults);
                clearInterval(intervalTimer);
              }
            })
            .catch((e) => {
              console.log("Error trying to get video generation status", error);
              clearInterval(intervalTimer);
              reject(e);
            });
        }, 3000);
      })
      .catch((e) => {
        console.log("Error trying to generate videos", error);
        reject(e);
      });
  });
  return myPromise;
}

function checkVideoGenerationStatus(videoStatusUrl) {
  const myPromise = new Promise((resolve, reject) => {
    fetch(videoStatusUrl, {
      method: "GET",
      headers: {
        "Content-Type": "application/json"
      },
      redirect: "follow"
    })
      .then((response) => response.text())
      .then((result) => {
        // console.log(result);
        var result = JSON.parse(result);
        resolve(result);
      })
      .catch((error) => {
        console.log("error", error);
        reject(error);
      });
  });
  return myPromise;
}

// Receive WOXO's API results and Render Video Players
function renderVideoPlayers(data, parentId) {
  console.log("data", data);
  for (let index = 0; index < data.length; index++) {
    const { url, resolution, id } = data[index];
    if (!document.getElementById(id)) {
      var video = document.createElement("video");
      video.id = id;
      video.controls = true;
      var source = document.createElement("source");
      video.setAttribute("width", resolution.width / 4);
      video.setAttribute("height", resolution.height / 4);
      source.setAttribute("src", url);
      video.appendChild(source);
      console.log(document.getElementById(parentId));
      if (document.getElementById(parentId)) {
        document
          .getElementById(parentId)
          .querySelector(".video-render")
          .appendChild(video);
      }
    }
  }
}

//------------------ Utilities ---------------------/

// Used in Example 3
// Get crypto realtime prices
// https://documenter.getpostman.com/view/5734027/RzZ6Hzr3
function getCryptoRealtimeDataAPI() {
  return fetch("https://api.coinstats.app/public/v1/coins?skip=0&limit=10");
}

function showLoader(parentId) {
  var loaderParent = document.getElementById(parentId);
  console.log(loaderParent);
  if (loaderParent) {
    var loadingButton = loaderParent.querySelector(".btn");
    loadingButton.disabled = true;
    loadingButton.innerHTML = `
  <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
  Generating videos...
    `;
    loaderParent.querySelector(".video-render").innerHTML = "";
  }
}
function hideLoader(parentId) {
  var loaderParent = document.getElementById(parentId);
  console.log(loaderParent);
  if (loaderParent) {
    var loadingButton = loaderParent.querySelector(".btn");
    loadingButton.disabled = false;
    loadingButton.innerHTML = "Generate";
  }
}

function getBrowserName() {
  if (
    (navigator.userAgent.indexOf("Opera") ||
      navigator.userAgent.indexOf("OPR")) != -1
  ) {
    return "Opera";
  } else if (navigator.userAgent.indexOf("Chrome") != -1) {
    return "Chrome";
  } else if (navigator.userAgent.indexOf("Safari") != -1) {
    return "Safari";
  } else if (navigator.userAgent.indexOf("Firefox") != -1) {
    return "Firefox";
  } else if (
    navigator.userAgent.indexOf("MSIE") != -1 ||
    !!document.documentMode == true
  ) {
    return "Internet Explorer";
  } else {
    return "Not sure!";
  }
}

              
            
!
999px

Console