analyzeAudio 0.0.16__tar.gz → 0.0.18__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 (25) hide show
  1. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/PKG-INFO +12 -17
  2. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/README.md +1 -10
  3. analyzeaudio-0.0.18/analyzeAudio/__init__.py +14 -0
  4. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio/analyzersUseFilename.py +29 -21
  5. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio/analyzersUseSpectrogram.py +8 -7
  6. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio/analyzersUseTensor.py +2 -2
  7. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio/analyzersUseWaveform.py +7 -6
  8. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio/audioAspectsRegistry.py +67 -50
  9. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio/pythonator.py +13 -11
  10. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio.egg-info/PKG-INFO +12 -17
  11. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio.egg-info/requires.txt +6 -2
  12. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio.egg-info/top_level.txt +1 -0
  13. analyzeaudio-0.0.18/pyproject.toml +104 -0
  14. analyzeaudio-0.0.18/tests/test_other.py +11 -0
  15. analyzeaudio-0.0.16/analyzeAudio/__init__.py +0 -14
  16. analyzeaudio-0.0.16/pyproject.toml +0 -80
  17. analyzeaudio-0.0.16/tests/test_other.py +0 -10
  18. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/LICENSE +0 -0
  19. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio/py.typed +0 -0
  20. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio.egg-info/SOURCES.txt +0 -0
  21. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio.egg-info/dependency_links.txt +0 -0
  22. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/analyzeAudio.egg-info/entry_points.txt +0 -0
  23. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/setup.cfg +0 -0
  24. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/tests/conftest.py +0 -0
  25. {analyzeaudio-0.0.16 → analyzeaudio-0.0.18}/tests/test_audioAspectsRegistry.py +0 -0
@@ -1,38 +1,41 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: analyzeAudio
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: Measure one or more aspects of one or more audio files.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
7
7
  Project-URL: Donate, https://www.patreon.com/integrated
8
8
  Project-URL: Homepage, https://github.com/hunterhogan/analyzeAudio
9
+ Project-URL: Issues, https://github.com/hunterhogan/analyzeAudio/issues
9
10
  Project-URL: Repository, https://github.com/hunterhogan/analyzeAudio.git
10
- Keywords: audio,analysis,measurement,metrics,torch,spectrum,spectral,waveform,loudness,LUFS,RMS,FFmpeg,FFprobe,SRMR,audio-analysis,signal-processing
11
+ Keywords: FFmpeg,FFprobe,LUFS,RMS,SRMR,analysis,audio,audio-analysis,loudness,measurement,metrics,signal-processing,spectral,spectrum,torch,waveform
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Environment :: Console
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: Intended Audience :: End Users/Desktop
15
- Classifier: Intended Audience :: Science/Research
16
16
  Classifier: Intended Audience :: Information Technology
17
17
  Classifier: Intended Audience :: Other Audience
18
+ Classifier: Intended Audience :: Science/Research
18
19
  Classifier: Natural Language :: English
19
20
  Classifier: Operating System :: OS Independent
20
21
  Classifier: Programming Language :: Python
21
22
  Classifier: Programming Language :: Python :: 3
22
- Classifier: Programming Language :: Python :: 3.10
23
23
  Classifier: Programming Language :: Python :: 3.11
24
24
  Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
26
27
  Classifier: Topic :: Multimedia :: Sound/Audio
27
28
  Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
28
29
  Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
29
30
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
30
31
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
32
  Classifier: Typing :: Typed
32
- Requires-Python: >=3.10
33
+ Requires-Python: >=3.11
33
34
  Description-Content-Type: text/markdown
34
35
  License-File: LICENSE
36
+ Requires-Dist: Z0Z_tools
35
37
  Requires-Dist: cachetools
38
+ Requires-Dist: hunterMakesPy
36
39
  Requires-Dist: librosa
37
40
  Requires-Dist: numpy
38
41
  Requires-Dist: standard-aifc; python_version >= "3.13"
@@ -40,12 +43,13 @@ Requires-Dist: standard-sunau; python_version >= "3.13"
40
43
  Requires-Dist: torch
41
44
  Requires-Dist: torchmetrics[audio]
42
45
  Requires-Dist: tqdm
43
- Requires-Dist: Z0Z_tools
46
+ Requires-Dist: typing_extensions
47
+ Provides-Extra: development
48
+ Requires-Dist: setuptools-scm; extra == "development"
44
49
  Provides-Extra: testing
45
50
  Requires-Dist: pytest; extra == "testing"
46
51
  Requires-Dist: pytest-cov; extra == "testing"
47
52
  Requires-Dist: pytest-xdist; extra == "testing"
48
- Requires-Dist: pyupgrade; extra == "testing"
49
53
  Dynamic: license-file
50
54
 
51
55
  # analyzeAudio
