changeset 1:04c8a1b3976b

volume r/w on linux+osx
author drewp@bigasterisk.com
date Fri, 03 Mar 2023 21:24:53 -0800
parents 05bdded21d38
children 0ecb388a0b90
files pdm.lock pyproject.toml racc.py volume_linux.py volume_osx.py
diffstat 5 files changed, 93 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/pdm.lock	Fri Mar 03 18:33:19 2023 -0800
+++ b/pdm.lock	Fri Mar 03 21:24:53 2023 -0800
@@ -53,6 +53,14 @@
 summary = "Internationalized Domain Names in Applications (IDNA)"
 
 [[package]]
+name = "osascript"
+version = "2020.12.3"
+summary = ""
+dependencies = [
+    "runcmd",
+]
+
+[[package]]
 name = "prometheus-client"
 version = "0.16.0"
 requires_python = ">=3.6"
@@ -65,12 +73,34 @@
 summary = "Cross-platform lib for process and system monitoring in Python."
 
 [[package]]
+name = "pulsectl"
+version = "22.3.2"
+summary = "Python high-level interface and ctypes-based bindings for PulseAudio (libpulse)"
+
+[[package]]
+name = "pulsectl-asyncio"
+version = "1.0.0"
+requires_python = ">=3.6"
+summary = "Asyncio frontend for the pulsectl Python bindings of libpulse"
+dependencies = [
+    "pulsectl<=22.3.2,>=21.10.2",
+]
+
+[[package]]
 name = "pycparser"
 version = "2.21"
 requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 summary = "C parser in Python"
 
 [[package]]
+name = "runcmd"
+version = "2020.12.3"
+summary = ""
+dependencies = [
+    "psutil",
+]
+
+[[package]]
 name = "sniffio"
 version = "1.3.0"
 requires_python = ">=3.7"
@@ -114,7 +144,7 @@
 
 [metadata]
 lock_version = "4.0"
