modusa 0.4.19__tar.gz → 0.4.21__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.19 → modusa-0.4.21}/PKG-INFO +1 -1
- {modusa-0.4.19 → modusa-0.4.21}/pyproject.toml +1 -1
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/__init__.py +1 -1
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/ann_loader.py +18 -18
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/audio_loader.py +46 -43
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/audio_player.py +20 -17
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/plotter.py +4 -0
- {modusa-0.4.19 → modusa-0.4.21}/LICENSE.md +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/README.md +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/config.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/decorators.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/generate_docs_source.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/generate_template.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/list_authors.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/list_plugins.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/main.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/templates/generator.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/templates/io.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/templates/model.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/templates/plugin.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/templates/test.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/devtools/templates/tool.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/fonts/NotoSansDevanagari-Regular.ttf +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/__init__.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/audio.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/audio_waveforms.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/base.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/ftds.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/s1d.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/s2d.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/s_ax.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/t_ax.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/generators/tds.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/images/icon.png +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/__init__.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/audio.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/base.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/data.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/ftds.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/s1d.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/s2d.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/s_ax.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/t_ax.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/models/tds.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/plugins/__init__.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/plugins/base.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/__init__.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/_plotter_old.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/audio_converter.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/audio_recorder.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/audio_saver.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/base.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/math_ops.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/tools/youtube_downloader.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/utils/__init__.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/utils/config.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/utils/excp.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/utils/logger.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/utils/np_func_cat.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/src/modusa/utils/plot.py +0 -0
- {modusa-0.4.19 → modusa-0.4.21}/tests/__init__.py +0 -0
@@ -6,7 +6,7 @@
|
|
6
6
|
# Email: ankit0.anand0@gmail.com
|
7
7
|
#---------------------------------
|
8
8
|
|
9
|
-
def load_ann(path,
|
9
|
+
def load_ann(path, trim=None):
|
10
10
|
"""
|
11
11
|
Load annotation from audatity label
|
12
12
|
text file and also ctm file.
|
@@ -15,9 +15,9 @@ def load_ann(path, clip=None):
|
|
15
15
|
----------
|
16
16
|
path: str
|
17
17
|
- label text/ctm file path.
|
18
|
-
|
19
|
-
- Incase you
|
20
|
-
- If you
|
18
|
+
trim: tuple[number, number] | number | None
|
19
|
+
- Incase you trimmed the audio signal, this parameter will help clip the annotation making sure that the timings are aligned to the trimmed audio.
|
20
|
+
- If you trimmed the audio, say from (10, 20), set the trim to (10, 20).
|
21
21
|
- Default: None
|
22
22
|
|
23
23
|
Returns
|
@@ -42,14 +42,14 @@ def load_ann(path, clip=None):
|
|
42
42
|
ann = [] # This will store the annotation
|
43
43
|
|
44
44
|
# Clipping the annotation to match with the clipped audio
|
45
|
-
if
|
45
|
+
if trim is not None:
|
46
46
|
# Map clip input to the right format
|
47
|
-
if isinstance(
|
48
|
-
|
49
|
-
elif isinstance(
|
50
|
-
|
47
|
+
if isinstance(trim, int or float):
|
48
|
+
trim = (0, trim)
|
49
|
+
elif isinstance(trim, tuple) and len(trim) > 1:
|
50
|
+
trim = (trim[0], trim[1])
|
51
51
|
else:
|
52
|
-
raise ValueError(f"Invalid clip type or length: {type(
|
52
|
+
raise ValueError(f"Invalid clip type or length: {type(trim)}, len={len(trim)}")
|
53
53
|
|
54
54
|
if path.suffix == ".txt":
|
55
55
|
with open(str(path), "r") as f:
|
@@ -60,11 +60,11 @@ def load_ann(path, clip=None):
|
|
60
60
|
|
61
61
|
# Incase user has clipped the audio signal, we adjust the annotation
|
62
62
|
# to match the clipped audio
|
63
|
-
if
|
64
|
-
offset =
|
63
|
+
if trim is not None:
|
64
|
+
offset = trim[0]
|
65
65
|
# Clamp annotation to clip boundaries
|
66
|
-
new_start = max(start,
|
67
|
-
new_end = min(end,
|
66
|
+
new_start = max(start, trim[0]) - offset
|
67
|
+
new_end = min(end, trim[1]) - offset
|
68
68
|
|
69
69
|
# only keep if there's still overlap
|
70
70
|
if new_start < new_end:
|
@@ -89,11 +89,11 @@ def load_ann(path, clip=None):
|
|
89
89
|
|
90
90
|
# Incase user has clipped the audio signal, we adjust the annotation
|
91
91
|
# to match the clipped audio
|
92
|
-
if
|
93
|
-
offset =
|
92
|
+
if trim is not None:
|
93
|
+
offset = trim[0]
|
94
94
|
# Clamp annotation to clip boundaries
|
95
|
-
new_start = max(start,
|
96
|
-
new_end = min(end,
|
95
|
+
new_start = max(start, trim[0]) - offset
|
96
|
+
new_end = min(end, trim[1]) - offset
|
97
97
|
|
98
98
|
# only keep if there's still overlap
|
99
99
|
if new_start < new_end:
|
@@ -3,14 +3,14 @@
|
|
3
3
|
|
4
4
|
import soundfile as sf
|
5
5
|
from scipy.signal import resample
|
6
|
+
import numpy as np
|
6
7
|
from pathlib import Path
|
7
8
|
import tempfile
|
8
9
|
from scipy.signal import resample
|
9
10
|
from .youtube_downloader import download
|
10
11
|
from .audio_converter import convert
|
11
12
|
|
12
|
-
|
13
|
-
def load(path, sr=None, clip=None):
|
13
|
+
def load(path, sr=None, trim=None, mono=True):
|
14
14
|
"""
|
15
15
|
Loads audio file from various sources.
|
16
16
|
|
@@ -19,19 +19,21 @@ def load(path, sr=None, clip=None):
|
|
19
19
|
import modusa as ms
|
20
20
|
audio_fp = ms.load(
|
21
21
|
"https://www.youtube.com/watch?v=lIpw9-Y_N0g",
|
22
|
-
sr = None,
|
22
|
+
sr = None, trim=(5, 10))
|
23
23
|
|
24
24
|
Parameters
|
25
25
|
----------
|
26
26
|
path: str
|
27
|
-
- Path to the audio
|
28
|
-
-
|
27
|
+
- Path to the audio file.
|
28
|
+
- YouTube URL.
|
29
29
|
sr: int | None
|
30
30
|
- Sampling rate to load the audio in.
|
31
|
-
|
32
|
-
-
|
33
|
-
-
|
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
34
|
- Default: None => Entire audio.
|
35
|
+
mono: bool
|
36
|
+
- If True, loads the signal in mono.
|
35
37
|
|
36
38
|
Return
|
37
39
|
------
|
@@ -43,7 +45,6 @@ def load(path, sr=None, clip=None):
|
|
43
45
|
- Title of the loaded audio.
|
44
46
|
- Filename without extension or YouTube title.
|
45
47
|
"""
|
46
|
-
|
47
48
|
# Check if the path is YouTube
|
48
49
|
if ".youtube." in str(path):
|
49
50
|
# Download the audio in temp directory using tempfile module
|
@@ -57,50 +58,52 @@ def load(path, sr=None, clip=None):
|
|
57
58
|
# Load the audio in memory
|
58
59
|
audio_data, audio_sr = sf.read(wav_audio_fp)
|
59
60
|
title = audio_fp.stem
|
60
|
-
|
61
|
-
# Convert to mono if it's multi-channel
|
62
|
-
if audio_data.ndim > 1:
|
63
|
-
audio_data = audio_data.mean(axis=1)
|
64
|
-
|
65
|
-
# Resample if needed
|
66
|
-
if sr is not None:
|
67
|
-
if audio_sr != sr:
|
68
|
-
n_samples = int(len(audio_data) * sr / audio_sr)
|
69
|
-
audio_data = resample(audio_data, n_samples)
|
70
|
-
audio_sr = sr
|
71
|
-
|
72
61
|
else:
|
73
62
|
# Check if the file exists
|
74
63
|
fp = Path(path)
|
75
64
|
|
76
65
|
if not fp.exists():
|
77
66
|
raise FileNotFoundError(f"{path} does not exist.")
|
78
|
-
|
67
|
+
|
79
68
|
# Load the audio in memory
|
80
69
|
audio_data, audio_sr = sf.read(fp)
|
81
70
|
title = fp.stem
|
82
71
|
|
83
|
-
|
84
|
-
|
85
|
-
|
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)
|
86
89
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
if clip is not None:
|
96
|
-
# Map clip input to the right format
|
97
|
-
if isinstance(clip, int or float):
|
98
|
-
clip = (0, clip)
|
99
|
-
elif isinstance(clip, tuple) and len(clip) > 1:
|
100
|
-
clip = (clip[0], clip[1])
|
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])
|
101
98
|
else:
|
102
|
-
raise ValueError(f"Invalid
|
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)
|
103
108
|
|
104
|
-
|
105
|
-
|
106
|
-
return audio_data, audio_sr, title
|
109
|
+
return audio_data.T, audio_sr, title
|
@@ -1,28 +1,31 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
|
3
|
-
from IPython.display import Audio, HTML, display
|
4
|
-
import numpy as np
|
5
|
-
|
6
|
-
from IPython.display import Audio, HTML, display
|
7
|
-
import numpy as np
|
8
|
-
from pathlib import Path
|
9
|
-
|
10
3
|
from IPython.display import Audio, HTML, display
|
11
4
|
from pathlib import Path
|
12
5
|
import numpy as np
|
13
6
|
import base64
|
14
7
|
|
15
|
-
def play(
|
16
|
-
y: np.ndarray,
|
17
|
-
sr: float,
|
18
|
-
clip: tuple[float, float] | None = None,
|
19
|
-
label: str | None = None,
|
20
|
-
) -> None:
|
8
|
+
def play(y, sr: float, clip=None, label=None):
|
21
9
|
"""
|
22
|
-
Audio player with optional clip selection, transcription-style label
|
23
|
-
|
10
|
+
Audio player with optional clip selection, transcription-style label.
|
11
|
+
|
12
|
+
Parameters
|
13
|
+
----------
|
14
|
+
y: ndarray
|
15
|
+
- Audio signal.
|
16
|
+
sr: float
|
17
|
+
- Sampling rate.
|
18
|
+
clip: tuple[float, float] | None
|
19
|
+
- The portion from the audio signal to be played.
|
20
|
+
label: str | None
|
21
|
+
- Could be transcription/labels attached to the audio.
|
22
|
+
|
23
|
+
Returns
|
24
|
+
-------
|
25
|
+
None
|
24
26
|
"""
|
25
|
-
start_time
|
27
|
+
start_time = 0.0
|
28
|
+
end_time = len(y) / sr if y.ndim < 2 else y[0].size / sr
|
26
29
|
|
27
30
|
# Optional clip selection
|
28
31
|
if clip is not None:
|
@@ -91,7 +94,7 @@ def play(
|
|
91
94
|
box-shadow:0 1px 3px rgba(0,0,0,0.05);
|
92
95
|
">
|
93
96
|
{label_html}
|
94
|
-
<div style="margin-top:10px;">
|
97
|
+
<div style="margin-top:10px; margin-bottom:10px">
|
95
98
|
{audio_html}
|
96
99
|
</div>
|
97
100
|
{logo_html}
|
@@ -209,6 +209,10 @@ class Fig:
|
|
209
209
|
|
210
210
|
axs[-1, 0].tick_params(bottom=True, labelbottom=True)
|
211
211
|
|
212
|
+
# Add the figure title on top-left (if any)
|
213
|
+
if self._fig_num is not None:
|
214
|
+
fig.suptitle(f'fig - {self._fig_num}', fontsize=12, fontweight='bold', x=0.01, ha='left', va='top', y=0.98)
|
215
|
+
|
212
216
|
# xlim should be applied on reference subplot, rest all subplots will automatically adjust
|
213
217
|
if xlim is not None:
|
214
218
|
axs[0, 0].set_xlim(xlim)
|
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
|