Metadata-Version: 2.4
Name: easyaudiostream
Version: 0.0.1
Summary: Simple playback for intermittent audio byte streams in Python.
Project-URL: Homepage, https://github.com/zhudotexe/easyaudiostream
Project-URL: Bug Tracker, https://github.com/zhudotexe/easyaudiostream/issues
Author-email: Andrew Zhu <andrew@zhu.codes>
License: MIT License
        
        Copyright (c) 2024-present Andrew Zhu
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Sound/Audio :: Players
Requires-Python: >=3.10
Requires-Dist: pydub<1.0.0,>=0.25.0
Provides-Extra: pyaudio
Requires-Dist: pyaudio<1.0.0,>=0.2.0; extra == 'pyaudio'
Description-Content-Type: text/markdown

# easyaudiostream

Simple playback for intermittent audio byte streams in Python.

I built this library because I was tired of existing audio playback solutions in Python not supporting audio *streams*
particularly well (compared to audio *files*). *easyaudiostream* is intended for simple, high-enough-fidelity playback
of audio byte streams:

- No choppiness between bytestream segments
- Handles non-real-time streams -- faster and slower than real-time
- Handles intermittent streams (i.e., streams that may not yield bytes for a while)
- asyncio-safe -- no blocking computation on main thread

_easyaudiostream_ also includes basic utilities for recording audio from a microphone.

## Installation

Requires Python 3.8 or higher.

By default, easyaudiostream will use the `ffmpeg` (if it is installed) or the bundled PyDub library for playback.

```shell
$ pip install easyaudiostream
```

> [!NOTE]
> You can also install PyAudio to slightly improve playback startup time and enable the microphone input utilities:
>
> ```shell
> $ pip install easyaudiostream[pyaudio]
> ```
>
> This is not installed by default due to compatibility issues across operating systems.

## Usage

There are two main ways of playing an audio stream: using an iterator (if your audio generator yields bytes) or
callbacks (if your audio generator calls a function with generated bytes).

### Iterator Mode

For iterator mode, use the following two functions:

```python
def play_stream(audio_stream: Iterable[bytes]):
    """
    Consume bytes from the audio stream and play them.

    .. note::
        This function will return as soon as the stream is exhausted!
        If your program ends after calling this function, the audio will not play - you might need to ``sleep(...)``.
    """


def play_raw_stream(
    audio_stream: Iterable[bytes], *, sample_width: int = 2, channels: int = 1, frame_rate: int = 24000
):
    """
    Consume PCM audio bytes from the audio stream and play them.

    .. warning::
        If you aren't sure which function to use, use :func:`.stream_audio` instead! Playing raw audio bytes is
        a minor optimization that avoids converting the audio format, but requires a specific input format.

    .. note::
        This function will return as soon as the stream is exhausted!
        If your program ends after calling this function, the audio will not play - you might need to ``sleep(...)``.

    By default, this method accepts raw 16 bit PCM audio at 24kHz, 1 channel, little-endian. You can control the
    expected format of the raw audio using the keyword arguments.
    """

```

### Callback Mode

For callback mode, use the following two functions:

```python
def play_audio(audio_bytes: bytes):
    """
    Play the given audio at the next available opportunity, using a global audio queue.

    .. note::
        This function will return immediately - it just puts the audio on a queue!
        If your program ends after calling this function, the audio will not play - you might need to ``sleep(...)``.
    """


def play_raw_audio(audio_bytes: bytes, *, sample_width: int = 2, channels: int = 1, frame_rate: int = 24000):
    """
    Play the given raw audio at the next available opportunity, using a global audio queue.

    .. warning::
        If you aren't sure which function to use, use :func:`.play_audio` instead! Playing raw audio bytes is
        a minor optimization that avoids converting the audio format, but requires a specific input format.

    .. note::
        This function will return immediately - it just puts the audio on a queue!
        If your program ends after calling this function, the audio will not play - you might need to ``sleep(...)``.

    By default, this method accepts raw 16 bit PCM audio at 24kHz, 1 channel, little-endian. You can control the
    expected format of the raw audio using the keyword arguments.
    """
```

### Record Mic

The following functions are available to record audio from a mic:

```python
def get_mic_stream(mic_id: int | None) -> Iterable[bytes]:
    """Return an audio stream manager that yields audio frames from the given mic."""


def get_mic_stream_async(mic_id: int | None) -> AsyncIterable[bytes]:
    """Return an audio stream manager that yields audio frames from the given mic."""


def list_mics():
    """Print a list of all microphones connected to this device."""
```

You can use `python -m easyaudiostream` to view a list of connected microphones.

## Examples

```python
import time

from easyaudiostream import play_stream

# load dummy audio from file -- normally you would get an audio stream from somewhere else
# in this example, test_audio.wav contains 16bLE mono PCM audio at 24kHz
with open("test_audio.wav", "rb") as f:
    audio = f.read()


# yield it in 1s packets every 0.95s
# but every 5s, add a 3s delay
def choppy_stream():
    for sec in range(0, len(audio), 48000 * 5):
        for i in range(0, 48000 * 5, 48000):
            yield audio[sec + i : sec + i + 48000]
            time.sleep(0.95)

        # oh no, my bytes!
        time.sleep(3)


play_stream(choppy_stream())
time.sleep(10)  # to allow all the audio to play

```
