Skip to content

Commit

Permalink
code restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
hdwivedi9R committed Mar 30, 2022
1 parent 049b21a commit 9b1c3a1
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 172 deletions.
174 changes: 2 additions & 172 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -1,174 +1,4 @@
#! /usr/bin/env node
let { exec } = require("child_process");
const yargs = require("yargs");
const path = require("path");
const fs = require("fs/promises");
const fse = require("fs-extra");
const dir = require("node-dir");
const util = require("util");
const utils = require("./utils.js");
const { ffopti } = require("../src/index.js");

exec = util.promisify(exec);

const ffmpeg = "ffmpeg";
const pngquant = "pngquant";

const CHUNK_IMG = 50;
const CHUNK_VID = 5;
const SUFFIX = "_cmp";
const imageExt = ["jpg", "png", "webp", "bmp", "tiff", "gif"];
const videoExt = ["mp4", "mov", "webm", "mkv", "avi", "wmv", "flv"];
const validExt = [...imageExt, ...videoExt];

let { yargsOptions, getExt, addSuffix } = utils;
let argv = yargs
.options(yargsOptions)
.usage(
`
Compress image or video files.
usage: $0 [options] [input] [input]...
where input is file or directory.
Supported formats
Images: ${imageExt.join(", ")}
Videos: ${videoExt.join(", ")}
`
)
.demandCommand(1, "Atleast one input is required").argv;

let { _, replace, suffix, silent, stats } = argv;
suffix = suffix && suffix.length > 0 ? suffix : SUFFIX;
const cstats = { img: 0, vid: 0 };
const timer = "compression_time";

const validFiles = [];
const validFilesChunks = [];

async function getValidFiles(args) {
if (!args || !Array.isArray(args)) return;
for (const inp of args) {
try {
let fileStat;
try {
fileStat = fse.lstatSync(inp);
} catch (err) {
console.log(`${inp} is not a valid file or directory`);
continue;
}
if (fileStat.isFile()) {
if (validExt.includes(getExt(inp))) {
if (replace) {
validFiles.push(inp);
continue;
}
let out = addSuffix(inp, suffix);
fse.copyFileSync(inp, out);
validFiles.push(out);
} else {
console.log(`${inp} file type not supported`);
}
} else if (fileStat.isDirectory()) {
let out;
if (replace) {
out = inp;
} else {
out = addSuffix(inp, suffix);
fse.emptyDirSync(out);
fse.copySync(inp, out);
}
files = await dir.promiseFiles(out);
files.forEach((file) => {
if (validExt.includes(getExt(file))) validFiles.push(file);
});
}
} catch (err) {
console.error(err);
}
}
}

function chunk() {
let tempImg = [];
let tempVid = [];

for (const inp of validFiles) {
let ext = getExt(inp);
if (imageExt.includes(ext)) {
tempImg.push(inp);
if (tempImg.length >= CHUNK_IMG) {
validFilesChunks.push(tempImg);
tempImg = [];
}
} else if (videoExt.includes(ext)) {
tempVid.push(inp);
if (tempVid.length >= CHUNK_VID) {
validFilesChunks.push(tempVid);
tempVid = [];
}
}
}
if (tempImg.length > 0) validFilesChunks.push(tempImg);
if (tempVid.length > 0) validFilesChunks.push(tempVid);
}

async function comp(inp) {
try {
let ext = getExt(inp);
let tmp = addSuffix(inp, "#");
await fs.rename(inp, tmp);

let cmd;
if (imageExt.includes(ext)) {
cstats.img += 1;
if (ext === "png")
cmd = `${pngquant} -f --strip --quality 0-80 "${tmp}" -o "${inp}"`;
else if (ext === "jpg")
cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y -q:v 7 "${inp}"`;
else cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y "${inp}"`;
} else if (videoExt.includes(ext)) {
cstats.vid += 1;
if (ext === "mp4")
cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y -map_metadata -1 -movflags +faststart -c:v libx264 -c:a aac -b:a 64k -vf "scale=-2:min(720\\,trunc(ih/2)*2)" -profile:v main -r 30 -b:v 1000k "${inp}"`;
else
cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y -map_metadata -1 -vf "scale=-2:min(720\\,trunc(ih/2)*2)" -r 30 -b:v 1000k "${inp}"`;
}

try {
await exec(cmd);
silent || console.log(`${inp} compressed`);
await fs.unlink(tmp);
} catch (err) {
console.error(`${inp} compression failed: `, err);
replace ? await fs.rename(tmp, inp) : await fs.unlink(tmp);
}
} catch (err) {
console.error(err);
}
}

async function compressFiles() {
for (const inputFiles of validFilesChunks) {
await Promise.all(
inputFiles.map((inp) => {
return comp(inp);
})
);
}
}

function showStats() {
console.timeEnd(timer);
console.log(`images compressed: ${cstats.img}`);
console.log(`videos compressed: ${cstats.vid}`);
}

async function compress() {
stats && console.time(timer);
await getValidFiles(_);
chunk();
await compressFiles();
stats && showStats();
}

if (_ && _.length > 0) compress();
ffopti();
64 changes: 64 additions & 0 deletions src/ffopti/compress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
let { exec } = require("child_process");
const fs = require("fs/promises");
const util = require("util");

const { replace, silent } = require("../helpers/options.js");
const { getExt, addSuffix } = require("../helpers/utils.js");
const { cstats } = require("../helpers/stats.js");
const {
ffmpeg,
pngquant,
imageExt,
videoExt,
} = require("../helpers/constants.js");

