@groovenet/cli 1.0.3 → 2.1.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/README.md +27 -1
- package/build/bin/groovenet.js +2 -2
- package/build/bin/groovenet.js.map +1 -1
- package/build/commands/fingerprintLibrary.d.ts +74 -0
- package/build/commands/fingerprintLibrary.d.ts.map +1 -0
- package/build/commands/fingerprintLibrary.js +165 -0
- package/build/commands/fingerprintLibrary.js.map +1 -0
- package/build/commands/playlists.d.ts.map +1 -1
- package/build/commands/playlists.js +31 -0
- package/build/commands/playlists.js.map +1 -1
- package/package.json +10 -5
- package/build/commands/play.d.ts +0 -3
- package/build/commands/play.d.ts.map +0 -1
- package/build/commands/play.js +0 -81
- package/build/commands/play.js.map +0 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ groovenet albums search "kind of blue" # browse albums
|
|
|
27
27
|
groovenet playlists list # list playlists
|
|
28
28
|
groovenet friends list # manage the friends system
|
|
29
29
|
groovenet play <track-id> # play a track via MPD on the server
|
|
30
|
+
groovenet fingerprint-library # index reference audio for matching
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
Every command accepts `--json` for machine-readable output:
|
|
@@ -46,7 +47,32 @@ Run `groovenet --help` (or `groovenet <command> --help`) for the full command re
|
|
|
46
47
|
| `albums` | Browse and manage albums |
|
|
47
48
|
| `playlists` | Manage playlists |
|
|
48
49
|
| `friends` | Manage the friends system |
|
|
49
|
-
| `
|
|
50
|
+
| `fingerprint-library` | Build the reference fingerprint index used to recognise vinyl as it plays |
|
|
51
|
+
|
|
52
|
+
### fingerprint-library
|
|
53
|
+
|
|
54
|
+
Fingerprints the library's reference audio so live vinyl playback can be
|
|
55
|
+
recognised against it. Safe to re-run: each track's audio is hashed and skipped
|
|
56
|
+
when nothing has changed.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
groovenet fingerprint-library # only tracks not yet indexed
|
|
60
|
+
groovenet fingerprint-library --changed # re-check already-indexed files
|
|
61
|
+
groovenet fingerprint-library --all --force # regenerate everything
|
|
62
|
+
groovenet fingerprint-library --track 12345-1 # one track
|
|
63
|
+
groovenet fingerprint-library --release 12345 # one release
|
|
64
|
+
groovenet fingerprint-library --no-wait # queue it and return
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Indexing missing — chromaprint 1
|
|
69
|
+
3653 queued, 241 without reference audio
|
|
70
|
+
✗ 12345-2: ffmpeg exited 1: Invalid data found when processing input
|
|
71
|
+
✓ 3412 indexed ⤼ 0 skipped ✗ 2 failed – 241 no audio
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Tracks with no local audio are counted, not failed — only the part of the
|
|
75
|
+
library you have downloaded can be fingerprinted. Exits 1 if any track failed.
|
|
50
76
|
|
|
51
77
|
## Related
|
|
52
78
|
|
package/build/bin/groovenet.js
CHANGED
|
@@ -5,8 +5,8 @@ import { addConfigCommands } from "../commands/config.js";
|
|
|
5
5
|
import { addTracksCommands } from "../commands/tracks.js";
|
|
6
6
|
import { addAlbumsCommands } from "../commands/albums.js";
|
|
7
7
|
import { addPlaylistsCommands } from "../commands/playlists.js";
|
|
8
|
-
import { addPlayCommands } from "../commands/play.js";
|
|
9
8
|
import { addFriendsCommands } from "../commands/friends.js";
|
|
9
|
+
import { addFingerprintLibraryCommand } from "../commands/fingerprintLibrary.js";
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
12
12
|
const { version } = require("../../package.json");
|
|
@@ -19,8 +19,8 @@ addConfigCommands(program);
|
|
|
19
19
|
addTracksCommands(program);
|
|
20
20
|
addAlbumsCommands(program);
|
|
21
21
|
addPlaylistsCommands(program);
|
|
22
|
-
addPlayCommands(program);
|
|
23
22
|
addFriendsCommands(program);
|
|
23
|
+
addFingerprintLibraryCommand(program);
|
|
24
24
|
program.parseAsync(process.argv).catch((err) => {
|
|
25
25
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
26
26
|
process.exit(1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groovenet.js","sourceRoot":"","sources":["../../src/bin/groovenet.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"groovenet.js","sourceRoot":"","sources":["../../src/bin/groovenet.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAEjF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,sEAAsE;AACtE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;AAEzE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,sCAAsC,CAAC;KACnD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,4BAA4B,CAAC,OAAO,CAAC,CAAC;AAEtC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC/D,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { GroovenetClient } from "@groovenet/client";
|
|
3
|
+
import type { FingerprintIndexRequest, FingerprintIndexRun } from "@groovenet/client";
|
|
4
|
+
export declare function makeClient(): GroovenetClient;
|
|
5
|
+
/** Just enough of the client for one run, so a test can pass two functions. */
|
|
6
|
+
export type FingerprintLibraryClient = Pick<GroovenetClient, "startFingerprintIndex" | "getFingerprintIndexRun">;
|
|
7
|
+
/**
|
|
8
|
+
* Where the command's output goes.
|
|
9
|
+
*
|
|
10
|
+
* Injected rather than reaching for `console` and `process.stdout` directly, so
|
|
11
|
+
* the whole run — header, per-failure lines, the rewritten progress line and
|
|
12
|
+
* the summary — can be asserted without a terminal.
|
|
13
|
+
*/
|
|
14
|
+
export interface FingerprintLibraryIO {
|
|
15
|
+
/** A whole line, newline appended. */
|
|
16
|
+
log: (line: string) => void;
|
|
17
|
+
/** Raw, no newline — the progress line rewrites itself with \r. */
|
|
18
|
+
write: (text: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare const consoleIO: FingerprintLibraryIO;
|
|
21
|
+
export interface FingerprintLibraryOptions {
|
|
22
|
+
missing?: boolean;
|
|
23
|
+
changed?: boolean;
|
|
24
|
+
all?: boolean;
|
|
25
|
+
track?: string;
|
|
26
|
+
release?: string;
|
|
27
|
+
friendId?: number;
|
|
28
|
+
force?: boolean;
|
|
29
|
+
wait?: boolean;
|
|
30
|
+
pollInterval?: number;
|
|
31
|
+
json?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Turn the scope flags into one request.
|
|
35
|
+
*
|
|
36
|
+
* Exactly one scope, so `--missing --all` is refused rather than silently
|
|
37
|
+
* resolved to whichever the parser happened to see last. With no scope flag at
|
|
38
|
+
* all it is `--missing`: the cheap, common case, since the alternatives re-read
|
|
39
|
+
* and re-hash every already-indexed file.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveScope(opts: FingerprintLibraryOptions): FingerprintIndexRequest;
|
|
42
|
+
/** How many of the queued tracks have reached a terminal state. */
|
|
43
|
+
export declare function settledCount(run: FingerprintIndexRun): number;
|
|
44
|
+
/** `indexing 3412/3653 ██████████░░` — one line, rewritten in place. */
|
|
45
|
+
export declare function formatProgress(run: FingerprintIndexRun, width?: number): string;
|
|
46
|
+
/**
|
|
47
|
+
* The four counters the run reports.
|
|
48
|
+
*
|
|
49
|
+
* `unindexable` is listed apart from `failed` on purpose: a track with no
|
|
50
|
+
* `local_audio_url` has no reference audio to fingerprint, which is a fact
|
|
51
|
+
* about how much of the library has been downloaded, not a failure of the run.
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatSummary(run: FingerprintIndexRun): string;
|
|
54
|
+
/**
|
|
55
|
+
* Poll until every queued track has settled.
|
|
56
|
+
*
|
|
57
|
+
* `onProgress` is called with each poll so the caller owns the rendering — the
|
|
58
|
+
* loop itself stays testable without a terminal.
|
|
59
|
+
*/
|
|
60
|
+
export declare function waitForRun(client: Pick<GroovenetClient, "getFingerprintIndexRun">, runId: string, opts: {
|
|
61
|
+
pollIntervalMs: number;
|
|
62
|
+
onProgress?: (run: FingerprintIndexRun) => void;
|
|
63
|
+
}): Promise<FingerprintIndexRun>;
|
|
64
|
+
/**
|
|
65
|
+
* One `fingerprint-library` run, start to finish. Returns the process exit
|
|
66
|
+
* code: non-zero when any track failed, so the command composes in a script.
|
|
67
|
+
*
|
|
68
|
+
* Separate from the commander wiring because everything interesting happens
|
|
69
|
+
* here — scope resolution, the two output modes, the polling and the exit code
|
|
70
|
+
* — and a commander action is not callable from a test.
|
|
71
|
+
*/
|
|
72
|
+
export declare function runFingerprintLibrary(client: FingerprintLibraryClient, opts: FingerprintLibraryOptions, io?: FingerprintLibraryIO): Promise<number>;
|
|
73
|
+
export declare function addFingerprintLibraryCommand(program: Command): void;
|
|
74
|
+
//# sourceMappingURL=fingerprintLibrary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprintLibrary.d.ts","sourceRoot":"","sources":["../../src/commands/fingerprintLibrary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAc,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAItF,wBAAgB,UAAU,IAAI,eAAe,CAO5C;AAED,+EAA+E;AAC/E,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,eAAe,EACf,uBAAuB,GAAG,wBAAwB,CACnD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,sCAAsC;IACtC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,mEAAmE;IACnE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,eAAO,MAAM,SAAS,EAAE,oBAGvB,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,yBAAyB,GAC9B,uBAAuB,CAwBzB;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAE7D;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,GAAG,EAAE,mBAAmB,EAAE,KAAK,SAAK,GAAG,MAAM,CAM3E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAQ9D;AAMD;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,wBAAwB,CAAC,EACvD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAA;CAAE,GAChF,OAAO,CAAC,mBAAmB,CAAC,CAO9B;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,wBAAwB,EAChC,IAAI,EAAE,yBAAyB,EAC/B,EAAE,GAAE,oBAAgC,GACnC,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwBnE"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { GroovenetClient, loadConfig } from "@groovenet/client";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { printError } from "../output.js";
|
|
4
|
+
export function makeClient() {
|
|
5
|
+
const cfg = loadConfig();
|
|
6
|
+
return new GroovenetClient({
|
|
7
|
+
baseUrl: cfg.api_base,
|
|
8
|
+
apiKey: cfg.api_key,
|
|
9
|
+
insecureTls: cfg.insecure_tls,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export const consoleIO = {
|
|
13
|
+
log: (line) => console.log(line),
|
|
14
|
+
write: (text) => process.stdout.write(text),
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Turn the scope flags into one request.
|
|
18
|
+
*
|
|
19
|
+
* Exactly one scope, so `--missing --all` is refused rather than silently
|
|
20
|
+
* resolved to whichever the parser happened to see last. With no scope flag at
|
|
21
|
+
* all it is `--missing`: the cheap, common case, since the alternatives re-read
|
|
22
|
+
* and re-hash every already-indexed file.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveScope(opts) {
|
|
25
|
+
const chosen = [
|
|
26
|
+
opts.missing ? "missing" : null,
|
|
27
|
+
opts.changed ? "changed" : null,
|
|
28
|
+
opts.all ? "all" : null,
|
|
29
|
+
opts.track ? "track" : null,
|
|
30
|
+
opts.release ? "release" : null,
|
|
31
|
+
].filter((scope) => scope !== null);
|
|
32
|
+
if (chosen.length > 1) {
|
|
33
|
+
throw new Error(`Choose one scope, not ${chosen.length}: ${chosen.map((s) => `--${s}`).join(" ")}`);
|
|
34
|
+
}
|
|
35
|
+
const scope = (chosen[0] ?? "missing");
|
|
36
|
+
const request = { scope };
|
|
37
|
+
if (scope === "track")
|
|
38
|
+
request.track_id = opts.track;
|
|
39
|
+
if (scope === "release")
|
|
40
|
+
request.release_id = opts.release;
|
|
41
|
+
if (opts.friendId !== undefined)
|
|
42
|
+
request.friend_id = opts.friendId;
|
|
43
|
+
if (opts.force)
|
|
44
|
+
request.force = true;
|
|
45
|
+
return request;
|
|
46
|
+
}
|
|
47
|
+
/** How many of the queued tracks have reached a terminal state. */
|
|
48
|
+
export function settledCount(run) {
|
|
49
|
+
return run.indexed + run.skipped + run.failed;
|
|
50
|
+
}
|
|
51
|
+
/** `indexing 3412/3653 ██████████░░` — one line, rewritten in place. */
|
|
52
|
+
export function formatProgress(run, width = 24) {
|
|
53
|
+
const done = settledCount(run);
|
|
54
|
+
const ratio = run.queued > 0 ? Math.min(done / run.queued, 1) : 1;
|
|
55
|
+
const filled = Math.round(ratio * width);
|
|
56
|
+
const bar = "█".repeat(filled) + "░".repeat(width - filled);
|
|
57
|
+
return ` indexing ${done}/${run.queued} ${bar}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The four counters the run reports.
|
|
61
|
+
*
|
|
62
|
+
* `unindexable` is listed apart from `failed` on purpose: a track with no
|
|
63
|
+
* `local_audio_url` has no reference audio to fingerprint, which is a fact
|
|
64
|
+
* about how much of the library has been downloaded, not a failure of the run.
|
|
65
|
+
*/
|
|
66
|
+
export function formatSummary(run) {
|
|
67
|
+
const parts = [
|
|
68
|
+
chalk.green(`✓ ${run.indexed} indexed`),
|
|
69
|
+
chalk.gray(`⤼ ${run.skipped} skipped`),
|
|
70
|
+
run.failed > 0 ? chalk.red(`✗ ${run.failed} failed`) : chalk.gray("✗ 0 failed"),
|
|
71
|
+
chalk.gray(`– ${run.unindexable} no audio`),
|
|
72
|
+
];
|
|
73
|
+
return ` ${parts.join(" ")}`;
|
|
74
|
+
}
|
|
75
|
+
function sleep(ms) {
|
|
76
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Poll until every queued track has settled.
|
|
80
|
+
*
|
|
81
|
+
* `onProgress` is called with each poll so the caller owns the rendering — the
|
|
82
|
+
* loop itself stays testable without a terminal.
|
|
83
|
+
*/
|
|
84
|
+
export async function waitForRun(client, runId, opts) {
|
|
85
|
+
for (;;) {
|
|
86
|
+
const run = await client.getFingerprintIndexRun(runId);
|
|
87
|
+
opts.onProgress?.(run);
|
|
88
|
+
if (run.complete)
|
|
89
|
+
return run;
|
|
90
|
+
await sleep(opts.pollIntervalMs);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* One `fingerprint-library` run, start to finish. Returns the process exit
|
|
95
|
+
* code: non-zero when any track failed, so the command composes in a script.
|
|
96
|
+
*
|
|
97
|
+
* Separate from the commander wiring because everything interesting happens
|
|
98
|
+
* here — scope resolution, the two output modes, the polling and the exit code
|
|
99
|
+
* — and a commander action is not callable from a test.
|
|
100
|
+
*/
|
|
101
|
+
export async function runFingerprintLibrary(client, opts, io = consoleIO) {
|
|
102
|
+
const request = resolveScope(opts);
|
|
103
|
+
const started = await client.startFingerprintIndex(request);
|
|
104
|
+
if (!opts.json) {
|
|
105
|
+
io.log(chalk.bold(`Indexing ${started.scope}`) +
|
|
106
|
+
chalk.gray(` — ${started.fingerprint_type} ${started.fingerprint_version}`));
|
|
107
|
+
io.log(chalk.gray(` ${started.queued} queued, ${started.unindexable} without reference audio`));
|
|
108
|
+
}
|
|
109
|
+
if (!opts.wait) {
|
|
110
|
+
if (opts.json)
|
|
111
|
+
io.write(JSON.stringify(started, null, 2) + "\n");
|
|
112
|
+
else
|
|
113
|
+
io.log(chalk.gray(` run ${started.run_id}`));
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const finished = await waitForRun(client, started.run_id, {
|
|
118
|
+
pollIntervalMs: opts.pollInterval ?? 1000,
|
|
119
|
+
onProgress: (run) => {
|
|
120
|
+
if (opts.json)
|
|
121
|
+
return;
|
|
122
|
+
// Failures scroll above the progress line as they happen, so a long run
|
|
123
|
+
// does not hide them until the very end.
|
|
124
|
+
for (const failure of run.errors) {
|
|
125
|
+
if (seen.has(failure))
|
|
126
|
+
continue;
|
|
127
|
+
seen.add(failure);
|
|
128
|
+
io.log(chalk.red(` ✗ ${failure}`));
|
|
129
|
+
}
|
|
130
|
+
io.write(`\r${formatProgress(run)}`);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
if (opts.json) {
|
|
134
|
+
io.write(JSON.stringify(finished, null, 2) + "\n");
|
|
135
|
+
return finished.failed > 0 ? 1 : 0;
|
|
136
|
+
}
|
|
137
|
+
io.write("\r\x1b[2K");
|
|
138
|
+
io.log(formatSummary(finished));
|
|
139
|
+
return finished.failed > 0 ? 1 : 0;
|
|
140
|
+
}
|
|
141
|
+
export function addFingerprintLibraryCommand(program) {
|
|
142
|
+
program
|
|
143
|
+
.command("fingerprint-library")
|
|
144
|
+
.description("Build the reference fingerprint index the live vinyl matcher searches")
|
|
145
|
+
.option("--missing", "Only tracks with no fingerprint yet (default)")
|
|
146
|
+
.option("--changed", "Only tracks already indexed, re-checked for edits")
|
|
147
|
+
.option("--all", "Every track with reference audio")
|
|
148
|
+
.option("--track <id>", "One track")
|
|
149
|
+
.option("--release <id>", "Every track on one release")
|
|
150
|
+
.option("--friend-id <n>", "Narrow to one friend's library", parseInt)
|
|
151
|
+
.option("--force", "Re-fingerprint even when the audio is unchanged")
|
|
152
|
+
.option("--no-wait", "Queue the run and exit without waiting")
|
|
153
|
+
.option("--poll-interval <ms>", "How often to poll progress", parseInt, 1000)
|
|
154
|
+
.option("--json", "Output as JSON")
|
|
155
|
+
.action(async (opts) => {
|
|
156
|
+
try {
|
|
157
|
+
process.exitCode = await runFingerprintLibrary(makeClient(), opts);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=fingerprintLibrary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprintLibrary.js","sourceRoot":"","sources":["../../src/commands/fingerprintLibrary.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playlists.d.ts","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"playlists.d.ts","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgG3D"}
|
|
@@ -6,6 +6,37 @@ function makeClient() {
|
|
|
6
6
|
}
|
|
7
7
|
export function addPlaylistsCommands(program) {
|
|
8
8
|
const playlists = program.command("playlists").description("Manage playlists");
|
|
9
|
+
const sets = playlists.command("set").description("Manage a playlist's optional set record");
|
|
10
|
+
sets.command("show <id>").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
11
|
+
try {
|
|
12
|
+
const set = await makeClient().getLiveSet(id);
|
|
13
|
+
opts.json ? printJson(set) : console.log(`${set.title || "Untitled set"} · ${set.status}`);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
sets.command("create <id>").action(async (id) => {
|
|
21
|
+
try {
|
|
22
|
+
await makeClient().createLiveSet(id);
|
|
23
|
+
printSuccess("Set created.");
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
sets.command("delete <id>").action(async (id) => {
|
|
31
|
+
try {
|
|
32
|
+
await makeClient().deleteLiveSet(id);
|
|
33
|
+
printSuccess("Set removed; playlist kept.");
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
9
40
|
playlists
|
|
10
41
|
.command("list")
|
|
11
42
|
.description("List all playlists")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playlists.js","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEhG,SAAS,UAAU;IACjB,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,OAAO,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;AAC5G,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"playlists.js","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groovenet/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CLI for managing your Groovenet DJ collection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc",
|
|
14
14
|
"dev": "tsc --watch",
|
|
15
|
-
"start": "node build/bin/groovenet.js"
|
|
15
|
+
"start": "node build/bin/groovenet.js",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=lcov",
|
|
18
|
+
"test:watch": "vitest"
|
|
16
19
|
},
|
|
17
20
|
"files": [
|
|
18
21
|
"build/"
|
|
@@ -36,13 +39,15 @@
|
|
|
36
39
|
"homepage": "https://github.com/Public-Vinyl-Radio/groovenet/tree/main/packages/groovenet-cli#readme",
|
|
37
40
|
"bugs": "https://github.com/Public-Vinyl-Radio/groovenet/issues",
|
|
38
41
|
"dependencies": {
|
|
39
|
-
"@groovenet/client": "^1.0
|
|
42
|
+
"@groovenet/client": "^2.1.0",
|
|
40
43
|
"chalk": "^6",
|
|
41
44
|
"cli-table3": "^0.6",
|
|
42
45
|
"commander": "^15"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {
|
|
45
|
-
"@types/node": "^26",
|
|
46
|
-
"
|
|
48
|
+
"@types/node": "^26.4.1",
|
|
49
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
50
|
+
"typescript": "^7.0",
|
|
51
|
+
"vitest": "^5.0.0"
|
|
47
52
|
}
|
|
48
53
|
}
|
package/build/commands/play.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"play.d.ts","sourceRoot":"","sources":["../../src/commands/play.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuEtD"}
|
package/build/commands/play.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { GroovenetClient, loadConfig } from "@groovenet/client";
|
|
2
|
-
import { printJson, printSuccess, printError } from "../output.js";
|
|
3
|
-
function makeClient() {
|
|
4
|
-
const cfg = loadConfig();
|
|
5
|
-
return new GroovenetClient({ baseUrl: cfg.api_base, apiKey: cfg.api_key, insecureTls: cfg.insecure_tls });
|
|
6
|
-
}
|
|
7
|
-
export function addPlayCommands(program) {
|
|
8
|
-
program
|
|
9
|
-
.command("play <track-id>")
|
|
10
|
-
.description("Play a track via MPD on the server (resolves local_audio_url, sends to MPD)")
|
|
11
|
-
.option("--friend-id <n>", "Friend ID (defaults to config default_friend_id)", parseInt)
|
|
12
|
-
.action(async (trackId, opts) => {
|
|
13
|
-
try {
|
|
14
|
-
const cfg = loadConfig();
|
|
15
|
-
const client = makeClient();
|
|
16
|
-
const track = await client.getTrack(trackId, opts.friendId ?? cfg.default_friend_id);
|
|
17
|
-
if (!track.local_audio_url) {
|
|
18
|
-
printError(`Track "${track.title}" has no local audio file. Download it first.`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
await client.play(track.local_audio_url);
|
|
22
|
-
printSuccess(`▶ Playing: ${track.title} — ${track.artist}`);
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
program
|
|
30
|
-
.command("pause")
|
|
31
|
-
.description("Pause playback")
|
|
32
|
-
.action(async () => {
|
|
33
|
-
try {
|
|
34
|
-
const client = makeClient();
|
|
35
|
-
await client.pause();
|
|
36
|
-
printSuccess("⏸ Paused.");
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
program
|
|
44
|
-
.command("stop")
|
|
45
|
-
.description("Stop playback")
|
|
46
|
-
.action(async () => {
|
|
47
|
-
try {
|
|
48
|
-
const client = makeClient();
|
|
49
|
-
await client.stop();
|
|
50
|
-
printSuccess("⏹ Stopped.");
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
program
|
|
58
|
-
.command("now-playing")
|
|
59
|
-
.description("Show current playback status")
|
|
60
|
-
.option("--json", "Output as JSON")
|
|
61
|
-
.action(async (opts) => {
|
|
62
|
-
try {
|
|
63
|
-
const client = makeClient();
|
|
64
|
-
const status = await client.getPlaybackStatus();
|
|
65
|
-
if (opts.json) {
|
|
66
|
-
printJson(status);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
console.log(`Playback enabled: ${status.enabled}`);
|
|
70
|
-
if (status.status) {
|
|
71
|
-
console.log("Status:", JSON.stringify(status.status, null, 2));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
//# sourceMappingURL=play.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"play.js","sourceRoot":"","sources":["../../src/commands/play.ts"],"names":[],"mappings":"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"}
|