audio(dtmf): added DTMF audio files and load_dtmf_audio()

This commit is contained in:
Aleix Conchillo Flaqué
2025-08-28 15:32:30 -07:00
parent 79be0695dd
commit 5787743ab3
15 changed files with 87 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence"
]
dependencies = [
"aiofiles>=24.1.0,<25",
"aiohttp>=3.11.12,<4",
"audioop-lts~=0.2.1; python_version>='3.13'",
"docstring_parser~=0.16",
@@ -137,6 +138,20 @@ where = ["src"]
[tool.setuptools.package-data]
"pipecat" = ["py.typed"]
"pipecat.audio.dtmf" = [
"src/pipecat/audio/dtmf/dtmf-0.wav",
"src/pipecat/audio/dtmf/dtmf-1.wav",
"src/pipecat/audio/dtmf/dtmf-2.wav",
"src/pipecat/audio/dtmf/dtmf-3.wav",
"src/pipecat/audio/dtmf/dtmf-4.wav",
"src/pipecat/audio/dtmf/dtmf-5.wav",
"src/pipecat/audio/dtmf/dtmf-6.wav",
"src/pipecat/audio/dtmf/dtmf-7.wav",
"src/pipecat/audio/dtmf/dtmf-8.wav",
"src/pipecat/audio/dtmf/dtmf-9.wav",
"src/pipecat/audio/dtmf/dtmf-pound.wav",
"src/pipecat/audio/dtmf/dtmf-star.wav",
]
"pipecat.services.aws_nova_sonic" = ["src/pipecat/services/aws_nova_sonic/ready.wav"]
[tool.pytest.ini_options]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,70 @@
#
# Copyright (c) 20242025, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
"""DTMF audio utilities.
This module provides functionality to load DTMF (Dual-Tone Multi-Frequency)
audio files corresponding to phone keypad entries. Audio data is cached
in-memory after first load to improve performance on subsequent accesses.
"""
import asyncio
import io
import wave
from importlib.resources import files
from typing import Dict, Optional
import aiofiles
from pipecat.audio.dtmf.types import KeypadEntry
from pipecat.audio.resamplers.base_audio_resampler import BaseAudioResampler
from pipecat.audio.utils import create_file_resampler
__DTMF_LOCK__ = asyncio.Lock()
__DTMF_AUDIO__: Dict[KeypadEntry, bytes] = {}
__DTMF_RESAMPLER__: Optional[BaseAudioResampler] = None
__DTMF_FILE_NAME = {
KeypadEntry.POUND: "dtmf-pound.wav",
KeypadEntry.STAR: "dtmf-star.wav",
}
async def load_dtmf_audio(button: KeypadEntry, *, sample_rate: int = 8000) -> bytes:
"""Load audio for DTMF tones associated with the given button.
Args:
button (KeypadEntry): The button for which the DTMF audio is to be loaded.
sample_rate (int, optional): The sample rate for the audio. Defaults to 8000.
Returns:
bytes: The audio data for the DTMF tone as bytes.
"""
global __DTMF_AUDIO__, __DTMF_RESAMPLER__
async with __DTMF_LOCK__:
if button in __DTMF_AUDIO__:
return __DTMF_AUDIO__[button]
if not __DTMF_RESAMPLER__:
__DTMF_RESAMPLER__ = create_file_resampler()
dtmf_file_name = __DTMF_FILE_NAME.get(button, f"dtmf-{button.value}.wav")
dtmf_file_path = files("pipecat.audio.dtmf").joinpath(dtmf_file_name)
async with aiofiles.open(dtmf_file_path, "rb") as f:
data = await f.read()
with io.BytesIO(data) as buffer:
with wave.open(buffer, "rb") as wf:
audio = wf.readframes(wf.getnframes())
in_sample_rate = wf.getframerate()
resampled_audio = await __DTMF_RESAMPLER__.resample(
audio, in_sample_rate, sample_rate
)
__DTMF_AUDIO__[button] = resampled_audio
return __DTMF_AUDIO__[button]

2
uv.lock generated
View File

@@ -4189,6 +4189,7 @@ wheels = [
name = "pipecat-ai"
source = { editable = "." }
dependencies = [
{ name = "aiofiles" },
{ name = "aiohttp" },
{ name = "audioop-lts", marker = "python_full_version >= '3.13'" },
{ name = "docstring-parser" },
@@ -4409,6 +4410,7 @@ docs = [
requires-dist = [
{ name = "accelerate", marker = "extra == 'moondream'", specifier = "~=1.10.0" },
{ name = "aioboto3", marker = "extra == 'aws'", specifier = "~=15.0.0" },
{ name = "aiofiles", specifier = ">=24.1.0,<25" },
{ name = "aiohttp", specifier = ">=3.11.12,<4" },
{ name = "aiortc", marker = "extra == 'webrtc'", specifier = "~=1.11.0" },
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = "~=0.49.0" },