mirror of
https://github.com/onyx-and-iris/vban-cli.git
synced 2026-03-02 21:19:11 +00:00
Compare commits
15 Commits
61a3bc38a8
...
a9c3168542
| Author | SHA1 | Date | |
|---|---|---|---|
| a9c3168542 | |||
| fba3eddea8 | |||
| 6c2c924a48 | |||
| 27290e1a0e | |||
| 6188da4f51 | |||
| 230e537414 | |||
| e6ebf86c86 | |||
| 1a0fb979e0 | |||
| 080e26f75f | |||
| f6d82c5064 | |||
| 627ada3b09 | |||
| f389eb53b8 | |||
| 341c81fde1 | |||
| e062da51ed | |||
| c82a021708 |
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
|
||||||
42
README.md
42
README.md
@ -85,10 +85,28 @@ examples:
|
|||||||
|
|
||||||
```console
|
```console
|
||||||
vban-cli strip 0 eq cell 0 on false
|
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.
|
see `vban-cli strip eq cell --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
|
### Bus Command
|
||||||
|
|
||||||
Usage: vban-cli bus \<index> COMMAND [ARGS]
|
Usage: vban-cli bus \<index> COMMAND [ARGS]
|
||||||
@ -117,14 +135,30 @@ vban-cli command restart
|
|||||||
|
|
||||||
see `vban-cli command --help` for more info.
|
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
|
||||||
|
|
||||||
Usage: vban-cli sendtext TEXT
|
Usage: vban-cli sendtext TEXT
|
||||||
|
|
||||||
*To Voicemeeter*
|
|
||||||
|
|
||||||
examples:
|
examples:
|
||||||
|
|
||||||
|
*To Voicemeeter*
|
||||||
|
|
||||||
```console
|
```console
|
||||||
vban-cli sendtext 'Strip[0].Mute=1;Bus[0].Mono=2'
|
vban-cli sendtext 'Strip[0].Mute=1;Bus[0].Mono=2'
|
||||||
```
|
```
|
||||||
@ -143,7 +177,7 @@ 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 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.
|
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].
|
||||||
|
|
||||||
@ -166,5 +200,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,11 +1,11 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "vban-cli"
|
name = "vban-cli"
|
||||||
version = "0.6.0"
|
version = "0.9.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", "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",
|
||||||
|
|||||||
@ -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, command, console, strip
|
from . import bus, command, console, recorder, strip
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
|
||||||
app = App(
|
app = App(
|
||||||
@ -17,6 +17,7 @@ app = App(
|
|||||||
app.command(strip.app.meta, name='strip')
|
app.command(strip.app.meta, name='strip')
|
||||||
app.command(bus.app.meta, name='bus')
|
app.command(bus.app.meta, name='bus')
|
||||||
app.command(command.app, name='command')
|
app.command(command.app, name='command')
|
||||||
|
app.command(recorder.app, name='recorder')
|
||||||
app.register_install_completion_command()
|
app.register_install_completion_command()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -64,7 +64,7 @@ def cell_launcher(
|
|||||||
Only channel 0 is supported, see https://github.com/onyx-and-iris/vban-cli?tab=readme-ov-file#implementation-notes - 3.
|
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, _ = cell_app.parse_args(tokens)
|
||||||
additional_kwargs['target'] = target.channel[0].cell[band]
|
additional_kwargs['target'] = target.channel[0].cell[band]
|
||||||
|
|
||||||
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
return command(*bound.args, **bound.kwargs, **additional_kwargs)
|
||||||
@ -88,3 +88,83 @@ def cell_on(
|
|||||||
# console.out.print(target.on)
|
# console.out.print(target.on)
|
||||||
return
|
return
|
||||||
target.on = new_state
|
target.on = new_state
|
||||||
|
|
||||||
|
|
||||||
|
@cell_app.command(name='freq')
|
||||||
|
def cell_freq(
|
||||||
|
new_freq: Annotated[float, Argument()] = None,
|
||||||
|
*,
|
||||||
|
target: Annotated[object, Parameter(show=False)] = None,
|
||||||
|
):
|
||||||
|
"""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.
|
||||||
|
# console.out.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(show=False)] = None,
|
||||||
|
):
|
||||||
|
"""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.
|
||||||
|
# console.out.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(show=False)] = None,
|
||||||
|
):
|
||||||
|
"""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.
|
||||||
|
# console.out.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(show=False)] = None,
|
||||||
|
):
|
||||||
|
"""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.
|
||||||
|
# console.out.print(target.type)
|
||||||
|
return
|
||||||
|
target.type = new_type
|
||||||
|
|||||||
51
src/vban_cli/gainlayer.py
Normal file
51
src/vban_cli/gainlayer.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from cyclopts import App, Argument, Parameter
|
||||||
|
|
||||||
|
from . import console
|
||||||
|
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()] = None,
|
||||||
|
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
||||||
|
index: Annotated[int, Argument()] = None,
|
||||||
|
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||||
|
):
|
||||||
|
"""Control the gainlayers."""
|
||||||
|
additional_kwargs = {}
|
||||||
|
command, bound, _ = app.parse_args(tokens)
|
||||||
|
if index is not None and gainlayer_index is not None:
|
||||||
|
additional_kwargs['strip_index'] = index
|
||||||
|
additional_kwargs['gainlayer_index'] = gainlayer_index
|
||||||
|
else:
|
||||||
|
raise ValueError('Both gainlayer_index and index must be provided.')
|
||||||
|
if ctx is not None:
|
||||||
|
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(show=False)] = None,
|
||||||
|
gainlayer_index: Annotated[int, Parameter(show=False)] = None,
|
||||||
|
ctx: Annotated[Context, Parameter(show=False)] = None,
|
||||||
|
):
|
||||||
|
"""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:
|
||||||
|
console.out.print(ctx.client.strip[strip_index].gainlayer[gainlayer_index].gain)
|
||||||
|
return
|
||||||
|
ctx.client.strip[strip_index].gainlayer[gainlayer_index].gain = new_level
|
||||||
@ -95,6 +95,22 @@ class EqHelpFormatter(BaseHelpFormatter):
|
|||||||
console.print(f'[bold]Usage:[/bold] {modified_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',
|
||||||
|
str(usage),
|
||||||
|
)
|
||||||
|
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||||
|
|
||||||
|
|
||||||
class CellHelpFormatter(BaseHelpFormatter):
|
class CellHelpFormatter(BaseHelpFormatter):
|
||||||
"""Help formatter for cell commands that works with both strip and bus commands.
|
"""Help formatter for cell commands that works with both strip and bus commands.
|
||||||
|
|
||||||
@ -105,7 +121,7 @@ class CellHelpFormatter(BaseHelpFormatter):
|
|||||||
if usage:
|
if usage:
|
||||||
modified_usage = re.sub(
|
modified_usage = re.sub(
|
||||||
r'(\S+\s+)(\w+)(\s+eq\s+cell\s+)(COMMAND)',
|
r'(\S+\s+)(\w+)(\s+eq\s+cell\s+)(COMMAND)',
|
||||||
r'\1\2 <index>\3<band> \4',
|
r'\1\2 <index>\3<[cyan]band[/cyan]> \4',
|
||||||
str(usage),
|
str(usage),
|
||||||
)
|
)
|
||||||
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
console.print(f'[bold]Usage:[/bold] {modified_usage}')
|
||||||
|
|||||||
118
src/vban_cli/recorder.py
Normal file
118
src/vban_cli/recorder.py
Normal 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.')
|
||||||
@ -2,7 +2,7 @@ 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, console, denoiser, eq, gainlayer, gate
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from .help import StripHelpFormatter
|
from .help import StripHelpFormatter
|
||||||
|
|
||||||
@ -11,6 +11,7 @@ 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
|
||||||
|
|||||||
9
src/vban_cli/validation.py
Normal file
9
src/vban_cli/validation.py
Normal 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
4
uv.lock
generated
@ -124,7 +124,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vban-cli"
|
name = "vban-cli"
|
||||||
version = "0.6.0"
|
version = "0.9.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.1"
|
||||||
source = { editable = "../vban-cmd-python" }
|
source = { editable = "../vban-cmd-python" }
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user