14 Commits

Author SHA1 Message Date
61a3bc38a8 minor bump 2026-03-01 17:59:07 +00:00
8991737011 add Command Command to README 2026-03-01 17:58:58 +00:00
84d716b2ad add command subcommand group 2026-03-01 17:58:08 +00:00
b58bb3dde7 reword 2026-03-01 17:35:42 +00:00
d414844f8f md fix 2026-03-01 17:33:21 +00:00
f605178da6 add note about sendtext --help. 2026-03-01 17:26:21 +00:00
c535ae5571 minor bump 2026-03-01 17:25:23 +00:00
7b5d2150c7 add Sentext Command to README 2026-03-01 17:25:16 +00:00
28ec67839a add sendtext command, attach it to the root command.
disable the rt listeners if sendtext command is invoked.
2026-03-01 17:25:01 +00:00
dd0d150202 fix bus mono implementation
bump vban-cmd dep version

patch bump
2026-03-01 17:23:38 +00:00
78952aa3ff add Strip EQ and Strip EQ Cell examples.
fix Bus examples.
2026-03-01 01:32:22 +00:00
c4d67527f5 upd further notes 2026-03-01 01:22:18 +00:00
b3cfc6bc4a remove StripSubcommandHelpFormatter, StripHelpFormatter now handles commands + command groups. 2026-03-01 01:04:54 +00:00
c642bbc1f2 show the index arg 2026-02-28 16:03:34 +00:00
10 changed files with 155 additions and 44 deletions

View File

@@ -51,7 +51,7 @@ export VBAN_CLI_STREAMNAME=Command1
### Strip Command ### Strip Command
Usage: vban-cli strip <index> COMMAND [ARGS] Usage: vban-cli strip \<index> COMMAND [ARGS]
examples: examples:
@@ -65,26 +65,86 @@ vban-cli strip 2 gain -18.7
see `vban-cli strip --help` for more info. see `vban-cli strip --help` for more info.
### Bus Command #### Strip EQ
Usage: vban-cli bus <index> COMMAND [ARGS] Usage: vban-cli strip \<index> eq COMMAND [OPTIONS]
examples: examples:
```console ```console
vban-cli bus mode normal vban-cli strip 0 eq on true
```
vban-cli bus mute true see `vban-cli strip eq --help` for more info.
#### Strip EQ Cell Command
Usage: vban-cli strip \<index> eq cell \<band> COMMAND [ARGS]
examples:
```console
vban-cli strip 0 eq cell 0 on false
```
see `vban-cli strip eq cell --help` for more info.
### Bus Command
Usage: vban-cli bus \<index> COMMAND [ARGS]
examples:
```console
vban-cli bus 0 mode tvmix
vban-cli bus 1 mute true
``` ```
see `vban-cli bus --help` for more info. see `vban-cli bus --help` for more info.
### Command Command
Usage: vban-cli command COMMAND
examples:
```console
vban-cli command show
vban-cli command restart
```
see `vban-cli command --help` for more info.
### Sendtext Command
Usage: vban-cli sendtext TEXT
*To Voicemeeter*
examples:
```console
vban-cli sendtext 'Strip[0].Mute=1;Bus[0].Mono=2'
```
*To Matrix*
```console
vban-cli sendtext 'Command.Version = ?'
vban-cli sendtext 'Point(ASIO128.IN[1..4],ASIO128.OUT[1]).dBGain = -3.0'
```
see `vban-cli sendtext --help` for more info.
--- ---
## Implementation Notes ## Implementation Notes
1. The VBAN TEXT subprotocol defines two packet structures [ident:0][ident-0] and [ident:1][ident-1]. Neither of them contain the data for Bus EQ parameters. 1. The VBAN TEXT subprotocol defines two packet structures [ident:0][ident-0] and [ident:1][ident-1]. Neither of them contain the data for Bus EQ parameters.
2. Packet structure with [ident:1][ident-1] is emitted by the VBAN server only on pdirty events. This means we do not receive the initial state of those parameters on initial subscription. Therefore any commands which are intended to fetch the value of parameters defined in packet [ident:1][ident-1] will not work in this CLI. 2. Packet structure with [ident:1][ident-1] is emitted by the VBAN server only on pdirty events. This means we do not receive the current state of those parameters on initial subscription. Therefore any commands which are intended to fetch the value of parameters defined in packet [ident:1][ident-1] will not work in this CLI.
3. Packet structure with [ident:1][ident-1] defines parameteric EQ data only for the [first channel][ident-1-peq]. 3. Packet structure with [ident:1][ident-1] defines parameteric EQ data only for the [first channel][ident-1-peq].
--- ---
@@ -93,9 +153,7 @@ see `vban-cli bus --help` for more info.
I've made the effort to set up the basic skeletal structure of the CLI as well as demonstrate how to combine subcommand groups with subcommand groups so more can be implemented, it just needs doing. There may be restrictions on some things however, for example, retrieving values is only possible for parameters [defined in the protocol](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L787). Setting parameters can be done for anything possible by a string request. I've made the effort to set up the basic skeletal structure of the CLI as well as demonstrate how to combine subcommand groups with subcommand groups so more can be implemented, it just needs doing. There may be restrictions on some things however, for example, retrieving values is only possible for parameters [defined in the protocol](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L787). Setting parameters can be done for anything possible by a string request.
Shell completion scripts are available (for zsh, bash and fish) but I haven't tested them Shell completion scripts are available (for zsh, bash and fish) but they haven't been thoroughly tested.
Some of the help output needs improving for commands that branch off positional arguments.
If there's something missing that you would like to see added the best bet is to submit a PR. You may raise an issue and if it's quick and simple to do I may (or may not) do it. If there's something missing that you would like to see added the best bet is to submit a PR. You may raise an issue and if it's quick and simple to do I may (or may not) do it.

