ser-standardizer 0.1.0__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.
- ser_standardizer-0.1.0/LICENSE +21 -0
- ser_standardizer-0.1.0/PKG-INFO +166 -0
- ser_standardizer-0.1.0/README.md +116 -0
- ser_standardizer-0.1.0/pyproject.toml +51 -0
- ser_standardizer-0.1.0/setup.cfg +4 -0
- ser_standardizer-0.1.0/src/ser_standardizer/__init__.py +1 -0
- ser_standardizer-0.1.0/src/ser_standardizer/api.py +83 -0
- ser_standardizer-0.1.0/src/ser_standardizer/features/__init__.py +3 -0
- ser_standardizer-0.1.0/src/ser_standardizer/features/extractor.py +97 -0
- ser_standardizer-0.1.0/src/ser_standardizer/features/preprocessing.py +92 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/__init__.py +21 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/crema_d.py +125 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/emodb.py +121 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/emouerj.py +72 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/iemocap.py +140 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/msp_improv.py +147 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/msp_podcast.py +118 -0
- ser_standardizer-0.1.0/src/ser_standardizer/loaders/ravdess.py +111 -0
- ser_standardizer-0.1.0/src/ser_standardizer/main.py +40 -0
- ser_standardizer-0.1.0/src/ser_standardizer.egg-info/PKG-INFO +166 -0
- ser_standardizer-0.1.0/src/ser_standardizer.egg-info/SOURCES.txt +23 -0
- ser_standardizer-0.1.0/src/ser_standardizer.egg-info/dependency_links.txt +1 -0
- ser_standardizer-0.1.0/src/ser_standardizer.egg-info/entry_points.txt +2 -0
- ser_standardizer-0.1.0/src/ser_standardizer.egg-info/requires.txt +12 -0
- ser_standardizer-0.1.0/src/ser_standardizer.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vinicius Suaiden
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ser-standardizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A tool for standardizing Speech Emotion Recognition (SER) datasets and extracting acoustic features.
|
|
5
|
+
Author-email: Vinicius Suaiden <vinicius.suaiden@usp.br>, Miguel Arjona Ramirez <maramire@usp.br>, Wesley Beccaro <wesleybeccaro@usp.br>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Vinicius Suaiden
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/ViniciusSuaiden/ser-standardizer
|
|
29
|
+
Project-URL: Repository, https://github.com/ViniciusSuaiden/ser-standardizer
|
|
30
|
+
Project-URL: Issues, https://github.com/ViniciusSuaiden/ser-standardizer/issues
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
35
|
+
Requires-Python: >=3.8
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: pandas>=1.3.0
|
|
39
|
+
Requires-Dist: audb>=1.0.0
|
|
40
|
+
Provides-Extra: features
|
|
41
|
+
Requires-Dist: numpy<3.0,>=1.21.0; extra == "features"
|
|
42
|
+
Requires-Dist: soundfile>=0.12.0; extra == "features"
|
|
43
|
+
Requires-Dist: scipy>=1.10.0; extra == "features"
|
|
44
|
+
Requires-Dist: ipython>=9.0.0; extra == "features"
|
|
45
|
+
Requires-Dist: ipywidgets>=8.1.8; extra == "features"
|
|
46
|
+
Requires-Dist: noisereduce>=3.0.3; extra == "features"
|
|
47
|
+
Requires-Dist: tqdm>=4.60.0; extra == "features"
|
|
48
|
+
Requires-Dist: opensmile>=2.4.0; extra == "features"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# SER-Standardizer
|
|
52
|
+
|
|
53
|
+
🇺🇸 English | [🇧🇷 Português](README.pt-br.md)
|
|
54
|
+
|
|
55
|
+
[](https://opensource.org/licenses/MIT)
|
|
56
|
+
[](https://www.python.org/downloads/)
|
|
57
|
+
|
|
58
|
+
**A modular infrastructure for dataset standardization and feature extraction in Speech Emotion Recognition (SER).**
|
|
59
|
+
|
|
60
|
+
**SER-Standardizer** is a Python package built to unify the formatting of multiple speech databases, mitigating metadata heterogeneity. Its main goal is to facilitate *cross-corpus training* and generalization testing of machine learning models.
|
|
61
|
+
|
|
62
|
+
Beyond the standardization pipeline, the tool provides an extension module for acoustic feature extraction (spectral and prosodic) by integrating the **openSMILE** framework.
|
|
63
|
+
|
|
64
|
+
## 📋 Currently Supported Datasets
|
|
65
|
+
|
|
66
|
+
The tool currently supports loading and standardizing the following databases:
|
|
67
|
+
|
|
68
|
+
* **CREMA-D** | **IEMOCAP** | **EmoUERJ** (PT-BR) | **MSP-IMPROV** | **MSP-PODCAST** | **RAVDESS** | **EmoDB** (DE)
|
|
69
|
+
|
|
70
|
+
Per-dataset download and directory-layout guides are available under [`docs/en/`](docs/en/).
|
|
71
|
+
|
|
72
|
+
## 🚀 Installation
|
|
73
|
+
|
|
74
|
+
### Prerequisites
|
|
75
|
+
* Python 3.8 or higher
|
|
76
|
+
* Libraries listed in `pyproject.toml` (installed automatically).
|
|
77
|
+
|
|
78
|
+
### Installation from source
|
|
79
|
+
Clone this repository to install it with `pip` (read more about it below):
|
|
80
|
+
```
|
|
81
|
+
git clone https://github.com/ViniciusSuaiden/ser-standardizer.git
|
|
82
|
+
cd ser-standardizer
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The library was designed with flexibility in mind, offering two installation modes to optimize the use of computational resources:
|
|
86
|
+
|
|
87
|
+
#### 1. Base Installation (Lightweight Metadata)
|
|
88
|
+
Installs only the essential packages (`pandas`, `audb`) for tabular standardization and filtering.
|
|
89
|
+
```bash
|
|
90
|
+
pip install .
|
|
91
|
+
```
|
|
92
|
+
#### 2. Full Installation (With Signal Processing)
|
|
93
|
+
Installs the heavy dependencies required for the feature extraction and audio manipulation module (opensmile, noisereduce).
|
|
94
|
+
```bash
|
|
95
|
+
pip install '.[features]'
|
|
96
|
+
```
|
|
97
|
+
### 💻 How to Use
|
|
98
|
+
The workflow is split into three logical steps:
|
|
99
|
+
|
|
100
|
+
**1. Preprocessing (CLI)**
|
|
101
|
+
|
|
102
|
+
After installation, the `ser-std` command becomes available in your terminal.
|
|
103
|
+
To standardize a specific dataset:
|
|
104
|
+
```bash
|
|
105
|
+
# Example: crema_d
|
|
106
|
+
ser-std --dataset crema_d --input_dir /path/to/crema
|
|
107
|
+
```
|
|
108
|
+
The standardized `.csv` file is written to the user's home folder, with a specific name for each database.
|
|
109
|
+
|
|
110
|
+
**2. Data Handling and Filtering (Python)**
|
|
111
|
+
|
|
112
|
+
After preprocessing, use the library to load, filter and manipulate the audio directly in your code or Jupyter Notebook.
|
|
113
|
+
```python
|
|
114
|
+
import ser_standardizer as ser
|
|
115
|
+
|
|
116
|
+
# Loads multiple standardized datasets into a single DataFrame
|
|
117
|
+
df = ser.load_datasets(["crema_d", "ravdess", "iemocap"])
|
|
118
|
+
|
|
119
|
+
# Filters by dataset, emotion and gender
|
|
120
|
+
df_target = ser.filters(
|
|
121
|
+
df,
|
|
122
|
+
datasets=['ravdess', 'iemocap'],
|
|
123
|
+
emotions=['anger', 'sad'],
|
|
124
|
+
genders=['female']
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
**3. Acoustic Feature Extraction (Requires [features])**
|
|
128
|
+
|
|
129
|
+
The extraction module automates digital signal processing. It includes the option to apply a Voice Activity Detection (VAD) mask based on RMS energy to extract features strictly from the effective phonation segments.
|
|
130
|
+
```python
|
|
131
|
+
# Batch extraction via openSMILE
|
|
132
|
+
# Supported 'feature_set' values: 'eGeMAPS' or 'ComParE'
|
|
133
|
+
features_df = ser.extract_features(
|
|
134
|
+
df_target,
|
|
135
|
+
feature_set='eGeMAPS',
|
|
136
|
+
feature_level='functionals', # 'functionals' (1 vector per utterance) or 'llds' (per frame)
|
|
137
|
+
use_vad=False, # VAD (silence removal) is only allowed with feature_level='llds'
|
|
138
|
+
sr=16000 # Sampling rate
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# The result preserves the original indices, making concatenation easy
|
|
142
|
+
import pandas as pd
|
|
143
|
+
final_dataset = pd.concat([df_target, features_df], axis=1)
|
|
144
|
+
|
|
145
|
+
# Group-wise normalization (z-score) — removes corpus/speaker offsets.
|
|
146
|
+
# Pass the grouping column (e.g., 'dataset' or 'speaker_id').
|
|
147
|
+
X_corpus = ser.normalize(features_df, df_target['dataset'])
|
|
148
|
+
X_speaker = ser.normalize(features_df, df_target['speaker_id'])
|
|
149
|
+
|
|
150
|
+
# --- Extra Utilities ---
|
|
151
|
+
# Listen to the audio in a Jupyter Notebook
|
|
152
|
+
ser.listen(df_target, index=42)
|
|
153
|
+
|
|
154
|
+
# Load the raw audio as a NumPy array (e.g., to feed neural networks)
|
|
155
|
+
audio_array = ser.load_audio(df_target, index=42)
|
|
156
|
+
|
|
157
|
+
# Load a batch of audio with zero-padding
|
|
158
|
+
batch = ser.load_batch(df_target, begin=0, end=32)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### ✍️ Authors
|
|
162
|
+
Vinicius Suaiden - USP - vinicius.suaiden@usp.br
|
|
163
|
+
|
|
164
|
+
Miguel Arjona Ramirez - USP - maramire@usp.br
|
|
165
|
+
|
|
166
|
+
Wesley Beccaro - USP - wesleybeccaro@usp.br
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# SER-Standardizer
|
|
2
|
+
|
|
3
|
+
🇺🇸 English | [🇧🇷 Português](README.pt-br.md)
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
|
|
8
|
+
**A modular infrastructure for dataset standardization and feature extraction in Speech Emotion Recognition (SER).**
|
|
9
|
+
|
|
10
|
+
**SER-Standardizer** is a Python package built to unify the formatting of multiple speech databases, mitigating metadata heterogeneity. Its main goal is to facilitate *cross-corpus training* and generalization testing of machine learning models.
|
|
11
|
+
|
|
12
|
+
Beyond the standardization pipeline, the tool provides an extension module for acoustic feature extraction (spectral and prosodic) by integrating the **openSMILE** framework.
|
|
13
|
+
|
|
14
|
+
## 📋 Currently Supported Datasets
|
|
15
|
+
|
|
16
|
+
The tool currently supports loading and standardizing the following databases:
|
|
17
|
+
|
|
18
|
+
* **CREMA-D** | **IEMOCAP** | **EmoUERJ** (PT-BR) | **MSP-IMPROV** | **MSP-PODCAST** | **RAVDESS** | **EmoDB** (DE)
|
|
19
|
+
|
|
20
|
+
Per-dataset download and directory-layout guides are available under [`docs/en/`](docs/en/).
|
|
21
|
+
|
|
22
|
+
## 🚀 Installation
|
|
23
|
+
|
|
24
|
+
### Prerequisites
|
|
25
|
+
* Python 3.8 or higher
|
|
26
|
+
* Libraries listed in `pyproject.toml` (installed automatically).
|
|
27
|
+
|
|
28
|
+
### Installation from source
|
|
29
|
+
Clone this repository to install it with `pip` (read more about it below):
|
|
30
|
+
```
|
|
31
|
+
git clone https://github.com/ViniciusSuaiden/ser-standardizer.git
|
|
32
|
+
cd ser-standardizer
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The library was designed with flexibility in mind, offering two installation modes to optimize the use of computational resources:
|
|
36
|
+
|
|
37
|
+
#### 1. Base Installation (Lightweight Metadata)
|
|
38
|
+
Installs only the essential packages (`pandas`, `audb`) for tabular standardization and filtering.
|
|
39
|
+
```bash
|
|
40
|
+
pip install .
|
|
41
|
+
```
|
|
42
|
+
#### 2. Full Installation (With Signal Processing)
|
|
43
|
+
Installs the heavy dependencies required for the feature extraction and audio manipulation module (opensmile, noisereduce).
|
|
44
|
+
```bash
|
|
45
|
+
pip install '.[features]'
|
|
46
|
+
```
|
|
47
|
+
### 💻 How to Use
|
|
48
|
+
The workflow is split into three logical steps:
|
|
49
|
+
|
|
50
|
+
**1. Preprocessing (CLI)**
|
|
51
|
+
|
|
52
|
+
After installation, the `ser-std` command becomes available in your terminal.
|
|
53
|
+
To standardize a specific dataset:
|
|
54
|
+
```bash
|
|
55
|
+
# Example: crema_d
|
|
56
|
+
ser-std --dataset crema_d --input_dir /path/to/crema
|
|
57
|
+
```
|
|
58
|
+
The standardized `.csv` file is written to the user's home folder, with a specific name for each database.
|
|
59
|
+
|
|
60
|
+
**2. Data Handling and Filtering (Python)**
|
|
61
|
+
|
|
62
|
+
After preprocessing, use the library to load, filter and manipulate the audio directly in your code or Jupyter Notebook.
|
|
63
|
+
```python
|
|
64
|
+
import ser_standardizer as ser
|
|
65
|
+
|
|
66
|
+
# Loads multiple standardized datasets into a single DataFrame
|
|
67
|
+
df = ser.load_datasets(["crema_d", "ravdess", "iemocap"])
|
|
68
|
+
|
|
69
|
+
# Filters by dataset, emotion and gender
|
|
70
|
+
df_target = ser.filters(
|
|
71
|
+
df,
|
|
72
|
+
datasets=['ravdess', 'iemocap'],
|
|
73
|
+
emotions=['anger', 'sad'],
|
|
74
|
+
genders=['female']
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
**3. Acoustic Feature Extraction (Requires [features])**
|
|
78
|
+
|
|
79
|
+
The extraction module automates digital signal processing. It includes the option to apply a Voice Activity Detection (VAD) mask based on RMS energy to extract features strictly from the effective phonation segments.
|
|
80
|
+
```python
|
|
81
|
+
# Batch extraction via openSMILE
|
|
82
|
+
# Supported 'feature_set' values: 'eGeMAPS' or 'ComParE'
|
|
83
|
+
features_df = ser.extract_features(
|
|
84
|
+
df_target,
|
|
85
|
+
feature_set='eGeMAPS',
|
|
86
|
+
feature_level='functionals', # 'functionals' (1 vector per utterance) or 'llds' (per frame)
|
|
87
|
+
use_vad=False, # VAD (silence removal) is only allowed with feature_level='llds'
|
|
88
|
+
sr=16000 # Sampling rate
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# The result preserves the original indices, making concatenation easy
|
|
92
|
+
import pandas as pd
|
|
93
|
+
final_dataset = pd.concat([df_target, features_df], axis=1)
|
|
94
|
+
|
|
95
|
+
# Group-wise normalization (z-score) — removes corpus/speaker offsets.
|
|
96
|
+
# Pass the grouping column (e.g., 'dataset' or 'speaker_id').
|
|
97
|
+
X_corpus = ser.normalize(features_df, df_target['dataset'])
|
|
98
|
+
X_speaker = ser.normalize(features_df, df_target['speaker_id'])
|
|
99
|
+
|
|
100
|
+
# --- Extra Utilities ---
|
|
101
|
+
# Listen to the audio in a Jupyter Notebook
|
|
102
|
+
ser.listen(df_target, index=42)
|
|
103
|
+
|
|
104
|
+
# Load the raw audio as a NumPy array (e.g., to feed neural networks)
|
|
105
|
+
audio_array = ser.load_audio(df_target, index=42)
|
|
106
|
+
|
|
107
|
+
# Load a batch of audio with zero-padding
|
|
108
|
+
batch = ser.load_batch(df_target, begin=0, end=32)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### ✍️ Authors
|
|
112
|
+
Vinicius Suaiden - USP - vinicius.suaiden@usp.br
|
|
113
|
+
|
|
114
|
+
Miguel Arjona Ramirez - USP - maramire@usp.br
|
|
115
|
+
|
|
116
|
+
Wesley Beccaro - USP - wesleybeccaro@usp.br
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ser-standardizer"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A tool for standardizing Speech Emotion Recognition (SER) datasets and extracting acoustic features."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Vinicius Suaiden", email = "vinicius.suaiden@usp.br" },
|
|
12
|
+
{ name = "Miguel Arjona Ramirez", email = "maramire@usp.br" },
|
|
13
|
+
{ name = "Wesley Beccaro", email = "wesleybeccaro@usp.br" },
|
|
14
|
+
]
|
|
15
|
+
license = { file = "LICENSE" }
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pandas>=1.3.0",
|
|
24
|
+
"audb>=1.0.0"
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.8"
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
features = [
|
|
30
|
+
"numpy>=1.21.0,<3.0",
|
|
31
|
+
"soundfile>=0.12.0",
|
|
32
|
+
"scipy>=1.10.0",
|
|
33
|
+
"ipython>=9.0.0",
|
|
34
|
+
"ipywidgets>=8.1.8",
|
|
35
|
+
"noisereduce>=3.0.3",
|
|
36
|
+
"tqdm>=4.60.0",
|
|
37
|
+
"opensmile>=2.4.0"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/ViniciusSuaiden/ser-standardizer"
|
|
42
|
+
Repository = "https://github.com/ViniciusSuaiden/ser-standardizer"
|
|
43
|
+
Issues = "https://github.com/ViniciusSuaiden/ser-standardizer/issues"
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
ser-std = "ser_standardizer.main:main"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
where = ["src"]
|
|
50
|
+
include = ["ser_standardizer*"]
|
|
51
|
+
namespaces = false
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .api import load_datasets, load_audio, load_batch, listen, filters, normalize, extract_features
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def load_datasets(datasets):
|
|
6
|
+
if isinstance(datasets, str):
|
|
7
|
+
target_datasets = [datasets]
|
|
8
|
+
else:
|
|
9
|
+
target_datasets = datasets
|
|
10
|
+
|
|
11
|
+
dataframes = []
|
|
12
|
+
DATA_DIR = os.path.join(Path.home(), ".ser_standardizer_data")
|
|
13
|
+
|
|
14
|
+
for name in target_datasets:
|
|
15
|
+
file_path = os.path.join(DATA_DIR, f"process_{name}.csv")
|
|
16
|
+
if not file_path or not os.path.exists(file_path):
|
|
17
|
+
raise FileNotFoundError(
|
|
18
|
+
f"O dataset '{name}' não foi encontrado. "
|
|
19
|
+
f"Certifique-se de ter rodado: ser-std --dataset {name} ..."
|
|
20
|
+
)
|
|
21
|
+
df = pd.read_csv(file_path)
|
|
22
|
+
dataframes.append(df)
|
|
23
|
+
|
|
24
|
+
if not dataframes:
|
|
25
|
+
raise ValueError("Nenhum dataset válido foi fornecido.")
|
|
26
|
+
|
|
27
|
+
return pd.concat(dataframes, ignore_index=True)
|
|
28
|
+
|
|
29
|
+
def filters(df, datasets=None, emotions=None, genders=None, languages=None, speakers=None):
|
|
30
|
+
mask = pd.Series([True] * len(df), index=df.index)
|
|
31
|
+
|
|
32
|
+
def to_list(val):
|
|
33
|
+
if isinstance(val, str):
|
|
34
|
+
return [val]
|
|
35
|
+
return val
|
|
36
|
+
|
|
37
|
+
if datasets:
|
|
38
|
+
target = [x.lower() for x in to_list(datasets)]
|
|
39
|
+
mask = mask & df['dataset'].str.lower().isin(target)
|
|
40
|
+
|
|
41
|
+
if emotions:
|
|
42
|
+
target = [x.lower() for x in to_list(emotions)]
|
|
43
|
+
mask = mask & df['emotion'].str.lower().isin(target)
|
|
44
|
+
|
|
45
|
+
if genders:
|
|
46
|
+
target = [x.lower() for x in to_list(genders)]
|
|
47
|
+
mask = mask & df['gender'].str.lower().isin(target)
|
|
48
|
+
|
|
49
|
+
if languages:
|
|
50
|
+
target = [x.lower() for x in to_list(languages)]
|
|
51
|
+
mask = mask & df['language'].str.lower().isin(target)
|
|
52
|
+
|
|
53
|
+
if speakers:
|
|
54
|
+
target = [x.lower() for x in to_list(speakers)]
|
|
55
|
+
mask = mask & df['speaker_id'].str.lower().isin(target)
|
|
56
|
+
|
|
57
|
+
print(f"Filtro aplicado. Restaram {len(df[mask])} amostras.")
|
|
58
|
+
return df[mask].copy()
|
|
59
|
+
|
|
60
|
+
def normalize(features, group, eps=1e-8):
|
|
61
|
+
"""Aplica z-score por grupo em `features` (ex.: por dataset ou locutor)."""
|
|
62
|
+
g = group.reindex(features.index)
|
|
63
|
+
out = features.groupby(g).transform(
|
|
64
|
+
lambda c: (c - c.mean()) / (c.std(ddof=0) + eps)
|
|
65
|
+
)
|
|
66
|
+
return out.fillna(0.0)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
from .features.preprocessing import listen, load_audio, load_batch
|
|
70
|
+
from .features.extractor import extract_features
|
|
71
|
+
FEATURES_AVAILABLE = True
|
|
72
|
+
|
|
73
|
+
except ImportError:
|
|
74
|
+
FEATURES_AVAILABLE = False
|
|
75
|
+
|
|
76
|
+
def _missing_deps_error(*args, **kwargs):
|
|
77
|
+
raise ImportError(
|
|
78
|
+
"Esta função requer o submódulo de extração acústica.\n"
|
|
79
|
+
"Para utilizá-la, reinstale o pacote com as dependências completas:\n\n"
|
|
80
|
+
" pip install '.[features]'"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
listen = load_audio = load_batch = extract_features = _missing_deps_error
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from tqdm.auto import tqdm
|
|
4
|
+
import opensmile
|
|
5
|
+
from .preprocessing import load_audio, remove_silence
|
|
6
|
+
|
|
7
|
+
def extract_features(df, feature_set='eGeMAPS', feature_level='functionals', use_vad=False, sr=16000):
|
|
8
|
+
"""
|
|
9
|
+
Extrai características acústicas utilizando o openSMILE.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
df: DataFrame contendo a coluna 'file_path'.
|
|
13
|
+
feature_set: 'eGeMAPS' ou 'ComParE' (padrões validados na literatura).
|
|
14
|
+
feature_level: 'functionals' (estatísticas globais por arquivo) ou 'llds' (nível de frame).
|
|
15
|
+
use_vad: Se True, remove silêncios baseados na energia RMS.
|
|
16
|
+
ATENÇÃO: Permitido apenas quando feature_level='llds'.
|
|
17
|
+
sr: Taxa de amostragem esperada.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Um novo pd.DataFrame contendo as features.
|
|
21
|
+
Para 'functionals', mantém o índice original.
|
|
22
|
+
Para 'llds', adiciona o índice original ('file_idx') para cruzamento de metadados.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
if use_vad and feature_level.lower() == 'functionals':
|
|
26
|
+
raise ValueError(
|
|
27
|
+
"Erro de Metodologia: A aplicação de VAD no áudio bruto antes da extração "
|
|
28
|
+
"de funcionais corrompe as métricas temporais padronizadas do openSMILE "
|
|
29
|
+
"(como taxa de fala e proporção de pausas) e introduz artefatos no espectro. \n"
|
|
30
|
+
"-> Para extrair 'functionals' padronizados, defina use_vad=False. \n"
|
|
31
|
+
"-> O uso de use_vad=True é permitido apenas para extração no nível de frame ('llds')."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if feature_set.lower() == 'egemaps':
|
|
35
|
+
fset = opensmile.FeatureSet.eGeMAPSv02
|
|
36
|
+
elif feature_set.lower() == 'compare':
|
|
37
|
+
fset = opensmile.FeatureSet.ComParE_2016
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError("Conjunto não suportado. Escolha 'eGeMAPS' ou 'ComParE'.")
|
|
40
|
+
|
|
41
|
+
# 2. Definir o Nível da Extração
|
|
42
|
+
if feature_level.lower() == 'functionals':
|
|
43
|
+
flevel = opensmile.FeatureLevel.Functionals
|
|
44
|
+
level_name = "Functionals"
|
|
45
|
+
elif feature_level.lower() in ['lld', 'llds']:
|
|
46
|
+
flevel = opensmile.FeatureLevel.LowLevelDescriptors
|
|
47
|
+
level_name = "LowLevelDescriptors"
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError("Nível não suportado. Escolha 'functionals' ou 'llds'.")
|
|
50
|
+
|
|
51
|
+
print(f"Inicializando openSMILE ({feature_set} - {level_name})...")
|
|
52
|
+
smile = opensmile.Smile(
|
|
53
|
+
feature_set=fset,
|
|
54
|
+
feature_level=flevel,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
features_list = []
|
|
58
|
+
|
|
59
|
+
for idx in tqdm(df.index, desc=f"Extraindo características ({level_name})"):
|
|
60
|
+
file_path = df.loc[idx, 'file_path']
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if use_vad:
|
|
64
|
+
audio = load_audio(df, idx, sr=sr)
|
|
65
|
+
audio_clean = remove_silence(audio, sr=sr)
|
|
66
|
+
|
|
67
|
+
if len(audio_clean) == 0:
|
|
68
|
+
audio_clean = np.zeros(sr)
|
|
69
|
+
|
|
70
|
+
feat_df = smile.process_signal(audio_clean, sr)
|
|
71
|
+
else:
|
|
72
|
+
feat_df = smile.process_file(file_path)
|
|
73
|
+
|
|
74
|
+
if flevel == opensmile.FeatureLevel.Functionals:
|
|
75
|
+
feat_series = feat_df.iloc[0].rename(idx)
|
|
76
|
+
features_list.append(feat_series)
|
|
77
|
+
else:
|
|
78
|
+
feat_df['file_idx'] = idx
|
|
79
|
+
features_list.append(feat_df)
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"Erro na extração do índice {idx}: {e}")
|
|
83
|
+
if flevel == opensmile.FeatureLevel.Functionals:
|
|
84
|
+
features_list.append(pd.Series(dtype=float, name=idx))
|
|
85
|
+
else:
|
|
86
|
+
features_list.append(pd.DataFrame({'file_idx': [idx]}))
|
|
87
|
+
|
|
88
|
+
print("Consolidando matriz de features...")
|
|
89
|
+
|
|
90
|
+
if flevel == opensmile.FeatureLevel.Functionals:
|
|
91
|
+
result_df = pd.DataFrame(features_list)
|
|
92
|
+
else:
|
|
93
|
+
result_df = pd.concat(features_list)
|
|
94
|
+
if 'file_idx' in result_df.columns:
|
|
95
|
+
result_df = result_df.set_index('file_idx', append=True)
|
|
96
|
+
|
|
97
|
+
return result_df
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import soundfile as sf
|
|
2
|
+
import scipy.signal
|
|
3
|
+
import numpy as np
|
|
4
|
+
from IPython.display import Audio, display
|
|
5
|
+
from tqdm.auto import tqdm
|
|
6
|
+
import noisereduce as nr
|
|
7
|
+
|
|
8
|
+
def scipy_load(path, target_sr=None):
|
|
9
|
+
|
|
10
|
+
audio, native_sr = sf.read(path)
|
|
11
|
+
|
|
12
|
+
if audio.ndim > 1:
|
|
13
|
+
audio = np.mean(audio, axis=1)
|
|
14
|
+
|
|
15
|
+
if target_sr is not None and native_sr != target_sr:
|
|
16
|
+
num_samples = round(len(audio) * float(target_sr) / native_sr)
|
|
17
|
+
audio = scipy.signal.resample(audio, num_samples)
|
|
18
|
+
return audio, target_sr
|
|
19
|
+
|
|
20
|
+
return audio, native_sr
|
|
21
|
+
|
|
22
|
+
def remove_silence(audio, sr=16000, frame_length=2048, hop_length=512, threshold=0.25):
|
|
23
|
+
audio_clean = nr.reduce_noise(y=audio, sr=sr)
|
|
24
|
+
|
|
25
|
+
n_frames = 1 + (len(audio_clean) - frame_length) // hop_length
|
|
26
|
+
if n_frames <= 0:
|
|
27
|
+
return audio_clean
|
|
28
|
+
|
|
29
|
+
rms = np.array([
|
|
30
|
+
np.sqrt(np.mean(audio_clean[i*hop_length : i*hop_length + frame_length] ** 2))
|
|
31
|
+
for i in range(n_frames)
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
max_rms = np.max(rms)
|
|
35
|
+
if max_rms == 0:
|
|
36
|
+
return np.array([])
|
|
37
|
+
|
|
38
|
+
rms_norm = rms / max_rms
|
|
39
|
+
speech_mask = rms_norm > threshold
|
|
40
|
+
|
|
41
|
+
sample_mask = np.zeros(len(audio_clean), dtype=bool)
|
|
42
|
+
for i, active in enumerate(speech_mask):
|
|
43
|
+
if active:
|
|
44
|
+
start = i * hop_length
|
|
45
|
+
end = min(start + hop_length, len(audio_clean))
|
|
46
|
+
sample_mask[start:end] = True
|
|
47
|
+
|
|
48
|
+
return audio_clean[sample_mask]
|
|
49
|
+
|
|
50
|
+
def listen(df, index):
|
|
51
|
+
if index not in df.index:
|
|
52
|
+
print(f"Aviso: Não há nenhum áudio disponível com o índice {index}.")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
path = df.loc[index, 'file_path']
|
|
56
|
+
emotion = df.loc[index, 'emotion']
|
|
57
|
+
|
|
58
|
+
print(f"Tocando áudio {index} | Emoção: {emotion}")
|
|
59
|
+
display(Audio(filename=path))
|
|
60
|
+
|
|
61
|
+
def load_audio(df, index, sr=16000):
|
|
62
|
+
if index not in df.index:
|
|
63
|
+
raise IndexError("Índice não encontrado no Dataset.")
|
|
64
|
+
path = df.loc[index, 'file_path']
|
|
65
|
+
audio, _ = scipy_load(path, target_sr=sr)
|
|
66
|
+
return audio
|
|
67
|
+
|
|
68
|
+
def load_batch(df, begin, end, sr=16000, max_1000=True):
|
|
69
|
+
qtd = end - begin
|
|
70
|
+
if qtd > 1000 and max_1000:
|
|
71
|
+
raise ValueError(f"Você tentou carregar {qtd} áudios. O limite é 1000. Defina max_1000=False se tiver certeza.")
|
|
72
|
+
|
|
73
|
+
batch = []
|
|
74
|
+
subset_indices = df.index[begin:end]
|
|
75
|
+
|
|
76
|
+
for idx in tqdm(subset_indices, desc="Carregando áudios"):
|
|
77
|
+
try:
|
|
78
|
+
audio = load_audio(df, idx, sr)
|
|
79
|
+
batch.append(audio)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"Erro ao carregar índice {idx}: {e}")
|
|
82
|
+
batch.append(None)
|
|
83
|
+
|
|
84
|
+
max_len = max(len(x) for x in batch if x is not None)
|
|
85
|
+
padded_batch = np.zeros((len(batch), max_len), dtype=np.float32)
|
|
86
|
+
|
|
87
|
+
for i, audio in enumerate(batch):
|
|
88
|
+
if audio is not None:
|
|
89
|
+
padded_batch[i, :audio.shape[0]] = audio
|
|
90
|
+
|
|
91
|
+
return padded_batch
|
|
92
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .crema_d import process as process_crema
|
|
2
|
+
from .iemocap import process as process_iemocap
|
|
3
|
+
from .msp_improv import process as process_msp
|
|
4
|
+
from .msp_podcast import process as process_msp_podcast
|
|
5
|
+
from .emouerj import process as process_emouerj
|
|
6
|
+
from .ravdess import process as process_ravdess
|
|
7
|
+
from .emodb import process as process_emodb
|
|
8
|
+
|
|
9
|
+
# Dicionário que mapeia o nome do dataset (string) para a função de processamento
|
|
10
|
+
DATASET_LOADERS = {
|
|
11
|
+
'crema_d': process_crema,
|
|
12
|
+
'iemocap': process_iemocap,
|
|
13
|
+
'msp_improv': process_msp,
|
|
14
|
+
'msp_podcast': process_msp_podcast,
|
|
15
|
+
'emouerj': process_emouerj,
|
|
16
|
+
'ravdess': process_ravdess,
|
|
17
|
+
'emodb': process_emodb
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def get_available_datasets():
|
|
21
|
+
return list(DATASET_LOADERS.keys())
|