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.
- analyzeAudio/__init__.py +5 -5
- analyzeAudio/analyzersUseFilename.py +144 -144
- analyzeAudio/analyzersUseSpectrogram.py +14 -12
- analyzeAudio/analyzersUseTensor.py +4 -4
- analyzeAudio/analyzersUseWaveform.py +11 -10
- analyzeAudio/audioAspectsRegistry.py +166 -167
- analyzeAudio/pythonator.py +78 -77
- analyzeaudio-0.0.12.dist-info/LICENSE +407 -0
- {analyzeAudio-0.0.11.dist-info → analyzeaudio-0.0.12.dist-info}/METADATA +24 -29
- analyzeaudio-0.0.12.dist-info/RECORD +16 -0
- {analyzeAudio-0.0.11.dist-info → analyzeaudio-0.0.12.dist-info}/WHEEL +1 -1
- analyzeaudio-0.0.12.dist-info/entry_points.txt +2 -0
- tests/test_audioAspectsRegistry.py +0 -1
- tests/test_other.py +7 -10
- analyzeAudio-0.0.11.dist-info/RECORD +0 -14
- {analyzeAudio-0.0.11.dist-info → analyzeaudio-0.0.12.dist-info}/top_level.txt +0 -0
|
@@ -1,195 +1,194 @@
|
|
|
1
|
-
from
|
|
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
|
|
5
|
-
from
|
|
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
|
|
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
|
-
|
|
18
|
+
from typing import TypedDict
|
|
19
19
|
else:
|
|
20
|
-
|
|
20
|
+
TypedDict = dict
|
|
21
21
|
|
|
22
22
|
if __name__ == '__main__':
|
|
23
|
-
|
|
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
|
-
|
|
34
|
-
|
|
32
|
+
analyzer: Callable[..., Any]
|
|
33
|
+
analyzerParameters: list[str]
|
|
35
34
|
|
|
36
|
-
audioAspects:
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def analyzeAudioFile(pathFilename:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def getListAvailableAudioAspects() ->
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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)
|
analyzeAudio/pythonator.py
CHANGED
|
@@ -1,85 +1,86 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any
|
|
3
3
|
import json
|
|
4
4
|
import numpy
|
|
5
5
|
|
|
6
6
|
def pythonizeFFprobe(FFprobeJSON_utf8: str):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|