Marcel0123 commited on
Commit
e0317f8
·
verified ·
1 Parent(s): acf9a0b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -192
app.py CHANGED
@@ -1,201 +1,101 @@
1
 
2
- import os
3
- import time
4
- import tempfile
5
- import random
6
- import numpy as np
7
  import gradio as gr
8
-
9
- # Optioneel: sneller importeren als torch aanwezig is
10
- try:
11
- import torch
12
- HAS_CUDA = torch.cuda.is_available()
13
- except Exception:
14
- HAS_CUDA = False
15
-
16
- # Coqui TTS (XTTS-v2)
17
- from TTS.api import TTS
18
-
19
- # Audio post-processing
20
- import soundfile as sf
21
- import librosa
22
-
23
- MODEL_NAME = "tts_models/multilingual/multi-dataset/xtts_v2"
24
-
25
- # Laad model één keer bij startup
26
- def load_model():
27
- # Gebruik GPU als beschikbaar; anders CPU
28
- t0 = time.time()
29
- tts = TTS(MODEL_NAME, gpu=HAS_CUDA)
30
- load_s = f"{time.time() - t0:.1f}s"
31
- return tts, load_s
32
-
33
- tts, MODEL_LOAD_SECONDS = load_model()
34
-
35
- # Beperkte maar praktische set van taal-codes die XTTS goed aankan
36
- LANGS = [
37
- "nl", "en", "de", "fr", "es", "it", "pt", "pl", "tr", "ru", "ja", "ko", "zh"
38
- ]
39
-
40
- HELP_MD = """
41
- ### ℹ️ Hoe gebruik je deze Space
42
- 1. **Neem 6–20 seconden referentie-audio op** (of upload een bestaand fragment) van de stem die je wilt klonen.
43
- 2. Kies de **taal** van de **uitvoer** (niet per se de referentietaal).
44
- 3. Vink aan dat je **toestemming** hebt van de stem-eigenaar.
45
- 4. Voer de **tekst** in en klik **Genereer**.
46
-
47
- > Gebruik dit alleen met **expliciete toestemming**. De uitvoer is synthetisch; misleiding is verboden.
48
- """
49
-
50
- EXAMPLE_TEXTS = [
51
- "Welkom! Dit is een korte demo van de AI-stemkloner.",
52
- "Vandaag bespreken we de roadmap en de belangrijkste prioriteiten.",
53
- "Dit is een testzin om te horen hoe de uitspraak klinkt.",
54
- "Hallo! Fijn dat je luistert naar deze AI-gegenereerde stem.",
55
- ]
56
-
57
- def validate_inputs(text, ref_audio, declare_consent):
58
- if not declare_consent:
59
- raise gr.Error("Je moet bevestigen dat je toestemming hebt van de stem-eigenaar.")
60
- if not text or not text.strip():
61
- raise gr.Error("Voer tekst in om uit te spreken.")
62
- if ref_audio is None:
63
- raise gr.Error("Upload of neem een korte referentie-audio op (ca. 6–20 s).")
64
-
65
- def add_silence(wav, sr, pre_ms=0, post_ms=0):
66
- pre = np.zeros(int(sr * pre_ms / 1000.0), dtype=wav.dtype)
67
- post = np.zeros(int(sr * post_ms / 1000.0), dtype=wav.dtype)
68
- if wav.ndim == 1:
69
- return np.concatenate([pre, wav, post], axis=0)
70
- else:
71
- pre = np.tile(pre[:, None], (1, wav.shape[1]))
72
- post = np.tile(post[:, None], (1, wav.shape[1]))
73
- return np.concatenate([pre, wav, post], axis=0)
74
-
75
- def time_stretch_safe(wav, sr, rate=1.0):
76
- # Gebruik librosa voor tijdrekken zonder toonhoogte te veranderen
77
- if rate == 1.0:
78
- return wav
79
- # librosa verwacht mono; als stereo -> per kanaal
80
- if wav.ndim == 1:
81
- return librosa.effects.time_stretch(wav, rate=rate)
82
- else:
83
- chs = []
84
- for ch in range(wav.shape[1]):
85
- chs.append(librosa.effects.time_stretch(wav[:, ch], rate=rate))
86
- # pad / trim naar gelijke lengte
87
- maxlen = max(c.shape[0] for c in chs)
88
- chs = [np.pad(c, (0, maxlen - c.shape[0])) for c in chs]
89
- return np.stack(chs, axis=1)
90
-
91
- def postprocess_audio(path, speed, pre_ms, post_ms):
92
- y, sr = librosa.load(path, sr=None, mono=False)
93
- y_proc = time_stretch_safe(y, sr, rate=speed)
94
- y_proc = add_silence(y_proc, sr, pre_ms=pre_ms, post_ms=post_ms)
95
- # Overschrijf of schrijf naar nieuw pad
96
- out_path = os.path.join(tempfile.gettempdir(), f"xtts_out_pp_{int(time.time())}.wav")
97
- sf.write(out_path, y_proc, sr)
98
- return out_path
99
-
100
- def tts_clone(text, ref_audio, language, declare_consent, speed, pre_ms, post_ms, seed, progress=gr.Progress(track_tqdm=True)):
101
- validate_inputs(text, ref_audio, declare_consent)
102
-
103
- # Seeds instellen voor reproduceerbaarheid (voor zover mogelijk)
104
- try:
105
- random.seed(seed)
106
- np.random.seed(seed % (2**32 - 1))
107
- if 'torch' in globals():
108
- torch.manual_seed(seed)
109
- if HAS_CUDA:
110
- torch.cuda.manual_seed_all(seed)
111
- except Exception:
112
- pass
113
-
114
- progress(0, desc="Controleren van invoer…")
115
- # Bestandslimiet check
116
- try:
117
- size_mb = os.path.getsize(ref_audio) / (1024 * 1024)
118
- if size_mb > 5:
119
- raise gr.Error("Referentie-audio is groter dan 5 MB. Gebruik een korter fragment (6–20 s).")
120
- except Exception:
121
- pass
122
-
123
- progress(0.2, desc="Synthese bezig…")
124
- raw_out = os.path.join(tempfile.gettempdir(), f"xtts_out_{int(time.time())}.wav")
125
-
126
- try:
127
- tts.tts_to_file(
128
- text=text,
129
- file_path=raw_out,
130
- speaker_wav=ref_audio,
131
- language=language
132
- )
133
- except Exception as e:
134
- raise gr.Error(f"Er ging iets mis bij synthese: {e}")
135
-
136
- progress(0.7, desc="Post-processing (tempo/pauzes)…")
137
- final_out = postprocess_audio(raw_out, speed=float(speed), pre_ms=int(pre_ms), post_ms=int(post_ms))
138
-
139
- progress(0.98, desc="Afronden…")
140
- time.sleep(0.1)
141
- return final_out, final_out # audio output + download button value
142
-
143
- with gr.Blocks(theme=gr.themes.Soft(), css="footer {visibility: hidden}") as demo:
144
- gr.Markdown(
145
- f"## 🔊 AI-Stemkloner (XTTS-v2)\n"
146
- f"**Model:** `{MODEL_NAME}` – geladen in ~{MODEL_LOAD_SECONDS} • "
147
- + ("**GPU** gedetecteerd ✅" if HAS_CUDA else "**CPU** modus 🐢 (langzamer)")
148
- )
149
-
150
  with gr.Row():