View File

@@ -1,15 +1,11 @@
[project] [project]
name = "vban-cli" name = "vban-cli"
version = "0.4.2" version = "0.6.0"
description = "A command-line interface for Voicemeeter leveraging VBAN." description = "A command-line interface for Voicemeeter leveraging VBAN."
readme = "README.md" readme = "README.md"
license = { text = "LICENSE" } license = { text = "LICENSE" }
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = ["cyclopts>=4.6.0", "loguru>=0.7.3", "vban-cmd>=2.7.1"]
"cyclopts>=4.6.0",
"loguru>=0.7.3",
"vban-cmd>=2.6.0",
]
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Programming Language :: Python", "Programming Language :: Python",
@@ -28,4 +24,4 @@ vban-cli = "vban_cli.app:run"
package = true package = true
[tool.uv.sources] [tool.uv.sources]
vban-cmd = { path = "../vban-cmd-python" } vban-cmd = { path = "../vban-cmd-python", editable = true }

View File

@@ -2,10 +2,10 @@ from dataclasses import dataclass
from typing import Annotated from typing import Annotated
import vban_cmd import vban_cmd
from cyclopts import App, Parameter, config from cyclopts import App, Argument, Parameter, config
from . import __version__ as version from . import __version__ as version
from . import bus, console, strip from . import bus, command, console, strip
from .context import Context from .context import Context
app = App( app = App(
@@ -16,6 +16,7 @@ app = App(
) )
app.command(strip.app.meta, name='strip') app.command(strip.app.meta, name='strip')
app.command(bus.app.meta, name='bus') app.command(bus.app.meta, name='bus')
app.command(command.app, name='command')
app.register_install_completion_command() app.register_install_completion_command()
@@ -37,15 +38,32 @@ def launcher(
if tokens[0] == '--install-completion': if tokens[0] == '--install-completion':
return command(*bound.args, **bound.kwargs) return command(*bound.args, **bound.kwargs)
disable_rt_listeners = False
if command.__name__ == 'sendtext':
disable_rt_listeners = True
with vban_cmd.api( with vban_cmd.api(
vban_config.kind, vban_config.kind,
ip=vban_config.host, ip=vban_config.host,
port=vban_config.port, port=vban_config.port,
streamname=vban_config.streamname, streamname=vban_config.streamname,
disable_rt_listeners=disable_rt_listeners,
) as client: ) as client:
return command(*bound.args, **bound.kwargs, ctx=Context(client=client)) return command(*bound.args, **bound.kwargs, ctx=Context(client=client))
@app.command(name='sendtext')
def sendtext(
text: Annotated[str, Argument()],
/,
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Send a text command to the current Voicemeeter/Matrix instance."""
if resp := ctx.client.sendtext(text):
console.out.print(resp)
def run(): def run():
try: try:
app.meta() app.meta()

View File

@@ -32,7 +32,9 @@ def launcher(
@app.command(name='mono') @app.command(name='mono')
def mono( def mono(
new_value: Annotated[Optional[bool], Argument()] = None, new_value: Annotated[
Optional[Literal['off', 'mono', 'stereoreverse']], Argument()
] = None,
*, *,
index: Annotated[int, Parameter(show=False)] = None, index: Annotated[int, Parameter(show=False)] = None,
ctx: Annotated[Context, Parameter(show=False)] = None, ctx: Annotated[Context, Parameter(show=False)] = None,
@@ -41,13 +43,13 @@ def mono(
Parameters Parameters
---------- ----------
new_value : bool, optional new_value : {'off', 'mono', 'stereoreverse'}, optional
If provided, sets the mono state to this value. If not provided, the current mono state is printed. If provided, sets the mono state to this value. If not provided, the current mono state is printed.
""" """
if new_value is None: if new_value is None:
console.out.print(ctx.client.bus[index].mono) console.out.print(['off', 'mono', 'stereoreverse'][ctx.client.bus[index].mono])
return return
ctx.client.bus[index].mono = new_value ctx.client.bus[index].mono = ['off', 'mono', 'stereoreverse'].index(new_value)
@app.command(name='mute') @app.command(name='mute')

49
src/vban_cli/command.py Normal file
View File

@@ -0,0 +1,49 @@
from typing import Annotated
from cyclopts import App, Parameter
from . import console
from .context import Context
from .help import BaseHelpFormatter
app = App(name='command', help_formatter=BaseHelpFormatter())
@app.command(name='show')
def show(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Bring the Voicemeeter GUI to the foreground."""
ctx.client.command.show()
console.out.print('Voicemeeter GUI should now be in the foreground.')
@app.command(name='hide')
def hide(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Send the Voicemeeter GUI to the background."""
ctx.client.command.hide()
console.out.print('Voicemeeter GUI should now be in the background.')
@app.command(name='shutdown')
def shutdown(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Shut down Voicemeeter."""
ctx.client.command.shutdown()
console.out.print('Voicemeeter should now be shut down.')
@app.command(name='restart')
def restart(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Restart the Voicemeeter engine."""
ctx.client.command.restart()
console.out.print('Voicemeeter engine should now be restarting.')

View File

@@ -3,9 +3,9 @@ from typing import Annotated
from cyclopts import App, Argument, Parameter from cyclopts import App, Argument, Parameter
from .context import Context from .context import Context
from .help import StripSubcommandHelpFormatter from .help import StripHelpFormatter
app = App(name='comp', help_formatter=StripSubcommandHelpFormatter()) app = App(name='comp', help_formatter=StripHelpFormatter())
@app.meta.default @app.meta.default

View File

@@ -3,9 +3,9 @@ from typing import Annotated
from cyclopts import App, Argument, Parameter from cyclopts import App, Argument, Parameter
from .context import Context from .context import Context
from .help import StripSubcommandHelpFormatter from .help import StripHelpFormatter
app = App(name='denoiser', help_formatter=StripSubcommandHelpFormatter()) app = App(name='denoiser', help_formatter=StripHelpFormatter())
@app.meta.default @app.meta.default

View File

@@ -3,9 +3,9 @@ from typing import Annotated
from cyclopts import App, Argument, Parameter from cyclopts import App, Argument, Parameter
from .context import Context from .context import Context
from .help import StripSubcommandHelpFormatter from .help import StripHelpFormatter
app = App(name='gate', help_formatter=StripSubcommandHelpFormatter()) app = App(name='gate', help_formatter=StripHelpFormatter())
@app.meta.default @app.meta.default

View File

@@ -61,18 +61,6 @@ class StripHelpFormatter(BaseHelpFormatter):
console.print(f'[bold]Usage:[/bold] {modified_usage}') console.print(f'[bold]Usage:[/bold] {modified_usage}')
class StripSubcommandHelpFormatter(BaseHelpFormatter):
"""Help formatter for strip subcommands that injects <index> after 'strip'."""
def render_usage(self, console: Console, options: ConsoleOptions, usage) -> None:
"""Render the usage line with index argument injected after 'strip'."""
if usage:
modified_usage = re.sub(
r'(\S+\s+strip)\s+(\w+)\s+(COMMAND)', r'\1 <index> \2 \3', str(usage)
)
console.print(f'[bold]Usage:[/bold] {modified_usage}')
class BusHelpFormatter(BaseHelpFormatter): class BusHelpFormatter(BaseHelpFormatter):
"""Help formatter for bus commands that injects <index> after 'bus'.""" """Help formatter for bus commands that injects <index> after 'bus'."""

8
uv.lock generated
View File

@@ -124,7 +124,7 @@ wheels = [
[[package]] [[package]]
name = "vban-cli" name = "vban-cli"
version = "0.4.2" version = "0.6.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "cyclopts" }, { name = "cyclopts" },
@@ -136,13 +136,13 @@ dependencies = [
requires-dist = [ requires-dist = [
{ name = "cyclopts", specifier = ">=4.6.0" }, { name = "cyclopts", specifier = ">=4.6.0" },
{ name = "loguru", specifier = ">=0.7.3" }, { name = "loguru", specifier = ">=0.7.3" },
{ name = "vban-cmd", directory = "../vban-cmd-python" }, { name = "vban-cmd", editable = "../vban-cmd-python" },
] ]
[[package]] [[package]]
name = "vban-cmd" name = "vban-cmd"
version = "2.6.0" version = "2.7.1"
source = { directory = "../vban-cmd-python" } source = { editable = "../vban-cmd-python" }
[package.metadata] [package.metadata]
requires-dist = [{ name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0.1,<3.0" }] requires-dist = [{ name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0.1,<3.0" }]