multifile support, added all filetypes, precision
This commit is contained in:
parent
981f8804f9
commit
57fdf245e1
11 changed files with 900 additions and 431 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -4,6 +4,8 @@ node_modules
|
|||
*.webm
|
||||
*.exe
|
||||
*.zip
|
||||
.gitignore
|
||||
*.webp
|
||||
*.ogg
|
||||
settings.json
|
||||
*test*
|
||||
/testing ground/
|
||||
/bin/index regular terminal.js
|
2
.npmrc
Normal file
2
.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
loglevel=silent
|
11
README.md
11
README.md
|
@ -6,4 +6,15 @@ Easy to use: Just drag a file on the executable and off it goes!
|
|||
|
||||

|
||||
|
||||
How to install(Windows with binaries):
|
||||
1. make a folder in `C:\Program Files` called "DMC", put release binaries and [ffmpeg windows full-build executables](https://github.com/GyanD/codexffmpeg/releases/) into `C:\Program Files\DMC\`
|
||||
2. Add ffmpeg to PATH: `[Win BTN] + R`, type `SystemPropertiesAdvanced`, click `Environment Variables`, under "User variables for (user)" find variable Path, click on it and edit, in the now open window click `new`, and paste `C:\Program Files\DMC\`.
|
||||
3. If the command `ffmpeg` in cmd works, you can now drag and drop files onto the binary and have it work!
|
||||
4. to set performance preset, doubleclick the binary
|
||||
|
||||
How to install(Windows, Linux, MacOS):
|
||||
1. get node.js from [here](https://nodejs.org)
|
||||
2. clone repo into any folder you like.
|
||||
3. run `npm install --save` to get all dependencies
|
||||
4. run `npm install -g {PATH_TO_PROJECT_FOLDER}`
|
||||
*might have bugs, currently videos only and windows only. Future plans will include audio: .ogg, and photos: .webm*
|
||||
|
|
121
bin/index.js
Normal file
121
bin/index.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
import fs from 'fs'
|
||||
import { Encoder } from "../lib/encoder.js"
|
||||
import { UI } from "../lib/ui.js"
|
||||
import termkit from "terminal-kit"
|
||||
import { SettingsManager } from "../lib/settingsManager.js"
|
||||
import path from "path"
|
||||
//get settings
|
||||
let settings = new SettingsManager()
|
||||
await settings.start()
|
||||
let resolve = path.resolve
|
||||
let term = termkit.terminal
|
||||
const ui = new UI(settings.settings, settings.currentSetting)
|
||||
|
||||
|
||||
/**
|
||||
* TODO : Adapt audio quality as well to accomodate long videos(Currently 5m is too much)
|
||||
* FIND A WAY TO COMPILE THIS:..
|
||||
*/
|
||||
|
||||
const inputList = process.argv.slice(2)
|
||||
//if launched without params
|
||||
if (!inputList[0]) {
|
||||
ui.startMenu() //stops program here
|
||||
}
|
||||
|
||||
//Parse file inputs (n Drag n drop or arguments)
|
||||
let filePaths = [], fileNames = [], fileTypes = []
|
||||
let presetIndexArg = undefined
|
||||
//if preset argument go through list from 2 and add argument
|
||||
if (inputList[0] == "-preset") {
|
||||
presetIndexArg = inputList[1]
|
||||
|
||||
for (let i = 2; i < inputList.length; i++) {
|
||||
let file
|
||||
file = resolve(inputList[i])
|
||||
|
||||
filePaths.push(file)
|
||||
|
||||
file = file.split("\\")
|
||||
file = file[file.length - 1]
|
||||
file = file.split(".")
|
||||
|
||||
fileTypes.push(file[1])
|
||||
fileNames.push(file[0])
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < inputList.length; i++) {
|
||||
let file
|
||||
file = resolve(inputList[i])
|
||||
|
||||
filePaths.push(file)
|
||||
|
||||
file = file.split("\\")
|
||||
file = file[file.length - 1]
|
||||
file = file.split(".")
|
||||
|
||||
fileTypes.push(file[1])
|
||||
fileNames.push(file[0])
|
||||
}
|
||||
}
|
||||
main()
|
||||
|
||||
async function main() {
|
||||
|
||||
//file checks
|
||||
let isListEncodable = true
|
||||
//check if all files exist
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
if (!fs.existsSync(filePaths[i])) {
|
||||
term.italic(`${filePaths[i]}`).bold.red(" <- Path or File doesn't exist\n")
|
||||
term.grey("press enter to exit...")
|
||||
isListEncodable = false
|
||||
term.inputField(function () { process.exit() })
|
||||
}
|
||||
}
|
||||
|
||||
//check if all files are valid formats
|
||||
if (isListEncodable) {
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
if (fileTypes[i] == "jpg" || fileTypes[i] == "JPG" || fileTypes[i] == "png" || fileTypes[i] == "PNG" || fileTypes[i] == "webp" ||
|
||||
fileTypes[i] == "webm" || fileTypes[i] == "mp4" || fileTypes[i] == "mov" || fileTypes[i] == "mkv" || fileTypes[i] == "avi" ||
|
||||
fileTypes[i] == "ogg" || fileTypes[i] == "mp3" || fileTypes[i] == "aiff" || fileTypes[i] == "wav" || fileTypes[i] == "flac") {
|
||||
}
|
||||
else {
|
||||
term.italic(`${fileTypes[i]}`).bold.red(` <- Unsupported format\n`)
|
||||
term.grey("press enter to exit...")
|
||||
isListEncodable = false
|
||||
term.inputField(function () { process.exit() })
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//start encoding all
|
||||
if (isListEncodable) {
|
||||
|
||||
let encoder = []
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
encoder.push(new Encoder(settings.settings, settings.currentSetting, presetIndexArg))
|
||||
console.log(`Encoding with "${settings.currentSetting.name}" preset...`)
|
||||
|
||||
if (fileTypes[i] == "jpg" || fileTypes[i] == "JPG" || fileTypes[i] == "png" || fileTypes[i] == "PNG" || fileTypes[i] == "webp") {
|
||||
ui.newBar(await encoder[i].encodePicture(filePaths[i], fileNames[i]))
|
||||
ui.updateBar("time=00:00:01", i, false, true)
|
||||
encoder[i].on("close", () => { ui.encodeFinished(i) })
|
||||
}
|
||||
else if (fileTypes[i] == "webm" || fileTypes[i] == "mp4" || fileTypes[i] == "mov" || fileTypes[i] == "mkv" || fileTypes[i] == "avi") {
|
||||
ui.newBar(await encoder[i].encodeVideo(filePaths[i], fileNames[i]))
|
||||
encoder[i].on("update", (chunk) => { ui.updateBar(chunk, i) })
|
||||
encoder[i].on("close", () => { ui.encodeFinished(i) })
|
||||
}
|
||||
else if (fileTypes[i] == "ogg" || fileTypes[i] == "mp3" || fileTypes[i] == "aiff" || fileTypes[i] == "wav" || fileTypes[i] == "flac") {
|
||||
ui.newBar(await encoder[i].encodeAudio(filePaths[i], fileNames[i]))
|
||||
encoder[i].on("update", (chunk) => { ui.updateBar(chunk, i, false) })
|
||||
encoder[i].on("close", () => { ui.encodeFinished(i) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
26
fieldTest.cjs
Normal file
26
fieldTest.cjs
Normal file
|
@ -0,0 +1,26 @@
|
|||
const child_process = require("child_process")
|
||||
const FileHound = require('filehound')
|
||||
|
||||
let exec = child_process.exec
|
||||
|
||||
let command = "npm run start"
|
||||
let testFiles = FileHound.create()
|
||||
.path("testing ground")
|
||||
.findSync()
|
||||
|
||||
testFiles.forEach((value, index) => {
|
||||
console.log(value, index)
|
||||
command += ` "${value}"`
|
||||
})
|
||||
console.log(command)
|
||||
let test = exec(command)
|
||||
test.stdout.on("data", (chunk) => {
|
||||
console.log(chunk)
|
||||
})
|
||||
test.stderr.on("data", (chunk) => {
|
||||
console.log(chunk)
|
||||
})
|
||||
|
||||
test.on("close", () => {
|
||||
console.log("finished")
|
||||
})
|
423
index.js
423
index.js
|
@ -1,423 +0,0 @@
|
|||
const fs = require('fs')
|
||||
const { exec, execSync } = require('child_process')
|
||||
const cliProgress = require('cli-progress')
|
||||
const term = require("terminal-kit").terminal
|
||||
|
||||
/**
|
||||
* TODO : Adapt audio quality as well to accomodate long videos(Currently 5m is too much)
|
||||
* FIND A WAY TO COMPILE THIS:..
|
||||
*/
|
||||
|
||||
//Parse file inputs (Drag n drop or arguments)
|
||||
inputList = process.argv.slice(2)
|
||||
input = inputList[0]
|
||||
let file, fileType, bar1
|
||||
//if launched without params
|
||||
if (!input) {
|
||||
startMenu()
|
||||
}
|
||||
else {
|
||||
file = input.split("\\")
|
||||
file = file[file.length - 1]
|
||||
|
||||
fileType = file.split(".")[1]
|
||||
|
||||
if (!fs.existsSync(input)) {
|
||||
term.italic(`${input}`).bold.red(" <- Path or File doesn't exist\n")
|
||||
term.grey("press enter to exit...")
|
||||
term.inputField(function () { process.exit() })
|
||||
}
|
||||
else {
|
||||
bar1 = new cliProgress.SingleBar({
|
||||
synchronousUpdate: true,
|
||||
align: "left",
|
||||
hideCursor: true
|
||||
}, cliProgress.Presets.shades_classic)
|
||||
|
||||
if (fileType == "jpg" || fileType == "JPG" || fileType == "png" || fileType == "PNG" || fileType == "webp") {
|
||||
encodePicture(input, file.split(".")[0])
|
||||
}
|
||||
else if (fileType == "webm" || fileType == "mp4" || fileType == "mov" || fileType == "mkv" || fileType == "avi") {
|
||||
encodeVideo(input, file.split(".")[0])
|
||||
}
|
||||
else if ("ogg" || "mp3" || "aiff" || "wav" || "flac") {
|
||||
encodeAudio(input, file.split(".")[0])
|
||||
}
|
||||
else {
|
||||
term.italic(`${file}`).bold.red(` <- Unsupported format\n`)
|
||||
term.grey("press enter to exit...")
|
||||
term.inputField(function () { process.exit() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function encodeVideo(path, out) {
|
||||
//create progress bar
|
||||
|
||||
const [command, presetName, duration, isTwoPass] = await constructVideoCommand(path, out)
|
||||
bar1.start(duration, 0, { speed: "N/A" })
|
||||
let isPastHalf = false
|
||||
let encoder = exec(command)
|
||||
encoder.stderr.on("data", (chunk) => {
|
||||
currentTime = chunk.split("time=")[1]?.split(" ")[0]
|
||||
if (currentTime) {
|
||||
const arr = currentTime.split(":") // splitting the string by colon
|
||||
let seconds = Number.parseFloat(arr[0] * 3600 + arr[1] * 60 + (+arr[2])) // converting to s
|
||||
|
||||
console.clear()
|
||||
//If 2nd pass add that portion in
|
||||
console.log(`Encoding ${out}.webm with "${presetName}" preset...`)
|
||||
if (isTwoPass) {
|
||||
if (seconds / 2 >= (duration - 0.2) / 2) isPastHalf = true
|
||||
isPastHalf ? bar1.update(Math.round(seconds * 50) / 100 + (duration / 2)) : bar1.update(Math.round(seconds * 50) / 100)
|
||||
}
|
||||
else {
|
||||
bar1.update(Math.round(seconds * 100) / 100)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
encoder.on("close", () => {
|
||||
console.clear()
|
||||
bar1.stop()
|
||||
fs.rm("ffmpeg2pass-0.log", (error) => { error })
|
||||
term.bold.green("Finished!\n")
|
||||
term.grey("press enter to exit...\n")
|
||||
term.inputField(() => { process.exit() })
|
||||
})
|
||||
}
|
||||
async function encodeAudio(path, out) {
|
||||
let [duration, resolution] = await getDurationAndResolution(path)
|
||||
const bitrateLimit = Math.round(62000 / duration)
|
||||
|
||||
bar1.start(duration, 0, { speed: "N/A" })
|
||||
|
||||
const encoder = exec(`ffmpeg -y -i "${path}" -c:a libvorbis -b:a ${bitrateLimit}k ${out}.ogg`)
|
||||
|
||||
encoder.stderr.on("data", (chunk) => {
|
||||
currentTime = chunk.split("time=")[1]?.split(" ")[0]
|
||||
if (currentTime) {
|
||||
const arr = currentTime.split(":")
|
||||
let seconds = Number.parseFloat(arr[0] * 3600 + arr[1] * 60 + (+arr[2])) // converting to s
|
||||
console.clear()
|
||||
console.log(`Encoding ${out}.ogg`)
|
||||
bar1.update(Math.round(seconds * 100) / 100)
|
||||
}
|
||||
})
|
||||
encoder.on("close", () => {
|
||||
console.clear()
|
||||
term.bold.green("Finished!\n")
|
||||
term.grey("press enter to exit...\n")
|
||||
term.inputField(() => { process.exit() })
|
||||
})
|
||||
}
|
||||
|
||||
async function encodePicture(path, out) {
|
||||
const encoder = exec(`ffmpeg -y -i "${path}" -qscale 80 -compression_level 6 ${out}.webp`)
|
||||
encoder.stderr.on("data", (chunk) => {
|
||||
console.clear()
|
||||
term.yellow(`Encoding ${out}.webp...`)
|
||||
|
||||
})
|
||||
encoder.on("close", () => {
|
||||
console.clear()
|
||||
term.bold.green("Finished!\n")
|
||||
term.grey("press enter to exit...\n")
|
||||
term.inputField(() => { process.exit() })
|
||||
})
|
||||
}
|
||||
|
||||
async function constructVideoCommand(path, out) {
|
||||
|
||||
//gets settings file, if doesnt exist makes a new file and uses those defaults
|
||||
let settings = await getSettings().catch(async (err) => {
|
||||
settings = undefined
|
||||
})
|
||||
if (!settings) settings = await makeNewSettingsFile()
|
||||
settings = JSON.parse(settings.toString())
|
||||
settings = settings.presets[settings.currentSetting]
|
||||
|
||||
let [duration, resolutionHeight] = await getDurationAndResolution(path)
|
||||
//Calculates video bitrate to fit right under 8mb @224kb vorbis audio bitrate
|
||||
const bitrateLimit = Math.round((62000 - (224 * duration)) / duration)
|
||||
|
||||
let command = ""
|
||||
let crfIndex = 0
|
||||
let isTwoPass = true
|
||||
while (resolutionHeight > settings.crfMap[crfIndex].resolution) {
|
||||
crfIndex++
|
||||
//if the resolution is still higher, just use highest res
|
||||
if (!settings.crfMap[crfIndex]?.resolution) {
|
||||
crfIndex--
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (pass = 1; pass <= 2; pass++) {
|
||||
command += `ffmpeg -y -i "${path}" -vcodec libvpx-vp9 -acodec libvorbis -qscale:a 7 `
|
||||
command += `-deadline ${settings.deadline} `
|
||||
command += `-cpu-used ${settings.cpuUsed} `
|
||||
if (settings?.minrate) {
|
||||
command += `-b:v ${Math.round(bitrateLimit * 0.95)}k `
|
||||
command += `-minrate ${Math.round(bitrateLimit / 100 * settings.minrate)}k `
|
||||
command += `-maxrate ${bitrateLimit}k `
|
||||
}
|
||||
else {
|
||||
command += `-b:v ${bitrateLimit}k `
|
||||
command += `-crf ${settings.crfMap[crfIndex].crf} `
|
||||
}
|
||||
//realtime doesnt support two pass
|
||||
if (settings.deadline == "realtime") {
|
||||
command += `-row-mt 1 "${out}.webm"`
|
||||
isTwoPass = false
|
||||
break
|
||||
}
|
||||
pass == 1 ? command += `-pass 1 -row-mt 1 -f webm NUL && ` : command += `-pass 2 -row-mt 1 "${out}.webm" `
|
||||
}
|
||||
return [command, settings.name, duration, isTwoPass]
|
||||
}
|
||||
|
||||
async function getDurationAndResolution(file) {
|
||||
let query = await ffprobe(file)
|
||||
//duration in seconds
|
||||
duration = query.split("Duration: ")[1].split(",")[0]
|
||||
const arr = duration.split(":") // splitting the string by colon
|
||||
const seconds = arr[0] * 3600 + arr[1] * 60 + (+arr[2]) // converting to s
|
||||
|
||||
//resolution height
|
||||
resolutionHeight = query.split("Stream #0:0")[1]?.split(",")[2].split(" ")[1].split("x")[1]
|
||||
|
||||
return [Number.parseFloat(seconds), resolutionHeight]
|
||||
}
|
||||
|
||||
function ffprobe(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`ffprobe "${file}"`, (error, stdout, stderr) => {
|
||||
resolve(stderr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getSettings() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSettings = fs.readFile("settings.json", (err, data) => {
|
||||
resolve(data)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function startMenu() {
|
||||
console.clear()
|
||||
//gets settings file, if doesnt exist makes a new file and uses those defaults
|
||||
let settings = await getSettings().catch(async (err) => {
|
||||
settings = undefined
|
||||
})
|
||||
if (!settings) settings = await makeNewSettingsFile()
|
||||
|
||||
settings = JSON.parse(settings.toString())
|
||||
let menu = []
|
||||
for (i = 0; i < settings.presets.length; i++) {
|
||||
menu.push(`${i}. ${settings.presets[i].name}`)
|
||||
}
|
||||
term.italic("How to convert: [app] [filename.extension]\n")
|
||||
term.yellow("Hello! This menu is for selecting performance/speed preset.\n")
|
||||
term.yellow("Currently using ").bgMagenta(`"${settings.presets[settings.currentSetting].name}"`).yellow(" preset")
|
||||
term.singleColumnMenu(menu, (error, response) => {
|
||||
settings.currentSetting = response.selectedIndex
|
||||
fs.writeFileSync("settings.json", JSON.stringify(settings))
|
||||
term.green("\n Using").green.bold(` ${settings.presets[settings.currentSetting].name} `).green("setting\n")
|
||||
term.grey("Press enter to exit...")
|
||||
term.inputField(() => { process.exit() })
|
||||
})
|
||||
}
|
||||
|
||||
function makeNewSettingsFile() {
|
||||
const settings = `
|
||||
{
|
||||
"currentSetting": 2,
|
||||
"presets": [{
|
||||
"name": "Most efficient 8 megabytes of your life",
|
||||
"cpuUsed": 0,
|
||||
"deadline": "best",
|
||||
"minrate": 90,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I have some time to kill",
|
||||
"cpuUsed": 1,
|
||||
"deadline": "good",
|
||||
"minrate": 75,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 17
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 15
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Mid",
|
||||
"cpuUsed": 3,
|
||||
"deadline": "good",
|
||||
"minrate":75,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 25
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 15
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I don't like waiting",
|
||||
"cpuUsed": 4,
|
||||
"deadline": 100,
|
||||
"minrate": 90,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 45
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 42
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 40
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 35
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 25
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 20
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I want it, NOW!",
|
||||
"cpuUsed": 4,
|
||||
"deadline": "realtime",
|
||||
"minrate": 50,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 40
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 35
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 25
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 15
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
`
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile("settings.json", settings, () => {
|
||||
resolve(settings)
|
||||
})
|
||||
})
|
||||
}
|
138
lib/encoder.js
Normal file
138
lib/encoder.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
import { exec } from 'child_process'
|
||||
|
||||
export class Encoder {
|
||||
currentSetting
|
||||
settings
|
||||
encoder
|
||||
encodePresetIndexArg
|
||||
constructor(settings, currentSetting, encodePresetIndexArg = undefined) {
|
||||
this.settings = settings
|
||||
this.currentSetting = currentSetting
|
||||
this.encodePresetIndexArg = encodePresetIndexArg
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} path absolute path to file
|
||||
* @param {String} out output filename
|
||||
* @returns [duration, isTwoPass]
|
||||
*/
|
||||
async encodeVideo(path, out) {
|
||||
//create progress bar
|
||||
const [command, duration, isTwoPass] = await this.#constructVideoCommand(path, out)
|
||||
this.encoder = exec(command)
|
||||
return [duration, out, isTwoPass]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} path absolute path to file
|
||||
* @param {String} out output filename
|
||||
* @returns duration
|
||||
*/
|
||||
async encodeAudio(path, out) {
|
||||
let [duration, resolution] = await this.#getDurationAndResolution(path)
|
||||
const videoBitRate = Math.round(62000 / duration)
|
||||
this.encoder = exec(`ffmpeg -y -i "${path}" -c:a libvorbis -b:a ${videoBitRate}k ${out}.ogg`)
|
||||
return [duration, out, undefined]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} path absolute path to file
|
||||
* @param {String} out output filename
|
||||
*/
|
||||
async encodePicture(path, out) {
|
||||
this.encoder = exec(`ffmpeg -y -i "${path}" -qscale 80 -compression_level 6 ${out}.webp`)
|
||||
return [1, out, undefined]
|
||||
}
|
||||
|
||||
async #constructVideoCommand(path, out) {
|
||||
let [duration, resolutionHeight] = await this.#getDurationAndResolution(path)
|
||||
|
||||
//Calculates video bitrate to fit right under 8mb 2:6 audio:video
|
||||
const audioBitRate = Math.round(62000 / 8 * 2 / duration)
|
||||
const videoBitRate = Math.round(62000 / 8 * 6 / duration)
|
||||
|
||||
//if command had argument of anotehr quality setting change to use that setting
|
||||
if (this.encodePresetIndexArg) {
|
||||
this.currentSetting = this.settings.presets[this.encodePresetIndexArg]
|
||||
}
|
||||
|
||||
let command = ""
|
||||
let crfIndex = 0
|
||||
let isTwoPass = true
|
||||
//Compares current video height to CRFMAP to determine optimal CRF
|
||||
while (resolutionHeight > this.currentSetting.crfMap[crfIndex].resolution) {
|
||||
crfIndex++
|
||||
//if the resolution is still higher, just use highest res
|
||||
if (!this.currentSetting.crfMap[crfIndex]?.resolution) {
|
||||
crfIndex--
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (let pass = 1; pass <= 2; pass++) {
|
||||
command += `ffmpeg -y -i "${path}" -vcodec libvpx-vp9 -acodec libvorbis `
|
||||
command += `-deadline ${this.currentSetting.deadline} `
|
||||
command += `-cpu-used ${this.currentSetting.cpuUsed} `
|
||||
if (this.currentSetting?.minrate) {
|
||||
command += `-b:v ${Math.round(videoBitRate * 0.95)}k `
|
||||
command += `-minrate ${Math.round(videoBitRate / 100 * this.currentSetting.minrate)}k `
|
||||
command += `-maxrate ${videoBitRate}k `
|
||||
}
|
||||
else {
|
||||
command += `-b:v ${videoBitRate}k `
|
||||
command += `-b:a ${audioBitRate}k `
|
||||
command += `-crf ${this.currentSetting.crfMap[crfIndex].crf} `
|
||||
}
|
||||
//realtime doesnt support two pass
|
||||
if (this.currentSetting.deadline == "realtime") {
|
||||
command += `-row-mt 1 "${out}.webm"`
|
||||
isTwoPass = false
|
||||
break
|
||||
}
|
||||
pass == 1 ? command += `-pass 1 -row-mt 1 -f webm NUL && ` : command += `-pass 2 -row-mt 1 "${out}.webm" `
|
||||
}
|
||||
return [command, duration, isTwoPass]
|
||||
}
|
||||
|
||||
async #getDurationAndResolution(file) {
|
||||
let query = await this.#ffprobe(file)
|
||||
//duration in seconds
|
||||
const duration = query.split("Duration: ")[1].split(",")[0]
|
||||
const arr = duration.split(":") // splitting the string by colon
|
||||
const seconds = arr[0] * 3600 + arr[1] * 60 + (+arr[2]) // converting to s
|
||||
|
||||
//resolution height
|
||||
const resolutionHeight = query.split("Stream #0:0")[1]?.split(",")[2].split(" ")[1].split("x")[1]
|
||||
|
||||
return [Number.parseFloat(seconds), resolutionHeight]
|
||||
}
|
||||
|
||||
#ffprobe(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`ffprobe "${file}"`, (error, stdout, stderr) => {
|
||||
resolve(stderr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
on(channel, callback) {
|
||||
switch (channel) {
|
||||
case "close":
|
||||
this.encoder.on("close", () => {
|
||||
callback(true)
|
||||
})
|
||||
break
|
||||
case "update":
|
||||
this.encoder.stderr.on("data", (chunk) => {
|
||||
callback(chunk)
|
||||
})
|
||||
break
|
||||
default:
|
||||
throw new Error("Incorrect Channel")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
221
lib/settingsManager.js
Normal file
221
lib/settingsManager.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
import fs from "fs"
|
||||
|
||||
export class SettingsManager {
|
||||
|
||||
settings
|
||||
currentSetting
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
async start() {
|
||||
await this.#init()
|
||||
}
|
||||
|
||||
async #init() {
|
||||
this.settings = await this.#getSettings().catch(async (err) => {
|
||||
this.settings = undefined
|
||||
})
|
||||
if (!this.settings) this.settings = await this.#makeNewSettingsFile()
|
||||
this.settings = JSON.parse(this.settings.toString())
|
||||
this.currentSetting = this.settings.presets[this.settings.currentSetting]
|
||||
}
|
||||
async #getSettings() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSettings = fs.readFile("settings.json", (err, data) => {
|
||||
resolve(data)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async #makeNewSettingsFile() {
|
||||
const settings = `
|
||||
{
|
||||
"currentSetting": 2,
|
||||
"presets": [{
|
||||
"name": "Most efficient 8 megabytes of your life",
|
||||
"cpuUsed": 0,
|
||||
"deadline": "best",
|
||||
"minrate": 90,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 1
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I have some time to kill",
|
||||
"cpuUsed": 1,
|
||||
"deadline": "good",
|
||||
"minrate": 75,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 17
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 15
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Mid",
|
||||
"cpuUsed": 3,
|
||||
"deadline": "good",
|
||||
"minrate":75,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 25
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 15
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I don't like waiting",
|
||||
"cpuUsed": 4,
|
||||
"deadline": 100,
|
||||
"minrate": 90,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 45
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 42
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 40
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 35
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 25
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 20
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "I want it, NOW!",
|
||||
"cpuUsed": 4,
|
||||
"deadline": "realtime",
|
||||
"minrate": 50,
|
||||
"crfMap": [{
|
||||
"resolution": 240,
|
||||
"crf": 40
|
||||
},
|
||||
{
|
||||
"resolution": 360,
|
||||
"crf": 35
|
||||
},
|
||||
{
|
||||
"resolution": 480,
|
||||
"crf": 30
|
||||
},
|
||||
{
|
||||
"resolution": 720,
|
||||
"crf": 25
|
||||
},
|
||||
{
|
||||
"resolution": 1080,
|
||||
"crf": 20
|
||||
},
|
||||
{
|
||||
"resolution": 1440,
|
||||
"crf": 15
|
||||
},
|
||||
{
|
||||
"resolution": 2160,
|
||||
"crf": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
`
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile("settings.json", settings, () => {
|
||||
resolve(settings)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
121
lib/ui.js
Normal file
121
lib/ui.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
import termkit from "terminal-kit"
|
||||
import cliProgress from "cli-progress"
|
||||
import fs from "fs"
|
||||
|
||||
export class UI {
|
||||
term
|
||||
bars = []
|
||||
multibar
|
||||
settings
|
||||
currentSetting
|
||||
|
||||
constructor(settings, currentSetting) {
|
||||
this.term = termkit.terminal
|
||||
|
||||
this.multibar = new cliProgress.MultiBar({
|
||||
format: '[{bar}] {percentage}% | output: "{filename}" | ETA: {eta}s | {value}/{total}',
|
||||
align: "left",
|
||||
hideCursor: true,
|
||||
autopadding: true,
|
||||
}, cliProgress.Presets.shades_grey)
|
||||
|
||||
this.settings = settings
|
||||
this.currentSetting = currentSetting
|
||||
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Number} duration Duration of the encoded media
|
||||
* @param {String} filename name of the encoding file
|
||||
* @param {Boolean} isTwoPass is the encoded media two pass
|
||||
* @returns
|
||||
*/
|
||||
async newBar(encoderOutput) {
|
||||
let duration = encoderOutput[0]
|
||||
let filename = encoderOutput[1]
|
||||
let isTwoPass = encoderOutput[2]
|
||||
this.bars.push({
|
||||
"bar": this.multibar.create(duration, 0, { speed: "N/A" }),
|
||||
"isTwoPass": isTwoPass,
|
||||
"isPastHalf": false,
|
||||
"filename": filename,
|
||||
"duration": duration,
|
||||
"finished": false
|
||||
}
|
||||
)
|
||||
const barIndex = this.bars.length - 1
|
||||
return barIndex
|
||||
}
|
||||
|
||||
async updateBar(chunk, barIndex = 0, isVideo = true, isImage = false) {
|
||||
if (!chunk) return
|
||||
if (isImage) {
|
||||
this.bars[barIndex]?.bar.update(1, { filename: `${this.bars[barIndex].filename}.webp` })
|
||||
return
|
||||
}
|
||||
if (isVideo) {
|
||||
const currentTime = chunk.split("time=")[1]?.split(" ")[0]
|
||||
if (!currentTime) return
|
||||
const arr = currentTime.split(":")
|
||||
let seconds = Number.parseFloat(arr[0] * 3600 + arr[1] * 60 + (+arr[2])) // converting to s
|
||||
|
||||
//If 2 pass divide bar into two parts to show both in progress in one progress
|
||||
if (this.bars[barIndex].isTwoPass) {
|
||||
if (seconds / 2 >= (this.bars[barIndex].duration - 0.2) / 2) this.bars[barIndex].isPastHalf = true
|
||||
|
||||
if (this.bars[barIndex].isPastHalf) this.bars[barIndex].bar.update(Math.round(seconds * 50) / 100 + (this.bars[barIndex].duration / 2), { filename: `${this.bars[barIndex].filename}.webm` })
|
||||
else this.bars[barIndex].bar.update(Math.round(seconds * 50) / 100, { filename: `${this.bars[barIndex].filename}.webm` })
|
||||
}
|
||||
else {
|
||||
this.bars[barIndex].bar.update(Math.round(seconds * 100) / 100, { filename: `${this.bars[barIndex].filename}.webm` })
|
||||
}
|
||||
}
|
||||
else {
|
||||
const currentTime = chunk.split("time=")[1]?.split(" ")[0]
|
||||
if (!currentTime) return
|
||||
const arr = currentTime.split(":")
|
||||
let seconds = Number.parseFloat(arr[0] * 3600 + arr[1] * 60 + (+arr[2])) // converting to s
|
||||
this.bars[barIndex].bar.update(Math.round(seconds * 100) / 100, { filename: `${this.bars[barIndex].filename}.ogg` })
|
||||
}
|
||||
}
|
||||
|
||||
encodeFinished(barIndex) {
|
||||
this.bars[barIndex].fininshed = true
|
||||
// if all are finished stop multibars and exit
|
||||
for (let i = 0; i < this.bars.length; i++) {
|
||||
if (this.bars[i].finished) return
|
||||
}
|
||||
this.multibar.stop()
|
||||
fs.rm("ffmpeg2pass-0.log", (error) => { error })
|
||||
console.clear()
|
||||
this.term.bold.green("Finished!\n")
|
||||
this.term.grey("press enter to exit...\n")
|
||||
this.term.inputField(() => { process.exit() })
|
||||
}
|
||||
|
||||
async startMenu() {
|
||||
await this.#menu()
|
||||
}
|
||||
async #menu() {
|
||||
console.clear()
|
||||
let menu = []
|
||||
for (let i = 0; i < settings.presets.length; i++) {
|
||||
menu.push(`${i}. ${settings.presets[i].name}`)
|
||||
}
|
||||
this.term("How to convert: [app] [optional: -preset {Index}] [filename.extension(s)]\n")
|
||||
this.term("examples: \n")
|
||||
this.term.italic(" npx DMC -preset 0 file.mp3 file4.mov img.jpg\n")
|
||||
this.term.italic(" DMC.exe -preset 2 file34.wav file2.mp3\n\n")
|
||||
this.term.yellow("Hello! This menu is for selecting performance/speed preset.\n")
|
||||
this.term.yellow("Currently using ").bgMagenta(`"${settings.presets[settings.currentSetting].name}"`).yellow(" preset")
|
||||
this.term.singleColumnMenu(menu, (error, response) => {
|
||||
currentSetting = response.selectedIndex
|
||||
fs.writeFileSync("settings.json", JSON.stringify(settings))
|
||||
this.term.green("\n Using").green.bold(` ${settings.presets[settings.currentSetting].name} `).green("setting\n")
|
||||
this.term.grey("Press enter to exit...")
|
||||
this.term.inputField(() => { process.exit() })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
251
package-lock.json
generated
251
package-lock.json
generated
|
@ -1,21 +1,22 @@
|
|||
{
|
||||
"name": "discord-media-compressor-8mb",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "discord-media-compressor-8mb",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cli-progress": "^3.11.0",
|
||||
"filehound": "^1.17.6",
|
||||
"pkg": "^5.6.0",
|
||||
"require-runtime": "^2.0.0",
|
||||
"terminal-kit": "^2.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"discord-media-compressor-8mb": "index.js"
|
||||
"DMC": "bin/index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
|
@ -157,6 +158,11 @@
|
|||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
|
@ -199,6 +205,19 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
|
@ -383,6 +402,11 @@
|
|||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
|
@ -481,6 +505,11 @@
|
|||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/err-code": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
|
||||
"integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA="
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
|
@ -546,6 +575,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
|
@ -574,6 +608,49 @@
|
|||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/file-js": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/file-js/-/file-js-0.3.0.tgz",
|
||||
"integrity": "sha1-+rRr94I0bJKUSZ8fDSrQfYOPJdE=",
|
||||
"dependencies": {
|
||||
"bluebird": "^3.4.7",
|
||||
"minimatch": "^3.0.3",
|
||||
"proper-lockfile": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-js/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/file-js/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/filehound": {
|
||||
"version": "1.17.6",
|
||||
"resolved": "https://registry.npmjs.org/filehound/-/filehound-1.17.6.tgz",
|
||||
"integrity": "sha512-5q4zjFkI8W2zLmvbvyvI//K882IpEj6sMNXPUQlk5H6W4Wh3OSSylEAIEmMLELP9G7ileYjTKPXOn0YzzS55Lg==",
|
||||
"dependencies": {
|
||||
"bluebird": "^3.7.2",
|
||||
"file-js": "0.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^5.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"unit-compare": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
|
@ -869,6 +946,11 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
|
@ -911,6 +993,17 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
|
||||
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
|
@ -921,6 +1014,14 @@
|
|||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
||||
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -1230,6 +1331,17 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proper-lockfile": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-1.2.0.tgz",
|
||||
"integrity": "sha1-zv9d2J0+XxD7deHo52vHWAGlnDQ=",
|
||||
"dependencies": {
|
||||
"err-code": "^1.0.0",
|
||||
"extend": "^3.0.0",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"retry": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
|
@ -1315,6 +1427,14 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
|
||||
"integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
|
@ -1633,6 +1753,14 @@
|
|||
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
|
||||
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8="
|
||||
},
|
||||
"node_modules/unit-compare": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unit-compare/-/unit-compare-1.0.1.tgz",
|
||||
"integrity": "sha1-DHRZ8OW/U2N+qHPKPO4Y3i7so4Y=",
|
||||
"dependencies": {
|
||||
"moment": "^2.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
|
@ -1917,6 +2045,11 @@
|
|||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
|
@ -1944,6 +2077,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
|
@ -2076,6 +2222,11 @@
|
|||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
|
@ -2151,6 +2302,11 @@
|
|||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"err-code": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
|
||||
"integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA="
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
|
@ -2188,6 +2344,11 @@
|
|||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
|
@ -2213,6 +2374,48 @@
|
|||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"file-js": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/file-js/-/file-js-0.3.0.tgz",
|
||||
"integrity": "sha1-+rRr94I0bJKUSZ8fDSrQfYOPJdE=",
|
||||
"requires": {
|
||||
"bluebird": "^3.4.7",
|
||||
"minimatch": "^3.0.3",
|
||||
"proper-lockfile": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"filehound": {
|
||||
"version": "1.17.6",
|
||||
"resolved": "https://registry.npmjs.org/filehound/-/filehound-1.17.6.tgz",
|
||||
"integrity": "sha512-5q4zjFkI8W2zLmvbvyvI//K882IpEj6sMNXPUQlk5H6W4Wh3OSSylEAIEmMLELP9G7ileYjTKPXOn0YzzS55Lg==",
|
||||
"requires": {
|
||||
"bluebird": "^3.7.2",
|
||||
"file-js": "0.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^5.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"unit-compare": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
|
@ -2435,6 +2638,11 @@
|
|||
"type-check": "~0.3.2"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
|
@ -2462,6 +2670,14 @@
|
|||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
|
||||
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
|
@ -2472,6 +2688,11 @@
|
|||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
|
||||
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -2698,6 +2919,17 @@
|
|||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
|
||||
},
|
||||
"proper-lockfile": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-1.2.0.tgz",
|
||||
"integrity": "sha1-zv9d2J0+XxD7deHo52vHWAGlnDQ=",
|
||||
"requires": {
|
||||
"err-code": "^1.0.0",
|
||||
"extend": "^3.0.0",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"retry": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
|
@ -2757,6 +2989,11 @@
|
|||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"retry": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
|
||||
"integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q="
|
||||
},
|
||||
"reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
|
@ -2988,6 +3225,14 @@
|
|||
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
|
||||
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8="
|
||||
},
|
||||
"unit-compare": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unit-compare/-/unit-compare-1.0.1.tgz",
|
||||
"integrity": "sha1-DHRZ8OW/U2N+qHPKPO4Y3i7so4Y=",
|
||||
"requires": {
|
||||
"moment": "^2.14.1"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
|
|
11
package.json
11
package.json
|
@ -3,10 +3,14 @@
|
|||
"version": "1.0.1",
|
||||
"description": "helps free discord users to send any media(image, video, audio) and not get limited by discords 8mb file limit",
|
||||
"main": "index.js",
|
||||
"bin": "./index.js",
|
||||
"bin": {
|
||||
"DMC": "./bin/index.js"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"build": "pkg -t --compress GZip package.json"
|
||||
"start": "node bin/index.js",
|
||||
"build": "pkg .",
|
||||
"test": "node fieldTest.cjs"
|
||||
},
|
||||
"keywords": [
|
||||
"discord",
|
||||
|
@ -18,6 +22,7 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cli-progress": "^3.11.0",
|
||||
"filehound": "^1.17.6",
|
||||
"pkg": "^5.6.0",
|
||||
"require-runtime": "^2.0.0",
|
||||
"terminal-kit": "^2.4.0"
|
||||
|
|
Loading…
Reference in a new issue