151
  with gr.Column(scale=1):
152
- gr.Markdown(HELP_MD)
153
- lang = gr.Dropdown(choices=LANGS, value="nl", label="Taal van de uitvoer")
154
- consent = gr.Checkbox(
155
- label="Ik heb expliciete toestemming van de stem-eigenaar.",
156
- value=False
157
- )
158
- ref = gr.Audio(
159
- sources=["microphone", "upload"],
160
- type="filepath",
161
- label="Referentie-stem (6–20 s, .wav/.mp3)",
162
- )
163
- text = gr.Textbox(
164
- label="Tekst om uit te spreken",
165
- placeholder="Typ hier je tekst…",
166
- lines=4,
167
- value=EXAMPLE_TEXTS[0]
168
- )
169
-
170
- with gr.Accordion("Geavanceerd", open=False):
171
- speed = gr.Slider(0.5, 1.5, value=1.0, step=0.05, label="Tempo (0.5–1.5×)")
172
- pre_ms = gr.Slider(0, 2000, value=0, step=50, label="Pauze vóór (ms)")
173
- post_ms = gr.Slider(0, 2000, value=0, step=50, label="Pauze ná (ms)")
174
- seed = gr.Number(value=42, precision=0, label="Seed (reproduceerbaarheid)")
175
-
176
- with gr.Row():
177
- btn = gr.Button("Genereer", variant="primary")
178
- clear = gr.Button("Wissen")
179
-
180
  with gr.Column(scale=1):
181
- out = gr.Audio(label="Uitvoer", type="filepath")
182
- download = gr.DownloadButton(label="Download WAV", value=None)
183
- gr.Markdown(
184
- "#### Tips\n"
185
- "- Beste kwaliteit: een **stille, heldere** referentie-opname.\n"
186
- "- Praat natuurlijk; 10–15 seconden werkt meestal prima.\n"
187
- "- **Kies de juiste uitvoertaal** voor betere uitspraak.\n"
188
- "- Geavanceerd: pas **tempo** en **stilte** aan, gebruik een **seed** om variatie te fixeren.\n"
189
- )
190
 
191
- # Wiring
192
- btn.click(
193
- fn=tts_clone,
194
- inputs=[text, ref, lang, consent, speed, pre_ms, post_ms, seed],
195
- outputs=[out, download],
196
- api_name="clone"
197
- )
198
- clear.click(lambda: (None, None), outputs=[out, download])
199
 
200
  if __name__ == "__main__":
