mirror of
https://github.com/onyx-and-iris/obsws-cli.git
synced 2025-08-07 20:21:48 +00:00
Compare commits
12 Commits
44527b35e2
...
1a1fbf1da1
Author | SHA1 | Date | |
---|---|---|---|
1a1fbf1da1 | |||
fd2baf3350 | |||
5334879ba9 | |||
77dbe52ae6 | |||
1ff610410a | |||
cd7614bfd6 | |||
74503f17e0 | |||
32bc4277f2 | |||
21f1b5e1bb | |||
434f8c0e0c | |||
81518a14ea | |||
ddb92bb317 |
15
CHANGELOG.md
15
CHANGELOG.md
@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
# [0.16.5] - 2025-06-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- [Disable Colouring](https://github.com/onyx-and-iris/obsws-cli?tab=readme-ov-file#disable-colouring) section added to README.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- error output:
|
||||||
|
- now printed in bold red.
|
||||||
|
- highlights are now yellow
|
||||||
|
- normal output:
|
||||||
|
- highlights are now green
|
||||||
|
- help messages:
|
||||||
|
- removed a lot of the `[default: None]`, this affects optional flags/arguments without default values.
|
||||||
|
|
||||||
# [0.16.1] - 2025-06-04
|
# [0.16.1] - 2025-06-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -265,6 +265,8 @@ obsws-cli group status START "test_group"
|
|||||||
- --input: Filter by input type.
|
- --input: Filter by input type.
|
||||||
- --output: Filter by output type.
|
- --output: Filter by output type.
|
||||||
- --colour: Filter by colour source type.
|
- --colour: Filter by colour source type.
|
||||||
|
- --ffmpeg: Filter by ffmpeg source type.
|
||||||
|
- --vlc: Filter by VLC source type.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
obsws-cli input list
|
obsws-cli input list
|
||||||
@ -602,6 +604,10 @@ obsws-cli projector open --monitor-index=1 "test_group"
|
|||||||
obsws-cli screenshot save --width=2560 --height=1440 "Scene" "C:\Users\me\Videos\screenshot.png"
|
obsws-cli screenshot save --width=2560 --height=1440 "Scene" "C:\Users\me\Videos\screenshot.png"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Disable Colouring
|
||||||
|
|
||||||
|
If you prefer colourless output you can set the environment variable `NO_COLOR`. See the [rich documentation][rich-doc-envvars]
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
`obsws-cli` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
`obsws-cli` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
||||||
@ -609,3 +615,4 @@ obsws-cli screenshot save --width=2560 --height=1440 "Scene" "C:\Users\me\Videos
|
|||||||
|
|
||||||
[obs-studio]: https://obsproject.com/
|
[obs-studio]: https://obsproject.com/
|
||||||
[obs-keyids]: https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h
|
[obs-keyids]: https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h
|
||||||
|
[rich-doc-envvars]: https://rich.readthedocs.io/en/stable/console.html#environment-variables
|
@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2025-present onyx-and-iris <code@onyxandiris.online>
|
# SPDX-FileCopyrightText: 2025-present onyx-and-iris <code@onyxandiris.online>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
__version__ = "0.16.3"
|
__version__ = "0.16.8"
|
||||||
|
@ -34,7 +34,7 @@ for sub_typer in (
|
|||||||
app.add_typer(module.app, name=sub_typer)
|
app.add_typer(module.app, name=sub_typer)
|
||||||
|
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
def version_callback(value: bool):
|
def version_callback(value: bool):
|
||||||
|
@ -12,7 +12,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -39,23 +39,27 @@ def list_(
|
|||||||
resp = ctx.obj.get_source_filter_list(source_name)
|
resp = ctx.obj.get_source_filter_list(source_name)
|
||||||
except obsws.error.OBSSDKRequestError as e:
|
except obsws.error.OBSSDKRequestError as e:
|
||||||
if e.code == 600:
|
if e.code == 600:
|
||||||
err_console.print(f"No source was found by the name of '{source_name}'.")
|
err_console.print(
|
||||||
|
f'No source was found by the name of [yellow]{source_name}[/yellow].'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if not resp.filters:
|
if not resp.filters:
|
||||||
out_console.print(f'No filters found for source {source_name}')
|
out_console.print(f'No filters found for source [yellow]{source_name}[/yellow]')
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
table = Table(title=f'Filters for Source: {source_name}', padding=(0, 2))
|
table = Table(title=f'Filters for Source: {source_name}', padding=(0, 2))
|
||||||
|
|
||||||
for column in ('Filter Name', 'Kind', 'Enabled', 'Settings'):
|
columns = [
|
||||||
table.add_column(
|
('Filter Name', 'left', 'cyan'),
|
||||||
column,
|
('Kind', 'left', 'cyan'),
|
||||||
justify='left' if column in ('Filter Name', 'Kind') else 'center',
|
('Enabled', 'center', None),
|
||||||
style='cyan',
|
('Settings', 'center', 'cyan'),
|
||||||
)
|
]
|
||||||
|
for name, justify, style in columns:
|
||||||
|
table.add_column(name, justify=justify, style=style if style else None)
|
||||||
|
|
||||||
for filter in resp.filters:
|
for filter in resp.filters:
|
||||||
resp = ctx.obj.get_source_filter_default_settings(filter['filterKind'])
|
resp = ctx.obj.get_source_filter_default_settings(filter['filterKind'])
|
||||||
@ -101,12 +105,14 @@ def enable(
|
|||||||
"""Enable a filter for a source."""
|
"""Enable a filter for a source."""
|
||||||
if _get_filter_enabled(ctx, source_name, filter_name):
|
if _get_filter_enabled(ctx, source_name, filter_name):
|
||||||
err_console.print(
|
err_console.print(
|
||||||
f'Filter {filter_name} is already enabled for source {source_name}'
|
f'Filter [yellow]{filter_name}[/yellow] is already enabled for source [yellow]{source_name}[/yellow]'
|
||||||
)
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_source_filter_enabled(source_name, filter_name, enabled=True)
|
ctx.obj.set_source_filter_enabled(source_name, filter_name, enabled=True)
|
||||||
out_console.print(f'Enabled filter {filter_name} for source {source_name}')
|
out_console.print(
|
||||||
|
f'Enabled filter [green]{filter_name}[/green] for source [green]{source_name}[/green]'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.command('disable | off')
|
@app.command('disable | off')
|
||||||
@ -128,12 +134,14 @@ def disable(
|
|||||||
"""Disable a filter for a source."""
|
"""Disable a filter for a source."""
|
||||||
if not _get_filter_enabled(ctx, source_name, filter_name):
|
if not _get_filter_enabled(ctx, source_name, filter_name):
|
||||||
err_console.print(
|
err_console.print(
|
||||||
f'Filter {filter_name} is already disabled for source {source_name}'
|
f'Filter [yellow]{filter_name}[/yellow] is already disabled for source [yellow]{source_name}[/yellow]'
|
||||||
)
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_source_filter_enabled(source_name, filter_name, enabled=False)
|
ctx.obj.set_source_filter_enabled(source_name, filter_name, enabled=False)
|
||||||
out_console.print(f'Disabled filter {filter_name} for source {source_name}')
|
out_console.print(
|
||||||
|
f'Disabled filter [green]{filter_name}[/green] for source [green]{source_name}[/green]'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.command('toggle | tg')
|
@app.command('toggle | tg')
|
||||||
@ -158,9 +166,13 @@ def toggle(
|
|||||||
|
|
||||||
ctx.obj.set_source_filter_enabled(source_name, filter_name, enabled=new_state)
|
ctx.obj.set_source_filter_enabled(source_name, filter_name, enabled=new_state)
|
||||||
if new_state:
|
if new_state:
|
||||||
out_console.print(f'Enabled filter {filter_name} for source {source_name}')
|
out_console.print(
|
||||||
|
f'Enabled filter [green]{filter_name}[/green] for source [green]{source_name}[/green]'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
out_console.print(f'Disabled filter {filter_name} for source {source_name}')
|
out_console.print(
|
||||||
|
f'Disabled filter [green]{filter_name}[/green] for source [green]{source_name}[/green]'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.command('status | ss')
|
@app.command('status | ss')
|
||||||
@ -182,6 +194,10 @@ def status(
|
|||||||
"""Get the status of a filter for a source."""
|
"""Get the status of a filter for a source."""
|
||||||
is_enabled = _get_filter_enabled(ctx, source_name, filter_name)
|
is_enabled = _get_filter_enabled(ctx, source_name, filter_name)
|
||||||
if is_enabled:
|
if is_enabled:
|
||||||
out_console.print(f'Filter {filter_name} is enabled for source {source_name}')
|
out_console.print(
|
||||||
|
f'Filter [green]{filter_name}[/green] is enabled for source [green]{source_name}[/green]'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
out_console.print(f'Filter {filter_name} is disabled for source {source_name}')
|
out_console.print(
|
||||||
|
f'Filter [green]{filter_name}[/green] is disabled for source [green]{source_name}[/green]'
|
||||||
|
)
|
||||||
|
@ -12,7 +12,7 @@ from .protocols import DataclassProtocol
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -52,10 +52,13 @@ def list_(
|
|||||||
|
|
||||||
table = Table(title=f'Groups in Scene: {scene_name}', padding=(0, 2))
|
table = Table(title=f'Groups in Scene: {scene_name}', padding=(0, 2))
|
||||||
|
|
||||||
for column in ('ID', 'Group Name', 'Enabled'):
|
columns = [
|
||||||
table.add_column(
|
('ID', 'center', 'cyan'),
|
||||||
column, justify='left' if column == 'Group Name' else 'center', style='cyan'
|
('Group Name', 'left', 'cyan'),
|
||||||
)
|
('Enabled', 'center', None),
|
||||||
|
]
|
||||||
|
for column, justify, style in columns:
|
||||||
|
table.add_column(column, justify=justify, style=style)
|
||||||
|
|
||||||
for item_id, group_name, is_enabled in groups:
|
for item_id, group_name, is_enabled in groups:
|
||||||
table.add_row(
|
table.add_row(
|
||||||
@ -98,7 +101,9 @@ def show(
|
|||||||
|
|
||||||
resp = ctx.obj.get_scene_item_list(scene_name)
|
resp = ctx.obj.get_scene_item_list(scene_name)
|
||||||
if (group := _get_group(group_name, resp)) is None:
|
if (group := _get_group(group_name, resp)) is None:
|
||||||
err_console.print(f"Group '{group_name}' not found in scene {scene_name}.")
|
err_console.print(
|
||||||
|
f'Group [yellow]{group_name}[/yellow] not found in scene [yellow]{scene_name}[/yellow].'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_scene_item_enabled(
|
ctx.obj.set_scene_item_enabled(
|
||||||
@ -107,7 +112,7 @@ def show(
|
|||||||
enabled=True,
|
enabled=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
out_console.print(f"Group '{group_name}' is now visible.")
|
out_console.print(f'Group [green]{group_name}[/green] is now visible.')
|
||||||
|
|
||||||
|
|
||||||
@app.command('hide | h')
|
@app.command('hide | h')
|
||||||
@ -122,12 +127,14 @@ def hide(
|
|||||||
):
|
):
|
||||||
"""Hide a group in a scene."""
|
"""Hide a group in a scene."""
|
||||||
if not validate.scene_in_scenes(ctx, scene_name):
|
if not validate.scene_in_scenes(ctx, scene_name):
|
||||||
err_console.print(f"Scene '{scene_name}' not found.")
|
err_console.print(f'Scene [yellow]{scene_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
resp = ctx.obj.get_scene_item_list(scene_name)
|
resp = ctx.obj.get_scene_item_list(scene_name)
|
||||||
if (group := _get_group(group_name, resp)) is None:
|
if (group := _get_group(group_name, resp)) is None:
|
||||||
err_console.print(f"Group '{group_name}' not found in scene {scene_name}.")
|
err_console.print(
|
||||||
|
f'Group [yellow]{group_name}[/yellow] not found in scene [yellow]{scene_name}[/yellow].'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_scene_item_enabled(
|
ctx.obj.set_scene_item_enabled(
|
||||||
@ -136,7 +143,7 @@ def hide(
|
|||||||
enabled=False,
|
enabled=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
out_console.print(f"Group '{group_name}' is now hidden.")
|
out_console.print(f'Group [green]{group_name}[/green] is now hidden.')
|
||||||
|
|
||||||
|
|
||||||
@app.command('toggle | tg')
|
@app.command('toggle | tg')
|
||||||
@ -151,12 +158,14 @@ def toggle(
|
|||||||
):
|
):
|
||||||
"""Toggle a group in a scene."""
|
"""Toggle a group in a scene."""
|
||||||
if not validate.scene_in_scenes(ctx, scene_name):
|
if not validate.scene_in_scenes(ctx, scene_name):
|
||||||
err_console.print(f"Scene '{scene_name}' not found.")
|
err_console.print(f'Scene [yellow]{scene_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
resp = ctx.obj.get_scene_item_list(scene_name)
|
resp = ctx.obj.get_scene_item_list(scene_name)
|
||||||
if (group := _get_group(group_name, resp)) is None:
|
if (group := _get_group(group_name, resp)) is None:
|
||||||
err_console.print(f"Group '{group_name}' not found in scene {scene_name}.")
|
err_console.print(
|
||||||
|
f'Group [yellow]{group_name}[/yellow] not found in scene [yellow]{scene_name}[/yellow].'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
new_state = not group.get('sceneItemEnabled')
|
new_state = not group.get('sceneItemEnabled')
|
||||||
@ -167,9 +176,9 @@ def toggle(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if new_state:
|
if new_state:
|
||||||
out_console.print(f"Group '{group_name}' is now visible.")
|
out_console.print(f'Group [green]{group_name}[/green] is now visible.')
|
||||||
else:
|
else:
|
||||||
out_console.print(f"Group '{group_name}' is now hidden.")
|
out_console.print(f'Group [green]{group_name}[/green] is now hidden.')
|
||||||
|
|
||||||
|
|
||||||
@app.command('status | ss')
|
@app.command('status | ss')
|
||||||
@ -184,12 +193,14 @@ def status(
|
|||||||
):
|
):
|
||||||
"""Get the status of a group in a scene."""
|
"""Get the status of a group in a scene."""
|
||||||
if not validate.scene_in_scenes(ctx, scene_name):
|
if not validate.scene_in_scenes(ctx, scene_name):
|
||||||
err_console.print(f"Scene '{scene_name}' not found.")
|
err_console.print(f'Scene [yellow]{scene_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
resp = ctx.obj.get_scene_item_list(scene_name)
|
resp = ctx.obj.get_scene_item_list(scene_name)
|
||||||
if (group := _get_group(group_name, resp)) is None:
|
if (group := _get_group(group_name, resp)) is None:
|
||||||
err_console.print(f"Group '{group_name}' not found in scene {scene_name}.")
|
err_console.print(
|
||||||
|
f'Group [yellow]{group_name}[/yellow] not found in scene [yellow]{scene_name}[/yellow].'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
enabled = ctx.obj.get_scene_item_enabled(
|
enabled = ctx.obj.get_scene_item_enabled(
|
||||||
@ -198,6 +209,6 @@ def status(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if enabled.scene_item_enabled:
|
if enabled.scene_item_enabled:
|
||||||
out_console.print(f"Group '{group_name}' is now visible.")
|
out_console.print(f'Group [green]{group_name}[/green] is now visible.')
|
||||||
else:
|
else:
|
||||||
out_console.print(f"Group '{group_name}' is now hidden.")
|
out_console.print(f'Group [green]{group_name}[/green] is now hidden.')
|
||||||
|
@ -10,7 +10,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
|
@ -11,7 +11,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -25,6 +25,8 @@ def list_(
|
|||||||
input: Annotated[bool, typer.Option(help='Filter by input type.')] = False,
|
input: Annotated[bool, typer.Option(help='Filter by input type.')] = False,
|
||||||
output: Annotated[bool, typer.Option(help='Filter by output type.')] = False,
|
output: Annotated[bool, typer.Option(help='Filter by output type.')] = False,
|
||||||
colour: Annotated[bool, typer.Option(help='Filter by colour source type.')] = False,
|
colour: Annotated[bool, typer.Option(help='Filter by colour source type.')] = False,
|
||||||
|
ffmpeg: Annotated[bool, typer.Option(help='Filter by ffmpeg source type.')] = False,
|
||||||
|
vlc: Annotated[bool, typer.Option(help='Filter by VLC source type.')] = False,
|
||||||
):
|
):
|
||||||
"""List all inputs."""
|
"""List all inputs."""
|
||||||
resp = ctx.obj.get_input_list()
|
resp = ctx.obj.get_input_list()
|
||||||
@ -36,31 +38,50 @@ def list_(
|
|||||||
kinds.append('output')
|
kinds.append('output')
|
||||||
if colour:
|
if colour:
|
||||||
kinds.append('color')
|
kinds.append('color')
|
||||||
if not any([input, output, colour]):
|
if ffmpeg:
|
||||||
kinds = ['input', 'output', 'color']
|
kinds.append('ffmpeg')
|
||||||
|
if vlc:
|
||||||
|
kinds.append('vlc')
|
||||||
|
if not any([input, output, colour, ffmpeg, vlc]):
|
||||||
|
kinds = ['input', 'output', 'color', 'ffmpeg', 'vlc']
|
||||||
|
|
||||||
inputs = [
|
inputs = sorted(
|
||||||
(input_.get('inputName'), input_.get('inputKind'))
|
(
|
||||||
for input_ in filter(
|
(input_.get('inputName'), input_.get('inputKind'))
|
||||||
lambda input_: any(kind in input_.get('inputKind') for kind in kinds),
|
for input_ in filter(
|
||||||
resp.inputs,
|
lambda input_: any(kind in input_.get('inputKind') for kind in kinds),
|
||||||
)
|
resp.inputs,
|
||||||
]
|
)
|
||||||
|
),
|
||||||
|
key=lambda x: x[0], # Sort by input name
|
||||||
|
)
|
||||||
|
|
||||||
if not inputs:
|
if not inputs:
|
||||||
out_console.print('No inputs found.')
|
out_console.print('No inputs found.')
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
table = Table(title='Inputs', padding=(0, 2))
|
table = Table(title='Inputs', padding=(0, 2))
|
||||||
for column in ('Input Name', 'Kind'):
|
columns = [
|
||||||
table.add_column(
|
('Input Name', 'left', 'cyan'),
|
||||||
column, justify='left' if column == 'Input Name' else 'center', style='cyan'
|
('Kind', 'center', 'cyan'),
|
||||||
)
|
('Muted', 'center', None),
|
||||||
|
]
|
||||||
|
for column, justify, style in columns:
|
||||||
|
table.add_column(column, justify=justify, style=style)
|
||||||
|
|
||||||
for input_name, input_kind in inputs:
|
for input_name, input_kind in inputs:
|
||||||
|
input_mark = ''
|
||||||
|
if any(
|
||||||
|
kind in input_kind
|
||||||
|
for kind in ['input_capture', 'output_capture', 'ffmpeg', 'vlc']
|
||||||
|
):
|
||||||
|
input_muted = ctx.obj.get_input_mute(name=input_name).input_muted
|
||||||
|
input_mark = ':white_heavy_check_mark:' if input_muted else ':x:'
|
||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
input_name,
|
input_name,
|
||||||
util.snakecase_to_titlecase(input_kind),
|
util.snakecase_to_titlecase(input_kind),
|
||||||
|
input_mark,
|
||||||
)
|
)
|
||||||
|
|
||||||
out_console.print(table)
|
out_console.print(table)
|
||||||
@ -75,7 +96,7 @@ def mute(
|
|||||||
):
|
):
|
||||||
"""Mute an input."""
|
"""Mute an input."""
|
||||||
if not validate.input_in_inputs(ctx, input_name):
|
if not validate.input_in_inputs(ctx, input_name):
|
||||||
err_console.print(f"Input '{input_name}' not found.")
|
err_console.print(f'Input [yellow]{input_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_input_mute(
|
ctx.obj.set_input_mute(
|
||||||
@ -83,7 +104,7 @@ def mute(
|
|||||||
muted=True,
|
muted=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
out_console.print(f"Input '{input_name}' muted.")
|
out_console.print(f'Input [green]{input_name}[/green] muted.')
|
||||||
|
|
||||||
|
|
||||||
@app.command('unmute | um')
|
@app.command('unmute | um')
|
||||||
@ -96,7 +117,7 @@ def unmute(
|
|||||||
):
|
):
|
||||||
"""Unmute an input."""
|
"""Unmute an input."""
|
||||||
if not validate.input_in_inputs(ctx, input_name):
|
if not validate.input_in_inputs(ctx, input_name):
|
||||||
err_console.print(f"Input '{input_name}' not found.")
|
err_console.print(f'Input [yellow]{input_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_input_mute(
|
ctx.obj.set_input_mute(
|
||||||
@ -104,7 +125,7 @@ def unmute(
|
|||||||
muted=False,
|
muted=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
out_console.print(f"Input '{input_name}' unmuted.")
|
out_console.print(f'Input [green]{input_name}[/green] unmuted.')
|
||||||
|
|
||||||
|
|
||||||
@app.command('toggle | tg')
|
@app.command('toggle | tg')
|
||||||
@ -117,7 +138,7 @@ def toggle(
|
|||||||
):
|
):
|
||||||
"""Toggle an input."""
|
"""Toggle an input."""
|
||||||
if not validate.input_in_inputs(ctx, input_name):
|
if not validate.input_in_inputs(ctx, input_name):
|
||||||
err_console.print(f"Input '{input_name}' not found.")
|
err_console.print(f'Input [yellow]{input_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
resp = ctx.obj.get_input_mute(name=input_name)
|
resp = ctx.obj.get_input_mute(name=input_name)
|
||||||
@ -128,6 +149,11 @@ def toggle(
|
|||||||
muted=new_state,
|
muted=new_state,
|
||||||
)
|
)
|
||||||
|
|
||||||
out_console.print(
|
if new_state:
|
||||||
f"Input '{input_name}' {'muted' if new_state else 'unmuted'}.",
|
out_console.print(
|
||||||
)
|
f'Input [green]{input_name}[/green] muted.',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
out_console.print(
|
||||||
|
f'Input [green]{input_name}[/green] unmuted.',
|
||||||
|
)
|
||||||
|
@ -11,7 +11,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -25,12 +25,12 @@ def list_(ctx: typer.Context):
|
|||||||
resp = ctx.obj.get_profile_list()
|
resp = ctx.obj.get_profile_list()
|
||||||
|
|
||||||
table = Table(title='Profiles', padding=(0, 2))
|
table = Table(title='Profiles', padding=(0, 2))
|
||||||
for column in ('Profile Name', 'Current'):
|
columns = [
|
||||||
table.add_column(
|
('Profile Name', 'left', 'cyan'),
|
||||||
column,
|
('Current', 'center', None),
|
||||||
justify='left' if column == 'Profile Name' else 'center',
|
]
|
||||||
style='cyan',
|
for column, justify, style in columns:
|
||||||
)
|
table.add_column(column, justify=justify, style=style)
|
||||||
|
|
||||||
for profile in resp.profiles:
|
for profile in resp.profiles:
|
||||||
table.add_row(
|
table.add_row(
|
||||||
@ -60,16 +60,18 @@ def switch(
|
|||||||
):
|
):
|
||||||
"""Switch to a profile."""
|
"""Switch to a profile."""
|
||||||
if not validate.profile_exists(ctx, profile_name):
|
if not validate.profile_exists(ctx, profile_name):
|
||||||
err_console.print(f"Profile '{profile_name}' not found.")
|
err_console.print(f'Profile [yellow]{profile_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
resp = ctx.obj.get_profile_list()
|
resp = ctx.obj.get_profile_list()
|
||||||
if resp.current_profile_name == profile_name:
|
if resp.current_profile_name == profile_name:
|
||||||
err_console.print(f"Profile '{profile_name}' is already the current profile.")
|
err_console.print(
|
||||||
|
f'Profile [yellow]{profile_name}[/yellow] is already the current profile.'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_current_profile(profile_name)
|
ctx.obj.set_current_profile(profile_name)
|
||||||
out_console.print(f"Switched to profile '{profile_name}'.")
|
out_console.print(f'Switched to profile [green]{profile_name}[/green].')
|
||||||
|
|
||||||
|
|
||||||
@app.command('create | new')
|
@app.command('create | new')
|
||||||
@ -82,11 +84,11 @@ def create(
|
|||||||
):
|
):
|
||||||
"""Create a new profile."""
|
"""Create a new profile."""
|
||||||
if validate.profile_exists(ctx, profile_name):
|
if validate.profile_exists(ctx, profile_name):
|
||||||
err_console.print(f"Profile '{profile_name}' already exists.")
|
err_console.print(f'Profile [yellow]{profile_name}[/yellow] already exists.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.create_profile(profile_name)
|
ctx.obj.create_profile(profile_name)
|
||||||
out_console.print(f"Created profile '{profile_name}'.")
|
out_console.print(f'Created profile [green]{profile_name}[/green].')
|
||||||
|
|
||||||
|
|
||||||
@app.command('remove | rm')
|
@app.command('remove | rm')
|
||||||
@ -99,8 +101,8 @@ def remove(
|
|||||||
):
|
):
|
||||||
"""Remove a profile."""
|
"""Remove a profile."""
|
||||||
if not validate.profile_exists(ctx, profile_name):
|
if not validate.profile_exists(ctx, profile_name):
|
||||||
err_console.print(f"Profile '{profile_name}' not found.")
|
err_console.print(f'Profile [yellow]{profile_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.remove_profile(profile_name)
|
ctx.obj.remove_profile(profile_name)
|
||||||
out_console.print(f"Removed profile '{profile_name}'.")
|
out_console.print(f'Removed profile [green]{profile_name}[/green].')
|
||||||
|
@ -10,7 +10,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -61,11 +61,21 @@ def open(
|
|||||||
if not source_name:
|
if not source_name:
|
||||||
source_name = ctx.obj.get_current_program_scene().scene_name
|
source_name = ctx.obj.get_current_program_scene().scene_name
|
||||||
|
|
||||||
ctx.obj.open_source_projector(
|
monitors = ctx.obj.get_monitor_list().monitors
|
||||||
source_name=source_name,
|
for monitor in monitors:
|
||||||
monitor_index=monitor_index,
|
if monitor['monitorIndex'] == monitor_index:
|
||||||
)
|
ctx.obj.open_source_projector(
|
||||||
|
source_name=source_name,
|
||||||
|
monitor_index=monitor_index,
|
||||||
|
)
|
||||||
|
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f'Opened projector for source [bold]{source_name}[/] on monitor [bold]{monitor_index}[/].'
|
f'Opened projector for source [green]{source_name}[/] on monitor [green]{monitor["monitorName"]}[/].'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
err_console.print(
|
||||||
|
f'Monitor with index [yellow]{monitor_index}[/yellow] not found.'
|
||||||
|
)
|
||||||
|
raise typer.Exit(code=1)
|
||||||
|
@ -10,7 +10,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -49,7 +49,9 @@ def stop(ctx: typer.Context):
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
resp = ctx.obj.stop_record()
|
resp = ctx.obj.stop_record()
|
||||||
out_console.print(f'Recording stopped successfully. Saved to: {resp.output_path}')
|
out_console.print(
|
||||||
|
f'Recording stopped successfully. Saved to: [green]{resp.output_path}[/green]'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.command('toggle | tg')
|
@app.command('toggle | tg')
|
||||||
@ -125,5 +127,5 @@ def directory(
|
|||||||
out_console.print(f'Recording directory updated to: {record_directory}')
|
out_console.print(f'Recording directory updated to: {record_directory}')
|
||||||
else:
|
else:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f'Recording directory: {ctx.obj.get_record_directory().record_directory}'
|
f'Recording directory: [green]{ctx.obj.get_record_directory().record_directory}[/green]'
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
|
@ -11,7 +11,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -29,8 +29,12 @@ def list_(ctx: typer.Context):
|
|||||||
)
|
)
|
||||||
|
|
||||||
table = Table(title='Scenes', padding=(0, 2))
|
table = Table(title='Scenes', padding=(0, 2))
|
||||||
for column in ('Scene Name', 'UUID'):
|
columns = [
|
||||||
table.add_column(column, justify='left', style='cyan')
|
('Scene Name', 'left', 'cyan'),
|
||||||
|
('UUID', 'left', 'cyan'),
|
||||||
|
]
|
||||||
|
for column, justify, style in columns:
|
||||||
|
table.add_column(column, justify=justify, style=style)
|
||||||
|
|
||||||
for scene_name, scene_uuid in scenes:
|
for scene_name, scene_uuid in scenes:
|
||||||
table.add_row(
|
table.add_row(
|
||||||
@ -78,12 +82,12 @@ def switch(
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
if not validate.scene_in_scenes(ctx, scene_name):
|
if not validate.scene_in_scenes(ctx, scene_name):
|
||||||
err_console.print(f"Scene '{scene_name}' not found.")
|
err_console.print(f'Scene [yellow]{scene_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
if preview:
|
if preview:
|
||||||
ctx.obj.set_current_preview_scene(scene_name)
|
ctx.obj.set_current_preview_scene(scene_name)
|
||||||
out_console.print(f'Switched to preview scene: {scene_name}')
|
out_console.print(f'Switched to preview scene: [green]{scene_name}[/green]')
|
||||||
else:
|
else:
|
||||||
ctx.obj.set_current_program_scene(scene_name)
|
ctx.obj.set_current_program_scene(scene_name)
|
||||||
out_console.print(f'Switched to program scene: {scene_name}')
|
out_console.print(f'Switched to program scene: [green]{scene_name}[/green]')
|
||||||
|
@ -11,7 +11,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -49,7 +49,9 @@ def switch(
|
|||||||
):
|
):
|
||||||
"""Switch to a scene collection."""
|
"""Switch to a scene collection."""
|
||||||
if not validate.scene_collection_in_scene_collections(ctx, scene_collection_name):
|
if not validate.scene_collection_in_scene_collections(ctx, scene_collection_name):
|
||||||
err_console.print(f"Scene collection '{scene_collection_name}' not found.")
|
err_console.print(
|
||||||
|
f'Scene collection [yellow]{scene_collection_name}[/yellow] not found.'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
current_scene_collection = (
|
current_scene_collection = (
|
||||||
@ -57,12 +59,14 @@ def switch(
|
|||||||
)
|
)
|
||||||
if scene_collection_name == current_scene_collection:
|
if scene_collection_name == current_scene_collection:
|
||||||
err_console.print(
|
err_console.print(
|
||||||
f'Scene collection "{scene_collection_name}" is already active.'
|
f'Scene collection [yellow]{scene_collection_name}[/yellow] is already active.'
|
||||||
)
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.set_current_scene_collection(scene_collection_name)
|
ctx.obj.set_current_scene_collection(scene_collection_name)
|
||||||
out_console.print(f"Switched to scene collection '{scene_collection_name}'")
|
out_console.print(
|
||||||
|
f'Switched to scene collection [green]{scene_collection_name}[/green].'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.command('create | new')
|
@app.command('create | new')
|
||||||
@ -74,8 +78,12 @@ def create(
|
|||||||
):
|
):
|
||||||
"""Create a new scene collection."""
|
"""Create a new scene collection."""
|
||||||
if validate.scene_collection_in_scene_collections(ctx, scene_collection_name):
|
if validate.scene_collection_in_scene_collections(ctx, scene_collection_name):
|
||||||
err_console.print(f"Scene collection '{scene_collection_name}' already exists.")
|
err_console.print(
|
||||||
|
f'Scene collection [yellow]{scene_collection_name}[/yellow] already exists.'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
ctx.obj.create_scene_collection(scene_collection_name)
|
ctx.obj.create_scene_collection(scene_collection_name)
|
||||||
out_console.print(f'Created scene collection {scene_collection_name}')
|
out_console.print(
|
||||||
|
f'Created scene collection [green]{scene_collection_name}[/green].'
|
||||||
|
)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""module containing commands for manipulating items in scenes."""
|
"""module containing commands for manipulating items in scenes."""
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
from typing import Annotated, Optional
|
from typing import Annotated, Optional
|
||||||
|
|
||||||
import obsws_python as obsws
|
import obsws_python as obsws
|
||||||
@ -13,7 +12,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
@ -37,7 +36,7 @@ def list_(
|
|||||||
scene_name = ctx.obj.get_current_program_scene().scene_name
|
scene_name = ctx.obj.get_current_program_scene().scene_name
|
||||||
|
|
||||||
if not validate.scene_in_scenes(ctx, scene_name):
|
if not validate.scene_in_scenes(ctx, scene_name):
|
||||||
err_console.print(f"Scene '{scene_name}' not found.")
|
err_console.print(f'Scene [yellow]{scene_name}[/yellow] not found.')
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
resp = ctx.obj.get_scene_item_list(scene_name)
|
resp = ctx.obj.get_scene_item_list(scene_name)
|
||||||
@ -55,14 +54,19 @@ def list_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not items:
|
if not items:
|
||||||
out_console.print(f"No items found in scene '{scene_name}'.")
|
out_console.print(f'No items found in scene [green]{scene_name}[/green].')
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
table = Table(title=f'Items in Scene: {scene_name}', padding=(0, 2))
|
table = Table(title=f'Items in Scene: {scene_name}', padding=(0, 2))
|
||||||
for column in ('Item ID', 'Item Name', 'In Group', 'Enabled'):
|
columns = [
|
||||||
table.add_column(
|
('Item ID', 'center', 'cyan'),
|
||||||
column, justify='left' if column == 'Item Name' else 'center', style='cyan'
|
('Item Name', 'left', 'cyan'),
|
||||||
)
|
('In Group', 'left', 'cyan'),
|
||||||
|
('Enabled', 'center', None),
|
||||||
|
]
|
||||||
|
# Add columns to the table
|
||||||
|
for column, justify, style in columns:
|
||||||
|
table.add_column(column, justify=justify, style=style)
|
||||||
|
|
||||||
for item_id, item_name, is_group, is_enabled in items:
|
for item_id, item_name, is_group, is_enabled in items:
|
||||||
if is_group:
|
if is_group:
|
||||||
@ -98,37 +102,31 @@ def list_(
|
|||||||
out_console.print(table)
|
out_console.print(table)
|
||||||
|
|
||||||
|
|
||||||
def _validate_scene_name_and_item_name(
|
def _validate_sources(
|
||||||
func: Callable,
|
ctx: typer.Context,
|
||||||
):
|
scene_name: str,
|
||||||
|
item_name: str,
|
||||||
|
group: Optional[str] = None,
|
||||||
|
) -> bool:
|
||||||
"""Validate the scene name and item name."""
|
"""Validate the scene name and item name."""
|
||||||
|
if not validate.scene_in_scenes(ctx, scene_name):
|
||||||
|
err_console.print(f'Scene [yellow]{scene_name}[/yellow] not found.')
|
||||||
|
return False
|
||||||
|
|
||||||
def wrapper(
|
if group:
|
||||||
ctx: typer.Context,
|
if not validate.item_in_scene_item_list(ctx, scene_name, group):
|
||||||
scene_name: str,
|
err_console.print(
|
||||||
item_name: str,
|
f'Group [yellow]{group}[/yellow] not found in scene [yellow]{scene_name}[/yellow].'
|
||||||
group: Optional[str] = None,
|
)
|
||||||
):
|
return False
|
||||||
if not validate.scene_in_scenes(ctx, scene_name):
|
else:
|
||||||
err_console.print(f"Scene '{scene_name}' not found.")
|
if not validate.item_in_scene_item_list(ctx, scene_name, item_name):
|
||||||
raise typer.Exit(1)
|
err_console.print(
|
||||||
|
f'Item [yellow]{item_name}[/yellow] not found in scene [yellow]{scene_name}[/yellow].'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
if group:
|
return True
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, group):
|
|
||||||
err_console.print(
|
|
||||||
f"Parent group '{group}' not found in scene '{scene_name}'."
|
|
||||||
)
|
|
||||||
raise typer.Exit(1)
|
|
||||||
else:
|
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, item_name):
|
|
||||||
err_console.print(
|
|
||||||
f"Item '{item_name}' not found in scene '{scene_name}'."
|
|
||||||
)
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
return func(ctx, scene_name, item_name, group)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def _get_scene_name_and_item_id(
|
def _get_scene_name_and_item_id(
|
||||||
@ -143,7 +141,9 @@ def _get_scene_name_and_item_id(
|
|||||||
scene_item_id = item.get('sceneItemId')
|
scene_item_id = item.get('sceneItemId')
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
err_console.print(f"Item '{item_name}' not found in group '{group}'.")
|
err_console.print(
|
||||||
|
f'Item [yellow]{item_name}[/yellow] not found in group [yellow]{group}[/yellow].'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -151,9 +151,9 @@ def _get_scene_name_and_item_id(
|
|||||||
except obsws.error.OBSSDKRequestError as e:
|
except obsws.error.OBSSDKRequestError as e:
|
||||||
if e.code == 600:
|
if e.code == 600:
|
||||||
err_console.print(
|
err_console.print(
|
||||||
f"Item '{item_name}' not found in scene '{scene_name}'. Is the item in a group? "
|
f'Item [yellow]{item_name}[/yellow] not found in scene [yellow]{scene_name}[/yellow]. Is the item in a group? '
|
||||||
'If so use the --group option to specify the parent group. '
|
'If so use the --group option to specify the parent group. '
|
||||||
'See `obsws-cli sceneitem list` for a list of items in the scene.'
|
'Use `obsws-cli sceneitem list` for a list of items in the scene.'
|
||||||
)
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
else:
|
else:
|
||||||
@ -163,17 +163,23 @@ def _get_scene_name_and_item_id(
|
|||||||
return scene_name, scene_item_id
|
return scene_name, scene_item_id
|
||||||
|
|
||||||
|
|
||||||
@_validate_scene_name_and_item_name
|
|
||||||
@app.command('show | sh')
|
@app.command('show | sh')
|
||||||
def show(
|
def show(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
scene_name: Annotated[
|
||||||
|
str, typer.Argument(..., show_default=False, help='Scene name the item is in')
|
||||||
|
],
|
||||||
item_name: Annotated[
|
item_name: Annotated[
|
||||||
str, typer.Argument(..., help='Item name to show in the scene')
|
str,
|
||||||
|
typer.Argument(..., show_default=False, help='Item name to show in the scene'),
|
||||||
],
|
],
|
||||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||||
):
|
):
|
||||||
"""Show an item in a scene."""
|
"""Show an item in a scene."""
|
||||||
|
if not _validate_sources(ctx, scene_name, item_name, group):
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
old_scene_name = scene_name
|
||||||
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
||||||
ctx, scene_name, item_name, group
|
ctx, scene_name, item_name, group
|
||||||
)
|
)
|
||||||
@ -186,27 +192,35 @@ def show(
|
|||||||
|
|
||||||
if group:
|
if group:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in group '{group}' in scene '{scene_name}' has been shown."
|
f'Item [green]{item_name}[/green] in group [green]{group}[/green] in scene [green]{old_scene_name}[/green] has been shown.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If not in a parent group, just show the scene name
|
# If not in a parent group, just show the scene name
|
||||||
# This is to avoid confusion with the parent group name
|
# This is to avoid confusion with the parent group name
|
||||||
# which is not the same as the scene name
|
# which is not the same as the scene name
|
||||||
# and is not needed in this case
|
# and is not needed in this case
|
||||||
out_console.print(f"Item '{item_name}' in scene '{scene_name}' has been shown.")
|
out_console.print(
|
||||||
|
f'Item [green]{item_name}[/green] in scene [green]{scene_name}[/green] has been shown.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@_validate_scene_name_and_item_name
|
|
||||||
@app.command('hide | h')
|
@app.command('hide | h')
|
||||||
def hide(
|
def hide(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
scene_name: Annotated[
|
||||||
|
str, typer.Argument(..., show_default=False, help='Scene name the item is in')
|
||||||
|
],
|
||||||
item_name: Annotated[
|
item_name: Annotated[
|
||||||
str, typer.Argument(..., help='Item name to hide in the scene')
|
str,
|
||||||
|
typer.Argument(..., show_default=False, help='Item name to hide in the scene'),
|
||||||
],
|
],
|
||||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||||
):
|
):
|
||||||
"""Hide an item in a scene."""
|
"""Hide an item in a scene."""
|
||||||
|
if not _validate_sources(ctx, scene_name, item_name, group):
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
old_scene_name = scene_name
|
||||||
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
||||||
ctx, scene_name, item_name, group
|
ctx, scene_name, item_name, group
|
||||||
)
|
)
|
||||||
@ -219,7 +233,7 @@ def hide(
|
|||||||
|
|
||||||
if group:
|
if group:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in group '{group}' in scene '{scene_name}' has been hidden."
|
f'Item [green]{item_name}[/green] in group [green]{group}[/green] in scene [green]{old_scene_name}[/green] has been hidden.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If not in a parent group, just show the scene name
|
# If not in a parent group, just show the scene name
|
||||||
@ -227,36 +241,29 @@ def hide(
|
|||||||
# which is not the same as the scene name
|
# which is not the same as the scene name
|
||||||
# and is not needed in this case
|
# and is not needed in this case
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in scene '{scene_name}' has been hidden."
|
f'Item [green]{item_name}[/green] in scene [green]{scene_name}[/green] has been hidden.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@_validate_scene_name_and_item_name
|
|
||||||
@app.command('toggle | tg')
|
@app.command('toggle | tg')
|
||||||
def toggle(
|
def toggle(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
scene_name: Annotated[
|
||||||
|
str, typer.Argument(..., show_default=False, help='Scene name the item is in')
|
||||||
|
],
|
||||||
item_name: Annotated[
|
item_name: Annotated[
|
||||||
str, typer.Argument(..., help='Item name to toggle in the scene')
|
str,
|
||||||
|
typer.Argument(
|
||||||
|
..., show_default=False, help='Item name to toggle in the scene'
|
||||||
|
),
|
||||||
],
|
],
|
||||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||||
):
|
):
|
||||||
"""Toggle an item in a scene."""
|
"""Toggle an item in a scene."""
|
||||||
if not validate.scene_in_scenes(ctx, scene_name):
|
if not _validate_sources(ctx, scene_name, item_name, group):
|
||||||
err_console.print(f"Scene '{scene_name}' not found.")
|
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
if group:
|
old_scene_name = scene_name
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, group):
|
|
||||||
err_console.print(
|
|
||||||
f"Parent group '{group}' not found in scene '{scene_name}'."
|
|
||||||
)
|
|
||||||
raise typer.Exit(1)
|
|
||||||
else:
|
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, item_name):
|
|
||||||
err_console.print(f"Item '{item_name}' not found in scene '{scene_name}'.")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
||||||
ctx, scene_name, item_name, group
|
ctx, scene_name, item_name, group
|
||||||
)
|
)
|
||||||
@ -276,11 +283,11 @@ def toggle(
|
|||||||
if group:
|
if group:
|
||||||
if new_state:
|
if new_state:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in group '{group}' in scene '{scene_name}' has been shown."
|
f'Item [green]{item_name}[/green] in group [green]{group}[/green] in scene [green]{old_scene_name}[/green] has been shown.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in group '{group}' in scene '{scene_name}' has been hidden."
|
f'Item [green]{item_name}[/green] in group [green]{group}[/green] in scene [green]{old_scene_name}[/green] has been hidden.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If not in a parent group, just show the scene name
|
# If not in a parent group, just show the scene name
|
||||||
@ -289,35 +296,31 @@ def toggle(
|
|||||||
# and is not needed in this case
|
# and is not needed in this case
|
||||||
if new_state:
|
if new_state:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in scene '{scene_name}' has been shown."
|
f'Item [green]{item_name}[/green] in scene [green]{scene_name}[/green] has been shown.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in scene '{scene_name}' has been hidden."
|
f'Item [green]{item_name}[/green] in scene [green]{scene_name}[/green] has been hidden.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@_validate_scene_name_and_item_name
|
|
||||||
@app.command('visible | v')
|
@app.command('visible | v')
|
||||||
def visible(
|
def visible(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
scene_name: Annotated[
|
||||||
|
str, typer.Argument(..., show_default=False, help='Scene name the item is in')
|
||||||
|
],
|
||||||
item_name: Annotated[
|
item_name: Annotated[
|
||||||
str, typer.Argument(..., help='Item name to check visibility in the scene')
|
str,
|
||||||
|
typer.Argument(
|
||||||
|
..., show_default=False, help='Item name to check visibility in the scene'
|
||||||
|
),
|
||||||
],
|
],
|
||||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||||
):
|
):
|
||||||
"""Check if an item in a scene is visible."""
|
"""Check if an item in a scene is visible."""
|
||||||
if group:
|
if not _validate_sources(ctx, scene_name, item_name, group):
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, group):
|
raise typer.Exit(1)
|
||||||
err_console.print(
|
|
||||||
f"Parent group '{group}' not found in scene '{scene_name}'."
|
|
||||||
)
|
|
||||||
raise typer.Exit(1)
|
|
||||||
else:
|
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, item_name):
|
|
||||||
err_console.print(f"Item '{item_name}' not found in scene '{scene_name}'.")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
old_scene_name = scene_name
|
old_scene_name = scene_name
|
||||||
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
||||||
@ -331,7 +334,7 @@ def visible(
|
|||||||
|
|
||||||
if group:
|
if group:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in group '{group}' in scene '{old_scene_name}' is currently {'visible' if enabled.scene_item_enabled else 'hidden'}."
|
f'Item [green]{item_name}[/green] in group [green]{group}[/green] in scene [green]{old_scene_name}[/green] is currently {"visible" if enabled.scene_item_enabled else "hidden"}.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If not in a parent group, just show the scene name
|
# If not in a parent group, just show the scene name
|
||||||
@ -339,16 +342,22 @@ def visible(
|
|||||||
# which is not the same as the scene name
|
# which is not the same as the scene name
|
||||||
# and is not needed in this case
|
# and is not needed in this case
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in scene '{scene_name}' is currently {'visible' if enabled.scene_item_enabled else 'hidden'}."
|
f'Item [green]{item_name}[/green] in scene [green]{scene_name}[/green] is currently {"visible" if enabled.scene_item_enabled else "hidden"}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@_validate_scene_name_and_item_name
|
|
||||||
@app.command('transform | t')
|
@app.command('transform | t')
|
||||||
def transform(
|
def transform(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
scene_name: str,
|
scene_name: Annotated[
|
||||||
item_name: str,
|
str, typer.Argument(..., show_default=False, help='Scene name the item is in')
|
||||||
|
],
|
||||||
|
item_name: Annotated[
|
||||||
|
str,
|
||||||
|
typer.Argument(
|
||||||
|
..., show_default=False, help='Item name to transform in the scene'
|
||||||
|
),
|
||||||
|
],
|
||||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||||
alignment: Annotated[
|
alignment: Annotated[
|
||||||
Optional[int], typer.Option(help='Alignment of the item in the scene')
|
Optional[int], typer.Option(help='Alignment of the item in the scene')
|
||||||
@ -397,16 +406,8 @@ def transform(
|
|||||||
] = None,
|
] = None,
|
||||||
):
|
):
|
||||||
"""Set the transform of an item in a scene."""
|
"""Set the transform of an item in a scene."""
|
||||||
if group:
|
if not _validate_sources(ctx, scene_name, item_name, group):
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, group):
|
raise typer.Exit(1)
|
||||||
err_console.print(
|
|
||||||
f"Parent group '{group}' not found in scene '{scene_name}'."
|
|
||||||
)
|
|
||||||
raise typer.Exit(1)
|
|
||||||
else:
|
|
||||||
if not validate.item_in_scene_item_list(ctx, scene_name, item_name):
|
|
||||||
err_console.print(f"Item '{item_name}' not found in scene '{scene_name}'.")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
old_scene_name = scene_name
|
old_scene_name = scene_name
|
||||||
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
scene_name, scene_item_id = _get_scene_name_and_item_id(
|
||||||
@ -457,7 +458,7 @@ def transform(
|
|||||||
|
|
||||||
if group:
|
if group:
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in group '{group}' in scene '{old_scene_name}' has been transformed."
|
f'Item [green]{item_name}[/green] in group [green]{group}[/green] in scene [green]{old_scene_name}[/green] has been transformed.'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If not in a parent group, just show the scene name
|
# If not in a parent group, just show the scene name
|
||||||
@ -465,5 +466,5 @@ def transform(
|
|||||||
# which is not the same as the scene name
|
# which is not the same as the scene name
|
||||||
# and is not needed in this case
|
# and is not needed in this case
|
||||||
out_console.print(
|
out_console.print(
|
||||||
f"Item '{item_name}' in scene '{scene_name}' has been transformed."
|
f'Item [green]{item_name}[/green] in scene [green]{scene_name}[/green] has been transformed.'
|
||||||
)
|
)
|
||||||
|
@ -79,14 +79,16 @@ def save(
|
|||||||
match e.code:
|
match e.code:
|
||||||
case 403:
|
case 403:
|
||||||
err_console.print(
|
err_console.print(
|
||||||
'The image format (file extension) must be included in the file name '
|
'The [yellow]image format[/yellow] (file extension) must be included in the file name, '
|
||||||
"for example: '/path/to/screenshot.png'.",
|
"for example: '/path/to/screenshot.png'.",
|
||||||
)
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
case 600:
|
case 600:
|
||||||
err_console.print(f"No source was found by the name of '{source_name}'")
|
err_console.print(
|
||||||
|
f'No source was found by the name of [yellow]{source_name}[/yellow]'
|
||||||
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
case _:
|
case _:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
out_console.print(f"Screenshot saved to [bold]'{output_path}'[/bold].")
|
out_console.print(f'Screenshot saved to [green]{output_path}[/green].')
|
||||||
|
@ -7,7 +7,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
|
@ -7,7 +7,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
|
@ -46,3 +46,9 @@ def profile_exists(ctx: typer.Context, profile_name: str) -> bool:
|
|||||||
"""Check if a profile exists."""
|
"""Check if a profile exists."""
|
||||||
resp = ctx.obj.get_profile_list()
|
resp = ctx.obj.get_profile_list()
|
||||||
return any(profile == profile_name for profile in resp.profiles)
|
return any(profile == profile_name for profile in resp.profiles)
|
||||||
|
|
||||||
|
|
||||||
|
def monitor_exists(ctx: typer.Context, monitor_index: int) -> bool:
|
||||||
|
"""Check if a monitor exists."""
|
||||||
|
resp = ctx.obj.get_monitor_list()
|
||||||
|
return any(monitor['monitorIndex'] == monitor_index for monitor in resp.monitors)
|
||||||
|
@ -7,7 +7,7 @@ from .alias import AliasGroup
|
|||||||
|
|
||||||
app = typer.Typer(cls=AliasGroup)
|
app = typer.Typer(cls=AliasGroup)
|
||||||
out_console = Console()
|
out_console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True, style='bold red')
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
|
@ -27,4 +27,4 @@ def test_filter_list_invalid_source():
|
|||||||
"""Test the filter list command with an invalid source."""
|
"""Test the filter list command with an invalid source."""
|
||||||
result = runner.invoke(app, ['filter', 'list', 'invalid_source'])
|
result = runner.invoke(app, ['filter', 'list', 'invalid_source'])
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "No source was found by the name of 'invalid_source'" in result.stderr
|
assert 'No source was found by the name of invalid_source' in result.stderr
|
||||||
|
@ -18,29 +18,29 @@ def test_group_show():
|
|||||||
"""Test the group show command."""
|
"""Test the group show command."""
|
||||||
result = runner.invoke(app, ['group', 'show', 'Scene', 'test_group'])
|
result = runner.invoke(app, ['group', 'show', 'Scene', 'test_group'])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "Group 'test_group' is now visible." in result.stdout
|
assert 'Group test_group is now visible.' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_group_toggle():
|
def test_group_toggle():
|
||||||
"""Test the group toggle command."""
|
"""Test the group toggle command."""
|
||||||
result = runner.invoke(app, ['group', 'status', 'Scene', 'test_group'])
|
result = runner.invoke(app, ['group', 'status', 'Scene', 'test_group'])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
enabled = "Group 'test_group' is now visible." in result.stdout
|
enabled = 'Group test_group is now visible.' in result.stdout
|
||||||
|
|
||||||
result = runner.invoke(app, ['group', 'toggle', 'Scene', 'test_group'])
|
result = runner.invoke(app, ['group', 'toggle', 'Scene', 'test_group'])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
if enabled:
|
if enabled:
|
||||||
assert "Group 'test_group' is now hidden." in result.stdout
|
assert 'Group test_group is now hidden.' in result.stdout
|
||||||
else:
|
else:
|
||||||
assert "Group 'test_group' is now visible." in result.stdout
|
assert 'Group test_group is now visible.' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_group_status():
|
def test_group_status():
|
||||||
"""Test the group status command."""
|
"""Test the group status command."""
|
||||||
result = runner.invoke(app, ['group', 'show', 'Scene', 'test_group'])
|
result = runner.invoke(app, ['group', 'show', 'Scene', 'test_group'])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "Group 'test_group' is now visible." in result.stdout
|
assert 'Group test_group is now visible.' in result.stdout
|
||||||
|
|
||||||
result = runner.invoke(app, ['group', 'status', 'Scene', 'test_group'])
|
result = runner.invoke(app, ['group', 'status', 'Scene', 'test_group'])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "Group 'test_group' is now visible." in result.stdout
|
assert 'Group test_group is now visible.' in result.stdout
|
||||||
|
@ -36,3 +36,10 @@ def test_scene_switch():
|
|||||||
result = runner.invoke(app, ['scene', 'switch', 'pytest_scene'])
|
result = runner.invoke(app, ['scene', 'switch', 'pytest_scene'])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert 'Switched to program scene: pytest_scene' in result.stdout
|
assert 'Switched to program scene: pytest_scene' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_scene_switch_invalid():
|
||||||
|
"""Test the scene switch command with an invalid scene."""
|
||||||
|
result = runner.invoke(app, ['scene', 'switch', 'non_existent_scene'])
|
||||||
|
assert result.exit_code != 0
|
||||||
|
assert 'Scene non_existent_scene not found' in result.stderr
|
||||||
|
@ -29,6 +29,6 @@ def test_sceneitem_transform():
|
|||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert (
|
assert (
|
||||||
"Item 'pytest_input_2' in scene 'pytest_scene' has been transformed"
|
'Item pytest_input_2 in scene pytest_scene has been transformed'
|
||||||
in result.stdout
|
in result.stdout
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user