One place for hosting & domains

      Media

      7 Tools That Can Help Boost Your Social Media Engagement


      With so many people plugged into social media, you probably already know that you can’t avoid building up your business’s presence on these platforms. However, it’s about more than just showing up – you’ll also need to find a way to engage with your followers.

      Fortunately, you don’t have to do it alone. By wielding a few powerful social media management tools, you can start interacting with your audience more than ever before.

      In this article, we’ll cover a few reasons why boosting social media engagement is a worthwhile goal. Then, we’ll show you seven tools that can help you achieve it. Let’s get started!

      The Importance of Social Media Engagement

      The very nature of social media is an interactive one. A recent study found that up to 56 percent of people reported recently liking posts from others. As such, these platforms present a unique opportunity to connect with your customers on a personal level.

      Forging this connection has several benefits. For starters, social engagement metrics can provide valuable insights. If you can identify the demographics that are most frequently commenting, liking, and sharing your posts, you can use that data to adjust your target audience. It’s also an efficient way to find potential influencers.

      Moreover, it can give you an idea of how people view your brand. Unfortunately, not all engagement will be positive – you might find comments from disgruntled customers from time to time. However, these can also help you learn about areas where your audience thinks you could improve.

      Engagement is about visibility, too. People who share your content are by extension spreading the word about your brand. This could lead to increased conversions or more widespread awareness of your company.

      Many social media platforms such as Twitter and TikTok use algorithms to determine what users see on their timelines. Thus, boosting your engagement could help you reach more people.

      7 Tools That Can Help Boost Your Social Media Engagement

      You may know that social media engagement is important, but knowing exactly how to improve your brand’s performance in this area can be tricky. Here are seven effective tools to help you get started.

      1. Canva

      The interface for the media-editing program Canva.

      Images are an essential part of your social media presence. Therefore, you’ll want to make sure to take high-quality photos and optimize their effectiveness through careful editing. If you’re looking for an intuitive tool that helps you design stunning images, consider using Canva.

      This tool is not only easy to use, but it also offers a wealth of features to help support your media creation, including:

      • Built-in templates geared towards the best layouts for individual platforms
      • A cohesive Brand Kit feature to keep your posts consistent with colors, fonts, and brand logos
      • Seamless collaboration features for small or large teams

      Stunning designs are more likely to catch your users’ eyes. More importantly, they can help you boost engagement. This is especially true if you use this opportunity to include exciting Calls To Action (CTA).

      If you’re looking to get the most out of Canva, we recommend that you use your designs across all of your platforms. This way, you’re providing a more seamless transition into new graphics for your customers. This can also contribute to consistency in your branding.

      Pricing: Canva offers a powerful free version. If you want all the features it has to offer, you can start with the Pro plan at $119.99 per year. You can also request an enterprise-level quote if you’re running a large team.

      2. Revive Old Posts

      A sample dashboard for Revive Old Posts.

      Content creation can be a challenging process. Sometimes it’s because you’re trying to keep up with the latest trends, but other times it’s because you’re struggling to produce enough posts to keep your page updated. If you want to alleviate some of this pressure, Revive Old Posts is definitely worth your consideration.

      As you can probably guess, this tool can help you make the most of your old content, by ensuring that it gets all the exposure that it deserves. With the help of this plugin, you can give your audience more opportunities to engage.

      Here are some impressive features offered by Revive Old Posts:

      • Automatic addition of optimized hashtags
      • The option to share posts instantly when you publish them on your website
      • Works for pages, posts, media, and custom posts to encompass nearly all types of content

      This plugin can also help you free up some time. Without the need to constantly update your social media pages, you can focus on other areas of your business. Alternatively, you might just find that you can polish your new content even more.

      You might want to consider using Revive Old Posts selectively, based on the platform. For example, if you know that your photo posts perform better on Instagram, you can focus on recycling that content for that specific account.

      Pricing: Plans for Revive Old Posts begin at $75 for a single site. This will give you five feeds and up to 50 shared accounts.

      3. CoSchedule

      A sample calendar made using CoSchedule.

      When it comes to social media, scheduling can be a big deal. After all, an abandoned or even slow-to-update profile doesn’t attract customers. That’s why a tool like CoSchedule can help.

      CoSchedule is a scheduling software made specifically for marketers. It can help organize your posting calendar with color-coded posts. Whether you’re running a platform-wide campaign or just hoping to polish your email strategy, this program can centralize your work and make sure you always have content planned.

      However, it’s a lot more than just a simple calendar. Here are a few other ways that CoSchedule can help you boost your engagement metrics:

      • Social publishing automation based on your custom plan
      • The ability to organize calendars around different teams or platforms
      • Stores assets and files to ensure that you include robust content for every post

      Consistent posting is key to increased engagement. Not only can CoSchedule help you keep this up, but it can also take some of the work off your plate by automating the process. You can also collaborate with other creators.

      If you choose to use CoSchedule, we highly recommend that you make use of the content progress function. This way, you’ll always know if you’re ready to publish a new post. This can be especially useful for large-scale marketing efforts.

      Pricing: For $29 per person, per month, you can gain access to all of CoSchedule’s features. If you’re interested in the full marketing suite, you will need to contact sales for a quote.

      Want more social media tips in your inbox?

      Click below to sign up for more how-to’s and tutorials just like this one, delivered to your inbox.

      marketing tips

      4. Sniply

      The sample interface for Sniply.

      For most social media platforms, you have to contend with a certain word count. This means every character counts. This can be tricky if you’re using long, complicated URLs. If you’re looking to get the most value out of a short post, we recommend using Sniply.

      Sniply is a URL shortener at its core. However, it can also help you streamline a customized link to serve a specific CTA. As such, you can achieve more brand consistency in a much cleaner way.

      In addition, Sniply has a few other impressive functions, such as:

      • The ability to embed URLs in the form of buttons, text, form, or images for the most natural integration
      • Engagement options ranging from links to email list sign-ups
      • Individual tracking metrics to see how each link’s post is performing

      Sniply also makes it easy for users to engage. Complex links can scare people away. However, short, neat URLs may help your audience feel more comfortable sharing your posts.

      Pricing: The basic option begins at $29 per month for up to two brand profiles, one team member, and 5,000 clicks. The Pro plan comes in at $79 per month and gets you six brand profiles, three team members, and 20,000 clicks.

      5. Woorise

      An example of a giveaway made with Woorise.

      If you want your users to engage with your brand, you may need to go beyond traditional CTAs. Providing users with more interactive content such as giveaways can get them more invested – especially if engagement metrics such as likes and shares are part of the entry conditions.

      This is why we recommend Woorise. This tool comes with several functions, but we’re particularly impressed with its giveaway functionality. It offers a ton of ways to create a stellar contest that will get your audience interacting with you, such as:

      • Require combinations of specific engagement activities such as follows and comments for users to enter
      • Customize the sign-up page with everything from the color palette to photos
      • Embed widgets on social media to ensure that all of your audiences have a chance to sign up

      Simply put, giveaways work because users get invested. Not only will this boost your engagement metrics, but it can also encourage them to follow your brand for more opportunities.

      To get the most out of Woorise, consider doing small giveaways regularly. These provide more chances for people to share your content. As a bonus, these can also be a lot easier on your budget.

      Pricing: You can get started with a free version that has limited functionality. As for the paid version, you can start at $23 per month for one site with unlimited campaigns and 2,000 entries per month. If you want to bump that up to 5,000 entries, you can choose the Grow plan at $39 per month.

      6. Social Searcher

      An example of multiple tweets collected by Social Searcher.

      Part of successfully boosting engagement is knowing what your audience is talking about. This doesn’t just apply to trends, either: knowing how your users feel about your brand, in general, is crucial to creating the content that they want to interact with. That’s why we’re fans of Social Searcher.

      Social Searcher is – as the name suggests – a search engine geared towards social media platforms. It can help you monitor what people are saying about you across multiple sites at once. This applies to individual sites or a more generalized report.

      Here are a few stand-out features that make Social Searcher worth your consideration:

      • Tracking of tagged posts, as well as ones that mention your brand
      • Analysis comparing positive mentions against negative ones
      • Alerts about top hashtags in your niche to help your content stay timely

      Generally speaking, users will only interact with posts that are relevant to them. Social Searcher can help you boost engagement by pinpointing these topics and responding accordingly. You might also be able to use it to learn more about your audience.

      Our favorite thing about this tool is the universal dashboard. You can get information on users across several different sites, allowing you to focus on specific campaigns while also understanding the bigger picture.

      Pricing: You can use a limited version of this tool for free. Paid plans start at 3.49 Euros per month (roughly $4.04). They also offer a 14-day free trial, so you can give it a shot before you commit.

      7. Easy Affiliate

      A sample interface for the Easy Affiliate plugin.

      If you aren’t using an affiliate program for your brand, now might be the time to start. Working with influencers can be a natural and affordable way to reach new audiences on social media. Their work on these platforms can in turn lead curious users to your profile for more information.

      However, managing affiliates across different social media accounts can be tough. If you’re looking for a tool to help you out, we recommend Easy Affiliate. This resource enables you to manage, track, and reward the influencers that boost your engagement metrics the most.

      Easy Affiliate uses several powerful features to help you organize your program, including:

      • The opportunity to provide affiliates with full feature links and banners for a consistent brand experience
      • An easy-to-understand dashboard that helps you see your top performers
      • A simple payout system to accurately compensate those who can increase your engagement the most

      Influencers are more than just a way to advertise your site – they can also organically boost brand awareness. Users who trust these affiliates might in turn trust your brand and seek out your content. This can help you increase your engagement and boost your conversion rate.

      Pricing: Let’s take a look at Easy Affiliate’s pricing. The basic plan starts at $99.50. This will give you all the features for a single site, including unlimited affiliates and a full tracking system.

      Getting by With a Little Help From Our Friends

      Social media engagement is essential for modern brands. User interactions can be the difference between a low profile and a huge following. Fortunately, boosting your engagement metrics can be easy with the help of a few key tools.

      In this article, we showed you seven different tools. Some of them can appeal to users directly, while others can provide crucial insights into your audience. By using one – or even a few! – you can start taking full advantage of your brand’s potential.

      Get Social and Grow Your Business with DreamHost

      Our experts will help create a powerful social media strategy and level up your execution so you can focus on running your business.

      social media marketing



      Source link

      How To Build a Media Processing API in Node.js With Express and FFmpeg.wasm


      The author selected the Electronic Frontier Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Handling media assets is becoming a common requirement of modern back-end services. Using dedicated, cloud-based solutions may help when you’re dealing with massive scale or performing expensive operations, such as video transcoding. However, the extra cost and added complexity may be hard to justify when all you need is to extract a thumbnail from a video or check that user-generated content is in the correct format. Particularly at a smaller scale, it makes sense to add media processing capability directly to your Node.js API.

      In this guide, you will build a media API in Node.js with Express and ffmpeg.wasm — a WebAssembly port of the popular media processing tool. You’ll build an endpoint that extracts a thumbnail from a video as an example. You can use the same techniques to add other features supported by FFmpeg to your API.

      When you’re finished, you will have a good grasp on handling binary data in Express and processing them with ffmpeg.wasm. You’ll also handle requests made to your API that cannot be processed in parallel.

      Prerequisites

      To complete this tutorial, you will need:

      This tutorial was verified with Node v16.11.0, npm v7.15.1, express v4.17.1, and ffmpeg.wasm v0.10.1.

      Step 1 — Setting Up the Project and Creating a Basic Express Server

      In this step, you will create a project directory, initialize Node.js and install ffmpeg, and set up a basic Express server.

      Start by opening the terminal and creating a new directory for the project:

      Navigate to the new directory:

      Use npm init to create a new package.json file. The -y parameter indicates that you’re happy with the default settings for the project.

      Finally, use npm install to install the packages required to build the API. The --save flag indicates that you wish to save those as dependencies in the package.json file.

      • npm install --save @ffmpeg/ffmpeg @ffmpeg/core express cors multer p-queue

      Now that you have installed ffmpeg, you’ll set up a web server that responds to requests using Express.

      First, open a new file called server.mjs with nano or your editor of choice:

      The code in this file will register the cors middleware which will permit requests made from websites with a different origin. At the top of the file, import the express and cors dependencies:

      server.mjs

      import express from 'express';
      import cors from 'cors';
      

      Then, create an Express app and start the server on the port :3000 by adding the following code below the import statements:

      server.mjs

      ...
      const app = express();
      const port = 3000;
      
      app.use(cors());
      
      app.listen(port, () => {
          console.log(`[info] ffmpeg-api listening at http://localhost:${port}`)
      });
      

      You can start the server by running the following command:

      You’ll see the following output:

      Output

      [info] ffmpeg-api listening at http://localhost:3000

      When you try loading http://localhost:3000 in your browser, you’ll see Cannot GET /. This is Express telling you it is listening for requests.

      With your Express server now set up, you’ll create a client to upload the video and make requests to your Express server.

       Step 2 — Creating a Client and Testing the Server

      In this section, you’ll create a web page that will let you select a file and upload it to the API for processing.

      Start by opening a new file called client.html:

      In your client.html file, create a file input and a Create Thumbnail button. Below, add an empty <div> element to display errors and an image that will show the thumbnail that the API sends back. At the very end of the <body> tag, load a script called client.js. Your final HTML template should look as follows:

      client.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Create a Thumbnail from a Video</title>
          <style>
              #thumbnail {
                  max-width: 100%;
              }
          </style>
      </head>
      <body>
          <div>
              <input id="file-input" type="file" />
              <button id="submit">Create Thumbnail</button>
              <div id="error"></div>
              <img id="thumbnail" />
          </div>
          <script src="https://www.digitalocean.com/community/tutorials/client.js"></script>
      </body>
      </html>
      

      Note that each element has a unique id. You’ll need them when referring to the elements from the client.js script. The styling on the #thumbnail element is there to ensure that the image fits on the screen when it loads.

      Save the client.html file and open client.js:

      In your client.js file, start by defining variables that store references to your HTML elements you created:

      client.js

      const fileInput = document.querySelector('#file-input');
      const submitButton = document.querySelector('#submit');
      const thumbnailPreview = document.querySelector('#thumbnail');
      const errorDiv = document.querySelector('#error');
      

      Then, attach a click event listener to the submitButton variable to check whether you’ve selected a file:

      client.js

      ...
      submitButton.addEventListener('click', async () => {
          const { files } = fileInput;
      }
      

      Next, create a function showError() that will output an error message when a file is not selected. Add the showError() function above your event listener:

      client.js

      const fileInput = document.querySelector('#file-input');
      const submitButton = document.querySelector('#submit');
      const thumbnailPreview = document.querySelector('#thumbnail');
      const errorDiv = document.querySelector('#error');
      
      function showError(msg) {
          errorDiv.innerText = `ERROR: ${msg}`;
      }
      
      submitButton.addEventListener('click', async () => {
      ...
      

      Now, you will build a function createThumbnail() that will make a request to the API, send the video, and receive a thumbnail in response. At the top of your client.js file, define a new constant with the URL to a /thumbnail endpoint:

      const API_ENDPOINT = 'http://localhost:3000/thumbnail';
      
      const fileInput = document.querySelector('#file-input');
      const submitButton = document.querySelector('#submit');
      const thumbnailPreview = document.querySelector('#thumbnail');
      const errorDiv = document.querySelector('#error');
      ...
      

      You will define and use the /thumbnail endpoint in your Express server.

      Next, add the createThumbnail() function below your showError() function:

      client.js

      ...
      function showError(msg) {
          errorDiv.innerText = `ERROR: ${msg}`;
      }
      
      async function createThumbnail(video) {
      
      }
      ...
      

      Web APIs frequently use JSON to transfer structured data from and to the client. To include a video in a JSON, you would have to encode it in base64, which would increase its size by about 30%. You can avoid this by using multipart requests instead. Multipart requests allow you to transfer structured data including binary files over http without the unnecessary overhead. You can do this using the FormData() constructor function.

      Inside the createThumbnail() function, create an instance of FormData and append the video file to the object. Then make a POST request to the API endpoint using the Fetch API with the FormData() instance as the body. Interpret the response as a binary file (or blob) and convert it to a data URL so that you can assign it to the <img> tag you created earlier.

      Here’s the full implementation of createThumbnail():

      client.js

      ...
      async function createThumbnail(video) {
          const payload = new FormData();
          payload.append('video', video);
      
          const res = await fetch(API_ENDPOINT, {
              method: 'POST',
              body: payload
          });
      
          if (!res.ok) {
              throw new Error('Creating thumbnail failed');
          }
      
          const thumbnailBlob = await res.blob();
          const thumbnail = await blobToDataURL(thumbnailBlob);
      
          return thumbnail;
      }
      ...
      

      You’ll notice createThumbnail() has the function blobToDataURL() in its body. This is a helper function that will convert a blob to a data URL.

      Above your createThumbnail() function, create the function blobDataToURL() that returns a promise:

      client.js

      ...
      async function blobToDataURL(blob) {
          return new Promise((resolve, reject) => {
              const reader = new FileReader();
              reader.onload = () => resolve(reader.result);
              reader.onerror = () => reject(reader.error);
              reader.onabort = () => reject(new Error("Read aborted"));
              reader.readAsDataURL(blob);
          });
      }
      ...
      

      blobToDataURL() uses FileReader to read the contents of the binary file and format it as a data URL.

      With the createThumbnail() and showError() functions now defined, you can use them to finish implementing the event listener:

      client.js

      ...
      submitButton.addEventListener('click', async () => {
          const { files } = fileInput;
      
          if (files.length > 0) {
              const file = files[0];
              try {
                  const thumbnail = await createThumbnail(file);
                  thumbnailPreview.src = thumbnail;
              } catch(error) {
                  showError(error);
              }
          } else {
              showError('Please select a file');
          }
      });
      

      When a user clicks on the button, the event listener will pass the file to the createThumbnail() function. If successful, it will assign the thumbnail to the <img> element you created earlier. In case the user doesn’t select a file or the request fails, it will call the showError() function to display an error.

      At this point, your client.js file will look like the following:

      client.js

      const API_ENDPOINT = 'http://localhost:3000/thumbnail';
      
      const fileInput = document.querySelector('#file-input');
      const submitButton = document.querySelector('#submit');
      const thumbnailPreview = document.querySelector('#thumbnail');
      const errorDiv = document.querySelector('#error');
      
      function showError(msg) {
          errorDiv.innerText = `ERROR: ${msg}`;
      }
      
      async function blobToDataURL(blob) {
          return new Promise((resolve, reject) => {
              const reader = new FileReader();
              reader.onload = () => resolve(reader.result);
              reader.onerror = () => reject(reader.error);
              reader.onabort = () => reject(new Error("Read aborted"));
              reader.readAsDataURL(blob);
          });
      }
      
      async function createThumbnail(video) {
          const payload = new FormData();
          payload.append('video', video);
      
          const res = await fetch(API_ENDPOINT, {
              method: 'POST',
              body: payload
          });
      
          if (!res.ok) {
              throw new Error('Creating thumbnail failed');
          }
      
          const thumbnailBlob = await res.blob();
          const thumbnail = await blobToDataURL(thumbnailBlob);
      
          return thumbnail;
      }
      
      submitButton.addEventListener('click', async () => {
          const { files } = fileInput;
      
          if (files.length > 0) {
              const file = files[0];
      
              try {
                  const thumbnail = await createThumbnail(file);
                  thumbnailPreview.src = thumbnail;
              } catch(error) {
                  showError(error);
              }
          } else {
              showError('Please select a file');
          }
      });
      

      Start the server again by running:

      With your client now set up, uploading the video file here will result in receiving an error message. This is because the /thumbnail endpoint is not built yet. In the next step, you’ll create the /thumbnail endpoint in Express to accept the video file and create the thumbnail.

       Step 3 — Setting Up an Endpoint to Accept Binary Data

      In this step, you will set up a POST request for the /thumbnail endpoint and use middleware to accept multipart requests.

      Open server.mjs in an editor:

      Then, import multer at the top of the file:

      server.mjs

      import express from 'express';
      import cors from 'cors';
      import multer from 'multer';
      ...
      

      Multer is a middleware that processes incoming multipart/form-data requests before passing them to your endpoint handler. It extracts fields and files from the body and makes them available as an array on the request object in Express. You can configure where to store the uploaded files and set limits on file size and format.

      After importing it, initialize the multer middleware with the following options:

      server.mjs

      ...
      const app = express();
      const port = 3000;
      
      const upload = multer({
          storage: multer.memoryStorage(),
          limits: { fileSize: 100 * 1024 * 1024 }
      });
      
      app.use(cors());
      ...
      

      The storage option lets you choose where to store the incoming files. Calling multer.memoryStorage() will initialize a storage engine that keeps files in Buffer objects in memory as opposed to writing them to disk. The limits option lets you define various limits on what files will be accepted. Set the fileSize limit to 100MB or a different number that matches your needs and the amount of memory available on your server. This will prevent your API from crashing when the input file is too big.

      Note: Due to the limitations of WebAssembly, ffmpeg.wasm cannot handle input files over 2GB in size.

      Next, set up the POST /thumbnail endpoint itself:

      server.mjs

      ...
      app.use(cors());
      
      app.post('/thumbnail', upload.single('video'), async (req, res) => {
          const videoData = req.file.buffer;
      
          res.sendStatus(200);
      });
      
      app.listen(port, () => {
          console.log(`[info] ffmpeg-api listening at http://localhost:${port}`)
      });
      

      The upload.single('video') call will set up a middleware for that endpoint only that will parse the body of a multipart request that includes a single file. The first parameter is the field name. It must match the one you gave to FormData when creating the request in client.js. In this case, it’s video. multer will then attach the parsed file to the req parameter. The content of the file will be under req.file.buffer.

      At this point, the endpoint doesn’t do anything with the data it receives. It acknowledges the request by sending an empty 200 response. In the next step, you’ll replace that with the code that extracts a thumbnail from the video data received.

      In this step, you’ll use ffmpeg.wasm to extract a thumbnail from the video file received by the POST /thumbnail endpoint.

      ffmpeg.wasm is a pure WebAssembly and JavaScript port of FFmpeg. Its main goal is to allow running FFmpeg directly in the browser. However, because Node.js is built on top of V8 — Chrome’s JavaScript engine — you can use the library on the server too.

      The benefit of using a native port of FFmpeg over a wrapper built on top of the ffmpeg command is that if you’re planning to deploy your app with Docker, you don’t have to build a custom image that includes both FFmpeg and Node.js. This will save you time and reduce the maintenance burden of your service.

      Add the following import to the top of server.mjs:

      server.mjs

      import express from 'express';
      import cors from 'cors';
      import multer from 'multer';
      import { createFFmpeg } from '@ffmpeg/ffmpeg';
      ...
      

      Then, create an instance of ffmpeg.wasm and start loading the core:

      server.mjs

      ...
      import { createFFmpeg } from '@ffmpeg/ffmpeg';
      
      const ffmpegInstance = createFFmpeg({ log: true });
      let ffmpegLoadingPromise = ffmpegInstance.load();
      
      const app = express();
      ...
      

      The ffmpegInstance variable holds a reference to the library. Calling ffmpegInstance.load() starts loading the core into memory asynchronously and returns a promise. Store the promise in the ffmpegLoadingPromise variable so that you can check whether the core has loaded.

      Next, define the following helper function that will use fmpegLoadingPromise to wait for the core to load in case the first request arrives before it’s ready:

      server.mjs

      ...
      let ffmpegLoadingPromise = ffmpegInstance.load();
      
      async function getFFmpeg() {
          if (ffmpegLoadingPromise) {
              await ffmpegLoadingPromise;
              ffmpegLoadingPromise = undefined;
          }
      
          return ffmpegInstance;
      }
      
      const app = express();
      ...
      

      The getFFmpeg() function returns a reference to the library stored in the ffmpegInstance variable. Before returning it, it checks whether the library has finished loading. If not, it will wait until ffmpegLoadingPromise resolves. In case the first request to your POST /thumbnail endpoint arrives before ffmpegInstance is ready to use, your API will wait and resolve it when it can rather than rejecting it.

      Now, implement the POST /thumbnail endpoint handler. Replace res.sendStatus(200); at the end of the end of the function with a call to getFFmpeg to get a reference to ffmpeg.wasm when it’s ready:

      server.mjs

      ...
      app.post('/thumbnail', upload.single('video'), async (req, res) => {
          const videoData = req.file.buffer;
      
          const ffmpeg = await getFFmpeg();
      });
      ...
      

      ffmpeg.wasm works on top of an in-memory file system. You can read and write to it using ffmpeg.FS. When running FFmpeg operations, you will pass virtual file names to the ffmpeg.run function as an argument the same way as you would when working with the CLI tool. Any output files created by FFmpeg will be written to the file system for you to retrieve.

      In this case, the input file is a video. The output file will be a single PNG image. Define the following variables:

      server.mjs

      ...
          const ffmpeg = await getFFmpeg();
      
          const inputFileName = `input-video`;
          const outputFileName = `output-image.png`;
          let outputData = null;
      });
      ...
      

      The file names will be used on the virtual file system. outputData is where you’ll store the thumbnail when it’s ready.

      Call ffmpeg.FS() to write the video data to the in-memory file system:

      server.mjs

      ...
          let outputData = null;
      
          ffmpeg.FS('writeFile', inputFileName, videoData);
      });
      ...
      

      Then, run the FFmpeg operation:

      server.mjs

      ...
          ffmpeg.FS('writeFile', inputFileName, videoData);
      
          await ffmpeg.run(
              '-ss', '00:00:01.000',
              '-i', inputFileName,
              '-frames:v', '1',
              outputFileName
          );
      });
      ...
      

      The -i parameter specifies the input file. -ss seeks to the specified time (in this case, 1 second from the beginning of the video). -frames:v limits the number of frames that will be written to the output (a single frame in this scenario). outputFileName at the end indicates where will FFmpeg write the output.

      After FFmpeg exits, use ffmpeg.FS() to read the data from the file system and delete both the input and output files to free up memory:

      server.mjs

      ...
          await ffmpeg.run(
              '-ss', '00:00:01.000',
              '-i', inputFileName,
              '-frames:v', '1',
              outputFileName
          );
      
          outputData = ffmpeg.FS('readFile', outputFileName);
          ffmpeg.FS('unlink', inputFileName);
          ffmpeg.FS('unlink', outputFileName);
      });
      ...
      

      Finally, dispatch the output data in the body of the response:

      server.mjs

      ...
          ffmpeg.FS('unlink', outputFileName);
      
          res.writeHead(200, {
              'Content-Type': 'image/png',
              'Content-Disposition': `attachment;filename=${outputFileName}`,
              'Content-Length': outputData.length
          });
          res.end(Buffer.from(outputData, 'binary'));
      });
      ...
      

      Calling res.writeHead() dispatches the response head. The second parameter includes custom http headers) with information about the data in the body of the request that will follow. The res.end() function sends the data from its first argument as the body of the request and finalizes the request. The outputData variable is a raw array of bytes as returned by ffmpeg.FS(). Passing it to Buffer.from() initializes a Buffer to ensure the binary data will be handled correctly by res.end().

      At this point, your POST /thumbnail endpoint implementation should look like this:

      server.mjs

      ...
      app.post('/thumbnail', upload.single('video'), async (req, res) => {
          const videoData = req.file.buffer;
      
          const ffmpeg = await getFFmpeg();
      
          const inputFileName = `input-video`;
          const outputFileName = `output-image.png`;
          let outputData = null;
      
          ffmpeg.FS('writeFile', inputFileName, videoData);
      
          await ffmpeg.run(
              '-ss', '00:00:01.000',
              '-i', inputFileName,
              '-frames:v', '1',
              outputFileName
          );
      
          outputData = ffmpeg.FS('readFile', outputFileName);
          ffmpeg.FS('unlink', inputFileName);
          ffmpeg.FS('unlink', outputFileName);
      
          res.writeHead(200, {
              'Content-Type': 'image/png',
              'Content-Disposition': `attachment;filename=${outputFileName}`,
              'Content-Length': outputData.length
          });
          res.end(Buffer.from(outputData, 'binary'));
      });
      ...
      

      Aside from the 100MB file limit for uploads, there’s no input validation or error handling. When ffmpeg.wasm fails to process a file, reading the output from the virtual file system will fail and prevent the response from being sent. For the purposes of this tutorial, wrap the implementation of the endpoint in a try-catch block to handle that scenario:

      server.mjs

      ...
      app.post('/thumbnail', upload.single('video'), async (req, res) => {
          try {
              const videoData = req.file.buffer;
      
              const ffmpeg = await getFFmpeg();
      
              const inputFileName = `input-video`;
              const outputFileName = `output-image.png`;
              let outputData = null;
      
              ffmpeg.FS('writeFile', inputFileName, videoData);
      
              await ffmpeg.run(
                  '-ss', '00:00:01.000',
                  '-i', inputFileName,
                  '-frames:v', '1',
                  outputFileName
              );
      
              outputData = ffmpeg.FS('readFile', outputFileName);
              ffmpeg.FS('unlink', inputFileName);
              ffmpeg.FS('unlink', outputFileName);
      
              res.writeHead(200, {
                  'Content-Type': 'image/png',
                  'Content-Disposition': `attachment;filename=${outputFileName}`,
                  'Content-Length': outputData.length
              });
              res.end(Buffer.from(outputData, 'binary'));
          } catch(error) {
              console.error(error);
              res.sendStatus(500);
          }
      ...
      });
      

      Secondly, ffmpeg.wasm cannot handle two requests in parallel. You can try this yourself by launching the server:

      • node --experimental-wasm-threads server.mjs

      Note the flag required for ffmpeg.wasm to work. The library depends on WebAssembly threads and bulk memory operations. These have been in V8/Chrome since 2019. However, as of Node.js v16.11.0, WebAssembly threads remain behind a flag in case there might be changes before the proposal is finalised. Bulk memory operations also require a flag in older versions of Node. If you’re running Node.js 15 or lower, add --experimental-wasm-bulk-memory as well.

      The output of the command will look like this:

      Output

      [info] use ffmpeg.wasm v0.10.1 [info] load ffmpeg-core [info] loading ffmpeg-core [info] fetch ffmpeg.wasm-core script from @ffmpeg/core [info] ffmpeg-api listening at http://localhost:3000 [info] ffmpeg-core loaded

      Open client.html in a web browser and select a video file. When you click the Create Thumbnail button, you should see the thumbnail appear on the page. Behind the scenes, the site uploads the video to the API, which processes it and responds with the image. However, when you click the button repeatedly in quick succession, the API will handle the first request. The subsequent requests will fail:

      Output

      Error: ffmpeg.wasm can only run one command at a time at Object.run (.../ffmpeg-api/node_modules/@ffmpeg/ffmpeg/src/createFFmpeg.js:126:13) at file://.../ffmpeg-api/server.mjs:54:26 at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:95:5)

      In the next section, you’ll learn how to deal with concurrent requests.

      Step 5 — Handling Concurrent Requests

      Since ffmpeg.wasm can only execute a single operation at a time, you’ll need a way of serializing requests that come in and processing them one at a time. In this scenario, a promise queue is a perfect solution. Instead of starting to process each request right away, it will be queued up and processed when all the requests that arrived before it have been handled.

      Open server.mjs in your preferred editor:

      Import p-queue at the top of server.mjs:

      server.mjs

      import express from 'express';
      import cors from 'cors';
      import { createFFmpeg } from '@ffmpeg/ffmpeg';
      import PQueue from 'p-queue';
      ...
      

      Then, create a new queue at the top of server.mjs file under the variable ffmpegLoadingPromise:

      server.mjs

      ...
      const ffmpegInstance = createFFmpeg({ log: true });
      let ffmpegLoadingPromise = ffmpegInstance.load();
      
      const requestQueue = new PQueue({ concurrency: 1 });
      ...
      

      In the POST /thumbnail endpoint handler, wrap the calls to ffmpeg in a function that will be queued up:

      server.mjs

      ...
      app.post('/thumbnail', upload.single('video'), async (req, res) => {
          try {
              const videoData = req.file.buffer;
      
              const ffmpeg = await getFFmpeg();
      
              const inputFileName = `input-video`;
              const outputFileName = `thumbnail.png`;
              let outputData = null;
      
              await requestQueue.add(async () => {
                  ffmpeg.FS('writeFile', inputFileName, videoData);
      
                  await ffmpeg.run(
                      '-ss', '00:00:01.000',
                      '-i', inputFileName,
                      '-frames:v', '1',
                      outputFileName
                  );
      
                  outputData = ffmpeg.FS('readFile', outputFileName);
                  ffmpeg.FS('unlink', inputFileName);
                  ffmpeg.FS('unlink', outputFileName);
              });
      
              res.writeHead(200, {
                  'Content-Type': 'image/png',
                  'Content-Disposition': `attachment;filename=${outputFileName}`,
                  'Content-Length': outputData.length
              });
              res.end(Buffer.from(outputData, 'binary'));
          } catch(error) {
              console.error(error);
              res.sendStatus(500);
          }
      });
      ...
      

      Every time a new request comes in, it will only start processing when there’s nothing else queued up in front of it. Note that the final sending of the response can happen asynchronously. Once the ffmpeg.wasm operation finishes running, another request can start processing while the response goes out.

      To test that everything works as expected, start up the server again:

      • node --experimental-wasm-threads server.mjs

      Open the client.html file in your browser and try uploading a file.

      A screenshot of client.html with a thumbnail loaded

      With the queue in place, the API will now respond every time. The requests will be handled sequentially in the order in which they arrive.

      Conclusion

      In this article, you built a Node.js service that extracts a thumbnail from a video using ffmpeg.wasm. You learned how to upload binary data from the browser to your Express API using multipart requests and how to process media with FFmpeg in Node.js without relying on external tools or having to write data to disk.

      FFmpeg is an incredibly versatile tool. You can use the knowledge from this tutorial to take advantage of any features that FFmpeg supports and use them in your project. For example, to generate a three-second GIF, change the ffmpeg.run call to this on the POST /thumbnail endpoint:

      server.mjs

      ...
      await ffmpeg.run(
          '-y',
          '-t', '3',
          '-i', inputFileName,
          '-filter_complex', 'fps=5,scale=720:-1:flags=lanczos[x];[x]split[x1][x2];[x1]palettegen[p];[x2][p]paletteuse',
          '-f', 'gif',
          outputFileName
      );
      ...
      

      The library accepts the same parameters as the original ffmpeg CLI tool. You can use the official documentation to find a solution for your use case and test it quickly in the terminal.

      Thanks to ffmpeg.wasm being self-contained, you can dockerize this service using the stock Node.js base images and scale your service up by keeping multiple nodes behind a load balancer. Follow the tutorial How To Build a Node.js Application with Docker to learn more.

      If your use case requires performing more expensive operations, such as transcoding large videos, make sure that you run your service on machines with enough memory to store them. Due to current limitations in WebAssembly, the maximum input file size cannot exceed 2GB, although this might change in the future.

      Additionally, ffmpeg.wasm cannot take advantage of some x86 assembly optimizations from the original FFmpeg codebase. That means some operations can take a long time to finish. If that’s the case, consider whether this is the right solution for your use case. Alternatively, make requests to your API asynchronous. Instead of waiting for the operation to finish, queue it up and respond with a unique ID. Create another endpoint that the clients can query to find out whether the processing ended and the output file is ready. Learn more about the asynchronous request-reply pattern for REST APIs and how to implement it.



      Source link

      Social Media Marketing for Organic Engagement & Brand Trust


      Video

      About the Talk

      It’s no secret that social media for businesses has become a pay-to-play landscape. Yet, no amount of paid ads can generate the push authenticity can. Regardless of your budget, there is a way to authentically engage and interact with your customers via social media platforms. Explore organic strategies that help you speak more authentically to your followers while building trust in your brand.

      What You’ll Learn

      • Which metrics really matter (Say goodbye to vanity metrics!)
      • Engagement optimization tactics
      • Rooting intentions in values

      Resources

      Slides

      About the Presenter

      Kirby is the Senior Social Media Manager at DigitalOcean. She was also the company Culture Champion award winner in 2020. Her work is driven by making a positive impact on others and doing everything with love at our core.



      Source link