dev-toolkit-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/actions/action-compress.d.ts +13 -0
- package/bin/actions/action-compress.d.ts.map +1 -0
- package/bin/actions/action-compress.js +83 -0
- package/bin/actions/action-compress.js.map +1 -0
- package/bin/actions/index.d.ts +2 -0
- package/bin/actions/index.d.ts.map +1 -0
- package/bin/actions/index.js +1 -0
- package/bin/actions/index.js.map +1 -0
- package/bin/lib/bash.d.ts +5 -0
- package/bin/lib/bash.d.ts.map +1 -0
- package/bin/lib/bash.js +33 -0
- package/bin/lib/bash.js.map +1 -0
- package/bin/lib/constants.d.ts +37 -0
- package/bin/lib/constants.d.ts.map +1 -0
- package/bin/lib/constants.js +44 -0
- package/bin/lib/constants.js.map +1 -0
- package/bin/lib/utils/index.d.ts +13 -0
- package/bin/lib/utils/index.d.ts.map +1 -0
- package/bin/lib/utils/index.js +89 -0
- package/bin/lib/utils/index.js.map +1 -0
- package/bin/lib/utils 16-28-47-345/index.js +89 -0
- package/bin/lib/validation.d.ts +4 -0
- package/bin/lib/validation.d.ts.map +1 -0
- package/bin/lib/validation.js +21 -0
- package/bin/lib/validation.js.map +1 -0
- package/bin/main.d.ts +2 -0
- package/bin/main.d.ts.map +1 -0
- package/bin/main.js +35 -0
- package/bin/main.js.map +1 -0
- package/package.json +29 -0
- package/src/actions/action-compress.ts +150 -0
- package/src/actions/index.ts +1 -0
- package/src/lib/bash.ts +50 -0
- package/src/lib/constants.ts +47 -0
- package/src/lib/utils/index.ts +135 -0
- package/src/lib/validation.ts +28 -0
- package/src/main.ts +47 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Resolution } from "../lib/constants";
|
|
2
|
+
export declare const FileType: readonly ["image", "video"];
|
|
3
|
+
type FileType = (typeof FileType)[number];
|
|
4
|
+
export type CompressFileArgs = {
|
|
5
|
+
verbose: number;
|
|
6
|
+
type: FileType;
|
|
7
|
+
resolution: Resolution;
|
|
8
|
+
force: boolean;
|
|
9
|
+
horizontal: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function file(pathname: string, { verbose, type, resolution, force, horizontal }: CompressFileArgs): Promise<void>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=action-compress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-compress.d.ts","sourceRoot":"","sources":["../../src/actions/action-compress.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAa9C,eAAO,MAAM,QAAQ,6BAA8B,CAAC;AACpD,KAAK,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAU1C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAsB,IAAI,CACxB,QAAQ,EAAE,MAAM,EAChB,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,gBAAgB,iBA+CnE"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { CommanderError } from "commander";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import mime from "mime";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import sharp from "sharp";
|
|
6
|
+
import { addFilenamePrefix, getAvailablePathname, getDirectory, getFilename, getFolderFiles, normalizeFilename, removeFilenamePrefix, replaceFilenameExtension, } from "../lib/utils/index.js";
|
|
7
|
+
import { check_dependencies, ffmpeg_video_compress } from "../lib/bash.js";
|
|
8
|
+
export const FileType = ["image", "video"];
|
|
9
|
+
const unsupportedFormats = {
|
|
10
|
+
image: ["image/webp", "image/gif", "image/svg+xml"],
|
|
11
|
+
video: [],
|
|
12
|
+
};
|
|
13
|
+
const compressedFlagPrefix = "compressed_";
|
|
14
|
+
const compressTempPrefix = "temp.";
|
|
15
|
+
export async function file(pathname, { verbose, type, resolution, force, horizontal }) {
|
|
16
|
+
await check_dependencies(["ffmpeg"]);
|
|
17
|
+
if (force)
|
|
18
|
+
console.warn("⚠️ Ignoring already compressed files");
|
|
19
|
+
if (horizontal)
|
|
20
|
+
console.warn("⚠️ Forcing horizontal aspect ratio 16x9");
|
|
21
|
+
let verbose_title = "";
|
|
22
|
+
const filePathList = await getFolderFiles(pathname, true);
|
|
23
|
+
for (let filePath of filePathList) {
|
|
24
|
+
const mimetype = mime.getType(filePath);
|
|
25
|
+
if (!mimetype)
|
|
26
|
+
continue;
|
|
27
|
+
const fileType = mimetype.split("/")[0];
|
|
28
|
+
if (!(type ? [type] : FileType).includes(fileType) ||
|
|
29
|
+
(unsupportedFormats[fileType] || []).includes(mimetype) ||
|
|
30
|
+
(!force &&
|
|
31
|
+
[compressedFlagPrefix, compressTempPrefix].some((pf) => getFilename(filePath).startsWith(pf))))
|
|
32
|
+
continue;
|
|
33
|
+
const title = filePath
|
|
34
|
+
.substring(pathname.length)
|
|
35
|
+
.split("/")
|
|
36
|
+
.slice(1, 1 + verbose)
|
|
37
|
+
.join("/");
|
|
38
|
+
if (title !== verbose_title)
|
|
39
|
+
console.log(`Compressing ${fileType} at:`, title);
|
|
40
|
+
verbose_title = title;
|
|
41
|
+
try {
|
|
42
|
+
if (fileType === "image")
|
|
43
|
+
await compressImage(filePath);
|
|
44
|
+
else if (fileType === "video")
|
|
45
|
+
await compressVideo(filePath, resolution, horizontal);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error(" Error compressing", `"${filePath}"`);
|
|
49
|
+
// console.error("Error compressing", `"${filePath}"`, err.message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
console.log("\nCompression successfully finished 🚀");
|
|
53
|
+
}
|
|
54
|
+
async function compressImage(filePath) {
|
|
55
|
+
await sharp(filePath)
|
|
56
|
+
.webp()
|
|
57
|
+
.toFile(replaceFilenameExtension(filePath, "webp"));
|
|
58
|
+
await fs.rm(filePath);
|
|
59
|
+
}
|
|
60
|
+
async function compressVideo(filePath, resolution, force) {
|
|
61
|
+
const directory = getDirectory(filePath);
|
|
62
|
+
let filename = getFilename(filePath);
|
|
63
|
+
const normalizedFilename = normalizeFilename(filename);
|
|
64
|
+
if (filename !== normalizedFilename) {
|
|
65
|
+
const oldPathname = path.join(directory, filename);
|
|
66
|
+
const newPathname = await getAvailablePathname(path.join(directory, normalizedFilename));
|
|
67
|
+
if (!newPathname) {
|
|
68
|
+
throw new CommanderError(1, "P003", "Could not find a new file destination");
|
|
69
|
+
}
|
|
70
|
+
const newFilename = getFilename(newPathname);
|
|
71
|
+
console.log(" File renamed. From:", `'${filename.trim()}'`, "to:", `'${newFilename.trim()}'`);
|
|
72
|
+
await fs.rename(oldPathname, newPathname);
|
|
73
|
+
filename = newFilename;
|
|
74
|
+
}
|
|
75
|
+
const input = path.join(directory, filename);
|
|
76
|
+
const output = addFilenamePrefix(input, compressTempPrefix);
|
|
77
|
+
await ffmpeg_video_compress(input, output, resolution, force).catch(async (err) => {
|
|
78
|
+
await fs.rm(output);
|
|
79
|
+
throw err;
|
|
80
|
+
});
|
|
81
|
+
await fs.rm(input);
|
|
82
|
+
await fs.rename(output, addFilenamePrefix(removeFilenamePrefix(input, compressedFlagPrefix), compressedFlagPrefix));
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-compress.js","sourceRoot":"","sources":["../../src/actions/action-compress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as compress from "./action-compress.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Resolution } from "./constants.js";
|
|
2
|
+
export declare const execAsync: (command: string) => Promise<unknown>;
|
|
3
|
+
export declare function check_dependencies(dependencies?: string[]): Promise<void>;
|
|
4
|
+
export declare function ffmpeg_video_compress(input: string, output: string, resolution?: Resolution, forceResolution?: boolean): Promise<unknown>;
|
|
5
|
+
//# sourceMappingURL=bash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/lib/bash.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAqB,MAAM,gBAAgB,CAAC;AAG/D,eAAO,MAAM,SAAS,YAAa,MAAM,qBAMrC,CAAC;AAUL,wBAAsB,kBAAkB,CAAC,YAAY,WAAqB,iBAczE;AAED,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,UAAmB,EAC/B,eAAe,CAAC,EAAE,OAAO,oBAS1B"}
|
package/bin/lib/bash.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { resolutionMapping } from "./constants.js";
|
|
3
|
+
import { CommanderError } from "commander";
|
|
4
|
+
export const execAsync = (command) => new Promise((resolve, reject) => {
|
|
5
|
+
exec(command, (error, stdout, stderr) => {
|
|
6
|
+
if (error)
|
|
7
|
+
reject(new Error(stderr));
|
|
8
|
+
else
|
|
9
|
+
resolve(stdout);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
const globalDependencies = ["ffmpeg"];
|
|
13
|
+
async function check_dependency(dependency) {
|
|
14
|
+
return execAsync(`${dependency} -h`)
|
|
15
|
+
.then(() => true)
|
|
16
|
+
.catch(() => false);
|
|
17
|
+
}
|
|
18
|
+
export async function check_dependencies(dependencies = globalDependencies) {
|
|
19
|
+
const missing_dependencies = [];
|
|
20
|
+
for (let dep of dependencies) {
|
|
21
|
+
if (!(await check_dependency(dep)))
|
|
22
|
+
missing_dependencies.push(dep);
|
|
23
|
+
}
|
|
24
|
+
if (missing_dependencies.length > 0) {
|
|
25
|
+
throw new CommanderError(1, "missing_dependencies", missing_dependencies.map((dep) => `'${dep}'`).join(", "));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function ffmpeg_video_compress(input, output, resolution = "480p", forceResolution) {
|
|
29
|
+
const { w, h } = resolutionMapping[resolution];
|
|
30
|
+
return execAsync(forceResolution
|
|
31
|
+
? `ffmpeg -i "${input}" -s ${w}x${h} -acodec copy -y "${output}"`
|
|
32
|
+
: `ffmpeg -i "${input}" -filter:v scale=-1:${h} -acodec copy -y "${output}"`);
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/lib/bash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAc,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAe,EAAE,EAAE,CAC3C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC9B,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;QACtC,IAAI,KAAK;YAAE,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;;YAChC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,MAAM,kBAAkB,GAAG,CAAC,QAAQ,CAAC,CAAC;AAEtC,KAAK,UAAU,gBAAgB,CAAC,UAAkB;IAChD,OAAO,SAAS,CAAC,GAAG,UAAU,KAAK,CAAC;SACjC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;SAChB,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,YAAY,GAAG,kBAAkB;IACxE,MAAM,oBAAoB,GAAG,EAAE,CAAC;IAEhC,KAAK,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;QAC7B,IAAI,CAAC,CAAC,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAAE,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,cAAc,CACtB,CAAC,EACD,sBAAsB,EACtB,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACzD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAAa,EACb,MAAc,EACd,aAAyB,MAAM,EAC/B,eAAyB;IAEzB,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAE/C,OAAO,SAAS,CACd,eAAe;QACb,CAAC,CAAC,cAAc,KAAK,QAAQ,CAAC,IAAI,CAAC,qBAAqB,MAAM,GAAG;QACjE,CAAC,CAAC,cAAc,KAAK,wBAAwB,CAAC,qBAAqB,MAAM,GAAG,CAC/E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const resolutionMapping: {
|
|
2
|
+
readonly "4320p": {
|
|
3
|
+
readonly w: 7680;
|
|
4
|
+
readonly h: 4320;
|
|
5
|
+
};
|
|
6
|
+
readonly "2160p": {
|
|
7
|
+
readonly w: 3840;
|
|
8
|
+
readonly h: 2160;
|
|
9
|
+
};
|
|
10
|
+
readonly "1440p": {
|
|
11
|
+
readonly w: 2560;
|
|
12
|
+
readonly h: 1440;
|
|
13
|
+
};
|
|
14
|
+
readonly "1080p": {
|
|
15
|
+
readonly w: 1920;
|
|
16
|
+
readonly h: 1080;
|
|
17
|
+
};
|
|
18
|
+
readonly "720p": {
|
|
19
|
+
readonly w: 1280;
|
|
20
|
+
readonly h: 720;
|
|
21
|
+
};
|
|
22
|
+
readonly "480p": {
|
|
23
|
+
readonly w: 854;
|
|
24
|
+
readonly h: 480;
|
|
25
|
+
};
|
|
26
|
+
readonly "360p": {
|
|
27
|
+
readonly w: 640;
|
|
28
|
+
readonly h: 360;
|
|
29
|
+
};
|
|
30
|
+
readonly "240p": {
|
|
31
|
+
readonly w: 426;
|
|
32
|
+
readonly h: 240;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export declare const Resolution: readonly ["4320p", "2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"];
|
|
36
|
+
export type Resolution = (typeof Resolution)[number];
|
|
37
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCpB,CAAC;AAEX,eAAO,MAAM,UAAU,+EASb,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const resolutionMapping = {
|
|
2
|
+
"4320p": {
|
|
3
|
+
w: 7680,
|
|
4
|
+
h: 4320,
|
|
5
|
+
},
|
|
6
|
+
"2160p": {
|
|
7
|
+
w: 3840,
|
|
8
|
+
h: 2160,
|
|
9
|
+
},
|
|
10
|
+
"1440p": {
|
|
11
|
+
w: 2560,
|
|
12
|
+
h: 1440,
|
|
13
|
+
},
|
|
14
|
+
"1080p": {
|
|
15
|
+
w: 1920,
|
|
16
|
+
h: 1080,
|
|
17
|
+
},
|
|
18
|
+
"720p": {
|
|
19
|
+
w: 1280,
|
|
20
|
+
h: 720,
|
|
21
|
+
},
|
|
22
|
+
"480p": {
|
|
23
|
+
w: 854,
|
|
24
|
+
h: 480,
|
|
25
|
+
},
|
|
26
|
+
"360p": {
|
|
27
|
+
w: 640,
|
|
28
|
+
h: 360,
|
|
29
|
+
},
|
|
30
|
+
"240p": {
|
|
31
|
+
w: 426,
|
|
32
|
+
h: 240,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export const Resolution = [
|
|
36
|
+
"4320p",
|
|
37
|
+
"2160p",
|
|
38
|
+
"1440p",
|
|
39
|
+
"1080p",
|
|
40
|
+
"720p",
|
|
41
|
+
"480p",
|
|
42
|
+
"360p",
|
|
43
|
+
"240p",
|
|
44
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO,EAAE;QACP,CAAC,EAAE,IAAI;QACP,CAAC,EAAE,IAAI;KACR;IACD,OAAO,EAAE;QACP,CAAC,EAAE,IAAI;QACP,CAAC,EAAE,IAAI;KACR;IACD,OAAO,EAAE;QACP,CAAC,EAAE,IAAI;QACP,CAAC,EAAE,IAAI;KACR;IACD,OAAO,EAAE;QACP,CAAC,EAAE,IAAI;QACP,CAAC,EAAE,IAAI;KACR;IACD,MAAM,EAAE;QACN,CAAC,EAAE,IAAI;QACP,CAAC,EAAE,GAAG;KACP;IACD,MAAM,EAAE;QACN,CAAC,EAAE,GAAG;QACN,CAAC,EAAE,GAAG;KACP;IACD,MAAM,EAAE;QACN,CAAC,EAAE,GAAG;QACN,CAAC,EAAE,GAAG;KACP;IACD,MAAM,EAAE;QACN,CAAC,EAAE,GAAG;QACN,CAAC,EAAE,GAAG;KACP;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function getFolderFiles(pathname: string, recursive?: boolean): Promise<Array<string>>;
|
|
2
|
+
export declare function getFolderList(pathname: string): Promise<string[]>;
|
|
3
|
+
export declare function getFilenameExtension(filename: string): string | undefined;
|
|
4
|
+
export declare function replaceFilenameExtension(filename: string, newExtension: string): string;
|
|
5
|
+
export declare function addFilenameSuffix(pathname: string, suffix: string): string;
|
|
6
|
+
export declare function addFilenamePrefix(pathname: string, prefix: string): string;
|
|
7
|
+
export declare function removeFilenamePrefix(pathname: string, prefix: string): string;
|
|
8
|
+
export declare function getFilenameWithoutExtension(filename: string): string;
|
|
9
|
+
export declare function getAvailablePathname(pathname: string): Promise<string | null>;
|
|
10
|
+
export declare function getFilename(pathname: string): string;
|
|
11
|
+
export declare function getDirectory(pathname: string): string;
|
|
12
|
+
export declare function normalizeFilename(filename: string): string;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/index.ts"],"names":[],"mappings":"AAMA,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,UAAQ,GAChB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CA4BxB;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,qBAWnD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,sBAIpD;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,UAGrB;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UASjE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAIjE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAKpE;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,UAK3D;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,0BAQ1D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,UAE3C;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,UAE5C;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,UAoBjD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import fsSync from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export async function getFolderFiles(pathname, recursive = false) {
|
|
5
|
+
const list = await fs.readdir(pathname);
|
|
6
|
+
let { folder_list, file_list } = await list.reduce(async (_acc, name) => {
|
|
7
|
+
const acc = await _acc;
|
|
8
|
+
const is_directory = (await fs.lstat(path.join(pathname, name))).isDirectory();
|
|
9
|
+
if (is_directory)
|
|
10
|
+
acc.folder_list.push(name);
|
|
11
|
+
else
|
|
12
|
+
acc.file_list.push(name);
|
|
13
|
+
return acc;
|
|
14
|
+
}, Promise.resolve({ folder_list: [], file_list: [] }));
|
|
15
|
+
file_list = file_list.map((name) => path.join(pathname, name));
|
|
16
|
+
if (!recursive)
|
|
17
|
+
return file_list;
|
|
18
|
+
const subfolder_file_list = await Promise.all(folder_list.map((folder_name) => getFolderFiles(path.join(pathname, folder_name), recursive))).then((l) => l.flatMap((l) => l));
|
|
19
|
+
file_list.push(...subfolder_file_list);
|
|
20
|
+
return file_list;
|
|
21
|
+
}
|
|
22
|
+
export async function getFolderList(pathname) {
|
|
23
|
+
return Promise.all((await fs.readdir(pathname)).map(async (name) => {
|
|
24
|
+
const is_directory = (await fs.lstat(path.join(pathname, name))).isDirectory();
|
|
25
|
+
return { name, is_directory };
|
|
26
|
+
})).then((result) => result.filter((it) => it.is_directory).map((it) => it.name));
|
|
27
|
+
}
|
|
28
|
+
export function getFilenameExtension(filename) {
|
|
29
|
+
return ((filename.includes(".") && filename.split(".").slice(-1)[0]) || undefined);
|
|
30
|
+
}
|
|
31
|
+
export function replaceFilenameExtension(filename, newExtension) {
|
|
32
|
+
return `${getFilenameWithoutExtension(filename)}.${newExtension}`;
|
|
33
|
+
}
|
|
34
|
+
export function addFilenameSuffix(pathname, suffix) {
|
|
35
|
+
const directory = getDirectory(pathname);
|
|
36
|
+
const filename = getFilename(pathname);
|
|
37
|
+
const extension = getFilenameExtension(filename);
|
|
38
|
+
const filenameWithoutExtension = getFilenameWithoutExtension(filename);
|
|
39
|
+
return path.join(directory, `${filenameWithoutExtension}${suffix}.${extension}`);
|
|
40
|
+
}
|
|
41
|
+
export function addFilenamePrefix(pathname, prefix) {
|
|
42
|
+
const directory = getDirectory(pathname);
|
|
43
|
+
const filename = getFilename(pathname);
|
|
44
|
+
return path.join(directory, `${prefix}${filename}`);
|
|
45
|
+
}
|
|
46
|
+
export function removeFilenamePrefix(pathname, prefix) {
|
|
47
|
+
const directory = getDirectory(pathname);
|
|
48
|
+
const filename = getFilename(pathname);
|
|
49
|
+
if (!filename.startsWith(prefix))
|
|
50
|
+
return pathname;
|
|
51
|
+
return path.join(directory, filename.substring(prefix.length));
|
|
52
|
+
}
|
|
53
|
+
export function getFilenameWithoutExtension(filename) {
|
|
54
|
+
return ((filename.includes(".") && filename.split(".").slice(0, -1).join(".")) ||
|
|
55
|
+
filename);
|
|
56
|
+
}
|
|
57
|
+
export async function getAvailablePathname(pathname) {
|
|
58
|
+
for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) {
|
|
59
|
+
const availablePathname = i === 0 ? pathname : addFilenameSuffix(pathname, ` (${i})`);
|
|
60
|
+
if (!fsSync.existsSync(availablePathname))
|
|
61
|
+
return availablePathname;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function getFilename(pathname) {
|
|
66
|
+
return pathname.split("/").slice(-1)[0];
|
|
67
|
+
}
|
|
68
|
+
export function getDirectory(pathname) {
|
|
69
|
+
return pathname.split("/").slice(0, -1).join("/");
|
|
70
|
+
}
|
|
71
|
+
export function normalizeFilename(filename) {
|
|
72
|
+
return (filename
|
|
73
|
+
.normalize("NFD")
|
|
74
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
75
|
+
// Avoid multiple characters
|
|
76
|
+
.replace(/[ ]{2,}/g, " ")
|
|
77
|
+
.replace(/[.]{2,}/g, ".")
|
|
78
|
+
.replace(/[-]{2,}/g, "-")
|
|
79
|
+
// Replace ' - ' to '-'
|
|
80
|
+
.replace(/[ ][-][ ]/g, "-")
|
|
81
|
+
.replace(/[-][ ]/g, "-")
|
|
82
|
+
.replace(/[ ][-]/g, "-")
|
|
83
|
+
// Replace space to '_'
|
|
84
|
+
.replace(/[ ]/g, "_")
|
|
85
|
+
// Remove all not allowed characters
|
|
86
|
+
.replace(/[^-.0-9_a-zA-Z[]]/g, "")
|
|
87
|
+
// Remove all not alphanumeric characters on start
|
|
88
|
+
.replace(/^[^0-9a-zA-Z]/g, ""));
|
|
89
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/utils/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import fsSync from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export async function getFolderFiles(pathname, recursive = false) {
|
|
5
|
+
const list = await fs.readdir(pathname);
|
|
6
|
+
let { folder_list, file_list } = await list.reduce(async (_acc, name) => {
|
|
7
|
+
const acc = await _acc;
|
|
8
|
+
const is_directory = (await fs.lstat(path.join(pathname, name))).isDirectory();
|
|
9
|
+
if (is_directory)
|
|
10
|
+
acc.folder_list.push(name);
|
|
11
|
+
else
|
|
12
|
+
acc.file_list.push(name);
|
|
13
|
+
return acc;
|
|
14
|
+
}, Promise.resolve({ folder_list: [], file_list: [] }));
|
|
15
|
+
file_list = file_list.map((name) => path.join(pathname, name));
|
|
16
|
+
if (!recursive)
|
|
17
|
+
return file_list;
|
|
18
|
+
const subfolder_file_list = await Promise.all(folder_list.map((folder_name) => getFolderFiles(path.join(pathname, folder_name), recursive))).then((l) => l.flatMap((l) => l));
|
|
19
|
+
file_list.push(...subfolder_file_list);
|
|
20
|
+
return file_list;
|
|
21
|
+
}
|
|
22
|
+
export async function getFolderList(pathname) {
|
|
23
|
+
return Promise.all((await fs.readdir(pathname)).map(async (name) => {
|
|
24
|
+
const is_directory = (await fs.lstat(path.join(pathname, name))).isDirectory();
|
|
25
|
+
return { name, is_directory };
|
|
26
|
+
})).then((result) => result.filter((it) => it.is_directory).map((it) => it.name));
|
|
27
|
+
}
|
|
28
|
+
export function getFilenameExtension(filename) {
|
|
29
|
+
return ((filename.includes(".") && filename.split(".").slice(-1)[0]) || undefined);
|
|
30
|
+
}
|
|
31
|
+
export function replaceFilenameExtension(filename, newExtension) {
|
|
32
|
+
return `${getFilenameWithoutExtension(filename)}.${newExtension}`;
|
|
33
|
+
}
|
|
34
|
+
export function addFilenameSuffix(pathname, suffix) {
|
|
35
|
+
const directory = getDirectory(pathname);
|
|
36
|
+
const filename = getFilename(pathname);
|
|
37
|
+
const extension = getFilenameExtension(filename);
|
|
38
|
+
const filenameWithoutExtension = getFilenameWithoutExtension(filename);
|
|
39
|
+
return path.join(directory, `${filenameWithoutExtension}${suffix}.${extension}`);
|
|
40
|
+
}
|
|
41
|
+
export function addFilenamePrefix(pathname, prefix) {
|
|
42
|
+
const directory = getDirectory(pathname);
|
|
43
|
+
const filename = getFilename(pathname);
|
|
44
|
+
return path.join(directory, `${prefix}${filename}`);
|
|
45
|
+
}
|
|
46
|
+
export function removeFilenamePrefix(pathname, prefix) {
|
|
47
|
+
const directory = getDirectory(pathname);
|
|
48
|
+
const filename = getFilename(pathname);
|
|
49
|
+
if (!filename.startsWith(prefix))
|
|
50
|
+
return pathname;
|
|
51
|
+
return path.join(directory, filename.substring(prefix.length));
|
|
52
|
+
}
|
|
53
|
+
export function getFilenameWithoutExtension(filename) {
|
|
54
|
+
return ((filename.includes(".") && filename.split(".").slice(0, -1).join(".")) ||
|
|
55
|
+
filename);
|
|
56
|
+
}
|
|
57
|
+
export async function getAvailablePathname(pathname) {
|
|
58
|
+
for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) {
|
|
59
|
+
const availablePathname = i === 0 ? pathname : addFilenameSuffix(pathname, ` (${i})`);
|
|
60
|
+
if (!fsSync.existsSync(availablePathname))
|
|
61
|
+
return availablePathname;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function getFilename(pathname) {
|
|
66
|
+
return pathname.split("/").slice(-1)[0];
|
|
67
|
+
}
|
|
68
|
+
export function getDirectory(pathname) {
|
|
69
|
+
return pathname.split("/").slice(0, -1).join("/");
|
|
70
|
+
}
|
|
71
|
+
export function normalizeFilename(filename) {
|
|
72
|
+
return (filename
|
|
73
|
+
.normalize("NFD")
|
|
74
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
75
|
+
// Avoid multiple characters
|
|
76
|
+
.replace(/[ ]{2,}/g, " ")
|
|
77
|
+
.replace(/[.]{2,}/g, ".")
|
|
78
|
+
.replace(/[-]{2,}/g, "-")
|
|
79
|
+
// Replace ' - ' to '-'
|
|
80
|
+
.replace(/[ ][-][ ]/g, "-")
|
|
81
|
+
.replace(/[-][ ]/g, "-")
|
|
82
|
+
.replace(/[ ][-]/g, "-")
|
|
83
|
+
// Replace space to '_'
|
|
84
|
+
.replace(/[ ]/g, "_")
|
|
85
|
+
// Remove all not allowed characters
|
|
86
|
+
.replace(/[^-.0-9_a-zA-Z[]]/g, "")
|
|
87
|
+
// Remove all not alphanumeric characters on start
|
|
88
|
+
.replace(/^[^0-9a-zA-Z]/g, ""));
|
|
89
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function PathArgument(previous: string): string;
|
|
2
|
+
export declare function NumericOption(previous: string): number;
|
|
3
|
+
export declare function EnumOption<E extends readonly string[], T extends E[number]>(enumerator: E): (previous: string) => T;
|
|
4
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/lib/validation.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,UAI5C;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,UAK7C;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EACzE,UAAU,EAAE,CAAC,cAEK,MAAM,KAAG,CAAC,CAQ7B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { InvalidArgumentError, InvalidOptionArgumentError } from "commander";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
export function PathArgument(previous) {
|
|
4
|
+
if (!fs.existsSync(previous))
|
|
5
|
+
throw new InvalidArgumentError("No such a file or directory");
|
|
6
|
+
return previous;
|
|
7
|
+
}
|
|
8
|
+
export function NumericOption(previous) {
|
|
9
|
+
const result = parseInt(previous);
|
|
10
|
+
if (Number.isNaN(result))
|
|
11
|
+
throw new InvalidOptionArgumentError("Not a number");
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
export function EnumOption(enumerator) {
|
|
15
|
+
return (previous) => {
|
|
16
|
+
if (!enumerator.includes(previous)) {
|
|
17
|
+
throw new InvalidOptionArgumentError(`Expected '${enumerator.join(" | ")} ', received '${previous}'`);
|
|
18
|
+
}
|
|
19
|
+
return previous;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/lib/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC1B,MAAM,IAAI,oBAAoB,CAAC,6BAA6B,CAAC,CAAC;IAChE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACtB,MAAM,IAAI,0BAA0B,CAAC,cAAc,CAAC,CAAC;IACvD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,UAAa;IAEb,OAAO,CAAC,QAAgB,EAAK,EAAE;QAC7B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,0BAA0B,CAClC,aAAa,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,QAAQ,GAAG,CAChE,CAAC;QACJ,CAAC;QACD,OAAO,QAAa,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC"}
|
package/bin/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
|
package/bin/main.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// #!/usr/bin/env node
|
|
2
|
+
import { Command, CommanderError } from "commander";
|
|
3
|
+
// import { ZodError } from "zod";
|
|
4
|
+
import { FileType } from "./actions/action-compress.js";
|
|
5
|
+
import * as actions from "./actions/index.js";
|
|
6
|
+
import { Resolution } from "./lib/constants.js";
|
|
7
|
+
import { EnumOption, NumericOption, PathArgument } from "./lib/validation.js";
|
|
8
|
+
/** Setup */
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name("dev-toolkit")
|
|
12
|
+
.description("A simple CLI app to help developers in their tasks.");
|
|
13
|
+
program
|
|
14
|
+
.command("compress")
|
|
15
|
+
.description("Compress large files and save storage space.")
|
|
16
|
+
.argument("<pathname>", "Pathname", PathArgument)
|
|
17
|
+
.option("-V, --verbose <number>", "Level of pathname to log", NumericOption, 10)
|
|
18
|
+
.option("-T, --type <string>", "File type", EnumOption(FileType))
|
|
19
|
+
.option("-R, --resolution <string>", "Resolution", EnumOption(Resolution), "480p")
|
|
20
|
+
.option("-F, --force", "Ignores compressed flag")
|
|
21
|
+
.option("-H, --horizontal", "Force horizontal 16x9 aspect ratio")
|
|
22
|
+
.action(actions.compress.file);
|
|
23
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
24
|
+
if (err instanceof CommanderError) {
|
|
25
|
+
console.log(`Error (${err.code}): ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
else if (err instanceof Error) {
|
|
28
|
+
if (process.env.NODE_ENV === "development") {
|
|
29
|
+
console.error(err);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.error(err.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
package/bin/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,sBAAsB;AAEtB,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACpD,kCAAkC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE9E,YAAY;AACZ,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAC9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,qDAAqD,CAAC,CAAC;AAEtE,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,8CAA8C,CAAC;KAC3D,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,YAAY,CAAC;KAChD,MAAM,CACL,wBAAwB,EACxB,0BAA0B,EAC1B,aAAa,EACb,EAAE,CACH;KACA,MAAM,CAAC,qBAAqB,EAAE,WAAW,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;KAChE,MAAM,CACL,2BAA2B,EAC3B,YAAY,EACZ,UAAU,CAAC,UAAU,CAAC,EACtB,MAAM,CACP;KACA,MAAM,CAAC,aAAa,EAAE,yBAAyB,CAAC;KAChD,MAAM,CAAC,kBAAkB,EAAE,oCAAoC,CAAC;KAChE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAEjC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC7C,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACrD,CAAC;SAAM,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dev-toolkit-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"repository": "https://github.com/daviinacio/dev-toolkit",
|
|
5
|
+
"author": "Davi Inácio <aazz6850@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./bin/main.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"dtk": "./bin/main.js"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "tsc -w",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"unlink-cli": "yarn --global unlink dev-toolkit-cli",
|
|
16
|
+
"link-cli": "(yarn unlink-cli || true) && chmod +x bin/main.js && yarn --global link",
|
|
17
|
+
"prepublish": "yarn build",
|
|
18
|
+
"publish": "npm publish"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22.13.5",
|
|
22
|
+
"typescript": "^5.7.3"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"commander": "^13.1.0",
|
|
26
|
+
"mime": "^4.0.6",
|
|
27
|
+
"sharp": "^0.33.5"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { CommanderError } from "commander";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import mime from "mime";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import sharp from "sharp";
|
|
6
|
+
|
|
7
|
+
import { Resolution } from "../lib/constants.js";
|
|
8
|
+
import {
|
|
9
|
+
addFilenamePrefix,
|
|
10
|
+
getAvailablePathname,
|
|
11
|
+
getDirectory,
|
|
12
|
+
getFilename,
|
|
13
|
+
getFolderFiles,
|
|
14
|
+
normalizeFilename,
|
|
15
|
+
removeFilenamePrefix,
|
|
16
|
+
replaceFilenameExtension,
|
|
17
|
+
} from "../lib/utils/index.js";
|
|
18
|
+
import { check_dependencies, ffmpeg_video_compress } from "../lib/bash.js";
|
|
19
|
+
|
|
20
|
+
export const FileType = ["image", "video"] as const;
|
|
21
|
+
type FileType = (typeof FileType)[number];
|
|
22
|
+
|
|
23
|
+
const unsupportedFormats: { [key in FileType]: string[] } = {
|
|
24
|
+
image: ["image/webp", "image/gif", "image/svg+xml"],
|
|
25
|
+
video: [],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const compressedFlagPrefix = "compressed_" as const;
|
|
29
|
+
const compressTempPrefix = "temp." as const;
|
|
30
|
+
|
|
31
|
+
export type CompressFileArgs = {
|
|
32
|
+
verbose: number;
|
|
33
|
+
type: FileType;
|
|
34
|
+
resolution: Resolution;
|
|
35
|
+
force: boolean;
|
|
36
|
+
horizontal: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export async function file(
|
|
40
|
+
pathname: string,
|
|
41
|
+
{ verbose, type, resolution, force, horizontal }: CompressFileArgs
|
|
42
|
+
) {
|
|
43
|
+
await check_dependencies(["ffmpeg"]);
|
|
44
|
+
|
|
45
|
+
if (force) console.warn("⚠️ Ignoring already compressed files");
|
|
46
|
+
if (horizontal) console.warn("⚠️ Forcing horizontal aspect ratio 16x9");
|
|
47
|
+
|
|
48
|
+
let verbose_title = "";
|
|
49
|
+
|
|
50
|
+
const filePathList = await getFolderFiles(pathname, true);
|
|
51
|
+
|
|
52
|
+
for (let filePath of filePathList) {
|
|
53
|
+
const mimetype = mime.getType(filePath);
|
|
54
|
+
if (!mimetype) continue;
|
|
55
|
+
const fileType = mimetype.split("/")[0] as FileType;
|
|
56
|
+
if (
|
|
57
|
+
!(type ? [type] : FileType).includes(fileType as FileType) ||
|
|
58
|
+
(unsupportedFormats[fileType] || []).includes(mimetype) ||
|
|
59
|
+
(!force &&
|
|
60
|
+
[compressedFlagPrefix, compressTempPrefix].some((pf) =>
|
|
61
|
+
getFilename(filePath).startsWith(pf)
|
|
62
|
+
))
|
|
63
|
+
)
|
|
64
|
+
continue;
|
|
65
|
+
|
|
66
|
+
const title = filePath
|
|
67
|
+
.substring(pathname.length)
|
|
68
|
+
.split("/")
|
|
69
|
+
.slice(1, 1 + verbose)
|
|
70
|
+
.join("/");
|
|
71
|
+
|
|
72
|
+
if (title !== verbose_title)
|
|
73
|
+
console.log(`Compressing ${fileType} at:`, title);
|
|
74
|
+
|
|
75
|
+
verbose_title = title;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
if (fileType === "image") await compressImage(filePath);
|
|
79
|
+
else if (fileType === "video")
|
|
80
|
+
await compressVideo(filePath, resolution, horizontal);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error(" Error compressing", `"${filePath}"`);
|
|
83
|
+
// console.error("Error compressing", `"${filePath}"`, err.message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log("\nCompression successfully finished 🚀");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function compressImage(filePath: string) {
|
|
91
|
+
await sharp(filePath)
|
|
92
|
+
.webp()
|
|
93
|
+
.toFile(replaceFilenameExtension(filePath, "webp"));
|
|
94
|
+
await fs.rm(filePath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function compressVideo(
|
|
98
|
+
filePath: string,
|
|
99
|
+
resolution: Resolution,
|
|
100
|
+
force?: boolean
|
|
101
|
+
) {
|
|
102
|
+
const directory = getDirectory(filePath);
|
|
103
|
+
let filename = getFilename(filePath);
|
|
104
|
+
const normalizedFilename = normalizeFilename(filename);
|
|
105
|
+
|
|
106
|
+
if (filename !== normalizedFilename) {
|
|
107
|
+
const oldPathname = path.join(directory, filename);
|
|
108
|
+
const newPathname = await getAvailablePathname(
|
|
109
|
+
path.join(directory, normalizedFilename)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (!newPathname) {
|
|
113
|
+
throw new CommanderError(
|
|
114
|
+
1,
|
|
115
|
+
"P003",
|
|
116
|
+
"Could not find a new file destination"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const newFilename = getFilename(newPathname);
|
|
121
|
+
console.log(
|
|
122
|
+
" File renamed. From:",
|
|
123
|
+
`'${filename.trim()}'`,
|
|
124
|
+
"to:",
|
|
125
|
+
`'${newFilename.trim()}'`
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await fs.rename(oldPathname, newPathname);
|
|
129
|
+
filename = newFilename;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const input = path.join(directory, filename);
|
|
133
|
+
const output = addFilenamePrefix(input, compressTempPrefix);
|
|
134
|
+
|
|
135
|
+
await ffmpeg_video_compress(input, output, resolution, force).catch(
|
|
136
|
+
async (err) => {
|
|
137
|
+
await fs.rm(output);
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
await fs.rm(input);
|
|
143
|
+
await fs.rename(
|
|
144
|
+
output,
|
|
145
|
+
addFilenamePrefix(
|
|
146
|
+
removeFilenamePrefix(input, compressedFlagPrefix),
|
|
147
|
+
compressedFlagPrefix
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as compress from "./action-compress.js";
|
package/src/lib/bash.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { Resolution, resolutionMapping } from "./constants.js";
|
|
3
|
+
import { CommanderError } from "commander";
|
|
4
|
+
|
|
5
|
+
export const execAsync = (command: string) =>
|
|
6
|
+
new Promise((resolve, reject) => {
|
|
7
|
+
exec(command, (error, stdout, stderr) => {
|
|
8
|
+
if (error) reject(new Error(stderr));
|
|
9
|
+
else resolve(stdout);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const globalDependencies = ["ffmpeg"];
|
|
14
|
+
|
|
15
|
+
async function check_dependency(dependency: string): Promise<boolean> {
|
|
16
|
+
return execAsync(`${dependency} -h`)
|
|
17
|
+
.then(() => true)
|
|
18
|
+
.catch(() => false);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function check_dependencies(dependencies = globalDependencies) {
|
|
22
|
+
const missing_dependencies = [];
|
|
23
|
+
|
|
24
|
+
for (let dep of dependencies) {
|
|
25
|
+
if (!(await check_dependency(dep))) missing_dependencies.push(dep);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (missing_dependencies.length > 0) {
|
|
29
|
+
throw new CommanderError(
|
|
30
|
+
1,
|
|
31
|
+
"missing_dependencies",
|
|
32
|
+
missing_dependencies.map((dep) => `'${dep}'`).join(", ")
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function ffmpeg_video_compress(
|
|
38
|
+
input: string,
|
|
39
|
+
output: string,
|
|
40
|
+
resolution: Resolution = "480p",
|
|
41
|
+
forceResolution?: boolean
|
|
42
|
+
) {
|
|
43
|
+
const { w, h } = resolutionMapping[resolution];
|
|
44
|
+
|
|
45
|
+
return execAsync(
|
|
46
|
+
forceResolution
|
|
47
|
+
? `ffmpeg -i "${input}" -s ${w}x${h} -acodec copy -y "${output}"`
|
|
48
|
+
: `ffmpeg -i "${input}" -filter:v scale=-1:${h} -acodec copy -y "${output}"`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const resolutionMapping = {
|
|
2
|
+
"4320p": {
|
|
3
|
+
w: 7680,
|
|
4
|
+
h: 4320,
|
|
5
|
+
},
|
|
6
|
+
"2160p": {
|
|
7
|
+
w: 3840,
|
|
8
|
+
h: 2160,
|
|
9
|
+
},
|
|
10
|
+
"1440p": {
|
|
11
|
+
w: 2560,
|
|
12
|
+
h: 1440,
|
|
13
|
+
},
|
|
14
|
+
"1080p": {
|
|
15
|
+
w: 1920,
|
|
16
|
+
h: 1080,
|
|
17
|
+
},
|
|
18
|
+
"720p": {
|
|
19
|
+
w: 1280,
|
|
20
|
+
h: 720,
|
|
21
|
+
},
|
|
22
|
+
"480p": {
|
|
23
|
+
w: 854,
|
|
24
|
+
h: 480,
|
|
25
|
+
},
|
|
26
|
+
"360p": {
|
|
27
|
+
w: 640,
|
|
28
|
+
h: 360,
|
|
29
|
+
},
|
|
30
|
+
"240p": {
|
|
31
|
+
w: 426,
|
|
32
|
+
h: 240,
|
|
33
|
+
},
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export const Resolution = [
|
|
37
|
+
"4320p",
|
|
38
|
+
"2160p",
|
|
39
|
+
"1440p",
|
|
40
|
+
"1080p",
|
|
41
|
+
"720p",
|
|
42
|
+
"480p",
|
|
43
|
+
"360p",
|
|
44
|
+
"240p",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
export type Resolution = (typeof Resolution)[number];
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import fsSync from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
type FolderContent = { folder_list: Array<string>; file_list: Array<string> };
|
|
6
|
+
|
|
7
|
+
export async function getFolderFiles(
|
|
8
|
+
pathname: string,
|
|
9
|
+
recursive = false
|
|
10
|
+
): Promise<Array<string>> {
|
|
11
|
+
const list = await fs.readdir(pathname);
|
|
12
|
+
|
|
13
|
+
let { folder_list, file_list } = await list.reduce(async (_acc, name) => {
|
|
14
|
+
const acc = await _acc;
|
|
15
|
+
const is_directory = (
|
|
16
|
+
await fs.lstat(path.join(pathname, name))
|
|
17
|
+
).isDirectory();
|
|
18
|
+
|
|
19
|
+
if (is_directory) acc.folder_list.push(name);
|
|
20
|
+
else acc.file_list.push(name);
|
|
21
|
+
|
|
22
|
+
return acc;
|
|
23
|
+
}, Promise.resolve({ folder_list: [], file_list: [] } as FolderContent));
|
|
24
|
+
|
|
25
|
+
file_list = file_list.map((name) => path.join(pathname, name));
|
|
26
|
+
|
|
27
|
+
if (!recursive) return file_list;
|
|
28
|
+
|
|
29
|
+
const subfolder_file_list = await Promise.all(
|
|
30
|
+
folder_list.map((folder_name) =>
|
|
31
|
+
getFolderFiles(path.join(pathname, folder_name), recursive)
|
|
32
|
+
)
|
|
33
|
+
).then((l) => l.flatMap((l) => l));
|
|
34
|
+
|
|
35
|
+
file_list.push(...subfolder_file_list);
|
|
36
|
+
|
|
37
|
+
return file_list;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getFolderList(pathname: string) {
|
|
41
|
+
return Promise.all(
|
|
42
|
+
(await fs.readdir(pathname)).map(async (name) => {
|
|
43
|
+
const is_directory = (
|
|
44
|
+
await fs.lstat(path.join(pathname, name))
|
|
45
|
+
).isDirectory();
|
|
46
|
+
return { name, is_directory };
|
|
47
|
+
})
|
|
48
|
+
).then((result) =>
|
|
49
|
+
result.filter((it) => it.is_directory).map((it) => it.name)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getFilenameExtension(filename: string) {
|
|
54
|
+
return (
|
|
55
|
+
(filename.includes(".") && filename.split(".").slice(-1)[0]) || undefined
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function replaceFilenameExtension(
|
|
60
|
+
filename: string,
|
|
61
|
+
newExtension: string
|
|
62
|
+
) {
|
|
63
|
+
return `${getFilenameWithoutExtension(filename)}.${newExtension}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function addFilenameSuffix(pathname: string, suffix: string) {
|
|
67
|
+
const directory = getDirectory(pathname);
|
|
68
|
+
const filename = getFilename(pathname);
|
|
69
|
+
const extension = getFilenameExtension(filename);
|
|
70
|
+
const filenameWithoutExtension = getFilenameWithoutExtension(filename);
|
|
71
|
+
return path.join(
|
|
72
|
+
directory,
|
|
73
|
+
`${filenameWithoutExtension}${suffix}.${extension}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function addFilenamePrefix(pathname: string, prefix: string) {
|
|
78
|
+
const directory = getDirectory(pathname);
|
|
79
|
+
const filename = getFilename(pathname);
|
|
80
|
+
return path.join(directory, `${prefix}${filename}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function removeFilenamePrefix(pathname: string, prefix: string) {
|
|
84
|
+
const directory = getDirectory(pathname);
|
|
85
|
+
const filename = getFilename(pathname);
|
|
86
|
+
if (!filename.startsWith(prefix)) return pathname;
|
|
87
|
+
return path.join(directory, filename.substring(prefix.length));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getFilenameWithoutExtension(filename: string) {
|
|
91
|
+
return (
|
|
92
|
+
(filename.includes(".") && filename.split(".").slice(0, -1).join(".")) ||
|
|
93
|
+
filename
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function getAvailablePathname(pathname: string) {
|
|
98
|
+
for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) {
|
|
99
|
+
const availablePathname =
|
|
100
|
+
i === 0 ? pathname : addFilenameSuffix(pathname, ` (${i})`);
|
|
101
|
+
if (!fsSync.existsSync(availablePathname)) return availablePathname;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getFilename(pathname: string) {
|
|
108
|
+
return pathname.split("/").slice(-1)[0];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getDirectory(pathname: string) {
|
|
112
|
+
return pathname.split("/").slice(0, -1).join("/");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function normalizeFilename(filename: string) {
|
|
116
|
+
return (
|
|
117
|
+
filename
|
|
118
|
+
.normalize("NFD")
|
|
119
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
120
|
+
// Avoid multiple characters
|
|
121
|
+
.replace(/[ ]{2,}/g, " ")
|
|
122
|
+
.replace(/[.]{2,}/g, ".")
|
|
123
|
+
.replace(/[-]{2,}/g, "-")
|
|
124
|
+
// Replace ' - ' to '-'
|
|
125
|
+
.replace(/[ ][-][ ]/g, "-")
|
|
126
|
+
.replace(/[-][ ]/g, "-")
|
|
127
|
+
.replace(/[ ][-]/g, "-")
|
|
128
|
+
// Replace space to '_'
|
|
129
|
+
.replace(/[ ]/g, "_")
|
|
130
|
+
// Remove all not allowed characters
|
|
131
|
+
.replace(/[^-.0-9_a-zA-Z[]]/g, "")
|
|
132
|
+
// Remove all not alphanumeric characters on start
|
|
133
|
+
.replace(/^[^0-9a-zA-Z]/g, "")
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { InvalidArgumentError, InvalidOptionArgumentError } from "commander";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
|
|
4
|
+
export function PathArgument(previous: string) {
|
|
5
|
+
if (!fs.existsSync(previous))
|
|
6
|
+
throw new InvalidArgumentError("No such a file or directory");
|
|
7
|
+
return previous;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function NumericOption(previous: string) {
|
|
11
|
+
const result = parseInt(previous);
|
|
12
|
+
if (Number.isNaN(result))
|
|
13
|
+
throw new InvalidOptionArgumentError("Not a number");
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function EnumOption<E extends readonly string[], T extends E[number]>(
|
|
18
|
+
enumerator: E
|
|
19
|
+
) {
|
|
20
|
+
return (previous: string): T => {
|
|
21
|
+
if (!enumerator.includes(previous)) {
|
|
22
|
+
throw new InvalidOptionArgumentError(
|
|
23
|
+
`Expected '${enumerator.join(" | ")} ', received '${previous}'`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return previous as T;
|
|
27
|
+
};
|
|
28
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// #!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command, CommanderError } from "commander";
|
|
4
|
+
// import { ZodError } from "zod";
|
|
5
|
+
import { FileType } from "./actions/action-compress.js";
|
|
6
|
+
import * as actions from "./actions/index.js";
|
|
7
|
+
import { Resolution } from "./lib/constants.js";
|
|
8
|
+
import { EnumOption, NumericOption, PathArgument } from "./lib/validation.js";
|
|
9
|
+
|
|
10
|
+
/** Setup */
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name("dev-toolkit")
|
|
14
|
+
.description("A simple CLI app to help developers in their tasks.");
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command("compress")
|
|
18
|
+
.description("Compress large files and save storage space.")
|
|
19
|
+
.argument("<pathname>", "Pathname", PathArgument)
|
|
20
|
+
.option(
|
|
21
|
+
"-V, --verbose <number>",
|
|
22
|
+
"Level of pathname to log",
|
|
23
|
+
NumericOption,
|
|
24
|
+
10
|
|
25
|
+
)
|
|
26
|
+
.option("-T, --type <string>", "File type", EnumOption(FileType))
|
|
27
|
+
.option(
|
|
28
|
+
"-R, --resolution <string>",
|
|
29
|
+
"Resolution",
|
|
30
|
+
EnumOption(Resolution),
|
|
31
|
+
"480p"
|
|
32
|
+
)
|
|
33
|
+
.option("-F, --force", "Ignores compressed flag")
|
|
34
|
+
.option("-H, --horizontal", "Force horizontal 16x9 aspect ratio")
|
|
35
|
+
.action(actions.compress.file);
|
|
36
|
+
|
|
37
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
38
|
+
if (err instanceof CommanderError) {
|
|
39
|
+
console.log(`Error (${err.code}): ${err.message}`);
|
|
40
|
+
} else if (err instanceof Error) {
|
|
41
|
+
if (process.env.NODE_ENV === "development") {
|
|
42
|
+
console.error(err);
|
|
43
|
+
} else {
|
|
44
|
+
console.error(err.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"outDir": "./bin",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true
|
|
12
|
+
}
|
|
13
|
+
}
|