exec = util.promisify(exec);

async function compress(inp) {
try {
let ext = getExt(inp);
let tmp = addSuffix(inp, "#");
await fs.rename(inp, tmp);

let cmd;
if (imageExt.includes(ext)) {
cstats.img += 1;
if (ext === "png")
cmd = `${pngquant} -f --strip --quality 0-80 "${tmp}" -o "${inp}"`;
else if (ext === "jpg")
cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y -q:v 7 "${inp}"`;
else cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y "${inp}"`;
} else if (videoExt.includes(ext)) {
cstats.vid += 1;
if (ext === "mp4")
cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y -map_metadata -1 -movflags +faststart -c:v libx264 -c:a aac -b:a 64k -vf "scale=-2:min(720\\,trunc(ih/2)*2)" -profile:v main -r 30 -b:v 1000k "${inp}"`;
else
cmd = `${ffmpeg} -loglevel 16 -i "${tmp}" -y -map_metadata -1 -vf "scale=-2:min(720\\,trunc(ih/2)*2)" -r 30 -b:v 1000k "${inp}"`;
}

try {
await exec(cmd);
silent || console.log(`${inp} compressed`);
await fs.unlink(tmp);
} catch (err) {
console.error(`${inp} compression failed: `, err);
replace ? await fs.rename(tmp, inp) : await fs.unlink(tmp);
}
} catch (err) {
console.error(err);
}
}

async function compressFiles(validFilesChunks = []) {
for (const inputFiles of validFilesChunks) {
await Promise.all(
inputFiles.map((inp) => {
return compress(inp);
})
);
}
}

module.exports = {
compressFiles,
};
89 changes: 89 additions & 0 deletions src/ffopti/files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const fse = require("fs-extra");
const dir = require("node-dir");

const { replace, suffix } = require("../helpers/options.js");
const { getExt, addSuffix } = require("../helpers/utils.js");
const {
CHUNK_IMG,
CHUNK_VID,
imageExt,
videoExt,
} = require("../helpers/constants.js");

async function getValidFiles(args) {
const validExt = [...imageExt, ...videoExt];
let validFiles = [];

if (!args || !Array.isArray(args)) return;
for (const inp of args) {
try {
let fileStat;
try {
fileStat = fse.lstatSync(inp);
} catch (err) {
console.log(`${inp} is not a valid file or directory`);
continue;
}
if (fileStat.isFile()) {
if (validExt.includes(getExt(inp))) {
if (replace) {
validFiles.push(inp);
continue;
}
let out = addSuffix(inp, suffix);
fse.copyFileSync(inp, out);
validFiles.push(out);
} else {
console.log(`${inp} file type not supported`);
}
} else if (fileStat.isDirectory()) {
let out;
if (replace) {
out = inp;
} else {
out = addSuffix(inp, suffix);
fse.emptyDirSync(out);
fse.copySync(inp, out);
}
files = await dir.promiseFiles(out);
files.forEach((file) => {
if (validExt.includes(getExt(file))) validFiles.push(file);
});
}
} catch (err) {
console.error(err);
}
}
return validFiles;
}

function chunkFiles(validFiles) {
let tempImg = [];
let tempVid = [];
let validFilesChunks = [];

if (!validFiles || validFiles.length <= 0) return [];

for (const inp of validFiles) {
let ext = getExt(inp);
if (imageExt.includes(ext)) {
tempImg.push(inp);
if (tempImg.length >= CHUNK_IMG) {
validFilesChunks.push(tempImg);
tempImg = [];
}
} else if (videoExt.includes(ext)) {
tempVid.push(inp);
if (tempVid.length >= CHUNK_VID) {
validFilesChunks.push(tempVid);
tempVid = [];
}
}
}
if (tempImg.length > 0) validFilesChunks.push(tempImg);
if (tempVid.length > 0) validFilesChunks.push(tempVid);

return validFilesChunks;
}

module.exports = { getValidFiles, chunkFiles };
19 changes: 19 additions & 0 deletions src/helpers/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const ffmpeg = "ffmpeg";
const pngquant = "pngquant";

const CHUNK_IMG = 50;
const CHUNK_VID = 5;
const SUFFIX = "_cmp";

const imageExt = ["jpg", "png", "webp", "bmp", "tiff", "gif"];
const videoExt = ["mp4", "mov", "webm", "mkv", "avi", "wmv", "flv"];

module.exports = {
ffmpeg,
pngquant,
CHUNK_IMG,
CHUNK_VID,
SUFFIX,
imageExt,
videoExt,
};
46 changes: 46 additions & 0 deletions src/helpers/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const yargs = require("yargs");
const { SUFFIX, imageExt, videoExt } = require("./constants.js");

const usage = `
Compress image or video files.
usage: $0 [options] [input] [input]...
where input is file or directory.
Supported formats
Images: ${imageExt.join(", ")}
Videos: ${videoExt.join(", ")}
`;

const options = {
r: {
alias: "replace",
describe: "Replace original",
type: "boolean",
},
s: {
alias: "silent",
describe: "Run quietly with no info",
type: "boolean",
},
suffix: {
default: SUFFIX,
describe: "Final compressed path suffix",
type: "string",
},
stats: {
describe: "Show compression stats",
type: "boolean",
},
};

const argv = yargs
.options(options)
.usage(usage)
.demandCommand(1, "Atleast one input is required").argv;

if (!argv.suffix || argv.suffix.length <= 0) {
argv.suffix = SUFFIX;
}

module.exports = argv;
Loading

0 comments on commit 9b1c3a1

Please sign in to comment.