hrlima commited on
Commit
c6c1304
·
verified ·
1 Parent(s): bf5b535

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -32
app.py CHANGED
@@ -1,5 +1,8 @@
1
  import os
2
  import json
 
 
 
3
  import firebase_admin
4
  from firebase_admin import credentials, firestore
5
  from flask import Flask, request, jsonify
@@ -21,23 +24,28 @@ try:
21
  except Exception as e:
22
  print(f"❌ Erro ao inicializar Firebase: {e}")
23
 
24
- # ====== MODELO ======
 
25
  try:
26
- model_pipeline = pipeline("text-classification", model="pysentimiento/robertuito-emotion-analysis")
27
- print("✅ Modelo carregado com sucesso!")
 
 
 
28
  except Exception as e:
29
- print(f"❌ Erro ao carregar modelo: {e}")
30
- model_pipeline = None
31
 
32
- # ====== MAPEAMENTO DE EMOÇÕES ======
33
  emotion_labels = {
34
- "sadness": "tristeza",
35
- "joy": "alegria",
36
- "anger": "raiva",
37
- "fear": "ansiedade",
38
  "disgust": "insegurança",
39
- "surprise": "alegria",
40
- "others": "neutro"
 
 
 
 
41
  }
42
 
43
  # ====== SUGESTÕES ======
@@ -82,7 +90,7 @@ def fallback_emotion(text):
82
 
83
  # ====== AJUSTE HÍBRIDO ======
84
  def hybrid_emotion(text, result):
85
- text_lower = text.lower()
86
  detected = result.get("emotion", "neutro")
87
  max_matches = 0
88
 
@@ -108,47 +116,128 @@ def hybrid_emotion(text, result):
108
  "debug": result.get("debug", "Híbrido aplicado")
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  # ====== ROTA DE ANÁLISE ======
112
  @app.route("/analyze", methods=["POST"])
113
  def analyze():
114
  try:
