haepada commited on
Commit
a29bd41
·
verified ·
1 Parent(s): 45493cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +110 -52
app.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import gradio as gr
2
  import numpy as np
3
  import librosa
@@ -25,14 +27,35 @@ text_analyzer = pipeline(
25
  model="nlptown/bert-base-multilingual-uncased-sentiment"
26
  )
27
 
28
- def map_acoustic_to_emotion(features):
29
- """음향학적 특성을 감정으로 매핑"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  # 음성 특성 정규화
31
- energy_norm = min(features["energy"] * 100, 100) # 에너지 레벨 (0-100)
32
- tempo_norm = min(features["tempo"] / 200, 1) # 템포 정규화 (0-1)
33
- pitch_norm = min(features["pitch"] * 2, 1) # 피치 정규화 (0-1)
34
-
35
- # 상세 감정 분석
 
 
 
 
 
36
  emotions = {
37
  "primary": "",
38
  "intensity": energy_norm,
@@ -40,8 +63,8 @@ def map_acoustic_to_emotion(features):
40
  "secondary": "",
41
  "characteristics": []
42
  }
43
-
44
- # 주요 감정 결정
45
  if energy_norm > 70:
46
  if tempo_norm > 0.6:
47
  emotions["primary"] = "기쁨/열정"
@@ -50,7 +73,6 @@ def map_acoustic_to_emotion(features):
50
  emotions["primary"] = "분노/강조"
51
  emotions["characteristics"].append("강한 음성 강도")
52
  emotions["confidence"] = energy_norm / 100
53
-
54
  elif pitch_norm > 0.6:
55
  if energy_norm > 50:
56
  emotions["primary"] = "놀람/흥분"
@@ -59,7 +81,6 @@ def map_acoustic_to_emotion(features):
59
  emotions["primary"] = "관심/호기심"
60
  emotions["characteristics"].append("음고 변화가 큼")
61
  emotions["confidence"] = pitch_norm
62
-
63
  elif energy_norm < 30:
64
  if tempo_norm < 0.4:
65
  emotions["primary"] = "슬픔/우울"
@@ -68,7 +89,6 @@ def map_acoustic_to_emotion(features):
68
  emotions["primary"] = "피로/무기력"
69
  emotions["characteristics"].append("낮은 에너지 레벨")
70
  emotions["confidence"] = (30 - energy_norm) / 30
71
-
72
  else:
73
  if tempo_norm > 0.5:
74
  emotions["primary"] = "평온/안정"
@@ -77,15 +97,14 @@ def map_acoustic_to_emotion(features):
77
  emotions["primary"] = "차분/진지"
78
  emotions["characteristics"].append("안정적인 음성 특성")
79
  emotions["confidence"] = 0.5
80
-
81
- # 음성 특성 상세 분석
82
  emotions["details"] = {
83
  "energy_level": f"{energy_norm:.1f}%",
84
  "speech_rate": f"{'빠름' if tempo_norm > 0.6 else '보통' if tempo_norm > 0.4 else '느림'}",
85
  "pitch_variation": f"{'높음' if pitch_norm > 0.6 else '보통' if pitch_norm > 0.3 else '낮음'}",
86
  "voice_volume": f"{'큼' if features['volume'] > 0.7 else '보통' if features['volume'] > 0.3 else '작음'}"
87
  }
88
-
89
  return emotions
90
 
91
  def generate_image_from_prompt(prompt):
@@ -116,7 +135,6 @@ def generate_image_from_prompt(prompt):
116
  print(f"Error: {response.status_code}")
117
  print(f"Response: {response.text}")
118
  return None
119
-
120
  except Exception as e:
121
  print(f"Error generating image: {str(e)}")
122
  return None
@@ -133,8 +151,7 @@ def generate_detailed_prompt(text, emotions, text_sentiment):
133
  "평온/안정": "부드러운 초록과 베이지",
134
  "차분/진지": "차분한 남색과 깊은 보라"
135
  }
136
-
137
- # 감정 강도에 따른 시각적 표현
138
  if emotions["intensity"] > 70:
139
  visual_style = "역동적인 붓질과 강한 대비"
140
  elif emotions["intensity"] > 40:
@@ -142,7 +159,6 @@ def generate_detailed_prompt(text, emotions, text_sentiment):
142
  else:
143
  visual_style = "부드러운 그라데이션과 차분한 톤"
144
 
145
- # 프롬프트 구성
146
  prompt = f"한국 전통 민화 스타일의 추상화, {emotion_colors.get(emotions['primary'], '자연스러운 색상')} 기반. "
147
  prompt += f"{visual_style}로 표현된 {emotions['primary']}의 감정. "
148
  prompt += f"음성의 특징({', '.join(emotions['characteristics'])})을 화면의 동적 요소로 표현. "
@@ -154,11 +170,12 @@ def create_interface():
154
  with gr.Blocks(theme=gr.themes.Soft()) as app:
155
  state = gr.State({
156
  "user_name": "",
 
157
  "reflections": [],
158
  "voice_analysis": None,
159
  "final_prompt": ""
160
  })
161
- z
162
  # 헤더
163
  header = gr.Markdown("# 디지털 굿판")
164
  user_display = gr.Markdown("")
@@ -166,10 +183,22 @@ z
166
  with gr.Tabs() as tabs:
167
  # 입장
168
  with gr.Tab("입장"):
169
- gr.Markdown("""# 디지털 굿판에 오신 것을 환영합니다""")
170
  name_input = gr.Textbox(label="이름을 알려주세요")
