2

I am trying to decrease the resolution of a video to under 500x500. I don't want to change it to exactly 500x500 because that would mess up video quality. So what I am trying to do is to decrease the resolution by 75% in a loop, and that loop would only stop when the video is under 500x500. In theory that would not be hard, but I can't seem to figure it out.

var vidwidth = 501; //Create variable and put it to 501
var vidheight = 501; //so that it won't go through the If Statement
fs.copyFile(filepath2, './media/media.mp4', (err: any) => { //Copy given file to directory
    console.log('filepath2 was copied to media.mp4'); //Log confirmation (Not appearing for some reason, but file is copied)
})
while (true) {
    getDimensions('./media/media.mp4').then(function (dimensions: any) { //Get dimensions of copied video
        var vidwidth = parseInt(dimensions.width)   //Parse to Int
        var vidheight = parseInt(dimensions.height) //and put in variables
    })
    ffmpeg('./media/media.mp4')                 //Call ffmpeg function with copied video path
        .output('./media/media.mp4')            //Set output to the same file so we can loop it
        .size('75%')                            //Reduce resolution by 75%
        .on('end', function() {                 //Log confirmation on end
            console.log('Finished processing'); //(Not appearing)
        })                                      //
        .run();                                 //Run function
    if (vidwidth < 500 && vidheight < 500) {    //Check if both the width and height is under 500px
        break;                                  //If true, break the loop and continue
    }
}

This is the current code I am using with comments. Basically what happens is it gets stuck in the while loop because the dimensions of the video won't change. Tested with console.log() line. I think that if I can fix the ffmpeg problem somehow it will all be fixed. I'd appreciate any help :)

PS: This is all made in typescript, then build into js using npx tsc

rioV8
  • 24,506
  • 3
  • 32
  • 49
WoJo
  • 500
  • 1
  • 3
  • 20
  • `copyFile` is asynchronous so the loop is reached before the copying is done, same goes for `getDimensions(...).then`. You can't just put asynchronous calls one after another like that and expect them to run in order. This is a basically a duplicate of https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call – ibrahim mahrir Jul 24 '20 at 13:36
  • @ibrahimmahrir understand how that might go wrong, but when I look in my file explorer, the file is actually copied. It might not be copied already before the loop takes place, but even then it should see it sometime in the loop, right? EDIT: I have changed `copyFile` to `copyFileSync` and nothing has changed. – WoJo Jul 24 '20 at 13:39
  • Yeah, this is a tough one. The loop actually freezes everything. Since javascript runs on one thread, it will be stuck handling the loop and the callbacks to `copyFile` and `then` will never be called, that's why you don't see the console log. `vidWidth` and `vidHeight` will never change either because the callback that changes them is never called because javascript is busy iterating the loop. I asked [**a similar question**](https://stackoverflow.com/q/41814068/9867451) years ago, the answer there may clear some confusion. – ibrahim mahrir Jul 24 '20 at 13:45
  • 1
    BTW, you don't need neither the loop nor `copyFile` for this kind of stuff, `ffmpeg` can handle this itself, I'll post an answer in a few moments – ibrahim mahrir Jul 24 '20 at 13:47
  • 1
    I took a look at the thread you linked, and it cleared some stuff up. No idea node.js runs single threaded. Also, thank you for posting an answer soon :) I appreciate it – WoJo Jul 24 '20 at 13:49
  • 1
    One question, is the input video a square one (same height and width)? If not what do you want the desired output be: **1.** the video is stretched to fill `500x500`, **2.** aspect ratio kept the same and the video is padded by black stripes on the smaller edges to make it a `500x500` or **3.** the aspect ratio kept the same and the video is not necessarly `500x500` (for example it can be `500x400` or `266x500`)? – ibrahim mahrir Jul 24 '20 at 14:00
  • 1
    I would want the video to be the same aspect ratio, but just smaller than 500x500. It doesn't matter if it is 240x360 for example, as long as it keeps the same aspect ratio and is under 500x500. (Basically your last option ;) ) – WoJo Jul 24 '20 at 14:01
  • You don't need a loop for this. scale filter can handle this on its own with appropriate expressions. – Gyan Jul 25 '20 at 06:26

1 Answers1

2

The problem is that the loop prevents the callbacks from getting called because javascript runs on one thread (read more about this in this other SO question: Callback of an asynchronous function is never called). One of those callbacks that doesn't get called is the callback of then where the variables vidwidth and vidheight get changed, so the condition that checks if they're less than 500 and eventually break the loop is never true and the loop keeps running forever. This is not the proper way to deal with asynchronous functions anyway (read more about this in this other SO question: How do I return the response from an asynchronous call?).

By the way, copyFile and the while loop are not necessary at all for this kind of work, you can just use getDimensions to get the dimensions of the video, calculate the desired dimensions based on them and start an ffmpeg task (ffmpeg will handle the creation of the resulting file without altering the input file so no need for copyFile). Like so:

getDimensions(filepath2).then((dimensions: any) => {                            // get the dimension of the input file
  let sizeStr = dimensions.width < dimensions.height ? "?x500" : "500x?";       // if width is smaller than height, reduce the height to 500 and calculate width based on that, same goes for the other way around
  
  ffmpeg(filepath2)                                                             // the input is the original video, don't worry 'ffmpeg' won't alter the input file
    .output('./media/media.mp4')                                                // the output file path
    .size(sizeStr)                                                              // use the 'sizeStr' string calculated previously (read more about it here: https://github.com/fluent-ffmpeg/node-fluent-ffmpeg#video-frame-size-options) 
    .on('end', () => console.log('Finished processing'))
    .run();       
});

As simple as that!

ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73