Make videos with JavaScript 3

javascript, beginners, showdev, programming

4 minute read

08/04/2022

Since my last blog post I've fixed a few persisting bugs and continued work with ffmpeg. The app is going to take user input for a stackoverflow questions url and a youtube videos url and create a narrated tiktok similar to this project. First I'm going to briefly explain some of those bugs and how I fixed them and then I'm going to explain the first function call that starts everything off.

Changes Made and Bugs Fixed

The bugs I ended up fixing ended up actually being apart of the makeApiCall() function I'll talk about later in the post. I was having async issues when fetching data from the stackoverflow API. This is my first time making fetch requests in Node, and I forgot to add await before fetching the data. This wasn't always an issue, only occasionally the functions that relied on the response would error out. Since it was so infrequent usually, I thought it was an issue with the API and how many requests I was making. I decided to look at the function a little closer and noticed that both API calls were missing await and I haven't had the error since.

I stepped away from this project to work on my portfolio for a while. I haven't made a ton of progress since my last post, but I have still made some. The function that is adding the mp3 audio from Googles text-to-speech API to the screenshots works sometimes, but I'm still having a few persisting errors I'm ironing out. Even just in writing this post and looking at the errors I have a few more ideas to fix the issues and hopefully I'll get everything working a little better by the next post.

The api-call.js File

api-call.js is the first function that get's called inside of startVideoEdit() after the user has passed in their inputs. The function takes in the question url and a few global variables that it uses to store parts of the response.

The file is importing three modules

import fetch from "node-fetch";
import chalk from "chalk";
import { parseText } from "./parse-text.js";

Node Fetch is pretty straight forward, and I've already explained what Chalk is and how it's used in previous posts. Parse text get's called from within this file to convert the HTML strings returned from the API calls into plain text.

The first step is to extract the question ID from the url passed into the function as it's the only part of the url we actually need.

const url = questionURL;
const questionId = url
  .match(/\/(\d+)+[\/]?/g)
  .map((id) => id.replace(/\//g, ""));

Then we can interpolate questionId into two different urls, one to get the questions data, and another for the answers.

const qURL = `https://api.stackexchange.com/2.3/questions/${questionId}?order=desc&sort=activity&site=stackoverflow&filter=withbody`;
const aURL = `https://api.stackexchange.com/2.3/questions/${questionId}/answers?order=desc&sort=activity&site=stackoverflow&filter=withbody`;

Then we're able to use the qURL to query the API

const getQuestionData = async () => {
  await fetch(qURL)
    .then((r) => r.json())
    .then((data) => handleQuestionResponse(data));
};

This is where that bug I talked about earlier was coming up. Now I know to always await fetches in Node. I then call a helper function handleQuestionResponse() and pass in the responses JSON data.

First, if the question isn't answered none of this is going to work. If the question has been answered I then save the number of answers, the html string of the questions body as well as the html string of the questions title.

async function handleQuestionResponse(data) {
  if (data.items[0].is_answered !== true) {
    console.log(
      "Sorry, this question isn't answered yet. Try again with another question."
    );
    questionDataObj.isAnswered = data.items[0].is_answered;
  } else {
    questionDataObj.answerCount = data.items[0].answer_count;
    questionDataObj.htmlString = [data.items[0].body];
    questionDataObj.title = [data.items[0].title];
  }
}

Then the aURL is used to query the API

const getAnswerData = async () => {
  await fetch(aURL)
    .then((r) => r.json())
    .then((data) => handleAnswerResponse(data));
};

Again, this is where that await bug was causing issues for me. Again, I'm using a helper function that takes in the responses JSON data.

First, I map over each object in the JSON saving the ID's in an array. Then I map over each object again, saving the HTML strings to an array. I do the same again to interpolate the ID into a string in order to give each answer a title to use with files later. Then I call the parseText() function three times. The first is used to get plain text for the Answers, the second is used for the questions body and the last is for the title. Looking at this function now, I'm not sure why parseText() is inside of handleAnswerResponse() instead of being called outside in the main function being exported from the file. I'll probably mess around with this and move those function calls outside of this helper function.

async function handleAnswerResponse(data) {
  let answersIds = data.items.map((el) => el.answer_id);
  questionDataObj.answerIds = answersIds;
  let htmlAns = data.items.map((el) => el.body);
  let titles = data.items.map((el) => `answer${el.answer_id}`);
  titles.forEach((el) => fileNames.push(el));
  // parse answers
  await parseText(htmlAns, plainTextStrings, questionDataObj);
  // parse question body
  await parseText(
    questionDataObj.htmlString,
    plainTextStrings,
    questionDataObj,
    false
  );
  // parse question title
  await parseText(
    questionDataObj.title,
    plainTextStrings,
    questionDataObj,
    true
  );
}

That's all there is in this file other than calling getAnswerData() and getQuestionData(). There's some room to improve here for sure, and I plan on having some of this changed by the next time I write about this project. If you have any questions about I'll do my best to reply in the comments. Thanks for reading and feel free to check out some of my other posts here on dev.to

If you have any questions please leave a comment over on dev.to

Built with Next.js, Tailwind and Vercel