Spaces:
Runtime error
Runtime error
Initial commit
Browse files- .gitattributes +16 -32
- API_DOCUMENTATION.md +280 -0
- API_SUMMARY.md +96 -0
- DEPLOYMENT_GUIDE.md +184 -0
- DEPLOYMENT_SUMMARY.md +165 -0
- api_app.py +214 -0
- app.py +17 -0
- app_with_api.py +368 -0
- config.yaml +9 -0
- deploy.sh +152 -0
- test_api.py +232 -0
- test_deployment.py +221 -0
.gitattributes
CHANGED
@@ -1,35 +1,19 @@
|
|
1 |
-
|
2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
-
*.
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.
|
14 |
-
|
15 |
-
|
16 |
-
*.
|
17 |
-
*.
|
18 |
-
*.
|
19 |
-
|
20 |
-
|
|
|
21 |
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
1 |
+
# Modèles Hugging Face
|
|
|
2 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
*.model filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.json filter=lfs diff=lfs merge=lfs -text
|
6 |
+
|
7 |
+
# Fichiers audio
|
8 |
+
*.wav filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.flac filter=lfs diff=lfs merge=lfs -text
|
11 |
+
|
12 |
+
# Fichiers de données
|
13 |
+
*.csv filter=lfs diff=lfs merge=lfs -text
|
14 |
*.pkl filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
16 |
+
|
17 |
+
# Cache et modèles locaux
|
18 |
+
models/ filter=lfs diff=lfs merge=lfs -text
|
19 |
+
hf_model/ filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
API_DOCUMENTATION.md
ADDED
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 🔌 API REST - Analyse de Sentiment Audio
|
2 |
+
|
3 |
+
## 📋 Vue d'ensemble
|
4 |
+
|
5 |
+
L'API REST permet d'intégrer l'analyse de sentiment audio dans vos applications. Elle est accessible via les endpoints suivants :
|
6 |
+
|
7 |
+
**Base URL** : `https://huggingface.co/spaces/<username>/sentiment-audio-analyzer`
|
8 |
+
|
9 |
+
## 🚀 Endpoints disponibles
|
10 |
+
|
11 |
+
### 1. **GET /** - Informations générales
|
12 |
+
```bash
|
13 |
+
curl https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/
|
14 |
+
```
|
15 |
+
|
16 |
+
**Réponse :**
|
17 |
+
```json
|
18 |
+
{
|
19 |
+
"message": "API Multimodale de Transcription & Sentiment",
|
20 |
+
"version": "1.0",
|
21 |
+
"endpoints": {
|
22 |
+
"docs": "/api/docs",
|
23 |
+
"predict": "/api/predict",
|
24 |
+
"health": "/api/health"
|
25 |
+
},
|
26 |
+
"supported_formats": ["wav", "flac", "mp3"]
|
27 |
+
}
|
28 |
+
```
|
29 |
+
|
30 |
+
### 2. **GET /api/health** - Vérification de l'état
|
31 |
+
```bash
|
32 |
+
curl https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/health
|
33 |
+
```
|
34 |
+
|
35 |
+
**Réponse :**
|
36 |
+
```json
|
37 |
+
{
|
38 |
+
"status": "healthy",
|
39 |
+
"models_loaded": true,
|
40 |
+
"timestamp": "2024-01-01T00:00:00Z"
|
41 |
+
}
|
42 |
+
```
|
43 |
+
|
44 |
+
### 3. **POST /api/predict** - Analyse audio
|
45 |
+
```bash
|
46 |
+
curl -X POST "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict" \
|
47 |
+
-F "[email protected]"
|
48 |
+
```
|
49 |
+
|
50 |
+
**Paramètres :**
|
51 |
+
- `file` : Fichier audio (WAV, FLAC, MP3, max 50MB)
|
52 |
+
|
53 |
+
**Réponse :**
|
54 |
+
```json
|
55 |
+
{
|
56 |
+
"transcription": "je suis très content de ce produit",
|
57 |
+
"sentiment": {
|
58 |
+
"négatif": 0.05,
|
59 |
+
"neutre": 0.10,
|
60 |
+
"positif": 0.85
|
61 |
+
},
|
62 |
+
"filename": "audio.wav",
|
63 |
+
"file_size": 123456
|
64 |
+
}
|
65 |
+
```
|
66 |
+
|
67 |
+
### 4. **POST /api/predict_text** - Analyse textuelle
|
68 |
+
```bash
|
69 |
+
curl -X POST "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict_text" \
|
70 |
+
-H "Content-Type: application/json" \
|
71 |
+
-d '{"text": "je suis très content de ce produit"}'
|
72 |
+
```
|
73 |
+
|
74 |
+
**Paramètres :**
|
75 |
+
- `text` : Texte à analyser (string)
|
76 |
+
|
77 |
+
**Réponse :**
|
78 |
+
```json
|
79 |
+
{
|
80 |
+
"text": "je suis très content de ce produit",
|
81 |
+
"sentiment": {
|
82 |
+
"négatif": 0.05,
|
83 |
+
"neutre": 0.10,
|
84 |
+
"positif": 0.85
|
85 |
+
}
|
86 |
+
}
|
87 |
+
```
|
88 |
+
|
89 |
+
## 📖 Exemples d'utilisation
|
90 |
+
|
91 |
+
### Python avec requests
|
92 |
+
```python
|
93 |
+
import requests
|
94 |
+
|
95 |
+
# Analyse audio
|
96 |
+
url = "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict"
|
97 |
+
files = {"file": open("audio.wav", "rb")}
|
98 |
+
response = requests.post(url, files=files)
|
99 |
+
result = response.json()
|
100 |
+
print(f"Transcription: {result['transcription']}")
|
101 |
+
print(f"Sentiment: {result['sentiment']}")
|
102 |
+
|
103 |
+
# Analyse textuelle
|
104 |
+
url = "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict_text"
|
105 |
+
data = {"text": "je suis très content de ce produit"}
|
106 |
+
response = requests.post(url, json=data)
|
107 |
+
result = response.json()
|
108 |
+
print(f"Sentiment: {result['sentiment']}")
|
109 |
+
```
|
110 |
+
|
111 |
+
### JavaScript avec fetch
|
112 |
+
```javascript
|
113 |
+
// Analyse audio
|
114 |
+
const formData = new FormData();
|
115 |
+
formData.append('file', audioFile);
|
116 |
+
|
117 |
+
fetch('https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict', {
|
118 |
+
method: 'POST',
|
119 |
+
body: formData
|
120 |
+
})
|
121 |
+
.then(response => response.json())
|
122 |
+
.then(data => {
|
123 |
+
console.log('Transcription:', data.transcription);
|
124 |
+
console.log('Sentiment:', data.sentiment);
|
125 |
+
});
|
126 |
+
|
127 |
+
// Analyse textuelle
|
128 |
+
fetch('https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict_text', {
|
129 |
+
method: 'POST',
|
130 |
+
headers: {
|
131 |
+
'Content-Type': 'application/json',
|
132 |
+
},
|
133 |
+
body: JSON.stringify({
|
134 |
+
text: 'je suis très content de ce produit'
|
135 |
+
})
|
136 |
+
})
|
137 |
+
.then(response => response.json())
|
138 |
+
.then(data => {
|
139 |
+
console.log('Sentiment:', data.sentiment);
|
140 |
+
});
|
141 |
+
```
|
142 |
+
|
143 |
+
### Node.js avec axios
|
144 |
+
```javascript
|
145 |
+
const axios = require('axios');
|
146 |
+
const FormData = require('form-data');
|
147 |
+
const fs = require('fs');
|
148 |
+
|
149 |
+
// Analyse audio
|
150 |
+
const formData = new FormData();
|
151 |
+
formData.append('file', fs.createReadStream('audio.wav'));
|
152 |
+
|
153 |
+
axios.post('https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict', formData, {
|
154 |
+
headers: formData.getHeaders()
|
155 |
+
})
|
156 |
+
.then(response => {
|
157 |
+
console.log('Transcription:', response.data.transcription);
|
158 |
+
console.log('Sentiment:', response.data.sentiment);
|
159 |
+
});
|
160 |
+
|
161 |
+
// Analyse textuelle
|
162 |
+
axios.post('https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict_text', {
|
163 |
+
text: 'je suis très content de ce produit'
|
164 |
+
})
|
165 |
+
.then(response => {
|
166 |
+
console.log('Sentiment:', response.data.sentiment);
|
167 |
+
});
|
168 |
+
```
|
169 |
+
|
170 |
+
## ⚠️ Gestion des erreurs
|
171 |
+
|
172 |
+
### Erreur 400 - Fichier invalide
|
173 |
+
```json
|
174 |
+
{
|
175 |
+
"detail": "Seuls les fichiers audio WAV/FLAC/MP3 sont acceptés."
|
176 |
+
}
|
177 |
+
```
|
178 |
+
|
179 |
+
### Erreur 400 - Fichier trop volumineux
|
180 |
+
```json
|
181 |
+
{
|
182 |
+
"detail": "Fichier trop volumineux. Taille maximale: 50MB"
|
183 |
+
}
|
184 |
+
```
|
185 |
+
|
186 |
+
### Erreur 500 - Erreur serveur
|
187 |
+
```json
|
188 |
+
{
|
189 |
+
"detail": "Erreur lors de l'analyse: [description de l'erreur]"
|
190 |
+
}
|
191 |
+
```
|
192 |
+
|
193 |
+
## 🔧 Configuration
|
194 |
+
|
195 |
+
### Headers recommandés
|
196 |
+
```bash
|
197 |
+
Content-Type: multipart/form-data # Pour /api/predict
|
198 |
+
Content-Type: application/json # Pour /api/predict_text
|
199 |
+
```
|
200 |
+
|
201 |
+
### Limites
|
202 |
+
- **Taille fichier** : 50MB maximum
|
203 |
+
- **Formats supportés** : WAV, FLAC, MP3
|
204 |
+
- **Langue** : Français (optimisé)
|
205 |
+
- **Rate limiting** : Selon les limites HF Spaces
|
206 |
+
|
207 |
+
## 📊 Codes de réponse
|
208 |
+
|
209 |
+
| Code | Description |
|
210 |
+
|------|-------------|
|
211 |
+
| 200 | Succès |
|
212 |
+
| 400 | Erreur de requête (fichier invalide, trop volumineux) |
|
213 |
+
| 500 | Erreur serveur (modèles, traitement) |
|
214 |
+
|
215 |
+
## 🎯 Cas d'usage
|
216 |
+
|
217 |
+
### 1. **Intégration chatbot**
|
218 |
+
```python
|
219 |
+
def analyze_user_audio(audio_file):
|
220 |
+
response = requests.post(API_URL, files={"file": audio_file})
|
221 |
+
result = response.json()
|
222 |
+
|
223 |
+
if result["sentiment"]["positif"] > 0.7:
|
224 |
+
return "Je suis ravi que vous soyez satisfait !"
|
225 |
+
elif result["sentiment"]["négatif"] > 0.7:
|
226 |
+
return "Je comprends votre préoccupation. Comment puis-je vous aider ?"
|
227 |
+
else:
|
228 |
+
return "Merci pour votre retour."
|
229 |
+
```
|
230 |
+
|
231 |
+
### 2. **Analyse de feedback clients**
|
232 |
+
```python
|
233 |
+
def analyze_customer_feedback(audio_files):
|
234 |
+
results = []
|
235 |
+
for audio in audio_files:
|
236 |
+
response = requests.post(API_URL, files={"file": audio})
|
237 |
+
results.append(response.json())
|
238 |
+
|
239 |
+
# Statistiques
|
240 |
+
positive_count = sum(1 for r in results if r["sentiment"]["positif"] > 0.5)
|
241 |
+
return f"Taux de satisfaction: {positive_count/len(results)*100:.1f}%"
|
242 |
+
```
|
243 |
+
|
244 |
+
### 3. **Monitoring en temps réel**
|
245 |
+
```python
|
246 |
+
import time
|
247 |
+
|
248 |
+
def monitor_audio_stream():
|
249 |
+
while True:
|
250 |
+
# Capture audio
|
251 |
+
audio_data = capture_audio()
|
252 |
+
|
253 |
+
# Analyse
|
254 |
+
response = requests.post(API_URL, files={"file": audio_data})
|
255 |
+
result = response.json()
|
256 |
+
|
257 |
+
# Alerte si sentiment négatif
|
258 |
+
if result["sentiment"]["négatif"] > 0.8:
|
259 |
+
send_alert("Sentiment très négatif détecté")
|
260 |
+
|
261 |
+
time.sleep(30) # Analyse toutes les 30 secondes
|
262 |
+
```
|
263 |
+
|
264 |
+
## 🔗 Documentation interactive
|
265 |
+
|
266 |
+
Accédez à la documentation interactive Swagger UI :
|
267 |
+
```
|
268 |
+
https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/docs
|
269 |
+
```
|
270 |
+
|
271 |
+
## 📞 Support
|
272 |
+
|
273 |
+
Pour toute question ou problème :
|
274 |
+
1. Consultez les logs dans l'interface HF Spaces
|
275 |
+
2. Vérifiez la documentation Swagger
|
276 |
+
3. Testez avec l'interface Gradio
|
277 |
+
|
278 |
+
---
|
279 |
+
|
280 |
+
*API développée avec FastAPI et optimisée pour Hugging Face Spaces*
|
API_SUMMARY.md
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 🔌 API REST - Résumé
|
2 |
+
|
3 |
+
## ✅ **API opérationnelle !**
|
4 |
+
|
5 |
+
Votre API REST est maintenant **entièrement fonctionnelle** et intégrée dans le déploiement Hugging Face Spaces.
|
6 |
+
|
7 |
+
## 🚀 **Endpoints disponibles**
|
8 |
+
|
9 |
+
| Endpoint | Méthode | Description |
|
10 |
+
|----------|---------|-------------|
|
11 |
+
| `/api/` | GET | Informations générales |
|
12 |
+
| `/api/health` | GET | Vérification état |
|
13 |
+
| `/api/predict` | POST | Analyse audio |
|
14 |
+
| `/api/predict_text` | POST | Analyse textuelle |
|
15 |
+
| `/api/docs` | GET | Documentation Swagger |
|
16 |
+
|
17 |
+
## 📖 **Utilisation rapide**
|
18 |
+
|
19 |
+
### Analyse audio
|
20 |
+
```bash
|
21 |
+
curl -X POST "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict" \
|
22 |
+
-F "[email protected]"
|
23 |
+
```
|
24 |
+
|
25 |
+
### Analyse textuelle
|
26 |
+
```bash
|
27 |
+
curl -X POST "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict_text" \
|
28 |
+
-H "Content-Type: application/json" \
|
29 |
+
-d '{"text": "je suis content"}'
|
30 |
+
```
|
31 |
+
|
32 |
+
## 🎯 **Fonctionnalités**
|
33 |
+
|
34 |
+
- ✅ **Transcription audio** avec Wav2Vec2
|
35 |
+
- ✅ **Analyse sentiment** avec BERT
|
36 |
+
- ✅ **Gestion d'erreurs** robuste
|
37 |
+
- ✅ **Validation fichiers** (WAV, FLAC, MP3, max 50MB)
|
38 |
+
- ✅ **Documentation Swagger** interactive
|
39 |
+
- ✅ **Support CORS** pour intégration web
|
40 |
+
- ✅ **Fallback** vers analyse textuelle si multimodal échoue
|
41 |
+
|
42 |
+
## 🔧 **Intégration**
|
43 |
+
|
44 |
+
### Python
|
45 |
+
```python
|
46 |
+
import requests
|
47 |
+
|
48 |
+
# Analyse audio
|
49 |
+
response = requests.post(API_URL + "/api/predict", files={"file": open("audio.wav", "rb")})
|
50 |
+
result = response.json()
|
51 |
+
print(f"Sentiment: {result['sentiment']}")
|
52 |
+
```
|
53 |
+
|
54 |
+
### JavaScript
|
55 |
+
```javascript
|
56 |
+
// Analyse audio
|
57 |
+
const formData = new FormData();
|
58 |
+
formData.append('file', audioFile);
|
59 |
+
|
60 |
+
fetch(API_URL + '/api/predict', {
|
61 |
+
method: 'POST',
|
62 |
+
body: formData
|
63 |
+
})
|
64 |
+
.then(response => response.json())
|
65 |
+
.then(data => console.log(data.sentiment));
|
66 |
+
```
|
67 |
+
|
68 |
+
## 📊 **Réponse type**
|
69 |
+
|
70 |
+
```json
|
71 |
+
{
|
72 |
+
"transcription": "je suis très content de ce produit",
|
73 |
+
"sentiment": {
|
74 |
+
"négatif": 0.05,
|
75 |
+
"neutre": 0.10,
|
76 |
+
"positif": 0.85
|
77 |
+
},
|
78 |
+
"filename": "audio.wav",
|
79 |
+
"file_size": 123456
|
80 |
+
}
|
81 |
+
```
|
82 |
+
|
83 |
+
## 🧪 **Tests**
|
84 |
+
|
85 |
+
Testez votre API avec :
|
86 |
+
```bash
|
87 |
+
python test_api.py
|
88 |
+
```
|
89 |
+
|
90 |
+
## 📚 **Documentation complète**
|
91 |
+
|
92 |
+
Consultez `API_DOCUMENTATION.md` pour la documentation détaillée.
|
93 |
+
|
94 |
+
---
|
95 |
+
|
96 |
+
**🎉 Votre API est prête pour la production !**
|
DEPLOYMENT_GUIDE.md
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 🚀 Guide de Déploiement sur Hugging Face Spaces
|
2 |
+
|
3 |
+
Ce guide vous accompagne pour déployer votre projet d'analyse de sentiment audio sur Hugging Face Spaces.
|
4 |
+
|
5 |
+
## 📋 Prérequis
|
6 |
+
|
7 |
+
1. **Compte Hugging Face** : Créez un compte sur [huggingface.co](https://huggingface.co)
|
8 |
+
2. **Git** : Assurez-vous d'avoir Git installé
|
9 |
+
3. **Projet prêt** : Votre projet doit être fonctionnel localement
|
10 |
+
|
11 |
+
## 🎯 Étapes de déploiement
|
12 |
+
|
13 |
+
### 1. Préparation du repository
|
14 |
+
|
15 |
+
```bash
|
16 |
+
# Cloner votre projet (si pas déjà fait)
|
17 |
+
git clone <votre-repo-url>
|
18 |
+
cd sentiment_hf_
|
19 |
+
|
20 |
+
# Vérifier que tous les fichiers sont présents
|
21 |
+
ls -la
|
22 |
+
```
|
23 |
+
|
24 |
+
### 2. Fichiers nécessaires pour HF Spaces
|
25 |
+
|
26 |
+
Assurez-vous d'avoir ces fichiers à la racine :
|
27 |
+
|
28 |
+
- ✅ `app.py` - Application Gradio principale
|
29 |
+
- ✅ `requirements_hf.txt` - Dépendances Python
|
30 |
+
- ✅ `config.yaml` - Configuration du Space
|
31 |
+
- ✅ `README_HF.md` - Documentation
|
32 |
+
- ✅ `.gitattributes` - Gestion des fichiers binaires
|
33 |
+
- ✅ `src/` - Votre code source
|
34 |
+
|
35 |
+
### 3. Création du Space sur Hugging Face
|
36 |
+
|
37 |
+
1. **Allez sur** [huggingface.co/spaces](https://huggingface.co/spaces)
|
38 |
+
2. **Cliquez** sur "Create new Space"
|
39 |
+
3. **Remplissez** les informations :
|
40 |
+
- **Owner** : Votre nom d'utilisateur
|
41 |
+
- **Space name** : `sentiment-audio-analyzer` (ou autre nom)
|
42 |
+
- **License** : MIT
|
43 |
+
- **SDK** : Gradio
|
44 |
+
- **Python version** : 3.10
|
45 |
+
- **Hardware** : CPU (gratuit) ou GPU (payant)
|
46 |
+
|
47 |
+
### 4. Upload des fichiers
|
48 |
+
|
49 |
+
#### Option A : Via l'interface web
|
50 |
+
1. Dans votre Space, allez dans l'onglet "Files"
|
51 |
+
2. Uploadez tous les fichiers un par un
|
52 |
+
|
53 |
+
#### Option B : Via Git (recommandé)
|
54 |
+
```bash
|
55 |
+
# Ajouter le remote HF
|
56 |
+
git remote add hf https://huggingface.co/spaces/<votre-username>/<nom-du-space>
|
57 |
+
|
58 |
+
# Pousser le code
|
59 |
+
git add .
|
60 |
+
git commit -m "Initial commit for HF Space"
|
61 |
+
git push hf main
|
62 |
+
```
|
63 |
+
|
64 |
+
### 5. Configuration des variables d'environnement
|
65 |
+
|
66 |
+
Dans les paramètres de votre Space :
|
67 |
+
- **HF_SPACE** : `true`
|
68 |
+
- **GRADIO_SERVER_NAME** : `0.0.0.0`
|
69 |
+
- **GRADIO_SERVER_PORT** : `7860`
|
70 |
+
|
71 |
+
## 🔧 Optimisations recommandées
|
72 |
+
|
73 |
+
### 1. Gestion de la mémoire
|
74 |
+
|
75 |
+
```python
|
76 |
+
# Dans app.py, ajoutez :
|
77 |
+
import gc
|
78 |
+
import torch
|
79 |
+
|
80 |
+
# Après chaque prédiction
|
81 |
+
gc.collect()
|
82 |
+
torch.cuda.empty_cache() if torch.cuda.is_available() else None
|
83 |
+
```
|
84 |
+
|
85 |
+
### 2. Cache des modèles
|
86 |
+
|
87 |
+
```python
|
88 |
+
# Utilisez le cache HF par défaut
|
89 |
+
processor_ctc = Wav2Vec2Processor.from_pretrained(
|
90 |
+
"jonatasgrosman/wav2vec2-large-xlsr-53-french"
|
91 |
+
)
|
92 |
+
```
|
93 |
+
|
94 |
+
### 3. Gestion des erreurs
|
95 |
+
|
96 |
+
```python
|
97 |
+
# Ajoutez des try/catch robustes
|
98 |
+
try:
|
99 |
+
# Votre code
|
100 |
+
except Exception as e:
|
101 |
+
return f"Erreur : {str(e)}", "", pd.DataFrame(), {}
|
102 |
+
```
|
103 |
+
|
104 |
+
## 🚨 Dépannage courant
|
105 |
+
|
106 |
+
### Problème : "Out of memory"
|
107 |
+
**Solution** :
|
108 |
+
- Utilisez un hardware plus puissant (GPU)
|
109 |
+
- Optimisez le chargement des modèles
|
110 |
+
- Ajoutez la gestion de mémoire
|
111 |
+
|
112 |
+
### Problème : "Model not found"
|
113 |
+
**Solution** :
|
114 |
+
- Vérifiez les noms des modèles
|
115 |
+
- Assurez-vous qu'ils sont publics sur HF
|
116 |
+
- Ajoutez des fallbacks
|
117 |
+
|
118 |
+
### Problème : "Port already in use"
|
119 |
+
**Solution** :
|
120 |
+
- Vérifiez la configuration dans `app.py`
|
121 |
+
- Utilisez le port 7860 par défaut
|
122 |
+
|
123 |
+
## 📊 Monitoring
|
124 |
+
|
125 |
+
### Logs
|
126 |
+
- Consultez les logs dans l'onglet "Logs" de votre Space
|
127 |
+
- Surveillez les erreurs et performances
|
128 |
+
|
129 |
+
### Métriques
|
130 |
+
- Temps de réponse
|
131 |
+
- Utilisation mémoire
|
132 |
+
- Nombre de requêtes
|
133 |
+
|
134 |
+
## 🔄 Mise à jour
|
135 |
+
|
136 |
+
Pour mettre à jour votre Space :
|
137 |
+
|
138 |
+
```bash
|
139 |
+
# Modifiez votre code local
|
140 |
+
git add .
|
141 |
+
git commit -m "Update: nouvelle fonctionnalité"
|
142 |
+
git push hf main
|
143 |
+
```
|
144 |
+
|
145 |
+
## 🌟 Fonctionnalités avancées
|
146 |
+
|
147 |
+
### 1. API REST
|
148 |
+
Ajoutez un endpoint API dans votre Space :
|
149 |
+
|
150 |
+
```python
|
151 |
+
# Dans app.py
|
152 |
+
@app.get("/api/health")
|
153 |
+
def health_check():
|
154 |
+
return {"status": "healthy"}
|
155 |
+
```
|
156 |
+
|
157 |
+
### 2. Webhooks
|
158 |
+
Configurez des webhooks pour les notifications :
|
159 |
+
|
160 |
+
```python
|
161 |
+
# Dans config.yaml
|
162 |
+
webhook: true
|
163 |
+
```
|
164 |
+
|
165 |
+
### 3. Custom CSS
|
166 |
+
Personnalisez l'interface :
|
167 |
+
|
168 |
+
```python
|
169 |
+
# Dans app.py
|
170 |
+
demo = gr.Blocks(
|
171 |
+
css="custom.css",
|
172 |
+
theme=gr.themes.Monochrome(primary_hue="purple")
|
173 |
+
)
|
174 |
+
```
|
175 |
+
|
176 |
+
## 📞 Support
|
177 |
+
|
178 |
+
- **Documentation HF** : [huggingface.co/docs/hub/spaces](https://huggingface.co/docs/hub/spaces)
|
179 |
+
- **Community** : [huggingface.co/forums](https://huggingface.co/forums)
|
180 |
+
- **Discord** : [huggingface.co/join/discord](https://huggingface.co/join/discord)
|
181 |
+
|
182 |
+
---
|
183 |
+
|
184 |
+
🎉 **Félicitations !** Votre Space est maintenant déployé et accessible au monde entier !
|
DEPLOYMENT_SUMMARY.md
ADDED
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 🎯 Résumé du Déploiement - Analyse de Sentiment Audio
|
2 |
+
|
3 |
+
## 📋 Fichiers créés pour le déploiement
|
4 |
+
|
5 |
+
### ✅ Fichiers principaux
|
6 |
+
- **`app_with_api.py`** - Application Gradio + API FastAPI intégrée
|
7 |
+
- **`api_app.py`** - API FastAPI standalone
|
8 |
+
- **`requirements_hf.txt`** - Dépendances Python avec versions fixes
|
9 |
+
- **`config.yaml`** - Configuration du Space Hugging Face
|
10 |
+
- **`README_HF.md`** - Documentation optimisée pour le Space
|
11 |
+
|
12 |
+
### ✅ Fichiers de support
|
13 |
+
- **`.gitattributes`** - Gestion des fichiers binaires (LFS)
|
14 |
+
- **`test_deployment.py`** - Script de test avant déploiement
|
15 |
+
- **`deploy.sh`** - Script de déploiement automatisé
|
16 |
+
- **`DEPLOYMENT_GUIDE.md`** - Guide détaillé de déploiement
|
17 |
+
- **`API_DOCUMENTATION.md`** - Documentation complète de l'API REST
|
18 |
+
|
19 |
+
## 🚀 Étapes de déploiement rapide
|
20 |
+
|
21 |
+
### 1. Test local
|
22 |
+
```bash
|
23 |
+
python test_deployment.py
|
24 |
+
```
|
25 |
+
|
26 |
+
### 2. Créer le Space sur Hugging Face
|
27 |
+
1. Allez sur [huggingface.co/spaces](https://huggingface.co/spaces)
|
28 |
+
2. Cliquez "Create new Space"
|
29 |
+
3. Remplissez :
|
30 |
+
- **Owner** : Votre nom d'utilisateur
|
31 |
+
- **Space name** : `sentiment-audio-analyzer`
|
32 |
+
- **SDK** : Gradio
|
33 |
+
- **Hardware** : CPU (gratuit)
|
34 |
+
|
35 |
+
### 3. Déploiement automatisé
|
36 |
+
```bash
|
37 |
+
./deploy.sh <votre-username> sentiment-audio-analyzer
|
38 |
+
```
|
39 |
+
|
40 |
+
### 4. Déploiement manuel (alternative)
|
41 |
+
```bash
|
42 |
+
git remote add hf https://huggingface.co/spaces/<username>/sentiment-audio-analyzer
|
43 |
+
git add .
|
44 |
+
git commit -m "Initial deployment"
|
45 |
+
git push hf main
|
46 |
+
```
|
47 |
+
|
48 |
+
## 🔧 Optimisations apportées
|
49 |
+
|
50 |
+
### Performance
|
51 |
+
- ✅ Gestion de mémoire optimisée
|
52 |
+
- ✅ Cache des modèles configuré
|
53 |
+
- ✅ Gestion d'erreurs robuste
|
54 |
+
- ✅ Interface responsive
|
55 |
+
|
56 |
+
### Compatibilité
|
57 |
+
- ✅ Versions de dépendances fixes
|
58 |
+
- ✅ Configuration HF Spaces
|
59 |
+
- ✅ Support multi-plateforme
|
60 |
+
- ✅ Gestion des fichiers binaires
|
61 |
+
|
62 |
+
### UX/UI
|
63 |
+
- ✅ Interface moderne avec emojis
|
64 |
+
- ✅ Instructions claires
|
65 |
+
- ✅ Feedback utilisateur
|
66 |
+
- ✅ Export de données
|
67 |
+
|
68 |
+
## 📊 Fonctionnalités du Space
|
69 |
+
|
70 |
+
### 🎤 Entrée audio
|
71 |
+
- Enregistrement microphone
|
72 |
+
- Upload de fichiers (WAV, MP3, FLAC)
|
73 |
+
- Validation des formats
|
74 |
+
|
75 |
+
### 🔍 Analyse
|
76 |
+
- Transcription avec Wav2Vec2
|
77 |
+
- Analyse sentiment avec BERT
|
78 |
+
- Segmentation par phrase
|
79 |
+
- Scores de confiance
|
80 |
+
|
81 |
+
### 📈 Visualisation
|
82 |
+
- Transcription en temps réel
|
83 |
+
- Sentiment avec emojis
|
84 |
+
- Tableau détaillé par segment
|
85 |
+
- Historique des analyses
|
86 |
+
|
87 |
+
### 💾 Export
|
88 |
+
- Sauvegarde CSV
|
89 |
+
- Historique persistant
|
90 |
+
- Données structurées
|
91 |
+
|
92 |
+
### 🔌 API REST
|
93 |
+
- Endpoint `/api/predict` pour analyse audio
|
94 |
+
- Endpoint `/api/predict_text` pour analyse textuelle
|
95 |
+
- Documentation Swagger interactive
|
96 |
+
- Support CORS pour intégration web
|
97 |
+
|
98 |
+
## 🛠️ Technologies utilisées
|
99 |
+
|
100 |
+
| Composant | Modèle/Technologie |
|
101 |
+
|-----------|-------------------|
|
102 |
+
| **Transcription** | `jonatasgrosman/wav2vec2-large-xlsr-53-french` |
|
103 |
+
| **Sentiment** | `nlptown/bert-base-multilingual-uncased-sentiment` |
|
104 |
+
| **Interface** | Gradio 4.15.0 |
|
105 |
+
| **API** | FastAPI avec CORS |
|
106 |
+
| **Backend** | PyTorch 2.1.2, Transformers 4.36.2 |
|
107 |
+
| **Audio** | SoundFile, TorchAudio |
|
108 |
+
|
109 |
+
## 🎯 Cas d'usage
|
110 |
+
|
111 |
+
- **Analyse d'appels clients** : Sentiment des conversations
|
112 |
+
- **Évaluation de podcasts** : Analyse de contenu audio
|
113 |
+
- **Validation qualitative** : Proof of concept
|
114 |
+
- **Recherche** : Pipeline multimodal
|
115 |
+
|
116 |
+
## 📞 Support et maintenance
|
117 |
+
|
118 |
+
### Monitoring
|
119 |
+
- Logs dans l'interface HF Spaces
|
120 |
+
- Métriques de performance
|
121 |
+
- Gestion des erreurs
|
122 |
+
|
123 |
+
### Mises à jour
|
124 |
+
```bash
|
125 |
+
# Modifier le code local
|
126 |
+
git add .
|
127 |
+
git commit -m "Update: nouvelle fonctionnalité"
|
128 |
+
git push hf main
|
129 |
+
```
|
130 |
+
|
131 |
+
### Dépannage
|
132 |
+
- Vérifier les logs dans HF Spaces
|
133 |
+
- Tester localement avec `test_deployment.py`
|
134 |
+
- Consulter la documentation HF
|
135 |
+
|
136 |
+
## 🌟 Prochaines étapes
|
137 |
+
|
138 |
+
### Améliorations possibles
|
139 |
+
- [ ] Support GPU pour plus de performance
|
140 |
+
- [x] API REST complète ✅
|
141 |
+
- [ ] Modèles personnalisés
|
142 |
+
- [ ] Interface multilingue
|
143 |
+
- [ ] Intégration webhooks
|
144 |
+
|
145 |
+
### Optimisations
|
146 |
+
- [ ] Cache des modèles persistants
|
147 |
+
- [ ] Compression audio
|
148 |
+
- [ ] Batch processing
|
149 |
+
- [ ] Métriques avancées
|
150 |
+
|
151 |
+
---
|
152 |
+
|
153 |
+
## 🎉 Félicitations !
|
154 |
+
|
155 |
+
Votre projet d'analyse de sentiment audio est maintenant prêt pour le déploiement sur Hugging Face Spaces !
|
156 |
+
|
157 |
+
**URL finale** : `https://huggingface.co/spaces/<votre-username>/sentiment-audio-analyzer`
|
158 |
+
|
159 |
+
**Temps de build estimé** : 5-10 minutes
|
160 |
+
|
161 |
+
**Hardware recommandé** : CPU (gratuit) pour commencer, GPU pour la production
|
162 |
+
|
163 |
+
---
|
164 |
+
|
165 |
+
*Développé avec ❤️ pour l'analyse de sentiment audio en français*
|
api_app.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import tempfile
|
2 |
+
import os
|
3 |
+
import gc
|
4 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException
|
5 |
+
from fastapi.responses import JSONResponse
|
6 |
+
from fastapi.middleware.cors import CORSMiddleware
|
7 |
+
import torch.nn.functional as F
|
8 |
+
import torchaudio
|
9 |
+
import torch
|
10 |
+
|
11 |
+
from transformers import Wav2Vec2Processor, Wav2Vec2ForCTC
|
12 |
+
from src.transcription import SpeechEncoder
|
13 |
+
from src.sentiment import TextEncoder
|
14 |
+
from src.multimodal import MultimodalSentimentClassifier
|
15 |
+
|
16 |
+
# Configuration pour Hugging Face Spaces
|
17 |
+
HF_SPACE = os.getenv("HF_SPACE", "false").lower() == "true"
|
18 |
+
|
19 |
+
app = FastAPI(
|
20 |
+
title="API Multimodale de Transcription & Sentiment",
|
21 |
+
description="API pour l'analyse de sentiment audio en français",
|
22 |
+
version="1.0",
|
23 |
+
docs_url="/docs",
|
24 |
+
redoc_url="/redoc"
|
25 |
+
)
|
26 |
+
|
27 |
+
# Configuration CORS pour Hugging Face Spaces
|
28 |
+
app.add_middleware(
|
29 |
+
CORSMiddleware,
|
30 |
+
allow_origins=["*"],
|
31 |
+
allow_credentials=True,
|
32 |
+
allow_methods=["*"],
|
33 |
+
allow_headers=["*"],
|
34 |
+
)
|
35 |
+
|
36 |
+
# Précharge des modèles
|
37 |
+
print("Chargement des modèles pour l'API...")
|
38 |
+
try:
|
39 |
+
processor_ctc = Wav2Vec2Processor.from_pretrained(
|
40 |
+
"jonatasgrosman/wav2vec2-large-xlsr-53-french",
|
41 |
+
cache_dir="./models" if not HF_SPACE else None
|
42 |
+
)
|
43 |
+
model_ctc = Wav2Vec2ForCTC.from_pretrained(
|
44 |
+
"jonatasgrosman/wav2vec2-large-xlsr-53-french",
|
45 |
+
cache_dir="./models" if not HF_SPACE else None
|
46 |
+
)
|
47 |
+
speech_enc = SpeechEncoder()
|
48 |
+
text_enc = TextEncoder()
|
49 |
+
model_mm = MultimodalSentimentClassifier()
|
50 |
+
print("✅ Modèles chargés avec succès pour l'API")
|
51 |
+
except Exception as e:
|
52 |
+
print(f"❌ Erreur chargement modèles API: {e}")
|
53 |
+
raise
|
54 |
+
|
55 |
+
def transcribe_ctc(wav_path: str) -> str:
|
56 |
+
"""Transcription audio avec Wav2Vec2"""
|
57 |
+
try:
|
58 |
+
waveform, sr = torchaudio.load(wav_path)
|
59 |
+
if sr != 16000:
|
60 |
+
waveform = torchaudio.transforms.Resample(sr, 16000)(waveform)
|
61 |
+
if waveform.size(0) > 1:
|
62 |
+
waveform = waveform.mean(dim=0, keepdim=True)
|
63 |
+
|
64 |
+
inputs = processor_ctc(
|
65 |
+
waveform.squeeze().numpy(),
|
66 |
+
sampling_rate=16000,
|
67 |
+
return_tensors="pt",
|
68 |
+
padding=True
|
69 |
+
)
|
70 |
+
|
71 |
+
with torch.no_grad():
|
72 |
+
logits = model_ctc(**inputs).logits
|
73 |
+
pred_ids = torch.argmax(logits, dim=-1)
|
74 |
+
transcription = processor_ctc.batch_decode(pred_ids)[0].lower()
|
75 |
+
|
76 |
+
return transcription
|
77 |
+
except Exception as e:
|
78 |
+
raise HTTPException(status_code=500, detail=f"Erreur transcription: {str(e)}")
|
79 |
+
|
80 |
+
@app.get("/")
|
81 |
+
async def root():
|
82 |
+
"""Endpoint racine avec informations sur l'API"""
|
83 |
+
return {
|
84 |
+
"message": "API Multimodale de Transcription & Sentiment",
|
85 |
+
"version": "1.0",
|
86 |
+
"endpoints": {
|
87 |
+
"docs": "/docs",
|
88 |
+
"predict": "/predict",
|
89 |
+
"health": "/health"
|
90 |
+
},
|
91 |
+
"supported_formats": ["wav", "flac", "mp3"]
|
92 |
+
}
|
93 |
+
|
94 |
+
@app.get("/health")
|
95 |
+
async def health_check():
|
96 |
+
"""Vérification de l'état de l'API"""
|
97 |
+
return {
|
98 |
+
"status": "healthy",
|
99 |
+
"models_loaded": True,
|
100 |
+
"timestamp": "2024-01-01T00:00:00Z"
|
101 |
+
}
|
102 |
+
|
103 |
+
@app.post("/predict")
|
104 |
+
async def predict(file: UploadFile = File(...)):
|
105 |
+
"""
|
106 |
+
Analyse de sentiment audio
|
107 |
+
|
108 |
+
Args:
|
109 |
+
file: Fichier audio (WAV, FLAC, MP3)
|
110 |
+
|
111 |
+
Returns:
|
112 |
+
JSON avec transcription et sentiment
|
113 |
+
"""
|
114 |
+
# 1. Vérifier le type de fichier
|
115 |
+
if not file.filename or not file.filename.lower().endswith((".wav", ".flac", ".mp3")):
|
116 |
+
raise HTTPException(
|
117 |
+
status_code=400,
|
118 |
+
detail="Seuls les fichiers audio WAV/FLAC/MP3 sont acceptés."
|
119 |
+
)
|
120 |
+
|
121 |
+
# 2. Vérifier la taille du fichier (max 50MB)
|
122 |
+
content = await file.read()
|
123 |
+
if len(content) > 50 * 1024 * 1024: # 50MB
|
124 |
+
raise HTTPException(
|
125 |
+
status_code=400,
|
126 |
+
detail="Fichier trop volumineux. Taille maximale: 50MB"
|
127 |
+
)
|
128 |
+
|
129 |
+
# 3. Sauvegarder temporairement
|
130 |
+
suffix = os.path.splitext(file.filename)[1]
|
131 |
+
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
132 |
+
tmp.write(content)
|
133 |
+
tmp_path = tmp.name
|
134 |
+
|
135 |
+
try:
|
136 |
+
# 4. Transcription
|
137 |
+
transcription = transcribe_ctc(tmp_path)
|
138 |
+
|
139 |
+
if not transcription.strip():
|
140 |
+
return JSONResponse({
|
141 |
+
"transcription": "",
|
142 |
+
"sentiment": {"négatif": 0.33, "neutre": 0.34, "positif": 0.33},
|
143 |
+
"warning": "Aucune transcription détectée"
|
144 |
+
})
|
145 |
+
|
146 |
+
# 5. Features multimodales
|
147 |
+
try:
|
148 |
+
audio_feat = speech_enc.extract_features(tmp_path)
|
149 |
+
text_feat = text_enc.extract_features([transcription])
|
150 |
+
|
151 |
+
# 6. Classification
|
152 |
+
logits = model_mm.classifier(torch.cat([audio_feat, text_feat], dim=1))
|
153 |
+
probs = F.softmax(logits, dim=1).squeeze().tolist()
|
154 |
+
labels = ["négatif", "neutre", "positif"]
|
155 |
+
sentiment = {labels[i]: round(probs[i], 3) for i in range(len(labels))}
|
156 |
+
except Exception as e:
|
157 |
+
# Fallback vers analyse textuelle uniquement
|
158 |
+
print(f"Erreur multimodal, fallback textuel: {e}")
|
159 |
+
sent_dict = TextEncoder.analyze_sentiment(transcription)
|
160 |
+
sentiment = {k: round(v, 3) for k, v in sent_dict.items()}
|
161 |
+
|
162 |
+
# 7. Nettoyage mémoire
|
163 |
+
gc.collect()
|
164 |
+
if torch.cuda.is_available():
|
165 |
+
torch.cuda.empty_cache()
|
166 |
+
|
167 |
+
return JSONResponse({
|
168 |
+
"transcription": transcription,
|
169 |
+
"sentiment": sentiment,
|
170 |
+
"filename": file.filename,
|
171 |
+
"file_size": len(content)
|
172 |
+
})
|
173 |
+
|
174 |
+
except Exception as e:
|
175 |
+
raise HTTPException(status_code=500, detail=f"Erreur lors de l'analyse: {str(e)}")
|
176 |
+
|
177 |
+
finally:
|
178 |
+
# 8. Nettoyage fichier temporaire
|
179 |
+
try:
|
180 |
+
os.remove(tmp_path)
|
181 |
+
except:
|
182 |
+
pass
|
183 |
+
|
184 |
+
@app.post("/predict_text")
|
185 |
+
async def predict_text(text: str):
|
186 |
+
"""
|
187 |
+
Analyse de sentiment textuel uniquement
|
188 |
+
|
189 |
+
Args:
|
190 |
+
text: Texte à analyser
|
191 |
+
|
192 |
+
Returns:
|
193 |
+
JSON avec sentiment
|
194 |
+
"""
|
195 |
+
try:
|
196 |
+
sent_dict = TextEncoder.analyze_sentiment(text)
|
197 |
+
sentiment = {k: round(v, 3) for k, v in sent_dict.items()}
|
198 |
+
|
199 |
+
return JSONResponse({
|
200 |
+
"text": text,
|
201 |
+
"sentiment": sentiment
|
202 |
+
})
|
203 |
+
except Exception as e:
|
204 |
+
raise HTTPException(status_code=500, detail=f"Erreur analyse textuelle: {str(e)}")
|
205 |
+
|
206 |
+
# Configuration pour Hugging Face Spaces
|
207 |
+
if __name__ == "__main__":
|
208 |
+
import uvicorn
|
209 |
+
uvicorn.run(
|
210 |
+
app,
|
211 |
+
host="0.0.0.0" if HF_SPACE else "127.0.0.1",
|
212 |
+
port=8000,
|
213 |
+
log_level="info"
|
214 |
+
)
|
app.py
CHANGED
@@ -132,6 +132,23 @@ with demo:
|
|
132 |
</div>
|
133 |
""")
|
134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
with gr.Row():
|
136 |
with gr.Column(scale=2):
|
137 |
audio_in = gr.Audio(
|
|
|
132 |
</div>
|
133 |
""")
|
134 |
|
135 |
+
# Section API
|
136 |
+
with gr.Accordion("🔌 API REST", open=False):
|
137 |
+
gr.Markdown("""
|
138 |
+
### Endpoints disponibles :
|
139 |
+
|
140 |
+
- **`/api/predict`** - Analyse audio (POST)
|
141 |
+
- **`/api/predict_text`** - Analyse textuelle (POST)
|
142 |
+
- **`/api/health`** - Vérification état (GET)
|
143 |
+
- **`/api/docs`** - Documentation Swagger
|
144 |
+
|
145 |
+
### Exemple d'utilisation :
|
146 |
+
```bash
|
147 |
+
curl -X POST "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict" \
|
148 |
+
-F "[email protected]"
|
149 |
+
```
|
150 |
+
""")
|
151 |
+
|
152 |
with gr.Row():
|
153 |
with gr.Column(scale=2):
|
154 |
audio_in = gr.Audio(
|
app_with_api.py
ADDED
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
from datetime import datetime
|
4 |
+
import asyncio
|
5 |
+
import threading
|
6 |
+
|
7 |
+
import gradio as gr
|
8 |
+
import torch
|
9 |
+
import pandas as pd
|
10 |
+
import soundfile as sf
|
11 |
+
import torchaudio
|
12 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException
|
13 |
+
from fastapi.responses import JSONResponse
|
14 |
+
from fastapi.middleware.cors import CORSMiddleware
|
15 |
+
import torch.nn.functional as F
|
16 |
+
import uvicorn
|
17 |
+
|
18 |
+
from transformers import Wav2Vec2Processor, Wav2Vec2ForCTC
|
19 |
+
from src.transcription import SpeechEncoder
|
20 |
+
from src.sentiment import TextEncoder
|
21 |
+
from src.multimodal import MultimodalSentimentClassifier
|
22 |
+
|
23 |
+
# Configuration pour Hugging Face Spaces
|
24 |
+
HF_SPACE = os.getenv("HF_SPACE", "false").lower() == "true"
|
25 |
+
|
26 |
+
# Préchargement des modèles (partagés entre Gradio et API)
|
27 |
+
print("Chargement des modèles...")
|
28 |
+
processor_ctc = Wav2Vec2Processor.from_pretrained(
|
29 |
+
"jonatasgrosman/wav2vec2-large-xlsr-53-french",
|
30 |
+
cache_dir="./models" if not HF_SPACE else None
|
31 |
+
)
|
32 |
+
model_ctc = Wav2Vec2ForCTC.from_pretrained(
|
33 |
+
"jonatasgrosman/wav2vec2-large-xlsr-53-french",
|
34 |
+
cache_dir="./models" if not HF_SPACE else None
|
35 |
+
)
|
36 |
+
|
37 |
+
speech_enc = SpeechEncoder()
|
38 |
+
text_enc = TextEncoder()
|
39 |
+
print("Modèles chargés avec succès!")
|
40 |
+
|
41 |
+
# ===== FONCTIONS PARTAGÉES =====
|
42 |
+
|
43 |
+
def transcribe_ctc(wav_path: str) -> str:
|
44 |
+
"""Transcription audio avec Wav2Vec2"""
|
45 |
+
try:
|
46 |
+
waveform, sr = torchaudio.load(wav_path)
|
47 |
+
if sr != 16000:
|
48 |
+
waveform = torchaudio.transforms.Resample(sr, 16000)(waveform)
|
49 |
+
if waveform.size(0) > 1:
|
50 |
+
waveform = waveform.mean(dim=0, keepdim=True)
|
51 |
+
|
52 |
+
inputs = processor_ctc(
|
53 |
+
waveform.squeeze().numpy(),
|
54 |
+
sampling_rate=16000,
|
55 |
+
return_tensors="pt",
|
56 |
+
padding=True
|
57 |
+
)
|
58 |
+
|
59 |
+
with torch.no_grad():
|
60 |
+
logits = model_ctc(**inputs).logits
|
61 |
+
pred_ids = torch.argmax(logits, dim=-1)
|
62 |
+
transcription = processor_ctc.batch_decode(pred_ids)[0].lower()
|
63 |
+
|
64 |
+
return transcription
|
65 |
+
except Exception as e:
|
66 |
+
raise Exception(f"Erreur transcription: {str(e)}")
|
67 |
+
|
68 |
+
def analyze_audio(audio_path):
|
69 |
+
"""Analyse audio pour Gradio"""
|
70 |
+
if audio_path is None:
|
71 |
+
return "Aucun audio fourni", "", pd.DataFrame(), {}
|
72 |
+
|
73 |
+
try:
|
74 |
+
# Lecture et prétraitement
|
75 |
+
data, sr = sf.read(audio_path)
|
76 |
+
arr = data.T if data.ndim > 1 else data
|
77 |
+
wav = torch.from_numpy(arr).unsqueeze(0).float()
|
78 |
+
if sr != 16000:
|
79 |
+
wav = torchaudio.transforms.Resample(sr, 16000)(wav)
|
80 |
+
sr = 16000
|
81 |
+
if wav.size(0) > 1:
|
82 |
+
wav = wav.mean(dim=0, keepdim=True)
|
83 |
+
|
84 |
+
# Transcription
|
85 |
+
inputs = processor_ctc(wav.squeeze().numpy(), sampling_rate=sr, return_tensors="pt")
|
86 |
+
with torch.no_grad():
|
87 |
+
logits = model_ctc(**inputs).logits
|
88 |
+
pred_ids = torch.argmax(logits, dim=-1)
|
89 |
+
transcription = processor_ctc.batch_decode(pred_ids)[0].lower()
|
90 |
+
|
91 |
+
# Sentiment principal
|
92 |
+
sent_dict = TextEncoder.analyze_sentiment(transcription)
|
93 |
+
label, conf = max(sent_dict.items(), key=lambda x: x[1])
|
94 |
+
emojis = {"positif": "😊", "neutre": "😐", "négatif": "☹️"}
|
95 |
+
emoji = emojis.get(label, "")
|
96 |
+
|
97 |
+
# Segmentation par phrase
|
98 |
+
segments = [s.strip() for s in re.split(r'[.?!]', transcription) if s.strip()]
|
99 |
+
seg_results = []
|
100 |
+
for seg in segments:
|
101 |
+
sd = TextEncoder.analyze_sentiment(seg)
|
102 |
+
l, c = max(sd.items(), key=lambda x: x[1])
|
103 |
+
seg_results.append({"Segment": seg, "Sentiment": l.capitalize(), "Confiance (%)": round(c*100,1)})
|
104 |
+
seg_df = pd.DataFrame(seg_results)
|
105 |
+
|
106 |
+
# Historique entry
|
107 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
108 |
+
history_entry = {
|
109 |
+
"Horodatage": timestamp,
|
110 |
+
"Transcription": transcription,
|
111 |
+
"Sentiment": label.capitalize(),
|
112 |
+
"Confiance (%)": round(conf*100,1)
|
113 |
+
}
|
114 |
+
|
115 |
+
# Rendu
|
116 |
+
summary_html = (
|
117 |
+
f"<div style='display:flex;align-items:center;'>"
|
118 |
+
f"<span style='font-size:3rem;margin-right:10px;'>{emoji}</span>"
|
119 |
+
f"<h2 style='color:#6a0dad;'>{label.upper()}</h2>"
|
120 |
+
f"</div>"
|
121 |
+
f"<p><strong>Confiance :</strong> {conf*100:.1f}%</p>"
|
122 |
+
)
|
123 |
+
return transcription, summary_html, seg_df, history_entry
|
124 |
+
|
125 |
+
except Exception as e:
|
126 |
+
error_msg = f"Erreur lors de l'analyse: {str(e)}"
|
127 |
+
return error_msg, "", pd.DataFrame(), {}
|
128 |
+
|
129 |
+
# ===== API FASTAPI =====
|
130 |
+
|
131 |
+
app = FastAPI(
|
132 |
+
title="API Multimodale de Transcription & Sentiment",
|
133 |
+
description="API pour l'analyse de sentiment audio en français",
|
134 |
+
version="1.0",
|
135 |
+
docs_url="/api/docs",
|
136 |
+
redoc_url="/api/redoc"
|
137 |
+
)
|
138 |
+
|
139 |
+
# Configuration CORS
|
140 |
+
app.add_middleware(
|
141 |
+
CORSMiddleware,
|
142 |
+
allow_origins=["*"],
|
143 |
+
allow_credentials=True,
|
144 |
+
allow_methods=["*"],
|
145 |
+
allow_headers=["*"],
|
146 |
+
)
|
147 |
+
|
148 |
+
@app.get("/api/")
|
149 |
+
async def root():
|
150 |
+
"""Endpoint racine avec informations sur l'API"""
|
151 |
+
return {
|
152 |
+
"message": "API Multimodale de Transcription & Sentiment",
|
153 |
+
"version": "1.0",
|
154 |
+
"endpoints": {
|
155 |
+
"docs": "/api/docs",
|
156 |
+
"predict": "/api/predict",
|
157 |
+
"health": "/api/health"
|
158 |
+
},
|
159 |
+
"supported_formats": ["wav", "flac", "mp3"]
|
160 |
+
}
|
161 |
+
|
162 |
+
@app.get("/api/health")
|
163 |
+
async def health_check():
|
164 |
+
"""Vérification de l'état de l'API"""
|
165 |
+
return {
|
166 |
+
"status": "healthy",
|
167 |
+
"models_loaded": True,
|
168 |
+
"timestamp": "2024-01-01T00:00:00Z"
|
169 |
+
}
|
170 |
+
|
171 |
+
@app.post("/api/predict")
|
172 |
+
async def predict(file: UploadFile = File(...)):
|
173 |
+
"""Analyse de sentiment audio"""
|
174 |
+
# 1. Vérifier le type de fichier
|
175 |
+
if not file.filename or not file.filename.lower().endswith((".wav", ".flac", ".mp3")):
|
176 |
+
raise HTTPException(
|
177 |
+
status_code=400,
|
178 |
+
detail="Seuls les fichiers audio WAV/FLAC/MP3 sont acceptés."
|
179 |
+
)
|
180 |
+
|
181 |
+
# 2. Vérifier la taille du fichier (max 50MB)
|
182 |
+
content = await file.read()
|
183 |
+
if len(content) > 50 * 1024 * 1024: # 50MB
|
184 |
+
raise HTTPException(
|
185 |
+
status_code=400,
|
186 |
+
detail="Fichier trop volumineux. Taille maximale: 50MB"
|
187 |
+
)
|
188 |
+
|
189 |
+
# 3. Sauvegarder temporairement
|
190 |
+
import tempfile
|
191 |
+
suffix = os.path.splitext(file.filename)[1]
|
192 |
+
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
193 |
+
tmp.write(content)
|
194 |
+
tmp_path = tmp.name
|
195 |
+
|
196 |
+
try:
|
197 |
+
# 4. Transcription
|
198 |
+
transcription = transcribe_ctc(tmp_path)
|
199 |
+
|
200 |
+
if not transcription.strip():
|
201 |
+
return JSONResponse({
|
202 |
+
"transcription": "",
|
203 |
+
"sentiment": {"négatif": 0.33, "neutre": 0.34, "positif": 0.33},
|
204 |
+
"warning": "Aucune transcription détectée"
|
205 |
+
})
|
206 |
+
|
207 |
+
# 5. Features multimodales
|
208 |
+
try:
|
209 |
+
audio_feat = speech_enc.extract_features(tmp_path)
|
210 |
+
text_feat = text_enc.extract_features([transcription])
|
211 |
+
|
212 |
+
# 6. Classification
|
213 |
+
logits = model_mm.classifier(torch.cat([audio_feat, text_feat], dim=1))
|
214 |
+
probs = F.softmax(logits, dim=1).squeeze().tolist()
|
215 |
+
labels = ["négatif", "neutre", "positif"]
|
216 |
+
sentiment = {labels[i]: round(probs[i], 3) for i in range(len(labels))}
|
217 |
+
except Exception as e:
|
218 |
+
# Fallback vers analyse textuelle uniquement
|
219 |
+
print(f"Erreur multimodal, fallback textuel: {e}")
|
220 |
+
sent_dict = TextEncoder.analyze_sentiment(transcription)
|
221 |
+
sentiment = {k: round(v, 3) for k, v in sent_dict.items()}
|
222 |
+
|
223 |
+
return JSONResponse({
|
224 |
+
"transcription": transcription,
|
225 |
+
"sentiment": sentiment,
|
226 |
+
"filename": file.filename,
|
227 |
+
"file_size": len(content)
|
228 |
+
})
|
229 |
+
|
230 |
+
except Exception as e:
|
231 |
+
raise HTTPException(status_code=500, detail=f"Erreur lors de l'analyse: {str(e)}")
|
232 |
+
|
233 |
+
finally:
|
234 |
+
# Nettoyage fichier temporaire
|
235 |
+
try:
|
236 |
+
os.remove(tmp_path)
|
237 |
+
except:
|
238 |
+
pass
|
239 |
+
|
240 |
+
@app.post("/api/predict_text")
|
241 |
+
async def predict_text(text: str):
|
242 |
+
"""Analyse de sentiment textuel uniquement"""
|
243 |
+
try:
|
244 |
+
sent_dict = TextEncoder.analyze_sentiment(text)
|
245 |
+
sentiment = {k: round(v, 3) for k, v in sent_dict.items()}
|
246 |
+
|
247 |
+
return JSONResponse({
|
248 |
+
"text": text,
|
249 |
+
"sentiment": sentiment
|
250 |
+
})
|
251 |
+
except Exception as e:
|
252 |
+
raise HTTPException(status_code=500, detail=f"Erreur analyse textuelle: {str(e)}")
|
253 |
+
|
254 |
+
# ===== INTERFACE GRADIO =====
|
255 |
+
|
256 |
+
def export_history_csv(history):
|
257 |
+
if not history:
|
258 |
+
return None
|
259 |
+
df = pd.DataFrame(history)
|
260 |
+
path = "history.csv"
|
261 |
+
df.to_csv(path, index=False)
|
262 |
+
return path
|
263 |
+
|
264 |
+
# Interface Gradio
|
265 |
+
demo = gr.Blocks(
|
266 |
+
theme=gr.themes.Monochrome(primary_hue="purple"),
|
267 |
+
title="Analyse de Sentiment Audio - Hugging Face Space"
|
268 |
+
)
|
269 |
+
|
270 |
+
with demo:
|
271 |
+
gr.Markdown("""
|
272 |
+
# 🎤 Analyse de Sentiment Audio
|
273 |
+
|
274 |
+
Ce Space permet d'analyser le sentiment d'extraits audio en français en combinant :
|
275 |
+
- **Transcription audio** avec Wav2Vec2
|
276 |
+
- **Analyse de sentiment** avec BERT multilingue
|
277 |
+
- **API REST** pour intégration
|
278 |
+
""")
|
279 |
+
|
280 |
+
gr.HTML("""
|
281 |
+
<div style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px;">
|
282 |
+
<div style="background-color: #f3e8ff; padding: 12px 20px; border-radius: 12px; border-left: 5px solid #8e44ad;">
|
283 |
+
<strong>Étape 1 :</strong> Enregistrez votre voix ou téléversez un fichier audio (format WAV recommandé).
|
284 |
+
</div>
|
285 |
+
<div style="background-color: #e0f7fa; padding: 12px 20px; border-radius: 12px; border-left: 5px solid #0097a7;">
|
286 |
+
<strong>Étape 2 :</strong> Cliquez sur le bouton <em><b>Analyser</b></em> pour lancer la transcription et l'analyse.
|
287 |
+
</div>
|
288 |
+
<div style="background-color: #fff3e0; padding: 12px 20px; border-radius: 12px; border-left: 5px solid #fb8c00;">
|
289 |
+
<strong>Étape 3 :</strong> Visualisez les résultats : transcription, sentiment, et analyse détaillée.
|
290 |
+
</div>
|
291 |
+
<div style="background-color: #e8f5e9; padding: 12px 20px; border-radius: 12px; border-left: 5px solid #43a047;">
|
292 |
+
<strong>Étape 4 :</strong> Exportez l'historique des analyses au format CSV si besoin.
|
293 |
+
</div>
|
294 |
+
</div>
|
295 |
+
""")
|
296 |
+
|
297 |
+
# Section API
|
298 |
+
with gr.Accordion("🔌 API REST", open=False):
|
299 |
+
gr.Markdown("""
|
300 |
+
### Endpoints disponibles :
|
301 |
+
|
302 |
+
- **`/api/predict`** - Analyse audio (POST)
|
303 |
+
- **`/api/predict_text`** - Analyse textuelle (POST)
|
304 |
+
- **`/api/health`** - Vérification état (GET)
|
305 |
+
- **`/api/docs`** - Documentation Swagger
|
306 |
+
|
307 |
+
### Exemple d'utilisation :
|
308 |
+
```bash
|
309 |
+
curl -X POST "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer/api/predict" \
|
310 |
+
-F "[email protected]"
|
311 |
+
```
|
312 |
+
""")
|
313 |
+
|
314 |
+
with gr.Row():
|
315 |
+
with gr.Column(scale=2):
|
316 |
+
audio_in = gr.Audio(
|
317 |
+
sources=["microphone", "upload"],
|
318 |
+
type="filepath",
|
319 |
+
label="Audio Input",
|
320 |
+
info="Enregistrez ou téléversez un fichier audio"
|
321 |
+
)
|
322 |
+
btn = gr.Button("🔍 Analyser", variant="primary")
|
323 |
+
export_btn = gr.Button("📊 Exporter CSV")
|
324 |
+
|
325 |
+
with gr.Column(scale=3):
|
326 |
+
chat = gr.Chatbot(label="Historique des échanges")
|
327 |
+
transcription_out = gr.Textbox(label="Transcription", interactive=False)
|
328 |
+
summary_out = gr.HTML(label="Sentiment")
|
329 |
+
seg_out = gr.Dataframe(label="Détail par segment")
|
330 |
+
hist_out = gr.Dataframe(label="Historique")
|
331 |
+
|
332 |
+
state_chat = gr.State([])
|
333 |
+
state_hist = gr.State([])
|
334 |
+
|
335 |
+
def chat_callback(audio_path, chat_history, hist_state):
|
336 |
+
transcription, summary, seg_df, hist_entry = analyze_audio(audio_path)
|
337 |
+
user_msg = "[Audio reçu]"
|
338 |
+
bot_msg = f"**Transcription :** {transcription}\n**Sentiment :** {summary}"
|
339 |
+
chat_history = chat_history + [(user_msg, bot_msg)]
|
340 |
+
if hist_entry:
|
341 |
+
hist_state = hist_state + [hist_entry]
|
342 |
+
return chat_history, transcription, summary, seg_df, hist_state
|
343 |
+
|
344 |
+
btn.click(
|
345 |
+
fn=chat_callback,
|
346 |
+
inputs=[audio_in, state_chat, state_hist],
|
347 |
+
outputs=[chat, transcription_out, summary_out, seg_out, state_hist]
|
348 |
+
)
|
349 |
+
|
350 |
+
export_btn.click(
|
351 |
+
fn=export_history_csv,
|
352 |
+
inputs=[state_hist],
|
353 |
+
outputs=[gr.File(label="Télécharger CSV")]
|
354 |
+
)
|
355 |
+
|
356 |
+
# ===== INTÉGRATION GRADIO + FASTAPI =====
|
357 |
+
|
358 |
+
# Monter l'API FastAPI dans Gradio
|
359 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
360 |
+
|
361 |
+
# Configuration pour Hugging Face Spaces
|
362 |
+
if __name__ == "__main__":
|
363 |
+
uvicorn.run(
|
364 |
+
app,
|
365 |
+
host="0.0.0.0" if HF_SPACE else "127.0.0.1",
|
366 |
+
port=7860,
|
367 |
+
log_level="info"
|
368 |
+
)
|
config.yaml
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
title: "Analyse de Sentiment Audio"
|
2 |
+
emoji: "🎤"
|
3 |
+
colorFrom: "purple"
|
4 |
+
colorTo: "indigo"
|
5 |
+
sdk: gradio
|
6 |
+
sdk_version: 4.15.0
|
7 |
+
app_file: app_with_api.py
|
8 |
+
pinned: false
|
9 |
+
license: mit
|
deploy.sh
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# Script de déploiement automatisé pour Hugging Face Spaces
|
4 |
+
# Usage: ./deploy.sh <votre-username> <nom-du-space>
|
5 |
+
|
6 |
+
set -e # Arrêter en cas d'erreur
|
7 |
+
|
8 |
+
# Couleurs pour l'affichage
|
9 |
+
RED='\033[0;31m'
|
10 |
+
GREEN='\033[0;32m'
|
11 |
+
YELLOW='\033[1;33m'
|
12 |
+
BLUE='\033[0;34m'
|
13 |
+
NC='\033[0m' # No Color
|
14 |
+
|
15 |
+
# Fonction pour afficher les messages
|
16 |
+
print_status() {
|
17 |
+
echo -e "${BLUE}[INFO]${NC} $1"
|
18 |
+
}
|
19 |
+
|
20 |
+
print_success() {
|
21 |
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
22 |
+
}
|
23 |
+
|
24 |
+
print_warning() {
|
25 |
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
26 |
+
}
|
27 |
+
|
28 |
+
print_error() {
|
29 |
+
echo -e "${RED}[ERROR]${NC} $1"
|
30 |
+
}
|
31 |
+
|
32 |
+
# Vérification des arguments
|
33 |
+
if [ $# -ne 2 ]; then
|
34 |
+
print_error "Usage: $0 <votre-username> <nom-du-space>"
|
35 |
+
print_error "Exemple: $0 john sentiment-audio-analyzer"
|
36 |
+
exit 1
|
37 |
+
fi
|
38 |
+
|
39 |
+
USERNAME=$1
|
40 |
+
SPACE_NAME=$2
|
41 |
+
SPACE_URL="https://huggingface.co/spaces/$USERNAME/$SPACE_NAME"
|
42 |
+
|
43 |
+
print_status "Démarrage du déploiement pour $SPACE_URL"
|
44 |
+
|
45 |
+
# 1. Vérification de la structure du projet
|
46 |
+
print_status "Vérification de la structure du projet..."
|
47 |
+
|
48 |
+
required_files=(
|
49 |
+
"app.py"
|
50 |
+
"requirements_hf.txt"
|
51 |
+
"config.yaml"
|
52 |
+
"README_HF.md"
|
53 |
+
".gitattributes"
|
54 |
+
"src/__init__.py"
|
55 |
+
"src/transcription.py"
|
56 |
+
"src/sentiment.py"
|
57 |
+
"src/multimodal.py"
|
58 |
+
"src/inference.py"
|
59 |
+
)
|
60 |
+
|
61 |
+
for file in "${required_files[@]}"; do
|
62 |
+
if [ ! -f "$file" ]; then
|
63 |
+
print_error "Fichier manquant: $file"
|
64 |
+
exit 1
|
65 |
+
fi
|
66 |
+
done
|
67 |
+
|
68 |
+
print_success "Structure du projet validée"
|
69 |
+
|
70 |
+
# 2. Test du projet
|
71 |
+
print_status "Exécution des tests..."
|
72 |
+
|
73 |
+
if [ -f "test_deployment.py" ]; then
|
74 |
+
python test_deployment.py
|
75 |
+
if [ $? -ne 0 ]; then
|
76 |
+
print_error "Les tests ont échoué. Corrigez les problèmes avant de continuer."
|
77 |
+
exit 1
|
78 |
+
fi
|
79 |
+
print_success "Tests passés avec succès"
|
80 |
+
else
|
81 |
+
print_warning "Script de test non trouvé, passage des tests..."
|
82 |
+
fi
|
83 |
+
|
84 |
+
# 3. Vérification de Git
|
85 |
+
print_status "Vérification de Git..."
|
86 |
+
|
87 |
+
if ! command -v git &> /dev/null; then
|
88 |
+
print_error "Git n'est pas installé"
|
89 |
+
exit 1
|
90 |
+
fi
|
91 |
+
|
92 |
+
# 4. Initialisation Git si nécessaire
|
93 |
+
if [ ! -d ".git" ]; then
|
94 |
+
print_status "Initialisation du repository Git..."
|
95 |
+
git init
|
96 |
+
git add .
|
97 |
+
git commit -m "Initial commit"
|
98 |
+
fi
|
99 |
+
|
100 |
+
# 5. Ajout du remote Hugging Face
|
101 |
+
print_status "Configuration du remote Hugging Face..."
|
102 |
+
|
103 |
+
# Supprimer l'ancien remote s'il existe
|
104 |
+
git remote remove hf 2>/dev/null || true
|
105 |
+
|
106 |
+
# Ajouter le nouveau remote
|
107 |
+
git remote add hf "https://huggingface.co/spaces/$USERNAME/$SPACE_NAME"
|
108 |
+
|
109 |
+
print_success "Remote configuré: $SPACE_URL"
|
110 |
+
|
111 |
+
# 6. Préparation du commit
|
112 |
+
print_status "Préparation du commit..."
|
113 |
+
|
114 |
+
# Ajouter tous les fichiers
|
115 |
+
git add .
|
116 |
+
|
117 |
+
# Créer le commit
|
118 |
+
git commit -m "Deploy: Analyse de sentiment audio v1.0" || {
|
119 |
+
print_warning "Aucun changement détecté, commit ignoré"
|
120 |
+
}
|
121 |
+
|
122 |
+
# 7. Déploiement
|
123 |
+
print_status "Déploiement sur Hugging Face Spaces..."
|
124 |
+
|
125 |
+
# Demander confirmation
|
126 |
+
read -p "Voulez-vous déployer maintenant ? (y/N): " -n 1 -r
|
127 |
+
echo
|
128 |
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
129 |
+
print_warning "Déploiement annulé"
|
130 |
+
exit 0
|
131 |
+
fi
|
132 |
+
|
133 |
+
# Pousser vers Hugging Face
|
134 |
+
print_status "Poussage du code..."
|
135 |
+
git push hf main
|
136 |
+
|
137 |
+
print_success "Déploiement terminé avec succès !"
|
138 |
+
print_success "Votre Space est accessible à: $SPACE_URL"
|
139 |
+
|
140 |
+
# 8. Instructions post-déploiement
|
141 |
+
echo
|
142 |
+
print_status "Instructions post-déploiement:"
|
143 |
+
echo "1. Allez sur $SPACE_URL"
|
144 |
+
echo "2. Attendez que le build se termine (peut prendre 5-10 minutes)"
|
145 |
+
echo "3. Testez votre application"
|
146 |
+
echo "4. Consultez les logs en cas de problème"
|
147 |
+
|
148 |
+
# 9. Vérification du statut
|
149 |
+
print_status "Vérification du statut du Space..."
|
150 |
+
echo "Vous pouvez vérifier le statut à: $SPACE_URL"
|
151 |
+
|
152 |
+
print_success "Script de déploiement terminé !"
|
test_api.py
ADDED
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Script de test pour l'API REST
|
4 |
+
"""
|
5 |
+
|
6 |
+
import requests
|
7 |
+
import json
|
8 |
+
import tempfile
|
9 |
+
import numpy as np
|
10 |
+
import soundfile as sf
|
11 |
+
import time
|
12 |
+
|
13 |
+
def test_api_health(base_url):
|
14 |
+
"""Test de l'endpoint health"""
|
15 |
+
print("🔍 Test de l'endpoint health...")
|
16 |
+
|
17 |
+
try:
|
18 |
+
response = requests.get(f"{base_url}/api/health")
|
19 |
+
if response.status_code == 200:
|
20 |
+
data = response.json()
|
21 |
+
print(f"✅ Health check réussi: {data}")
|
22 |
+
return True
|
23 |
+
else:
|
24 |
+
print(f"❌ Health check échoué: {response.status_code}")
|
25 |
+
return False
|
26 |
+
except Exception as e:
|
27 |
+
print(f"❌ Erreur health check: {e}")
|
28 |
+
return False
|
29 |
+
|
30 |
+
def test_api_info(base_url):
|
31 |
+
"""Test de l'endpoint racine"""
|
32 |
+
print("🔍 Test de l'endpoint racine...")
|
33 |
+
|
34 |
+
try:
|
35 |
+
response = requests.get(f"{base_url}/api/")
|
36 |
+
if response.status_code == 200:
|
37 |
+
data = response.json()
|
38 |
+
print(f"✅ Info API récupérée: {data}")
|
39 |
+
return True
|
40 |
+
else:
|
41 |
+
print(f"❌ Info API échoué: {response.status_code}")
|
42 |
+
return False
|
43 |
+
except Exception as e:
|
44 |
+
print(f"❌ Erreur info API: {e}")
|
45 |
+
return False
|
46 |
+
|
47 |
+
def create_test_audio():
|
48 |
+
"""Crée un fichier audio de test"""
|
49 |
+
print("🎵 Création d'un fichier audio de test...")
|
50 |
+
|
51 |
+
# Créer un signal audio simple (1 seconde)
|
52 |
+
sample_rate = 16000
|
53 |
+
duration = 1.0
|
54 |
+
t = np.linspace(0, duration, int(sample_rate * duration))
|
55 |
+
|
56 |
+
# Signal avec parole simulée (fréquences vocales)
|
57 |
+
audio = 0.1 * np.sin(2 * np.pi * 440 * t) + 0.05 * np.sin(2 * np.pi * 880 * t)
|
58 |
+
|
59 |
+
# Sauvegarder
|
60 |
+
test_audio_path = "test_audio_api.wav"
|
61 |
+
sf.write(test_audio_path, audio, sample_rate)
|
62 |
+
print(f"✅ Fichier audio de test créé: {test_audio_path}")
|
63 |
+
|
64 |
+
return test_audio_path
|
65 |
+
|
66 |
+
def test_audio_prediction(base_url, audio_path):
|
67 |
+
"""Test de l'endpoint predict avec audio"""
|
68 |
+
print("🔍 Test de l'endpoint predict (audio)...")
|
69 |
+
|
70 |
+
try:
|
71 |
+
with open(audio_path, 'rb') as f:
|
72 |
+
files = {'file': f}
|
73 |
+
response = requests.post(f"{base_url}/api/predict", files=files)
|
74 |
+
|
75 |
+
if response.status_code == 200:
|
76 |
+
data = response.json()
|
77 |
+
print(f"✅ Prédiction audio réussie:")
|
78 |
+
print(f" Transcription: {data.get('transcription', 'N/A')}")
|
79 |
+
print(f" Sentiment: {data.get('sentiment', 'N/A')}")
|
80 |
+
return True
|
81 |
+
else:
|
82 |
+
print(f"❌ Prédiction audio échouée: {response.status_code}")
|
83 |
+
print(f" Erreur: {response.text}")
|
84 |
+
return False
|
85 |
+
except Exception as e:
|
86 |
+
print(f"❌ Erreur prédiction audio: {e}")
|
87 |
+
return False
|
88 |
+
|
89 |
+
def test_text_prediction(base_url):
|
90 |
+
"""Test de l'endpoint predict_text"""
|
91 |
+
print("🔍 Test de l'endpoint predict_text...")
|
92 |
+
|
93 |
+
test_texts = [
|
94 |
+
"je suis très content de ce produit",
|
95 |
+
"ce service est terrible",
|
96 |
+
"c'est neutre comme commentaire"
|
97 |
+
]
|
98 |
+
|
99 |
+
for text in test_texts:
|
100 |
+
try:
|
101 |
+
data = {"text": text}
|
102 |
+
response = requests.post(f"{base_url}/api/predict_text", json=data)
|
103 |
+
|
104 |
+
if response.status_code == 200:
|
105 |
+
result = response.json()
|
106 |
+
print(f"✅ Prédiction textuelle réussie pour '{text}':")
|
107 |
+
print(f" Sentiment: {result.get('sentiment', 'N/A')}")
|
108 |
+
else:
|
109 |
+
print(f"❌ Prédiction textuelle échouée pour '{text}': {response.status_code}")
|
110 |
+
return False
|
111 |
+
except Exception as e:
|
112 |
+
print(f"❌ Erreur prédiction textuelle: {e}")
|
113 |
+
return False
|
114 |
+
|
115 |
+
return True
|
116 |
+
|
117 |
+
def test_error_handling(base_url):
|
118 |
+
"""Test de la gestion d'erreurs"""
|
119 |
+
print("🔍 Test de la gestion d'erreurs...")
|
120 |
+
|
121 |
+
# Test avec fichier invalide
|
122 |
+
try:
|
123 |
+
with tempfile.NamedTemporaryFile(suffix='.txt', delete=False) as f:
|
124 |
+
f.write(b"Ceci n'est pas un fichier audio")
|
125 |
+
f.flush()
|
126 |
+
|
127 |
+
with open(f.name, 'rb') as audio_file:
|
128 |
+
files = {'file': audio_file}
|
129 |
+
response = requests.post(f"{base_url}/api/predict", files=files)
|
130 |
+
|
131 |
+
if response.status_code == 400:
|
132 |
+
print("✅ Gestion d'erreur fichier invalide: OK")
|
133 |
+
else:
|
134 |
+
print(f"❌ Gestion d'erreur fichier invalide: {response.status_code}")
|
135 |
+
return False
|
136 |
+
except Exception as e:
|
137 |
+
print(f"❌ Erreur test fichier invalide: {e}")
|
138 |
+
return False
|
139 |
+
|
140 |
+
# Test avec texte vide
|
141 |
+
try:
|
142 |
+
data = {"text": ""}
|
143 |
+
response = requests.post(f"{base_url}/api/predict_text", json=data)
|
144 |
+
|
145 |
+
if response.status_code in [200, 400]:
|
146 |
+
print("✅ Gestion d'erreur texte vide: OK")
|
147 |
+
else:
|
148 |
+
print(f"❌ Gestion d'erreur texte vide: {response.status_code}")
|
149 |
+
return False
|
150 |
+
except Exception as e:
|
151 |
+
print(f"❌ Erreur test texte vide: {e}")
|
152 |
+
return False
|
153 |
+
|
154 |
+
return True
|
155 |
+
|
156 |
+
def test_documentation(base_url):
|
157 |
+
"""Test de la documentation Swagger"""
|
158 |
+
print("🔍 Test de la documentation Swagger...")
|
159 |
+
|
160 |
+
try:
|
161 |
+
response = requests.get(f"{base_url}/api/docs")
|
162 |
+
if response.status_code == 200:
|
163 |
+
print("✅ Documentation Swagger accessible")
|
164 |
+
return True
|
165 |
+
else:
|
166 |
+
print(f"❌ Documentation Swagger inaccessible: {response.status_code}")
|
167 |
+
return False
|
168 |
+
except Exception as e:
|
169 |
+
print(f"❌ Erreur documentation Swagger: {e}")
|
170 |
+
return False
|
171 |
+
|
172 |
+
def main():
|
173 |
+
"""Fonction principale de test"""
|
174 |
+
print("🚀 Démarrage des tests de l'API...\n")
|
175 |
+
|
176 |
+
# URL de base (à adapter selon votre déploiement)
|
177 |
+
base_url = "http://localhost:7860" # Local
|
178 |
+
# base_url = "https://huggingface.co/spaces/<username>/sentiment-audio-analyzer" # HF Spaces
|
179 |
+
|
180 |
+
tests = [
|
181 |
+
("Health check", lambda: test_api_health(base_url)),
|
182 |
+
("Info API", lambda: test_api_info(base_url)),
|
183 |
+
("Documentation Swagger", lambda: test_documentation(base_url)),
|
184 |
+
("Gestion d'erreurs", lambda: test_error_handling(base_url)),
|
185 |
+
]
|
186 |
+
|
187 |
+
# Test avec audio (nécessite un fichier)
|
188 |
+
audio_path = create_test_audio()
|
189 |
+
tests.extend([
|
190 |
+
("Prédiction audio", lambda: test_audio_prediction(base_url, audio_path)),
|
191 |
+
("Prédiction textuelle", lambda: test_text_prediction(base_url)),
|
192 |
+
])
|
193 |
+
|
194 |
+
results = []
|
195 |
+
for test_name, test_func in tests:
|
196 |
+
print(f"\n{'='*50}")
|
197 |
+
print(f"Test: {test_name}")
|
198 |
+
print('='*50)
|
199 |
+
|
200 |
+
try:
|
201 |
+
result = test_func()
|
202 |
+
results.append((test_name, result))
|
203 |
+
except Exception as e:
|
204 |
+
print(f"❌ Erreur inattendue: {e}")
|
205 |
+
results.append((test_name, False))
|
206 |
+
|
207 |
+
# Résumé
|
208 |
+
print(f"\n{'='*50}")
|
209 |
+
print("📊 RÉSUMÉ DES TESTS API")
|
210 |
+
print('='*50)
|
211 |
+
|
212 |
+
passed = 0
|
213 |
+
total = len(results)
|
214 |
+
|
215 |
+
for test_name, result in results:
|
216 |
+
status = "✅ PASS" if result else "❌ FAIL"
|
217 |
+
print(f"{test_name}: {status}")
|
218 |
+
if result:
|
219 |
+
passed += 1
|
220 |
+
|
221 |
+
print(f"\nRésultat: {passed}/{total} tests réussis")
|
222 |
+
|
223 |
+
if passed == total:
|
224 |
+
print("🎉 Tous les tests API sont passés !")
|
225 |
+
return True
|
226 |
+
else:
|
227 |
+
print("⚠️ Certains tests API ont échoué.")
|
228 |
+
return False
|
229 |
+
|
230 |
+
if __name__ == "__main__":
|
231 |
+
success = main()
|
232 |
+
exit(0 if success else 1)
|
test_deployment.py
ADDED
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Script de test pour vérifier le bon fonctionnement de l'application
|
4 |
+
avant le déploiement sur Hugging Face Spaces.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import os
|
8 |
+
import sys
|
9 |
+
import tempfile
|
10 |
+
import numpy as np
|
11 |
+
import soundfile as sf
|
12 |
+
from pathlib import Path
|
13 |
+
|
14 |
+
def test_imports():
|
15 |
+
"""Test des imports nécessaires"""
|
16 |
+
print("🔍 Test des imports...")
|
17 |
+
|
18 |
+
try:
|
19 |
+
import gradio as gr
|
20 |
+
print("✅ Gradio importé avec succès")
|
21 |
+
except ImportError as e:
|
22 |
+
print(f"❌ Erreur import Gradio: {e}")
|
23 |
+
return False
|
24 |
+
|
25 |
+
try:
|
26 |
+
import torch
|
27 |
+
import torchaudio
|
28 |
+
print("✅ PyTorch et TorchAudio importés avec succès")
|
29 |
+
except ImportError as e:
|
30 |
+
print(f"❌ Erreur import PyTorch: {e}")
|
31 |
+
return False
|
32 |
+
|
33 |
+
try:
|
34 |
+
from transformers import Wav2Vec2Processor, Wav2Vec2ForCTC
|
35 |
+
print("✅ Transformers importé avec succès")
|
36 |
+
except ImportError as e:
|
37 |
+
print(f"❌ Erreur import Transformers: {e}")
|
38 |
+
return False
|
39 |
+
|
40 |
+
try:
|
41 |
+
from src.transcription import SpeechEncoder
|
42 |
+
from src.sentiment import TextEncoder
|
43 |
+
print("✅ Modules locaux importés avec succès")
|
44 |
+
except ImportError as e:
|
45 |
+
print(f"❌ Erreur import modules locaux: {e}")
|
46 |
+
return False
|
47 |
+
|
48 |
+
return True
|
49 |
+
|
50 |
+
def test_audio_generation():
|
51 |
+
"""Génère un fichier audio de test"""
|
52 |
+
print("🎵 Génération d'un fichier audio de test...")
|
53 |
+
|
54 |
+
# Créer un signal audio simple (1 seconde de silence avec un bip)
|
55 |
+
sample_rate = 16000
|
56 |
+
duration = 1.0
|
57 |
+
t = np.linspace(0, duration, int(sample_rate * duration))
|
58 |
+
|
59 |
+
# Signal simple (440 Hz)
|
60 |
+
audio = 0.1 * np.sin(2 * np.pi * 440 * t)
|
61 |
+
|
62 |
+
# Sauvegarder
|
63 |
+
test_audio_path = "test_audio.wav"
|
64 |
+
sf.write(test_audio_path, audio, sample_rate)
|
65 |
+
print(f"✅ Fichier audio de test créé: {test_audio_path}")
|
66 |
+
|
67 |
+
return test_audio_path
|
68 |
+
|
69 |
+
def test_model_loading():
|
70 |
+
"""Test du chargement des modèles"""
|
71 |
+
print("🤖 Test du chargement des modèles...")
|
72 |
+
|
73 |
+
try:
|
74 |
+
from transformers import Wav2Vec2Processor, Wav2Vec2ForCTC
|
75 |
+
|
76 |
+
# Test avec un modèle plus petit pour les tests
|
77 |
+
processor = Wav2Vec2Processor.from_pretrained(
|
78 |
+
"facebook/wav2vec2-base-960h",
|
79 |
+
cache_dir="./test_models"
|
80 |
+
)
|
81 |
+
model = Wav2Vec2ForCTC.from_pretrained(
|
82 |
+
"facebook/wav2vec2-base-960h",
|
83 |
+
cache_dir="./test_models"
|
84 |
+
)
|
85 |
+
print("✅ Modèles chargés avec succès")
|
86 |
+
return True
|
87 |
+
except Exception as e:
|
88 |
+
print(f"❌ Erreur chargement modèles: {e}")
|
89 |
+
return False
|
90 |
+
|
91 |
+
def test_app_creation():
|
92 |
+
"""Test de la création de l'application Gradio"""
|
93 |
+
print("🎨 Test de la création de l'application...")
|
94 |
+
|
95 |
+
try:
|
96 |
+
import gradio as gr
|
97 |
+
|
98 |
+
# Créer une interface simple
|
99 |
+
def dummy_function(audio):
|
100 |
+
return "Test transcription", "Test sentiment"
|
101 |
+
|
102 |
+
demo = gr.Interface(
|
103 |
+
fn=dummy_function,
|
104 |
+
inputs=gr.Audio(type="filepath"),
|
105 |
+
outputs=[gr.Textbox(), gr.Textbox()],
|
106 |
+
title="Test App"
|
107 |
+
)
|
108 |
+
print("✅ Application Gradio créée avec succès")
|
109 |
+
return True
|
110 |
+
except Exception as e:
|
111 |
+
print(f"❌ Erreur création app: {e}")
|
112 |
+
return False
|
113 |
+
|
114 |
+
def test_file_structure():
|
115 |
+
"""Vérifie la structure des fichiers"""
|
116 |
+
print("📁 Vérification de la structure des fichiers...")
|
117 |
+
|
118 |
+
required_files = [
|
119 |
+
"app.py",
|
120 |
+
"requirements_hf.txt",
|
121 |
+
"config.yaml",
|
122 |
+
"README_HF.md",
|
123 |
+
".gitattributes",
|
124 |
+
"src/__init__.py",
|
125 |
+
"src/transcription.py",
|
126 |
+
"src/sentiment.py",
|
127 |
+
"src/multimodal.py",
|
128 |
+
"src/inference.py"
|
129 |
+
]
|
130 |
+
|
131 |
+
missing_files = []
|
132 |
+
for file_path in required_files:
|
133 |
+
if not Path(file_path).exists():
|
134 |
+
missing_files.append(file_path)
|
135 |
+
else:
|
136 |
+
print(f"✅ {file_path}")
|
137 |
+
|
138 |
+
if missing_files:
|
139 |
+
print(f"❌ Fichiers manquants: {missing_files}")
|
140 |
+
return False
|
141 |
+
|
142 |
+
print("✅ Tous les fichiers requis sont présents")
|
143 |
+
return True
|
144 |
+
|
145 |
+
def test_requirements():
|
146 |
+
"""Vérifie le fichier requirements"""
|
147 |
+
print("📦 Vérification du fichier requirements...")
|
148 |
+
|
149 |
+
try:
|
150 |
+
with open("requirements_hf.txt", "r") as f:
|
151 |
+
requirements = f.read()
|
152 |
+
|
153 |
+
# Vérifier les dépendances essentielles
|
154 |
+
essential_deps = ["gradio", "torch", "transformers", "soundfile"]
|
155 |
+
missing_deps = []
|
156 |
+
|
157 |
+
for dep in essential_deps:
|
158 |
+
if dep not in requirements:
|
159 |
+
missing_deps.append(dep)
|
160 |
+
|
161 |
+
if missing_deps:
|
162 |
+
print(f"❌ Dépendances manquantes: {missing_deps}")
|
163 |
+
return False
|
164 |
+
|
165 |
+
print("✅ Fichier requirements valide")
|
166 |
+
return True
|
167 |
+
except Exception as e:
|
168 |
+
print(f"❌ Erreur lecture requirements: {e}")
|
169 |
+
return False
|
170 |
+
|
171 |
+
def main():
|
172 |
+
"""Fonction principale de test"""
|
173 |
+
print("🚀 Démarrage des tests de déploiement...\n")
|
174 |
+
|
175 |
+
tests = [
|
176 |
+
("Structure des fichiers", test_file_structure),
|
177 |
+
("Fichier requirements", test_requirements),
|
178 |
+
("Imports", test_imports),
|
179 |
+
("Chargement modèles", test_model_loading),
|
180 |
+
("Création app", test_app_creation),
|
181 |
+
]
|
182 |
+
|
183 |
+
results = []
|
184 |
+
for test_name, test_func in tests:
|
185 |
+
print(f"\n{'='*50}")
|
186 |
+
print(f"Test: {test_name}")
|
187 |
+
print('='*50)
|
188 |
+
|
189 |
+
try:
|
190 |
+
result = test_func()
|
191 |
+
results.append((test_name, result))
|
192 |
+
except Exception as e:
|
193 |
+
print(f"❌ Erreur inattendue: {e}")
|
194 |
+
results.append((test_name, False))
|
195 |
+
|
196 |
+
# Résumé
|
197 |
+
print(f"\n{'='*50}")
|
198 |
+
print("📊 RÉSUMÉ DES TESTS")
|
199 |
+
print('='*50)
|
200 |
+
|
201 |
+
passed = 0
|
202 |
+
total = len(results)
|
203 |
+
|
204 |
+
for test_name, result in results:
|
205 |
+
status = "✅ PASS" if result else "❌ FAIL"
|
206 |
+
print(f"{test_name}: {status}")
|
207 |
+
if result:
|
208 |
+
passed += 1
|
209 |
+
|
210 |
+
print(f"\nRésultat: {passed}/{total} tests réussis")
|
211 |
+
|
212 |
+
if passed == total:
|
213 |
+
print("🎉 Tous les tests sont passés ! Votre projet est prêt pour le déploiement.")
|
214 |
+
return True
|
215 |
+
else:
|
216 |
+
print("⚠️ Certains tests ont échoué. Corrigez les problèmes avant le déploiement.")
|
217 |
+
return False
|
218 |
+
|
219 |
+
if __name__ == "__main__":
|
220 |
+
success = main()
|
221 |
+
sys.exit(0 if success else 1)
|