analyzeAudio 0.0.11__py3-none-any.whl → 0.0.12__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.
@@ -1,195 +1,194 @@
1
- from Z0Z_tools import defineConcurrencyLimit, oopsieKwargsie
1
+ from collections.abc import Callable, Sequence
2
2
  from concurrent.futures import ProcessPoolExecutor, as_completed
3
3
  from numpy.typing import NDArray
4
- from tqdm.auto import tqdm
5
- from typing import Any, Callable, cast, Dict, List, Optional, ParamSpec, Sequence, TypeAlias, TYPE_CHECKING, TypeVar, Union
4
+ from typing import Any, cast, ParamSpec, TypeAlias, TYPE_CHECKING, TypeVar
5
+ from Z0Z_tools import defineConcurrencyLimit, oopsieKwargsie, stft
6
6
  import cachetools
7
7
  import inspect
8
8
  import librosa
9
9
  import multiprocessing
10
10
  import numpy
11
- import os
11
+ from os import PathLike
12
12
  import pathlib
13
13
  import soundfile
14
14
  import torch
15
15
  import warnings
16
16
 
17
17
  if TYPE_CHECKING:
18
- from typing import TypedDict
18
+ from typing import TypedDict
19
19
  else:
20
- TypedDict = dict
20
+ TypedDict = dict
21
21
 
22
22
  if __name__ == '__main__':
23
- multiprocessing.set_start_method('spawn')
23
+ multiprocessing.set_start_method('spawn')
24
24
 
25
25
  warnings.filterwarnings('ignore', category=UserWarning, module='torchmetrics', message='.*fast=True.*')
26
26
 
27
27
  parameterSpecifications = ParamSpec('parameterSpecifications')
28
28
  typeReturned = TypeVar('typeReturned')
29
29
 
30
- subclassTarget: TypeAlias = numpy.ndarray
31
30
  audioAspect: TypeAlias = str
32
31
  class analyzersAudioAspects(TypedDict):
33
- analyzer: Callable[..., Any]
34
- analyzerParameters: List[str]
32
+ analyzer: Callable[..., Any]
33
+ analyzerParameters: list[str]
35
34
 
36
- audioAspects: Dict[audioAspect, analyzersAudioAspects] = {}
35
+ audioAspects: dict[audioAspect, analyzersAudioAspects] = {}
37
36
  """A register of 1) measurable aspects of audio data, 2) analyzer functions to measure audio aspects, 3) and parameters of analyzer functions."""
38
37
 
39
38
  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.
