mtosity 0.0.2
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/README.md +71 -0
- package/bin/mtosity.js +2 -0
- package/dist/index.js +171 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# mtosity
|
|
2
|
+
|
|
3
|
+
My personal terminal, whatevery I need in one place. You can use it too! You may know something about me while using it.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
__ __ _ _ _
|
|
7
|
+
| \/ | | |_ ___ ___ (_) | |_ _ _
|
|
8
|
+
| |\/| | | __| / _ \ / __| | | | __| | | | |
|
|
9
|
+
| | | | | |_ | (_) | \__ \ | | | |_ | |_| |
|
|
10
|
+
|_| |_| \__| \___/ |___/ |_| \__| \__, |
|
|
11
|
+
|___/
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) installed and available in your PATH
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g mtosity
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
Run the CLI:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
mtosity
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Commands
|
|
33
|
+
|
|
34
|
+
| Command | Description |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `yt <url> [start] [end]` | Download YouTube video (MP4) |
|
|
37
|
+
| `yt-mp3 <url> [start] [end]` | Download YouTube audio (MP3) |
|
|
38
|
+
| `neofetch` | Display system information |
|
|
39
|
+
| `help` | Show available commands |
|
|
40
|
+
| `clear` | Clear the terminal |
|
|
41
|
+
| `exit` | Exit the CLI |
|
|
42
|
+
|
|
43
|
+
### Examples
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Download a video
|
|
47
|
+
yt https://youtu.be/dQw4w9WgXcQ
|
|
48
|
+
|
|
49
|
+
# Download audio only
|
|
50
|
+
yt-mp3 https://youtu.be/dQw4w9WgXcQ
|
|
51
|
+
|
|
52
|
+
# Download and trim (start at 0:30, duration 1:00)
|
|
53
|
+
yt https://youtu.be/dQw4w9WgXcQ 00:00:30 00:01:00
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Install dependencies
|
|
60
|
+
bun install
|
|
61
|
+
|
|
62
|
+
# Run in dev mode
|
|
63
|
+
bun run dev
|
|
64
|
+
|
|
65
|
+
# Build
|
|
66
|
+
bun run build
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
package/bin/mtosity.js
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const figlet_1 = __importDefault(require("figlet"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
13
|
+
const ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
|
|
14
|
+
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
if (ffmpeg_static_1.default) {
|
|
17
|
+
fluent_ffmpeg_1.default.setFfmpegPath(ffmpeg_static_1.default);
|
|
18
|
+
}
|
|
19
|
+
const youtubedlPkg = require("youtube-dl-exec");
|
|
20
|
+
const youtubedl = youtubedlPkg.create(process.env.YTDLP_PATH || "yt-dlp");
|
|
21
|
+
// --- Commands ---
|
|
22
|
+
async function runNeofetch() {
|
|
23
|
+
const cpu = await systeminformation_1.default.cpu();
|
|
24
|
+
const mem = await systeminformation_1.default.mem();
|
|
25
|
+
const osInfo = await systeminformation_1.default.osInfo();
|
|
26
|
+
console.log(chalk_1.default.cyan(`
|
|
27
|
+
. OS: ${osInfo.distro} ${osInfo.release}
|
|
28
|
+
/ \\ Host: ${os_1.default.hostname()}
|
|
29
|
+
/ \\ Kernel: ${os_1.default.release()}
|
|
30
|
+
/ | \\ Uptime: ${(os_1.default.uptime() / 3600).toFixed(2)} hours
|
|
31
|
+
/___|___\\ Shell: ${process.env.SHELL || "unknown"}
|
|
32
|
+
| CPU: ${cpu.manufacturer} ${cpu.brand}
|
|
33
|
+
| Memory: ${(mem.used / 1024 / 1024 / 1024).toFixed(2)}GiB / ${(mem.total / 1024 / 1024 / 1024).toFixed(2)}GiB
|
|
34
|
+
`));
|
|
35
|
+
}
|
|
36
|
+
async function downloadYouTube(url, format, start, end) {
|
|
37
|
+
const spinner = (0, ora_1.default)("Initializing yt-dlp...").start();
|
|
38
|
+
try {
|
|
39
|
+
// Get title first for filename (optional, or just use generic and rename? yt-dlp can output template)
|
|
40
|
+
// But we want to show title in spinner.
|
|
41
|
+
spinner.text = "Fetching metadata...";
|
|
42
|
+
const info = await youtubedl(url, {
|
|
43
|
+
dumpSingleJson: true,
|
|
44
|
+
noWarnings: true,
|
|
45
|
+
noCheckCertificates: true,
|
|
46
|
+
preferFreeFormats: true,
|
|
47
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
|
48
|
+
});
|
|
49
|
+
const title = info.title || "video";
|
|
50
|
+
const safeTitle = title.replace(/[^a-zA-Z0-9_\-\s]/g, "").replace(/\s+/g, "_") || "download";
|
|
51
|
+
const ext = format === "audio" ? "mp3" : "mp4";
|
|
52
|
+
const filename = `${safeTitle}.${ext}`;
|
|
53
|
+
const outputPath = path_1.default.resolve(process.cwd(), filename);
|
|
54
|
+
spinner.text = `Downloading: ${title}`;
|
|
55
|
+
const flags = {
|
|
56
|
+
output: outputPath,
|
|
57
|
+
noWarnings: true,
|
|
58
|
+
noCheckCertificates: true,
|
|
59
|
+
preferFreeFormats: true,
|
|
60
|
+
ffmpegLocation: ffmpeg_static_1.default,
|
|
61
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
|
62
|
+
};
|
|
63
|
+
if (format === "audio") {
|
|
64
|
+
flags.extractAudio = true;
|
|
65
|
+
flags.audioFormat = "mp3";
|
|
66
|
+
flags.format = "bestaudio/best";
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
flags.format = "bestvideo+bestaudio/best";
|
|
70
|
+
flags.mergeOutputFormat = "mp4";
|
|
71
|
+
}
|
|
72
|
+
// Handling start/end trimming via post-processor args if supported, or via ffmpeg externally.
|
|
73
|
+
// yt-dlp supports --download-sections usually.
|
|
74
|
+
// But simpler: download then trim? Or use ffmpeg directly on stream?
|
|
75
|
+
// yt-dlp can pipe to ffmpeg.
|
|
76
|
+
// But `youtube-dl-exec` with `exec`?
|
|
77
|
+
// For simplicity given the library constraints, let's just download first.
|
|
78
|
+
// If user wants trim, we can run ffmpeg on the output file?
|
|
79
|
+
// Or just let yt-dlp handle it if possible.
|
|
80
|
+
// `download_ranges` is complex.
|
|
81
|
+
// Let's ignore trim for now or apply it after download if user provided args.
|
|
82
|
+
await youtubedl(url, flags);
|
|
83
|
+
if ((start || end) && ffmpeg_static_1.default) {
|
|
84
|
+
spinner.text = "Trimming...";
|
|
85
|
+
const tempPath = outputPath + ".tmp";
|
|
86
|
+
fs_1.default.renameSync(outputPath, tempPath);
|
|
87
|
+
await new Promise((resolve, reject) => {
|
|
88
|
+
let command = (0, fluent_ffmpeg_1.default)(tempPath);
|
|
89
|
+
if (start)
|
|
90
|
+
command = command.setStartTime(start);
|
|
91
|
+
if (end)
|
|
92
|
+
command = command.setDuration(end);
|
|
93
|
+
command
|
|
94
|
+
.save(outputPath)
|
|
95
|
+
.on("end", () => {
|
|
96
|
+
fs_1.default.unlinkSync(tempPath);
|
|
97
|
+
resolve();
|
|
98
|
+
})
|
|
99
|
+
.on("error", (err) => reject(err));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
spinner.succeed(chalk_1.default.green(`Downloaded to ${filename}`));
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
106
|
+
spinner.fail(chalk_1.default.red(`Error: ${msg}`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// --- Main Loop ---
|
|
110
|
+
async function main() {
|
|
111
|
+
console.clear();
|
|
112
|
+
console.log(chalk_1.default.green(figlet_1.default.textSync("Mtosity", { horizontalLayout: "full" })));
|
|
113
|
+
console.log(chalk_1.default.dim("Welcome to Mtosity CLI. Type 'help' for commands.\n"));
|
|
114
|
+
while (true) {
|
|
115
|
+
const { input } = await inquirer_1.default.prompt([{
|
|
116
|
+
type: "input",
|
|
117
|
+
name: "input",
|
|
118
|
+
message: chalk_1.default.green("mtosity >"),
|
|
119
|
+
prefix: ""
|
|
120
|
+
}]);
|
|
121
|
+
const parts = input.trim().split(" ");
|
|
122
|
+
const cmd = parts[0];
|
|
123
|
+
const args = parts.slice(1);
|
|
124
|
+
switch (cmd) {
|
|
125
|
+
case "neofetch":
|
|
126
|
+
await runNeofetch();
|
|
127
|
+
break;
|
|
128
|
+
case "yt":
|
|
129
|
+
let url = args[0];
|
|
130
|
+
if (!url) {
|
|
131
|
+
const { inputUrl } = await inquirer_1.default.prompt([{
|
|
132
|
+
type: "input",
|
|
133
|
+
name: "inputUrl",
|
|
134
|
+
message: "Enter YouTube URL:",
|
|
135
|
+
}]);
|
|
136
|
+
url = inputUrl;
|
|
137
|
+
}
|
|
138
|
+
if (url)
|
|
139
|
+
await downloadYouTube(url, "video", args[1], args[2]);
|
|
140
|
+
break;
|
|
141
|
+
case "yt-mp3":
|
|
142
|
+
let urlMp3 = args[0];
|
|
143
|
+
if (!urlMp3) {
|
|
144
|
+
const { inputUrl } = await inquirer_1.default.prompt([{
|
|
145
|
+
type: "input",
|
|
146
|
+
name: "inputUrl",
|
|
147
|
+
message: "Enter YouTube URL:",
|
|
148
|
+
}]);
|
|
149
|
+
urlMp3 = inputUrl;
|
|
150
|
+
}
|
|
151
|
+
if (urlMp3)
|
|
152
|
+
await downloadYouTube(urlMp3, "audio", args[1], args[2]);
|
|
153
|
+
break;
|
|
154
|
+
case "clear":
|
|
155
|
+
console.clear();
|
|
156
|
+
break;
|
|
157
|
+
case "exit":
|
|
158
|
+
console.log(chalk_1.default.green("Goodbye!"));
|
|
159
|
+
process.exit(0);
|
|
160
|
+
case "":
|
|
161
|
+
break;
|
|
162
|
+
case "help":
|
|
163
|
+
console.log(chalk_1.default.cyan("Commands: neofetch, yt <url>, yt-mp3 <url>, clear, exit"));
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
console.log(chalk_1.default.red(`Unknown command: ${cmd}`));
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mtosity",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Mtosity CLI - YouTube downloader and system info tool",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mtosity": "bin/mtosity.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"bin"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"youtube",
|
|
16
|
+
"downloader",
|
|
17
|
+
"neofetch",
|
|
18
|
+
"terminal"
|
|
19
|
+
],
|
|
20
|
+
"author": "mtosity",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/mtosity/mtosity-cli.git"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"prepublishOnly": "tsc",
|
|
28
|
+
"build": "bun run tsc",
|
|
29
|
+
"start": "bun dist/index.js",
|
|
30
|
+
"dev": "bun src/index.ts"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@ffmpeg/ffmpeg": "^0.12.15",
|
|
34
|
+
"@ffmpeg/util": "^0.12.2",
|
|
35
|
+
"chalk": "^5.6.2",
|
|
36
|
+
"ffmpeg-static": "^5.3.0",
|
|
37
|
+
"figlet": "^1.10.0",
|
|
38
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
39
|
+
"inquirer": "^13.2.2",
|
|
40
|
+
"ora": "^9.3.0",
|
|
41
|
+
"systeminformation": "^5.30.7",
|
|
42
|
+
"youtube-dl-exec": "^3.1.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/figlet": "^1.7.0",
|
|
46
|
+
"@types/fluent-ffmpeg": "^2.1.28",
|
|
47
|
+
"@types/inquirer": "^9.0.9",
|
|
48
|
+
"@types/node": "^20.19.33",
|
|
49
|
+
"eslint": "^9",
|
|
50
|
+
"typescript": "^5.9.3"
|
|
51
|
+
}
|
|
52
|
+
}
|