201
  demo.launch()
 
1
 
 
 
 
 
 
2
  import gradio as gr
3
+ import numpy as np
4
+ from PIL import Image, ImageDraw, ImageFont
5
+ from deepface import DeepFace
6
+
7
+ # Instellingen voor "gewoon werken"
8
+ DETECTOR = "retinaface" # nauwkeuriger dan 'opencv'
9
+ TAU = 0.65 # onder deze confidence -> "Onzeker"
10
+ ACTIONS = ["emotion"]
11
+
12
+ def analyze_batch(files, tau=TAU, show_boxes=True):
13
+ all_rows = []
14
+ visuals = []
15
+
16
+ for f in files:
17
+ img = Image.open(f).convert("RGB")
18
+ np_img = np.array(img)
19
+
20
+ try:
21
+ result = DeepFace.analyze(
22
+ img_path=np_img,
23
+ actions=ACTIONS,
24
+ detector_backend=DETECTOR,
25
+ enforce_detection=False # crash niet als er geen gezicht is
26
+ )
27
+ except Exception as e:
28
+ all_rows.append({"file": f.name, "error": str(e)})
29
+ visuals.append(img)
30
+ continue
31
+
32
+ # DeepFace kan 1 dict of list teruggeven
33
+ results = result if isinstance(result, list) else [result]
34
+
35
+ draw = ImageDraw.Draw(img.copy())
36
+ try:
37
+ font = ImageFont.truetype("DejaVuSans.ttf", 16)
38
+ except:
39
+ font = None
40
+
41
+ rows = []
42
+ for r in results:
43
+ region = r.get("region") or {}
44
+ x, y, w, h = region.get("x",0), region.get("y",0), region.get("w",0), region.get("h",0)
45
+
46
+ emotions = r.get("emotion", {})
47
+ if not emotions or w==0 or h==0:
48
+ continue
49
+
50
+ # Sorteer op score (DeepFace geeft percentages 0..100)
51
+ sorted_items = sorted(emotions.items(), key=lambda kv: kv[1], reverse=True)
52
+ top_label, top_score = sorted_items[0][0], float(sorted_items[0][1]) / 100.0
53
+ final_label = top_label if top_score >= float(tau) else "Onzeker"
54
+
55
+ rows.append({
56
+ "bbox": {"x": x, "y": y, "w": w, "h": h},
57
+ "top1": top_label,
58
+ "confidence": round(top_score,3),
59
+ "label": final_label,
60
+ "top3": [
61
+ {"label": l, "conf": round(s/100.0,3)}
62
+ for l, s in sorted_items[:3]
63
+ ]
64
+ })
65
+
66
+ if show_boxes and w>0 and h>0:
67
+ draw.rectangle([x, y, x+w, y+h], outline=(0,255,0), width=3)
68
+ txt = f"{final_label} {int(top_score*100)}%"
69
+ tw, th = draw.textbbox((0,0), txt, font=font)[2:]
70
+ draw.rectangle([x, y-(th+6), x+tw+8, y], fill=(0,255,0))
71
+ draw.text((x+4, y-(th+5)), txt, fill=(0,0,0), font=font)
72
+
73
+ all_rows.append({
74
+ "file": f.name,
75
+ "faces": rows,
76
+ "note": "Geen gezichten gevonden." if not rows else f"Gezichten: {len(rows)}"
77
+ })
78
+ visuals.append(img if not rows else draw.im)
79
+
80
+ return visuals, all_rows
81
+
82
+ with gr.Blocks(title="Simpel & Betrouwbaar: Emotieherkenning") as demo:
83
+ gr.Markdown("## 😀 Emotieherkenning (serie foto’s)\n"
84
+ "- **RetinaFace** detectie + alignment\n"
85
+ "- Emoties: angry, disgust, fear, happy, sad, surprise, neutral\n"
86
+ "- Drempel voor **'Onzeker'** om fouten te voorkomen\n"
87
+ "> Let op: dit is een schatting van **gezichtsuitdrukking** (geen gemoedstoestand/intenties).")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  with gr.Row():
89
  with gr.Column(scale=1):
90
+ files = gr.File(label="Upload meerdere foto’s", file_count="multiple", type="filepath")
91
+ tau = gr.Slider(0.5, 0.9, value=TAU, step=0.01, label="Drempel τ voor 'Onzeker'")
92
+ show_boxes = gr.Checkbox(True, label="Toon kaders & labels")
93
+ btn = gr.Button("Analyseer")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  with gr.Column(scale=1):
95
+ gallery = gr.Gallery(label="Resultaat (met labels)").style(grid=2, height="auto")
96
+ out_json = gr.JSON(label="Details (per foto en gezicht)")
 
 
 
 
 
 
 
97
 
98
+ btn.click(analyze_batch, [files, tau, show_boxes], [gallery, out_json])
 
 
 
 
 
 
 
99
 
100
  if __name__ == "__main__":
101
  demo.launch()