42
-
43
- Parameters:
44
- aspectName: The audio aspect that the registrar will enter into the register, `audioAspects`.
45
- """
46
-
47
- def registrar(registrant: Callable[parameterSpecifications, typeReturned]) -> Callable[parameterSpecifications, typeReturned]:
48
- """
49
- `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
-
51
- Parameters:
52
- registrant: The function that analyzes an aspect of audio data.
53
-
54
- Note:
55
- `registrar` does not change the behavior of `registrant`, the analyzer function.
56
- """
57
- audioAspects[aspectName] = {
58
- 'analyzer': registrant,
59
- 'analyzerParameters': inspect.getfullargspec(registrant).args
60
- }
61
-
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
- if isinstance(registrant.__annotations__.get('return', type(None)), type) and issubclass(registrant.__annotations__.get('return', type(None)), subclassTarget): # maybe someday I will understand what all of this statement means
65
- def registrationAudioAspectMean(*arguments: parameterSpecifications.args, **keywordArguments: parameterSpecifications.kwargs) -> numpy.floating[Any]:
66
- """
67
- `registrar` updates the registry with a new analyzer function that calculates the mean of the analyzer's numpy.ndarray result.
68
-
69
- Returns:
70
- mean: Mean value of the analyzer's numpy.ndarray result.
71
- """
72
- aspectValue = registrant(*arguments, **keywordArguments)
73
- return numpy.mean(cast(subclassTarget, aspectValue))
74
- # return aspectValue.mean()
75
- audioAspects[f"{aspectName} mean"] = {
76
- 'analyzer': registrationAudioAspectMean,
77
- 'analyzerParameters': inspect.getfullargspec(registrant).args
78
- }
79
- return registrant
80
- return registrar
81
-
82
- def analyzeAudioFile(pathFilename: Union[str, os.PathLike[Any]], listAspectNames: List[str]) -> List[Union[str, float, NDArray[Any]]]:
83
- """
84
- Analyzes an audio file for specified aspects and returns the results.
85
-
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.
89
-
90
- Returns:
91
- listAspectValues: A list of analyzed values in the same order as `listAspectNames`.
92
- """
93
- pathlib.Path(pathFilename).stat() # raises FileNotFoundError if the file does not exist
94
- dictionaryAspectsAnalyzed: Dict[str, Union[str, float, NDArray[Any]]] = {aspectName: 'not found' for aspectName in listAspectNames}
95
- """Despite returning a list, use a dictionary to preserve the order of the listAspectNames.
96
- Similarly, 'not found' ensures the returned list length == len(listAspectNames)"""
97
-
98
- # waveform, sampleRate = librosa.load(path=str(pathFilename), sr=None, mono=False)
99
- with soundfile.SoundFile(pathFilename) as readSoundFile:
100
- sampleRate: int = readSoundFile.samplerate
101
- waveform = readSoundFile.read()
102
- waveform = waveform.T
103
-
104
- # I need "lazy" loading
105
- tryAgain = True
106
- while tryAgain: # `tenacity`?
107
- try:
108
- tensorAudio = torch.from_numpy(waveform) # memory-sharing
109
- tryAgain = False
110
- except RuntimeError as ERRORmessage:
111
- if 'negative stride' in str(ERRORmessage):
112
- waveform = waveform.copy() # not memory-sharing
113
- tryAgain = True
114
- else:
115
- raise ERRORmessage
116
-
117
- spectrogram = librosa.stft(y=waveform)
118
- spectrogramMagnitude, DISCARDEDphase = librosa.magphase(D=spectrogram)
119
- spectrogramPower = numpy.absolute(spectrogram) ** 2
120
-
121
- pytorchOnCPU = not torch.cuda.is_available() # False if GPU available, True if not
122
-
123
- for aspectName in listAspectNames:
124
- if aspectName in audioAspects:
125
- analyzer = audioAspects[aspectName]['analyzer']
126
- analyzerParameters = audioAspects[aspectName]['analyzerParameters']
127
- dictionaryAspectsAnalyzed[aspectName] = analyzer(*map(vars().get, analyzerParameters))
128
-
129
- return [dictionaryAspectsAnalyzed[aspectName] for aspectName in listAspectNames]
130
-
131
- def analyzeAudioListPathFilenames(listPathFilenames: Union[Sequence[str], Sequence[os.PathLike[Any]]], listAspectNames: List[str], CPUlimit: Optional[Union[int, float, bool]] = None) -> List[List[Union[str, float, NDArray[Any]]]]:
132
- """
133
- Analyzes a list of audio files for specified aspects of the individual files and returns the results.
134
-
135
- Parameters:
136
- listPathFilenames: A list of paths to the audio files to be analyzed.
137
- listAspectNames: A list of aspect names to analyze in each audio file.
138
- CPUlimit (gluttonous resource usage): whether and how to limit the CPU usage. See notes for details.
139
-
140
- Returns:
141
- rowsListFilenameAspectValues: A list of lists, where each inner list contains the filename and
142
- analyzed values corresponding to the specified aspects, which are in the same order as `listAspectNames`.
143
-
144
- Limits on CPU usage CPUlimit:
145
- False, None, or 0: No limits on CPU usage; uses all available CPUs. All other values will potentially limit CPU usage.
146
- True: Yes, limit the CPU usage; limits to 1 CPU.
147
- Integer >= 1: Limits usage to the specified number of CPUs.
148
- Decimal value (float) between 0 and 1: Fraction of total CPUs to use.
149
- Decimal value (float) between -1 and 0: Fraction of CPUs to *not* use.
150
- Integer <= -1: Subtract the absolute value from total CPUs.
151
-
152
- You can save the data with `Z0Z_tools.dataTabularTOpathFilenameDelimited()`.
153
- For example,
154
-
155
- ```python
156
- dataTabularTOpathFilenameDelimited(
157
- pathFilename = pathFilename,
158
- tableRows = rowsListFilenameAspectValues, # The return of this function
159
- tableColumns = ['File'] + listAspectNames # A parameter of this function
160
- )
161
- ```
162
-
163
- Nevertheless, I aspire to improve `analyzeAudioListPathFilenames` by radically improving the structure of the returned data.
164
- """
165
- rowsListFilenameAspectValues = []
166
-
167
- if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
168
- CPUlimit = oopsieKwargsie(CPUlimit)
169
- max_workers = defineConcurrencyLimit(CPUlimit)
170
-
171
- with ProcessPoolExecutor(max_workers=max_workers) as concurrencyManager:
172
- dictionaryConcurrency = {concurrencyManager.submit(analyzeAudioFile, pathFilename, listAspectNames)
173
- : pathFilename
174
- for pathFilename in listPathFilenames}
175
-
176
- for claimTicket in as_completed(dictionaryConcurrency):
177
- cacheAudioAnalyzers.pop(dictionaryConcurrency[claimTicket], None)
178
- listAspectValues = claimTicket.result()
179
- rowsListFilenameAspectValues.append(
180
- [str(pathlib.PurePath(dictionaryConcurrency[claimTicket]).as_posix())]
181
- + listAspectValues)
182
-
183
- return rowsListFilenameAspectValues
184
-
185
- def getListAvailableAudioAspects() -> List[str]:
186
- """
187
- Returns a sorted list of audio aspect names. All valid values for the parameter `listAspectNames`, for example,
188
- are returned by this function.
189
-
190
- Returns:
191
- listAvailableAudioAspects: The list of aspect names registered in `audioAspects`.
192
- """
193
- return sorted(audioAspects.keys())
194
-
195
- cacheAudioAnalyzers = cachetools.LRUCache(maxsize=256)
39
+ """
40
+ A function to "decorate" a registrant-analyzer function and the aspect of audio data it can analyze.
41
+
42
+ Parameters:
43
+ aspectName: The audio aspect that the registrar will enter into the register, `audioAspects`.
44
+ """
45
+
46
+ def registrar(registrant: Callable[parameterSpecifications, typeReturned]) -> Callable[parameterSpecifications, typeReturned]:
47
+ """
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.
49
+
50
+ Parameters:
51
+ registrant: The function that analyzes an aspect of audio data.
52
+
53
+ Note:
54
+ `registrar` does not change the behavior of `registrant`, the analyzer function.
55
+ """
56
+ audioAspects[aspectName] = {
57
+ 'analyzer': registrant,
58
+ 'analyzerParameters': inspect.getfullargspec(registrant).args
59
+ }
60
+
61
+ # if registrant.__annotations__.get('return') is not None and issubclass(registrant.__annotations__['return'], subclassTarget): # maybe someday I will understand why this doesn't work
62
+ # 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
63
+ 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
64
+ def registrationAudioAspectMean(*arguments: parameterSpecifications.args, **keywordArguments: parameterSpecifications.kwargs) -> numpy.floating[Any]:
65
+ """
66
+ `registrar` updates the registry with a new analyzer function that calculates the mean of the analyzer's numpy.ndarray result.
67
+
68
+ Returns:
69
+ mean: Mean value of the analyzer's numpy.ndarray result.
70
+ """
71
+ aspectValue = registrant(*arguments, **keywordArguments)
72
+ return numpy.mean(cast(NDArray[Any], aspectValue))
73
+ # return aspectValue.mean()
74
+ audioAspects[f"{aspectName} mean"] = {
75
+ 'analyzer': registrationAudioAspectMean,
76
+ 'analyzerParameters': inspect.getfullargspec(registrant).args
77
+ }
78
+ return registrant
79
+ return registrar
80
+
81
+ def analyzeAudioFile(pathFilename: str | PathLike[Any], listAspectNames: list[str]) -> list[str | float | NDArray[Any]]:
82
+ """
83
+ Analyzes an audio file for specified aspects and returns the results.
84
+
85
+ Parameters:
86
+ pathFilename: The path to the audio file to be analyzed.
87
+ listAspectNames: A list of aspect names to analyze in the audio file.
88
+
89
+ Returns:
90
+ listAspectValues: A list of analyzed values in the same order as `listAspectNames`.
91
+ """
92
+ pathlib.Path(pathFilename).stat() # raises FileNotFoundError if the file does not exist
93
+ dictionaryAspectsAnalyzed: dict[str, str | float | NDArray[Any]] = {aspectName: 'not found' for aspectName in listAspectNames}
94
+ """Despite returning a list, use a dictionary to preserve the order of the listAspectNames.
95
+ Similarly, 'not found' ensures the returned list length == len(listAspectNames)"""
96
+
97
+ with soundfile.SoundFile(pathFilename) as readSoundFile:
98
+ sampleRate: int = readSoundFile.samplerate
99
+ waveform = readSoundFile.read(dtype='float32').astype(numpy.float32)
100
+ waveform = waveform.T
101
+
102
+ # I need "lazy" loading
103
+ tryAgain = True
104
+ while tryAgain: # `tenacity`?
105
+ try:
106
+ tensorAudio = torch.from_numpy(waveform) # memory-sharing
107
+ tryAgain = False
108
+ except RuntimeError as ERRORmessage:
109
+ if 'negative stride' in str(ERRORmessage):
110
+ waveform = waveform.copy() # not memory-sharing
111
+ tryAgain = True
112
+ else:
113
+ raise ERRORmessage
114
+
115
+ spectrogram = stft(waveform, sampleRate=sampleRate)
116
+ spectrogramMagnitude = numpy.absolute(spectrogram)
117
+ spectrogramPower = spectrogramMagnitude ** 2
118
+
119
+ pytorchOnCPU = not torch.cuda.is_available() # False if GPU available, True if not
120
+
121
+ for aspectName in listAspectNames:
122
+ if aspectName in audioAspects:
123
+ analyzer = audioAspects[aspectName]['analyzer']
124
+ analyzerParameters = audioAspects[aspectName]['analyzerParameters']
125
+ dictionaryAspectsAnalyzed[aspectName] = analyzer(*map(vars().get, analyzerParameters))
126
+
127
+ return [dictionaryAspectsAnalyzed[aspectName] for aspectName in listAspectNames]
128
+
129
+ def analyzeAudioListPathFilenames(listPathFilenames: Sequence[str] | Sequence[PathLike[Any]], listAspectNames: list[str], CPUlimit: int | float | bool | None = None) -> list[list[str | float | NDArray[Any]]]:
130
+ """
131
+ Analyzes a list of audio files for specified aspects of the individual files and returns the results.
132
+
133
+ Parameters:
134
+ listPathFilenames: A list of paths to the audio files to be analyzed.
135
+ listAspectNames: A list of aspect names to analyze in each audio file.
136
+ CPUlimit (gluttonous resource usage): whether and how to limit the CPU usage. See notes for details.
137
+
138
+ Returns:
139
+ rowsListFilenameAspectValues: A list of lists, where each inner list contains the filename and
140
+ analyzed values corresponding to the specified aspects, which are in the same order as `listAspectNames`.
141
+
142
+ You can save the data with `Z0Z_tools.dataTabularTOpathFilenameDelimited()`.
143
+ For example,
144
+
145
+ ```python
146
+ dataTabularTOpathFilenameDelimited(
147
+ pathFilename = pathFilename,
148
+ tableRows = rowsListFilenameAspectValues, # The return of this function
149
+ tableColumns = ['File'] + listAspectNames # A parameter of this function
150
+ )
151
+ ```
152
+
153
+ Nevertheless, I aspire to improve `analyzeAudioListPathFilenames` by radically improving the structure of the returned data.
154
+
155
+ Limits on CPU usage CPUlimit:
156
+ False, None, or 0: No limits on CPU usage; uses all available CPUs. All other values will potentially limit CPU usage.
157
+ True: Yes, limit the CPU usage; limits to 1 CPU.
158
+ Integer >= 1: Limits usage to the specified number of CPUs.
159
+ Decimal value (float) between 0 and 1: Fraction of total CPUs to use.
160
+ Decimal value (float) between -1 and 0: Fraction of CPUs to *not* use.
161
+ Integer <= -1: Subtract the absolute value from total CPUs.
162
+
163
+ """
164
+ rowsListFilenameAspectValues: list[list[str | float | NDArray[Any]]] = []
165
+
166
+ if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
167
+ CPUlimit = oopsieKwargsie(CPUlimit)
168
+ max_workers = defineConcurrencyLimit(CPUlimit)
169
+
170
+ with ProcessPoolExecutor(max_workers=max_workers) as concurrencyManager:
171
+ dictionaryConcurrency = {concurrencyManager.submit(analyzeAudioFile, pathFilename, listAspectNames)
172
+ : pathFilename
173
+ for pathFilename in listPathFilenames}
174
+
175
+ for claimTicket in as_completed(dictionaryConcurrency):
176
+ cacheAudioAnalyzers.pop(dictionaryConcurrency[claimTicket], None)
177
+ listAspectValues = claimTicket.result()
178
+ rowsListFilenameAspectValues.append(
179
+ [str(pathlib.PurePath(dictionaryConcurrency[claimTicket]).as_posix())]
180
+ + listAspectValues)
181
+
182
+ return rowsListFilenameAspectValues
183
+
184
+ def getListAvailableAudioAspects() -> list[str]:
185
+ """
186
+ Returns a sorted list of audio aspect names. All valid values for the parameter `listAspectNames`, for example,
187
+ are returned by this function.
188
+
189
+ Returns:
190
+ listAvailableAudioAspects: The list of aspect names registered in `audioAspects`.
191
+ """
192
+ return sorted(audioAspects.keys())
193
+
194
+ cacheAudioAnalyzers: cachetools.LRUCache[str | PathLike[Any], NDArray[Any]] = cachetools.LRUCache(maxsize=256)
@@ -1,85 +1,86 @@
1
1
  from collections import defaultdict
2
- from typing import Dict, Any, Tuple, Union
2
+ from typing import Any
3
3
  import json
4
4
  import numpy
5
5
 
6
6
  def pythonizeFFprobe(FFprobeJSON_utf8: str):
7
- FFroot: Dict[str, Any] = json.loads(FFprobeJSON_utf8)
8
- Z0Z_dictionaries: Dict[str, Union[numpy.ndarray, Dict[str, numpy.ndarray]]] = {}
9
- if 'packets_and_frames' in FFroot: # Divide into 'packets' and 'frames'
10
- FFroot = defaultdict(list, FFroot)
11
- for packetOrFrame in FFroot['packets_and_frames']:
12
- if 'type' in packetOrFrame:
13
- FFroot[section := packetOrFrame['type'] + 's'].append(packetOrFrame)
14
- del FFroot[section][-1]['type']
15
- else:
16
- raise ValueError("'packets_and_frames' for the win!")
17
- del FFroot['packets_and_frames']
7
+ FFroot: dict[str, Any] = json.loads(FFprobeJSON_utf8)
8
+ Z0Z_dictionaries: dict[str, numpy.ndarray[Any, Any] | dict[str, numpy.ndarray[Any, Any]]] = {}
9
+ if 'packets_and_frames' in FFroot: # Divide into 'packets' and 'frames'
10
+ FFroot = defaultdict(list, FFroot)
11
+ for packetOrFrame in FFroot['packets_and_frames']:
12
+ if 'type' in packetOrFrame:
13
+ FFroot[section := packetOrFrame['type'] + 's'].append(packetOrFrame)
14
+ del FFroot[section][-1]['type']
15
+ else:
16
+ raise ValueError("'packets_and_frames' for the win!")
17
+ del FFroot['packets_and_frames']
18
18
 
19
- Z0Z_register = [
20
- 'aspectralstats',
21
- 'astats',
22
- 'r128',
23
- 'signalstats',
24
- ]
25
- if 'frames' in FFroot:
26
- leftCrumbs = False
27
- listTuplesBlackdetect = []
28
- for indexFrame, FFframe in enumerate(FFroot['frames']):
29
- if 'tags' in FFframe:
30
- if 'lavfi.black_start' in FFframe['tags']:
31
- listTuplesBlackdetect.append(float(FFframe['tags']['lavfi.black_start']))
32
- del FFframe['tags']['lavfi.black_start']
33
- if 'lavfi.black_end' in FFframe['tags']:
34
- listTuplesBlackdetect[-1] = (listTuplesBlackdetect[-1], float(FFframe['tags']['lavfi.black_end']))
35
- del FFframe['tags']['lavfi.black_end']
19
+ Z0Z_register = [
20
+ 'aspectralstats',
21
+ 'astats',
22
+ 'r128',
23
+ 'signalstats',
24
+ ]
25
+ leftCrumbs = False
26
+ if 'frames' in FFroot:
27
+ leftCrumbs = False
28
+ listTuplesBlackdetect: list[float | tuple[float]] = []
29
+ for indexFrame, FFframe in enumerate(FFroot['frames']):
30
+ if 'tags' in FFframe:
31
+ if 'lavfi.black_start' in FFframe['tags']:
32
+ listTuplesBlackdetect.append(float(FFframe['tags']['lavfi.black_start']))
33
+ del FFframe['tags']['lavfi.black_start']
34
+ if 'lavfi.black_end' in FFframe['tags']:
35
+ listTuplesBlackdetect[-1] = (listTuplesBlackdetect[-1], float(FFframe['tags']['lavfi.black_end']))
36
+ del FFframe['tags']['lavfi.black_end']
36
37
 
37
- # This is not the way to do it
38
- for keyName, keyValue in FFframe['tags'].items():
39
- if 'lavfi' in (keyNameDeconstructed := keyName.split('.'))[0]:
40
- channel = None
41
- if (registrant := keyNameDeconstructed[1]) in Z0Z_register:
42
- keyNameDeconstructed = keyNameDeconstructed[2:]
43
- if keyNameDeconstructed[0].isdigit():
44
- channel = int(keyNameDeconstructed[0])
45
- keyNameDeconstructed = keyNameDeconstructed[1:]
46
- statistic = '.'.join(keyNameDeconstructed)
47
- if channel is None:
48
- while True:
49
- try:
50
- Z0Z_dictionaries[registrant][statistic][indexFrame] = float(keyValue)
51
- break # If successful, exit the loop
52
- except KeyError:
53
- if registrant not in Z0Z_dictionaries:
54
- Z0Z_dictionaries[registrant] = {}
55
- elif statistic not in Z0Z_dictionaries[registrant]:
56
- Z0Z_dictionaries[registrant][statistic] = numpy.zeros((len(FFroot['frames'])))
57
- else:
58
- raise # Re-raise the exception
59
- else:
60
- while True:
61
- try:
62
- Z0Z_dictionaries[registrant][statistic][channel - 1, indexFrame] = float(keyValue)
63
- break # If successful, exit the loop
64
- except KeyError:
65
- if registrant not in Z0Z_dictionaries:
66
- Z0Z_dictionaries[registrant] = {}
67
- elif statistic not in Z0Z_dictionaries[registrant]:
68
- Z0Z_dictionaries[registrant][statistic] = numpy.zeros((channel, len(FFroot['frames'])))
69
- else:
70
- raise # Re-raise the exception
71
- except IndexError:
72
- if channel > Z0Z_dictionaries[registrant][statistic].shape[0]:
73
- Z0Z_dictionaries[registrant][statistic].resize((channel, len(FFroot['frames'])))
74
- else:
75
- raise # Re-raise the exception
38
+ # This is not the way to do it
39
+ for keyName, keyValue in FFframe['tags'].items():
40
+ if 'lavfi' in (keyNameDeconstructed := keyName.split('.'))[0]:
41
+ channel = None
42
+ if (registrant := keyNameDeconstructed[1]) in Z0Z_register:
43
+ keyNameDeconstructed = keyNameDeconstructed[2:]
44
+ if keyNameDeconstructed[0].isdigit():
45
+ channel = int(keyNameDeconstructed[0])
46
+ keyNameDeconstructed = keyNameDeconstructed[1:]
47
+ statistic = '.'.join(keyNameDeconstructed)
48
+ if channel is None:
49
+ while True:
50
+ try:
51
+ Z0Z_dictionaries[registrant][statistic][indexFrame] = float(keyValue)
52
+ break # If successful, exit the loop
53
+ except KeyError:
54
+ if registrant not in Z0Z_dictionaries:
55
+ Z0Z_dictionaries[registrant] = {}
56
+ elif statistic not in Z0Z_dictionaries[registrant]:
57
+ Z0Z_dictionaries[registrant][statistic] = numpy.zeros(len(FFroot['frames']))
58
+ else:
59
+ raise # Re-raise the exception
60
+ else:
61
+ while True:
62
+ try:
63
+ Z0Z_dictionaries[registrant][statistic][channel - 1, indexFrame] = float(keyValue)
64
+ break # If successful, exit the loop
65
+ except KeyError:
66
+ if registrant not in Z0Z_dictionaries:
67
+ Z0Z_dictionaries[registrant] = {}
68
+ elif statistic not in Z0Z_dictionaries[registrant]:
69
+ Z0Z_dictionaries[registrant][statistic] = numpy.zeros((channel, len(FFroot['frames'])))
70
+ else:
71
+ raise # Re-raise the exception
72
+ except IndexError:
73
+ if channel > Z0Z_dictionaries[registrant][statistic].shape[0]:
74
+ Z0Z_dictionaries[registrant][statistic].resize((channel, len(FFroot['frames'])))
75
+ else:
76
+ raise # Re-raise the exception
76
77
 
77
- if not FFframe['tags']: # empty = False
78
- del FFframe['tags']
79
- if FFframe:
80
- leftCrumbs = True
81
- if listTuplesBlackdetect:
82
- Z0Z_dictionaries['blackdetect'] = numpy.array(listTuplesBlackdetect, dtype=[('black_start', numpy.float32), ('black_end', numpy.float32)], copy=False)
83
- if not leftCrumbs:
84
- del FFroot['frames']
85
- return FFroot, Z0Z_dictionaries
78
+ if not FFframe['tags']: # empty = False
79
+ del FFframe['tags']
80
+ if FFframe:
81
+ leftCrumbs = True
82
+ if listTuplesBlackdetect:
83
+ Z0Z_dictionaries['blackdetect'] = numpy.array(listTuplesBlackdetect, dtype=[('black_start', numpy.float32), ('black_end', numpy.float32)], copy=False)
84
+ if not leftCrumbs:
85
+ del FFroot['frames']
86
+ return FFroot, Z0Z_dictionaries