10 Commits

Author SHA1 Message Date
f389eb53b8 patch bump 2026-03-01 21:59:49 +00:00
341c81fde1 minor bump 2026-03-01 21:16:11 +00:00
e062da51ed add Recorder Command to README 2026-03-01 21:15:51 +00:00
c82a021708 add recorder subcommand group
add validation module
2026-03-01 21:15:42 +00:00
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
7 changed files with 217 additions and 7 deletions

View File

@@ -103,7 +103,35 @@ 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.
### Recorder Command
Usage: vban-cli recorder COMMAND
examples:
```console
vban-cli recorder play
vban-cli recorder rew
vban-cli recorder replay
```
see `vban-cli recorder --help` for more info.
### Sendtext Command ### Sendtext Command
@@ -125,10 +153,14 @@ vban-cli sendtext 'Command.Version = ?'
vban-cli sendtext 'Point(ASIO128.IN[1..4],ASIO128.OUT[1]).dBGain = -3.0' 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].
--- ---

View File

@@ -1,11 +1,11 @@
[project] [project]
name = "vban-cli" name = "vban-cli"
version = "0.5.0" version = "0.7.1"
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 = ["cyclopts>=4.6.0", "loguru>=0.7.3", "vban-cmd>=2.7.1"] dependencies = ["cyclopts>=4.6.0", "loguru>=0.7.3", "vban-cmd>=2.8.1"]
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Programming Language :: Python", "Programming Language :: Python",

View File

@@ -5,7 +5,7 @@ import vban_cmd
from cyclopts import App, Argument, 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, recorder, strip
from .context import Context from .context import Context
app = App( app = App(
@@ -16,6 +16,8 @@ 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.command(recorder.app, name='recorder')
app.register_install_completion_command() app.register_install_completion_command()

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.')

118
src/vban_cli/recorder.py Normal file
View File

@@ -0,0 +1,118 @@
from pathlib import Path
from typing import Annotated
from cyclopts import App, Parameter, validators
from . import console, validation
from .context import Context
from .help import BaseHelpFormatter
app = App(name='recorder', help_formatter=BaseHelpFormatter())
@app.command(name='play')
def play(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Start the recorder playback."""
ctx.client.recorder.play()
console.out.print('Recorder playback started.')
@app.command(name='stop')
def stop(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Stop the recorder playback."""
ctx.client.recorder.stop()
console.out.print('Recorder playback stopped.')
@app.command(name='pause')
def pause(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Pause the recorder playback."""
ctx.client.recorder.pause()
console.out.print('Recorder playback paused.')
@app.command(name='replay')
def replay(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Replay the recorder playback."""
ctx.client.recorder.replay()
console.out.print('Recorder playback replay started.')
@app.command(name='record')
def record(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Start recording."""
ctx.client.recorder.record()
console.out.print('Recording started.')
@app.command(name='ff')
def ff(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Fast forward the recorder playback."""
ctx.client.recorder.ff()
console.out.print('Recorder playback fast forwarded.')
@app.command(name='rew')
def rew(
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Rewind the recorder playback."""
ctx.client.recorder.rew()
console.out.print('Recorder playback rewound.')
@app.command(name='load')
def load(
file_path: Annotated[
Path,
Parameter(
help='The path to the recording file to load.',
validator=validators.Path(exists=True),
),
],
/,
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Load a file into the recorder.
note: This command may only work if vban-cli is running on localhost and may not work if vban-cli is running on a remote server."""
ctx.client.recorder.load(file_path)
console.out.print(f'Loaded file: {file_path}')
@app.command(name='goto')
def goto(
time_string: Annotated[
str,
Parameter(
help='The timestamp to go to in the recorder playback (format: HH:MM:SS).',
validator=validation.is_valid_time_string,
),
],
/,
*,
ctx: Annotated[Context, Parameter(show=False)] = None,
):
"""Go to a specific timestamp in the recorder playback."""
ctx.client.recorder.goto(time_string)
console.out.print(f'Went to timestamp {time_string} in recorder playback.')

View File

@@ -0,0 +1,9 @@
import re
def is_valid_time_string(type_, value: str) -> str:
"""Validate if the given string is a valid time format (HH:MM:SS)."""
pattern = r'^(?:[01]\d|2[0123]):(?:[012345]\d):(?:[012345]\d)$'
if not re.match(pattern, value):
raise ValueError('Invalid time format. Expected HH:MM:SS.')
return value

4
uv.lock generated
View File

@@ -124,7 +124,7 @@ wheels = [
[[package]] [[package]]
name = "vban-cli" name = "vban-cli"
version = "0.5.0" version = "0.7.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "cyclopts" }, { name = "cyclopts" },
@@ -141,7 +141,7 @@ requires-dist = [
[[package]] [[package]]
name = "vban-cmd" name = "vban-cmd"
version = "2.7.1" version = "2.8.0"
source = { editable = "../vban-cmd-python" } source = { editable = "../vban-cmd-python" }
[package.metadata] [package.metadata]