171
  start_btn = gr.Button("여정 시작하기")
172
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  # 청신
174
  with gr.Tab("청신"):
175
  with gr.Row():
@@ -192,25 +221,6 @@ z
192
  label="기록된 감상들"
193
  )
194
 
195
- def save_reflection(text, state):
196
- """감상 저장"""
197
- if not text.strip():
198
- return state, state["reflections"]
199
-
200
- try:
201
- current_time = datetime.now().strftime("%H:%M:%S")
202
- sentiment = text_analyzer(text)[0]
203
- new_reflection = [current_time, text, f"{sentiment['label']} ({sentiment['score']:.2f})"]
204
-
205
- if "reflections" not in state:
206
- state["reflections"] = []
207
-
208
- state["reflections"].append(new_reflection)
209
- return state, state["reflections"]
210
- except Exception as e:
211
- print(f"Error in save_reflection: {str(e)}")
212
- return state, []
213
-
214
  # 기원
215
  with gr.Tab("기원"):
216
  gr.Markdown("## 기원 - 목소리로 전하기")
@@ -254,10 +264,52 @@ z
254
  type="pil"
255
  )
256
 
257
- # 인터페이스 함수들
258
  def start_journey(name):
259
  """여정 시작"""
260
- return f"# 환영합니다, {name}님의 디지털 굿판", gr.update(selected="청신")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
  def clear_voice_input():
263
  """음성 입력 초기화"""
@@ -271,7 +323,6 @@ z
271
  try:
272
  y, sr = librosa.load(audio_path, sr=16000)
273
 
274
- # 음향학적 특성 분석
275
  acoustic_features = {
276
  "energy": float(np.mean(librosa.feature.rms(y=y))),
277
  "tempo": float(librosa.beat.tempo(y)[0]),
@@ -279,17 +330,14 @@ z
279
  "volume": float(np.mean(np.abs(y)))
280
  }
281
 
282
- # 감정 분석
283
- emotions = map_acoustic_to_emotion(acoustic_features)
 
284
 
285
- # 음성 인식
286
  transcription = speech_recognizer(y)
287
  text = transcription["text"]
288
-
289
- # 텍스트 감정 분석
290
  text_sentiment = text_analyzer(text)[0]
291
 
292
- # 결과 포맷팅
293
  voice_result = (
294
  f"음성 감정: {emotions['primary']} "
295
  f"(강도: {emotions['intensity']:.1f}%, 신뢰도: {emotions['confidence']:.2f})\n"
@@ -301,9 +349,11 @@ z
301
  f"- 음성 크기: {emotions['details']['voice_volume']}"
302
  )
