Compare commits

..

12 Commits

Author SHA1 Message Date
1a1fbf1da1 sort input list by input name
patch bump
2025-06-07 00:24:48 +01:00
fd2baf3350 remove no filter line 2025-06-07 00:06:53 +01:00
5334879ba9 patch bump 2025-06-06 23:27:45 +01:00
77dbe52ae6 upd input list to include new options 2025-06-06 23:27:31 +01:00
1ff610410a use tuples as records to build the tables
add --fempg and --vlc options to filter list

add Muted column to list table
2025-06-06 23:27:16 +01:00
cd7614bfd6 use tuples as records to build the tables 2025-06-06 23:26:33 +01:00
74503f17e0 upd console colouring
patch bump
2025-06-06 22:27:17 +01:00
32bc4277f2 add 0.16.5 to CHANGELOG 2025-06-06 21:09:33 +01:00
21f1b5e1bb add note about disabling console colouring to README 2025-06-06 20:58:28 +01:00
434f8c0e0c add monitor validate function
upd tests to match console colour changes
2025-06-06 20:58:15 +01:00
81518a14ea error messages now have style bold red
error highlights are now yellow

normal highlights are now green

_validate_scene_name_and_item_name renamed to _validate_sources

its now a normal function and not a decorator

it also returns bool instead of raising typer.Exit()

patch bump
2025-06-06 20:55:35 +01:00
ddb92bb317 upd console colouring
error messages now have style `bold red`
error highlights are now yellow

normal highlights are now green

patch bump
2025-06-06 20:53:35 +01:00
24 changed files with 329 additions and 212 deletions

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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):

View File

@ -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]'
)

View File

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

View File

@ -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()

View File

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

View File

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

View File

@ -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)

View File

@ -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]'
) )

View File

@ -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()

View File

@ -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]')

View File

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

View File

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

View File

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

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
) )