mirror of
https://github.com/onyx-and-iris/obsws-cli.git
synced 2025-08-07 12:11:53 +00:00
Compare commits
19 Commits
51a4a60aa6
...
ab71414d27
Author | SHA1 | Date | |
---|---|---|---|
ab71414d27 | |||
ab0679174b | |||
37781f6de7 | |||
5e84becc57 | |||
b8dd94ccbc | |||
657fa84ea3 | |||
59f52417cd | |||
2d351e00b5 | |||
5f606b42d0 | |||
ae4ec542aa | |||
6ac63aa5e8 | |||
df90614352 | |||
d8e89285cc | |||
3e2a1e4663 | |||
723d79e306 | |||
868d40ec8d | |||
30f19f4d87 | |||
5b9dd97167 | |||
d41ad994b7 |
12
CHANGELOG.md
12
CHANGELOG.md
@ -5,6 +5,18 @@ 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
# [0.16.1] - 2025-06-04
|
||||
|
||||
### Added
|
||||
|
||||
- screenshot save command, see [Screenshot](https://github.com/onyx-and-iris/obsws-cli/tree/main?tab=readme-ov-file#screenshot)
|
||||
|
||||
### Changed
|
||||
|
||||
- filter list:
|
||||
- source_name arg is now optional, it defaults to the current scene.
|
||||
- default values are printed if unmodified.
|
||||
|
||||
# [0.15.0] - 2025-06-02
|
||||
|
||||
### Added
|
||||
|
21
README.md
21
README.md
@ -519,7 +519,10 @@ obsws-cli hotkey trigger-sequence OBS_KEY_F1 --shift --ctrl
|
||||
#### Filter
|
||||
|
||||
- list: List filters for a source.
|
||||
|
||||
*optional*
|
||||
- args: <source_name>
|
||||
- defaults to current scene
|
||||
|
||||
```console
|
||||
obsws-cli filter list "Mic/Aux"
|
||||
@ -580,6 +583,24 @@ obsws-cli projector open --monitor-index=1 "test_scene"
|
||||
obsws-cli projector open --monitor-index=1 "test_group"
|
||||
```
|
||||
|
||||
#### Screenshot
|
||||
|
||||
- save: Take a screenshot and save it to a file.
|
||||
- flags:
|
||||
|
||||
*optional*
|
||||
- --width:
|
||||
- defaults to 1920
|
||||
- --height:
|
||||
- defaults to 1080
|
||||
- --quality:
|
||||
- defaults to -1
|
||||
|
||||
- args: <source_name> <output_path>
|
||||
|
||||
```console
|
||||
obsws-cli screenshot save --width=2560 --height=1440 "Scene" "C:\Users\me\Videos\screenshot.png"
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2025-present onyx-and-iris <code@onyxandiris.online>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
__version__ = "0.15.2"
|
||||
__version__ = "0.16.2"
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Command line interface for the OBS WebSocket API."""
|
||||
|
||||
import importlib
|
||||
from typing import Annotated
|
||||
|
||||
import obsws_python as obsws
|
||||
@ -8,43 +9,29 @@ from rich.console import Console
|
||||
|
||||
from obsws_cli.__about__ import __version__ as obsws_cli_version
|
||||
|
||||
from . import (
|
||||
filter,
|
||||
group,
|
||||
hotkey,
|
||||
input,
|
||||
profile,
|
||||
projector,
|
||||
record,
|
||||
replaybuffer,
|
||||
scene,
|
||||
scenecollection,
|
||||
sceneitem,
|
||||
settings,
|
||||
stream,
|
||||
studiomode,
|
||||
virtualcam,
|
||||
)
|
||||
from . import settings
|
||||
from .alias import AliasGroup
|
||||
|
||||
app = typer.Typer(cls=AliasGroup)
|
||||
for module in (
|
||||
filter,
|
||||
group,
|
||||
hotkey,
|
||||
input,
|
||||
projector,
|
||||
profile,
|
||||
record,
|
||||
replaybuffer,
|
||||
scene,
|
||||
scenecollection,
|
||||
sceneitem,
|
||||
stream,
|
||||
studiomode,
|
||||
virtualcam,
|
||||
for sub_typer in (
|
||||
'filter',
|
||||
'group',
|
||||
'hotkey',
|
||||
'input',
|
||||
'profile',
|
||||
'projector',
|
||||
'record',
|
||||
'replaybuffer',
|
||||
'scene',
|
||||
'scenecollection',
|
||||
'sceneitem',
|
||||
'screenshot',
|
||||
'stream',
|
||||
'studiomode',
|
||||
'virtualcam',
|
||||
):
|
||||
app.add_typer(module.app, name=module.__name__.split('.')[-1])
|
||||
module = importlib.import_module(f'.{sub_typer}', package=__package__)
|
||||
app.add_typer(module.app, name=sub_typer)
|
||||
|
||||
out_console = Console()
|
||||
err_console = Console(stderr=True)
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""module containing commands for manipulating filters in scenes."""
|
||||
|
||||
from typing import Annotated, Optional
|
||||
|
||||
import obsws_python as obsws
|
||||
import typer
|
||||
from rich.console import Console
|
||||
@ -19,8 +21,20 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(ctx: typer.Context, source_name: str):
|
||||
def list_(
|
||||
ctx: typer.Context,
|
||||
source_name: Annotated[
|
||||
Optional[str],
|
||||
typer.Argument(
|
||||
show_default='The current scene',
|
||||
help='The source to list filters for',
|
||||
),
|
||||
] = None,
|
||||
):
|
||||
"""List filters for a source."""
|
||||
if not source_name:
|
||||
source_name = ctx.obj.get_current_program_scene().scene_name
|
||||
|
||||
try:
|
||||
resp = ctx.obj.get_source_filter_list(source_name)
|
||||
except obsws.error.OBSSDKRequestError as e:
|
||||
@ -44,6 +58,9 @@ def list(ctx: typer.Context, source_name: str):
|
||||
)
|
||||
|
||||
for filter in resp.filters:
|
||||
resp = ctx.obj.get_source_filter_default_settings(filter['filterKind'])
|
||||
settings = resp.default_filter_settings | filter['filterSettings']
|
||||
|
||||
table.add_row(
|
||||
filter['filterName'],
|
||||
util.snakecase_to_titlecase(filter['filterKind']),
|
||||
@ -51,7 +68,7 @@ def list(ctx: typer.Context, source_name: str):
|
||||
'\n'.join(
|
||||
[
|
||||
f'{util.snakecase_to_titlecase(k):<20} {v:>10}'
|
||||
for k, v in filter['filterSettings'].items()
|
||||
for k, v in settings.items()
|
||||
]
|
||||
),
|
||||
)
|
||||
@ -68,8 +85,18 @@ def _get_filter_enabled(ctx: typer.Context, source_name: str, filter_name: str):
|
||||
@app.command('enable | on')
|
||||
def enable(
|
||||
ctx: typer.Context,
|
||||
source_name: str = typer.Argument(..., help='The source to enable the filter for'),
|
||||
filter_name: str = typer.Argument(..., help='The name of the filter to enable'),
|
||||
source_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The source to enable the filter for'
|
||||
),
|
||||
],
|
||||
filter_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The name of the filter to enable'
|
||||
),
|
||||
],
|
||||
):
|
||||
"""Enable a filter for a source."""
|
||||
if _get_filter_enabled(ctx, source_name, filter_name):
|
||||
@ -85,8 +112,18 @@ def enable(
|
||||
@app.command('disable | off')
|
||||
def disable(
|
||||
ctx: typer.Context,
|
||||
source_name: str = typer.Argument(..., help='The source to disable the filter for'),
|
||||
filter_name: str = typer.Argument(..., help='The name of the filter to disable'),
|
||||
source_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The source to disable the filter for'
|
||||
),
|
||||
],
|
||||
filter_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The name of the filter to disable'
|
||||
),
|
||||
],
|
||||
):
|
||||
"""Disable a filter for a source."""
|
||||
if not _get_filter_enabled(ctx, source_name, filter_name):
|
||||
@ -102,8 +139,18 @@ def disable(
|
||||
@app.command('toggle | tg')
|
||||
def toggle(
|
||||
ctx: typer.Context,
|
||||
source_name: str = typer.Argument(..., help='The source to toggle the filter for'),
|
||||
filter_name: str = typer.Argument(..., help='The name of the filter to toggle'),
|
||||
source_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The source to toggle the filter for'
|
||||
),
|
||||
],
|
||||
filter_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The name of the filter to toggle'
|
||||
),
|
||||
],
|
||||
):
|
||||
"""Toggle a filter for a source."""
|
||||
is_enabled = _get_filter_enabled(ctx, source_name, filter_name)
|
||||
@ -119,12 +166,18 @@ def toggle(
|
||||
@app.command('status | ss')
|
||||
def status(
|
||||
ctx: typer.Context,
|
||||
source_name: str = typer.Argument(
|
||||
..., help='The source to get the filter status for'
|
||||
source_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The source to get the filter status for'
|
||||
),
|
||||
filter_name: str = typer.Argument(
|
||||
..., help='The name of the filter to get the status for'
|
||||
],
|
||||
filter_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='The name of the filter to get the status for'
|
||||
),
|
||||
],
|
||||
):
|
||||
"""Get the status of a filter for a source."""
|
||||
is_enabled = _get_filter_enabled(ctx, source_name, filter_name)
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""module containing commands for manipulating groups in scenes."""
|
||||
|
||||
from typing import Annotated, Optional
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
@ -19,11 +21,15 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(
|
||||
def list_(
|
||||
ctx: typer.Context,
|
||||
scene_name: str = typer.Argument(
|
||||
None, help='Scene name (optional, defaults to current scene)'
|
||||
scene_name: Annotated[
|
||||
Optional[str],
|
||||
typer.Argument(
|
||||
show_default='The current scene',
|
||||
help='Scene name to list groups for',
|
||||
),
|
||||
] = None,
|
||||
):
|
||||
"""List groups in a scene."""
|
||||
if not scene_name:
|
||||
@ -75,7 +81,16 @@ def _get_group(group_name: str, resp: DataclassProtocol) -> dict | None:
|
||||
|
||||
|
||||
@app.command('show | sh')
|
||||
def show(ctx: typer.Context, scene_name: str, group_name: str):
|
||||
def show(
|
||||
ctx: typer.Context,
|
||||
scene_name: Annotated[
|
||||
str,
|
||||
typer.Argument(..., show_default=False, help='Scene name the group is in'),
|
||||
],
|
||||
group_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Group name to show')
|
||||
],
|
||||
):
|
||||
"""Show a group in a scene."""
|
||||
if not validate.scene_in_scenes(ctx, scene_name):
|
||||
err_console.print(f"Scene '{scene_name}' not found.")
|
||||
@ -96,7 +111,15 @@ def show(ctx: typer.Context, scene_name: str, group_name: str):
|
||||
|
||||
|
||||
@app.command('hide | h')
|
||||
def hide(ctx: typer.Context, scene_name: str, group_name: str):
|
||||
def hide(
|
||||
ctx: typer.Context,
|
||||
scene_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Scene name the group is in')
|
||||
],
|
||||
group_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Group name to hide')
|
||||
],
|
||||
):
|
||||
"""Hide a group in a scene."""
|
||||
if not validate.scene_in_scenes(ctx, scene_name):
|
||||
err_console.print(f"Scene '{scene_name}' not found.")
|
||||
@ -117,7 +140,15 @@ def hide(ctx: typer.Context, scene_name: str, group_name: str):
|
||||
|
||||
|
||||
@app.command('toggle | tg')
|
||||
def toggle(ctx: typer.Context, scene_name: str, group_name: str):
|
||||
def toggle(
|
||||
ctx: typer.Context,
|
||||
scene_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Scene name the group is in')
|
||||
],
|
||||
group_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Group name to toggle')
|
||||
],
|
||||
):
|
||||
"""Toggle a group in a scene."""
|
||||
if not validate.scene_in_scenes(ctx, scene_name):
|
||||
err_console.print(f"Scene '{scene_name}' not found.")
|
||||
@ -142,7 +173,15 @@ def toggle(ctx: typer.Context, scene_name: str, group_name: str):
|
||||
|
||||
|
||||
@app.command('status | ss')
|
||||
def status(ctx: typer.Context, scene_name: str, group_name: str):
|
||||
def status(
|
||||
ctx: typer.Context,
|
||||
scene_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Scene name the group is in')
|
||||
],
|
||||
group_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Group name to check status')
|
||||
],
|
||||
):
|
||||
"""Get the status of a group in a scene."""
|
||||
if not validate.scene_in_scenes(ctx, scene_name):
|
||||
err_console.print(f"Scene '{scene_name}' not found.")
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""module containing commands for hotkey management."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
@ -17,7 +19,7 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(
|
||||
def list_(
|
||||
ctx: typer.Context,
|
||||
):
|
||||
"""List all hotkeys."""
|
||||
@ -35,7 +37,9 @@ def list(
|
||||
@app.command('trigger | tr')
|
||||
def trigger(
|
||||
ctx: typer.Context,
|
||||
hotkey: str = typer.Argument(..., help='The hotkey to trigger'),
|
||||
hotkey: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='The hotkey to trigger')
|
||||
],
|
||||
):
|
||||
"""Trigger a hotkey by name."""
|
||||
ctx.obj.trigger_hotkey_by_name(hotkey)
|
||||
@ -44,14 +48,26 @@ def trigger(
|
||||
@app.command('trigger-sequence | trs')
|
||||
def trigger_sequence(
|
||||
ctx: typer.Context,
|
||||
shift: bool = typer.Option(False, help='Press shift when triggering the hotkey'),
|
||||
ctrl: bool = typer.Option(False, help='Press control when triggering the hotkey'),
|
||||
alt: bool = typer.Option(False, help='Press alt when triggering the hotkey'),
|
||||
cmd: bool = typer.Option(False, help='Press cmd when triggering the hotkey'),
|
||||
key_id: str = typer.Argument(
|
||||
key_id: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
...,
|
||||
show_default=False,
|
||||
help='The OBS key ID to trigger, see https://github.com/onyx-and-iris/obsws-cli?tab=readme-ov-file#hotkey for more info',
|
||||
),
|
||||
],
|
||||
shift: Annotated[
|
||||
bool, typer.Option(..., help='Press shift when triggering the hotkey')
|
||||
] = False,
|
||||
ctrl: Annotated[
|
||||
bool, typer.Option(..., help='Press control when triggering the hotkey')
|
||||
] = False,
|
||||
alt: Annotated[
|
||||
bool, typer.Option(..., help='Press alt when triggering the hotkey')
|
||||
] = False,
|
||||
cmd: Annotated[
|
||||
bool, typer.Option(..., help='Press cmd when triggering the hotkey')
|
||||
] = False,
|
||||
):
|
||||
"""Trigger a hotkey by sequence."""
|
||||
ctx.obj.trigger_hotkey_by_key_sequence(key_id, shift, ctrl, alt, cmd)
|
||||
|
@ -20,7 +20,7 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(
|
||||
def list_(
|
||||
ctx: typer.Context,
|
||||
input: Annotated[bool, typer.Option(help='Filter by input type.')] = False,
|
||||
output: Annotated[bool, typer.Option(help='Filter by output type.')] = False,
|
||||
@ -67,7 +67,12 @@ def list(
|
||||
|
||||
|
||||
@app.command('mute | m')
|
||||
def mute(ctx: typer.Context, input_name: str):
|
||||
def mute(
|
||||
ctx: typer.Context,
|
||||
input_name: Annotated[
|
||||
str, typer.Argument(..., show_default=False, help='Name of the input to mute.')
|
||||
],
|
||||
):
|
||||
"""Mute an input."""
|
||||
if not validate.input_in_inputs(ctx, input_name):
|
||||
err_console.print(f"Input '{input_name}' not found.")
|
||||
@ -82,7 +87,13 @@ def mute(ctx: typer.Context, input_name: str):
|
||||
|
||||
|
||||
@app.command('unmute | um')
|
||||
def unmute(ctx: typer.Context, input_name: str):
|
||||
def unmute(
|
||||
ctx: typer.Context,
|
||||
input_name: Annotated[
|
||||
str,
|
||||
typer.Argument(..., show_default=False, help='Name of the input to unmute.'),
|
||||
],
|
||||
):
|
||||
"""Unmute an input."""
|
||||
if not validate.input_in_inputs(ctx, input_name):
|
||||
err_console.print(f"Input '{input_name}' not found.")
|
||||
@ -97,7 +108,13 @@ def unmute(ctx: typer.Context, input_name: str):
|
||||
|
||||
|
||||
@app.command('toggle | tg')
|
||||
def toggle(ctx: typer.Context, input_name: str):
|
||||
def toggle(
|
||||
ctx: typer.Context,
|
||||
input_name: Annotated[
|
||||
str,
|
||||
typer.Argument(..., show_default=False, help='Name of the input to toggle.'),
|
||||
],
|
||||
):
|
||||
"""Toggle an input."""
|
||||
if not validate.input_in_inputs(ctx, input_name):
|
||||
err_console.print(f"Input '{input_name}' not found.")
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""module containing commands for manipulating profiles in OBS."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
@ -18,7 +20,7 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(ctx: typer.Context):
|
||||
def list_(ctx: typer.Context):
|
||||
"""List profiles."""
|
||||
resp = ctx.obj.get_profile_list()
|
||||
|
||||
@ -47,7 +49,15 @@ def current(ctx: typer.Context):
|
||||
|
||||
|
||||
@app.command('switch | set')
|
||||
def switch(ctx: typer.Context, profile_name: str):
|
||||
def switch(
|
||||
ctx: typer.Context,
|
||||
profile_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
..., show_default=False, help='Name of the profile to switch to'
|
||||
),
|
||||
],
|
||||
):
|
||||
"""Switch to a profile."""
|
||||
if not validate.profile_exists(ctx, profile_name):
|
||||
err_console.print(f"Profile '{profile_name}' not found.")
|
||||
@ -63,7 +73,13 @@ def switch(ctx: typer.Context, profile_name: str):
|
||||
|
||||
|
||||
@app.command('create | new')
|
||||
def create(ctx: typer.Context, profile_name: str):
|
||||
def create(
|
||||
ctx: typer.Context,
|
||||
profile_name: Annotated[
|
||||
str,
|
||||
typer.Argument(..., show_default=False, help='Name of the profile to create.'),
|
||||
],
|
||||
):
|
||||
"""Create a new profile."""
|
||||
if validate.profile_exists(ctx, profile_name):
|
||||
err_console.print(f"Profile '{profile_name}' already exists.")
|
||||
@ -74,7 +90,13 @@ def create(ctx: typer.Context, profile_name: str):
|
||||
|
||||
|
||||
@app.command('remove | rm')
|
||||
def remove(ctx: typer.Context, profile_name: str):
|
||||
def remove(
|
||||
ctx: typer.Context,
|
||||
profile_name: Annotated[
|
||||
str,
|
||||
typer.Argument(..., show_default=False, help='Name of the profile to remove.'),
|
||||
],
|
||||
):
|
||||
"""Remove a profile."""
|
||||
if not validate.profile_exists(ctx, profile_name):
|
||||
err_console.print(f"Profile '{profile_name}' not found.")
|
||||
|
@ -52,7 +52,8 @@ def open(
|
||||
source_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
help='Name of the source to project. (optional, defaults to current scene)'
|
||||
show_default='The current scene',
|
||||
help='Name of the source to project.',
|
||||
),
|
||||
] = '',
|
||||
):
|
||||
|
@ -20,7 +20,7 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(ctx: typer.Context):
|
||||
def list_(ctx: typer.Context):
|
||||
"""List all scenes."""
|
||||
resp = ctx.obj.get_scene_list()
|
||||
scenes = (
|
||||
@ -64,7 +64,9 @@ def current(
|
||||
@app.command('switch | set')
|
||||
def switch(
|
||||
ctx: typer.Context,
|
||||
scene_name: str,
|
||||
scene_name: Annotated[
|
||||
str, typer.Argument(..., help='Name of the scene to switch to')
|
||||
],
|
||||
preview: Annotated[
|
||||
bool,
|
||||
typer.Option(help='Switch to the preview scene instead of the program scene'),
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""module containing commands for manipulating scene collections."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
@ -18,7 +20,7 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(ctx: typer.Context):
|
||||
def list_(ctx: typer.Context):
|
||||
"""List all scene collections."""
|
||||
resp = ctx.obj.get_scene_collection_list()
|
||||
|
||||
@ -39,7 +41,12 @@ def current(ctx: typer.Context):
|
||||
|
||||
|
||||
@app.command('switch | set')
|
||||
def switch(ctx: typer.Context, scene_collection_name: str):
|
||||
def switch(
|
||||
ctx: typer.Context,
|
||||
scene_collection_name: Annotated[
|
||||
str, typer.Argument(..., help='Name of the scene collection to switch to')
|
||||
],
|
||||
):
|
||||
"""Switch to a scene collection."""
|
||||
if not validate.scene_collection_in_scene_collections(ctx, scene_collection_name):
|
||||
err_console.print(f"Scene collection '{scene_collection_name}' not found.")
|
||||
@ -59,7 +66,12 @@ def switch(ctx: typer.Context, scene_collection_name: str):
|
||||
|
||||
|
||||
@app.command('create | new')
|
||||
def create(ctx: typer.Context, scene_collection_name: str):
|
||||
def create(
|
||||
ctx: typer.Context,
|
||||
scene_collection_name: Annotated[
|
||||
str, typer.Argument(..., help='Name of the scene collection to create')
|
||||
],
|
||||
):
|
||||
"""Create a new scene collection."""
|
||||
if validate.scene_collection_in_scene_collections(ctx, scene_collection_name):
|
||||
err_console.print(f"Scene collection '{scene_collection_name}' already exists.")
|
||||
|
@ -22,11 +22,15 @@ def main():
|
||||
|
||||
|
||||
@app.command('list | ls')
|
||||
def list(
|
||||
def list_(
|
||||
ctx: typer.Context,
|
||||
scene_name: str = typer.Argument(
|
||||
None, help='Scene name (optional, defaults to current scene)'
|
||||
scene_name: Annotated[
|
||||
Optional[str],
|
||||
typer.Argument(
|
||||
show_default='The current scene',
|
||||
help='Scene name to list items for',
|
||||
),
|
||||
] = None,
|
||||
):
|
||||
"""List all items in a scene."""
|
||||
if not scene_name:
|
||||
@ -163,8 +167,10 @@ def _get_scene_name_and_item_id(
|
||||
@app.command('show | sh')
|
||||
def show(
|
||||
ctx: typer.Context,
|
||||
scene_name: str,
|
||||
item_name: str,
|
||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
||||
item_name: Annotated[
|
||||
str, typer.Argument(..., help='Item name to show in the scene')
|
||||
],
|
||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||
):
|
||||
"""Show an item in a scene."""
|
||||
@ -194,8 +200,10 @@ def show(
|
||||
@app.command('hide | h')
|
||||
def hide(
|
||||
ctx: typer.Context,
|
||||
scene_name: str,
|
||||
item_name: str,
|
||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
||||
item_name: Annotated[
|
||||
str, typer.Argument(..., help='Item name to hide in the scene')
|
||||
],
|
||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||
):
|
||||
"""Hide an item in a scene."""
|
||||
@ -227,8 +235,10 @@ def hide(
|
||||
@app.command('toggle | tg')
|
||||
def toggle(
|
||||
ctx: typer.Context,
|
||||
scene_name: str,
|
||||
item_name: str,
|
||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
||||
item_name: Annotated[
|
||||
str, typer.Argument(..., help='Item name to toggle in the scene')
|
||||
],
|
||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||
):
|
||||
"""Toggle an item in a scene."""
|
||||
@ -291,8 +301,10 @@ def toggle(
|
||||
@app.command('visible | v')
|
||||
def visible(
|
||||
ctx: typer.Context,
|
||||
scene_name: str,
|
||||
item_name: str,
|
||||
scene_name: Annotated[str, typer.Argument(..., help='Scene name the item is in')],
|
||||
item_name: Annotated[
|
||||
str, typer.Argument(..., help='Item name to check visibility in the scene')
|
||||
],
|
||||
group: Annotated[Optional[str], typer.Option(help='Parent group name')] = None,
|
||||
):
|
||||
"""Check if an item in a scene is visible."""
|
||||
|
92
obsws_cli/screenshot.py
Normal file
92
obsws_cli/screenshot.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""module for taking screenshots using OBS WebSocket API."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
import obsws_python as obsws
|
||||
import typer
|
||||
from rich.console import Console
|
||||
|
||||
from .alias import AliasGroup
|
||||
|
||||
app = typer.Typer(cls=AliasGroup)
|
||||
out_console = Console()
|
||||
err_console = Console(
|
||||
stderr=True,
|
||||
)
|
||||
|
||||
|
||||
@app.callback()
|
||||
def main():
|
||||
"""Take screenshots using OBS."""
|
||||
|
||||
|
||||
@app.command('save | sv')
|
||||
def save(
|
||||
ctx: typer.Context,
|
||||
source_name: Annotated[
|
||||
str,
|
||||
typer.Argument(
|
||||
...,
|
||||
show_default=False,
|
||||
help='Name of the source to take a screenshot of.',
|
||||
),
|
||||
],
|
||||
output_path: Annotated[
|
||||
Path,
|
||||
# Since the CLI and OBS may be running on different platforms,
|
||||
# we won't validate the path here.
|
||||
typer.Argument(
|
||||
...,
|
||||
show_default=False,
|
||||
file_okay=True,
|
||||
dir_okay=False,
|
||||
help='Path to save the screenshot (must include file name and extension).',
|
||||
),
|
||||
],
|
||||
width: Annotated[
|
||||
float,
|
||||
typer.Option(
|
||||
help='Width of the screenshot.',
|
||||
),
|
||||
] = 1920,
|
||||
height: Annotated[
|
||||
float,
|
||||
typer.Option(
|
||||
help='Height of the screenshot.',
|
||||
),
|
||||
] = 1080,
|
||||
quality: Annotated[
|
||||
float,
|
||||
typer.Option(
|
||||
min=-1,
|
||||
max=100,
|
||||
help='Quality of the screenshot.',
|
||||
),
|
||||
] = -1,
|
||||
):
|
||||
"""Take a screenshot and save it to a file."""
|
||||
try:
|
||||
ctx.obj.save_source_screenshot(
|
||||
name=source_name,
|
||||
img_format=output_path.suffix.lstrip('.').lower(),
|
||||
file_path=str(output_path),
|
||||
width=width,
|
||||
height=height,
|
||||
quality=quality,
|
||||
)
|
||||
except obsws.error.OBSSDKRequestError as e:
|
||||
match e.code:
|
||||
case 403:
|
||||
err_console.print(
|
||||
'The image format (file extension) must be included in the file name '
|
||||
"for example: '/path/to/screenshot.png'.",
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
case 600:
|
||||
err_console.print(f"No source was found by the name of '{source_name}'")
|
||||
raise typer.Exit(1)
|
||||
case _:
|
||||
raise
|
||||
|
||||
out_console.print(f"Screenshot saved to [bold]'{output_path}'[/bold].")
|
Loading…
x
Reference in New Issue
Block a user