This is the era of social media. We see uncountable short form content these days, also known as reels or shorts or maybe stories and statuses. But have you ever wondered what happens when you press that upload or post button?
It might seem just a simple upload to the database and then distribution of that to the bigger mass - NO! not just it. A bunch of logic goes behind the whole process and let's decode it ☕

The journey

  • The user creates a content, edits it and then goes to upload.
  • When the user uploads the reel, it goes through a pipeline which has some set of compression processes.
  • The backend handles the video and compress it down to a desired bitrate for which the size of the reel is now reduced from the originally uploaded content. This size difference is massive!

For reference let's say you upload a video of 90 MB, and it is going through the pipeline. Now after the process is finished the video is now about 25-30 MB, incredible isn't it!

The average percentage compression from the above example comes down as-

                   90-25
 % compression =   ------ x 100 % = 72.222%
                    90
  • After the compression it simply follows the same method of uploading to the database and then comes to our feed.
Note: This is a simple overview but the market leading social medias are designed in such a way that it stores different version on top of compression (and their compression method can differ than mine) and shows us the best version according to our internet speed!
We will save that part for another blog 😉

Breakdown of compression

I will be explaining just the compression part with two packages called ffmpeg-static and fluent-ffmpeg you can easily find it in the npm library. Again, it's just an overview and you can definitely implement this method for video compression or any tool, it is not necessarily what Instagram or YouTube uses. Atleast it gets the job done 👀

Now let's get going to some technical stuff ☕

This part is based on pure backend, and I am using NodeJs and ExpressJs for the backend server. Before going to raw code let's just analyze the process a bit more in depth.

My method is used for compression and as well as storing the video in a dedicated database after compression and returns with the compressed video URL. The database part can vary since I have used a simple approach using firebase for demonstration.

What goes inside the function

  • We accept the video from the request body.
const inputFilePath = req.file.path;
  • The function is configured to accept both the video and the firebase endpoint where to store the compressed video.
async function compressAndUploadVideo(inputFilePath, firebaseStoragePath){
//function body
} 
  • There is a package "multer", which is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. This will create a temporary folder where the file from request body gets stored for processing.
const upload = multer({ dest: 'uploads/' }); //dest: destination, refer package documentation
.
.
.
app.post('/endpoint', upload.single('video'), async (req, res) => {
//function body
}
  • Before the compression begins the ffmpeg path has to be specified and this is done by the ffmpeg-static package.
const ffmpegPath = require('ffmpeg-static');
const ffmpeg = require('fluent-ffmpeg');

// Set the FFmpeg path from ffmpeg-static
ffmpeg.setFfmpegPath(ffmpegPath);
  • Now the major compression is done by the fluent-ffmpeg package, which has it's own set of rules to modify the codec and bitrate of the video. By lowering down the bitrate the size of the video decreases which turns out to be video compression and makes it suitable to be uploaded to the database and won't cause trouble in playing in any device.
await new Promise((resolve, reject) => {
  ffmpeg(inputFilePath)
    .output(outputFilePath)
    .videoCodec('libx264')
    .audioCodec('aac')
    .on('end', () => resolve())
    .on('error', (err) => reject(new Error(`Error compressing video: ${err.message}`)))
    .run();
  });
  • The "fs" and "path" modules play a crucial role to take the video from the response and process it in the API with some file based operations like reading and writing and stores it in a temporary location for uploading to firebase or any other database
const outputFilePath = path.join(__dirname, 'filename.mp4');
.
.
.
// Upload to Firebase Storage
  const storageFile = bucket.file(firebaseStoragePath);
  await storageFile.save(fs.readFileSync(outputFilePath), {
   contentType: 'video/mp4'
  });
.
.
.
 // Clean up temporary file
  if (fs.existsSync(outputFilePath)) {
    fs.unlinkSync(outputFilePath);
  }

The entire function

const fs = require('fs');
const path = require('path');
const ffmpegPath = require('ffmpeg-static');
const ffmpeg = require('fluent-ffmpeg');
const admin = require('firebase-admin');

// Set the FFmpeg path from ffmpeg-static
ffmpeg.setFfmpegPath(ffmpegPath);

const bucket = admin.storage().bucket();

/**
 * Compresses a video file using FFmpeg, uploads it to Firebase Storage, and updates a Firestore document.
 * @param {string} inputFilePath - Path to the input video file.
 * @param {string} firebaseStoragePath - Path in Firebase Storage where the video will be uploaded.
 * @returns {Promise<string>} - The download URL of the uploaded video.
 */
async function compressAndUploadVideo(inputFilePath, firebaseStoragePath) {
    const outputFilePath = path.join(__dirname, 'compressed_video.mp4');

    try {
        // Compress the video using FFmpeg (fluent-ffmpeg)
        await new Promise((resolve, reject) => {
            ffmpeg(inputFilePath)
                .output(outputFilePath)
                .videoCodec('libx264')
                .audioCodec('aac')
                .on('end', () => resolve())
                .on('error', (err) => reject(new Error(`Error compressing video: ${err.message}`)))
                .run();
        });

        // Upload to Firebase Storage
        const storageFile = bucket.file(firebaseStoragePath);
        await storageFile.save(fs.readFileSync(outputFilePath), {
            contentType: 'video/mp4'
        });

        // Get the download URL
        const downloadURL = await storageFile.getSignedUrl({
            action: 'read',
            expires: '03-01-2500' // Set a long expiration date
        });

        return downloadURL[0];
    } catch (error) {
        console.error('Error compressing and uploading video:', error);
        throw error;
    } finally {
        // Clean up temporary file
        if (fs.existsSync(outputFilePath)) {
            fs.unlinkSync(outputFilePath);
        }
    }
}

module.exports = compressAndUploadVideo;

Now you can import the compressAndUploadVideo function and use it in the root as explained earlier and the compressed video will be ready at your provided output location.

If you have came across till here make sure to follow me and you can also check my github. I will be sharing more unique ideas with ☕. Comment down anything you feel to share, I am open to suggestions. Till then see ya'll ✌️

Author Of article : Swarup Das Read full article