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/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
+ }