modusa 0.4.21__tar.gz → 0.4.23__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.
Files changed (73) hide show
  1. {modusa-0.4.21 → modusa-0.4.23}/PKG-INFO +2 -4
  2. {modusa-0.4.21 → modusa-0.4.23}/pyproject.toml +2 -4
  3. modusa-0.4.23/src/modusa/.DS_Store +0 -0
  4. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/__init__.py +1 -1
  5. modusa-0.4.23/src/modusa/tools/audio_loader.py +155 -0
  6. modusa-0.4.23/tests/.DS_Store +0 -0
  7. modusa-0.4.23/tests/test_load.py +158 -0
  8. modusa-0.4.23/tests/testdata/.DS_Store +0 -0
  9. modusa-0.4.23/tests/testdata/audio-formats/sample.aac +0 -0
  10. modusa-0.4.23/tests/testdata/audio-formats/sample.aiff +0 -0
  11. modusa-0.4.23/tests/testdata/audio-formats/sample.flac +0 -0
  12. modusa-0.4.23/tests/testdata/audio-formats/sample.m4a +0 -0
  13. modusa-0.4.23/tests/testdata/audio-formats/sample.mp3 +0 -0
  14. modusa-0.4.23/tests/testdata/audio-formats/sample.opus +0 -0
  15. modusa-0.4.23/tests/testdata/audio-formats/sample.wav +0 -0
  16. modusa-0.4.21/src/modusa/tools/audio_loader.py +0 -109
  17. {modusa-0.4.21 → modusa-0.4.23}/LICENSE.md +0 -0
  18. {modusa-0.4.21 → modusa-0.4.23}/README.md +0 -0
  19. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/config.py +0 -0
  20. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/decorators.py +0 -0
  21. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/generate_docs_source.py +0 -0
  22. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/generate_template.py +0 -0
  23. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/list_authors.py +0 -0
  24. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/list_plugins.py +0 -0
  25. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/main.py +0 -0
  26. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/templates/generator.py +0 -0
  27. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/templates/io.py +0 -0
  28. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/templates/model.py +0 -0
  29. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/templates/plugin.py +0 -0
  30. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/templates/test.py +0 -0
  31. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/devtools/templates/tool.py +0 -0
  32. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/fonts/NotoSansDevanagari-Regular.ttf +0 -0
  33. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/__init__.py +0 -0
  34. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/audio.py +0 -0
  35. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/audio_waveforms.py +0 -0
  36. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/base.py +0 -0
  37. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/ftds.py +0 -0
  38. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/s1d.py +0 -0
  39. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/s2d.py +0 -0
  40. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/s_ax.py +0 -0
  41. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/t_ax.py +0 -0
  42. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/generators/tds.py +0 -0
  43. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/images/icon.png +0 -0
  44. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/__init__.py +0 -0
  45. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/audio.py +0 -0
  46. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/base.py +0 -0
  47. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/data.py +0 -0
  48. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/ftds.py +0 -0
  49. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/s1d.py +0 -0
  50. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/s2d.py +0 -0
  51. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/s_ax.py +0 -0
  52. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/t_ax.py +0 -0
  53. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/models/tds.py +0 -0
  54. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/plugins/__init__.py +0 -0
  55. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/plugins/base.py +0 -0
  56. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/__init__.py +0 -0
  57. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/_plotter_old.py +0 -0
  58. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/ann_loader.py +0 -0
  59. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/audio_converter.py +0 -0
  60. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/audio_player.py +0 -0
  61. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/audio_recorder.py +0 -0
  62. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/audio_saver.py +0 -0
  63. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/base.py +0 -0
  64. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/math_ops.py +0 -0
  65. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/plotter.py +0 -0
  66. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/tools/youtube_downloader.py +0 -0
  67. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/utils/__init__.py +0 -0
  68. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/utils/config.py +0 -0
  69. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/utils/excp.py +0 -0
  70. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/utils/logger.py +0 -0
  71. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/utils/np_func_cat.py +0 -0
  72. {modusa-0.4.21 → modusa-0.4.23}/src/modusa/utils/plot.py +0 -0
  73. {modusa-0.4.21 → modusa-0.4.23}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modusa
3
- Version: 0.4.21
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: soundfile>=0.13.1
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
@@ -10,14 +10,12 @@ dependencies = [
10
10
  "matplotlib>=3.10.3",
11
11
  "yt-dlp==2025.9.23",
12
12
  "IPython>=9.5.0",
13
- "sounddevice>=0.5.2",
14
13
  "ipywidgets>=8.1.7",
15
- "soundfile>=0.13.1",
16
- "scipy>=1.16.2",
14
+ "imageio-ffmpeg>=0.6.0",
17
15
  ]
18
16
  requires-python = ">=3.11"
19
17
  readme = "README.md"
20
- version = "0.4.21"
18
+ version = "0.4.23"
21
19
 
22
20
  [project.license]
23
21
  text = "MIT"
Binary file
@@ -8,4 +8,4 @@ from modusa.tools import play, convert, record, save
8
8
  from modusa.tools import download
9
9
  from modusa.tools import load, load_ann
10
10
 
11
- __version__ = "0.4.21"
11
+ __version__ = "0.4.23"
@@ -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
@@ -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