Skip to content

Commit 3556faf

Browse files
committed
add sound operator tool
1 parent e230459 commit 3556faf

File tree

5 files changed

+164
-0
lines changed

5 files changed

+164
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,4 @@ assets/
166166
.langgraph_api
167167
generated/
168168
*.db
169+
*.wav

docs/references.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,10 @@
5858
### n8n
5959

6060
- [Hosting n8n / Installation / Server setups / Docker-Compose](https://docs.n8n.io/hosting/installation/server-setups/docker-compose/)
61+
62+
### Audio
63+
64+
- [Python の sounddevice を改めて試す](https://zenn.dev/kun432/scraps/f56760d41fc5aa)
65+
- [How To Install libportaudio2 on Ubuntu 22.04](https://www.installati.one/install-libportaudio2-ubuntu-22-04/): `sudo apt-get -y install libportaudio2`
66+
- [python-sounddevice](https://github.com/spatialaudio/python-sounddevice)
67+
- [python-soundfile](https://github.com/bastibe/python-soundfile)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ dependencies = [
2929
"pypdf>=5.9.0",
3030
"python-dotenv>=1.1.0",
3131
"qdrant-client>=1.15.1",
32+
"sounddevice>=0.5.2",
33+
"soundfile>=0.13.1",
3234
"streamlit>=1.48.0",
3335
"typer>=0.16.0",
3436
"youtube-transcript-api>=1.2.2",

scripts/sound_operator.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import logging
2+
3+
import sounddevice as sd
4+
import soundfile as sf
5+
import typer
6+
from dotenv import load_dotenv
7+
8+
from template_langgraph.loggers import get_logger
9+
10+
# Initialize the Typer application
11+
app = typer.Typer(
12+
add_completion=False,
13+
help="agent runner CLI",
14+
)
15+
16+
# Set up logging
17+
logger = get_logger(__name__)
18+
19+
20+
@app.command()
21+
def play(
22+
file: str = typer.Option(
23+
"input.wav",
24+
"--file",
25+
"-f",
26+
help="Path to the audio file to play",
27+
),
28+
verbose: bool = typer.Option(
29+
False,
30+
"--verbose",
31+
"-v",
32+
help="Enable verbose output",
33+
),
34+
):
35+
# Set up logging
36+
if verbose:
37+
logger.setLevel(logging.DEBUG)
38+
39+
data, fs = sf.read(
40+
file=file,
41+
dtype="float32",
42+
)
43+
44+
logger.info(f"Sampling rate: {fs}")
45+
logger.info(f"Channels: {data.shape[1] if len(data.shape) > 1 else 1}")
46+
logger.info(f"Data type: {data.dtype}")
47+
logger.info(f"Duration: {len(data) / fs:.2f} seconds")
48+
49+
sd.play(data, fs)
50+
sd.wait()
51+
52+
53+
@app.command()
54+
def list_devices(
55+
verbose: bool = typer.Option(
56+
False,
57+
"--verbose",
58+
"-v",
59+
help="Enable verbose output",
60+
),
61+
):
62+
# Set up logging
63+
if verbose:
64+
logger.setLevel(logging.DEBUG)
65+
66+
for idx, device in enumerate(sd.query_devices()):
67+
logger.info(f"Device {idx}:")
68+
for key, value in device.items():
69+
logger.info(f" {key}: {value}")
70+
71+
72+
@app.command()
73+
def record(
74+
duration: float = typer.Option(
75+
5.0,
76+
"--duration",
77+
"-d",
78+
help="Duration to record audio (in seconds)",
79+
),
80+
output: str = typer.Option(
81+
"output.wav",
82+
"--output",
83+
"-o",
84+
help="Path to the output audio file",
85+
),
86+
verbose: bool = typer.Option(
87+
False,
88+
"--verbose",
89+
"-v",
90+
help="Enable verbose output",
91+
),
92+
):
93+
# Set up logging
94+
if verbose:
95+
logger.setLevel(logging.DEBUG)
96+
97+
logger.info(f"Recording audio for {duration} seconds...")
98+
samplerate = 44100
99+
channels = 2
100+
recording = sd.rec(
101+
frames=int(duration * samplerate),
102+
samplerate=samplerate,
103+
channels=channels,
104+
)
105+
sd.wait()
106+
sf.write(output, recording, samplerate)
107+
logger.info(f"Recording saved to {output}")
108+
109+
110+
if __name__ == "__main__":
111+
load_dotenv(
112+
override=True,
113+
verbose=True,
114+
)
115+
app()

uv.lock

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)