modusa 0.4.21__tar.gz → 0.4.24__tar.gz
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-0.4.21 → modusa-0.4.24}/PKG-INFO +2 -3
- {modusa-0.4.21 → modusa-0.4.24}/pyproject.toml +2 -3
- modusa-0.4.24/src/modusa/.DS_Store +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/__init__.py +1 -1
- modusa-0.4.24/src/modusa/tools/audio_loader.py +155 -0
- modusa-0.4.24/tests/.DS_Store +0 -0
- modusa-0.4.24/tests/test_load.py +158 -0
- modusa-0.4.24/tests/testdata/.DS_Store +0 -0
- modusa-0.4.24/tests/testdata/audio-formats/sample.aac +0 -0
- modusa-0.4.24/tests/testdata/audio-formats/sample.aiff +0 -0
- modusa-0.4.24/tests/testdata/audio-formats/sample.flac +0 -0
- modusa-0.4.24/tests/testdata/audio-formats/sample.m4a +0 -0
- modusa-0.4.24/tests/testdata/audio-formats/sample.mp3 +0 -0
- modusa-0.4.24/tests/testdata/audio-formats/sample.opus +0 -0
- modusa-0.4.24/tests/testdata/audio-formats/sample.wav +0 -0
- modusa-0.4.21/src/modusa/tools/audio_loader.py +0 -109
- {modusa-0.4.21 → modusa-0.4.24}/LICENSE.md +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/README.md +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/config.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/decorators.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/generate_docs_source.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/generate_template.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/list_authors.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/list_plugins.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/main.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/templates/generator.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/templates/io.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/templates/model.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/templates/plugin.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/templates/test.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/devtools/templates/tool.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/fonts/NotoSansDevanagari-Regular.ttf +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/__init__.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/audio.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/audio_waveforms.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/base.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/ftds.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/s1d.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/s2d.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/s_ax.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/t_ax.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/generators/tds.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/images/icon.png +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/__init__.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/audio.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/base.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/data.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/ftds.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/s1d.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/s2d.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/s_ax.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/t_ax.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/models/tds.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/plugins/__init__.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/plugins/base.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/__init__.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/_plotter_old.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/ann_loader.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/audio_converter.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/audio_player.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/audio_recorder.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/audio_saver.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/base.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/math_ops.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/plotter.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/tools/youtube_downloader.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/utils/__init__.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/utils/config.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/utils/excp.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/utils/logger.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/utils/np_func_cat.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/src/modusa/utils/plot.py +0 -0
- {modusa-0.4.21 → modusa-0.4.24}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: modusa
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.24
|
4
4
|
Summary: A modular signal analysis python library.
|
5
5
|
Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
|
6
6
|
License: MIT
|
@@ -11,8 +11,7 @@ Requires-Dist: yt-dlp==2025.9.23
|
|
11
11
|
Requires-Dist: IPython>=9.5.0
|
12
12
|
Requires-Dist: sounddevice>=0.5.2
|
13
13
|
Requires-Dist: ipywidgets>=8.1.7
|
14
|
-
Requires-Dist:
|
15
|
-
Requires-Dist: scipy>=1.16.2
|
14
|
+
Requires-Dist: imageio-ffmpeg>=0.6.0
|
16
15
|
Description-Content-Type: text/markdown
|
17
16
|
|
18
17
|
# modusa
|
@@ -12,12 +12,11 @@ dependencies = [
|
|
12
12
|
"IPython>=9.5.0",
|
13
13
|
"sounddevice>=0.5.2",
|
14
14
|
"ipywidgets>=8.1.7",
|
15
|
-
"
|
16
|
-
"scipy>=1.16.2",
|
15
|
+
"imageio-ffmpeg>=0.6.0",
|
17
16
|
]
|
18
17
|
requires-python = ">=3.11"
|
19
18
|
readme = "README.md"
|
20
|
-
version = "0.4.
|
19
|
+
version = "0.4.24"
|
21
20
|
|
22
21
|
[project.license]
|
23
22
|
text = "MIT"
|
Binary file
|
@@ -0,0 +1,155 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
|
4
|
+
import subprocess
|
5
|
+
import numpy as np
|
6
|
+
import imageio_ffmpeg as ffmpeg
|
7
|
+
from pathlib import Path
|
8
|
+
import re
|
9
|
+
|
10
|
+
def _get_audio_info_ffmpeg(path: Path):
|
11
|
+
"""
|
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).
|
15
|
+
|
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.
|
44
|
+
|
45
|
+
Parameters
|
46
|
+
----------
|
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
|
83
|
+
- Sampling rate to load the audio in.
|
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.
|
91
|
+
|
92
|
+
Returns
|
93
|
+
-------
|
94
|
+
np.ndarray
|
95
|
+
- Audio signal Float32 waveform in [-1, 1].
|
96
|
+
int:
|
97
|
+
Sampling rate.
|
98
|
+
str:
|
99
|
+
File name stem.
|
100
|
+
"""
|
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)
|
122
|
+
|
123
|
+
if ch not in [1, 2]:
|
124
|
+
raise Exception(f"Error reading the metadata for number of channels {ch}, please set `ch` explicitly")
|
125
|
+
|
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)]
|
133
|
+
|
134
|
+
cmd += ["-i", str(path), "-f", "s16le", "-acodec", "pcm_s16le"]
|
135
|
+
cmd += ["-ar", str(sr)]
|
136
|
+
cmd += ["-ac", str(ch)]
|
137
|
+
|
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
|
149
|
+
|
150
|
+
# Delete the file if downloaded from youtube
|
151
|
+
if yt:
|
152
|
+
path.unlink(missing_ok=True)
|
153
|
+
path.parent.rmdir()
|
154
|
+
|
155
|
+
return audio, sr, path.stem
|
Binary file
|
@@ -0,0 +1,158 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
#---------------------------------
|
4
|
+
# Author: Ankit Anand
|
5
|
+
# Date: 21/10/25
|
6
|
+
# Email: ankit0.anand0@gmail.com
|
7
|
+
#---------------------------------
|
8
|
+
|
9
|
+
import pytest
|
10
|
+
import modusa as ms
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
|
14
|
+
#----------------------------------
|
15
|
+
# Loading different audio formats
|
16
|
+
#----------------------------------
|
17
|
+
this_dir = Path(__file__).parents[0].resolve()
|
18
|
+
def test_load_aac():
|
19
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aac")
|
20
|
+
assert title == "sample"
|
21
|
+
|
22
|
+
def test_load_aiff():
|
23
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aiff")
|
24
|
+
assert title == "sample"
|
25
|
+
|
26
|
+
def test_load_flac():
|
27
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.flac")
|
28
|
+
assert title == "sample"
|
29
|
+
|
30
|
+
def test_load_m4a():
|
31
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.m4a")
|
32
|
+
assert title == "sample"
|
33
|
+
|
34
|
+
def test_load_mp3():
|
35
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.mp3")
|
36
|
+
assert title == "sample"
|
37
|
+
|
38
|
+
def test_load_opus():
|
39
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.opus")
|
40
|
+
assert title == "sample"
|
41
|
+
|
42
|
+
def test_load_wav():
|
43
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.wav")
|
44
|
+
assert title == "sample"
|
45
|
+
|
46
|
+
#----------------------------------
|
47
|
+
# Resampe feature
|
48
|
+
#----------------------------------
|
49
|
+
SR = 16000 # Hz
|
50
|
+
def test_load_with_resample_aac():
|
51
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aac", sr=SR)
|
52
|
+
assert sr == SR
|
53
|
+
assert title == "sample"
|
54
|
+
|
55
|
+
def test_load_with_resample_aiff():
|
56
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aiff", sr=SR)
|
57
|
+
assert sr == SR
|
58
|
+
assert title == "sample"
|
59
|
+
|
60
|
+
def test_load_with_resample_flac():
|
61
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.flac", sr=SR)
|
62
|
+
assert sr == SR
|
63
|
+
assert title == "sample"
|
64
|
+
|
65
|
+
def test_load_with_resample_m4a():
|
66
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.m4a", sr=SR)
|
67
|
+
assert sr == SR
|
68
|
+
assert title == "sample"
|
69
|
+
|
70
|
+
def test_load_with_resample_mp3():
|
71
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.mp3", sr=SR)
|
72
|
+
assert sr == SR
|
73
|
+
assert title == "sample"
|
74
|
+
|
75
|
+
def test_load_with_resample_opus():
|
76
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.opus", sr=SR)
|
77
|
+
assert sr == SR
|
78
|
+
assert title == "sample"
|
79
|
+
|
80
|
+
def test_load_with_resample_wav():
|
81
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.wav", sr=SR)
|
82
|
+
assert sr == SR
|
83
|
+
assert title == "sample"
|
84
|
+
|
85
|
+
#----------------------------------
|
86
|
+
# Trim feature
|
87
|
+
#----------------------------------
|
88
|
+
def test_load_with_trim_aac():
|
89
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aac", trim=(0, 5.3))
|
90
|
+
assert title == "sample"
|
91
|
+
|
92
|
+
def test_load_with_trim_aiff():
|
93
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aiff", trim=(0, 5.3))
|
94
|
+
assert title == "sample"
|
95
|
+
|
96
|
+
def test_load_with_trim_flac():
|
97
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.flac", trim=(0, 5.3))
|
98
|
+
assert title == "sample"
|
99
|
+
|
100
|
+
def test_load_with_trim_m4a():
|
101
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.m4a", trim=(0, 5.3))
|
102
|
+
assert title == "sample"
|
103
|
+
|
104
|
+
def test_load_with_trim_mp3():
|
105
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.mp3", trim=(0, 5.3))
|
106
|
+
assert title == "sample"
|
107
|
+
|
108
|
+
def test_load_with_trim_opus():
|
109
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.opus", trim=(0, 5.3))
|
110
|
+
assert title == "sample"
|
111
|
+
|
112
|
+
def test_load_with_trim_wav():
|
113
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.wav", trim=(0, 5.3))
|
114
|
+
assert title == "sample"
|
115
|
+
|
116
|
+
#----------------------------------
|
117
|
+
# Mono feature
|
118
|
+
#----------------------------------
|
119
|
+
def test_load_in_stereo_aac():
|
120
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aac", ch=2)
|
121
|
+
assert y.ndim == 2
|
122
|
+
assert title == "sample"
|
123
|
+
|
124
|
+
def test_load_in_stereo_aiff():
|
125
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.aiff", ch=2)
|
126
|
+
assert y.ndim == 2
|
127
|
+
assert title == "sample"
|
128
|
+
|
129
|
+
def test_load_in_stereo_flac():
|
130
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.flac", ch=2)
|
131
|
+
assert y.ndim == 2
|
132
|
+
assert title == "sample"
|
133
|
+
|
134
|
+
def test_load_in_stereo_m4a():
|
135
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.m4a", ch=2)
|
136
|
+
assert y.ndim == 2
|
137
|
+
assert title == "sample"
|
138
|
+
|
139
|
+
def test_load_in_stereo_mp3():
|
140
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.mp3", ch=2)
|
141
|
+
assert y.ndim == 2
|
142
|
+
assert title == "sample"
|
143
|
+
|
144
|
+
def test_load_in_stereo_opus():
|
145
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.opus", ch=2)
|
146
|
+
assert y.ndim == 2
|
147
|
+
assert title == "sample"
|
148
|
+
|
149
|
+
def test_load_in_stereo_wav():
|
150
|
+
y, sr, title = ms.load(this_dir / "testdata/audio-formats/sample.wav", ch=2)
|
151
|
+
assert y.ndim == 2
|
152
|
+
assert title == "sample"
|
153
|
+
|
154
|
+
#----------------------------------
|
155
|
+
# Load from YouTube
|
156
|
+
#----------------------------------
|
157
|
+
def test_load_youtube_1():
|
158
|
+
y, sr, title = ms.load("https://www.youtube.com/watch?v=DIU_vmElPkU", ch=1)
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -1,109 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
|
4
|
-
import soundfile as sf
|
5
|
-
from scipy.signal import resample
|
6
|
-
import numpy as np
|
7
|
-
from pathlib import Path
|
8
|
-
import tempfile
|
9
|
-
from scipy.signal import resample
|
10
|
-
from .youtube_downloader import download
|
11
|
-
from .audio_converter import convert
|
12
|
-
|
13
|
-
def load(path, sr=None, trim=None, mono=True):
|
14
|
-
"""
|
15
|
-
Loads audio file from various sources.
|
16
|
-
|
17
|
-
.. code-block:: python
|
18
|
-
|
19
|
-
import modusa as ms
|
20
|
-
audio_fp = ms.load(
|
21
|
-
"https://www.youtube.com/watch?v=lIpw9-Y_N0g",
|
22
|
-
sr = None, trim=(5, 10))
|
23
|
-
|
24
|
-
Parameters
|
25
|
-
----------
|
26
|
-
path: str
|
27
|
-
- Path to the audio file.
|
28
|
-
- YouTube URL.
|
29
|
-
sr: int | None
|
30
|
-
- Sampling rate to load the audio in.
|
31
|
-
trim: number | tuple[number, number] | None
|
32
|
-
- Segment of the audio to load.
|
33
|
-
- Example: 10 => First 10 seconds, (5, 10) => 5 to 10 seconds.
|
34
|
-
- Default: None => Entire audio.
|
35
|
-
mono: bool
|
36
|
-
- If True, loads the signal in mono.
|
37
|
-
|
38
|
-
Return
|
39
|
-
------
|
40
|
-
np.ndarray
|
41
|
-
- Audio signal.
|
42
|
-
int
|
43
|
-
- Sampling rate of the loaded audio signal.
|
44
|
-
title
|
45
|
-
- Title of the loaded audio.
|
46
|
-
- Filename without extension or YouTube title.
|
47
|
-
"""
|
48
|
-
# Check if the path is YouTube
|
49
|
-
if ".youtube." in str(path):
|
50
|
-
# Download the audio in temp directory using tempfile module
|
51
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
52
|
-
# Download
|
53
|
-
audio_fp: Path = download(url=path, content_type="audio", output_dir=Path(tmpdir))
|
54
|
-
|
55
|
-
# Convert the audio to ".wav" form for loading
|
56
|
-
wav_audio_fp: Path = convert(inp_audio_fp=audio_fp, output_audio_fp=audio_fp.with_suffix(".wav"))
|
57
|
-
|
58
|
-
# Load the audio in memory
|
59
|
-
audio_data, audio_sr = sf.read(wav_audio_fp)
|
60
|
-
title = audio_fp.stem
|
61
|
-
else:
|
62
|
-
# Check if the file exists
|
63
|
-
fp = Path(path)
|
64
|
-
|
65
|
-
if not fp.exists():
|
66
|
-
raise FileNotFoundError(f"{path} does not exist.")
|
67
|
-
|
68
|
-
# Load the audio in memory
|
69
|
-
audio_data, audio_sr = sf.read(fp)
|
70
|
-
title = fp.stem
|
71
|
-
|
72
|
-
# Convert to mono if requested and it's multi-channel
|
73
|
-
if mono and audio_data.ndim > 1:
|
74
|
-
audio_data = audio_data.mean(axis=1)
|
75
|
-
|
76
|
-
# Resample if needed
|
77
|
-
if sr is not None and audio_sr != sr:
|
78
|
-
n_samples = int(len(audio_data) * sr / audio_sr)
|
79
|
-
|
80
|
-
if audio_data.ndim == 1:
|
81
|
-
# Mono
|
82
|
-
audio_data = resample(audio_data, n_samples)
|
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
|
91
|
-
|
92
|
-
# Trim if requested
|
93
|
-
if trim is not None:
|
94
|
-
if isinstance(trim, (int, float)):
|
95
|
-
trim = (0, trim)
|
96
|
-
elif isinstance(trim, tuple) and len(trim) > 1:
|
97
|
-
trim = (trim[0], trim[1])
|
98
|
-
else:
|
99
|
-
raise ValueError(f"Invalid trim type or length: {type(trim)}, len={len(trim)}")
|
100
|
-
|
101
|
-
start = int(trim[0] * audio_sr)
|
102
|
-
end = int(trim[1] * audio_sr)
|
103
|
-
audio_data = audio_data[start:end]
|
104
|
-
|
105
|
-
# Clip to avoid out-of-range playback issues
|
106
|
-
if np.issubdtype(audio_data.dtype, np.floating):
|
107
|
-
audio_data = np.clip(audio_data, -1.0, 1.0)
|
108
|
-
|
109
|
-
return audio_data.T, audio_sr, title
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|