spessoplayer 0.5.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/COMMAND-LINE-OPTIONS.md +59 -0
- package/LICENSE +674 -0
- package/NOTICE +8 -0
- package/README.md +29 -0
- package/audioBuffer.mjs +284 -0
- package/cli.mjs +657 -0
- package/install.mjs +81 -0
- package/main.mjs +815 -0
- package/package.json +33 -0
- package/uninstall.mjs +80 -0
- package/utils.mjs +317 -0
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spessoplayer",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "A midi converter that uses spessasynth_core to generate the data",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"midi", "soundfont",
|
|
7
|
+
"mid", "sf2", "dls",
|
|
8
|
+
"midiplayer", "player",
|
|
9
|
+
"cli",
|
|
10
|
+
"node",
|
|
11
|
+
"stdout",
|
|
12
|
+
"lossless", "lossy",
|
|
13
|
+
"wav", "pcm", "flac", "mp3"
|
|
14
|
+
],
|
|
15
|
+
"license": "GPL-3.0-only",
|
|
16
|
+
"author": "unixatch",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "main.js",
|
|
19
|
+
"preferGlobal": true,
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.19.0"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"spessoplayer": "main.mjs"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node main.js",
|
|
28
|
+
"postinstall": "node install.mjs"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"spessasynth_core": "^4.0.25"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/uninstall.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
Copyright (C) 2025 unixatch
|
|
4
|
+
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with spessoplayer. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { spawnSync } = await import("child_process");
|
|
20
|
+
const { tryToUninstall } = await import("./utils.mjs");
|
|
21
|
+
|
|
22
|
+
let readline,
|
|
23
|
+
stdin,
|
|
24
|
+
stdout,
|
|
25
|
+
stderr;
|
|
26
|
+
/**
|
|
27
|
+
* Checks if a program exists, if it doesn't exist,
|
|
28
|
+
* it asks the user for confirmation to install via package managers
|
|
29
|
+
* @param {String} program - the program to check
|
|
30
|
+
* @param {String} [noInstallMsg=""] - the message to show when the user refuses to install it
|
|
31
|
+
*/
|
|
32
|
+
async function runCheck(program, noInstallMsg = "") {
|
|
33
|
+
try {
|
|
34
|
+
if (!readline) {
|
|
35
|
+
readline = await import("readline/promises");
|
|
36
|
+
({ stdin, stdout, stderr } = await import ("process"));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
40
|
+
const isSox = (program === "sox") ? "[Y|n]" : "[y|N]";
|
|
41
|
+
const answer = await rl.question("Do you want to uninstall it " + isSox + "? ");
|
|
42
|
+
rl.close()
|
|
43
|
+
if (!/(?:n|no|y|yes)/.test(answer)) {
|
|
44
|
+
if (program === "sox") {
|
|
45
|
+
return tryToUninstall(program, spawnSync, { stdout, stderr })
|
|
46
|
+
}
|
|
47
|
+
return console.warn("\x1b[33m"+noInstallMsg+"\x1b[0m")
|
|
48
|
+
}
|
|
49
|
+
// ↓ In case it's neither y or n
|
|
50
|
+
if (/(?:y|yes)/i.test(answer)) {
|
|
51
|
+
tryToUninstall(program, spawnSync, { stdout, stderr })
|
|
52
|
+
} else if (/(?:n|no)/.test(answer)) {
|
|
53
|
+
console.warn("\x1b[33m"+noInstallMsg+"\x1b[0m")
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (e.name === "AbortError") {
|
|
57
|
+
console.error(`\n${gray}Uninstallation of dependencies interrupted with Ctrl+c${normal}`);
|
|
58
|
+
process.exit(2)
|
|
59
|
+
}
|
|
60
|
+
console.error(e);
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ffmpeg check
|
|
66
|
+
await runCheck(
|
|
67
|
+
"ffmpeg",
|
|
68
|
+
"Continuing uninstallation, keeping ffmpeg"
|
|
69
|
+
)
|
|
70
|
+
// SoX check
|
|
71
|
+
await runCheck(
|
|
72
|
+
"sox",
|
|
73
|
+
"Continuing uninstallation, keeping SoX"
|
|
74
|
+
)
|
|
75
|
+
// mpv check
|
|
76
|
+
await runCheck(
|
|
77
|
+
"mpv",
|
|
78
|
+
"Continuing uninstallation, keeping mpv"
|
|
79
|
+
)
|
|
80
|
+
export { runCheck }
|
package/utils.mjs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2025 unixatch
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License
|
|
15
|
+
along with spessoplayer. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { join, parse } from "path"
|
|
19
|
+
import { fileURLToPath } from "url"
|
|
20
|
+
|
|
21
|
+
function declareColors() {
|
|
22
|
+
// Custom formatting
|
|
23
|
+
global.normal= "\x1b[0m"
|
|
24
|
+
global.bold= "\x1b[1m"
|
|
25
|
+
global.italics= "\x1b[3m"
|
|
26
|
+
global.underline= "\x1b[4m"
|
|
27
|
+
// Actual colors
|
|
28
|
+
global.yellow= "\x1b[33;1m"
|
|
29
|
+
global.normalYellow= "\x1b[33m"
|
|
30
|
+
global.magenta= "\x1b[35m"
|
|
31
|
+
global.brightMagenta= "\x1b[95m"
|
|
32
|
+
global.dimYellow = "\x1b[2;33m"
|
|
33
|
+
global.green= "\x1b[32m"
|
|
34
|
+
global.dimGreen= "\x1b[32;2m"
|
|
35
|
+
global.normalRed= "\x1b[31m"
|
|
36
|
+
global.red= "\x1b[31;1m"
|
|
37
|
+
global.normalRed= "\x1b[31m"
|
|
38
|
+
global.dimRed= "\x1b[31;2m"
|
|
39
|
+
global.gray= "\x1b[90;1m"
|
|
40
|
+
global.dimGray= "\x1b[37;2m"
|
|
41
|
+
global.dimGrayBold= "\x1b[37;2;1m"
|
|
42
|
+
}
|
|
43
|
+
declareColors()
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Clears lines from the last line up
|
|
47
|
+
* @param {Number[]} lines - two numbers, x and y values
|
|
48
|
+
* @example
|
|
49
|
+
* // Clears only the last line
|
|
50
|
+
* clearLastLines([0, -1])
|
|
51
|
+
*/
|
|
52
|
+
const clearLastLines = lines => {
|
|
53
|
+
if (!Array.isArray(lines)) throw new TypeError("Didn't give an array");
|
|
54
|
+
let lineX, lineY;
|
|
55
|
+
lines
|
|
56
|
+
.forEach((line, i) => {
|
|
57
|
+
if (typeof line === "string") throw new TypeError(`Gave string "${line}", numbers only allowed`)
|
|
58
|
+
const int = parseInt(line);
|
|
59
|
+
if (isNaN(int)) throw new TypeError("Didn't give a number")
|
|
60
|
+
if (i === 0) {
|
|
61
|
+
lineX = line;
|
|
62
|
+
} else lineY = line;
|
|
63
|
+
})
|
|
64
|
+
process.stdout
|
|
65
|
+
.moveCursor(lineX, lineY);
|
|
66
|
+
process.stdout
|
|
67
|
+
.clearScreenDown();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Runs program synchronously and throws a ReferenceError if it doesn't find it
|
|
71
|
+
* @param {Object} obj - the obj passed
|
|
72
|
+
* @param {Function} obj.spawnSync - child_process.spawnSync
|
|
73
|
+
* @param {String} obj.program - program to find
|
|
74
|
+
* @param {string[]} obj.args - optional arguments
|
|
75
|
+
* @param {(string|string[])} obj.stdioArray - stdio to set for the process
|
|
76
|
+
*/
|
|
77
|
+
function runProgramSync({ spawnSync, program, args = [], stdioArray = "pipe" }) {
|
|
78
|
+
const code = spawnSync(
|
|
79
|
+
program,
|
|
80
|
+
args,
|
|
81
|
+
{ stdio: stdioArray }
|
|
82
|
+
)?.error?.code
|
|
83
|
+
|
|
84
|
+
if (code === "ENOENT") {
|
|
85
|
+
throw new ReferenceError("Program doesn't exist")
|
|
86
|
+
} else return true
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Tries to check and install the program via a package manager
|
|
90
|
+
* @param {String} packageToUse - package to search and install
|
|
91
|
+
* @param {Function} spawnSync - child_process.spawnSync
|
|
92
|
+
* @param {Object} stdioObj - object passed for stdout and stderr
|
|
93
|
+
* @param {Writable} obj.stdout - process' stdout
|
|
94
|
+
* @param {Writable} obj.stderr - process' stderr
|
|
95
|
+
*/
|
|
96
|
+
function tryToInstall(packageToUse, spawnSync, { stdout, stderr }) {
|
|
97
|
+
const packageManagers = [
|
|
98
|
+
"apt",
|
|
99
|
+
"dnf",
|
|
100
|
+
"yum",
|
|
101
|
+
"zypper",
|
|
102
|
+
"pacman",
|
|
103
|
+
"emerge",
|
|
104
|
+
"pkg",
|
|
105
|
+
"winget",
|
|
106
|
+
"brew",
|
|
107
|
+
"rpm",
|
|
108
|
+
"apk"
|
|
109
|
+
];
|
|
110
|
+
for (const packageManager of packageManagers) {
|
|
111
|
+
switch (packageManager) {
|
|
112
|
+
case "apt":
|
|
113
|
+
case "dnf":
|
|
114
|
+
case "yum":
|
|
115
|
+
case "zypper":
|
|
116
|
+
case "pkg":
|
|
117
|
+
case "winget":
|
|
118
|
+
case "brew":
|
|
119
|
+
try {
|
|
120
|
+
return runProgramSync({
|
|
121
|
+
spawnSync,
|
|
122
|
+
program: packageManager,
|
|
123
|
+
args: ["install", packageToUse],
|
|
124
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
125
|
+
})
|
|
126
|
+
} catch { break; }
|
|
127
|
+
case "pacman":
|
|
128
|
+
try {
|
|
129
|
+
return runProgramSync({
|
|
130
|
+
spawnSync,
|
|
131
|
+
program: packageManager,
|
|
132
|
+
args: ["-S", packageToUse],
|
|
133
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
134
|
+
})
|
|
135
|
+
} catch { break; }
|
|
136
|
+
case "emerge":
|
|
137
|
+
try {
|
|
138
|
+
return runProgramSync({
|
|
139
|
+
spawnSync,
|
|
140
|
+
program: packageManager,
|
|
141
|
+
args: ["--ask", "--verbose", packageToUse],
|
|
142
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
143
|
+
})
|
|
144
|
+
} catch { break; }
|
|
145
|
+
case "apk":
|
|
146
|
+
try {
|
|
147
|
+
return runProgramSync({
|
|
148
|
+
spawnSync,
|
|
149
|
+
program: packageManager,
|
|
150
|
+
args: ["add", packageToUse],
|
|
151
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
152
|
+
})
|
|
153
|
+
} catch { break; }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
console.warn(`${yellow}Couldn't find any package manager in the list${normal}`)
|
|
157
|
+
console.warn(`${yellow}install it either manually or with a package manager you use${normal}`)
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Tries to check and uninstall the program via a package manager
|
|
161
|
+
* @param {String} packageToUse - package to search and install
|
|
162
|
+
* @param {Function} spawnSync - child_process.spawnSync
|
|
163
|
+
* @param {Object} stdioObj - object passed for stdout and stderr
|
|
164
|
+
* @param {Writable} obj.stdout - process' stdout
|
|
165
|
+
* @param {Writable} obj.stderr - process' stderr
|
|
166
|
+
*/
|
|
167
|
+
function tryToUninstall(packageToUse, spawnSync, { stdout, stderr }) {
|
|
168
|
+
const packageManagers = [
|
|
169
|
+
"apt",
|
|
170
|
+
"dnf",
|
|
171
|
+
"yum",
|
|
172
|
+
"zypper",
|
|
173
|
+
"pacman",
|
|
174
|
+
"emerge",
|
|
175
|
+
"pkg",
|
|
176
|
+
"winget",
|
|
177
|
+
"brew",
|
|
178
|
+
"rpm",
|
|
179
|
+
"apk"
|
|
180
|
+
];
|
|
181
|
+
for (const packageManager of packageManagers) {
|
|
182
|
+
switch (packageManager) {
|
|
183
|
+
case "apt":
|
|
184
|
+
case "dnf":
|
|
185
|
+
case "yum":
|
|
186
|
+
case "zypper":
|
|
187
|
+
try {
|
|
188
|
+
return runProgramSync({
|
|
189
|
+
spawnSync,
|
|
190
|
+
program: packageManager,
|
|
191
|
+
args: ["remove", packageToUse],
|
|
192
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
193
|
+
})
|
|
194
|
+
} catch { break; }
|
|
195
|
+
case "pkg":
|
|
196
|
+
case "winget":
|
|
197
|
+
case "brew":
|
|
198
|
+
try {
|
|
199
|
+
return runProgramSync({
|
|
200
|
+
spawnSync,
|
|
201
|
+
program: packageManager,
|
|
202
|
+
args: ["uninstall", packageToUse],
|
|
203
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
204
|
+
})
|
|
205
|
+
} catch { break; }
|
|
206
|
+
case "pacman":
|
|
207
|
+
try {
|
|
208
|
+
return runProgramSync({
|
|
209
|
+
spawnSync,
|
|
210
|
+
program: packageManager,
|
|
211
|
+
args: ["-Rs", packageToUse],
|
|
212
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
213
|
+
})
|
|
214
|
+
} catch { break; }
|
|
215
|
+
case "emerge":
|
|
216
|
+
try {
|
|
217
|
+
return runProgramSync({
|
|
218
|
+
spawnSync,
|
|
219
|
+
program: packageManager,
|
|
220
|
+
args: ["--ask", "--verbose", "--depclean", packageToUse],
|
|
221
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
222
|
+
})
|
|
223
|
+
} catch { break; }
|
|
224
|
+
case "apk":
|
|
225
|
+
try {
|
|
226
|
+
return runProgramSync({
|
|
227
|
+
spawnSync,
|
|
228
|
+
program: packageManager,
|
|
229
|
+
args: ["del", packageToUse],
|
|
230
|
+
stdioArray: ["pipe", stdout, stderr]
|
|
231
|
+
})
|
|
232
|
+
} catch { break; }
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
console.warn(`${yellow}Couldn't find any package manager in the list${normal}`)
|
|
236
|
+
console.warn(`${yellow}uninstall it either manually or with a package manager you use${normal}`)
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Logger
|
|
240
|
+
* @param {Number} level - level of the log
|
|
241
|
+
* @param {Number} time - time that it takes to creates this log
|
|
242
|
+
* @param {string[]} ...messages - messages to print
|
|
243
|
+
*/
|
|
244
|
+
function log(level, time, ...messages) {
|
|
245
|
+
const spacesAmount = new Date().toISOString().length + ((time+"").length + 7) + 2;
|
|
246
|
+
const debugLevelSpesso = process.env["DEBUG_LEVEL_SPESSO"];
|
|
247
|
+
const debugFileSpesso = process.env["DEBUG_FILE_SPESSO"];
|
|
248
|
+
if (debugLevelSpesso
|
|
249
|
+
&& debugLevelSpesso <= level
|
|
250
|
+
|| global.verboseLevel <= level) {
|
|
251
|
+
const message = [
|
|
252
|
+
new Date(),
|
|
253
|
+
"["+time+" ms]",
|
|
254
|
+
messages
|
|
255
|
+
.join("")
|
|
256
|
+
// Place the header data on a new line with padding
|
|
257
|
+
.replace(/header file (\d+)+/, "header file:\n"+" ".repeat(spacesAmount)+"$1")
|
|
258
|
+
// Place the SoX arguments on a new line with padding
|
|
259
|
+
.replace(/with (sox -t.*)/, "with:\n"+" ".repeat(spacesAmount)+"\"$1\"")
|
|
260
|
+
// Place the ffmpeg arguments on a new line with padding
|
|
261
|
+
.replace(/with (ffmpeg -i.*)/, "with:\n"+" ".repeat(spacesAmount)+"\"$1\"")
|
|
262
|
+
// Add dimmed gray to the output
|
|
263
|
+
.replace(/(.*)/s, `${dimGray}$1${normal}`)
|
|
264
|
+
];
|
|
265
|
+
if (messages[0] === "Finished printing to stdout") message.unshift("\n")
|
|
266
|
+
console.error(...message);
|
|
267
|
+
const path = debugFileSpesso || global.logFilePath;
|
|
268
|
+
if (path) {
|
|
269
|
+
message[0] = message[0].toISOString();
|
|
270
|
+
message[message.length-1] = message[message.length-1].replace(/\x1b\[.{1,10}m/, "")
|
|
271
|
+
message.push("\n")
|
|
272
|
+
fs.appendFileSync(path, message.join(" "))
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Returns a new path with a new number (adds 1) at the end of the filename
|
|
278
|
+
* if necessary otherwise it returns the given path
|
|
279
|
+
* @param {String} path - The path to parse and modify if needed
|
|
280
|
+
* @example
|
|
281
|
+
* // It'll return out1.wav
|
|
282
|
+
* newFileName("out.wav")
|
|
283
|
+
* @example
|
|
284
|
+
* // It'll return out2.wav
|
|
285
|
+
* newFileName("out1.wav")
|
|
286
|
+
* @returns {String} The path, modified or not
|
|
287
|
+
*/
|
|
288
|
+
function newFileName(path) {
|
|
289
|
+
if (fs.existsSync(path)) {
|
|
290
|
+
const pathDir = parse(path).dir;
|
|
291
|
+
const pathFileName = (parse(path).name.match(/[0-9]+$/g)?.length > 0)
|
|
292
|
+
? parse(path).name.replace(/[0-9]+$/, "")
|
|
293
|
+
+ (Number(parse(path).name.match(/[0-9]+$/g)[0]) + 1)
|
|
294
|
+
: parse(path).name.replace(/[0-9]+$/, "") + 1;
|
|
295
|
+
const pathExt = parse(path).ext;
|
|
296
|
+
path = join(pathDir, pathFileName + pathExt);
|
|
297
|
+
|
|
298
|
+
if (fs.existsSync(path)) return newFileName(path);
|
|
299
|
+
return path;
|
|
300
|
+
}
|
|
301
|
+
return path;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Simply returns the programs' current directory
|
|
306
|
+
* @type {String}
|
|
307
|
+
*/
|
|
308
|
+
const _dirname_ = fileURLToPath(new URL(".", import.meta.url));
|
|
309
|
+
export {
|
|
310
|
+
_dirname_,
|
|
311
|
+
clearLastLines,
|
|
312
|
+
runProgramSync,
|
|
313
|
+
tryToInstall,
|
|
314
|
+
tryToUninstall,
|
|
315
|
+
log,
|
|
316
|
+
newFileName
|
|
317
|
+
}
|