-content_hash = "sha256:37aab9383d44d97e9e9296176d348df2527022c22eda65680036ee05e71d5bae"
+content_hash = "sha256:154a0840707c5c174819c62d9d6fe17c300e92fada090c6125686011e9ca2bc8"
 
 [metadata.files]
 "anyio 3.6.2" = [
@@ -206,6 +236,9 @@
     {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
     {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
 ]
+"osascript 2020.12.3" = [
+    {url = "https://files.pythonhosted.org/packages/15/21/36f43fde613eefa23e8f569a4020964c5417cd8fad7b069383717d2a57b0/osascript-2020.12.3.tar.gz", hash = "sha256:36d3f30fb6c43c868c70d1fb06e4c970844a5cbe0a481b9fbd5059398916e069"},
+]
 "prometheus-client 0.16.0" = [
     {url = "https://files.pythonhosted.org/packages/5b/8e/6a546e439b4366ab9eab0a736876eb1e1916dd93b4a1fa560ef711d24f8c/prometheus_client-0.16.0-py3-none-any.whl", hash = "sha256:0836af6eb2c8f4fed712b2f279f6c0a8bbab29f9f4aa15276b91c7cb0d1616ab"},
     {url = "https://files.pythonhosted.org/packages/d0/55/9e34c73e1e490b105b4cd13d08497b1f7cb086a260e4161b7b7c2928b196/prometheus_client-0.16.0.tar.gz", hash = "sha256:a03e35b359f14dd1630898543e2120addfdeacd1a6069c1367ae90fd93ad3f48"},
@@ -226,10 +259,21 @@
     {url = "https://files.pythonhosted.org/packages/a5/73/35cea01aad1baf901c915dc95ea33a2f271c8ff8cf2f1c73b7f591f1bdf1/psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
     {url = "https://files.pythonhosted.org/packages/ec/be/b8df2071eda861e65a1b2cec35770bb1f4523737e84a10aa41c53e39e9bc/psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
 ]
+"pulsectl 22.3.2" = [
+    {url = "https://files.pythonhosted.org/packages/38/b5/7fb041271e9db6ad54a74eecbc84f8c9a9faf696c267b63dfc9fbbf63d49/pulsectl-22.3.2.tar.gz", hash = "sha256:cc174e1ceebd4e6222c5b78f4f415f12e80753c9ab75ab354159b4cb5950b084"},
+    {url = "https://files.pythonhosted.org/packages/e7/dc/53454b4d9f52f53aa5d95b0c5aa1adaf80e324f760e9086db4e2de2dab24/pulsectl-22.3.2-py2.py3-none-any.whl", hash = "sha256:6fded96a8e6a9ac6c387ed3a9bdb1afb20d80256b0c9df4b98b58461d74ba0c4"},
+]
+"pulsectl-asyncio 1.0.0" = [
+    {url = "https://files.pythonhosted.org/packages/02/98/b5618e70db2279c9d7f604283df21eadd0d0a5e878bc5a0b9fd85e14fc96/pulsectl-asyncio-1.0.0.tar.gz", hash = "sha256:46ef1573a17fb064196e8d8f0d9031a9377f717cde26769d97959ce4b9395af0"},
+    {url = "https://files.pythonhosted.org/packages/41/4d/b973e74ed465735fc230089cc9167152f692d7f6221ffccf35a3b3156e7f/pulsectl_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:5842fe428da9ede62077469f15dedd7c19f1e0085a641f22e2266449fd394dd2"},
+]
 "pycparser 2.21" = [
     {url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
     {url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
 ]
+"runcmd 2020.12.3" = [
+    {url = "https://files.pythonhosted.org/packages/41/b7/08c18bc2944c0443aa77672e5610e861c6946f93f247bef0aa8d7737b248/runcmd-2020.12.3.tar.gz", hash = "sha256:1f2652c2e51404bb0faaa83610f23fdafd4c246b46e361a5dd6d4334d013fc24"},
+]
 "sniffio 1.3.0" = [
     {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
     {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
--- a/pyproject.toml	Fri Mar 03 18:33:19 2023 -0800
+++ b/pyproject.toml	Fri Mar 03 21:24:53 2023 -0800
@@ -12,6 +12,8 @@
     "background-loop>=1.7.0",
     "starlette-exporter>=0.15.1",
     "xprintidle>=0.2",
+    "pulsectl-asyncio>=1.0.0",
+    "osascript>=2020.12.3",
 ]
 requires-python = ">=3.10"
 license = {text = "MIT"}
--- a/racc.py	Fri Mar 03 18:33:19 2023 -0800
+++ b/racc.py	Fri Mar 03 21:24:53 2023 -0800
@@ -16,8 +16,10 @@
 import sys
 if sys.implementation._multiarch == 'darwin':
     import idle_osx as idle
+    import volume_osx as volume
 elif 'linux' in sys.implementation._multiarch:
     import idle_linux as idle
+    import volume_linux as volume
 else:
     raise NotImplementedError(repr(sys.implementation))
 
@@ -61,8 +63,9 @@
     RACC_IDLE.labels(host=hostname).set(idle.get_idle_seconds())
 
 
-def root(req: Request) -> HTMLResponse:
-    return HTMLResponse(f'''controls for {hostname}''')
+async def root(req: Request) -> HTMLResponse:
+    vol = await volume.get_volume()
+    return HTMLResponse(f'''controls for {hostname} whose volume is {vol}''')
 
 
 def main():
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/volume_linux.py	Fri Mar 03 21:24:53 2023 -0800
@@ -0,0 +1,30 @@
+from pulsectl_asyncio import PulseAsync
+
+_pulse = None
+
+
+async def _init():
+    global _pulse
+    if _pulse is not None:
+        return
+    _pulse = PulseAsync('racc')
+    await _pulse.connect()
+
+
+async def _get_sink():
+    [sink] = [
+        s for s in (await _pulse.sink_list())
+        if s.description == 'Built-in Audio Analog Stereo'
+    ]
+    return sink
+
+
+async def get_volume():
+    await _init()
+    return (await _get_sink()).volume.value_flat
+
+
+async def set_volume(v: float):
+    await _init()
+    sync = await _get_sink()
+    await _pulse.volume_set_all_chans(sink, v)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/volume_osx.py	Fri Mar 03 21:24:53 2023 -0800
@@ -0,0 +1,11 @@
+from osascript import osascript
+
+
+async def get_volume():
+    out = osascript('output volume of (get volume settings)')
+    return int(out[1]) / 100
+
+
+async def set_volume(v: float):
+    i = round(v * 100)
+    osascript(f"set volume output volume {i}")