303
 
304
- text_result = f"텍스트 감정 분석 (1-5): {text_sentiment['score']}"
 
 
305
 
306
- # 프롬프트 생성
307
  prompt = generate_detailed_prompt(text, emotions, text_sentiment)
308
 
309
  return state, text, voice_result, text_result, prompt
@@ -312,11 +362,17 @@ z
312
 
313
  # 이벤트 연결
314
  start_btn.click(
315
- fn=lambda name: (f"# 환영합니다, {name}님의 디지털 굿판", gr.update(selected="청신")),
316
  inputs=[name_input],
317
  outputs=[user_display, tabs]
318
  )
319
 
 
 
 
 
 
 
320
  save_btn.click(
321
  fn=save_reflection,
322
  inputs=[reflection_input, state],
@@ -341,6 +397,8 @@ z
341
  outputs=[result_image]
342
  )
343
 
 
 
344
  if __name__ == "__main__":
345
  demo = create_interface()
346
  demo.launch(debug=True)
 
1
+ ```python
2
+ # 1/2 시작 - app.py
3
  import gradio as gr
4
  import numpy as np
5
  import librosa
 
27
  model="nlptown/bert-base-multilingual-uncased-sentiment"
28
  )
29
 
30
+ def calculate_baseline_features(audio_path):
31
+ """기준점 음성 특성 분석"""
32
+ try:
33
+ y, sr = librosa.load(audio_path, sr=16000)
34
+ features = {
35
+ "energy": float(np.mean(librosa.feature.rms(y=y))),
36
+ "tempo": float(librosa.beat.tempo(y)[0]),
37
+ "pitch": float(np.mean(librosa.feature.zero_crossing_rate(y))),
38
+ "volume": float(np.mean(np.abs(y))),
39
+ "mfcc": librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13).mean(axis=1).tolist()
40
+ }
41
+ return features
42
+ except Exception as e:
43
+ print(f"Error calculating baseline: {str(e)}")
44
+ return None
45
+
46
+ def map_acoustic_to_emotion(features, baseline_features=None):
47
+ """음향학적 특성을 감정으로 매핑 (기준점 대비)"""
48
  # 음성 특성 정규화
49
+ energy_norm = min(features["energy"] * 100, 100)
50
+ tempo_norm = min(features["tempo"] / 200, 1)
51
+ pitch_norm = min(features["pitch"] * 2, 1)
52
+
53
+ # 기준점이 있는 경우 상대적 변화 계산
54
+ if baseline_features:
55
+ energy_norm = (features["energy"] / baseline_features["energy"]) * 50
56
+ tempo_norm = (features["tempo"] / baseline_features["tempo"])
57
+ pitch_norm = (features["pitch"] / baseline_features["pitch"])
58
+
59
  emotions = {
60
  "primary": "",
61
  "intensity": energy_norm,
 
63
  "secondary": "",
64
  "characteristics": []
65
  }
66
+
67
+ # 감정 매핑 로직
68
  if energy_norm > 70:
69
  if tempo_norm > 0.6:
70
  emotions["primary"] = "기쁨/열정"
 
73
  emotions["primary"] = "분노/강조"
74
  emotions["characteristics"].append("강한 음성 강도")
75
  emotions["confidence"] = energy_norm / 100
 
76
  elif pitch_norm > 0.6:
77
  if energy_norm > 50:
78
  emotions["primary"] = "놀람/흥분"
 
81
  emotions["primary"] = "관심/호기심"
82
  emotions["characteristics"].append("음고 변화가 큼")
83
  emotions["confidence"] = pitch_norm
 
84
  elif energy_norm < 30:
85
  if tempo_norm < 0.4:
86
  emotions["primary"] = "슬픔/우울"
 
89
  emotions["primary"] = "피로/무기력"
90
  emotions["characteristics"].append("낮은 에너지 레벨")
91
  emotions["confidence"] = (30 - energy_norm) / 30
 
92
  else:
93
  if tempo_norm > 0.5:
94
  emotions["primary"] = "평온/안정"
 
97
  emotions["primary"] = "차분/진지"
98
  emotions["characteristics"].append("안정적인 음성 특성")
99
  emotions["confidence"] = 0.5
100
+
 
101
  emotions["details"] = {
102
  "energy_level": f"{energy_norm:.1f}%",
103
  "speech_rate": f"{'빠름' if tempo_norm > 0.6 else '보통' if tempo_norm > 0.4 else '느림'}",
104
  "pitch_variation": f"{'높음' if pitch_norm > 0.6 else '보통' if pitch_norm > 0.3 else '낮음'}",
105
  "voice_volume": f"{'큼' if features['volume'] > 0.7 else '보통' if features['volume'] > 0.3 else '작음'}"
106
  }
107
+
108
  return emotions
109
 
110
  def generate_image_from_prompt(prompt):
 
135
  print(f"Error: {response.status_code}")
136
  print(f"Response: {response.text}")
137
  return None
 
138
  except Exception as e:
139
  print(f"Error generating image: {str(e)}")
140
  return None
 
151
  "평온/안정": "부드러운 초록과 베이지",
152
  "차분/진지": "차분한 남색과 깊은 보라"
153
  }
154
+
 
155
  if emotions["intensity"] > 70:
156
  visual_style = "역동적인 붓질과 강한 대비"
157
  elif emotions["intensity"] > 40:
 
159
  else:
160
  visual_style = "부드러운 그라데이션과 차분한 톤"
161
 
 
162
  prompt = f"한국 전통 민화 스타일의 추상화, {emotion_colors.get(emotions['primary'], '자연스러운 색상')} 기반. "
163
  prompt += f"{visual_style}로 표현된 {emotions['primary']}의 감정. "
164
  prompt += f"음성의 특징({', '.join(emotions['characteristics'])})을 화면의 동적 요소로 표현. "
 
170
  with gr.Blocks(theme=gr.themes.Soft()) as app:
171
  state = gr.State({
172
  "user_name": "",
173
+ "baseline_features": None, # 개인화된 기준점 저장
174
  "reflections": [],
175
  "voice_analysis": None,
176
  "final_prompt": ""
177
  })
178
+
179
  # 헤더
180
  header = gr.Markdown("# 디지털 굿판")
181
  user_display = gr.Markdown("")
 
183
  with gr.Tabs() as tabs:
184
  # 입장
185
  with gr.Tab("입장"):
186
+ gr.Markdown("### 디지털 굿판에 오신 것을 환영합니다")
187
  name_input = gr.Textbox(label="이름을 알려주세요")
188
  start_btn = gr.Button("여정 시작하기")
189
 
190
+ # 기준 설정
191
+ with gr.Tab("기준 설정"):
192
+ gr.Markdown("### 축원의 문장을 평온한 마음으로 읽어주세요")
193
+ gr.Markdown("'당신의 건강과 행복이 늘 가득하기를'")
194
+ baseline_audio = gr.Audio(
195
+ label="축원 문장 녹음하기",
196
+ sources=["microphone"],
197
+ type="filepath"
198
+ )
199
+ set_baseline_btn = gr.Button("기준점 설정 완료")
200
+ baseline_status = gr.Markdown("")
201
+
202
  # 청신
203
  with gr.Tab("청신"):
204
  with gr.Row():
 
221
  label="기록된 감상들"
222
  )
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  # 기원
225
  with gr.Tab("기원"):
226
  gr.Markdown("## 기원 - 목소리로 전하기")
 
264
  type="pil"
265
  )
266
 
 
267
  def start_journey(name):
268
  """여정 시작"""
269
+ welcome_text = f"""
270
+ # 환영합니다, {name}님의 디지털 굿판
271
+
272
+ ## 굿판의 세계관 🌌
273
+
274
+ 디지털 굿판은 현대 도시 속에서 잊혀진 전통 굿의 정수를 담아낸 **디지털 의례의 공간**입니다.
275
+ 이곳에서는 사람들의 목소리와 감정을 통해 **영적 교감**을 나누고, **자연과 도시의 에너지**를 연결하며,
276
+ 평온함과 치유를 경험하게 됩니다.
277
+
278
+ ## 여정을 시작하며 🚀
279
+
280
+ 먼저, 평온한 마음으로 축원의 문장을 읽어주세요.
281
+ 이는 당신의 감정을 더 정확하게 이해하기 위한 기준점이 될 것입니다.
282
+ """
283
+ return welcome_text, gr.update(selected="기준 설정")
284
+
285
+ def set_baseline(audio_path, state):
286
+ """기준점 설정"""
287
+ if audio_path is None:
288
+ return state, "먼저 축원 문장을 녹음해주세요."
289
+
290
+ baseline_features = calculate_baseline_features(audio_path)
291
+ state = state.copy()
292
+ state["baseline_features"] = baseline_features
293
+ return state, "기준점이 설정되었습니다. 이제 청신 단계로 이동하실 수 있습니다.", gr.update(selected="청신")
294
+
295
+ def save_reflection(text, state):
296
+ """감상 저장"""
297
+ if not text.strip():
298
+ return state, state["reflections"]
299
+
300
+ try:
301
+ current_time = datetime.now().strftime("%H:%M:%S")
302
+ sentiment = text_analyzer(text)[0]
303
+ new_reflection = [current_time, text, f"{sentiment['label']} ({sentiment['score']:.2f})"]
304
+
305
+ if "reflections" not in state:
306
+ state["reflections"] = []
307
+
308
+ state["reflections"].append(new_reflection)
309
+ return state, state["reflections"]
310
+ except Exception as e:
311
+ print(f"Error in save_reflection: {str(e)}")
312
+ return state, []
313
 
314
  def clear_voice_input():
315
  """음성 입력 초기화"""
 
323
  try:
324
  y, sr = librosa.load(audio_path, sr=16000)
325
 
 
326
  acoustic_features = {
327
  "energy": float(np.mean(librosa.feature.rms(y=y))),
328
  "tempo": float(librosa.beat.tempo(y)[0]),
 
330
  "volume": float(np.mean(np.abs(y)))
331
  }
332
 
333
+ # 기준점이 있는 경우 상대적 분석
334
+ baseline = state.get("baseline_features")
335
+ emotions = map_acoustic_to_emotion(acoustic_features, baseline)
336
 
 
337
  transcription = speech_recognizer(y)
338
  text = transcription["text"]
 
 
339
  text_sentiment = text_analyzer(text)[0]
340
 
 
341
  voice_result = (
342
  f"음성 감정: {emotions['primary']} "
343
  f"(강도: {emotions['intensity']:.1f}%, 신뢰도: {emotions['confidence']:.2f})\n"
 
349
  f"- 음성 크기: {emotions['details']['voice_volume']}"
350
  )
351
 
352
+ if baseline:
353
+ voice_result += "\n\n[기준점 대비 분석]\n"
354
+ voice_result += f"기준 상태와 비교한 감정 강도 변화: {emotions['intensity']-50:.1f}%"
355
 
356
+ text_result = f"텍스트 감정 분석 (1-5): {text_sentiment['score']}"
357
  prompt = generate_detailed_prompt(text, emotions, text_sentiment)
358
 
359
  return state, text, voice_result, text_result, prompt
 
362
 
363
  # 이벤트 연결
364
  start_btn.click(
365
+ fn=start_journey,
366
  inputs=[name_input],
367
  outputs=[user_display, tabs]
368
  )
369
 
370
+ set_baseline_btn.click(
371
+ fn=set_baseline,
372
+ inputs=[baseline_audio, state],
373
+ outputs=[state, baseline_status, tabs]
374
+ )
375
+
376
  save_btn.click(
377
  fn=save_reflection,
378
  inputs=[reflection_input, state],
 
397
  outputs=[result_image]
398
  )
399
 
400
+ return app
401
+
402
  if __name__ == "__main__":
403
  demo = create_interface()
404
  demo.launch(debug=True)