-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from tahsinature/add-audio-compression
Add audio compression
- Loading branch information
Showing
9 changed files
with
233 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,54 @@ | ||
import { askFiles, askPreset, askToggle } from "./prompts"; | ||
import { main } from "./engine"; | ||
import { askBoolean, askChoose, askFiles, askFilter, askPreset } from "./prompts"; | ||
import { compressVideo, compressAudio } from "./engine"; | ||
import { checkRequiredBinaries } from "./check"; | ||
import { Operations } from "./types"; | ||
|
||
const hardbrake = async () => { | ||
await checkRequiredBinaries(); | ||
const videoCompress = async () => { | ||
await checkRequiredBinaries(Operations.VIDEO_COMPRESS); | ||
|
||
const files = await askFiles(); | ||
if (files.length === 0) { | ||
console.log("No files selected. Exiting."); | ||
process.exit(0); | ||
} | ||
const files = await askFiles(["mp4", "mkv", "avi", "mov", "flv", "wmv", "webm", "m4v", "lrf"]); | ||
|
||
const preset = await askPreset(); | ||
const keepAudio = await askToggle("Do you want to keep the audio?", { initial: true }); | ||
|
||
await main(files, preset, { keepAudio }); | ||
const keepAudio = await askBoolean("Do you want to keep the audio?", "true"); | ||
|
||
const happyWithResults = await askToggle("Are you happy with the results?", { initial: true }); | ||
await compressVideo(files, preset, { keepAudio }); | ||
|
||
const happyWithResults = await askBoolean("Are you happy with the results?", "true"); | ||
if (!happyWithResults) { | ||
for (const file of files) await file.deleteOutput(); | ||
console.log("Deleted all the output files."); | ||
process.exit(0); | ||
} | ||
|
||
const deleteOriginalFiles = await askToggle("Do you want to delete the original files?", { initial: false }); | ||
const deleteOriginalFiles = await askBoolean("Do you want to delete the original files?", "false"); | ||
if (deleteOriginalFiles) { | ||
for (const file of files) await file.deleteOriginal(); | ||
console.log("Deleted all the original files."); | ||
} | ||
}; | ||
|
||
export default hardbrake; | ||
const audioCompress = async () => { | ||
await checkRequiredBinaries(Operations.AUDIO_COMPRESS); | ||
|
||
const files = await askFiles(["mp3", "wav", "flac", "m4a", "aac", "ogg", "wma", "aiff", "alac"]); | ||
const bitrate = await askFilter("Select a bitrate", ["16k", "32k", "64k", "128k", "256k", "320k"], { limit: 1, min: 1 }); | ||
|
||
for (const file of files) { | ||
await compressAudio(file, bitrate[0]); | ||
} | ||
}; | ||
|
||
const fnMap: Record<Operations, () => Promise<void>> = { | ||
[Operations.VIDEO_COMPRESS]: videoCompress, | ||
[Operations.AUDIO_COMPRESS]: audioCompress, | ||
}; | ||
|
||
const root = async () => { | ||
const choice = await askChoose("Select an operation", Object.values(Operations)); | ||
const op = choice[0] as Operations; | ||
|
||
await fnMap[op](); | ||
}; | ||
|
||
export default root; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,88 @@ | ||
import prompts from "prompts"; | ||
import path from "path"; | ||
import { getPresets, runShellCommand } from "./utils"; | ||
import fs from "fs"; | ||
import { getPresets, runShellCommandAndReturnOutput } from "./utils"; | ||
import { File } from "./blueprints"; | ||
|
||
function promptsWithOnCancel<T extends string = string>(questions: prompts.PromptObject<T> | Array<prompts.PromptObject<T>>) { | ||
return prompts(questions, { | ||
onCancel: () => { | ||
process.exit(0); | ||
}, | ||
}); | ||
} | ||
export const askFiles = async (supportedExtensions: string[]) => { | ||
const dir = await askFolderPath(); | ||
const files = fs.readdirSync(dir); | ||
const videos = files.filter((file) => supportedExtensions.includes(file.split(".").pop() || "")); | ||
if (videos.length === 0) throw new Error("No files found in the folder"); | ||
const selectedVids = await askFilter("Choose videos", videos, { min: 1 }); | ||
if (selectedVids.length === 0) throw new Error("No files selected"); | ||
|
||
export const askFiles = async () => { | ||
const supportedExt = ["mp4", "mkv", "avi", "mov", "flv", "wmv", "webm", "m4v", "lrf"]; | ||
const extentionsFlags = supportedExt.map((ext) => `-e ${ext}`).join(" "); | ||
const command = `fd ${extentionsFlags} -t f --max-depth 1 --absolute-path . | fzf --multi --bind ctrl-space:toggle-all`; | ||
return selectedVids.map((file) => new File(path.join(dir, file))); | ||
}; | ||
|
||
export const askFolderPath = async () => { | ||
console.log("Select a directory or, a file. If a file is selected, the parent directory will be used."); | ||
|
||
const files = await runShellCommand(command); | ||
if (!files) return []; | ||
const command = `gum file --all --directory .`; | ||
const result = runShellCommandAndReturnOutput(command); | ||
if (result.length === 0) throw new Error("Nothing selected"); | ||
let dirPath = result[0]; | ||
|
||
const filtered = files.split("\n").filter(Boolean); | ||
return filtered.map((file) => path.resolve(file)).map((file) => new File(file)); | ||
const isDir = fs.lstatSync(dirPath).isDirectory(); | ||
if (!isDir) dirPath = path.dirname(dirPath); | ||
return dirPath; | ||
}; | ||
|
||
const askAutoComplete = async (message: string, choices: string[]) => { | ||
const response = await promptsWithOnCancel({ | ||
type: "autocomplete", | ||
name: "value", | ||
message, | ||
choices: choices.map((choice) => ({ title: choice, value: choice })), | ||
}); | ||
export const askChoose = async (message: string, choices: string[], { limit = 1 }: { limit?: number } = {}) => { | ||
try { | ||
const gumChoices = choices.join(","); | ||
let command = `gum choose {${gumChoices}}`; | ||
|
||
if (limit) command += ` --limit ${limit}`; | ||
else command += " --no-limit"; | ||
|
||
return response.value; | ||
return runShellCommandAndReturnOutput(command); | ||
} catch (error: any) { | ||
console.error(error.message); | ||
process.exit(1); | ||
} | ||
}; | ||
|
||
export const askFilter = async (message: string, choices: string[], { limit = 0, min = 0 }: { limit?: number; min?: number } = {}) => { | ||
try { | ||
const gumChoices = choices.join("\n"); | ||
let command = `echo "${gumChoices}" | gum filter`; | ||
|
||
if (limit) command += ` --limit ${limit}`; | ||
else command += " --no-limit"; | ||
|
||
const selected = runShellCommandAndReturnOutput(command); | ||
if (selected.length < min) throw new Error("Minimum number of choices not selected"); | ||
return selected; | ||
} catch (error: any) { | ||
console.error(error.message); | ||
process.exit(1); | ||
} | ||
}; | ||
|
||
const fullPresetList = await getPresets(); | ||
const categories = Object.keys(fullPresetList); | ||
|
||
export const askPreset = async () => { | ||
const category = await askAutoComplete("Select a category", categories); | ||
const presets = fullPresetList[category]; | ||
const preset = await askAutoComplete("Select a preset", presets); | ||
const category = await askFilter("Select a category", categories, { limit: 1, min: 1 }); | ||
const presets = fullPresetList[category[0]]; | ||
const preset = await askFilter("Select a preset", presets, { limit: 1, min: 1 }); | ||
|
||
return preset; | ||
return preset[0]; | ||
}; | ||
|
||
export const askBoolean = async (message: string) => { | ||
const response = await promptsWithOnCancel({ | ||
type: "confirm", | ||
name: "answer", | ||
message, | ||
}); | ||
|
||
return response.answer; | ||
export const askBoolean = async (message: string, initial?: "true" | "false") => { | ||
const defaultFlag = initial ? `--default=${initial}` : ""; | ||
const command = `gum confirm ${defaultFlag} "${message}" && echo "true" || echo "false"`; | ||
const response = runShellCommandAndReturnOutput(command); | ||
return response[0] === "true"; | ||
}; | ||
|
||
export const askToggle = async (message: string, { initial = false } = {}) => { | ||
const response = await promptsWithOnCancel({ | ||
type: "toggle", | ||
name: "value", | ||
message, | ||
initial, | ||
active: "yes", | ||
inactive: "no", | ||
}); | ||
|
||
return response.value; | ||
export const askInteger = async (message: string, initial?: number) => { | ||
const initialFlag = initial ? `--value=${initial}` : ""; | ||
const command = `gum input --placeholder="${message}" ${initialFlag}`; | ||
const response = runShellCommandAndReturnOutput(command); | ||
const nm = parseInt(response[0]); | ||
if (isNaN(nm)) throw new Error("Invalid number"); | ||
|
||
return nm; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export enum Operations { | ||
AUDIO_COMPRESS = "AUDIO_COMPRESS", | ||
VIDEO_COMPRESS = "VIDEO_COMPRESS", | ||
} |
Oops, something went wrong.