modusa 0.2.22__py3-none-any.whl → 0.3__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 +8 -1
- modusa/decorators.py +4 -4
- modusa/devtools/generate_docs_source.py +96 -0
- modusa/devtools/generate_template.py +13 -13
- modusa/devtools/main.py +4 -3
- modusa/devtools/templates/generator.py +1 -1
- modusa/devtools/templates/io.py +1 -1
- modusa/devtools/templates/{signal.py → model.py} +18 -11
- modusa/devtools/templates/plugin.py +1 -1
- modusa/devtools/templates/test.py +2 -3
- modusa/devtools/templates/{engine.py → tool.py} +3 -8
- modusa/generators/__init__.py +9 -1
- modusa/generators/audio.py +188 -0
- modusa/generators/audio_waveforms.py +22 -13
- modusa/generators/base.py +1 -1
- modusa/generators/ftds.py +298 -0
- modusa/generators/s1d.py +270 -0
- modusa/generators/s2d.py +300 -0
- modusa/generators/s_ax.py +102 -0
- modusa/generators/t_ax.py +64 -0
- modusa/generators/tds.py +267 -0
- modusa/main.py +0 -30
- modusa/models/__init__.py +14 -0
- modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
- modusa/models/audio.py +90 -0
- modusa/models/base.py +70 -0
- modusa/models/data.py +457 -0
- modusa/models/ftds.py +584 -0
- modusa/models/s1d.py +578 -0
- modusa/models/s2d.py +619 -0
- modusa/models/s_ax.py +448 -0
- modusa/models/t_ax.py +335 -0
- modusa/models/tds.py +465 -0
- modusa/plugins/__init__.py +3 -1
- modusa/tmp.py +98 -0
- modusa/tools/__init__.py +7 -0
- modusa/tools/audio_converter.py +73 -0
- modusa/tools/audio_loader.py +90 -0
- modusa/tools/audio_player.py +89 -0
- modusa/tools/base.py +43 -0
- modusa/tools/math_ops.py +335 -0
- modusa/tools/plotter.py +351 -0
- modusa/tools/youtube_downloader.py +72 -0
- modusa/utils/excp.py +15 -42
- modusa/utils/np_func_cat.py +44 -0
- modusa/utils/plot.py +142 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/METADATA +5 -16
- modusa-0.3.dist-info/RECORD +60 -0
- modusa/engines/.DS_Store +0 -0
- modusa/engines/__init__.py +0 -3
- modusa/engines/base.py +0 -14
- modusa/io/__init__.py +0 -9
- modusa/io/audio_converter.py +0 -76
- modusa/io/audio_loader.py +0 -214
- modusa/io/audio_player.py +0 -72
- modusa/io/base.py +0 -43
- modusa/io/plotter.py +0 -430
- modusa/io/youtube_downloader.py +0 -139
- modusa/signals/__init__.py +0 -7
- modusa/signals/audio_signal.py +0 -483
- modusa/signals/base.py +0 -34
- modusa/signals/frequency_domain_signal.py +0 -329
- modusa/signals/signal_ops.py +0 -158
- modusa/signals/spectrogram.py +0 -465
- modusa/signals/time_domain_signal.py +0 -309
- modusa-0.2.22.dist-info/RECORD +0 -47
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/WHEEL +0 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/entry_points.txt +0 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/licenses/LICENSE.md +0 -0
modusa/io/plotter.py
DELETED
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from modusa import excp
|
|
5
|
-
from modusa.decorators import validate_args_type
|
|
6
|
-
from modusa.io import ModusaIO
|
|
7
|
-
import numpy as np
|
|
8
|
-
import matplotlib.pyplot as plt
|
|
9
|
-
from matplotlib.patches import Rectangle
|
|
10
|
-
from matplotlib.ticker import MaxNLocator
|
|
11
|
-
import warnings
|
|
12
|
-
|
|
13
|
-
warnings.filterwarnings("ignore", message="Glyph .* missing from font.*") # To supress any font related warnings, TODO: Add support to Devnagri font
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Plotter(ModusaIO):
|
|
17
|
-
"""
|
|
18
|
-
Plots different kind of signals using `matplotlib`.
|
|
19
|
-
|
|
20
|
-
Note
|
|
21
|
-
----
|
|
22
|
-
- The class has `plot_` methods to plot different types of signals (1D, 2D).
|
|
23
|
-
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
#--------Meta Information----------
|
|
27
|
-
_name = ""
|
|
28
|
-
_description = ""
|
|
29
|
-
_author_name = "Ankit Anand"
|
|
30
|
-
_author_email = "ankit0.anand0@gmail.com"
|
|
31
|
-
_created_at = "2025-07-06"
|
|
32
|
-
#----------------------------------
|
|
33
|
-
|
|
34
|
-
@staticmethod
|
|
35
|
-
def plot_signal(
|
|
36
|
-
y: np.ndarray,
|
|
37
|
-
x: np.ndarray | None,
|
|
38
|
-
scale_y: tuple[float, float] | None = None,
|
|
39
|
-
ax: plt.Axes | None = None,
|
|
40
|
-
color: str = "k",
|
|
41
|
-
marker: str | None = None,
|
|
42
|
-
linestyle: str | None = None,
|
|
43
|
-
stem: bool = False,
|
|
44
|
-
labels: tuple[str, str, str] | None = None,
|
|
45
|
-
legend_loc: str | None = None,
|
|
46
|
-
title: str | None = None,
|
|
47
|
-
ylabel: str | None = None,
|
|
48
|
-
xlabel: str | None = None,
|
|
49
|
-
ylim: tuple[float, float] | None = None,
|
|
50
|
-
xlim: tuple[float, float] | None = None,
|
|
51
|
-
highlight: list[tuple[float, float], ...] | None = None,
|
|
52
|
-
) -> plt.Figure | None:
|
|
53
|
-
"""
|
|
54
|
-
Plots 1D signal using `matplotlib` with various settings passed through the
|
|
55
|
-
arguments.
|
|
56
|
-
|
|
57
|
-
.. code-block:: python
|
|
58
|
-
|
|
59
|
-
from modusa.io import Plotter
|
|
60
|
-
import numpy as np
|
|
61
|
-
|
|
62
|
-
# Generate a sample sine wave
|
|
63
|
-
x = np.linspace(0, 2 * np.pi, 100)
|
|
64
|
-
y = np.sin(x)
|
|
65
|
-
|
|
66
|
-
# Plot the signal
|
|
67
|
-
fig = Plotter.plot_signal(
|
|
68
|
-
y=y,
|
|
69
|
-
x=x,
|
|
70
|
-
scale_y=None,
|
|
71
|
-
ax=None,
|
|
72
|
-
color="blue",
|
|
73
|
-
marker=None,
|
|
74
|
-
linestyle="-",
|
|
75
|
-
stem=False,
|
|
76
|
-
labels=("Time", "Amplitude", "Sine Wave"),
|
|
77
|
-
legend_loc="upper right",
|
|
78
|
-
zoom=None,
|
|
79
|
-
highlight=[(2, 4)]
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Parameters
|
|
84
|
-
----------
|
|
85
|
-
y: np.ndarray
|
|
86
|
-
The signal values to plot on the y-axis.
|
|
87
|
-
x: np.ndarray | None
|
|
88
|
-
The x-axis values. If None, indices of `y` are used.
|
|
89
|
-
scale_y: tuple[float, float] | None
|
|
90
|
-
Linear scaling for `y` values, (a, b) => ay+b
|
|
91
|
-
ax: plt.Axes | None
|
|
92
|
-
matplotlib Axes object to draw on. If None, a new figure and axis are created. Return type depends on parameter value.
|
|
93
|
-
color: str
|
|
94
|
-
Color of the plotted line or markers. (e.g. "k")
|
|
95
|
-
marker: str | None
|
|
96
|
-
marker style for the plot (e.g., 'o', 'x'). If None, no marker is used.
|
|
97
|
-
linestyle: str | None
|
|
98
|
-
Line style for the plot (e.g., '-', '--'). If None, no line is drawn.
|
|
99
|
-
stem: bool
|
|
100
|
-
If True, plots a stem plot.
|
|
101
|
-
labels: tuple[str, str, str] | None
|
|
102
|
-
Tuple containing (title, xlabel, ylabel). If None, no labels are set.
|
|
103
|
-
legend_loc: str | None
|
|
104
|
-
Location string for legend placement (e.g., 'upper right'). If None, no legend is shown.
|
|
105
|
-
zoom: tuple | None
|
|
106
|
-
Tuple specifying x-axis limits for zoom as (start, end). If None, full x-range is shown.
|
|
107
|
-
highlight: list[tuple[float, float], ...] | None
|
|
108
|
-
List of (start, end) tuples to highlight regions on the plot. e.g. [(1, 2.5), (6, 10)]
|
|
109
|
-
|
|
110
|
-
Returns
|
|
111
|
-
-------
|
|
112
|
-
plt.Figure | None
|
|
113
|
-
Figure if `ax` is None else None.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
# Validate the important args and get the signal that needs to be plotted
|
|
119
|
-
if y.ndim != 1:
|
|
120
|
-
raise excp.InputValueError(f"`y` must be of dimension 1 not {y.ndim}.")
|
|
121
|
-
if y.shape[0] < 1:
|
|
122
|
-
raise excp.InputValueError(f"`y` must not be empty.")
|
|
123
|
-
|
|
124
|
-
if x is None:
|
|
125
|
-
x = np.arange(y.shape[0])
|
|
126
|
-
elif x.ndim != 1:
|
|
127
|
-
raise excp.InputValueError(f"`x` must be of dimension 1 not {x.ndim}.")
|
|
128
|
-
elif x.shape[0] < 1:
|
|
129
|
-
raise excp.InputValueError(f"`x` must not be empty.")
|
|
130
|
-
|
|
131
|
-
if x.shape[0] != y.shape[0]:
|
|
132
|
-
raise excp.InputValueError(f"`y` and `x` must be of same shape")
|
|
133
|
-
|
|
134
|
-
# Scale the signal if needed
|
|
135
|
-
if scale_y is not None:
|
|
136
|
-
if len(scale_y) != 2:
|
|
137
|
-
raise excp.InputValueError(f"`scale_y` must be tuple of two values (1, 2) => 1y+2")
|
|
138
|
-
a, b = scale_y
|
|
139
|
-
y = a * y + b
|
|
140
|
-
|
|
141
|
-
# Create a figure
|
|
142
|
-
if ax is None:
|
|
143
|
-
fig, ax = plt.subplots(figsize=(15, 2))
|
|
144
|
-
created_fig = True
|
|
145
|
-
else:
|
|
146
|
-
fig = ax.get_figure()
|
|
147
|
-
created_fig = False
|
|
148
|
-
|
|
149
|
-
# Plot the signal with right configurations
|
|
150
|
-
plot_label = labels[0] if labels is not None and len(labels) > 0 else None
|
|
151
|
-
if stem:
|
|
152
|
-
ax.stem(x, y, linefmt=color, markerfmt='o', label=title)
|
|
153
|
-
elif marker is not None:
|
|
154
|
-
ax.plot(x, y, c=color, linestyle=linestyle, lw=1.5, marker=marker, label=title)
|
|
155
|
-
else:
|
|
156
|
-
ax.plot(x, y, c=color, linestyle=linestyle, lw=1.5, label=title)
|
|
157
|
-
|
|
158
|
-
# Add legend
|
|
159
|
-
if legend_loc is not None:
|
|
160
|
-
ax.legend(loc=legend_loc)
|
|
161
|
-
|
|
162
|
-
# Set the labels
|
|
163
|
-
if title is not None:
|
|
164
|
-
ax.set_title(title)
|
|
165
|
-
if ylabel is not None:
|
|
166
|
-
ax.set_ylabel(ylabel)
|
|
167
|
-
if xlabel is not None:
|
|
168
|
-
ax.set_xlabel(xlabel)
|
|
169
|
-
|
|
170
|
-
# Applying axes limits into a region
|
|
171
|
-
if ylim is not None:
|
|
172
|
-
ax.set_ylim(ylim)
|
|
173
|
-
if xlim is not None:
|
|
174
|
-
ax.set_xlim(xlim)
|
|
175
|
-
|
|
176
|
-
# Highlight a list of regions
|
|
177
|
-
if highlight is not None:
|
|
178
|
-
for highlight_region in highlight:
|
|
179
|
-
if len(highlight_region) != 2:
|
|
180
|
-
raise excp.InputValueError(f"`highlight should be a list of tuple of 2 values (left, right) => (1, 10.5)")
|
|
181
|
-
l, r = highlight_region
|
|
182
|
-
ax.add_patch(Rectangle((l, np.min(y)), r - l, np.max(y) - np.min(y), color='red', alpha=0.2, zorder=10))
|
|
183
|
-
|
|
184
|
-
# Show/Return the figure as per needed
|
|
185
|
-
if created_fig:
|
|
186
|
-
fig.tight_layout()
|
|
187
|
-
if Plotter._in_notebook():
|
|
188
|
-
plt.close(fig)
|
|
189
|
-
return fig
|
|
190
|
-
else:
|
|
191
|
-
plt.show()
|
|
192
|
-
return fig
|
|
193
|
-
|
|
194
|
-
@staticmethod
|
|
195
|
-
@validate_args_type()
|
|
196
|
-
def plot_matrix(
|
|
197
|
-
M: np.ndarray,
|
|
198
|
-
r: np.ndarray | None = None,
|
|
199
|
-
c: np.ndarray | None = None,
|
|
200
|
-
log_compression_factor: int | float | None = None,
|
|
201
|
-
ax: plt.Axes | None = None,
|
|
202
|
-
cmap: str = "gray_r",
|
|
203
|
-
title: str | None = None,
|
|
204
|
-
Mlabel: str | None = None,
|
|
205
|
-
rlabel: str | None = None,
|
|
206
|
-
clabel: str | None = None,
|
|
207
|
-
rlim: tuple[float, float] | None = None,
|
|
208
|
-
clim: tuple[float, float] | None = None,
|
|
209
|
-
highlight: list[tuple[float, float, float, float]] | None = None,
|
|
210
|
-
origin: str = "lower", # or "lower"
|
|
211
|
-
show_colorbar: bool = True,
|
|
212
|
-
cax: plt.Axes | None = None,
|
|
213
|
-
show_grid: bool = True,
|
|
214
|
-
tick_mode: str = "center", # "center" or "edge"
|
|
215
|
-
n_ticks: tuple[int, int] | None = None,
|
|
216
|
-
) -> plt.Figure:
|
|
217
|
-
"""
|
|
218
|
-
Plot a 2D matrix with optional zooming, highlighting, and grid.
|
|
219
|
-
|
|
220
|
-
.. code-block:: python
|
|
221
|
-
|
|
222
|
-
from modusa.io import Plotter
|
|
223
|
-
import numpy as np
|
|
224
|
-
import matplotlib.pyplot as plt
|
|
225
|
-
|
|
226
|
-
# Create a 50x50 random matrix
|
|
227
|
-
M = np.random.rand(50, 50)
|
|
228
|
-
|
|
229
|
-
# Coordinate axes
|
|
230
|
-
r = np.linspace(0, 1, M.shape[0])
|
|
231
|
-
c = np.linspace(0, 1, M.shape[1])
|
|
232
|
-
|
|
233
|
-
# Plot the matrix
|
|
234
|
-
fig = Plotter.plot_matrix(
|
|
235
|
-
M=M,
|
|
236
|
-
r=r,
|
|
237
|
-
c=c,
|
|
238
|
-
log_compression_factor=None,
|
|
239
|
-
ax=None,
|
|
240
|
-
labels=None,
|
|
241
|
-
zoom=None,
|
|
242
|
-
highlight=None,
|
|
243
|
-
cmap="viridis",
|
|
244
|
-
origin="lower",
|
|
245
|
-
show_colorbar=True,
|
|
246
|
-
cax=None,
|
|
247
|
-
show_grid=False,
|
|
248
|
-
tick_mode="center",
|
|
249
|
-
n_ticks=(5, 5),
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
Parameters
|
|
254
|
-
----------
|
|
255
|
-
M: np.ndarray
|
|
256
|
-
2D matrix to plot.
|
|
257
|
-
r: np.ndarray
|
|
258
|
-
Row coordinate axes.
|
|
259
|
-
c: np.ndarray
|
|
260
|
-
Column coordinate axes.
|
|
261
|
-
log_compression_factor: int | float | None
|
|
262
|
-
Apply log compression to enhance contrast (if provided).
|
|
263
|
-
ax: plt.Axes | None
|
|
264
|
-
Matplotlib axis to draw on (creates new if None).
|
|
265
|
-
labels: tuple[str, str, str, str] | None
|
|
266
|
-
Labels for the plot (title, Mlabel, xlabel, ylabel).
|
|
267
|
-
zoom: tuple[float, float, float, float] | None
|
|
268
|
-
Zoom to (r1, r2, c1, c2) in matrix coordinates.
|
|
269
|
-
highlight: list[tuple[float, float, float, float]] | None
|
|
270
|
-
List of rectangles (r1, r2, c1, c2) to highlight.
|
|
271
|
-
cmap: str
|
|
272
|
-
Colormap to use.
|
|
273
|
-
origin: str
|
|
274
|
-
Image origin, e.g., "upper" or "lower".
|
|
275
|
-
show_colorbar: bool
|
|
276
|
-
Whether to display colorbar.
|
|
277
|
-
cax: plt.Axes | None
|
|
278
|
-
Axis to draw colorbar on (ignored if show_colorbar is False).
|
|
279
|
-
show_grid: bool
|
|
280
|
-
Whether to show grid lines.
|
|
281
|
-
tick_mode: str
|
|
282
|
-
Tick alignment mode: "center" or "edge".
|
|
283
|
-
n_ticks: tuple[int, int]
|
|
284
|
-
Number of ticks on row and column axes.
|
|
285
|
-
|
|
286
|
-
Returns
|
|
287
|
-
-------
|
|
288
|
-
plt.Figure
|
|
289
|
-
Matplotlib figure containing the plot.
|
|
290
|
-
|
|
291
|
-
"""
|
|
292
|
-
|
|
293
|
-
# Validate the important args and get the signal that needs to be plotted
|
|
294
|
-
if M.ndim != 2:
|
|
295
|
-
raise excp.InputValueError(f"`M` must have 2 dimension not {M.ndim}")
|
|
296
|
-
if r is None:
|
|
297
|
-
r = M.shape[0]
|
|
298
|
-
if c is None:
|
|
299
|
-
c = M.shape[1]
|
|
300
|
-
|
|
301
|
-
if r.ndim != 1 and c.ndim != 1:
|
|
302
|
-
raise excp.InputValueError(f"`r` and `c` must have 2 dimension not r:{r.ndim}, c:{c.ndim}")
|
|
303
|
-
|
|
304
|
-
if r.shape[0] != M.shape[0]:
|
|
305
|
-
raise excp.InputValueError(f"`r` must have shape as `M row` not {r.shape}")
|
|
306
|
-
if c.shape[0] != M.shape[1]:
|
|
307
|
-
raise excp.InputValueError(f"`c` must have shape as `M column` not {c.shape}")
|
|
308
|
-
|
|
309
|
-
# Scale the signal if needed
|
|
310
|
-
if log_compression_factor is not None:
|
|
311
|
-
M = np.log1p(float(log_compression_factor) * M)
|
|
312
|
-
|
|
313
|
-
# Create a figure
|
|
314
|
-
if ax is None:
|
|
315
|
-
fig, ax = plt.subplots(figsize=(15, 4))
|
|
316
|
-
created_fig = True
|
|
317
|
-
else:
|
|
318
|
-
fig = ax.get_figure()
|
|
319
|
-
created_fig = False
|
|
320
|
-
|
|
321
|
-
# Plot the signal with right configurations
|
|
322
|
-
# Compute extent
|
|
323
|
-
extent = Plotter._compute_centered_extent(r, c, origin)
|
|
324
|
-
|
|
325
|
-
# Plot image
|
|
326
|
-
im = ax.imshow(
|
|
327
|
-
M,
|
|
328
|
-
aspect="auto",
|
|
329
|
-
cmap=cmap,
|
|
330
|
-
origin=origin,
|
|
331
|
-
extent=extent
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
# Set the ticks and labels
|
|
335
|
-
if n_ticks is None:
|
|
336
|
-
n_ticks = (10, 10)
|
|
337
|
-
|
|
338
|
-
if tick_mode == "center":
|
|
339
|
-
ax.yaxis.set_major_locator(MaxNLocator(nbins=n_ticks[0]))
|
|
340
|
-
ax.xaxis.set_major_locator(MaxNLocator(nbins=n_ticks[1])) # limits ticks
|
|
341
|
-
|
|
342
|
-
elif tick_mode == "edge":
|
|
343
|
-
dr = np.diff(r).mean() if len(r) > 1 else 1
|
|
344
|
-
dc = np.diff(c).mean() if len(c) > 1 else 1
|
|
345
|
-
|
|
346
|
-
# Edge tick positions (centered)
|
|
347
|
-
xticks_all = np.append(c, c[-1] + dc) - dc / 2
|
|
348
|
-
yticks_all = np.append(r, r[-1] + dr) - dr / 2
|
|
349
|
-
|
|
350
|
-
# Determine number of ticks
|
|
351
|
-
nr, nc = n_ticks
|
|
352
|
-
|
|
353
|
-
# Choose evenly spaced tick indices
|
|
354
|
-
xtick_idx = np.linspace(0, len(xticks_all) - 1, nc, dtype=int)
|
|
355
|
-
ytick_idx = np.linspace(0, len(yticks_all) - 1, nr, dtype=int)
|
|
356
|
-
|
|
357
|
-
ax.set_xticks(xticks_all[xtick_idx])
|
|
358
|
-
ax.set_yticks(yticks_all[ytick_idx])
|
|
359
|
-
|
|
360
|
-
# Set the labels
|
|
361
|
-
if title is not None:
|
|
362
|
-
ax.set_title(title)
|
|
363
|
-
if rlabel is not None:
|
|
364
|
-
ax.set_ylabel(rlabel)
|
|
365
|
-
if clabel is not None:
|
|
366
|
-
ax.set_xlabel(clabel)
|
|
367
|
-
|
|
368
|
-
# Applying axes limits into a region
|
|
369
|
-
if rlim is not None:
|
|
370
|
-
ax.set_ylim(rlim)
|
|
371
|
-
if clim is not None:
|
|
372
|
-
ax.set_xlim(clim)
|
|
373
|
-
|
|
374
|
-
# Applying axes limits into a region
|
|
375
|
-
if rlim is not None:
|
|
376
|
-
ax.set_ylim(rlim)
|
|
377
|
-
if clim is not None:
|
|
378
|
-
ax.set_xlim(clim)
|
|
379
|
-
|
|
380
|
-
# Highlight a list of regions
|
|
381
|
-
if highlight is not None:
|
|
382
|
-
for r1, r2, c1, c2 in highlight:
|
|
383
|
-
row_min, row_max = min(r1, r2), max(r1, r2)
|
|
384
|
-
col_min, col_max = min(c1, c2), max(c1, c2)
|
|
385
|
-
width = col_max - col_min
|
|
386
|
-
height = row_max - row_min
|
|
387
|
-
ax.add_patch(Rectangle((col_min, row_min), width, height, color='red', alpha=0.2, zorder=10))
|
|
388
|
-
|
|
389
|
-
# Show colorbar
|
|
390
|
-
if show_colorbar is not None:
|
|
391
|
-
cbar = fig.colorbar(im, ax=ax, cax=cax)
|
|
392
|
-
if Mlabel is not None:
|
|
393
|
-
cbar.set_label(Mlabel)
|
|
394
|
-
|
|
395
|
-
# Show grid
|
|
396
|
-
if show_grid:
|
|
397
|
-
ax.grid(True, color="gray", linestyle="--", linewidth=0.5) # TODO
|
|
398
|
-
|
|
399
|
-
# Show/Return the figure as per needed
|
|
400
|
-
if created_fig:
|
|
401
|
-
fig.tight_layout()
|
|
402
|
-
if Plotter._in_notebook():
|
|
403
|
-
plt.close(fig)
|
|
404
|
-
return fig
|
|
405
|
-
else:
|
|
406
|
-
plt.show()
|
|
407
|
-
return fig
|
|
408
|
-
|
|
409
|
-
@staticmethod
|
|
410
|
-
def _compute_centered_extent(r: np.ndarray, c: np.ndarray, origin: str) -> list[float]:
|
|
411
|
-
"""
|
|
412
|
-
|
|
413
|
-
"""
|
|
414
|
-
dc = np.diff(c).mean() if len(c) > 1 else 1
|
|
415
|
-
dr = np.diff(r).mean() if len(r) > 1 else 1
|
|
416
|
-
left = c[0] - dc / 2
|
|
417
|
-
right = c[-1] + dc / 2
|
|
418
|
-
bottom = r[0] - dr / 2
|
|
419
|
-
top = r[-1] + dr / 2
|
|
420
|
-
return [left, right, top, bottom] if origin == "upper" else [left, right, bottom, top]
|
|
421
|
-
|
|
422
|
-
@staticmethod
|
|
423
|
-
def _in_notebook() -> bool:
|
|
424
|
-
try:
|
|
425
|
-
from IPython import get_ipython
|
|
426
|
-
shell = get_ipython()
|
|
427
|
-
return shell and shell.__class__.__name__ == "ZMQInteractiveShell"
|
|
428
|
-
except ImportError:
|
|
429
|
-
return False
|
|
430
|
-
|
modusa/io/youtube_downloader.py
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from modusa import excp
|
|
5
|
-
from modusa.decorators import validate_args_type
|
|
6
|
-
from modusa.io.base import ModusaIO
|
|
7
|
-
from typing import Any
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
import yt_dlp
|
|
10
|
-
|
|
11
|
-
class YoutubeDownloader(ModusaIO):
|
|
12
|
-
"""
|
|
13
|
-
Download highest quality audio/video from YouTube.
|
|
14
|
-
|
|
15
|
-
Note
|
|
16
|
-
----
|
|
17
|
-
- The engine uses `yt_dlp` python package to download content from YouTube.
|
|
18
|
-
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
#--------Meta Information----------
|
|
22
|
-
_name = "Youtube Downloader"
|
|
23
|
-
_description = "Download highest quality audio/video from YouTube."
|
|
24
|
-
_author_name = "Ankit Anand"
|
|
25
|
-
_author_email = "ankit0.anand0@gmail.com"
|
|
26
|
-
_created_at = "2025-07-05"
|
|
27
|
-
#----------------------------------
|
|
28
|
-
|
|
29
|
-
def __init__(self):
|
|
30
|
-
super().__init__()
|
|
31
|
-
|
|
32
|
-
@staticmethod
|
|
33
|
-
@validate_args_type()
|
|
34
|
-
def _download_audio(url: str, output_dir: str | Path):
|
|
35
|
-
"""
|
|
36
|
-
Download the highest quality audio from a given YouTube URL.
|
|
37
|
-
|
|
38
|
-
Parameters
|
|
39
|
-
----------
|
|
40
|
-
url: str
|
|
41
|
-
URL for the YouTube video.
|
|
42
|
-
output_dir:
|
|
43
|
-
Directory where the audio will be saved.
|
|
44
|
-
|
|
45
|
-
Returns
|
|
46
|
-
-------
|
|
47
|
-
Path
|
|
48
|
-
Path to the downloaded audio file.
|
|
49
|
-
"""
|
|
50
|
-
output_dir = Path(output_dir)
|
|
51
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
52
|
-
|
|
53
|
-
ydl_opts = {
|
|
54
|
-
'format': 'bestaudio/best',
|
|
55
|
-
'outtmpl': f'{output_dir}/%(title)s.%(ext)s',
|
|
56
|
-
'quiet': True,
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
60
|
-
info = ydl.extract_info(url, download=True)
|
|
61
|
-
return Path(info['requested_downloads'][0]['filepath'])
|
|
62
|
-
|
|
63
|
-
@staticmethod
|
|
64
|
-
@validate_args_type()
|
|
65
|
-
def _download_video(url: str, output_dir: str | Path):
|
|
66
|
-
"""
|
|
67
|
-
Download the highest quality video from a YouTube URL using yt-dlp.
|
|
68
|
-
|
|
69
|
-
Parameters
|
|
70
|
-
----------
|
|
71
|
-
url: str
|
|
72
|
-
URL for the YouTube video.
|
|
73
|
-
output_dir:
|
|
74
|
-
Directory where the video will be saved.
|
|
75
|
-
|
|
76
|
-
Returns
|
|
77
|
-
-------
|
|
78
|
-
Path
|
|
79
|
-
Path to the downloaded audio file
|
|
80
|
-
"""
|
|
81
|
-
output_dir = Path(output_dir)
|
|
82
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
83
|
-
|
|
84
|
-
ydl_opts = {
|
|
85
|
-
'format': 'bestvideo+bestaudio/best', # High quality
|
|
86
|
-
'outtmpl': str(output_dir / '%(title)s.%(ext)s'),
|
|
87
|
-
'merge_output_format': 'mp4',
|
|
88
|
-
'quiet': True, # Hide verbose output
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
92
|
-
info = ydl.extract_info(url, download=True)
|
|
93
|
-
return Path(info['requested_downloads'][0]['filepath'])
|
|
94
|
-
|
|
95
|
-
@staticmethod
|
|
96
|
-
@validate_args_type()
|
|
97
|
-
def download(url: str, content_type: str, output_dir: str | Path) -> Path:
|
|
98
|
-
"""
|
|
99
|
-
Downloads audio/video from YouTube.
|
|
100
|
-
|
|
101
|
-
.. code-block:: python
|
|
102
|
-
|
|
103
|
-
# To download audio
|
|
104
|
-
from modusa.io import YoutubeDownloader
|
|
105
|
-
audio_fp = YoutubeDownloader.download(
|
|
106
|
-
url="https://www.youtube.com/watch?v=lIpw9-Y_N0g",
|
|
107
|
-
content_type="audio",
|
|
108
|
-
output_dir="."
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
# To download video
|
|
112
|
-
from modusa.engines import YoutubeDownloaderEngine
|
|
113
|
-
video_fp = YoutubeDownloader.download(
|
|
114
|
-
url="https://www.youtube.com/watch?v=lIpw9-Y_N0g",
|
|
115
|
-
content_type="audio",
|
|
116
|
-
output_dir="."
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
Parameters
|
|
120
|
-
----------
|
|
121
|
-
url: str
|
|
122
|
-
Link to the YouTube video.
|
|
123
|
-
content_type: str
|
|
124
|
-
"audio" or "video"
|
|
125
|
-
output_dir: str | Path
|
|
126
|
-
Directory to save the YouTube content.
|
|
127
|
-
|
|
128
|
-
Returns
|
|
129
|
-
-------
|
|
130
|
-
Path
|
|
131
|
-
File path of the downloaded content.
|
|
132
|
-
|
|
133
|
-
"""
|
|
134
|
-
if content_type == "audio":
|
|
135
|
-
return YoutubeDownloader._download_audio(url=url, output_dir=output_dir)
|
|
136
|
-
elif content_type == "video":
|
|
137
|
-
return YoutubeDownloader._download_video(url=url, output_dir=output_dir)
|
|
138
|
-
else:
|
|
139
|
-
raise excp.InputValueError(f"`content_type` can either take 'audio' or 'video' not {content_type}")
|
modusa/signals/__init__.py
DELETED