115
- data = request.get_json()
116
- if not data or "text" not in data:
117
- return jsonify({"error": "Campo 'text' é obrigatório."}), 400
118
-
119
- text = data["text"]
120
-
121
- if not model_pipeline:
122
- return jsonify(fallback_emotion(text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- result = model_pipeline(text, return_all_scores=True)
125
- if not result or len(result) == 0:
126
- return jsonify(fallback_emotion(text))
127
-
128
- scores = {r["label"]: r["score"] for r in result[0]}
129
  top_label = max(scores, key=scores.get)
130
  confidence = round(scores[top_label], 2)
131
  emotion_pt = emotion_labels.get(top_label, "desconhecido")
132
 
133
- # Ajuste especial para "tristeza" muito forte
134
  if emotion_pt == "tristeza" and confidence >= 0.9:
135
  emotion_pt = "depressão"
136
 
 
 
 
137
  base_result = {
138
  "status": "ok",
139
  "emotion": emotion_pt,
140
  "emode": [emotion_pt],
141
  "confidence": confidence,
142
- "probabilities": {emotion_labels.get(k, k): round(v,3) for k,v in scores.items()},
143
- "suggestion": gerar_sugestao(emotion_pt)
 
144
  }
145
 
146
- # Aplica lógica híbrida com fallback de palavras-chave
147
- final_result = hybrid_emotion(text, base_result)
 
 
 
 
 
 
148
  return jsonify(final_result)
149
 
150
  except Exception as e:
151
  return jsonify({"error": str(e)}), 500
 
 
 
 
 
 
 
152
 
153
  if __name__ == "__main__":
154
- app.run(host="0.0.0.0", port=7860)
 
1
  import os
2
  import json
3
+ import base64
4
+ import tempfile
5
+ import requests
6
  import firebase_admin
7
  from firebase_admin import credentials, firestore
8
  from flask import Flask, request, jsonify
 
24
  except Exception as e:
25
  print(f"❌ Erro ao inicializar Firebase: {e}")
26
 
27
+ # ====== MODELO (AUDIO) ======
28
+ # Usamos pipeline de audio-classification com o modelo Whisper fine-tuned fornecido
29
  try:
30
+ audio_pipeline = pipeline(
31
+ task="audio-classification",
32
+ model="firdhokk/speech-emotion-recognition-with-openai-whisper-large-v3"
33
+ )
34
+ print("✅ Modelo de reconhecimento de emoção por voz carregado com sucesso!")
35
  except Exception as e:
36
+ print(f"❌ Erro ao carregar modelo de áudio: {e}")
37
+ audio_pipeline = None
38
 
39
+ # ====== MAPEAMENTO DE EMOÇÕES (ING->PT) ======
40
  emotion_labels = {
41
+ "angry": "raiva",
 
 
 
42
  "disgust": "insegurança",
43
+ "fearful": "ansiedade",
44
+ "happy": "alegria",
45
+ "neutral": "neutro",
46
+ "sad": "tristeza",
47
+ "surprised": "surpreso",
48
+ # fallback caso o label seja diferente
49
  }
50
 
51
  # ====== SUGESTÕES ======
 
90
 
91
  # ====== AJUSTE HÍBRIDO ======
92
  def hybrid_emotion(text, result):
93
+ text_lower = (text or "").lower()
94
  detected = result.get("emotion", "neutro")
95
  max_matches = 0
96
 
 
116
  "debug": result.get("debug", "Híbrido aplicado")
117
  }
118
 
119
+ # ====== HELPERS PARA ÁUDIO ======
120
+ def save_bytes_to_tempfile(bbytes, suffix=".wav"):
121
+ fd, path = tempfile.mkstemp(suffix=suffix)
122
+ os.close(fd)
123
+ with open(path, "wb") as f:
124
+ f.write(bbytes)
125
+ return path
126
+
127
+ def fetch_url_to_tempfile(url):
128
+ r = requests.get(url, timeout=15)
129
+ r.raise_for_status()
130
+ content_type = r.headers.get("content-type", "")
131
+ suffix = ".wav"
132
+ if "mpeg" in content_type or "mp3" in content_type:
133
+ suffix = ".mp3"
134
+ return save_bytes_to_tempfile(r.content, suffix=suffix)
135
+
136
  # ====== ROTA DE ANÁLISE ======
137
  @app.route("/analyze", methods=["POST"])
138
  def analyze():
139
  try:
140
+ # suportar multipart/form-data com file
141
+ audio_path = None
142
+ audio_bytes = None
143
+ data = None
144
+
145
+ # prioridade: arquivos enviados via multipart/form-data
146
+ if "file" in request.files:
147
+ f = request.files["file"]
148
+ audio_bytes = f.read()
149
+
150
+ else:
151
+ # tentar JSON
152
+ try:
153
+ data = request.get_json(silent=True)
154
+ except Exception:
155
+ data = None
156
+
157
+ if data:
158
+ # base64
159
+ if "audio_base64" in data:
160
+ audio_bytes = base64.b64decode(data["audio_base64"])
161
+ # url
162
+ elif "audio_url" in data:
163
+ audio_path = fetch_url_to_tempfile(data["audio_url"])
164
+ # se vier apenas 'text', usar fallback textual
165
+ elif "text" in data and (not audio_bytes and not audio_path):
166
+ text = data["text"]
167
+ return jsonify(fallback_emotion(text))
168
+
169
+ # se temos bytes, salva como tempfile
170
+ if audio_bytes:
171
+ audio_path = save_bytes_to_tempfile(audio_bytes, suffix=".wav")
172
+
173
+ # se não há áudio, retornar erro ou fallback
174
+ if not audio_path:
175
+ # se data com text já foi tratado acima; aqui devolvemos erro pedindo áudio/text
176
+ return jsonify({"error": "Nenhum áudio foi enviado. Envie 'file' (multipart/form-data), ou 'audio_base64'/'audio_url', ou 'text' para fallback."}), 400
177
+
178
+ # ====== Chamar pipeline de áudio ======
179
+ if not audio_pipeline:
180
+ # pipeline indisponível -> tentar extrair texto (se disponível) ou fallback
181
+ # se houver 'text' em JSON, use fallback_emotion
182
+ if data and "text" in data:
183
+ return jsonify(fallback_emotion(data["text"]))
184
+ return jsonify({"error": "Modelo de áudio indisponível no momento."}), 500
185
+
186
+ # A pipeline aceita caminho para arquivo
187
+ raw_result = audio_pipeline(audio_path, top_k=10) # lista de dicts: [{'label':..., 'score':...}, ...]
188
+ # Exemplo: raw_result = [{'label': 'Happy', 'score': 0.9}, ...]
189
+ # Normalizar labels para minúsculas
190
+ scores = {}
191
+ for item in raw_result:
192
+ label = item.get("label", "").lower()
193
+ # alguns modelos usam 'fear' vs 'fearful' etc. padronizar
194
+ if label == "fear":
195
+ label = "fearful"
196
+ scores[label] = float(item.get("score", 0.0))
197
+
198
+ if not scores:
199
+ return jsonify({"error": "Nenhum rótulo retornado pelo modelo."}), 500
200
 
 
 
 
 
 
201
  top_label = max(scores, key=scores.get)
202
  confidence = round(scores[top_label], 2)
203
  emotion_pt = emotion_labels.get(top_label, "desconhecido")
204
 
205
+ # Ajuste especial: se for tristeza muito forte -> 'depressão'
206
  if emotion_pt == "tristeza" and confidence >= 0.9:
207
  emotion_pt = "depressão"
208
 
209
+ # montar probabilidades mapeadas para pt (mantendo somente rótulos conhecidos)
210
+ probabilities_pt = { emotion_labels.get(k, k): round(v, 3) for k, v in scores.items() }
211
+
212
  base_result = {
213
  "status": "ok",
214
  "emotion": emotion_pt,
215
  "emode": [emotion_pt],
216
  "confidence": confidence,
217
+ "probabilities": probabilities_pt,
218
+ "suggestion": gerar_sugestao(emotion_pt),
219
+ "debug": "Modelo de áudio utilizado"
220
  }
221
 
222
+ # Ler (tentar) a transcrição de texto se o modelo retornar (muitos pipelines de audio-classification não transcrevem)
223
+ # Como fallback híbrido, se o usuário mandou também 'text' no JSON, usaremos isso para o híbrido.
224
+ text_for_hybrid = None
225
+ if data and "text" in data:
226
+ text_for_hybrid = data["text"]
227
+
228
+ final_result = hybrid_emotion(text_for_hybrid, base_result) if text_for_hybrid else base_result
229
+
230
  return jsonify(final_result)
231
 
232
  except Exception as e:
233
  return jsonify({"error": str(e)}), 500
234
+ finally:
235
+ # limpar tempfiles (se existirem)
236
+ try:
237
+ if 'audio_path' in locals() and audio_path and os.path.exists(audio_path):
238
+ os.remove(audio_path)
239
+ except Exception:
240
+ pass
241
 
242
  if __name__ == "__main__":
243
+ app.run(host="0.0.0.0", port=int(os.getenv("PORT", 7860)))