@@ -212,13 +216,4 @@ pip install analyzeAudio
212
216
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
213
217
  [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
214
218
 
215
- ## How to code
216
-
217
- Coding One Step at a Time:
218
-
219
- 0. WRITE CODE.
220
- 1. Don't write stupid code that's hard to revise.
221
- 2. Write good code.
222
- 3. When revising, write better code.
223
-
224
- [![CC-BY-NC-4.0](https://github.com/hunterhogan/analyzeAudio/blob/main/CC-BY-NC-4.0.svg)](https://creativecommons.org/licenses/by-nc/4.0/)
219
+ [![CC-BY-NC-4.0](https://raw.githubusercontent.com/hunterhogan/analyzeAudio/refs/heads/main/.github/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -162,13 +162,4 @@ pip install analyzeAudio
162
162
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
163
163
  [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
164
164
 
165
- ## How to code
166
-
167
- Coding One Step at a Time:
168
-
169
- 0. WRITE CODE.
170
- 1. Don't write stupid code that's hard to revise.
171
- 2. Write good code.
172
- 3. When revising, write better code.
173
-
174
- [![CC-BY-NC-4.0](https://github.com/hunterhogan/analyzeAudio/blob/main/CC-BY-NC-4.0.svg)](https://creativecommons.org/licenses/by-nc/4.0/)
165
+ [![CC-BY-NC-4.0](https://raw.githubusercontent.com/hunterhogan/analyzeAudio/refs/heads/main/.github/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -0,0 +1,14 @@
1
+ """Docstring?! Ain't nobody got time for that!."""
2
+ from .audioAspectsRegistry import (
3
+ analyzeAudioFile, analyzeAudioListPathFilenames, audioAspects, cacheAudioAnalyzers, getListAvailableAudioAspects,
4
+ registrationAudioAspect)
5
+
6
+ __all__ = [
7
+ 'analyzeAudioFile',
8
+ 'analyzeAudioListPathFilenames',
9
+ 'audioAspects',
10
+ 'getListAvailableAudioAspects',
11
+ ]
12
+
13
+ # isort: split
14
+ from . import analyzersUseFilename, analyzersUseSpectrogram, analyzersUseTensor, analyzersUseWaveform
@@ -1,6 +1,7 @@
1
1
  """Analyzers that use the filename of an audio file to analyze its audio data."""
2
+ # ruff: noqa: D103
3
+ from analyzeAudio import cacheAudioAnalyzers, registrationAudioAspect
2
4
  from analyzeAudio.pythonator import pythonizeFFprobe
3
- from analyzeAudio import registrationAudioAspect, cacheAudioAnalyzers
4
5
  from os import PathLike
5
6
  from statistics import mean
6
7
  from typing import Any, cast
@@ -12,18 +13,27 @@ import subprocess
12
13
 
13
14
  @registrationAudioAspect('SI-SDR mean')
14
15
  def getSI_SDRmean(pathFilenameAlpha: str | PathLike[Any], pathFilenameBeta: str | PathLike[Any]) -> float | None:
15
- """
16
- Calculate the mean Scale-Invariant Signal-to-Distortion Ratio (SI-SDR) between two audio files.
17
- This function uses FFmpeg to compute the SI-SDR between two audio files specified by their paths.
18
- The SI-SDR values are extracted from the FFmpeg output and their mean is calculated.
19
- Parameters:
20
- pathFilenameAlpha: Path to the first audio file.
21
- pathFilenameBeta: Path to the second audio file.
22
- Returns:
23
- SI_SDRmean: The mean SI-SDR value in decibels (dB).
24
- Raises:
25
- subprocess.CalledProcessError: If the FFmpeg command fails.
26
- ValueError: If no SI-SDR values are found in the FFmpeg output.
16
+ """Calculate the mean Scale-Invariant Signal-to-Distortion Ratio (SI-SDR) between two audio files.
17
+
18
+ Parameters
19
+ ----------
20
+ pathFilenameAlpha : str | PathLike[Any]
21
+ Path to the first audio file.
22
+ pathFilenameBeta : str | PathLike[Any]
23
+ Path to the second audio file.
24
+
25
+ Returns
26
+ -------
27
+ SI_SDRmean : float | None
28
+ The mean SI-SDR value in decibels (dB).
29
+
30
+ Raises
31
+ ------
32
+ subprocess.CalledProcessError
33
+ If the FFmpeg command fails.
34
+ ValueError
35
+ If no SI-SDR values are found in the FFmpeg output.
36
+
27
37
  """
28
38
  commandLineFFmpeg = [
29
39
  'ffmpeg', '-hide_banner', '-loglevel', '32',
@@ -37,8 +47,7 @@ def getSI_SDRmean(pathFilenameAlpha: str | PathLike[Any], pathFilenameBeta: str
37
47
  regexSI_SDR = regex.compile(r"^\[Parsed_asisdr_.* (.*) dB", regex.MULTILINE)
38
48
 
39
49
  listMatchesSI_SDR = regexSI_SDR.findall(stderrFFmpeg)
40
- SI_SDRmean = mean(float(match) for match in listMatchesSI_SDR)
41
- return SI_SDRmean
50
+ return mean(float(match) for match in listMatchesSI_SDR)
42
51
 
43
52
  @cachetools.cached(cache=cacheAudioAnalyzers)
44
53
  def ffprobeShotgunAndCache(pathFilename: str | PathLike[Any]) -> dict[str, float]:
@@ -67,16 +76,16 @@ def ffprobeShotgunAndCache(pathFilename: str | PathLike[Any]) -> dict[str, float
67
76
  dictionaryAspectsAnalyzed: dict[str, float] = {}
68
77
  if 'aspectralstats' in FFprobeStructured:
69
78
  for keyName in FFprobeStructured['aspectralstats']:
70
- # No matter how many channels, each keyName is `numpy.ndarray[tuple[int, int], numpy.dtype[numpy.float64]]`
71
- # where `tuple[int, int]` is (channel, frame)
72
- # NOTE (as of this writing) `registrar` can only understand the generic class `numpy.ndarray` and not more specific typing
73
- # dictionaryAspectsAnalyzed[keyName] = FFprobeStructured['aspectralstats'][keyName]
79
+ """No matter how many channels, each keyName is `numpy.ndarray[tuple[int, int], numpy.dtype[numpy.float64]]`
80
+ where `tuple[int, int]` is (channel, frame)
81
+ NOTE (as of this writing) `registrar` can only understand the generic class `numpy.ndarray` and not more specific typing
82
+ dictionaryAspectsAnalyzed[keyName] = FFprobeStructured['aspectralstats'][keyName]"""
74
83
  dictionaryAspectsAnalyzed[keyName] = numpy.mean(FFprobeStructured['aspectralstats'][keyName]).astype(float)
75
84
  if 'r128' in FFprobeStructured:
76
85
  for keyName in FFprobeStructured['r128']:
77
86
  dictionaryAspectsAnalyzed[keyName] = FFprobeStructured['r128'][keyName][-1]
78
87
  if 'astats' in FFprobeStructured:
79
- for keyName, arrayFeatureValues in cast(dict[str, numpy.ndarray[Any, Any]], FFprobeStructured['astats']).items():
88
+ for keyName, arrayFeatureValues in cast('dict[str, numpy.ndarray[Any, Any]]', FFprobeStructured['astats']).items():
80
89
  dictionaryAspectsAnalyzed[keyName.split('.')[-1]] = numpy.mean(arrayFeatureValues[..., -1:None]).astype(float)
81
90
 
82
91
  return dictionaryAspectsAnalyzed
@@ -187,7 +196,6 @@ def analyzeRolloff(pathFilename: str | PathLike[Any]) -> float | None:
187
196
 
188
197
  @registrationAudioAspect('Abs_Peak_count')
189
198
  def analyzeAbs_Peak_count(pathFilename: str | PathLike[Any]) -> float | None:
190
- print('Abs_Peak_count', pathFilename)
191
199
  return ffprobeShotgunAndCache(pathFilename).get('Abs_Peak_count')
192
200
 
193
201
  @registrationAudioAspect('Bit_depth')
@@ -1,30 +1,31 @@
1
1
  """Analyzers that use the spectrogram to analyze audio data."""
2
- from analyzeAudio import registrationAudioAspect, audioAspects, cacheAudioAnalyzers
2
+ # ruff: noqa: D103
3
+ from analyzeAudio import audioAspects, cacheAudioAnalyzers, registrationAudioAspect
4
+ from numpy import dtype, floating
3
5
  from typing import Any
4
6
  import cachetools
5
7
  import librosa
6
8
  import numpy
7
- from numpy import dtype, floating
8
9
 
9
10
  @registrationAudioAspect('Chromagram')
10
11
  def analyzeChromagram(spectrogramPower: numpy.ndarray[Any, dtype[floating[Any]]], sampleRate: int, **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
11
- return librosa.feature.chroma_stft(S=spectrogramPower, sr=sampleRate, **keywordArguments) # type: ignore
12
+ return librosa.feature.chroma_stft(S=spectrogramPower, sr=sampleRate, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
12
13
 
13
14
  @registrationAudioAspect('Spectral Contrast')
14
15
  def analyzeSpectralContrast(spectrogramMagnitude: numpy.ndarray[Any, dtype[floating[Any]]], **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
15
- return librosa.feature.spectral_contrast(S=spectrogramMagnitude, **keywordArguments) # type: ignore
16
+ return librosa.feature.spectral_contrast(S=spectrogramMagnitude, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
16
17
 
17
18
  @registrationAudioAspect('Spectral Bandwidth')
18
19
  def analyzeSpectralBandwidth(spectrogramMagnitude: numpy.ndarray[Any, dtype[floating[Any]]], **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
19
20
  centroid = audioAspects['Spectral Centroid']['analyzer'](spectrogramMagnitude)
20
- return librosa.feature.spectral_bandwidth(S=spectrogramMagnitude, centroid=centroid, **keywordArguments) # type: ignore
21
+ return librosa.feature.spectral_bandwidth(S=spectrogramMagnitude, centroid=centroid, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
21
22
 
22
23
  @cachetools.cached(cache=cacheAudioAnalyzers)
23
24
  @registrationAudioAspect('Spectral Centroid')
24
25
  def analyzeSpectralCentroid(spectrogramMagnitude: numpy.ndarray[Any, dtype[floating[Any]]], **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
25
- return librosa.feature.spectral_centroid(S=spectrogramMagnitude, **keywordArguments) # type: ignore
26
+ return librosa.feature.spectral_centroid(S=spectrogramMagnitude, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
26
27
 
27
28
  @registrationAudioAspect('Spectral Flatness')
28
29
  def analyzeSpectralFlatness(spectrogramMagnitude: numpy.ndarray[Any, dtype[floating[Any]]], **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
29
- spectralFlatness: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]] = librosa.feature.spectral_flatness(S=spectrogramMagnitude, **keywordArguments) # type: ignore
30
+ spectralFlatness: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]] = librosa.feature.spectral_flatness(S=spectrogramMagnitude, **keywordArguments) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
30
31
  return 20 * numpy.log10(spectralFlatness, where=(spectralFlatness != 0)) # dB
@@ -6,6 +6,6 @@ import numpy
6
6
  import torch
7
7
 
8
8
  @registrationAudioAspect('SRMR')
9
- def analyzeSRMR(tensorAudio: torch.Tensor, sampleRate: int, pytorchOnCPU: bool | None, **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
9
+ def analyzeSRMR(tensorAudio: torch.Tensor, sampleRate: int, pytorchOnCPU: bool | None, **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType] # noqa: D103, FBT001
10
10
  keywordArguments['fast'] = keywordArguments.get('fast') or pytorchOnCPU or None
11
- return torch.Tensor.numpy(speech_reverberation_modulation_energy_ratio(tensorAudio, sampleRate, **keywordArguments)) # type: ignore
11
+ return torch.Tensor.numpy(speech_reverberation_modulation_energy_ratio(tensorAudio, sampleRate, **keywordArguments)) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
@@ -1,26 +1,27 @@
1
1
  """Analyzers that use the waveform of audio data."""
2
- from analyzeAudio import registrationAudioAspect, audioAspects, cacheAudioAnalyzers
2
+ # ruff: noqa: D103
3
+ from analyzeAudio import audioAspects, cacheAudioAnalyzers, registrationAudioAspect
3
4
  from typing import Any
5
+ import cachetools
4
6
  import librosa
5
7
  import numpy
6
- import cachetools
7
8
 
8
9
  @cachetools.cached(cache=cacheAudioAnalyzers)
9
10
  @registrationAudioAspect('Tempogram')
10
11
  def analyzeTempogram(waveform: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]], sampleRate: int, **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
11
- return librosa.feature.tempogram(y=waveform, sr=sampleRate, **keywordArguments) # type: ignore
12
+ return librosa.feature.tempogram(y=waveform, sr=sampleRate, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
12
13
 
13
14
  # "RMS value from audio samples is faster ... However, ... spectrogram ... more accurate ... because ... windowed"
14
15
  @registrationAudioAspect('RMS from waveform')
15
16
  def analyzeRMS(waveform: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]], **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
16
- arrayRMS: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]] = librosa.feature.rms(y=waveform, **keywordArguments) # type: ignore
17
+ arrayRMS: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]] = librosa.feature.rms(y=waveform, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
17
18
  return 20 * numpy.log10(arrayRMS, where=(arrayRMS != 0)) # dB
18
19
 
19
20
  @registrationAudioAspect('Tempo')
20
21
  def analyzeTempo(waveform: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]], sampleRate: int, **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
21
22
  tempogram = audioAspects['Tempogram']['analyzer'](waveform, sampleRate)
22
- return librosa.feature.tempo(y=waveform, sr=sampleRate, tg=tempogram, **keywordArguments) # type: ignore
23
+ return librosa.feature.tempo(y=waveform, sr=sampleRate, tg=tempogram, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
23
24
 
24
25
  @registrationAudioAspect('Zero-crossing rate') # This is distinct from 'Zero-crossings rate'
25
26
  def analyzeZeroCrossingRate(waveform: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.floating[Any]]], **keywordArguments: Any) -> numpy.ndarray: # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
26
- return librosa.feature.zero_crossing_rate(y=waveform, **keywordArguments) # type: ignore
27
+ return librosa.feature.zero_crossing_rate(y=waveform, **keywordArguments) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
@@ -1,10 +1,14 @@
1
- from collections.abc import Callable, Sequence
2
- from concurrent.futures import ProcessPoolExecutor, as_completed
1
+ from collections.abc import Callable, Sequence # noqa: D100
2
+ from concurrent.futures import as_completed, ProcessPoolExecutor
3
+ from hunterMakesPy.parseParameters import defineConcurrencyLimit, oopsieKwargsie
4
+ from multiprocessing import set_start_method as multiprocessing_set_start_method
3
5
  from numpy.typing import NDArray
4
6
  from os import PathLike
5
- from typing import Any, cast, ParamSpec, TypeAlias, TYPE_CHECKING, TypeVar
6
- from Z0Z_tools import defineConcurrencyLimit, oopsieKwargsie, stft, Spectrogram
7
+ from typing import Any, cast, ParamSpec, TypeAlias, TypeVar
8
+ from typing_extensions import TypedDict
9
+ from Z0Z_tools import Spectrogram, stft
7
10
  import cachetools
11
+ import contextlib
8
12
  import inspect
9
13
  import numpy
10
14
  import pathlib
@@ -12,16 +16,8 @@ import soundfile
12
16
  import torch
13
17
  import warnings
14
18
 
15
- if TYPE_CHECKING:
16
- from typing import TypedDict
17
- else:
18
- TypedDict = dict
19
-
20
- from multiprocessing import set_start_method as multiprocessing_set_start_method
21
- try:
19
+ with contextlib.suppress(RuntimeError):
22
20
  multiprocessing_set_start_method('spawn')
23
- except RuntimeError:
24
- pass
25
21
 
26
22
  warnings.filterwarnings('ignore', category=UserWarning, module='torchmetrics', message='.*fast=True.*')
27
23
 
@@ -29,7 +25,8 @@ parameterSpecifications = ParamSpec('parameterSpecifications')
29
25
  typeReturned = TypeVar('typeReturned')
30
26
 
31
27
  audioAspect: TypeAlias = str
32
- class analyzersAudioAspects(TypedDict):
28
+
29
+ class analyzersAudioAspects(TypedDict): # noqa: D101
33
30
  analyzer: Callable[..., Any]
34
31
  analyzerParameters: list[str]
35
32
 
@@ -37,37 +34,44 @@ audioAspects: dict[audioAspect, analyzersAudioAspects] = {}
37
34
  """A register of 1) measurable aspects of audio data, 2) analyzer functions to measure audio aspects, 3) and parameters of analyzer functions."""
38
35
 
39
36
  def registrationAudioAspect(aspectName: str) -> Callable[[Callable[parameterSpecifications, typeReturned]], Callable[parameterSpecifications, typeReturned]]:
40
- """
41
- A function to "decorate" a registrant-analyzer function and the aspect of audio data it can analyze.
37
+ """'Decorate' a registrant-analyzer function and the aspect of audio data it can analyze.
38
+
39
+ Parameters
40
+ ----------
41
+ aspectName : str
42
+ The audio aspect that the registrar will enter into the register, `audioAspects`.
42
43
 
43
- Parameters:
44
- aspectName: The audio aspect that the registrar will enter into the register, `audioAspects`.
45
44
  """
46
45
 
47
46
  def registrar(registrant: Callable[parameterSpecifications, typeReturned]) -> Callable[parameterSpecifications, typeReturned]:
48
47
  """
49
48
  `registrar` updates the registry, `audioAspects`, with 1) the analyzer function, `registrant`, 2) the analyzer function's parameters, and 3) the aspect of audio data that the analyzer function measures.
50
49
 
51
- Parameters:
52
- registrant: The function that analyzes an aspect of audio data.
50
+ Parameters
51
+ ----------
52
+ registrant : Callable
53
+ The function that analyzes an aspect of audio data.
54
+
55
+ Note
56
+ ----
57
+ `registrar` does not change the behavior of `registrant`, the analyzer function.
53
58
 
54
- Note:
55
- `registrar` does not change the behavior of `registrant`, the analyzer function.
56
59
  """
57
60
  audioAspects[aspectName] = {
58
61
  'analyzer': registrant,
59
62
  'analyzerParameters': inspect.getfullargspec(registrant).args
60
63
  }
61
64
 
62
- # if registrant.__annotations__.get('return') is not None and issubclass(registrant.__annotations__['return'], subclassTarget): # maybe someday I will understand why this doesn't work
63
- # if registrant.__annotations__.get('return') is not None and issubclass(registrant.__annotations__.get('return', type(None)), subclassTarget): # maybe someday I will understand why this doesn't work
64
65
  if isinstance(registrant.__annotations__.get('return', type(None)), type) and issubclass(registrant.__annotations__.get('return', type(None)), numpy.ndarray): # maybe someday I will understand what all of this statement means
65
66
  def registrationAudioAspectMean(*arguments: parameterSpecifications.args, **keywordArguments: parameterSpecifications.kwargs) -> numpy.floating[Any]:
66
67
  """
67
68
  `registrar` updates the registry with a new analyzer function that calculates the mean of the analyzer's numpy.ndarray result.
68
69
 
69
- Returns:
70
- mean: Mean value of the analyzer's numpy.ndarray result.
70
+ Returns
71
+ -------
72
+ mean : float
73
+ Mean value of the analyzer's numpy.ndarray result.
74
+
71
75
  """
72
76
  aspectValue = registrant(*arguments, **keywordArguments)
73
77
  return numpy.mean(cast(NDArray[Any], aspectValue))
@@ -83,15 +87,21 @@ def analyzeAudioFile(pathFilename: str | PathLike[Any], listAspectNames: list[st
83
87
  """
84
88
  Analyzes an audio file for specified aspects and returns the results.
85
89
 
86
- Parameters:
87
- pathFilename: The path to the audio file to be analyzed.
88
- listAspectNames: A list of aspect names to analyze in the audio file.
90
+ Parameters
91
+ ----------
92
+ pathFilename : str or PathLike
93
+ The path to the audio file to be analyzed.
94
+ listAspectNames : list of str
95
+ A list of aspect names to analyze in the audio file.
96
+
97
+ Returns
98
+ -------
99
+ listAspectValues : list of (str or float or NDArray)
100
+ A list of analyzed values in the same order as `listAspectNames`.
89
101
 
90
- Returns:
91
- listAspectValues: A list of analyzed values in the same order as `listAspectNames`.
92
102
  """
93
103
  pathlib.Path(pathFilename).stat() # raises FileNotFoundError if the file does not exist
94
- dictionaryAspectsAnalyzed: dict[str, str | float | NDArray[Any]] = {aspectName: 'not found' for aspectName in listAspectNames}
104
+ dictionaryAspectsAnalyzed: dict[str, str | float | NDArray[Any]] = dict.fromkeys(listAspectNames, 'not found')
95
105
  """Despite returning a list, use a dictionary to preserve the order of the listAspectNames.
96
106
  Similarly, 'not found' ensures the returned list length == len(listAspectNames)"""
97
107
 
@@ -104,20 +114,20 @@ def analyzeAudioFile(pathFilename: str | PathLike[Any], listAspectNames: list[st
104
114
  tryAgain = True
105
115
  while tryAgain:
106
116
  try:
107
- tensorAudio = torch.from_numpy(waveform) # memory-sharing
117
+ tensorAudio = torch.from_numpy(waveform) # pyright: ignore[reportUnknownMemberType] # memory-sharing # noqa: F841
108
118
  tryAgain = False
109
119
  except RuntimeError as ERRORmessage:
110
120
  if 'negative stride' in str(ERRORmessage):
111
121
  waveform = waveform.copy() # not memory-sharing
112
122
  tryAgain = True
113
123
  else:
114
- raise ERRORmessage
124
+ raise ERRORmessage # noqa: TRY201
115
125
 
116
126
  spectrogram = stft(waveform, sampleRate=sampleRate)
117
127
  spectrogramMagnitude = numpy.absolute(spectrogram)
118
- spectrogramPower = spectrogramMagnitude ** 2
128
+ spectrogramPower = spectrogramMagnitude ** 2 # noqa: F841
119
129
 
120
- pytorchOnCPU = not torch.cuda.is_available() # False if GPU available, True if not
130
+ pytorchOnCPU = not torch.cuda.is_available() # False if GPU available, True if not # noqa: F841
121
131
 
122
132
  for aspectName in listAspectNames:
123
133
  if aspectName in audioAspects:
@@ -127,18 +137,23 @@ def analyzeAudioFile(pathFilename: str | PathLike[Any], listAspectNames: list[st
127
137
 
128
138
  return [dictionaryAspectsAnalyzed[aspectName] for aspectName in listAspectNames]
129
139
 
130
- def analyzeAudioListPathFilenames(listPathFilenames: Sequence[str] | Sequence[PathLike[Any]], listAspectNames: list[str], CPUlimit: int | float | bool | None = None) -> list[list[str | float | NDArray[Any]]]:
140
+ def analyzeAudioListPathFilenames(listPathFilenames: Sequence[str] | Sequence[PathLike[Any]], listAspectNames: list[str], CPUlimit: int | float | bool | None = None) -> list[list[str | float | NDArray[Any]]]: # noqa: FBT001
131
141
  """
132
142
  Analyzes a list of audio files for specified aspects of the individual files and returns the results.
133
143
 
134
- Parameters:
135
- listPathFilenames: A list of paths to the audio files to be analyzed.
136
- listAspectNames: A list of aspect names to analyze in each audio file.
137
- CPUlimit (gluttonous resource usage): whether and how to limit the CPU usage. See notes for details.
144
+ Parameters
145
+ ----------
146
+ listPathFilenames : Sequence of str or PathLike
147
+ A list of paths to the audio files to be analyzed.
148
+ listAspectNames : list of str
149
+ A list of aspect names to analyze in each audio file.
150
+ CPUlimit : int, float, bool, or None, default=None
151
+ Whether and how to limit the CPU usage. See notes for details.
138
152
 
139
- Returns:
140
- rowsListFilenameAspectValues: A list of lists, where each inner list contains the filename and
141
- analyzed values corresponding to the specified aspects, which are in the same order as `listAspectNames`.
153
+ Returns
154
+ -------
155
+ rowsListFilenameAspectValues : list of list of (str or float or NDArray)
156
+ A list of lists, where each inner list contains the filename and analyzed values corresponding to the specified aspects, which are in the same order as `listAspectNames`.
142
157
 
143
158
  You can save the data with `Z0Z_tools.dataTabularTOpathFilenameDelimited()`.
144
159
  For example,
@@ -166,7 +181,7 @@ def analyzeAudioListPathFilenames(listPathFilenames: Sequence[str] | Sequence[Pa
166
181
 
167
182
  if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
168
183
  CPUlimit = oopsieKwargsie(CPUlimit)
169
- max_workers = defineConcurrencyLimit(CPUlimit)
184
+ max_workers = defineConcurrencyLimit(limit=CPUlimit)
170
185
 
171
186
  with ProcessPoolExecutor(max_workers=max_workers) as concurrencyManager:
172
187
  dictionaryConcurrency = {concurrencyManager.submit(analyzeAudioFile, pathFilename, listAspectNames)
@@ -177,18 +192,20 @@ def analyzeAudioListPathFilenames(listPathFilenames: Sequence[str] | Sequence[Pa
177
192
  cacheAudioAnalyzers.pop(dictionaryConcurrency[claimTicket], None)
178
193
  listAspectValues = claimTicket.result()
179
194
  rowsListFilenameAspectValues.append(
180
- [str(pathlib.PurePath(dictionaryConcurrency[claimTicket]).as_posix())]
195
+ [str(pathlib.PurePath(dictionaryConcurrency[claimTicket]).as_posix())] # noqa: RUF005
181
196
  + listAspectValues)
182
197
 
183
198
  return rowsListFilenameAspectValues
184
199
 
185
200
  def getListAvailableAudioAspects() -> list[str]:
186
201
  """
187
- Returns a sorted list of audio aspect names. All valid values for the parameter `listAspectNames`, for example,
188
- are returned by this function.
202
+ Return a sorted list of audio aspect names. All valid values for the parameter `listAspectNames`, for example, are returned by this function.
203
+
204
+ Returns
205
+ -------
206
+ listAvailableAudioAspects : list of str
207
+ The list of aspect names registered in `audioAspects`.
189
208
 
190
- Returns:
191
- listAvailableAudioAspects: The list of aspect names registered in `audioAspects`.
192
209
  """
193
210
  return sorted(audioAspects.keys())
194
211
 
@@ -1,4 +1,5 @@
1
1
  """Convert FFprobe output to a standardized Python object."""
2
+ # ruff: noqa: D103
2
3
  from collections import defaultdict
3
4
  from typing import Any, cast, NamedTuple
4
5
  import json
@@ -12,11 +13,11 @@ import numpy
12
13
  # NOTE You changed the code because a static type checker was mad at you. Ask yourself,
13
14
  # "Are you the tool or is the type checker the tool?"
14
15
 
15
- class Blackdetect(NamedTuple):
16
+ class Blackdetect(NamedTuple): # noqa: D101
16
17
  black_start: float | None = None
17
18
  black_end: float | None = None
18
19
 
19
- def pythonizeFFprobe(FFprobeJSON_utf8: str):
20
+ def pythonizeFFprobe(FFprobeJSON_utf8: str) -> tuple[defaultdict[str, Any] | dict[str, Any], dict[str, numpy.ndarray[Any, Any] | dict[str, numpy.ndarray[Any, Any]]]]:
20
21
  FFroot: dict[str, Any] = json.loads(FFprobeJSON_utf8)
21
22
  Z0Z_dictionaries: dict[str, numpy.ndarray[Any, Any] | dict[str, numpy.ndarray[Any, Any]]] = {}
22
23
  if 'packets_and_frames' in FFroot: # Divide into 'packets' and 'frames'
@@ -26,7 +27,8 @@ def pythonizeFFprobe(FFprobeJSON_utf8: str):
26
27
  FFroot[section := packetOrFrame['type'] + 's'].append(packetOrFrame)
27
28
  del FFroot[section][-1]['type']
28
29
  else:
29
- raise ValueError("'packets_and_frames' for the win!")
30
+ msg = "'packets_and_frames' for the win!"
31
+ raise ValueError(msg)
30
32
  del FFroot['packets_and_frames']
31
33
 
32
34
  Z0Z_register = [
@@ -38,16 +40,16 @@ def pythonizeFFprobe(FFprobeJSON_utf8: str):
38
40
  leftCrumbs = False
39
41
  if 'frames' in FFroot:
40
42
  leftCrumbs = False
41
- # listTuplesBlackdetect = [] # uncommentToFixBlackdetect
43
+ # listTuplesBlackdetect = [] # uncommentToFixBlackdetect # noqa: ERA001
42
44
  listTuplesBlackdetect: list[Blackdetect] = []
43
45
  for indexFrame, FFframe in enumerate(FFroot['frames']):
44
46
  if 'tags' in FFframe:
45
47
  if 'lavfi.black_start' in FFframe['tags']:
46
- # listTuplesBlackdetect.append(float(FFframe['tags']['lavfi.black_start'])) # uncommentToFixBlackdetect
48
+ # listTuplesBlackdetect.append(float(FFframe['tags']['lavfi.black_start'])) # uncommentToFixBlackdetect # noqa: ERA001
47
49
  listTuplesBlackdetect.append(Blackdetect(black_start=float(FFframe['tags']['lavfi.black_start'])))
48
50
  del FFframe['tags']['lavfi.black_start']
49
51
  if 'lavfi.black_end' in FFframe['tags']:
50
- # listTuplesBlackdetect[-1] = (listTuplesBlackdetect[-1], float(FFframe['tags']['lavfi.black_end'])) # uncommentToFixBlackdetect
52
+ # listTuplesBlackdetect[-1] = (listTuplesBlackdetect[-1], float(FFframe['tags']['lavfi.black_end'])) # uncommentToFixBlackdetect # noqa: ERA001
51
53
  tupleBlackdetectLast = listTuplesBlackdetect.pop() if listTuplesBlackdetect else Blackdetect()
52
54
  match tupleBlackdetectLast.black_end:
53
55
  case None:
@@ -89,15 +91,15 @@ def pythonizeFFprobe(FFprobeJSON_utf8: str):
89
91
  if registrant not in Z0Z_dictionaries:
90
92
  Z0Z_dictionaries[registrant] = {}
91
93
  elif statistic not in Z0Z_dictionaries[registrant]:
92
- # NOTE (as of this writing) `registrar` can only understand the generic class `numpy.ndarray` and not more specific typing
93
- valueSherpa = cast(numpy.ndarray, numpy.zeros((channel, len(FFroot['frames'])))) # type: ignore
94
+ # NOTE (as of this writing) `registrar` can only understand the generic class `numpy.ndarray` and not more specific typing # noqa: ERA001
95
+ valueSherpa = cast(numpy.ndarray, numpy.zeros((channel, len(FFroot['frames'])))) # pyright: ignore[reportMissingTypeArgument, reportUnknownVariableType]
94
96
  Z0Z_dictionaries[registrant][statistic] = valueSherpa
95
97
  else:
96
98
  raise # Re-raise the exception
97
99
  except IndexError:
98
100
  if channel > Z0Z_dictionaries[registrant][statistic].shape[0]:
99
101
  Z0Z_dictionaries[registrant][statistic] = numpy.resize(Z0Z_dictionaries[registrant][statistic], (channel, len(FFroot['frames'])))
100
- # Z0Z_dictionaries[registrant][statistic].resize((channel, len(FFroot['frames'])))
102
+ # Z0Z_dictionaries[registrant][statistic].resize((channel, len(FFroot['frames']))) # noqa: ERA001
101
103
  else:
102
104
  raise # Re-raise the exception
103
105
 
@@ -106,7 +108,7 @@ def pythonizeFFprobe(FFprobeJSON_utf8: str):
106
108
  if FFframe:
107
109
  leftCrumbs = True
108
110
  if listTuplesBlackdetect:
109
- # 2025-03-06: I am _shocked_ that I was able to create a numpy structured array whenever it was that I originally wrote this code.
111
+ # 2025-03-06 I am _shocked_ that I was able to create a numpy structured array whenever it was when I originally wrote this code.
110
112
  arrayBlackdetect = numpy.array(
111
113
  [(
112
114
  -1.0 if detect.black_start is None else detect.black_start,
@@ -116,7 +118,7 @@ def pythonizeFFprobe(FFprobeJSON_utf8: str):
116
118
  copy=False
117
119
  )
118
120
  Z0Z_dictionaries['blackdetect'] = arrayBlackdetect
119
- # Z0Z_dictionaries['blackdetect'] = numpy.array(listTuplesBlackdetect, dtype=[('black_start', numpy.float32), ('black_end', numpy.float32)], copy=False) # uncommentToFixBlackdetect
121
+ # Z0Z_dictionaries['blackdetect'] = numpy.array(listTuplesBlackdetect, dtype=[('black_start', numpy.float32), ('black_end', numpy.float32)], copy=False) # uncommentToFixBlackdetect # noqa: ERA001
120
122
  if not leftCrumbs:
121
123
  del FFroot['frames']
122
124
  return FFroot, Z0Z_dictionaries
@@ -1,38 +1,41 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: analyzeAudio
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: Measure one or more aspects of one or more audio files.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
7
7
  Project-URL: Donate, https://www.patreon.com/integrated
8
8
  Project-URL: Homepage, https://github.com/hunterhogan/analyzeAudio
9
+ Project-URL: Issues, https://github.com/hunterhogan/analyzeAudio/issues
9
10
  Project-URL: Repository, https://github.com/hunterhogan/analyzeAudio.git
10
- Keywords: audio,analysis,measurement,metrics,torch,spectrum,spectral,waveform,loudness,LUFS,RMS,FFmpeg,FFprobe,SRMR,audio-analysis,signal-processing
11
+ Keywords: FFmpeg,FFprobe,LUFS,RMS,SRMR,analysis,audio,audio-analysis,loudness,measurement,metrics,signal-processing,spectral,spectrum,torch,waveform
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Environment :: Console
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: Intended Audience :: End Users/Desktop
15
- Classifier: Intended Audience :: Science/Research
16
16
  Classifier: Intended Audience :: Information Technology
17
17
  Classifier: Intended Audience :: Other Audience
18
+ Classifier: Intended Audience :: Science/Research
18
19
  Classifier: Natural Language :: English
19
20
  Classifier: Operating System :: OS Independent
20
21
  Classifier: Programming Language :: Python
21
22
  Classifier: Programming Language :: Python :: 3
22
- Classifier: Programming Language :: Python :: 3.10
23
23
  Classifier: Programming Language :: Python :: 3.11
24
24
  Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
26
27
  Classifier: Topic :: Multimedia :: Sound/Audio
27
28
  Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
28
29
  Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
29
30
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
30
31
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
32
  Classifier: Typing :: Typed
32
- Requires-Python: >=3.10
33
+ Requires-Python: >=3.11
33
34
  Description-Content-Type: text/markdown
34
35
  License-File: LICENSE
36
+ Requires-Dist: Z0Z_tools
35
37
  Requires-Dist: cachetools
38
+ Requires-Dist: hunterMakesPy
36
39
  Requires-Dist: librosa
37
40
  Requires-Dist: numpy
38
41
  Requires-Dist: standard-aifc; python_version >= "3.13"
@@ -40,12 +43,13 @@ Requires-Dist: standard-sunau; python_version >= "3.13"
40
43
  Requires-Dist: torch
41
44
  Requires-Dist: torchmetrics[audio]
42
45
  Requires-Dist: tqdm
43
- Requires-Dist: Z0Z_tools
46
+ Requires-Dist: typing_extensions
47
+ Provides-Extra: development
48
+ Requires-Dist: setuptools-scm; extra == "development"
44
49
  Provides-Extra: testing
45
50
  Requires-Dist: pytest; extra == "testing"
46
51
  Requires-Dist: pytest-cov; extra == "testing"
47
52
  Requires-Dist: pytest-xdist; extra == "testing"
48
- Requires-Dist: pyupgrade; extra == "testing"
49
53
  Dynamic: license-file
50
54
 
51
55
  # analyzeAudio
@@ -212,13 +216,4 @@ pip install analyzeAudio
212
216
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
213
217
  [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
214
218
 
215
- ## How to code
216
-
217
- Coding One Step at a Time:
218
-
219
- 0. WRITE CODE.
220
- 1. Don't write stupid code that's hard to revise.
221
- 2. Write good code.
222
- 3. When revising, write better code.
223
-
224
- [![CC-BY-NC-4.0](https://github.com/hunterhogan/analyzeAudio/blob/main/CC-BY-NC-4.0.svg)](https://creativecommons.org/licenses/by-nc/4.0/)
219
+ [![CC-BY-NC-4.0](https://raw.githubusercontent.com/hunterhogan/analyzeAudio/refs/heads/main/.github/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -1,17 +1,21 @@
1
+ Z0Z_tools
1
2
  cachetools
3
+ hunterMakesPy
2
4
  librosa
3
5
  numpy
4
6
  torch
5
7
  torchmetrics[audio]
6
8
  tqdm
7
- Z0Z_tools
9
+ typing_extensions
8
10
 
9
11
  [:python_version >= "3.13"]
10
12
  standard-aifc
11
13
  standard-sunau
12
14
 
15
+ [development]
16
+ setuptools-scm
17
+
13
18
  [testing]
14
19
  pytest
15
20
  pytest-cov
16
21
  pytest-xdist
17
- pyupgrade
@@ -1,4 +1,5 @@
1
1
  analyzeAudio
2
2
  citations
3
3
  dist
4
+ notesPythonator
4
5
  tests
@@ -0,0 +1,104 @@
1
+ [project]
2
+ name = "analyzeAudio"
3
+ version = "0.0.18"
4
+ description = "Measure one or more aspects of one or more audio files."
5
+ readme = { file = "README.md", content-type = "text/markdown" }
6
+ requires-python = ">=3.11"
7
+ license = { 'text' = "CC-BY-NC-4.0" }
8
+ authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
9
+ keywords = [
10
+ "FFmpeg",
11
+ "FFprobe",
12
+ "LUFS",
13
+ "RMS",
14
+ "SRMR",
15
+ "analysis",
16
+ "audio",
17
+ "audio-analysis",
18
+ "loudness",
19
+ "measurement",
20
+ "metrics",
21
+ "signal-processing",
22
+ "spectral",
23
+ "spectrum",
24
+ "torch",
25
+ "waveform",
26
+ ]
27
+ classifiers = [
28
+ "Development Status :: 3 - Alpha",
29
+ "Environment :: Console",
30
+ "Intended Audience :: Developers",
31
+ "Intended Audience :: End Users/Desktop",
32
+ "Intended Audience :: Information Technology",
33
+ "Intended Audience :: Other Audience",
34
+ "Intended Audience :: Science/Research",
35
+ "Natural Language :: English",
36
+ "Operating System :: OS Independent",
37
+ "Programming Language :: Python",
38
+ "Programming Language :: Python :: 3",
39
+ "Programming Language :: Python :: 3.11",
40
+ "Programming Language :: Python :: 3.12",
41
+ "Programming Language :: Python :: 3.13",
42
+ "Programming Language :: Python :: 3.14",
43
+ "Topic :: Multimedia :: Sound/Audio",
44
+ "Topic :: Multimedia :: Sound/Audio :: Analysis",
45
+ "Topic :: Multimedia :: Sound/Audio :: Conversion",
46
+ "Topic :: Scientific/Engineering :: Information Analysis",
47
+ "Topic :: Software Development :: Libraries :: Python Modules",
48
+ "Typing :: Typed",
49
+ ]
50
+ urls = { Donate = "https://www.patreon.com/integrated", Homepage = "https://github.com/hunterhogan/analyzeAudio", Issues = "https://github.com/hunterhogan/analyzeAudio/issues", Repository = "https://github.com/hunterhogan/analyzeAudio.git" }
51
+ dependencies = [
52
+ "Z0Z_tools",
53
+ "cachetools",
54
+ "hunterMakesPy",
55
+ "librosa",
56
+ "numpy",
57
+ "standard-aifc;python_version>='3.13'",
58
+ "standard-sunau;python_version>='3.13'",
59
+ "torch",
60
+ "torchmetrics[audio]",
61
+ "tqdm",
62
+ "typing_extensions",
63
+ ]
64
+ optional-dependencies = { development = [
65
+ "setuptools-scm",
66
+ ], testing = [
67
+ "pytest",
68
+ "pytest-cov",
69
+ "pytest-xdist",
70
+ ] }
71
+
72
+ [project.scripts]
73
+ whatMeasurements = "analyzeAudio.audioAspectsRegistry:getListAvailableAudioAspects"
74
+
75
+ [build-system]
76
+ requires = ["setuptools"]
77
+ build-backend = "setuptools.build_meta"
78
+
79
+ [tool.coverage]
80
+ report = { exclude_lines = [
81
+ "if TYPE_CHECKING:",
82
+ "if __name__ == \"__main__\":",
83
+ ], ignore_errors = false }
84
+ run = { branch = true, concurrency = [
85
+ "multiprocessing",
86
+ ], data_file = "tests/coverage/.coverage", omit = [
87
+ "tests/*",
88
+ ], parallel = true, source = [
89
+ ".",
90
+ ] }
91
+ xml = { output = "tests/coverage/coverage.xml" }
92
+
93
+ [tool.pytest]
94
+ addopts = ["--color=auto", "-n 4"]
95
+ log_auto_indent = "On"
96
+ testpaths = ["tests"]
97
+
98
+ [tool.setuptools]
99
+ package-data = { "*" = ["py.typed"] }
100
+ packages = { find = { } }
101
+
102
+ [tool.updateCitation]
103
+ filenameCitationDOTcff = "CITATION.cff"
104
+ pathFilenameCitationSSOT = "citations/CITATION.cff"
@@ -0,0 +1,11 @@
1
+ from collections.abc import Callable
2
+ from hunterMakesPy.pytestForYourUse import PytestFor_defineConcurrencyLimit, PytestFor_oopsieKwargsie
3
+ import pytest
4
+
5
+ @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_defineConcurrencyLimit())
6
+ def testConcurrencyLimit(nameOfTest: str, callablePytest: Callable[[], None]) -> None:
7
+ callablePytest()
8
+
9
+ @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_oopsieKwargsie())
10
+ def testOopsieKwargsie(nameOfTest: str, callablePytest: Callable[[], None]) -> None:
11
+ callablePytest()
@@ -1,14 +0,0 @@
1
- from .audioAspectsRegistry import registrationAudioAspect, cacheAudioAnalyzers, analyzeAudioFile, \
2
- analyzeAudioListPathFilenames, getListAvailableAudioAspects, audioAspects
3
-
4
- __all__ = [
5
- 'analyzeAudioFile',
6
- 'analyzeAudioListPathFilenames',
7
- 'audioAspects',
8
- 'getListAvailableAudioAspects',
9
- ]
10
-
11
- from . import analyzersUseFilename
12
- from . import analyzersUseSpectrogram
13
- from . import analyzersUseTensor
14
- from . import analyzersUseWaveform
@@ -1,80 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
7
- classifiers = [
8
- "Development Status :: 3 - Alpha",
9
- "Environment :: Console",
10
- "Intended Audience :: Developers",
11
- "Intended Audience :: End Users/Desktop",
12
- "Intended Audience :: Science/Research",
13
- "Intended Audience :: Information Technology",
14
- "Intended Audience :: Other Audience",
15
- "Natural Language :: English",
16
- "Operating System :: OS Independent",
17
- "Programming Language :: Python",
18
- "Programming Language :: Python :: 3",
19
- "Programming Language :: Python :: 3.10",
20
- "Programming Language :: Python :: 3.11",
21
- "Programming Language :: Python :: 3.12",
22
- "Programming Language :: Python :: 3.13",
23
- "Topic :: Multimedia :: Sound/Audio",
24
- "Topic :: Multimedia :: Sound/Audio :: Analysis",
25
- "Topic :: Multimedia :: Sound/Audio :: Conversion",
26
- "Topic :: Scientific/Engineering :: Information Analysis",
27
- "Topic :: Software Development :: Libraries :: Python Modules",
28
- "Typing :: Typed",]
29
- dependencies = [
30
- "cachetools",
31
- "librosa",
32
- "numpy",
33
- "standard-aifc;python_version>='3.13'",
34
- "standard-sunau;python_version>='3.13'",
35
- "torch",
36
- "torchmetrics[audio]",
37
- "tqdm",
38
- "Z0Z_tools",]
39
- description = "Measure one or more aspects of one or more audio files."
40
- keywords = ["audio", "analysis", "measurement", "metrics", "torch", "spectrum", "spectral", "waveform", "loudness", "LUFS", "RMS", "FFmpeg", "FFprobe", "SRMR", "audio-analysis", "signal-processing"]
41
- license = { 'text' = "CC-BY-NC-4.0" }
42
- name = "analyzeAudio"
43
- optional-dependencies = { testing = [
44
- "pytest",
45
- "pytest-cov",
46
- "pytest-xdist",
47
- "pyupgrade",] }
48
- readme = { file = "README.md", content-type = "text/markdown" }
49
- requires-python = ">=3.10"
50
- urls = { Donate = "https://www.patreon.com/integrated", Homepage = "https://github.com/hunterhogan/analyzeAudio", Repository = "https://github.com/hunterhogan/analyzeAudio.git" }
51
- version = "0.0.16"
52
-
53
- [project.scripts]
54
- whatMeasurements = "analyzeAudio.audioAspectsRegistry:getListAvailableAudioAspects"
55
-
56
- [tool.coverage]
57
- report = { exclude_lines = [
58
- "if TYPE_CHECKING:",
59
- "if __name__ == \"__main__\":",
60
- ], ignore_errors = false }
61
- run = { branch = true, concurrency = [
62
- "multiprocessing",
63
- ], data_file = "tests/coverage/.coverage", omit = [
64
- "tests/*",
65
- ], parallel = true, source = [
66
- ".",] }
67
- xml = { output = "tests/coverage/coverage.xml" }
68
-
69
- [tool.pytest.ini_options]
70
- log_auto_indent = true
71
- addopts = ["--color=auto", "-n 4"]
72
- testpaths = ["tests"]
73
-
74
- [tool.setuptools]
75
- package-data = { "*" = ["py.typed"] }
76
- packages = { find = {} }
77
-
78
- [tool.updateCitation]
79
- filenameCitationDOTcff = 'CITATION.cff'
80
- pathFilenameCitationSSOT = "citations/CITATION.cff"
@@ -1,10 +0,0 @@
1
- import pytest
2
- from Z0Z_tools.pytestForYourUse import PytestFor_defineConcurrencyLimit, PytestFor_oopsieKwargsie
3
-
4
- @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_defineConcurrencyLimit())
5
- def testConcurrencyLimit(nameOfTest, callablePytest):
6
- callablePytest()
7
-
8
- @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_oopsieKwargsie())
9
- def testOopsieKwargsie(nameOfTest, callablePytest):
10
- callablePytest()
File without changes
File without changes