Skip to content

Instantly share code, notes, and snippets.

@anuragteapot
Created December 12, 2020 07:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anuragteapot/56e60d364611d7cf8267ec31ecb3827c to your computer and use it in GitHub Desktop.
Save anuragteapot/56e60d364611d7cf8267ec31ecb3827c to your computer and use it in GitHub Desktop.
Video Processing helper code
import FfmpegCommand from "fluent-ffmpeg";
const spawn = require("child_process").spawn;
const exec = require("child_process").exec;
// const execFile = require("child_process").execFile;
import * as path from "path";
import * as fs from "fs-extra";
import moment from "moment";
import { HttpStatus, Util, Logs } from "./../index";
/**
* @class VideoProcessing
*/
export default class VideoProcessing {
[x: string]: any;
/**
* @constructor
*
* @param {String} [filePath]
* @param {String} [destinationPath]
*/
constructor(filePath, destinationPath) {
this.fileInfo = null;
this.filePath = filePath;
this.fileName = path.basename(filePath);
this.libraryPath = path.join("bento4", "bin");
this.destinationPath = destinationPath;
this.QUALITY = Util.QUALITY;
this.FfmpegCommand = FfmpegCommand;
}
hhmmss(secs: any): string {
let minutes: any = Math.floor(secs / 60);
secs = secs % 60;
let hours: any = Math.floor(minutes / 60);
minutes = minutes % 60;
if (hours < 10) {
hours = "0" + hours;
}
if (minutes < 10) {
minutes = "0" + minutes;
}
if (secs < 10) {
secs = "0" + secs;
}
return `${hours}:${minutes}:${secs}`;
}
async init(): Promise<this> {
return new Promise(async (resolve, reject) => {
try {
this.fileInfo = await this.getVideoInfo();
return resolve(this);
} catch (err) {
return reject(err);
}
});
}
/**
*
* @param {string} command
*
*/
async generateThumbnails(scale: number = 400, startSecond: number) {
const input = this.filePath;
const output = path.join(
this.destinationPath,
"trancoded",
"thumbnails",
`${this.fileName.split(".")[0]}_thumbnail_${scale}_${startSecond}.png`
);
const ss = this.hhmmss(startSecond);
return new Promise(async (resolve, reject) => {
try {
if (fs.existsSync(output)) {
return resolve(output);
}
if (!fs.existsSync(input)) {
return reject(new Error("[CUSTOM_ERROR] Input file not found."));
}
await fs.ensureDir(
path.join(this.destinationPath, "trancoded", "thumbnails")
);
const ffmpeg = spawn("ffmpeg", [
"-ss",
ss,
"-i",
input,
"-vframes",
"1",
"-vf",
`scale=${scale}:-2`,
output,
]);
ffmpeg.on("error", (error) => {
reject(error);
});
ffmpeg.on("close", () => {
resolve(output);
});
} catch (err) {
return reject(err);
}
});
}
async getThumbnails(scale = 400, number = 4) {
return new Promise(async (resolve, reject) => {
try {
let duration = 0;
if (this.fileInfo.format) {
duration = parseInt(this.fileInfo.format.duration, 10);
}
let ans = [];
for (let i = 0; i < number; i++) {
let time = 0;
if (i > 0) {
time = Math.floor(Math.random() * duration - 1) + 1;
}
const data = await this.generateThumbnails(scale, time);
ans.push(data);
}
resolve(ans);
} catch (err) {
reject(err);
}
});
}
/**
*
* @param {string} command
*
*/
async generateGif(scale = "300") {
if (this.fileInfo.format) {
const duration = parseInt(this.fileInfo.format.duration, 10);
if (duration < 20) {
return new Error("[CUSTOM_ERROR] Length is too small to generate gif");
}
}
const input = this.filePath;
const output = path.join(
this.destinationPath,
"trancoded",
"gif",
`${this.fileName.split(".")[0]}_gif_${scale}.gif`
);
return new Promise(async (resolve, reject) => {
try {
if (fs.existsSync(output)) {
return resolve(output);
}
if (!fs.existsSync(input)) {
return reject(new Error("[CUSTOM_ERROR] Input file not found."));
}
await fs.ensureDir(path.join(this.destinationPath, "trancoded", "gif"));
const ffmpeg = spawn("ffmpeg", [
"-ss",
"00:00:10",
"-i",
input,
"-to",
"10",
"-r",
"10",
"-vf",
`scale=${scale}:-2`,
output,
]);
ffmpeg.on("error", (error) => {
reject(error);
});
ffmpeg.on("close", () => {
resolve(output);
});
} catch (err) {
return reject(err);
}
});
}
/**
*
*/
async getVideoInfo() {
const input = this.filePath;
const command = `ffprobe -of json -show_streams -show_format ${input}`;
return new Promise(async (resolve, reject) => {
exec(command, (error, stdout) => {
if (error) {
reject(error);
} else {
resolve(JSON.parse(stdout));
}
});
});
}
/**
*
*/
async mp4Fragment() {
const input = this.filePath;
const output = path.join(
this.destinationPath,
"trancoded",
"fragment",
`${this.fileName.split(".")[0]}_fragmented.mp4`
);
const command = `${path.join(
this.libraryPath,
"mp4fragment"
)} ${input} ${output}`;
return new Promise(async (resolve, reject) => {
exec(command, (error, stdout) => {
if (error) {
reject(error);
} else {
resolve(JSON.parse(stdout));
}
});
});
}
/**
* Method to check isRotated
*
* @method isRotated
*
* @public
*/
isRotated() {
let rotate = false;
if (this.fileInfo && this.fileInfo.streams) {
if (this.fileInfo.streams[0]) {
if (
this.fileInfo.streams[0].tags.rotate ||
this.fileInfo.streams[0].tags.rotate == 90 ||
this.fileInfo.streams[0].side_data_list ||
this.fileInfo.streams[0].width < this.fileInfo.streams[0].height
) {
rotate = true;
}
}
}
return rotate;
}
/**
* Method to different format
*
* @method resizeVideo
*
* @param {Number} quality
*
* @public
*/
async resizeVideo(quality) {
if (!quality || !this.QUALITY[quality]) {
return new Error("NOT_SUPPPORTED");
}
const qData = this.QUALITY[quality];
const input = this.filePath;
const output = path.join(
this.destinationPath,
"trancoded",
"quality",
quality.toString(),
`${this.fileName.split(".")[0]}_${quality.toString()}.mp4`
);
return new Promise(async (resolve, reject) => {
try {
if (fs.existsSync(output)) {
return resolve(output);
}
if (!fs.existsSync(input)) {
return reject(new Error("[CUSTOM_ERROR] Input file not found."));
}
await fs.ensureDir(
path.join(
this.destinationPath,
"trancoded",
"quality",
quality.toString()
)
);
const rot = this.isRotated();
const ffmpeg = spawn("ffmpeg", [
"-i",
input,
"-preset",
"slow",
// "-codec:a",
// "libxaac",
// "-aspect",
// "16:9",
"-codec:v",
"libx264",
"-b:a",
"128k",
"-threads",
"0",
"-pix_fmt",
"yuv420p",
"-b:v",
qData["bv"],
"-minrate",
qData["minrate"],
"-maxrate",
qData["maxrate"],
"-bufsize",
qData["bufsize"],
"-vf",
`scale=${qData["width"]}:${qData["height"]}${
rot
? `:force_original_aspect_ratio=decrease,pad=${qData["width"]}:${qData["height"]}:(ow-iw)/2:(oh-ih)/2`
: ""
} `,
output,
]);
if (process.env.DEBUG_ENV) {
ffmpeg.stderr.on("data", (data) => {
Logs(`[VIDEO PROCESSING] ${data.toString("utf8")}`);
});
}
ffmpeg.on("error", (error) => {
reject(error);
});
ffmpeg.on("close", () => {
resolve(output);
});
} catch (err) {
return reject(err);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment