Over at DesignFrame, one of my clients hosts videos on their own site. In order to ensure that these videos will play correctly on all devices, I have been manually converting these videos using Cloudconvert. It's a very handy tool, but the process can be tedious when you have a lot of files to deal with, and it doesn't (at least to my knowledge) handle generating screenshots of your videos for you.
So, in-order to upload the videos to their website, my (admittedly awful) workflow looked something like this:
- Take each video, and use cloudconvert to create ogv, webm, and mp4 versions of each video
- Open the video and save a screenshot at a good place
- Upload each version of each video to their server
- Publish the video with the screenshot
This wasn't too bad, but as a programmer, doing manual, repetitive tasks makes my skin crawl, so I started looking into ways to automate this. I've been playing with creating small CLI applications with Node.js using commander lately, and decided that this would be an excellent place to start.
What's nice about starting with a CLI-based solution is that it allows me to spend most of my time focusing on the back-end instead of building out some kind of interface. If you build correctly, it should be easy to set up what you've built with an interface.
Here's what the script does:
- Add 3 commands accessible from my terminal's command line:
run
,screenshots
, andvideos
- Take all of the files in a specified directory, and convert the videos to ogv, webm, and mp4
- Automatically generate 6 screenshots of each video at different intervals throughout.
- Save the results of each video in a converted files directory, with each video title as the sub directory.
The nice thing about setting it up with Node is that, if the conversion job warrants it, you can spin up a cpu-optimized droplet on DigitalOcean, upload the files, and make the conversion quickly, and then destroy the droplet. This is way faster than doing it on your local machine, and since the droplet is usually destroyed in 1-2 hours you're going to spend very little money to get the job done. This isn't a requirement, of course; The script runs perfectly fine on a local machine - the conversion will just take longer.
Completed Project Files
You can get the completed project files here.
Project Structure
I set the project up to use 3 files.
-
index.js
- The entry point for our program. This is where we configure our CLI commands -
FileConverter.js
- Handles the actual conversion of a single file. -
MultiFileConverter.js
- Gathers up videos from a directory, creates instances ofFileConverter
, and runs the conversion.
Setting Up Your Project
Here is the resulting package.json
file that I'm using for this project:
{
"name": "video-converstion-script",
"version": "1.0.0",
"description": "Converts Videos",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"asconvert": "./index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@ffmpeg-installer/ffmpeg": "^1.0.15",
"@ffprobe-installer/ffprobe": "^1.0.9",
"commander": "^2.16.0",
"fluent-ffmpeg": "^2.1.2",
"junk": "^2.1.0"
}
}
Here's a list of each dependency and a brief description of their role in this project
-
@ffmpeg-installer/ffmpeg
- sets up the binaries needed to convert the videos and create screenshots -
@ffprobe-installer/ffprobe
- sets up the binaries needed to convert the videos and create screenshots -
commander
- Super awesome tool that allows us to build out a CLI from our Node.js application. -
fluent-ffmpeg
- Allows us to interface with ffmpeg using Node -
junk
- A nice little library that makes it easy to filter out junk files from our directory. This will keep us from trying to convert a .DS_Store file, or something like that.
Note that we also have set the bin object. This allows us to associate our CLI command asconvert
with our index.js
file. You can change asconvert
to whatever you want, just keep in mind that you will need to use whatever you call asconvert
instead of what I call it in this post.
Place JSON above into your package.json
file, and run npm install
. Once you do that, you'll also need to run npm link
. This will connect the bin configuration to your terminal so you can run your commands directly from the command line.
Setting up our Index file
Before we can start messing with our system, we need to set up some commander commands. This will allow us to test, debug, and tinker with our javascript from the terminal. We will be adding multiple commands later, but for now, let's simply add the run
command. The code below is a basic example, and should respond with "hello world!' in your terminal.
#!/usr/bin/env node
/**
* Allows us to run this script as a cli command
*/
const program = require('commander');
/**
* Sets up the command to run from the cli
*/
program
.version('0.1.0')
.description('Convert Video Files From a Directory');
/**
The run command
*/
program
.command('run')
.description('Converts the files in the files-to-convert directory of this project')
.action(() =>{
console.log('hello world!');
//We will put our actual command here.
});
program.parse(process.argv);
Once you add this, you should be able to run asconvert run
from your terminal and you should get "hello world!" back. Superkewl!
Set Up the MultiFileConverter Class
Now that we've got some simple command line things set up, let's start working on the good stuff.
Create a new file called MultiFileConverter.js
and add the following code.
/**
* Parses file names
*/
const path = require('path');
/**
* converts files from a directory
*/
class MultiFileConverter{
constructor(args = {}){
//Set the argument object
const defaults = {
directory: false,
formats: false
};
this.args = Object.assign(args, defaults);
//Construct from the args object
this.formats = this.args.formats;
this.directory = this.args.directory === false ? `${path.dirname(require.main.filename)}/files-to-convert/` : this.args.directory;
}
}
module.exports = MultiFileConverter;
This basic setup will allow us to pass an object of arguments to our constructor, which will merge with default arguments and build everything we'll need to complete the conversions.
Connect The Converter to the CLI
Once you do this, we need to set up our CLI command to use this object. Go back to your index.js file and create an instance of this class, like so.
#!/usr/bin/env node
/**
* Allows us to run this script as a cli command
*/
const program = require('commander');
const MultiFileConverter = require('./lib/MultiFileConverter');
/**
* Sets up the command to run from the cli
*/
program
.version('0.1.0')
.description('Convert Video Files From a Directory');
/**
The run command
*/
program
.command('run')
.description('Converts the files in the files-to-convert directory of this project')
.action(() =>{
const converter = new MultiFileConverter();
console.log(converter);
});
program.parse(process.argv);
If you run the command now, the converter object should be displayed in the terminal.
I personally organize my js files inside a lib
directory. You can put your files wherever you want, just make sure your include paths are correct.
Get the List of FileConverter objects
The primary purpose of the MultiFileConverter
class is to batch-convert files in the directory. In order to do that, we are going to loop through the files in the directory and construct an array of FileConverter
objects from each file. We'll let the FileConverter
object handle the actual conversion and other file-specific things.
I like to delay processes that have the potential to be time-consuming until I absolutely need them. That way I can construct the class without going through the time-consuming bits every time. To do this, I often create a getter method, like this:
/**
* Constructs the files object
* @returns {*}
*/
getFiles(){
if(this.files) return this.files;
this.files = [];
const files = fs.readdirSync(this.directory, {});
//Loop through and construct the files from the specified directory
files.filter(junk.not).forEach((file) =>{
this.files.push(new FileConverter(this.directory + file, false, this.formats));
});
return this.files;
}
You'll notice the first line checks to see if the class already has a files array set. If it does, it simply returns that array. Otherwise, it goes through and builds this array. This allows us to use getFiles()
throughout the class without re-building the array every time.
A lot is happening in this method. Let's break it down.
- Check to see if the files array exists. If it does, it returns the value
- Reads the specified directory and returns an array of files
- Filters out junk files, and then loops through the filtered array.
- Inside the loop, we push a new instance of
FileConverter
and pass the arguments into the the files array. - Return the files in the object
Update your MultiFileConverter
class to include a couple of required libraries, and add the getFiles()
class. You should end up with something like this:
/**
* Node File system
*/
const fs = require('fs');
/**
* Parses file names
*/
const path = require('path');
/**
* Allows us to filter out junk files in our results
*/
const junk = require('junk');
/**
* Handles the actual file conversion of individual files
* @type {FileConverter}
*/
const FileConverter = require('./FileConverter');
/**
* converts files from a directory
*/
class MultiFileConverter{
constructor(args = {}){
//Set the argument object
const defaults = {
directory: false,
formats: false
};
this.args = Object.assign(args, defaults);
//Construct from the args object
this.formats = this.args.formats;
this.directory = this.args.directory === false ? `${path.dirname(require.main.filename)}/files-to-convert/` : this.args.directory;
}
/**
* Constructs the files object
* @returns {*}
*/
getFiles(){
if(this.files) return this.files;
this.files = [];
const files = fs.readdirSync(this.directory, {});
//Loop through and construct the files from the specified directory
files.filter(junk.not).forEach((file) =>{
this.files.push(new FileConverter(this.directory + file, false, this.formats));
});
return this.files;
}
}
module.exports = MultiFileConverter;
Set Up the FileConverter Class
Now that we are looping through our files, it's time to build a basic instance of the FileConverter class so our files array builds properly.
/**
* Parses file names
*/
const path = require('path');
/**
* Node File system
*/
const fs = require('fs');
/**
* Handles the actual file conversion
*/
const ffmpegInstaller = require('@ffmpeg-installer/ffmpeg');
const ffprobePath = require('@ffprobe-installer/ffprobe').path;
const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffmpegInstaller.path);
ffmpeg.setFfprobePath(ffprobePath);
/**
* Converts files and takes screenshots
*/
class FileConverter{
constructor(inputPath, outputPath = false, formats = false){
this.formats = formats === false ? ['ogv', 'webm', 'mp4'] : formats.split(',');
this.file = path.basename(inputPath);
this.format = path.extname(this.file);
this.fileName = path.parse(this.file).name;
this.conversion = ffmpeg(inputPath);
this.outputPath = outputPath === false ? `${path.dirname(require.main.filename)}/converted-files/${this.fileName}` : `${outputPath}/${this.fileName}`;
}
}
module.exports = FileConverter;
You'll notice that we are constructing some useful data related to the file and its impending conversion, but we don't actually do the conversion step yet. This simply sets the file up. We'll add the actual conversion in a separate method.
Test It Out
We now have all 3 of our files all set up and connected. We haven't started the actual conversion process yet, but if we make a change to our command action we can check to make sure everything is working as-expected.
If you haven't yet, now would be a good time to create 2 directories in the root of your project. converted-files
and files-to-convert
. Add a few video files in your files-to-convert
directory.
Modify your commander action in your index.js
file so that it logs the result of the getFiles()
method. If all went well, you should get a big ol' array of objects.
#!/usr/bin/env node
/**
* Allows us to run this script as a cli command
*/
const program = require('commander');
const MultiFileConverter = require('./lib/MultiFileConverter');
/**
* Sets up the command to run from the cli
*/
program
.version('0.1.0')
.description('Convert Video Files From a Directory');
/**
The run command
*/
program
.command('run')
.description('Converts the files in the files-to-convert directory of this project')
.action(() =>{
const converter = new MultiFileConverter();
console.log(converter.getFiles());
});
program.parse(process.argv);
Convert Videos
Whew. All this effort and we haven't even started converting videos yet. Let's change that.
Add a new method, called getVideos()
to your MultiFileConverter.js
file.
/**
* Loops through and converts files
*/
getVideos(){
return this.getFiles().forEach(file => file.convert());
}
This iddy biddy method simply loops through our files array and runs the convert
method on each FileConverter
object. Of course, we have to actually create the convert method on the FileConverter
object for this to work, so let's do that now.
Add a new method, called convert()
to your FileConverter.js
file.
/**
* Converts the file into the specified formats
*/
convert(){
fs.mkdir(this.outputPath,() =>{
//Loop through file formats
this.formats.forEach((fileFormat) =>{
//Check to see if the current file format matches the given file's format
if(`.${fileFormat}` !== this.format){
//Start the conversion
this.conversion.output(`${this.outputPath}/${this.fileName}.${fileFormat}`)
.on('end', () => console.log(`${this.file} has been converted to a ${fileFormat}`))
.on('start', () =>{
console.log(`${this.fileName}.${fileFormat} conversion started`);
})
}
//If the file format matches the file's format, skip it and let us know.
else{
console.log(`Skipping ${this.fileName} conversion to ${fileFormat} as this file is already in the ${fileFormat} format.`);
}
});
this.conversion.run();
});
}
Here's the real meat and potatoes of the build. A lot is happening here, so let's break it down.
- Creates a directory named after the original video we're converting. This will hold all files generated for this video.
- Loops through each file format specified for this conversion.
- In the loop, we check to see if the current file format matches the format of the video we're converting. If they match, the converter skips that conversion and moves on to the next format. This keeps us from needlessly converting an .mp4 to another .mp4.
- If the formats are different, we queue up the converter using the specified format.
- Once we've looped through all of the formats we're converting to, we run the actual converter.
Test It Out
We have now set up the actual converter. Let's see if it works as expected.
Modify your commander action in your index.js
file to use the getVideos()
method, like so.
#!/usr/bin/env node
/**
* Allows us to run this script as a cli command
*/
const program = require('commander');
const MultiFileConverter = require('./lib/MultiFileConverter');
/**
* Sets up the command to run from the cli
*/
program
.version('0.1.0')
.description('Convert Video Files From a Directory');
/**
The run command
*/
program
.command('run')
.description('Converts the files in the files-to-convert directory of this project')
.action(() =>{
});
program.parse(process.argv);
You should see a message for each video, stating that the conversion started for each format. It will also let you know if it skipped one of the conversions, and why. This will take a long time to convert, and since we're just testing, cancel the command (CTRL+C on a Mac) after about 20 seconds. Check your converted-files
directory and see if the video conversion started to run.
Generate Screenshots
Sweet! Now that we have videos converting, let's get generate some screenshots while we're at it. The process of adding screenshots is very similar.
Add a new method, called getScreenshots()
to your MultiFileConverter.js
file.
/**
* Loops through and generates screenshots
*/
getScreenshots(){
return this.getFiles().forEach(file => file.getScreenshots());
}
This works just like getVideos()
, only it runs getScreenshots
method on each FileConverter
object instead. Again, we need to create the convert method on the FileConverter
object for this to work.
Add a new method, called getScreenshots()
to your FileConverter.js
file.
/**
* Creates 6 screenshots taken throughout the video
*/
getScreenshots(){
this.conversion
.on('filenames', filenames => console.log(`\n ${this.fileName} Will generate 6 screenshots, ${filenames.join('\n ')}`))
.on('end', () =>{
console.log(`\n Screenshots for ${this.fileName} complete.\n`)
})
.screenshots({
count: 6,
timestamps: [2, 5, '20%', '40%', '60%', '80%'],
folder: this.outputPath,
filename: `${this.fileName}-%s.png`
})
}
This method is a bit simpler than getVideos()
. We simply chain the screenshots()
method (included in our ffmpeg library) and pass some arguments. Our arguments instruct ffmpeg to create 6 screenshots at 2 seconds, 5 seconds, and at 20%, 40%, 60%, and 80% of the video. Each file is saved inside the same directory as our converted videos are saved.
Test It Out
Let's make sure that we can generate screenshots.
Modify your commander action in your index.js
file to use the getScreenshots()
method, like so.
#!/usr/bin/env node
/**
* Allows us to run this script as a cli command
*/
const program = require('commander');
const MultiFileConverter = require('./lib/MultiFileConverter');
/**
* Sets up the command to run from the cli
*/
program
.version('0.1.0')
.description('Convert Video Files From a Directory');
/**
The run command
*/
program
.command('run')
.description('Converts the files in the files-to-convert directory of this project')
.action(() =>{
const converter = new MultiFileConverter();
return converter.getScreenshots();
});
program.parse(process.argv);
You should see a message for each video, listing off the screenshots that will be created. This will take a some time to convert, and since we're just testing, cancel the command (CTRL+C on a Mac) after about 20 seconds. Check your converted-files
directory and see if the screenshots started to generate.
Generate Everything
Now that we have a way to generate screenshots and convert our videos, we need to make one more method in our MultiFileConverter.js
file. This method will run both the convert()
method and the getScreenshots()
method.
We are creating a third method to do both of these because it allows us to loop through the files once, instead of twice, and as such is more efficient than running getVideos()
and then getScreenshots()
separately.
Add this method to your MultiFileConverter
class.
/**
* Runs the complete converter, converting files and getting screenshots
*/
runConverter(){
return this.getFiles().forEach((file) =>{
file.convert();
file.getScreenshots();
});
Create Commands
Now that we have everything needed, let's create our 3 commands we talked about earlier - asconvert videos
, asconvert screenshots
, and asconvert run
/**
* Sets up the command to run from the cli
*/
program
.version('0.1.0')
.description('Convert Video Files From a Directory');
program
.command('run')
.description('Converts the files in the files-to-convert directory of this project')
.action(() =>{
const converter = new MultiFileConverter();
return converter.runConverter();
});
/**
* Sets up the command to run from the cli
*/
program
.command('screenshots')
.description('Gets a screenshot of each video')
.action(() =>{
const converter = new MultiFileConverter();
return converter.getScreenshots();
});
/**
* Sets up the command to run from the cli
*/
program
.command('videos')
.description('Gets conversions of each video')
.action(() =>{
const converter = new MultiFileConverter();
return converter.getVideos();
});
program.parse(process.argv);
You can now run any of those 3 commands, and convert videos, create screenshots, or do both at the same time.
Closing Remarks
There are a couple of things that could improve this tool.
- I'm sure someone who knows Docker better than I could put it in some kind of container to make this EZPZ to set up/tear down on a server
- The directory that houses the videos is a part of the project. With further configuration you could set this up so that the videos are pulled directly from Google Drive, or something like that. I didn't have a need for that, but it would be pretty slick.
All in all, it was a fun little build, and I'm sure it will save me some time in the future.
If you are using this, I'd love to hear about how it worked for you, and why you needed it. Cheers!
Top comments (63)
Thanks for sharing, very nice. I often use kinemaster pro at Techbigs.com for video editing. You can refer to here
Most of the people like to use kinemaster app for editing. Now this app premium version is available. Just download kinemaster pro mod apk on your android smartphone.
Thoptv is an Android TV app that offers live TV channels, movies and TV shows. Thoptv apk is available for download on the Google Play Store.
Mega Personal: A Dating Hookup Classified Website
Putting all of the frustrations of online dating aside, try out Megapersonal to discover and rediscover a dating hobby, or perhaps even find the woman or man of your dreams.
For those of you who’re interested in the art of photography and would love to experience amazing footages on your mobile devices, PicsArt Photo Editor will offer you the complete photography experiences with amazing photos and videos for you to enjoy and play with. Explore the exciting and interesting visual options in PicsArt, along with many other unique features that you can’t find anywhere else, to create and enjoy awesome visual experiences with your edits.
techhube.com/picsart-mod-apk/
picsart mod apk
The app offers both video and photo editor tools that were completely built into your devices. Therefore, allowing you to combine your act of taking photos or capturing videos with the complete editing options. Here, you can feel free to have fun with the awesome visual experiences with each of your edits and enjoy unique feels with the amazing visual customizations.
I can use apkvest.com site for downloading mod site
Thank you for sharing. You can experience Rokkr, an application to watch TV and dramas that you will enjoy.
Once the code is written, it can be tested locally before being deployed to a server for more robust testing and production use like frozen city mod apk. This allows developers to quickly iterate on their code and make changes as needed without having to worry about breaking existing functionality or introducing new bugs.
For video editing, I use SolveigMM HTML5 Video Editor (official version solveigmm.com/en/products/html5-cl...) - an advanced video editor that allows you to manage any content. With it, you can cut and merge videos, add transitions, text, graphics, video overlay, voiceover. SolveigMM HTML5 Video Editor is the only online editor with smart rendering.
Nice article. Video editing is my dream. I am a video designing student. But I studied online. Free chegg answers are very helpful for me on those times. Thanks for that.
thanks Alex for sharing this with us. everything is so detailed and is easy to get. before it, i used to use Kinemaster mod to edit video which supports batch editing. but it also helps me a lot. thanks again. i look forward to more of your posts.
Well Nod.js seems to be the easiest one but not sure how many professional features available on that?
Here are some of the video editors available on Linux: Lives Video Editor, Lightworks video editor, OpenShot video editor, Blender video editor, and here is a list of some best video editing software available in Linux.
thanks for sharing this helpful with us. everything is so detailed and is easy to get from the website which have Unlimited App with unlocked App. before it, i used to use Inshot mod to edit video which supports batch editing. but it also helps me a lot. thanks again. i look forward to more of your posts.
Thanks, an article that I had to read through. It’s too brilliant. So I want to ask about installing the app on the mobile phone. Some users have faced issues when they install the app and read the articles. So if you want to read some helpful information then click here.
Thanks for sharing, very nice. Nice to know you, i have a website Mod APK modtodays! Modtodays is a great resource for lovers of mod versions of popular mobile apps and games. It provides an opportunity to download customized versions of applications for a more unique experience. This can bring many benefits and new features to your favorite apps. If you want to share more information or have any questions about Modtodays, please share more!
VRL Tracking is a versatile and affordable GPS tracking solution that can be used for a variety of tracking applications. It is a reliable solution that is easy to use and provides accurate tracking data.
Lucky Patcher iOS app is a very popular app to hack games, remove app permissions, modify games, etc. There is a free application for iOS lucky patcher that can modify games and apps very easily. Through this, you can use the paid feature for free and enjoy the premium feature of iOS for free. You can use this application for your Android device as well as iPad and iPhone. Lucky Patcher iOS
Thanks you For sharing. im use PicsArt. Picsart is the best Video Editor And Photo Editer Download Picsart Mod Apk From apkfileok.net/picsart-apk/
College Brawl’s unique features encompass its fluid controls that facilitate precise actions, its captivating anime-inspired graphics that add visual flair, and its explicit content advisory that underscores the importance of responsible gameplay.
Well, it's for sure that you always learn something from coding and in this article everything is so clear. I'll definitely share it to my other friends.
Well, I also created a website which is Onlinebattegrounds to may check it out and give me some reviews
Embark on the heart-pounding adventure of Tag After School by downloading and installing the game. Whether you’re using an Android device or a PC, we’ve got you covered.
When i use kine master and do import some video. Sometimes i cannot import video it show that" you can't import this media. Kindly refresh it and import again."
If anyone know how to solve this problem kindly help me.
Thank you....
Some comments may only be visible to logged-in visitors. Sign in to view all comments.