@raphiiko/wavelink-cli 0.0.1
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 +123 -0
- package/dist/index.js +5787 -0
- package/package.json +57 -0
- package/src/index.ts +671 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { WaveLinkClient } from "@raphiiko/wavelink-ts";
|
|
3
|
+
import { Argument, Command } from "commander";
|
|
4
|
+
|
|
5
|
+
// Type definitions for finder results
|
|
6
|
+
type MixInfo = { id: string; name: string };
|
|
7
|
+
type OutputDeviceInfo = { deviceId: string; outputId: string; currentMixId: string };
|
|
8
|
+
type OutputInfo = OutputDeviceInfo & { level: number; isMuted: boolean };
|
|
9
|
+
type InputInfo = { deviceId: string; inputId: string; gain: number; isMuted: boolean };
|
|
10
|
+
type ChannelInfo = { id: string; name: string };
|
|
11
|
+
|
|
12
|
+
// Utility functions
|
|
13
|
+
function exitWithError(message: string): never {
|
|
14
|
+
console.error(`Error: ${message}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parsePercent(value: string, name: string): number {
|
|
19
|
+
const percent = parseInt(value, 10);
|
|
20
|
+
if (isNaN(percent) || percent < 0 || percent > 100) {
|
|
21
|
+
exitWithError(`${name} must be a number between 0 and 100`);
|
|
22
|
+
}
|
|
23
|
+
return percent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatPercent(value: number): string {
|
|
27
|
+
return `${(value * 100).toFixed(0)}%`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatMuted(isMuted: boolean): string {
|
|
31
|
+
return isMuted ? "Yes" : "No";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Finder functions
|
|
35
|
+
async function findMixByIdOrName(
|
|
36
|
+
client: WaveLinkClient,
|
|
37
|
+
idOrName: string
|
|
38
|
+
): Promise<MixInfo | null> {
|
|
39
|
+
const { mixes } = await client.getMixes();
|
|
40
|
+
const lowerIdOrName = idOrName.toLowerCase();
|
|
41
|
+
const mix = mixes.find(
|
|
42
|
+
(m) => m.id.toLowerCase() === lowerIdOrName || m.name.toLowerCase() === lowerIdOrName
|
|
43
|
+
);
|
|
44
|
+
return mix ? { id: mix.id, name: mix.name } : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function findOutputDeviceById(
|
|
48
|
+
client: WaveLinkClient,
|
|
49
|
+
deviceId: string
|
|
50
|
+
): Promise<OutputDeviceInfo | null> {
|
|
51
|
+
const { outputDevices } = await client.getOutputDevices();
|
|
52
|
+
const device = outputDevices.find((d) => d.id === deviceId);
|
|
53
|
+
const output = device?.outputs[0];
|
|
54
|
+
if (!output) return null;
|
|
55
|
+
return { deviceId: device.id, outputId: output.id, currentMixId: output.mixId };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function findOutputById(
|
|
59
|
+
client: WaveLinkClient,
|
|
60
|
+
outputId: string
|
|
61
|
+
): Promise<OutputInfo | null> {
|
|
62
|
+
const { outputDevices } = await client.getOutputDevices();
|
|
63
|
+
for (const device of outputDevices) {
|
|
64
|
+
const output = device.outputs.find((o) => o.id === outputId);
|
|
65
|
+
if (output) {
|
|
66
|
+
return {
|
|
67
|
+
deviceId: device.id,
|
|
68
|
+
outputId: output.id,
|
|
69
|
+
currentMixId: output.mixId,
|
|
70
|
+
level: output.level,
|
|
71
|
+
isMuted: output.isMuted,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function findInputById(client: WaveLinkClient, inputId: string): Promise<InputInfo | null> {
|
|
79
|
+
const { inputDevices } = await client.getInputDevices();
|
|
80
|
+
for (const device of inputDevices) {
|
|
81
|
+
const input = device.inputs.find((i) => i.id === inputId);
|
|
82
|
+
if (input) {
|
|
83
|
+
return {
|
|
84
|
+
deviceId: device.id,
|
|
85
|
+
inputId: input.id,
|
|
86
|
+
gain: input.gain.value,
|
|
87
|
+
isMuted: input.isMuted,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function findChannelById(
|
|
95
|
+
client: WaveLinkClient,
|
|
96
|
+
channelId: string
|
|
97
|
+
): Promise<ChannelInfo | null> {
|
|
98
|
+
const { channels } = await client.getChannels();
|
|
99
|
+
const channel = channels.find((c) => c.id === channelId);
|
|
100
|
+
return channel ? { id: channel.id, name: channel.image?.name || channel.id } : null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Required finder wrappers that exit on not found
|
|
104
|
+
async function requireMix(client: WaveLinkClient, mixId: string): Promise<MixInfo> {
|
|
105
|
+
const mix = await findMixByIdOrName(client, mixId);
|
|
106
|
+
if (!mix) exitWithError(`Mix '${mixId}' not found`);
|
|
107
|
+
return mix;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function requireOutputDevice(
|
|
111
|
+
client: WaveLinkClient,
|
|
112
|
+
deviceId: string
|
|
113
|
+
): Promise<OutputDeviceInfo> {
|
|
114
|
+
const device = await findOutputDeviceById(client, deviceId);
|
|
115
|
+
if (!device) exitWithError(`Output device '${deviceId}' not found`);
|
|
116
|
+
return device;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function requireOutput(client: WaveLinkClient, outputId: string): Promise<OutputInfo> {
|
|
120
|
+
const output = await findOutputById(client, outputId);
|
|
121
|
+
if (!output) exitWithError(`Output '${outputId}' not found`);
|
|
122
|
+
return output;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function requireInput(client: WaveLinkClient, inputId: string): Promise<InputInfo> {
|
|
126
|
+
const input = await findInputById(client, inputId);
|
|
127
|
+
if (!input) exitWithError(`Input '${inputId}' not found`);
|
|
128
|
+
return input;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function requireChannel(client: WaveLinkClient, channelId: string): Promise<ChannelInfo> {
|
|
132
|
+
const channel = await findChannelById(client, channelId);
|
|
133
|
+
if (!channel) exitWithError(`Channel '${channelId}' not found`);
|
|
134
|
+
return channel;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Operation functions
|
|
138
|
+
async function showApplicationInfo(client: WaveLinkClient): Promise<void> {
|
|
139
|
+
const info = await client.getApplicationInfo();
|
|
140
|
+
console.log("\n=== Wave Link Application Info ===\n");
|
|
141
|
+
console.log(`Application ID: ${info.appID}`);
|
|
142
|
+
console.log(`Name: ${info.name}`);
|
|
143
|
+
console.log(`Interface Revision: ${info.interfaceRevision}`);
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function setOutputVolume(
|
|
148
|
+
client: WaveLinkClient,
|
|
149
|
+
outputId: string,
|
|
150
|
+
volumePercent: number
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
const output = await requireOutput(client, outputId);
|
|
153
|
+
await client.setOutputVolume(output.deviceId, output.outputId, volumePercent / 100);
|
|
154
|
+
console.log(`Successfully set output '${output.outputId}' volume to ${volumePercent}%`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function setOutputMute(
|
|
158
|
+
client: WaveLinkClient,
|
|
159
|
+
outputId: string,
|
|
160
|
+
isMuted: boolean
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
const output = await requireOutput(client, outputId);
|
|
163
|
+
await client.setOutputDevice({
|
|
164
|
+
outputDevice: { id: output.deviceId, outputs: [{ id: output.outputId, isMuted }] },
|
|
165
|
+
});
|
|
166
|
+
console.log(`Successfully ${isMuted ? "muted" : "unmuted"} output '${output.outputId}'`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function setInputGain(
|
|
170
|
+
client: WaveLinkClient,
|
|
171
|
+
inputId: string,
|
|
172
|
+
gainPercent: number
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
const input = await requireInput(client, inputId);
|
|
175
|
+
await client.setInputGain(input.deviceId, input.inputId, gainPercent / 100);
|
|
176
|
+
console.log(`Successfully set input '${input.inputId}' gain to ${gainPercent}%`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function setInputMute(
|
|
180
|
+
client: WaveLinkClient,
|
|
181
|
+
inputId: string,
|
|
182
|
+
isMuted: boolean
|
|
183
|
+
): Promise<void> {
|
|
184
|
+
const input = await requireInput(client, inputId);
|
|
185
|
+
await client.setInputMute(input.deviceId, input.inputId, isMuted);
|
|
186
|
+
console.log(`Successfully ${isMuted ? "muted" : "unmuted"} input '${input.inputId}'`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function listOutputs(client: WaveLinkClient): Promise<void> {
|
|
190
|
+
const { outputDevices, mainOutput } = await client.getOutputDevices();
|
|
191
|
+
const { mixes } = await client.getMixes();
|
|
192
|
+
|
|
193
|
+
console.log("\n=== Output Devices ===\n");
|
|
194
|
+
|
|
195
|
+
if (outputDevices.length === 0) {
|
|
196
|
+
console.log("No output devices found.");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const device of outputDevices) {
|
|
201
|
+
const isMain = device.id === mainOutput ? " (MAIN OUTPUT)" : "";
|
|
202
|
+
console.log(`Device ID: ${device.id}${isMain}`);
|
|
203
|
+
|
|
204
|
+
if (device.outputs.length === 0) {
|
|
205
|
+
console.log(" No outputs available");
|
|
206
|
+
} else {
|
|
207
|
+
for (const output of device.outputs) {
|
|
208
|
+
const mix = mixes.find((m) => m.id === output.mixId);
|
|
209
|
+
console.log(` Output ID: ${output.id}`);
|
|
210
|
+
console.log(` Current Mix: ${mix?.name ?? output.mixId} (${output.mixId})`);
|
|
211
|
+
console.log(` Level: ${formatPercent(output.level)}`);
|
|
212
|
+
console.log(` Muted: ${formatMuted(output.isMuted)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
console.log();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function listMixes(client: WaveLinkClient): Promise<void> {
|
|
220
|
+
const { mixes } = await client.getMixes();
|
|
221
|
+
|
|
222
|
+
console.log("\n=== Mixes ===\n");
|
|
223
|
+
|
|
224
|
+
if (mixes.length === 0) {
|
|
225
|
+
console.log("No mixes found.");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const mix of mixes) {
|
|
230
|
+
console.log(`Mix: ${mix.name}`);
|
|
231
|
+
console.log(` ID: ${mix.id}`);
|
|
232
|
+
console.log(` Level: ${formatPercent(mix.level)}`);
|
|
233
|
+
console.log(` Muted: ${formatMuted(mix.isMuted)}`);
|
|
234
|
+
console.log();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function listChannels(client: WaveLinkClient): Promise<void> {
|
|
239
|
+
const { channels } = await client.getChannels();
|
|
240
|
+
|
|
241
|
+
console.log("\n=== Channels ===\n");
|
|
242
|
+
|
|
243
|
+
if (channels.length === 0) {
|
|
244
|
+
console.log("No channels found.");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const channel of channels) {
|
|
249
|
+
console.log(`Channel: ${channel.image?.name ?? channel.id}`);
|
|
250
|
+
console.log(` ID: ${channel.id}`);
|
|
251
|
+
console.log(` Type: ${channel.type}`);
|
|
252
|
+
console.log(` Level: ${formatPercent(channel.level)}`);
|
|
253
|
+
console.log(` Muted: ${formatMuted(channel.isMuted)}`);
|
|
254
|
+
|
|
255
|
+
if (channel.apps?.length) {
|
|
256
|
+
console.log(` Apps: ${channel.apps.map((a) => a.name || a.id).join(", ")}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (channel.mixes?.length) {
|
|
260
|
+
console.log(" Mix Assignments:");
|
|
261
|
+
for (const mix of channel.mixes) {
|
|
262
|
+
console.log(
|
|
263
|
+
` ${mix.id}: Level ${formatPercent(mix.level)}, Muted: ${formatMuted(mix.isMuted)}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
console.log();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function listInputs(client: WaveLinkClient): Promise<void> {
|
|
272
|
+
const { inputDevices } = await client.getInputDevices();
|
|
273
|
+
|
|
274
|
+
console.log("\n=== Input Devices ===\n");
|
|
275
|
+
|
|
276
|
+
if (inputDevices.length === 0) {
|
|
277
|
+
console.log("No input devices found.");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (const device of inputDevices) {
|
|
282
|
+
console.log(`Device ID: ${device.id}`);
|
|
283
|
+
console.log(` Wave Device: ${formatMuted(device.isWaveDevice)}`);
|
|
284
|
+
|
|
285
|
+
if (device.inputs.length === 0) {
|
|
286
|
+
console.log(" No inputs available");
|
|
287
|
+
} else {
|
|
288
|
+
for (const input of device.inputs) {
|
|
289
|
+
console.log(` Input ID: ${input.id}`);
|
|
290
|
+
console.log(` Gain: ${formatPercent(input.gain.value)} (max: ${input.gain.maxRange})`);
|
|
291
|
+
console.log(` Muted: ${formatMuted(input.isMuted)}`);
|
|
292
|
+
if (input.micPcMix) {
|
|
293
|
+
console.log(` Mic/PC Mix: ${formatPercent(input.micPcMix.value)}`);
|
|
294
|
+
}
|
|
295
|
+
if (input.effects?.length) {
|
|
296
|
+
const effectsList = input.effects
|
|
297
|
+
.map((e) => `${e.id} (${e.isEnabled ? "ON" : "OFF"})`)
|
|
298
|
+
.join(", ");
|
|
299
|
+
console.log(` Effects: ${effectsList}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
console.log();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function assignOutputToMix(
|
|
308
|
+
client: WaveLinkClient,
|
|
309
|
+
deviceId: string,
|
|
310
|
+
mixId: string
|
|
311
|
+
): Promise<void> {
|
|
312
|
+
const mix = await requireMix(client, mixId);
|
|
313
|
+
const device = await requireOutputDevice(client, deviceId);
|
|
314
|
+
|
|
315
|
+
if (device.currentMixId === mix.id) {
|
|
316
|
+
console.log(`Output device '${device.deviceId}' is already assigned to mix '${mix.name}'`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
await client.switchOutputMix(device.deviceId, device.outputId, mix.id);
|
|
321
|
+
console.log(`Successfully assigned output device '${device.deviceId}' to mix '${mix.name}'`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function unassignOutputDevice(client: WaveLinkClient, deviceId: string): Promise<void> {
|
|
325
|
+
const device = await requireOutputDevice(client, deviceId);
|
|
326
|
+
await client.removeOutputFromMix(device.deviceId, device.outputId);
|
|
327
|
+
console.log(`Successfully unassigned output device '${device.deviceId}'`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function setSingleOutputForMix(
|
|
331
|
+
client: WaveLinkClient,
|
|
332
|
+
deviceId: string,
|
|
333
|
+
mixId: string
|
|
334
|
+
): Promise<void> {
|
|
335
|
+
const mix = await requireMix(client, mixId);
|
|
336
|
+
const targetDevice = await requireOutputDevice(client, deviceId);
|
|
337
|
+
|
|
338
|
+
const { outputDevices } = await client.getOutputDevices();
|
|
339
|
+
const { mixes } = await client.getMixes();
|
|
340
|
+
|
|
341
|
+
const otherMix = mixes.find((m) => m.id !== mix.id);
|
|
342
|
+
if (!otherMix) {
|
|
343
|
+
exitWithError(
|
|
344
|
+
"Cannot use 'single' command with only one mix available. At least two mixes are required."
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let devicesReassigned = 0;
|
|
349
|
+
let targetDeviceAssigned = false;
|
|
350
|
+
|
|
351
|
+
for (const device of outputDevices) {
|
|
352
|
+
for (const output of device.outputs) {
|
|
353
|
+
const isTargetDevice = device.id === targetDevice.deviceId;
|
|
354
|
+
|
|
355
|
+
if (isTargetDevice && output.mixId !== mix.id) {
|
|
356
|
+
await client.switchOutputMix(device.id, output.id, mix.id);
|
|
357
|
+
targetDeviceAssigned = true;
|
|
358
|
+
} else if (!isTargetDevice && output.mixId === mix.id) {
|
|
359
|
+
await client.switchOutputMix(device.id, output.id, otherMix.id);
|
|
360
|
+
devicesReassigned++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (targetDeviceAssigned && devicesReassigned > 0) {
|
|
366
|
+
console.log(
|
|
367
|
+
`Successfully set '${targetDevice.deviceId}' as the only output for mix '${mix.name}' ` +
|
|
368
|
+
`(reassigned ${devicesReassigned} other device(s) to '${otherMix.name}')`
|
|
369
|
+
);
|
|
370
|
+
} else if (targetDeviceAssigned) {
|
|
371
|
+
console.log(
|
|
372
|
+
`Successfully assigned '${targetDevice.deviceId}' to mix '${mix.name}' ` +
|
|
373
|
+
`(it was already the only device on this mix)`
|
|
374
|
+
);
|
|
375
|
+
} else if (devicesReassigned > 0) {
|
|
376
|
+
console.log(
|
|
377
|
+
`'${targetDevice.deviceId}' was already assigned to mix '${mix.name}'. ` +
|
|
378
|
+
`Reassigned ${devicesReassigned} other device(s) to '${otherMix.name}'`
|
|
379
|
+
);
|
|
380
|
+
} else {
|
|
381
|
+
console.log(`'${targetDevice.deviceId}' is already the only output for mix '${mix.name}'`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function withClient<T>(action: (client: WaveLinkClient) => Promise<T>): Promise<T> {
|
|
386
|
+
const client = new WaveLinkClient({
|
|
387
|
+
autoReconnect: false,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
console.log("Connecting to Wave Link...");
|
|
392
|
+
await client.connect();
|
|
393
|
+
console.log("Connected successfully");
|
|
394
|
+
|
|
395
|
+
return await action(client);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (error instanceof Error) {
|
|
398
|
+
console.error(`Error: ${error.message}`);
|
|
399
|
+
} else {
|
|
400
|
+
console.error("An unexpected error occurred");
|
|
401
|
+
}
|
|
402
|
+
process.exit(1);
|
|
403
|
+
} finally {
|
|
404
|
+
client.disconnect();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const program = new Command();
|
|
409
|
+
|
|
410
|
+
program
|
|
411
|
+
.name("wavelink-cli")
|
|
412
|
+
.description("Manage Elgato Wave Link via command line")
|
|
413
|
+
.version("1.0.0");
|
|
414
|
+
|
|
415
|
+
// Info command
|
|
416
|
+
program
|
|
417
|
+
.command("info")
|
|
418
|
+
.description("Show Wave Link application information")
|
|
419
|
+
.action(() => withClient(showApplicationInfo));
|
|
420
|
+
|
|
421
|
+
// Output commands
|
|
422
|
+
const outputCmd = program.command("output").description("Manage output devices");
|
|
423
|
+
|
|
424
|
+
outputCmd
|
|
425
|
+
.command("list")
|
|
426
|
+
.description("List all output devices with their IDs and current mix assignments")
|
|
427
|
+
.action(() => withClient(listOutputs));
|
|
428
|
+
|
|
429
|
+
outputCmd
|
|
430
|
+
.command("assign")
|
|
431
|
+
.description("Assign an output device to a specific mix")
|
|
432
|
+
.addArgument(
|
|
433
|
+
new Argument("<output-id>", "ID of the output device (use 'output list' to find IDs)")
|
|
434
|
+
)
|
|
435
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
436
|
+
.action((outputId: string, mixId: string) =>
|
|
437
|
+
withClient((client) => assignOutputToMix(client, outputId, mixId))
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
outputCmd
|
|
441
|
+
.command("unassign")
|
|
442
|
+
.description("Unassign an output device from its current mix")
|
|
443
|
+
.addArgument(new Argument("<output-id>", "ID of the output device"))
|
|
444
|
+
.action((outputId: string) => withClient((client) => unassignOutputDevice(client, outputId)));
|
|
445
|
+
|
|
446
|
+
outputCmd
|
|
447
|
+
.command("set-volume")
|
|
448
|
+
.description("Set output device volume")
|
|
449
|
+
.addArgument(new Argument("<output-id>", "ID of the output device"))
|
|
450
|
+
.addArgument(new Argument("<volume>", "Volume level (0-100)"))
|
|
451
|
+
.action((outputId: string, volume: string) => {
|
|
452
|
+
const volumePercent = parsePercent(volume, "Volume");
|
|
453
|
+
return withClient((client) => setOutputVolume(client, outputId, volumePercent));
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
outputCmd
|
|
457
|
+
.command("mute")
|
|
458
|
+
.description("Mute an output device")
|
|
459
|
+
.addArgument(new Argument("<output-id>", "ID of the output device"))
|
|
460
|
+
.action((outputId: string) => withClient((client) => setOutputMute(client, outputId, true)));
|
|
461
|
+
|
|
462
|
+
outputCmd
|
|
463
|
+
.command("unmute")
|
|
464
|
+
.description("Unmute an output device")
|
|
465
|
+
.addArgument(new Argument("<output-id>", "ID of the output device"))
|
|
466
|
+
.action((outputId: string) => withClient((client) => setOutputMute(client, outputId, false)));
|
|
467
|
+
|
|
468
|
+
// Mix commands
|
|
469
|
+
const mixCmd = program.command("mix").description("Manage mixes");
|
|
470
|
+
|
|
471
|
+
mixCmd
|
|
472
|
+
.command("list")
|
|
473
|
+
.description("List all mixes with their IDs and names")
|
|
474
|
+
.action(() => withClient(listMixes));
|
|
475
|
+
|
|
476
|
+
mixCmd
|
|
477
|
+
.command("set-output")
|
|
478
|
+
.description(
|
|
479
|
+
"Set a device as the ONLY output for a mix (removes all other outputs from that mix)"
|
|
480
|
+
)
|
|
481
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
482
|
+
.addArgument(new Argument("<output-id>", "ID of the output device"))
|
|
483
|
+
.action((mixId: string, outputId: string) =>
|
|
484
|
+
withClient((client) => setSingleOutputForMix(client, outputId, mixId))
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
mixCmd
|
|
488
|
+
.command("set-volume")
|
|
489
|
+
.description("Set mix master volume")
|
|
490
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
491
|
+
.addArgument(new Argument("<volume>", "Volume level (0-100)"))
|
|
492
|
+
.action((mixId: string, volume: string) => {
|
|
493
|
+
const volumePercent = parsePercent(volume, "Volume");
|
|
494
|
+
return withClient(async (client) => {
|
|
495
|
+
const mix = await requireMix(client, mixId);
|
|
496
|
+
await client.setMixVolume(mix.id, volumePercent / 100);
|
|
497
|
+
console.log(`Successfully set mix '${mix.name}' volume to ${volumePercent}%`);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
mixCmd
|
|
502
|
+
.command("mute")
|
|
503
|
+
.description("Mute a mix")
|
|
504
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
505
|
+
.action((mixId: string) =>
|
|
506
|
+
withClient(async (client) => {
|
|
507
|
+
const mix = await requireMix(client, mixId);
|
|
508
|
+
await client.setMixMute(mix.id, true);
|
|
509
|
+
console.log(`Successfully muted mix '${mix.name}'`);
|
|
510
|
+
})
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
mixCmd
|
|
514
|
+
.command("unmute")
|
|
515
|
+
.description("Unmute a mix")
|
|
516
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
517
|
+
.action((mixId: string) =>
|
|
518
|
+
withClient(async (client) => {
|
|
519
|
+
const mix = await requireMix(client, mixId);
|
|
520
|
+
await client.setMixMute(mix.id, false);
|
|
521
|
+
console.log(`Successfully unmuted mix '${mix.name}'`);
|
|
522
|
+
})
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
mixCmd
|
|
526
|
+
.command("toggle-mute")
|
|
527
|
+
.description("Toggle mix mute state")
|
|
528
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
529
|
+
.action((mixId: string) =>
|
|
530
|
+
withClient(async (client) => {
|
|
531
|
+
const mix = await requireMix(client, mixId);
|
|
532
|
+
await client.toggleMixMute(mix.id);
|
|
533
|
+
console.log(`Successfully toggled mute for mix '${mix.name}'`);
|
|
534
|
+
})
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
// Channel commands
|
|
538
|
+
const channelCmd = program.command("channel").description("Manage channels");
|
|
539
|
+
|
|
540
|
+
channelCmd
|
|
541
|
+
.command("list")
|
|
542
|
+
.description("List all channels with their IDs and names")
|
|
543
|
+
.action(() => withClient(listChannels));
|
|
544
|
+
|
|
545
|
+
channelCmd
|
|
546
|
+
.command("set-volume")
|
|
547
|
+
.description("Set channel master volume")
|
|
548
|
+
.addArgument(new Argument("<channel-id>", "ID of the channel"))
|
|
549
|
+
.addArgument(new Argument("<volume>", "Volume level (0-100)"))
|
|
550
|
+
.action((channelId: string, volume: string) => {
|
|
551
|
+
const volumePercent = parsePercent(volume, "Volume");
|
|
552
|
+
return withClient(async (client) => {
|
|
553
|
+
const channel = await requireChannel(client, channelId);
|
|
554
|
+
await client.setChannelVolume(channel.id, volumePercent / 100);
|
|
555
|
+
console.log(`Successfully set channel '${channel.name}' volume to ${volumePercent}%`);
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
channelCmd
|
|
560
|
+
.command("mute")
|
|
561
|
+
.description("Mute a channel")
|
|
562
|
+
.addArgument(new Argument("<channel-id>", "ID of the channel"))
|
|
563
|
+
.action((channelId: string) =>
|
|
564
|
+
withClient(async (client) => {
|
|
565
|
+
const channel = await requireChannel(client, channelId);
|
|
566
|
+
await client.setChannelMute(channel.id, true);
|
|
567
|
+
console.log(`Successfully muted channel '${channel.name}'`);
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
channelCmd
|
|
572
|
+
.command("unmute")
|
|
573
|
+
.description("Unmute a channel")
|
|
574
|
+
.addArgument(new Argument("<channel-id>", "ID of the channel"))
|
|
575
|
+
.action((channelId: string) =>
|
|
576
|
+
withClient(async (client) => {
|
|
577
|
+
const channel = await requireChannel(client, channelId);
|
|
578
|
+
await client.setChannelMute(channel.id, false);
|
|
579
|
+
console.log(`Successfully unmuted channel '${channel.name}'`);
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
channelCmd
|
|
584
|
+
.command("toggle-mute")
|
|
585
|
+
.description("Toggle channel mute state")
|
|
586
|
+
.addArgument(new Argument("<channel-id>", "ID of the channel"))
|
|
587
|
+
.action((channelId: string) =>
|
|
588
|
+
withClient(async (client) => {
|
|
589
|
+
const channel = await requireChannel(client, channelId);
|
|
590
|
+
await client.toggleChannelMute(channel.id);
|
|
591
|
+
console.log(`Successfully toggled mute for channel '${channel.name}'`);
|
|
592
|
+
})
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
channelCmd
|
|
596
|
+
.command("set-mix-volume")
|
|
597
|
+
.description("Set channel volume in a specific mix")
|
|
598
|
+
.addArgument(new Argument("<channel-id>", "ID of the channel"))
|
|
599
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
600
|
+
.addArgument(new Argument("<volume>", "Volume level (0-100)"))
|
|
601
|
+
.action((channelId: string, mixId: string, volume: string) => {
|
|
602
|
+
const volumePercent = parsePercent(volume, "Volume");
|
|
603
|
+
return withClient(async (client) => {
|
|
604
|
+
const channel = await requireChannel(client, channelId);
|
|
605
|
+
const mix = await requireMix(client, mixId);
|
|
606
|
+
await client.setChannelMixVolume(channel.id, mix.id, volumePercent / 100);
|
|
607
|
+
console.log(
|
|
608
|
+
`Successfully set channel '${channel.name}' volume to ${volumePercent}% in mix '${mix.name}'`
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
channelCmd
|
|
614
|
+
.command("mute-in-mix")
|
|
615
|
+
.description("Mute a channel in a specific mix")
|
|
616
|
+
.addArgument(new Argument("<channel-id>", "ID of the channel"))
|
|
617
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
618
|
+
.action((channelId: string, mixId: string) =>
|
|
619
|
+
withClient(async (client) => {
|
|
620
|
+
const channel = await requireChannel(client, channelId);
|
|
621
|
+
const mix = await requireMix(client, mixId);
|
|
622
|
+
await client.setChannelMixMute(channel.id, mix.id, true);
|
|
623
|
+
console.log(`Successfully muted channel '${channel.name}' in mix '${mix.name}'`);
|
|
624
|
+
})
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
channelCmd
|
|
628
|
+
.command("unmute-in-mix")
|
|
629
|
+
.description("Unmute a channel in a specific mix")
|
|
630
|
+
.addArgument(new Argument("<channel-id>", "ID of the channel"))
|
|
631
|
+
.addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
|
|
632
|
+
.action((channelId: string, mixId: string) =>
|
|
633
|
+
withClient(async (client) => {
|
|
634
|
+
const channel = await requireChannel(client, channelId);
|
|
635
|
+
const mix = await requireMix(client, mixId);
|
|
636
|
+
await client.setChannelMixMute(channel.id, mix.id, false);
|
|
637
|
+
console.log(`Successfully unmuted channel '${channel.name}' in mix '${mix.name}'`);
|
|
638
|
+
})
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
// Input commands
|
|
642
|
+
const inputCmd = program.command("input").description("Manage input devices");
|
|
643
|
+
|
|
644
|
+
inputCmd
|
|
645
|
+
.command("list")
|
|
646
|
+
.description("List all input devices with their IDs")
|
|
647
|
+
.action(() => withClient(listInputs));
|
|
648
|
+
|
|
649
|
+
inputCmd
|
|
650
|
+
.command("set-gain")
|
|
651
|
+
.description("Set input device gain")
|
|
652
|
+
.addArgument(new Argument("<input-id>", "ID of the input device"))
|
|
653
|
+
.addArgument(new Argument("<gain>", "Gain level (0-100)"))
|
|
654
|
+
.action((inputId: string, gain: string) => {
|
|
655
|
+
const gainPercent = parsePercent(gain, "Gain");
|
|
656
|
+
return withClient((client) => setInputGain(client, inputId, gainPercent));
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
inputCmd
|
|
660
|
+
.command("mute")
|
|
661
|
+
.description("Mute an input device")
|
|
662
|
+
.addArgument(new Argument("<input-id>", "ID of the input device"))
|
|
663
|
+
.action((inputId: string) => withClient((client) => setInputMute(client, inputId, true)));
|
|
664
|
+
|
|
665
|
+
inputCmd
|
|
666
|
+
.command("unmute")
|
|
667
|
+
.description("Unmute an input device")
|
|
668
|
+
.addArgument(new Argument("<input-id>", "ID of the input device"))
|
|
669
|
+
.action((inputId: string) => withClient((client) => setInputMute(client, inputId, false)));
|
|
670
|
+
|
|
671
|
+
await program.parseAsync();
|