modusa 0.4.21__py3-none-any.whl → 0.4.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modusa/.DS_Store +0 -0
- modusa/__init__.py +1 -1
- modusa/tools/audio_loader.py +134 -88
- {modusa-0.4.21.dist-info → modusa-0.4.23.dist-info}/METADATA +2 -4
- {modusa-0.4.21.dist-info → modusa-0.4.23.dist-info}/RECORD +8 -7
- {modusa-0.4.21.dist-info → modusa-0.4.23.dist-info}/WHEEL +0 -0
- {modusa-0.4.21.dist-info → modusa-0.4.23.dist-info}/entry_points.txt +0 -0
- {modusa-0.4.21.dist-info → modusa-0.4.23.dist-info}/licenses/LICENSE.md +0 -0
modusa/.DS_Store
ADDED
Binary file
|
modusa/__init__.py
CHANGED
modusa/tools/audio_loader.py
CHANGED
@@ -1,109 +1,155 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
|
3
3
|
|
4
|
-
import
|
5
|
-
from scipy.signal import resample
|
4
|
+
import subprocess
|
6
5
|
import numpy as np
|
6
|
+
import imageio_ffmpeg as ffmpeg
|
7
7
|
from pathlib import Path
|
8
|
-
import
|
9
|
-
from scipy.signal import resample
|
10
|
-
from .youtube_downloader import download
|
11
|
-
from .audio_converter import convert
|
8
|
+
import re
|
12
9
|
|
13
|
-
def
|
10
|
+
def _get_audio_info_ffmpeg(path: Path):
|
14
11
|
"""
|
15
|
-
|
12
|
+
To get the original sampling rate and number of
|
13
|
+
channels of a given audio file by parsing the
|
14
|
+
metadata. (No extra tool required).
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
Parameters
|
17
|
+
----------
|
18
|
+
audiofp: PathLike
|
19
|
+
- Audio filepath
|
20
|
+
|
21
|
+
Returns
|
22
|
+
-------
|
23
|
+
int
|
24
|
+
- Original sampling rate (hz)
|
25
|
+
int
|
26
|
+
- Number of channels
|
27
|
+
"""
|
28
|
+
ffmpeg_exe = ffmpeg.get_ffmpeg_exe()
|
29
|
+
cmd = [ffmpeg_exe, "-i", str(path)]
|
30
|
+
proc = subprocess.run(cmd, stderr=subprocess.PIPE, text=True)
|
31
|
+
text = proc.stderr
|
32
|
+
|
33
|
+
# Example parse: "Stream #0:0: Audio: mp3, 44100 Hz, stereo, ..."
|
34
|
+
m = re.search(r'Audio:.*?(\d+)\s*Hz.*?(mono|stereo)', text)
|
35
|
+
if not m:
|
36
|
+
raise RuntimeError("Could not parse audio info")
|
37
|
+
sr = int(m.group(1))
|
38
|
+
channels = 1 if m.group(2) == "mono" else 2
|
39
|
+
return sr, channels
|
40
|
+
|
41
|
+
def _load_audio_from_youtube(url: str):
|
42
|
+
"""
|
43
|
+
Download audio from a YouTube URL, convert it to WAV, and return the path.
|
23
44
|
|
24
45
|
Parameters
|
25
46
|
----------
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
47
|
+
url : str
|
48
|
+
YouTube video URL.
|
49
|
+
|
50
|
+
Returns
|
51
|
+
-------
|
52
|
+
Path
|
53
|
+
Path to the converted WAV file (you can delete it later).
|
54
|
+
"""
|
55
|
+
from modusa.tools.youtube_downloader import download
|
56
|
+
from modusa.tools.audio_converter import convert
|
57
|
+
import tempfile
|
58
|
+
|
59
|
+
# Temporary directory to hold files (auto-created, not auto-deleted)
|
60
|
+
tmpdir = Path(tempfile.mkdtemp())
|
61
|
+
|
62
|
+
# Download YouTube audio (e.g. .m4a or .webm)
|
63
|
+
audio_fp: Path = download(url=url, content_type="audio", output_dir=tmpdir)
|
64
|
+
|
65
|
+
# Convert downloaded file to .wav
|
66
|
+
wav_audio_fp: Path = convert(inp_audio_fp=audio_fp, output_audio_fp=audio_fp.with_suffix(".wav"))
|
67
|
+
|
68
|
+
# Return path to the WAV file
|
69
|
+
return wav_audio_fp
|
70
|
+
|
71
|
+
#---------------------
|
72
|
+
# Main Function
|
73
|
+
#---------------------
|
74
|
+
def load(path, sr=None, trim=None, ch=None):
|
75
|
+
"""
|
76
|
+
Lightweight audio loader using imageio-ffmpeg.
|
77
|
+
|
78
|
+
Parameters
|
79
|
+
----------
|
80
|
+
path: PathLike/str/URL
|
81
|
+
- Path to the audio file / YouTube video
|
82
|
+
sr: int
|
30
83
|
- Sampling rate to load the audio in.
|
31
|
-
|
32
|
-
|
33
|
-
-
|
34
|
-
- Default: None =>
|
35
|
-
|
36
|
-
-
|
84
|
+
- Default: None => Use the original sampling rate
|
85
|
+
trim: tuple[number, number]
|
86
|
+
- (start, end) in seconds to trim the audio clip.
|
87
|
+
- Default: None => No trimming
|
88
|
+
ch: int
|
89
|
+
- 1 for mono and 2 for stereo
|
90
|
+
- Default: None => Use the original number of channels.
|
37
91
|
|
38
|
-
|
39
|
-
|
92
|
+
Returns
|
93
|
+
-------
|
40
94
|
np.ndarray
|
41
|
-
- Audio signal.
|
42
|
-
int
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
- Filename without extension or YouTube title.
|
95
|
+
- Audio signal Float32 waveform in [-1, 1].
|
96
|
+
int:
|
97
|
+
Sampling rate.
|
98
|
+
str:
|
99
|
+
File name stem.
|
47
100
|
"""
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
audio_data, audio_sr = sf.read(fp)
|
70
|
-
title = fp.stem
|
101
|
+
path = Path(path)
|
102
|
+
ffmpeg_exe = ffmpeg.get_ffmpeg_exe()
|
103
|
+
|
104
|
+
yt = False # Is the path a youtube URL
|
105
|
+
|
106
|
+
if ".youtube" in str(path):
|
107
|
+
yt = True
|
108
|
+
try:
|
109
|
+
path: Path = _load_audio_from_youtube(url=str(path))
|
110
|
+
except Exception as e:
|
111
|
+
raise ConnectionRefusedError("unable to download from YouTube")
|
112
|
+
|
113
|
+
# Find the real sample rate from the file
|
114
|
+
if sr is None:
|
115
|
+
sr, _ = _get_audio_info_ffmpeg(path)
|
116
|
+
if not (sr > 100 and sr < 80000):
|
117
|
+
raise Exception(f"Error reading the metadata for original sampling rate {sr}, please set `sr` explicitly")
|
118
|
+
|
119
|
+
# Find the real number of channels from the file
|
120
|
+
if ch is None:
|
121
|
+
_, ch = _get_audio_info_ffmpeg(path)
|
71
122
|
|
72
|
-
|
73
|
-
|
74
|
-
audio_data = audio_data.mean(axis=1)
|
123
|
+
if ch not in [1, 2]:
|
124
|
+
raise Exception(f"Error reading the metadata for number of channels {ch}, please set `ch` explicitly")
|
75
125
|
|
76
|
-
|
77
|
-
|
78
|
-
|
126
|
+
cmd = [ffmpeg_exe]
|
127
|
+
|
128
|
+
# Optional trimming
|
129
|
+
if trim is not None:
|
130
|
+
start, end = trim
|
131
|
+
duration = end - start
|
132
|
+
cmd += ["-ss", str(start), "-t", str(duration)]
|
79
133
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
else:
|
84
|
-
# Stereo or multi-channel: resample each channel independently
|
85
|
-
audio_data = np.stack([
|
86
|
-
resample(audio_data[:, ch], n_samples)
|
87
|
-
for ch in range(audio_data.shape[1])
|
88
|
-
], axis=1)
|
89
|
-
|
90
|
-
audio_sr = sr
|
134
|
+
cmd += ["-i", str(path), "-f", "s16le", "-acodec", "pcm_s16le"]
|
135
|
+
cmd += ["-ar", str(sr)]
|
136
|
+
cmd += ["-ac", str(ch)]
|
91
137
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
audio_data = audio_data[start:end]
|
138
|
+
cmd += ["-"]
|
139
|
+
|
140
|
+
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
141
|
+
raw = proc.stdout.read()
|
142
|
+
proc.wait()
|
143
|
+
|
144
|
+
audio = np.frombuffer(raw, np.int16).astype(np.float32) / 32768.0
|
145
|
+
|
146
|
+
# Stereo reshaping if forced
|
147
|
+
if ch == 2:
|
148
|
+
audio = audio.reshape(-1, 2).T
|
104
149
|
|
105
|
-
#
|
106
|
-
if
|
107
|
-
|
150
|
+
# Delete the file if downloaded from youtube
|
151
|
+
if yt:
|
152
|
+
path.unlink(missing_ok=True)
|
153
|
+
path.parent.rmdir()
|
108
154
|
|
109
|
-
return
|
155
|
+
return audio, sr, path.stem
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: modusa
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.23
|
4
4
|
Summary: A modular signal analysis python library.
|
5
5
|
Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
|
6
6
|
License: MIT
|
@@ -9,10 +9,8 @@ Requires-Dist: numpy>=2.2.6
|
|
9
9
|
Requires-Dist: matplotlib>=3.10.3
|
10
10
|
Requires-Dist: yt-dlp==2025.9.23
|
11
11
|
Requires-Dist: IPython>=9.5.0
|
12
|
-
Requires-Dist: sounddevice>=0.5.2
|
13
12
|
Requires-Dist: ipywidgets>=8.1.7
|
14
|
-
Requires-Dist:
|
15
|
-
Requires-Dist: scipy>=1.16.2
|
13
|
+
Requires-Dist: imageio-ffmpeg>=0.6.0
|
16
14
|
Description-Content-Type: text/markdown
|
17
15
|
|
18
16
|
# modusa
|
@@ -1,8 +1,9 @@
|
|
1
|
-
modusa-0.4.
|
2
|
-
modusa-0.4.
|
3
|
-
modusa-0.4.
|
4
|
-
modusa-0.4.
|
5
|
-
modusa
|
1
|
+
modusa-0.4.23.dist-info/METADATA,sha256=VcG0sWHvNgl2n0jMCiKiWM2BAeGHUJorSp0pFogxx90,1408
|
2
|
+
modusa-0.4.23.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
3
|
+
modusa-0.4.23.dist-info/entry_points.txt,sha256=fmKpleVXj6CdaBVL14WoEy6xx7JQCs85jvzwTi3lePM,73
|
4
|
+
modusa-0.4.23.dist-info/licenses/LICENSE.md,sha256=JTaXAjx5awk76VArKCx5dUW8vmLEWsL_ZlR7-umaHbA,1078
|
5
|
+
modusa/.DS_Store,sha256=hHecruWmea5u7ITMSdq62sVVdvT2I5lTN20gTfD3Btk,8196
|
6
|
+
modusa/__init__.py,sha256=90isCiArafICPkaTgChbljh2F5o_iDKhqMz1gArlDOE,324
|
6
7
|
modusa/config.py,sha256=bTqK4t00FZqERVITrxW_q284aDDJAa9aMSfFknfR-oU,280
|
7
8
|
modusa/decorators.py,sha256=8zeNX_wE37O6Vp0ysR4-WCZaEL8mq8dyCF_I5DHOzks,5905
|
8
9
|
modusa/devtools/generate_docs_source.py,sha256=UDflHsk-Yh9-3YJTVBzKL32y8hcxiRgAlFEBTMiDqwM,3301
|
@@ -44,7 +45,7 @@ modusa/tools/__init__.py,sha256=S7P1uYckyUdkha2UX9uj4P7mqpF6cc5SHqiW6NEupgs,342
|
|
44
45
|
modusa/tools/_plotter_old.py,sha256=KGow7mihA2H1WNq7s5bpivhCgGo2qVIeDaO6iabpsrg,19294
|
45
46
|
modusa/tools/ann_loader.py,sha256=m6Qu6jXnQ8LfUhKItoHSaHlGxUyzUJlGEyu4_50qJ8w,3099
|
46
47
|
modusa/tools/audio_converter.py,sha256=415qBoPm2sBIuBSI7m1XBKm0AbmVmPydIPPr-uO8D3c,1778
|
47
|
-
modusa/tools/audio_loader.py,sha256=
|
48
|
+
modusa/tools/audio_loader.py,sha256=nQl-E8xM1wdoYzZ3yGw25FJY8EogbqIZzPSMFt4Fv1E,3899
|
48
49
|
modusa/tools/audio_player.py,sha256=kyBUnodkOE9Ox-hKHkfPeGAQ1RPTddbZYXO1ezz6-9w,2494
|
49
50
|
modusa/tools/audio_recorder.py,sha256=K_LGqsPdjTdf3figEZTSQLmgMzYWgz18HTO8C1j5fE4,2788
|
50
51
|
modusa/tools/audio_saver.py,sha256=ldzfr_AydsHTnKbxmBLJblN-hLzTmOlppOm306xI4Ug,510
|
@@ -58,4 +59,4 @@ modusa/utils/excp.py,sha256=L9vhaGjKpv9viJYdmC9n5ndmk2GVbUBuFyZyhAQZmWY,906
|
|
58
59
|
modusa/utils/logger.py,sha256=K0rsnObeNKCxlNeSnVnJeRhgfmob6riB2uyU7h3dDmA,571
|
59
60
|
modusa/utils/np_func_cat.py,sha256=TyIFgRc6bARRMDnZxlVURO5Z0I-GWhxRONYyIv-Vwxs,1007
|
60
61
|
modusa/utils/plot.py,sha256=s_vNdxvKfwxEngvJPgrF1PcmxZNnNaaXPViHWjyjJ-c,5335
|
61
|
-
modusa-0.4.
|
62
|
+
modusa-0.4.23.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|