ser-standardizer 0.1.0__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.
@@ -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,3 @@
1
+ # Módulo opcional (instale com `pip install "ser-standardizer[features]"`).
2
+ # Sem imports aqui: os submódulos são importados sob demanda em api.py,
3
+ # dentro de try/except ImportError.
@@ -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())
@@ -0,0 +1,125 @@
1
+ import os
2
+ import pandas as pd
3
+
4
+ SENTENCE_MAP = {
5
+ "IEO": "It's eleven o'clock.",
6
+ "TIE": "That is exactly what happened.",
7
+ "IOM": "I'm on my way to the meeting.",
8
+ "IWW": "I wonder what this is about.",
9
+ "TAI": "The airplane is almost full.",
10
+ "MTI": "Maybe tomorrow it will be cold.",
11
+ "IWL": "I would like a new alarm clock.",
12
+ "ITH": "I think I have a cold.",
13
+ "DFA": "Don't forget a jacket.",
14
+ "ITS": "I think I'll wear my new suit.",
15
+ "TSI": "The surface is slick.",
16
+ "WSI": "We'll stop in a couple of minutes."
17
+ }
18
+
19
+ EMOTION_MAP = {
20
+ 'A': 'Anger',
21
+ 'D': 'Disgust',
22
+ 'F': 'Fear',
23
+ 'H': 'Happy',
24
+ 'S': 'Sad',
25
+ 'N': 'Neutral',
26
+ }
27
+
28
+ def process(base_dir):
29
+ """
30
+ Processa todos os arquivos de áudio do dataset CREMA-D,
31
+ consolida os metadados e retorna um DataFrame padronizado.
32
+
33
+ Args:
34
+ base_dir (str): O caminho raiz para o dataset CREMA-D
35
+ (ex: ".../data/CREMA-D")
36
+
37
+ Returns:
38
+ pandas.DataFrame: Um DataFrame com metadados padronizados
39
+ ou None se o processamento falhar.
40
+ """
41
+ print("Iniciando processamento do CREMA-D...")
42
+
43
+ # --- Definir Caminhos ---
44
+ AUDIO_DIR = os.path.join(base_dir, "AudioWAV")
45
+ DEMOGRAPHICS_FILE = os.path.join(base_dir, "VideoDemographics.csv")
46
+ RESPONSES_FILE = os.path.join(base_dir, "finishedResponses.csv")
47
+
48
+ # --- Carregar Metadados ---
49
+ print("Carregando arquivos de metadados do CREMA-D...")
50
+ try:
51
+ demographics_df = pd.read_csv(DEMOGRAPHICS_FILE).set_index('ActorID')
52
+
53
+ raw_responses_df = pd.read_csv(RESPONSES_FILE, usecols=['queryType', 'clipName', 'respEmo'])
54
+ voice_responses_df = raw_responses_df[raw_responses_df['queryType'] == 1].copy()
55
+
56
+ votes_df = pd.crosstab(voice_responses_df['clipName'], voice_responses_df['respEmo'])
57
+ emotion_columns = ['A', 'D', 'F', 'H', 'S', 'N']
58
+
59
+ votes_df['perceived_emotion_label'] = votes_df[emotion_columns].idxmax(axis=1)
60
+ print("Arquivos de metadados do CREMA-D carregados com sucesso.")
61
+
62
+ except Exception as e:
63
+ print(f"Erro ao carregar os arquivos CSV do CREMA-D: {e}")
64
+ return None
65
+
66
+ # --- Iterar sobre os arquivos de áudio ---
67
+ print(f"Iniciando processamento dos arquivos em: {AUDIO_DIR}")
68
+ all_file_data = []
69
+
70
+ for filename in os.listdir(AUDIO_DIR):
71
+ if filename.endswith('.wav'):
72
+ full_file_path = os.path.join(AUDIO_DIR, filename)
73
+ file_key = os.path.splitext(filename)[0]
74
+
75
+ try:
76
+ # --- A. Extrair dados do NOME DO ARQUIVO ---
77
+ parts = file_key.split('_')
78
+ actor_id = int(parts[0])
79
+ sentence_id = parts[1]
80
+ intended_emotion_code = parts[2]
81
+
82
+ # --- B. Buscar dados dos CSVs carregados ---
83
+ speaker_gender = demographics_df.loc[actor_id]['Sex']
84
+ sentence_text = SENTENCE_MAP.get(sentence_id, "Desconhecido")
85
+ perceived_emotion_code = votes_df.loc[file_key]['perceived_emotion_label']
86
+
87
+ # --- C. Padronizar e Consolidar ---
88
+ file_info = {
89
+ 'dataset': 'CREMA_D',
90
+ 'file_path': full_file_path,
91
+ # 'filename': filename,
92
+ 'speaker_id': f"crema_{actor_id}",
93
+ 'gender': speaker_gender,
94
+ 'language': 'en',
95
+ 'sentence_text': sentence_text,
96
+ 'emotion': EMOTION_MAP.get(perceived_emotion_code, perceived_emotion_code),
97
+ }
98
+
99
+ all_file_data.append(file_info)
100
+
101
+ except KeyError as e:
102
+ print(f"Aviso: Não foi possível encontrar metadados para {filename}. Chave não encontrada: {e}. Pulando...")
103
+ except Exception as e:
104
+ print(f"Erro ao processar o arquivo {filename}: {e}. Pulando...")
105
+
106
+ print(f"Processamento do CREMA-D concluído. {len(all_file_data)} arquivos processados.")
107
+
108
+ if not all_file_data:
109
+ return None
110
+
111
+ # --- 4. Criar e Retornar o DataFrame ---
112
+ final_df = pd.DataFrame(all_file_data)
113
+
114
+ # Reordenar colunas para um layout padrão
115
+ standard_columns = [
116
+ 'dataset', 'file_path', 'speaker_id', 'gender',
117
+ 'emotion', 'sentence_text', 'language'
118
+ ]
119
+
120
+ # Adiciona colunas que podem não existir no DF final (caso dê erro em todas)
121
+ for col in standard_columns:
122
+ if col not in final_df:
123
+ final_df[col] = pd.NA
124
+
125
+ return final_df[standard_columns]
@@ -0,0 +1,121 @@
1
+ import os
2
+ import pandas as pd
3
+
4
+ # EmoDB (Berlin Database of Emotional Speech) — alemão.
5
+ # Formato do nome do arquivo: SSTTTEV.wav
6
+ # SS -> número do locutor (2 dígitos)
7
+ # TTT -> código do texto (ex.: a01, b03)
8
+ # E -> letra da emoção (alemão)
9
+ # V -> versão (a, b, c, ...) quando há mais de uma gravação
10
+
11
+ # Letras de emoção (iniciais em alemão) mapeadas para o rótulo padrão do repo.
12
+ # W = Wut (raiva), L = Langeweile (tédio), E = Ekel (nojo),
13
+ # A = Angst (medo), F = Freude (alegria), T = Trauer (tristeza), N = Neutral
14
+ EMOTION_MAP = {
15
+ 'W': 'Anger',
16
+ 'L': 'Boredom',
17
+ 'E': 'Disgust',
18
+ 'A': 'Fear',
19
+ 'F': 'Happy',
20
+ 'T': 'Sad',
21
+ 'N': 'Neutral',
22
+ }
23
+
24
+ # Gênero de cada um dos 10 locutores (conforme documentação oficial do EmoDB).
25
+ SPEAKER_GENDER = {
26
+ '03': 'Male',
27
+ '08': 'Female',
28
+ '09': 'Female',
29
+ '10': 'Male',
30
+ '11': 'Male',
31
+ '12': 'Male',
32
+ '13': 'Female',
33
+ '14': 'Female',
34
+ '15': 'Male',
35
+ '16': 'Female',
36
+ }
37
+
38
+ # Frases em alemão associadas a cada código de texto.
39
+ SENTENCE_MAP = {
40
+ 'a01': "Der Lappen liegt auf dem Eisschrank.",
41
+ 'a02': "Das will sie am Mittwoch abgeben.",
42
+ 'a04': "Heute abend könnte ich es ihm sagen.",
43
+ 'a05': "Das schwarze Stück Papier befindet sich da oben neben dem Holzstück.",
44
+ 'a07': "In sieben Stunden wird es soweit sein.",
45
+ 'b01': "Was sind denn das für Tüten, die da unter dem Tisch stehen?",
46
+ 'b02': "Sie haben es gerade hochgetragen und jetzt gehen sie wieder runter.",
47
+ 'b03': "An den Wochenenden bin ich jetzt immer nach Hause gefahren und habe Agnes besucht.",
48
+ 'b09': "Ich will das eben wegbringen und dann mit Karl was trinken gehen.",
49
+ 'b10': "Die wird auf dem Platz sein, wo wir sie immer hinlegen.",
50
+ }
51
+
52
+
53
+ def process(base_dir):
54
+ """
55
+ Processa o dataset EmoDB (Berlin Database of Emotional Speech).
56
+
57
+ A estrutura esperada é uma pasta raiz contendo os arquivos .wav
58
+ (as pastas oficiais EmoDB_1/ e EmoDB_2/ podem ser extraídas dentro dela;
59
+ a busca é recursiva).
60
+
61
+ Args:
62
+ base_dir (str): Caminho raiz onde os .wav do EmoDB estão salvos.
63
+
64
+ Returns:
65
+ pandas.DataFrame: DataFrame padronizado ou None se nada for encontrado.
66
+ """
67
+ print("Iniciando processamento do EmoDB...")
68
+
69
+ all_file_data = []
70
+
71
+ for root, dirs, files in os.walk(base_dir):
72
+ for filename in files:
73
+ if not filename.lower().endswith('.wav'):
74
+ continue
75
+
76
+ file_key = os.path.splitext(filename)[0]
77
+
78
+ # Nome padrão do EmoDB tem 7 caracteres: SS TTT E V
79
+ if len(file_key) != 7:
80
+ print(f"Nome fora do padrão, ignorado: {filename}")
81
+ continue
82
+
83
+ speaker_id = file_key[0:2]
84
+ text_code = file_key[2:5]
85
+ emotion_code = file_key[5]
86
+ # file_key[6] é a versão, não usada no schema padrão.
87
+
88
+ emotion = EMOTION_MAP.get(emotion_code)
89
+ if emotion is None:
90
+ print(f"Emoção desconhecida ({emotion_code}) em {filename}, ignorado.")
91
+ continue
92
+
93
+ file_info = {
94
+ 'dataset': 'EmoDB',
95
+ 'file_path': os.path.join(root, filename),
96
+ 'speaker_id': f"emodb_{speaker_id}",
97
+ 'gender': SPEAKER_GENDER.get(speaker_id, pd.NA),
98
+ 'language': 'de',
99
+ 'emotion': emotion,
100
+ 'sentence_text': SENTENCE_MAP.get(text_code, "Unknown"),
101
+ }
102
+
103
+ all_file_data.append(file_info)
104
+
105
+ print(f"Processamento do EmoDB concluído. {len(all_file_data)} arquivos processados.")
106
+
107
+ if not all_file_data:
108
+ return None
109
+
110
+ final_df = pd.DataFrame(all_file_data)
111
+
112
+ standard_columns = [
113
+ 'dataset', 'file_path', 'speaker_id', 'gender',
114
+ 'emotion', 'sentence_text', 'language'
115
+ ]
116
+
117
+ for col in standard_columns:
118
+ if col not in final_df:
119
+ final_df[col] = pd.NA
120
+
121
+ return final_df[standard_columns]
@@ -0,0 +1,72 @@
1
+ import pandas as pd
2
+ import audb
3
+
4
+ EMOTION_MAP = {
5
+ 'anger': 'Anger',
6
+ 'happiness': 'Happy',
7
+ 'sadness': 'Sad',
8
+ 'neutral': 'Neutral'
9
+ }
10
+
11
+ def process(base_dir=None):
12
+ """
13
+ Carrega o dataset EmoUERJ via audb e padroniza para o formato do projeto.
14
+
15
+ Args:
16
+ base_dir (str): Opcional neste caso, pois o audb gerencia o cache.
17
+ Pode ser usado para definir onde o audb salva os arquivos
18
+ se você configurar o cache localmente.
19
+
20
+ Returns:
21
+ pandas.DataFrame: DataFrame padronizado.
22
+ """
23
+ print("Iniciando processamento do EmoUERJ via audb...")
24
+
25
+ try:
26
+ db = audb.load("emouerj", version="1.0.0", verbose=False)
27
+
28
+ original_df = db.get("emotion", ["gender", "speaker", "take"])
29
+
30
+ original_df['file_path'] = original_df.index
31
+ original_df = original_df.reset_index(drop=True)
32
+
33
+ print(f"EmoUERJ carregado. Processando {len(original_df)} arquivos...")
34
+
35
+ all_file_data = []
36
+
37
+ for row in original_df.itertuples():
38
+ try:
39
+ original_emotion = row.emotion
40
+ speaker = row.speaker
41
+ gender = row.gender.capitalize()
42
+ full_path = row.file_path
43
+ file_info = {
44
+ 'dataset': 'EmoUERJ',
45
+ 'file_path': full_path,
46
+ 'speaker_id': f"emouerj_{speaker}",
47
+ 'gender': gender,
48
+ 'language': 'pt-br',
49
+ 'sentence_text': pd.NA,
50
+ 'emotion': EMOTION_MAP.get(original_emotion, original_emotion),
51
+ }
52
+ all_file_data.append(file_info)
53
+ except Exception as e:
54
+ print(f"Erro ao processar linha do EmoUERJ: {e}")
55
+
56
+ final_df = pd.DataFrame(all_file_data)
57
+
58
+ standard_columns = [
59
+ 'dataset', 'file_path', 'speaker_id', 'gender',
60
+ 'emotion', 'sentence_text', 'language'
61
+ ]
62
+
63
+ for col in standard_columns:
64
+ if col not in final_df:
65
+ final_df[col] = pd.NA
66
+
67
+ print(f"Processamento do EmoUERJ concluído. {len(final_df)} arquivos.")
68
+ return final_df[standard_columns]
69
+
70
+ except Exception as e:
71
+ print(f"Falha crítica ao carregar EmoUERJ via audb: {e}")
72
+ return None