|
| 1 | +Read and Control the System Volume |
| 2 | +================================== |
| 3 | + |
| 4 | +Unattended runs often need a known audio baseline — mute before a noisy batch, |
| 5 | +restore a level afterwards, or assert the current volume — but the framework |
| 6 | +only had the blind media-key steps (``volume up`` / ``down`` nudge by an unknown |
| 7 | +amount with no read-back). ``system_volume`` adds absolute, read-backable |
| 8 | +control of the default output device. |
| 9 | + |
| 10 | +* :func:`get_volume` / :func:`set_volume` / :func:`change_volume` — read and |
| 11 | + write the master level as an integer percent ``0..100`` (``set_volume`` and |
| 12 | + ``change_volume`` clamp to that range). |
| 13 | +* :func:`is_muted` / :func:`set_mute` / :func:`mute` / :func:`unmute` / |
| 14 | + :func:`toggle_mute` — read and write the mute flag. |
| 15 | + |
| 16 | +All logic (clamping, percent <-> scalar conversion, toggle) is pure and runs |
| 17 | +through an injectable :class:`VolumeDriver` seam, so it is fully testable without |
| 18 | +an audio device. The default driver drives the Windows Core Audio |
| 19 | +``IAudioEndpointVolume`` interface through the optional ``pycaw`` dependency |
| 20 | +(``pip install je_auto_control[audio]``); on a platform / install without it the |
| 21 | +default driver raises a clear error telling the caller to pass ``driver=``. |
| 22 | +Imports no ``PySide6``. |
| 23 | + |
| 24 | +Headless API |
| 25 | +------------ |
| 26 | + |
| 27 | +.. code-block:: python |
| 28 | +
|
| 29 | + from je_auto_control import ( |
| 30 | + get_volume, set_volume, change_volume, is_muted, mute, unmute, |
| 31 | + toggle_mute, |
| 32 | + ) |
| 33 | +
|
| 34 | + get_volume() # e.g. 65 — current master volume percent |
| 35 | + set_volume(30) # set to 30 %, returns 30 |
| 36 | + change_volume(-10) # lower by 10 %, returns the applied percent |
| 37 | + is_muted() # False |
| 38 | + mute() # True — silence the output |
| 39 | + unmute() # False — restore it |
| 40 | + toggle_mute() # flip and return the new state |
| 41 | +
|
| 42 | +For tests (or any non-Windows host) pass a ``driver`` — any object exposing |
| 43 | +``get_scalar`` / ``set_scalar`` / ``get_mute`` / ``set_mute`` over a ``0.0..1.0`` |
| 44 | +scalar: |
| 45 | + |
| 46 | +.. code-block:: python |
| 47 | +
|
| 48 | + class FakeVolume: |
| 49 | + def __init__(self, scalar=0.5, muted=False): |
| 50 | + self.scalar, self.muted = scalar, muted |
| 51 | + def get_scalar(self): return self.scalar |
| 52 | + def set_scalar(self, s): self.scalar = s |
| 53 | + def get_mute(self): return self.muted |
| 54 | + def set_mute(self, m): self.muted = m |
| 55 | +
|
| 56 | + drv = FakeVolume() |
| 57 | + set_volume(73, driver=drv) # 73, drv.scalar == 0.73 |
| 58 | +
|
| 59 | +Executor commands |
| 60 | +----------------- |
| 61 | + |
| 62 | +``AC_get_volume`` (→ ``{volume, muted}``), ``AC_set_volume`` (``level`` → |
| 63 | +``{volume}``), ``AC_change_volume`` (``delta`` → ``{volume}``), ``AC_set_mute`` |
| 64 | +(``muted`` → ``{muted}``) and ``AC_toggle_mute`` (→ ``{muted}``). They are |
| 65 | +exposed as the matching ``ac_*`` MCP tools (the read is read-only, the writes |
| 66 | +side-effect-only) and as Script Builder commands under **Shell**. The executor |
| 67 | +and MCP layers use the default OS driver, so they require ``pycaw`` on Windows. |
0 commit comments