pulsectl (python-pulse-control)

repository·master·Indexed 41 days ago

https://github.com/mk-fg/python-pulse-control

A high-level, synchronous Python interface for PulseAudio using ctypes-based bindings. It provides tools to control mixer settings, manage default sinks and card profiles, inspect audio sinks, sources, and inputs, and monitor server state changes via an event loop.

Tokens
2.4K
Snippets
8
Records
11
Agent score
2%

What's inside pulsectl

  1. Manage volume using PulseVolumeInfo

    master

    In PulseAudio, volume is not a single number but a list of numbers (one per channel). In pulsectl, these are represented by PulseVolumeInfo objects.

    Key Concepts

    • Per-channel values: A list of floats representing volume for each channel (e.g., left, right).
    • value_flat: A property on PulseVolumeInfo that provides the average level across all channels.
    • Volume Range:
      • 0.0: Muted (PA_VOLUME_MUTED).
      • 1.0: Normal volume (PA_VOLUME_NORM).
      • > 1.0: Software-boosted volume (up to 65536.0). It is recommended to keep values in the 0.0 to 1.0 range and adjust hardware volume instead to avoid quality loss.

    Usage Patterns

    • Direct channel control: Create a PulseVolumeInfo from a list of channel levels: PulseVolumeInfo([0.15, 0.25]).
    • Uniform channel control: Create a PulseVolumeInfo with a single value applied to all channels: PulseVolumeInfo(0.5, n_channels).
    • Convenience methods: Use pulse.volume_set_all_chans(object, value) to quickly set the volume of an object to a single numeric value across all channels without manually constructing a PulseVolumeInfo object.
    from pulsectl import Pulse, PulseVolumeInfo
    import time
    
    with Pulse('volume-example') as pulse:
        sink_input = pulse.sink_input_list()[0]
    
        # Accessing volume
        volume = sink_input.volume
        print(volume.values)   # list of per-channel values (floats)
        print(volume.value_flat) # average level across channels (float)
    
        # Setting volume via flat value
        volume.value_flat = 0.3
        pulse.volume_set(sink_input, volume)
    
        # Setting volume via PulseVolumeInfo
        n_channels = len(volume.values)
        new_volume = PulseVolumeInfo(0.5, n_channels)
        pulse.volume_set(sink_input, new_volume)
    
        # Most common way for simple tasks:
        pulse.volume_set_all_chans(sink_input, 0.2)
  2. Use Enums instead of strings for event types and constants

    master

    The module returns EnumValue objects for C enums. These objects are comparable to strings but are safer to use because they allow the interpreter to signal errors on typos.

    Example comparison:

    pulsectl.PulseEventTypeEnum.change == 'change'  # Returns True
  3. Handle event loops and threading in pulsectl

    master

    The pulsectl module uses a blocking interface that wraps the libpulse event loop. When you call a method like pulse.mute(), the module runs the libpulse event loop until the operation completes.

    Breaking the event loop

    If you are using event_listen() and need to perform other Pulse operations, you can raise a pulsectl.PulseLoopStop exception inside your callback. This causes event_listen() to return, allowing you to run subsequent Pulse calls before restarting the listener.

    Thread Safety

    Because the libpulse event loop cannot be run from different threads simultaneously, you should initialize your client with threading_lock=True (or pass a specific lock instance) to ensure thread-safe access to the event loop.

    Async Integration

    For asyncio or twisted applications, it is highly recommended to use the pulsectl-asyncio module instead of trying to integrate this blocking module into an async loop.

  4. Run pulsectl tests

    master

    You can run the included test suite using unittest. To isolate specific tests or debug with verbose PulseAudio logs, use the -k flag and the PA_DEBUG=1 environment variable.

    # Run all tests
    python3 -m unittest discover
    
    # Run specific tests
    python3 -m unittest discover -k test_stream_move
    
    # Run tests with verbose PulseAudio logging
    PA_DEBUG=1 python3 -m unittest discover -k test_module_funcs
  5. Install and use pulsectl

    master

    The pulsectl module provides a high-level, synchronous, blocking interface to PulseAudio (libpulse) using ctypes-based bindings. It is primarily designed for mixer-like controls and system introspection rather than audio playback.

    Note: If you require an asynchronous version for use with asyncio, use the pulsectl-asyncio project instead.

    import pulsectl
    
    with pulsectl.Pulse('client-name') as pulse:
        # Your code here
        pass
  6. Listen for PulseAudio server state change events

    master

    You can monitor PulseAudio for changes (like sink or source updates) by setting an event mask and a callback function. Use pulse.event_listen(timeout=...) to start the listening loop.

    To stop the event loop before the timeout expires, raise pulsectl.PulseLoopStop within your callback.

    import pulsectl
    
    with pulsectl.Pulse('event-printer') as pulse:
        def print_events(ev):
            print('Pulse event:', ev)
            # To exit the loop early:
            # raise pulsectl.PulseLoopStop
    
        pulse.event_mask_set('all')
        pulse.event_callback_set(print_events)
        pulse.event_listen(timeout=10)
  7. Change volume for all channels on a sink

    master

    You can iterate through available sinks and adjust their volume. Volume is typically represented in a range of 0.0 to 1.0, where values greater than 1.0 represent soft-boosting.

    Use pulse.volume_change_all_chans(sink, change) to adjust volume by a relative amount, or pulse.volume_set_all_chans(sink, value) to set an absolute volume level.

    import pulsectl
    
    with pulsectl.Pulse('volume-increaser') as pulse:
        for sink in pulse.sink_list():
            # Adjust volume by +0.1
            pulse.volume_change_all_chans(sink, 0.1)
            # Or set absolute volume to 0.5
            pulse.volume_set_all_chans(sink, 0.5)
  8. Introspect PulseAudio sinks, sources, and inputs

    master

    The Pulse instance provides methods to list various PulseAudio entities. These methods return "Pulse-Something-Info" objects (e.g., PulseSinkInfo, PulseSourceInfo, PulseSinkInputInfo), which are thin wrappers around C structs containing metadata like name, index, mute status, and channel volumes.

    • pulse.sink_list(): Returns a list of available output sinks.
    • pulse.source_list(): Returns a list of available input sources.
    • pulse.sink_input_list(): Returns a list of active audio streams (inputs) currently playing through sinks.
    • pulse.card_list(): Returns a list of audio cards.

    Metadata such as application names or window information can be accessed via the .proplist attribute on PulseSinkInputInfo objects.

    import pulsectl
    
    with pulsectl.Pulse('my-client') as pulse:
        # List sinks
        sinks = pulse.sink_list()
        
        # List active audio inputs (e.g., players)
        inputs = pulse.sink_input_list()
        if inputs:
            print(inputs[0].proplist)  # Access metadata
    
        # List sources (microphones/monitors)
        sources = pulse.source_list()
  9. Manage default sinks and card profiles

    master

    You can change the system's default output device or modify audio card profiles using the Pulse instance.

    • To find the current default sink name: pulse.server_info().default_sink_name.
    • To set a specific sink as the default: pulse.default_set(sink).
    • To change a card's profile: pulse.card_profile_set(card, 'profile_name').
    import pulsectl
    
    with pulsectl.Pulse('profile-switcher') as pulse:
        # Set a sink as default
        sink = pulse.sink_list()[0]
        pulse.default_set(sink)
    
        # Change card profile
        card = pulse.card_list()[0]
        # Profiles can be found in card.profile_list
        pulse.card_profile_set(card, 'output:hdmi-stereo')