mirror of
https://github.com/onyx-and-iris/vban-cli.git
synced 2026-03-03 13:39:11 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2ae240605 | |||
| 18ed3ea7fb | |||
| d5ca50e9bd | |||
| 0f8be6de48 | |||
| aa6b156eff | |||
| 5c60a382fd | |||
| d7fef4f26a | |||
| e6a17d5772 | |||
| 5c3452bff5 | |||
| b0a524d125 | |||
| 2693fcce2c | |||
| bf058e910e | |||
| e84accd37a | |||
| d9810ce270 | |||
| 4fff581c95 | |||
| 1f6811d5a0 | |||
| a1da5c7256 | |||
| 642337d987 | |||
| 51002edb39 | |||
| d30c9f828d | |||
| a9c3168542 | |||
| fba3eddea8 | |||
| 6c2c924a48 | |||
| 27290e1a0e | |||
| 6188da4f51 | |||
| 230e537414 | |||
| e6ebf86c86 | |||
| 1a0fb979e0 | |||
| 080e26f75f | |||
| f6d82c5064 | |||
| 627ada3b09 | |||
| f389eb53b8 | |||
| 341c81fde1 | |||
| e062da51ed | |||
| c82a021708 | |||
| 61a3bc38a8 | |||
| 8991737011 | |||
| 84d716b2ad | |||
| b58bb3dde7 | |||
| d414844f8f | |||
| f605178da6 | |||
| c535ae5571 | |||
| 7b5d2150c7 | |||
| 28ec67839a | |||
| dd0d150202 | |||
| 78952aa3ff | |||
| c4d67527f5 | |||
| b3cfc6bc4a | |||
| c642bbc1f2 | |||
| 61a37bcd0f | |||
| b62ee185c3 | |||
| c7365bfe4e | |||
| c660778698 | |||
| 5460965945 | |||
| d03049e713 | |||
| 1dd518095a |
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,13 +1,3 @@
|
|||||||
# Python-generated files
|
|
||||||
__pycache__/
|
|
||||||
*.py[oc]
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
wheels/
|
|
||||||
*.egg-info
|
|
||||||
|
|
||||||
# Virtual environments
|
|
||||||
.venv
|
|
||||||
# Generated by ignr: github.com/onyx-and-iris/ignr
|
# Generated by ignr: github.com/onyx-and-iris/ignr
|
||||||
|
|
||||||
## Python ##
|
## Python ##
|
||||||
|
|||||||
7
.pre-commit-config.yaml
Normal file
7
.pre-commit-config.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
|
# uv version.
|
||||||
|
rev: 0.10.7
|
||||||
|
hooks:
|
||||||
|
# Update the uv lockfile
|
||||||
|
- id: uv-lock
|
||||||
173
README.md
173
README.md
@ -47,33 +47,166 @@ export VBAN_CLI_STREAMNAME=Command1
|
|||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
```console
|
### Strip Command
|
||||||
Usage: vban-cli COMMAND
|
|
||||||
|
|
||||||
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮
|
*Usage: vban-cli strip \<index> COMMAND [OPTIONS]*
|
||||||
│ bus Control the bus parameters. │
|
|
||||||
│ strip Control the strip parameters. │
|
examples:
|
||||||
│ --help (-h) Display this message and exit. │
|
|
||||||
│ --version Display application version. │
|
```console
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
vban-cli strip 0 mute true
|
||||||
╭─ Parameters ─────────────────────────────────────────────────────────────────────────────────────╮
|
|
||||||
│ --kind Kind of Voicemeeter [env var: VBAN_CLI_KIND] [default: potato] │
|
vban-cli strip 1 A1 true
|
||||||
│ --host VBAN host [env var: VBAN_CLI_HOST] [default: localhost] │
|
|
||||||
│ --port VBAN port [env var: VBAN_CLI_PORT] [default: 6980] │
|
vban-cli strip 2 gain -18.7
|
||||||
│ --streamname VBAN stream name [env var: VBAN_CLI_STREAMNAME] [default: Command1] │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For every command and subcommand there exists a `--help` flag for further usage information.
|
see `vban-cli strip --help` for more info.
|
||||||
|
|
||||||
|
##### Strip EQ
|
||||||
|
|
||||||
|
*Usage: vban-cli strip \<index> eq COMMAND [OPTIONS]*
|
||||||
|
|
||||||
|
examples:
|
||||||
|
|
||||||
|
```console
|
||||||
|
vban-cli strip 0 eq on 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
|
||||||
|
|
||||||
|
vban-cli strip 3 eq cell 2 freq 1500
|
||||||
|
|
||||||
|
vban-cli strip 4 eq cell 5 type 5
|
||||||
|
```
|
||||||
|
|
||||||
|
see `vban-cli strip eq cell --help` for more info.
|
||||||
|
|
||||||
|
##### Strip Comp Command
|
||||||
|
|
||||||
|
*Usage: vban-cli strip \<index> comp COMMAND [OPTIONS]*
|
||||||
|
|
||||||
|
examples:
|
||||||
|
|
||||||
|
```console
|
||||||
|
vban-cli strip 0 comp attack 2.0
|
||||||
|
|
||||||
|
vban-cli strip 3 comp auto-makeup true
|
||||||
|
```
|
||||||
|
|
||||||
|
see `vban-cli strip comp --help` for more info.
|
||||||
|
|
||||||
|
##### Strip Gate Command
|
||||||
|
|
||||||
|
*Usage: vban-cli strip \<index> gate COMMAND [OPTIONS]*
|
||||||
|
|
||||||
|
examples:
|
||||||
|
|
||||||
|
```console
|
||||||
|
vban-cli strip 2 gate attack 634
|
||||||
|
|
||||||
|
vban-cli strip 5 gate hold 2088.7
|
||||||
|
```
|
||||||
|
|
||||||
|
see `vban-cli strip comp --help` for more info.
|
||||||
|
|
||||||
|
##### Strip Gainlayer Command
|
||||||
|
|
||||||
|
*Usage: vban-cli strip \<index> gainlayer \<gainlayer_index> COMMAND [OPTIONS] [ARGS]*
|
||||||
|
|
||||||
|
examples:
|
||||||
|
|
||||||
|
```console
|
||||||
|
vban-cli strip 0 gainlayer 0 level
|
||||||
|
|
||||||
|
vban-cli strip 3 gainlayer 2 level -13.5
|
||||||
|
```
|
||||||
|
|
||||||
|
see `vban-cli strip gainlayer --help` for more info.
|
||||||
|
|
||||||
|
### Bus Command
|
||||||
|
|
||||||
|
*Usage: vban-cli bus \<index> COMMAND [OPTIONS]*
|
||||||
|
|
||||||
|
examples:
|
||||||
|
|
||||||
|
```console
|
||||||
|
vban-cli bus 0 mode tvmix
|
||||||
|
|
||||||
|
vban-cli bus 1 mute true
|
||||||
|
```
|
||||||
|
|
||||||
|
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 pause
|
||||||
|
|
||||||
|
vban-cli recorder goto "00:01:30"
|
||||||
|
```
|
||||||
|
|
||||||
|
see `vban-cli recorder --help` for more info.
|
||||||
|
|
||||||
|
### Sendtext Command
|
||||||
|
|
||||||
|
*Usage: vban-cli sendtext TEXT*
|
||||||
|
|
||||||
|
examples:
|
||||||
|
|
||||||
|
*To Voicemeeter*
|
||||||
|
|
||||||
|
```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 RT SERVICE 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].
|
||||||
|
4. There doesn't appear to be any way to retrieve the current recorder status, ie, recording, playing, stopped etc. I don't see the data available in either packet structures [ident:0][ident-0] or [ident:1][ident-1].
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -81,6 +214,8 @@ For every command and subcommand there exists a `--help` flag for further usage
|
|||||||
|
|
||||||
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 they haven't been thoroughly tested.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -92,5 +227,5 @@ If there's something missing that you would like to see added the best bet is to
|
|||||||
|
|
||||||
|
|
||||||
[ident-0]: https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L896
|
[ident-0]: https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L896
|
||||||
[ident-1]: https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L982
|
[ident-1]: https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L1053
|
||||||
[ident-1-peq]: https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L995
|
[ident-1-peq]: https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/VoicemeeterRemote.h#L995
|
||||||
@ -1,13 +1,13 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "vban-cli"
|
name = "vban-cli"
|
||||||
version = "0.3.0"
|
version = "0.12.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 = ["cyclopts>=4.6.0", "vban-cmd>=2.6.0"]
|
dependencies = ["cyclopts>=4.6.0", "loguru>=0.7.3", "vban-cmd>=2.9.1"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 4 - Beta",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
@ -24,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 }
|
||||||
|
|||||||
@ -2,20 +2,30 @@ 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 rich.traceback import install as install_rich_traceback
|
||||||
|
|
||||||
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
|
||||||
|
from .error import VbanCLIConnectionError
|
||||||
|
|
||||||
app = App(
|
app = App(
|
||||||
config=config.Env(
|
config=config.Env(
|
||||||
'VBAN_CLI_',
|
'VBAN_CLI_',
|
||||||
), # Environment variable prefix for configuration parameters
|
), # Environment variable prefix for configuration parameters
|
||||||
version=version,
|
version=version,
|
||||||
|
console=console.out,
|
||||||
|
error_console=console.err,
|
||||||
|
exit_on_error=True,
|
||||||
)
|
)
|
||||||
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()
|
||||||
|
|
||||||
|
install_rich_traceback(console=console.err)
|
||||||
|
|
||||||
|
|
||||||
@Parameter(name='*')
|
@Parameter(name='*')
|
||||||
@ -32,22 +42,38 @@ def launcher(
|
|||||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
vban_config: Annotated[VBANConfig, Parameter()] = VBANConfig(),
|
vban_config: Annotated[VBANConfig, Parameter()] = VBANConfig(),
|
||||||
):
|
):
|
||||||
|
command, bound, _ = app.parse_args(tokens)
|
||||||
|
if tokens[0] == '--install-completion':
|
||||||
|
return command(*bound.args, **bound.kwargs)
|
||||||
|
|
||||||
|
disable_rt_listeners = False
|
||||||
|
if command.__name__ == 'sendtext':
|
||||||
|
disable_rt_listeners = True
|
||||||
|
|
||||||
|
try:
|
||||||
with vban_cmd.api(
|
with vban_cmd.api(
|
||||||
vban_config.kind,
|
vban_config.kind,
|
||||||
ip=vban_config.host,
|
host=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:
|
||||||
additional_kwargs = {}
|
return command(*bound.args, **bound.kwargs, ctx=Context(client=client))
|
||||||
command, bound, _ = app.parse_args(tokens)
|
except vban_cmd.error.VBANCMDConnectionError as e:
|
||||||
additional_kwargs['ctx'] = Context(client=client)
|
raise VbanCLIConnectionError(str(e)) from e
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
|
||||||
|
@app.command(name='sendtext')
|
||||||
|
def sendtext(
|
||||||
|
text: Annotated[str, Argument()],
|
||||||
|
/,
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Send a text command to the current Voicemeeter/Matrix instance."""
|
||||||
|
if resp := ctx.client.sendtext(text):
|
||||||
|
app.console.print(resp)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
try:
|
|
||||||
app.meta()
|
app.meta()
|
||||||
except Exception as e:
|
|
||||||
console.err.print(f'Error: {e}')
|
|
||||||
return e.code
|
|
||||||
|
|||||||
@ -2,27 +2,27 @@ from typing import Annotated, Literal, Optional
|
|||||||
|
|
||||||
from cyclopts import App, Argument, Parameter
|
from cyclopts import App, Argument, Parameter
|
||||||
|
|
||||||
from . import console
|
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from .help import CustomHelpFormatter
|
from .help import BusHelpFormatter
|
||||||
|
|
||||||
app = App(name='bus', help_formatter=CustomHelpFormatter())
|
app = App(name='bus', help_formatter=BusHelpFormatter())
|
||||||
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 1.
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 1.
|
||||||
# app.command(eq.app.meta, name='eq')
|
# app.command(eq.app.meta, name='eq')
|
||||||
|
|
||||||
|
|
||||||
@app.meta.default
|
@app.meta.default
|
||||||
def launcher(
|
def launcher(
|
||||||
index: Annotated[int, Argument()] = None,
|
index: Annotated[int, Argument()],
|
||||||
|
/,
|
||||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Control the bus parameters."""
|
"""Control the bus parameters."""
|
||||||
additional_kwargs = {}
|
additional_kwargs = {}
|
||||||
command, bound, _ = app.parse_args(tokens)
|
command, bound, _ = app.parse_args(tokens)
|
||||||
if index is not None:
|
if tokens[0] == 'eq':
|
||||||
|
additional_kwargs['eq_kind'] = app.name[0]
|
||||||
additional_kwargs['index'] = index
|
additional_kwargs['index'] = index
|
||||||
if ctx is not None:
|
|
||||||
additional_kwargs['ctx'] = ctx
|
additional_kwargs['ctx'] = ctx
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
@ -30,30 +30,32 @@ 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(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the mono state of the specified bus.
|
"""Get or set the mono state of the specified bus.
|
||||||
|
|
||||||
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)
|
app.console.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')
|
||||||
def mute(
|
def mute(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the mute state of the specified bus.
|
"""Get or set the mute state of the specified bus.
|
||||||
|
|
||||||
@ -63,7 +65,7 @@ def mute(
|
|||||||
If provided, sets the mute state to this value. If not provided, the current mute state is printed.
|
If provided, sets the mute state to this value. If not provided, the current mute state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.bus[index].mute)
|
app.console.print(ctx.client.bus[index].mute)
|
||||||
return
|
return
|
||||||
ctx.client.bus[index].mute = new_value
|
ctx.client.bus[index].mute = new_value
|
||||||
|
|
||||||
@ -90,8 +92,8 @@ def mode(
|
|||||||
Argument(),
|
Argument(),
|
||||||
] = None,
|
] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the bus mode of the specified bus.
|
"""Get or set the bus mode of the specified bus.
|
||||||
|
|
||||||
@ -101,6 +103,6 @@ def mode(
|
|||||||
If provided, sets the bus mode to this value. If not provided, the current bus mode is printed.
|
If provided, sets the bus mode to this value. If not provided, the current bus mode is printed.
|
||||||
"""
|
"""
|
||||||
if type_ is None:
|
if type_ is None:
|
||||||
console.out.print(ctx.client.bus[index].mode.get())
|
app.console.print(ctx.client.bus[index].mode.get())
|
||||||
return
|
return
|
||||||
setattr(ctx.client.bus[index].mode, type_, True)
|
setattr(ctx.client.bus[index].mode, type_, True)
|
||||||
|
|||||||
48
src/vban_cli/command.py
Normal file
48
src/vban_cli/command.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from cyclopts import App, Parameter
|
||||||
|
|
||||||
|
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(parse=False)] = None,
|
||||||
|
):
|
||||||
|
"""Bring the Voicemeeter GUI to the foreground."""
|
||||||
|
ctx.client.command.show()
|
||||||
|
app.console.print('Voicemeeter GUI should now be in the foreground.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='hide')
|
||||||
|
def hide(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Send the Voicemeeter GUI to the background."""
|
||||||
|
ctx.client.command.hide()
|
||||||
|
app.console.print('Voicemeeter GUI should now be in the background.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='shutdown')
|
||||||
|
def shutdown(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Shut down Voicemeeter."""
|
||||||
|
ctx.client.command.shutdown()
|
||||||
|
app.console.print('Voicemeeter should now be shut down.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='restart')
|
||||||
|
def restart(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Restart the Voicemeeter engine."""
|
||||||
|
ctx.client.command.restart()
|
||||||
|
app.console.print('Voicemeeter engine should now be restarting.')
|
||||||
@ -3,23 +3,21 @@ 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 CustomHelpFormatter
|
from .help import StripHelpFormatter
|
||||||
|
|
||||||
app = App(name='comp', help_formatter=CustomHelpFormatter())
|
app = App(name='comp', help_formatter=StripHelpFormatter())
|
||||||
|
|
||||||
|
|
||||||
@app.meta.default
|
@app.meta.default
|
||||||
def launcher(
|
def launcher(
|
||||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
index: Annotated[int, Argument()] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Control the compressor parameters."""
|
"""Control the compressor parameters."""
|
||||||
additional_kwargs = {}
|
additional_kwargs = {}
|
||||||
command, bound, _ = app.parse_args(tokens)
|
command, bound, _ = app.parse_args(tokens)
|
||||||
if index is not None:
|
|
||||||
additional_kwargs['index'] = index
|
additional_kwargs['index'] = index
|
||||||
if ctx is not None:
|
|
||||||
additional_kwargs['ctx'] = ctx
|
additional_kwargs['ctx'] = ctx
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
@ -29,8 +27,8 @@ def launcher(
|
|||||||
def knob(
|
def knob(
|
||||||
new_knob: Annotated[float, Argument()] = None,
|
new_knob: Annotated[float, Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the knob of the specified compressor.
|
"""Get or set the knob of the specified compressor.
|
||||||
|
|
||||||
@ -41,7 +39,7 @@ def knob(
|
|||||||
"""
|
"""
|
||||||
if new_knob is None:
|
if new_knob is None:
|
||||||
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
# console.out.print(ctx.client.strip[index].comp.knob)
|
# app.console.print(ctx.client.strip[index].comp.knob)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].comp.knob = new_knob
|
ctx.client.strip[index].comp.knob = new_knob
|
||||||
|
|
||||||
@ -50,8 +48,8 @@ def knob(
|
|||||||
def input_gain(
|
def input_gain(
|
||||||
new_gain: Annotated[float, Argument()] = None,
|
new_gain: Annotated[float, Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the input gain of the specified compressor.
|
"""Get or set the input gain of the specified compressor.
|
||||||
|
|
||||||
@ -62,6 +60,153 @@ def input_gain(
|
|||||||
"""
|
"""
|
||||||
if new_gain is None:
|
if new_gain is None:
|
||||||
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
# console.out.print(ctx.client.strip[index].comp.gainin)
|
# app.console.print(ctx.client.strip[index].comp.gainin)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].comp.gainin = new_gain
|
ctx.client.strip[index].comp.gainin = new_gain
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='ratio')
|
||||||
|
def ratio(
|
||||||
|
new_ratio: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the ratio of the specified compressor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_ratio : float, optional
|
||||||
|
If provided, sets the ratio to this value. If not provided, the current ratio is printed.
|
||||||
|
"""
|
||||||
|
if new_ratio is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].comp.ratio)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].comp.ratio = new_ratio
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='threshold')
|
||||||
|
def threshold(
|
||||||
|
new_threshold: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the threshold of the specified compressor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_threshold : float, optional
|
||||||
|
If provided, sets the threshold to this value. If not provided, the current threshold is printed.
|
||||||
|
"""
|
||||||
|
if new_threshold is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].comp.threshold)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].comp.threshold = new_threshold
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='attack')
|
||||||
|
def attack(
|
||||||
|
new_attack: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the attack of the specified compressor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_attack : float, optional
|
||||||
|
If provided, sets the attack to this value. If not provided, the current attack is printed.
|
||||||
|
"""
|
||||||
|
if new_attack is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].comp.attack)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].comp.attack = new_attack
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='release')
|
||||||
|
def release(
|
||||||
|
new_release: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the release of the specified compressor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_release : float, optional
|
||||||
|
If provided, sets the release to this value. If not provided, the current release is printed.
|
||||||
|
"""
|
||||||
|
if new_release is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].comp.release)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].comp.release = new_release
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='knee')
|
||||||
|
def knee(
|
||||||
|
new_knee: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the knee of the specified compressor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_knee : float, optional
|
||||||
|
If provided, sets the knee to this value. If not provided, the current knee is printed.
|
||||||
|
"""
|
||||||
|
if new_knee is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].comp.knee)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].comp.knee = new_knee
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='output-gain')
|
||||||
|
def output_gain(
|
||||||
|
new_gain: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the output gain of the specified compressor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_gain : float, optional
|
||||||
|
If provided, sets the output gain to this value. If not provided, the current output gain is printed.
|
||||||
|
"""
|
||||||
|
if new_gain is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].comp.gainout)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].comp.gainout = new_gain
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='auto-makeup')
|
||||||
|
def makeup(
|
||||||
|
new_makeup: Annotated[bool, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the auto-makeup of the specified compressor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_makeup : bool, optional
|
||||||
|
If provided, sets the auto-makeup to this value. If not provided, the current auto-makeup is printed.
|
||||||
|
"""
|
||||||
|
if new_makeup is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].comp.makeup)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].comp.makeup = new_makeup
|
||||||
|
|||||||
@ -3,23 +3,21 @@ 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 CustomHelpFormatter
|
from .help import StripHelpFormatter
|
||||||
|
|
||||||
app = App(name='denoiser', help_formatter=CustomHelpFormatter())
|
app = App(name='denoiser', help_formatter=StripHelpFormatter())
|
||||||
|
|
||||||
|
|
||||||
@app.meta.default
|
@app.meta.default
|
||||||
def launcher(
|
def launcher(
|
||||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
index: Annotated[int, Argument()] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Control the denoiser parameters."""
|
"""Control the denoiser parameters."""
|
||||||
additional_kwargs = {}
|
additional_kwargs = {}
|
||||||
command, bound, _ = app.parse_args(tokens)
|
command, bound, _ = app.parse_args(tokens)
|
||||||
if index is not None:
|
|
||||||
additional_kwargs['index'] = index
|
additional_kwargs['index'] = index
|
||||||
if ctx is not None:
|
|
||||||
additional_kwargs['ctx'] = ctx
|
additional_kwargs['ctx'] = ctx
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
@ -29,8 +27,8 @@ def launcher(
|
|||||||
def knob(
|
def knob(
|
||||||
new_knob: Annotated[float, Argument()] = None,
|
new_knob: Annotated[float, Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the knob of the specified denoiser.
|
"""Get or set the knob of the specified denoiser.
|
||||||
|
|
||||||
@ -41,6 +39,6 @@ def knob(
|
|||||||
"""
|
"""
|
||||||
if new_knob is None:
|
if new_knob is None:
|
||||||
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
# console.out.print(ctx.client.strip[index].denoiser.knob)
|
# app.console.print(ctx.client.strip[index].denoiser.knob)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].denoiser.knob = new_knob
|
ctx.client.strip[index].denoiser.knob = new_knob
|
||||||
|
|||||||
@ -3,30 +3,32 @@ 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 CustomHelpFormatter
|
from .help import CellHelpFormatter, EqHelpFormatter
|
||||||
|
|
||||||
app = App(name='eq', help_formatter=CustomHelpFormatter())
|
cell_app = App(name='cell', help_formatter=CellHelpFormatter())
|
||||||
|
|
||||||
|
app = App(name='eq', help_formatter=EqHelpFormatter())
|
||||||
|
app.command(cell_app.meta, name='cell')
|
||||||
|
|
||||||
|
|
||||||
@app.meta.default
|
@app.meta.default
|
||||||
def launcher(
|
def launcher(
|
||||||
band: Annotated[int, Argument()] = None,
|
|
||||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
index: Annotated[int, Argument()] = None,
|
eq_kind: Annotated[str, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Control the EQ parameters.
|
"""Control the EQ parameters."""
|
||||||
|
|
||||||
Only channel 0 is supported, see https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 3.
|
|
||||||
"""
|
|
||||||
additional_kwargs = {}
|
additional_kwargs = {}
|
||||||
command, bound, _ = app.parse_args(tokens)
|
command, bound, _ = app.parse_args(tokens)
|
||||||
if index is not None:
|
match eq_kind:
|
||||||
additional_kwargs['index'] = index
|
case 'strip':
|
||||||
if band is not None:
|
target = ctx.client.strip[index].eq
|
||||||
additional_kwargs['band'] = band
|
case 'bus':
|
||||||
if ctx is not None:
|
target = ctx.client.bus[index].eq
|
||||||
additional_kwargs['ctx'] = ctx
|
case _:
|
||||||
|
raise ValueError(f'Invalid eq_kind: {eq_kind}')
|
||||||
|
additional_kwargs['target'] = target
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
|
|
||||||
@ -35,9 +37,7 @@ def launcher(
|
|||||||
def on(
|
def on(
|
||||||
new_state: Annotated[bool, Argument()] = None,
|
new_state: Annotated[bool, Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
target: Annotated[object, Parameter(parse=False)],
|
||||||
band: Annotated[int, Parameter(show=False)] = None,
|
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
|
||||||
):
|
):
|
||||||
"""Get or set the on state of the specified EQ band.
|
"""Get or set the on state of the specified EQ band.
|
||||||
|
|
||||||
@ -48,6 +48,124 @@ def on(
|
|||||||
"""
|
"""
|
||||||
if new_state is None:
|
if new_state is None:
|
||||||
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
# console.out.print(ctx.client.strip[index].eq.channel[0].cell[band].on)
|
# app.console.print(target.on)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].eq.channel[0].cell[band].on = new_state
|
target.on = new_state
|
||||||
|
|
||||||
|
|
||||||
|
@cell_app.meta.default
|
||||||
|
def cell_launcher(
|
||||||
|
band: Annotated[int, Argument()],
|
||||||
|
/,
|
||||||
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
|
target: Annotated[object, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Control the EQ Cell parameters.
|
||||||
|
|
||||||
|
Only channel 0 is supported, see https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 3.
|
||||||
|
"""
|
||||||
|
additional_kwargs = {}
|
||||||
|
command, bound, _ = cell_app.parse_args(tokens)
|
||||||
|
additional_kwargs['target'] = target.channel[0].cell[band]
|
||||||
|
|
||||||
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@cell_app.command(name='on')
|
||||||
|
def cell_on(
|
||||||
|
new_state: Annotated[bool, Argument()] = None,
|
||||||
|
*,
|
||||||
|
target: Annotated[object, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the on state of the specified EQ cell.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_state : bool
|
||||||
|
If provided, sets the on state to this value. If not provided, the current on state is printed.
|
||||||
|
"""
|
||||||
|
if new_state is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(target.on)
|
||||||
|
return
|
||||||
|
target.on = new_state
|
||||||
|
|
||||||
|
|
||||||
|
@cell_app.command(name='freq')
|
||||||
|
def cell_freq(
|
||||||
|
new_freq: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
target: Annotated[object, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the frequency of the specified EQ cell.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_freq : float
|
||||||
|
If provided, sets the frequency to this value. If not provided, the current frequency is printed.
|
||||||
|
"""
|
||||||
|
if new_freq is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(target.f)
|
||||||
|
return
|
||||||
|
target.f = new_freq
|
||||||
|
|
||||||
|
|
||||||
|
@cell_app.command(name='gain')
|
||||||
|
def cell_gain(
|
||||||
|
new_gain: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
target: Annotated[object, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the gain of the specified EQ cell.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_gain : float
|
||||||
|
If provided, sets the gain to this value. If not provided, the current gain is printed.
|
||||||
|
"""
|
||||||
|
if new_gain is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(target.gain)
|
||||||
|
return
|
||||||
|
target.gain = new_gain
|
||||||
|
|
||||||
|
|
||||||
|
@cell_app.command(name='quality')
|
||||||
|
def cell_q(
|
||||||
|
new_q: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
target: Annotated[object, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the Q of the specified EQ cell.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_q : float
|
||||||
|
If provided, sets the Q to this value. If not provided, the current Q is printed.
|
||||||
|
"""
|
||||||
|
if new_q is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(target.q)
|
||||||
|
return
|
||||||
|
target.q = new_q
|
||||||
|
|
||||||
|
|
||||||
|
@cell_app.command(name='type')
|
||||||
|
def cell_type(
|
||||||
|
new_type: Annotated[int, Argument()] = None,
|
||||||
|
*,
|
||||||
|
target: Annotated[object, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the type of the specified EQ cell.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_type : int
|
||||||
|
If provided, sets the type to this value. If not provided, the current type is printed.
|
||||||
|
"""
|
||||||
|
if new_type is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(target.type)
|
||||||
|
return
|
||||||
|
target.type = new_type
|
||||||
|
|||||||
14
src/vban_cli/error.py
Normal file
14
src/vban_cli/error.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class VbanCLIError(Exception):
|
||||||
|
"""Base class for exceptions in this module."""
|
||||||
|
|
||||||
|
def __init__(self, message: str, code: int = 1):
|
||||||
|
super().__init__(message)
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
|
||||||
|
class VbanCLIConnectionError(VbanCLIError):
|
||||||
|
"""Exception raised for connection errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class VbanCLIValidationError(VbanCLIError):
|
||||||
|
"""Exception raised for validation errors."""
|
||||||
47
src/vban_cli/gainlayer.py
Normal file
47
src/vban_cli/gainlayer.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from cyclopts import App, Argument, Parameter
|
||||||
|
|
||||||
|
from .context import Context
|
||||||
|
from .help import GainlayerHelpFormatter
|
||||||
|
|
||||||
|
app = App(name='gainlayer', help_formatter=GainlayerHelpFormatter())
|
||||||
|
|
||||||
|
|
||||||
|
@app.meta.default
|
||||||
|
def launcher(
|
||||||
|
gainlayer_index: Annotated[int, Argument()],
|
||||||
|
/,
|
||||||
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Control the gainlayers."""
|
||||||
|
additional_kwargs = {}
|
||||||
|
command, bound, _ = app.parse_args(tokens)
|
||||||
|
additional_kwargs['strip_index'] = index
|
||||||
|
additional_kwargs['gainlayer_index'] = gainlayer_index
|
||||||
|
additional_kwargs['ctx'] = ctx
|
||||||
|
|
||||||
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='level')
|
||||||
|
def level(
|
||||||
|
new_level: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
strip_index: Annotated[int, Parameter(parse=False)],
|
||||||
|
gainlayer_index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the level of the specified gainlayer.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_level : float
|
||||||
|
If provided, sets the level to this value. If not provided, the current level is printed.
|
||||||
|
"""
|
||||||
|
if new_level is None:
|
||||||
|
app.console.print(ctx.client.strip[strip_index].gainlayer[gainlayer_index].gain)
|
||||||
|
return
|
||||||
|
ctx.client.strip[strip_index].gainlayer[gainlayer_index].gain = new_level
|
||||||
@ -3,23 +3,21 @@ 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 CustomHelpFormatter
|
from .help import StripHelpFormatter
|
||||||
|
|
||||||
app = App(name='gate', help_formatter=CustomHelpFormatter())
|
app = App(name='gate', help_formatter=StripHelpFormatter())
|
||||||
|
|
||||||
|
|
||||||
@app.meta.default
|
@app.meta.default
|
||||||
def launcher(
|
def launcher(
|
||||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
index: Annotated[int, Argument()] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Control the compressor parameters."""
|
"""Control the compressor parameters."""
|
||||||
additional_kwargs = {}
|
additional_kwargs = {}
|
||||||
command, bound, _ = app.parse_args(tokens)
|
command, bound, _ = app.parse_args(tokens)
|
||||||
if index is not None:
|
|
||||||
additional_kwargs['index'] = index
|
additional_kwargs['index'] = index
|
||||||
if ctx is not None:
|
|
||||||
additional_kwargs['ctx'] = ctx
|
additional_kwargs['ctx'] = ctx
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
@ -29,8 +27,8 @@ def launcher(
|
|||||||
def knob(
|
def knob(
|
||||||
new_knob: Annotated[float, Argument()] = None,
|
new_knob: Annotated[float, Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the knob of the specified gate.
|
"""Get or set the knob of the specified gate.
|
||||||
|
|
||||||
@ -41,7 +39,7 @@ def knob(
|
|||||||
"""
|
"""
|
||||||
if new_knob is None:
|
if new_knob is None:
|
||||||
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
# console.out.print(ctx.client.strip[index].gate.knob)
|
# app.console.print(ctx.client.strip[index].gate.knob)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].gate.knob = new_knob
|
ctx.client.strip[index].gate.knob = new_knob
|
||||||
|
|
||||||
@ -50,8 +48,8 @@ def knob(
|
|||||||
def threshold(
|
def threshold(
|
||||||
new_threshold: Annotated[float, Argument()] = None,
|
new_threshold: Annotated[float, Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the threshold of the specified gate.
|
"""Get or set the threshold of the specified gate.
|
||||||
|
|
||||||
@ -62,6 +60,111 @@ def threshold(
|
|||||||
"""
|
"""
|
||||||
if new_threshold is None:
|
if new_threshold is None:
|
||||||
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
# console.out.print(ctx.client.strip[index].gate.threshold)
|
# app.console.print(ctx.client.strip[index].gate.threshold)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].gate.threshold = new_threshold
|
ctx.client.strip[index].gate.threshold = new_threshold
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='damping-max')
|
||||||
|
def damping_max(
|
||||||
|
new_damping_max: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the damping max of the specified gate.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_damping_max : float, optional
|
||||||
|
If provided, sets the damping max to this value. If not provided, the current damping max is printed.
|
||||||
|
"""
|
||||||
|
if new_damping_max is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].gate.damping)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].gate.damping = new_damping_max
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='bp-sidechain')
|
||||||
|
def bp_sidechain(
|
||||||
|
new_bp_sidechain: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the BP sidechain of the specified gate.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_bp_sidechain : float, optional
|
||||||
|
If provided, sets the BP sidechain to this value. If not provided, the current BP sidechain is printed.
|
||||||
|
"""
|
||||||
|
if new_bp_sidechain is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].gate.bpsidechain)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].gate.bpsidechain = new_bp_sidechain
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='attack')
|
||||||
|
def attack(
|
||||||
|
new_attack: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the attack of the specified gate.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_attack : float, optional
|
||||||
|
If provided, sets the attack to this value. If not provided, the current attack is printed.
|
||||||
|
"""
|
||||||
|
if new_attack is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].gate.attack)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].gate.attack = new_attack
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='hold')
|
||||||
|
def hold(
|
||||||
|
new_hold: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the hold of the specified gate.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_hold : float, optional
|
||||||
|
If provided, sets the hold to this value. If not provided, the current hold is printed.
|
||||||
|
"""
|
||||||
|
if new_hold is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].gate.hold)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].gate.hold = new_hold
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='release')
|
||||||
|
def release(
|
||||||
|
new_release: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Get or set the release of the specified gate.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
new_release : float, optional
|
||||||
|
If provided, sets the release to this value. If not provided, the current release is printed.
|
||||||
|
"""
|
||||||
|
if new_release is None:
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 2.
|
||||||
|
# app.console.print(ctx.client.strip[index].gate.release)
|
||||||
|
return
|
||||||
|
ctx.client.strip[index].gate.release = new_release
|
||||||
|
|||||||
@ -4,31 +4,24 @@ from cyclopts.help import DefaultFormatter, HelpPanel
|
|||||||
from rich.console import Console, ConsoleOptions
|
from rich.console import Console, ConsoleOptions
|
||||||
|
|
||||||
|
|
||||||
class CustomHelpFormatter(DefaultFormatter):
|
class BaseHelpFormatter(DefaultFormatter):
|
||||||
"""Custom help formatter that injects an index argument into the usage line and filters it out from the parameters list.
|
"""Base help formatter that provides common functionality."""
|
||||||
|
|
||||||
This formatter modifies the usage line to include an <index> argument after the 'strip' command,
|
|
||||||
and filters out any parameters related to 'index' from the Parameters section of the help output.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_usage(self, console: Console, options: ConsoleOptions, usage) -> None:
|
|
||||||
"""Render the usage line with index argument injected."""
|
|
||||||
if usage:
|
|
||||||
modified_usage = re.sub(
|
|
||||||
r'(\S+\s+[a-z]+)\s+(COMMAND)', r'\1 <index> \2', str(usage)
|
|
||||||
)
|
|
||||||
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self, console: Console, options: ConsoleOptions, panel: HelpPanel
|
self, console: Console, options: ConsoleOptions, panel: HelpPanel
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Render a help panel, filtering out the index parameter from Parameters sections."""
|
"""Render a help panel, filtering out hidden parameters from Parameters sections."""
|
||||||
if panel.title == 'Parameters':
|
if panel.title == 'Parameters':
|
||||||
filtered_entries = [
|
filtered_entries = [
|
||||||
entry
|
entry
|
||||||
for entry in panel.entries
|
for entry in panel.entries
|
||||||
if not (
|
if not (
|
||||||
entry.names and any('index' in name.lower() for name in entry.names)
|
entry.names
|
||||||
|
and any(
|
||||||
|
param in name.lower()
|
||||||
|
for name in entry.names
|
||||||
|
for param in self.get_filtered_params()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -41,3 +34,116 @@ class CustomHelpFormatter(DefaultFormatter):
|
|||||||
super().__call__(console, options, filtered_panel)
|
super().__call__(console, options, filtered_panel)
|
||||||
else:
|
else:
|
||||||
super().__call__(console, options, panel)
|
super().__call__(console, options, panel)
|
||||||
|
|
||||||
|
def get_filtered_params(self):
|
||||||
|
"""Return list of parameter names to filter out of help output."""
|
||||||
|
return ['index', 'band', 'ctx', 'target', 'eq_kind']
|
||||||
|
|
||||||
|
|
||||||
|
class StripHelpFormatter(BaseHelpFormatter):
|
||||||
|
"""Help formatter for strip commands 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'.
|
||||||
|
|
||||||
|
Handles both command groups (COMMAND) and individual commands (commandname [ARGS/OPTIONS]).
|
||||||
|
"""
|
||||||
|
if usage:
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'(\S+\s+strip)\s+(\w+\s+\[[^\]]+\]|\w+\s+\[[^\]]+\]|\w+(?:\s+\[[^\]]+\])*|COMMAND)',
|
||||||
|
r'\1 <index> \2',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
if modified_usage == str(usage):
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'(\S+\s+strip)\s+(\w+)',
|
||||||
|
r'\1 <index> \2 [OPTIONS] [ARGS]',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
# Handle main strip command and subcommand groups
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'\bCOMMAND\b(?!\s+\[)', 'COMMAND [OPTIONS]', modified_usage
|
||||||
|
)
|
||||||
|
# Remove the duplicate INDEX that gets automatically added by cyclopts
|
||||||
|
modified_usage = re.sub(r'\s+INDEX$', '', modified_usage)
|
||||||
|
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||||
|
|
||||||
|
|
||||||
|
class BusHelpFormatter(BaseHelpFormatter):
|
||||||
|
"""Help formatter for bus commands that injects <index> after 'bus'."""
|
||||||
|
|
||||||
|
def render_usage(self, console: Console, options: ConsoleOptions, usage) -> None:
|
||||||
|
"""Render the usage line with index argument injected after 'bus'.
|
||||||
|
|
||||||
|
Handles both command groups (COMMAND) and individual commands (commandname [ARGS/OPTIONS])."""
|
||||||
|
if usage:
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'(\S+\s+bus)\s+(\w+\s+\[[^\]]+\]|\w+\s+\[[^\]]+\]|\w+(?:\s+\[[^\]]+\])*|COMMAND)',
|
||||||
|
r'\1 <index> \2',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
if modified_usage == str(usage):
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'(\S+\s+bus)\s+(\w+)',
|
||||||
|
r'\1 <index> \2 [OPTIONS] [ARGS]',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
# Handle main bus command and subcommand groups
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'\bCOMMAND\b(?!\s+\[)', 'COMMAND [OPTIONS]', modified_usage
|
||||||
|
)
|
||||||
|
# Remove the duplicate INDEX that gets automatically added by cyclopts
|
||||||
|
modified_usage = re.sub(r'\s+INDEX$', '', modified_usage)
|
||||||
|
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||||
|
|
||||||
|
|
||||||
|
class EqHelpFormatter(BaseHelpFormatter):
|
||||||
|
"""Help formatter for eq commands that works with both strip and bus commands.
|
||||||
|
|
||||||
|
Injects <index> after 'strip' or 'bus' and <band> after 'cell'."""
|
||||||
|
|
||||||
|
def render_usage(self, console: Console, options: ConsoleOptions, usage) -> None:
|
||||||
|
"""Render the usage line with proper <index> placement for both strip and bus commands."""
|
||||||
|
if usage:
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'(\S+\s+)(\w+)(\s+eq\s+)(COMMAND)',
|
||||||
|
r'\1\2 <index>\3\4 [OPTIONS]',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||||
|
|
||||||
|
|
||||||
|
class GainlayerHelpFormatter(BaseHelpFormatter):
|
||||||
|
"""Help formatter for gainlayer commands that works with strip commands.
|
||||||
|
|
||||||
|
Injects <index> after 'strip' and <gainlayer_index> after 'gainlayer'."""
|
||||||
|
|
||||||
|
def render_usage(self, console: Console, options: ConsoleOptions, usage) -> None:
|
||||||
|
"""Render the usage line with proper <index> placement for strip commands."""
|
||||||
|
if usage:
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'(\S+\s+strip)(\s+gainlayer\s+)(COMMAND)',
|
||||||
|
r'\1 <index>\2<[cyan]gainlayer_index[/cyan]> \3 [OPTIONS] [ARGS]',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
# Remove the duplicate GAINLAYER_INDEX that gets automatically added by cyclopts
|
||||||
|
modified_usage = re.sub(r'\s+GAINLAYER_INDEX$', '', modified_usage)
|
||||||
|
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||||
|
|
||||||
|
|
||||||
|
class CellHelpFormatter(BaseHelpFormatter):
|
||||||
|
"""Help formatter for cell commands that works with both strip and bus commands.
|
||||||
|
|
||||||
|
Injects <index> after 'strip' or 'bus' and <band> after 'cell'."""
|
||||||
|
|
||||||
|
def render_usage(self, console: Console, options: ConsoleOptions, usage) -> None:
|
||||||
|
"""Render the usage line with proper <index> and <band> placement."""
|
||||||
|
if usage:
|
||||||
|
modified_usage = re.sub(
|
||||||
|
r'(\S+\s+)(\w+)(\s+eq\s+cell\s+)(COMMAND)',
|
||||||
|
r'\1\2 <index>\3<[cyan]band[/cyan]> \4 [ARGS]',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
# Remove the duplicate BAND that gets automatically added by cyclopts
|
||||||
|
modified_usage = re.sub(r'\s+BAND$', '', modified_usage)
|
||||||
|
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||||
|
|||||||
131
src/vban_cli/recorder.py
Normal file
131
src/vban_cli/recorder.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from cyclopts import App, Parameter, validators
|
||||||
|
|
||||||
|
from . import 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(parse=False)],
|
||||||
|
):
|
||||||
|
"""Start the recorder playback."""
|
||||||
|
ctx.client.recorder.play()
|
||||||
|
app.console.print('Recorder playback started.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='pause')
|
||||||
|
def pause(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Pause the recorder playback."""
|
||||||
|
ctx.client.recorder.stop()
|
||||||
|
app.console.print('Recorder playback paused.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='stop')
|
||||||
|
def stop(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Stop the recorder playback/recording and reset to the beginning."""
|
||||||
|
ctx.client.recorder.stop()
|
||||||
|
ctx.client.recorder.goto('00:00:00')
|
||||||
|
# We have no way of knowing if the recorder was playing or recording, so we print a generic message.
|
||||||
|
# See https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 4.
|
||||||
|
app.console.print('Recorder stopped.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='replay')
|
||||||
|
def replay(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Replay the recorder playback."""
|
||||||
|
ctx.client.recorder.replay()
|
||||||
|
app.console.print('Recorder playback replay started.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='record')
|
||||||
|
def record(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Start recording."""
|
||||||
|
ctx.client.recorder.record()
|
||||||
|
app.console.print('Recorder recording started.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='pause-recording')
|
||||||
|
def pause_recording(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Pause the recorder recording."""
|
||||||
|
ctx.client.recorder.pause()
|
||||||
|
app.console.print('Recorder recording paused.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='ff')
|
||||||
|
def ff(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Fast forward the recorder playback."""
|
||||||
|
ctx.client.recorder.ff()
|
||||||
|
app.console.print('Recorder playback fast forwarded.')
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(name='rew')
|
||||||
|
def rew(
|
||||||
|
*,
|
||||||
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
|
):
|
||||||
|
"""Rewind the recorder playback."""
|
||||||
|
ctx.client.recorder.rew()
|
||||||
|
app.console.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(parse=False)],
|
||||||
|
):
|
||||||
|
"""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)
|
||||||
|
app.console.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(parse=False)],
|
||||||
|
):
|
||||||
|
"""Go to a specific timestamp in the recorder playback."""
|
||||||
|
ctx.client.recorder.goto(time_string)
|
||||||
|
app.console.print(f'Went to timestamp {time_string} in recorder playback.')
|
||||||
@ -2,29 +2,31 @@ from typing import Annotated, Optional
|
|||||||
|
|
||||||
from cyclopts import App, Argument, Parameter
|
from cyclopts import App, Argument, Parameter
|
||||||
|
|
||||||
from . import comp, console, denoiser, eq, gate
|
from . import comp, denoiser, eq, gainlayer, gate
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from .help import CustomHelpFormatter
|
from .help import StripHelpFormatter
|
||||||
|
|
||||||
app = App(name='strip', help_formatter=CustomHelpFormatter())
|
app = App(name='strip', help_formatter=StripHelpFormatter())
|
||||||
app.command(eq.app.meta, name='eq')
|
app.command(eq.app.meta, name='eq')
|
||||||
app.command(comp.app.meta, name='comp')
|
app.command(comp.app.meta, name='comp')
|
||||||
app.command(gate.app.meta, name='gate')
|
app.command(gate.app.meta, name='gate')
|
||||||
app.command(denoiser.app.meta, name='denoiser')
|
app.command(denoiser.app.meta, name='denoiser')
|
||||||
|
app.command(gainlayer.app.meta, name='gainlayer')
|
||||||
|
|
||||||
|
|
||||||
@app.meta.default
|
@app.meta.default
|
||||||
def launcher(
|
def launcher(
|
||||||
index: Annotated[int, Argument()] = None,
|
index: Annotated[int, Argument()],
|
||||||
|
/,
|
||||||
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Control the strip parameters."""
|
"""Control the strip parameters."""
|
||||||
additional_kwargs = {}
|
additional_kwargs = {}
|
||||||
command, bound, _ = app.parse_args(tokens)
|
command, bound, _ = app.parse_args(tokens)
|
||||||
if index is not None:
|
if tokens[0] == 'eq':
|
||||||
|
additional_kwargs['eq_kind'] = app.name[0]
|
||||||
additional_kwargs['index'] = index
|
additional_kwargs['index'] = index
|
||||||
if ctx is not None:
|
|
||||||
additional_kwargs['ctx'] = ctx
|
additional_kwargs['ctx'] = ctx
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
@ -34,8 +36,8 @@ def launcher(
|
|||||||
def mono(
|
def mono(
|
||||||
new_state: Annotated[Optional[bool], Argument()] = None,
|
new_state: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the mono state of the specified strip.
|
"""Get or set the mono state of the specified strip.
|
||||||
|
|
||||||
@ -45,7 +47,7 @@ def mono(
|
|||||||
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_state is None:
|
if new_state is None:
|
||||||
console.out.print(ctx.client.strip[index].mono)
|
app.console.print(ctx.client.strip[index].mono)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].mono = new_state
|
ctx.client.strip[index].mono = new_state
|
||||||
|
|
||||||
@ -54,8 +56,8 @@ def mono(
|
|||||||
def solo(
|
def solo(
|
||||||
new_state: Annotated[Optional[bool], Argument()] = None,
|
new_state: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the solo state of the specified strip.
|
"""Get or set the solo state of the specified strip.
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ def solo(
|
|||||||
If provided, sets the solo state to this value. If not provided, the current solo state is printed.
|
If provided, sets the solo state to this value. If not provided, the current solo state is printed.
|
||||||
"""
|
"""
|
||||||
if new_state is None:
|
if new_state is None:
|
||||||
console.out.print(ctx.client.strip[index].solo)
|
app.console.print(ctx.client.strip[index].solo)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].solo = new_state
|
ctx.client.strip[index].solo = new_state
|
||||||
|
|
||||||
@ -74,8 +76,8 @@ def solo(
|
|||||||
def mute(
|
def mute(
|
||||||
new_state: Annotated[Optional[bool], Argument()] = None,
|
new_state: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the mute state of the specified strip.
|
"""Get or set the mute state of the specified strip.
|
||||||
|
|
||||||
@ -85,7 +87,7 @@ def mute(
|
|||||||
If provided, sets the mute state to this value. If not provided, the current mute state is printed.
|
If provided, sets the mute state to this value. If not provided, the current mute state is printed.
|
||||||
"""
|
"""
|
||||||
if new_state is None:
|
if new_state is None:
|
||||||
console.out.print(ctx.client.strip[index].mute)
|
app.console.print(ctx.client.strip[index].mute)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].mute = new_state
|
ctx.client.strip[index].mute = new_state
|
||||||
|
|
||||||
@ -94,8 +96,8 @@ def mute(
|
|||||||
def gain(
|
def gain(
|
||||||
new_value: Annotated[Optional[float], Argument()] = None,
|
new_value: Annotated[Optional[float], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the gain of the specified strip.
|
"""Get or set the gain of the specified strip.
|
||||||
|
|
||||||
@ -105,7 +107,7 @@ def gain(
|
|||||||
If provided, sets the gain to this value. If not provided, the current gain is printed.
|
If provided, sets the gain to this value. If not provided, the current gain is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].gain)
|
app.console.print(ctx.client.strip[index].gain)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].gain = new_value
|
ctx.client.strip[index].gain = new_value
|
||||||
|
|
||||||
@ -114,8 +116,8 @@ def gain(
|
|||||||
def a1(
|
def a1(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the A1 state of the specified strip.
|
"""Get or set the A1 state of the specified strip.
|
||||||
|
|
||||||
@ -125,7 +127,7 @@ def a1(
|
|||||||
If provided, sets the A1 state to this value. If not provided, the current A1 state is printed.
|
If provided, sets the A1 state to this value. If not provided, the current A1 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].A1)
|
app.console.print(ctx.client.strip[index].A1)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].A1 = new_value
|
ctx.client.strip[index].A1 = new_value
|
||||||
|
|
||||||
@ -134,8 +136,8 @@ def a1(
|
|||||||
def a2(
|
def a2(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the A2 state of the specified strip.
|
"""Get or set the A2 state of the specified strip.
|
||||||
|
|
||||||
@ -145,7 +147,7 @@ def a2(
|
|||||||
If provided, sets the A2 state to this value. If not provided, the current A2 state is printed.
|
If provided, sets the A2 state to this value. If not provided, the current A2 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].A2)
|
app.console.print(ctx.client.strip[index].A2)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].A2 = new_value
|
ctx.client.strip[index].A2 = new_value
|
||||||
|
|
||||||
@ -154,8 +156,8 @@ def a2(
|
|||||||
def a3(
|
def a3(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the A3 state of the specified strip.
|
"""Get or set the A3 state of the specified strip.
|
||||||
|
|
||||||
@ -165,7 +167,7 @@ def a3(
|
|||||||
If provided, sets the A3 state to this value. If not provided, the current A3 state is printed.
|
If provided, sets the A3 state to this value. If not provided, the current A3 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].A3)
|
app.console.print(ctx.client.strip[index].A3)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].A3 = new_value
|
ctx.client.strip[index].A3 = new_value
|
||||||
|
|
||||||
@ -174,8 +176,8 @@ def a3(
|
|||||||
def a4(
|
def a4(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the A4 state of the specified strip.
|
"""Get or set the A4 state of the specified strip.
|
||||||
|
|
||||||
@ -185,7 +187,7 @@ def a4(
|
|||||||
If provided, sets the A4 state to this value. If not provided, the current A4 state is printed.
|
If provided, sets the A4 state to this value. If not provided, the current A4 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].A4)
|
app.console.print(ctx.client.strip[index].A4)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].A4 = new_value
|
ctx.client.strip[index].A4 = new_value
|
||||||
|
|
||||||
@ -194,8 +196,8 @@ def a4(
|
|||||||
def a5(
|
def a5(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the A5 state of the specified strip.
|
"""Get or set the A5 state of the specified strip.
|
||||||
|
|
||||||
@ -205,7 +207,7 @@ def a5(
|
|||||||
If provided, sets the A5 state to this value. If not provided, the current A5 state is printed.
|
If provided, sets the A5 state to this value. If not provided, the current A5 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].A5)
|
app.console.print(ctx.client.strip[index].A5)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].A5 = new_value
|
ctx.client.strip[index].A5 = new_value
|
||||||
|
|
||||||
@ -214,8 +216,8 @@ def a5(
|
|||||||
def b1(
|
def b1(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the B1 state of the specified strip.
|
"""Get or set the B1 state of the specified strip.
|
||||||
|
|
||||||
@ -225,7 +227,7 @@ def b1(
|
|||||||
If provided, sets the B1 state to this value. If not provided, the current B1 state is printed.
|
If provided, sets the B1 state to this value. If not provided, the current B1 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].B1)
|
app.console.print(ctx.client.strip[index].B1)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].B1 = new_value
|
ctx.client.strip[index].B1 = new_value
|
||||||
|
|
||||||
@ -234,8 +236,8 @@ def b1(
|
|||||||
def b2(
|
def b2(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the B2 state of the specified strip.
|
"""Get or set the B2 state of the specified strip.
|
||||||
|
|
||||||
@ -245,7 +247,7 @@ def b2(
|
|||||||
If provided, sets the B2 state to this value. If not provided, the current B2 state is printed.
|
If provided, sets the B2 state to this value. If not provided, the current B2 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].B2)
|
app.console.print(ctx.client.strip[index].B2)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].B2 = new_value
|
ctx.client.strip[index].B2 = new_value
|
||||||
|
|
||||||
@ -254,8 +256,8 @@ def b2(
|
|||||||
def b3(
|
def b3(
|
||||||
new_value: Annotated[Optional[bool], Argument()] = None,
|
new_value: Annotated[Optional[bool], Argument()] = None,
|
||||||
*,
|
*,
|
||||||
index: Annotated[int, Parameter(show=False)] = None,
|
index: Annotated[int, Parameter(parse=False)],
|
||||||
ctx: Annotated[Context, Parameter(show=False)] = None,
|
ctx: Annotated[Context, Parameter(parse=False)],
|
||||||
):
|
):
|
||||||
"""Get or set the B3 state of the specified strip.
|
"""Get or set the B3 state of the specified strip.
|
||||||
|
|
||||||
@ -265,6 +267,6 @@ def b3(
|
|||||||
If provided, sets the B3 state to this value. If not provided, the current B3 state is printed.
|
If provided, sets the B3 state to this value. If not provided, the current B3 state is printed.
|
||||||
"""
|
"""
|
||||||
if new_value is None:
|
if new_value is None:
|
||||||
console.out.print(ctx.client.strip[index].B3)
|
app.console.print(ctx.client.strip[index].B3)
|
||||||
return
|
return
|
||||||
ctx.client.strip[index].B3 = new_value
|
ctx.client.strip[index].B3 = new_value
|
||||||
|
|||||||
11
src/vban_cli/validation.py
Normal file
11
src/vban_cli/validation.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from .error import VbanCLIValidationError
|
||||||
|
|
||||||
|
|
||||||
|
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 VbanCLIValidationError('Invalid time format. Expected HH:MM:SS.')
|
||||||
|
return value
|
||||||
41
uv.lock
generated
41
uv.lock
generated
@ -11,6 +11,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
|
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cyclopts"
|
name = "cyclopts"
|
||||||
version = "4.6.0"
|
version = "4.6.0"
|
||||||
@ -44,6 +53,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" },
|
{ url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loguru"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown-it-py"
|
name = "markdown-it-py"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
@ -102,23 +124,34 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vban-cli"
|
name = "vban-cli"
|
||||||
version = "0.3.0"
|
version = "0.12.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cyclopts" },
|
{ name = "cyclopts" },
|
||||||
|
{ name = "loguru" },
|
||||||
{ name = "vban-cmd" },
|
{ name = "vban-cmd" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "cyclopts", specifier = ">=4.6.0" },
|
{ name = "cyclopts", specifier = ">=4.6.0" },
|
||||||
{ name = "vban-cmd", directory = "../vban-cmd-python" },
|
{ name = "loguru", specifier = ">=0.7.3" },
|
||||||
|
{ name = "vban-cmd", editable = "../vban-cmd-python" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vban-cmd"
|
name = "vban-cmd"
|
||||||
version = "2.6.0"
|
version = "2.9.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" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-setctime"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
|
||||||
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user