Jedi09 commited on
Commit
2c5a44e
·
verified ·
1 Parent(s): 11fc8ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +344 -86
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  Danışman-Danışan Transkripsiyon Sistemi
3
  Speaker diarization + transcription pipeline.
4
- Zaman damgalı, konuşmacı ayrımlı çıktı.
5
  """
6
 
7
  import gradio as gr
@@ -10,6 +10,8 @@ import tempfile
10
  import time
11
  import os
12
  import torch
 
 
13
 
14
  from diarization import (
15
  get_diarization_pipeline,
@@ -19,7 +21,7 @@ from diarization import (
19
  )
20
 
21
  # ==================== CONFIGURATION ====================
22
- MODEL_SIZE = "medium" # Changed to small for HF Spaces memory limits
23
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
24
  COMPUTE_TYPE = "float16" if DEVICE == "cuda" else "int8"
25
  # =======================================================
@@ -38,6 +40,46 @@ print("✅ Whisper model yüklendi!")
38
  print("🔄 Diarization pipeline yükleniyor...")
39
  diarization_pipeline = get_diarization_pipeline()
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  def get_audio_duration(audio_path: str) -> float:
43
  """Get audio duration in seconds using ffprobe."""
@@ -54,29 +96,177 @@ def get_audio_duration(audio_path: str) -> float:
54
  return 0.0
55
 
56
 
57
- def transcribe_segment(audio_path: str, start: float, end: float) -> str:
58
- """
59
- Transcribe a specific segment of audio.
60
- """
61
- try:
62
- # Faster-whisper doesn't support segment extraction directly,
63
- # so we transcribe the whole file and filter by timestamp
64
- segments, _ = whisper_model.transcribe(
65
- audio_path,
66
- language="tr",
67
- beam_size=5
68
- )
69
-
70
- # Collect text from segments that fall within our time range
71
- text_parts = []
72
- for segment in segments:
73
- # Check if segment overlaps with our range
74
- if segment.end > start and segment.start < end:
75
- text_parts.append(segment.text)
76
-
77
- return " ".join(text_parts).strip()
78
- except Exception as e:
79
- return f"[Transkripsiyon hatası: {e}]"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
 
82
  def transcribe_with_diarization(audio_path: str) -> tuple:
@@ -122,7 +312,7 @@ def transcribe_with_diarization(audio_path: str) -> tuple:
122
  # Step 2: Transcribe each segment
123
  print("🎙️ Transkripsiyon başlıyor...")
124
  segments, info = whisper_model.transcribe(audio_path, language="tr", beam_size=5)
125
- whisper_segments = list(segments) # Convert generator to list
126
 
127
  # Track which whisper segments have been used
128
  used_whisper_indices = set()
@@ -135,18 +325,14 @@ def transcribe_with_diarization(audio_path: str) -> tuple:
135
  for start, end, speaker in diarization_segments:
136
  speaker_label = format_speaker_label(speaker)
137
 
138
- # Track speaker time
139
  if speaker_label not in speaker_times:
140
  speaker_times[speaker_label] = 0
141
  speaker_times[speaker_label] += (end - start)
142
 
143
- # Find whisper segments that overlap with this diarization segment
144
- # Only use segments that haven't been used before
145
  segment_text = []
146
  for idx, ws in enumerate(whisper_segments):
147
  if idx in used_whisper_indices:
148
  continue
149
- # Check if whisper segment's midpoint falls within diarization segment
150
  ws_midpoint = (ws.start + ws.end) / 2
151
  if start <= ws_midpoint <= end:
152
  segment_text.append(ws.text)
@@ -158,7 +344,6 @@ def transcribe_with_diarization(audio_path: str) -> tuple:
158
  timestamp_end = format_timestamp(end)
159
  transcript_parts.append(f"[{timestamp_start} → {timestamp_end}] {speaker_label}:\n{text}\n")
160
 
161
- # Build final output
162
  header = """═══════════════════════════════════════════════════
163
  📋 GÖRÜŞME TRANSKRİPTİ
164
  ═══════════════════════════════════════════════════
@@ -167,7 +352,6 @@ def transcribe_with_diarization(audio_path: str) -> tuple:
167
 
168
  body = "\n".join(transcript_parts)
169
 
170
- # Statistics
171
  elapsed = time.time() - start_time
172
  total_time = info.duration
173
 
@@ -188,7 +372,6 @@ def transcribe_with_diarization(audio_path: str) -> tuple:
188
 
189
  full_result = header + body + stats
190
 
191
- # Create downloadable file
192
  txt_file = tempfile.NamedTemporaryFile(
193
  mode='w',
194
  suffix='.txt',
@@ -212,108 +395,177 @@ def process_audio(audio_path):
212
  return f"❌ Beklenmeyen hata: {str(e)}", None
213
 
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  # ==================== GRADIO UI ====================
216
- with gr.Blocks(title="Görüşme Transkripsiyon") as demo:
217
 
218
  gr.HTML("""
219
  <style>
220
  footer { display: none !important; }
221
- .gradio-container { max-width: 900px !important; margin: auto !important; }
222
  </style>
223
  <div style="text-align: center; padding: 40px 20px 30px;
224
  background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%);
225
  border-radius: 20px; margin-bottom: 24px; color: white;">
226
  <h1 style="font-size: 2.2rem; font-weight: 700; margin: 0 0 8px 0;">
227
- 🎙️ Görüşme Transkripsiyon Sistemi
228
  </h1>
229
  <p style="font-size: 1rem; opacity: 0.95; margin: 0;">
230
- Danışman-Danışan görüşmelerini zaman damgalı ve konuşmacı ayrımlı olarak yazıya dökün
231
  </p>
232
  </div>
233
  """)
234
 
235
- with gr.Row():
236
- with gr.Column():
237
- gr.HTML('<div style="font-weight: 600; margin-bottom: 12px;">📤 Ses Dosyası</div>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
- audio_input = gr.Audio(
240
- label="Görüşme Kaydı",
241
- type="filepath",
242
- sources=["upload", "microphone"]
243
- )
244
-
245
- submit_btn = gr.Button(
246
- "🚀 Transkripsiyon Başlat",
247
- variant="primary",
248
- size="lg"
249
- )
250
-
251
- # Info box
 
 
 
 
252
  gr.HTML("""
253
- <div style="background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
254
- border: 1px solid #7dd3fc; border-radius: 12px;
255
- padding: 16px 20px; margin-top: 16px;">
256
- <p style="margin: 0; color: #0369a1; font-size: 14px;">
257
- ℹ️ <strong>Nasıl Çalışır:</strong><br>
258
- 1. Ses dosyasını yükleyin (MP3, WAV, M4A)<br>
259
- 2. AI otomatik olarak konuşmacıları ayırır<br>
260
- 3. Zaman damgalı transkript oluşturulur
261
  </p>
262
  </div>
263
  """)
264
-
265
- with gr.Row():
266
- with gr.Column():
267
- gr.HTML('<div style="font-weight: 600; margin-bottom: 12px;">📝 Transkript Sonucu</div>')
268
-
269
- output_text = gr.Textbox(
270
- label="",
271
- placeholder="Transkript burada görünecek...",
272
- lines=20,
273
- interactive=False
274
- )
275
 
276
- download_file = gr.File(
277
- label="📥 Transkripti İndir (.txt)"
278
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  # Features
281
  gr.HTML("""
282
- <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 24px;">
283
  <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
284
  <div style="font-size: 24px; margin-bottom: 6px;">🎭</div>
285
- <div style="font-size: 12px; color: #6b7280; font-weight: 500;">Konuşmacı Ayrımı</div>
286
  </div>
287
  <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
288
  <div style="font-size: 24px; margin-bottom: 6px;">⏱️</div>
289
- <div style="font-size: 12px; color: #6b7280; font-weight: 500;">Zaman Damgası</div>
290
  </div>
291
  <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
292
  <div style="font-size: 24px; margin-bottom: 6px;">🔒</div>
293
- <div style="font-size: 12px; color: #6b7280; font-weight: 500;">%100 Local</div>
294
  </div>
295
- <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
296
- <div style="font-size: 24px; margin-bottom: 6px;">🇹🇷</div>
297
- <div style="font-size: 12px; color: #6b7280; font-weight: 500;">Türkçe Optimizeli</div>
 
 
 
 
298
  </div>
299
  </div>
300
  """)
301
 
302
- # Privacy notice
303
  gr.HTML("""
304
  <div style="background: #ecfdf5; border: 1px solid #6ee7b7; border-radius: 8px;
305
  padding: 12px 16px; margin-top: 16px;">
306
  <p style="margin: 0; color: #047857; font-size: 13px;">
307
  🔒 <strong>Gizlilik:</strong> Tüm işlemler yerel olarak yapılır.
308
- Ses dosyalarınız hiçbir sunucuya gönderilmez.
309
  </p>
310
  </div>
311
  """)
312
 
313
- # Footer
314
  gr.HTML("""
315
  <div style="text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px;">
316
- <p>Powered by Faster-Whisper & Pyannote-Audio • GPU & CPU Destekli</p>
317
  </div>
318
  """)
319
 
@@ -323,6 +575,12 @@ with gr.Blocks(title="Görüşme Transkripsiyon") as demo:
323
  inputs=[audio_input],
324
  outputs=[output_text, download_file]
325
  )
 
 
 
 
 
 
326
 
327
  # Launch
328
  if __name__ == "__main__":
 
1
  """
2
  Danışman-Danışan Transkripsiyon Sistemi
3
  Speaker diarization + transcription pipeline.
4
+ Zaman damgalı, konuşmacı ayrımlı çıktı + Psikolog Analiz Araçları.
5
  """
6
 
7
  import gradio as gr
 
10
  import time
11
  import os
12
  import torch
13
+ import re
14
+ from collections import Counter
15
 
16
  from diarization import (
17
  get_diarization_pipeline,
 
21
  )
22
 
23
  # ==================== CONFIGURATION ====================
24
+ MODEL_SIZE = "medium"
25
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
26
  COMPUTE_TYPE = "float16" if DEVICE == "cuda" else "int8"
27
  # =======================================================
 
40
  print("🔄 Diarization pipeline yükleniyor...")
41
  diarization_pipeline = get_diarization_pipeline()
42
 
43
+ # ==================== EMOTION KEYWORDS (TURKISH) ====================
44
+ EMOTION_KEYWORDS = {
45
+ "Üzüntü": [
46
+ "üzgün", "üzülüyorum", "ağlıyorum", "ağladım", "mutsuz", "kötü", "berbat",
47
+ "acı", "acıyor", "kederli", "hüzünlü", "depresif", "bunalım", "çaresiz",
48
+ "umutsuz", "yalnız", "yalnızlık", "terk", "kayıp", "ölüm", "öldü", "kaybettim"
49
+ ],
50
+ "Kaygı/Anksiyete": [
51
+ "endişe", "endişeli", "kaygı", "kaygılı", "korku", "korkuyorum", "panik",
52
+ "stres", "stresli", "gergin", "tedirgin", "huzursuz", "rahatsız", "bunaltı",
53
+ "sıkıntı", "sıkıntılı", "belirsiz", "güvensiz", "tehlike", "tehdit"
54
+ ],
55
+ "Öfke": [
56
+ "kızgın", "sinirli", "öfkeli", "öfke", "kızdım", "sinirlendim", "çıldırdım",
57
+ "nefret", "kin", "intikam", "haksızlık", "adaletsiz", "ihanet", "hayal kırıklığı",
58
+ "bıktım", "usandım", "yeter", "dayanamıyorum"
59
+ ],
60
+ "Mutluluk": [
61
+ "mutlu", "sevinçli", "neşeli", "iyi", "güzel", "harika", "mükemmel",
62
+ "seviyorum", "aşk", "heyecan", "heyecanlı", "umut", "umutlu", "başarı",
63
+ "gurur", "gururlu", "şükür", "minnet", "rahat", "huzur"
64
+ ],
65
+ "Korku": [
66
+ "korku", "korkuyorum", "korktum", "dehşet", "panik", "ürperti",
67
+ "kaçmak", "saklanmak", "tehlike", "tehdit", "ölüm", "felaket"
68
+ ]
69
+ }
70
+
71
+ # Turkish stop words to exclude from word frequency
72
+ TURKISH_STOP_WORDS = {
73
+ "bir", "bu", "şu", "o", "ve", "ile", "için", "de", "da", "ki", "ne", "var", "yok",
74
+ "ben", "sen", "biz", "siz", "onlar", "ama", "fakat", "çünkü", "eğer", "gibi",
75
+ "daha", "en", "çok", "az", "kadar", "sonra", "önce", "şimdi", "zaman", "her",
76
+ "hiç", "bile", "sadece", "hem", "ya", "veya", "ise", "mi", "mı", "mu", "mü",
77
+ "nasıl", "neden", "nerede", "kim", "hangi", "olan", "olarak", "oldu", "olur",
78
+ "oluyor", "olmuş", "olacak", "yapmak", "yapıyor", "yaptı", "etti", "ediyor",
79
+ "gidiyor", "geliyor", "diyor", "dedi", "söyledi", "bence", "aslında", "yani",
80
+ "işte", "hani", "evet", "hayır", "tamam", "peki"
81
+ }
82
+
83
 
84
  def get_audio_duration(audio_path: str) -> float:
85
  """Get audio duration in seconds using ffprobe."""
 
96
  return 0.0
97
 
98
 
99
+ def analyze_emotions(text: str) -> dict:
100
+ """Analyze emotions in text based on keyword matching."""
101
+ text_lower = text.lower()
102
+ words = re.findall(r'\b\w+\b', text_lower)
103
+
104
+ emotion_counts = {}
105
+ emotion_matched_words = {}
106
+
107
+ for emotion, keywords in EMOTION_KEYWORDS.items():
108
+ count = 0
109
+ matched = []
110
+ for keyword in keywords:
111
+ # Count occurrences
112
+ occurrences = text_lower.count(keyword)
113
+ if occurrences > 0:
114
+ count += occurrences
115
+ matched.append(keyword)
116
+ emotion_counts[emotion] = count
117
+ emotion_matched_words[emotion] = matched
118
+
119
+ total = sum(emotion_counts.values())
120
+ emotion_percentages = {}
121
+
122
+ if total > 0:
123
+ for emotion, count in emotion_counts.items():
124
+ emotion_percentages[emotion] = (count / total) * 100
125
+ else:
126
+ for emotion in emotion_counts:
127
+ emotion_percentages[emotion] = 0
128
+
129
+ return {
130
+ "counts": emotion_counts,
131
+ "percentages": emotion_percentages,
132
+ "matched_words": emotion_matched_words,
133
+ "total_emotional_words": total
134
+ }
135
+
136
+
137
+ def get_word_frequency(text: str, top_n: int = 15) -> list:
138
+ """Get most frequent meaningful words."""
139
+ # Clean and tokenize
140
+ words = re.findall(r'\b[a-zA-ZçğıöşüÇĞİÖŞÜ]{3,}\b', text.lower())
141
+
142
+ # Filter stop words
143
+ meaningful_words = [w for w in words if w not in TURKISH_STOP_WORDS]
144
+
145
+ # Count frequencies
146
+ word_counts = Counter(meaningful_words)
147
+
148
+ return word_counts.most_common(top_n)
149
+
150
+
151
+ def generate_psychology_report(transcript: str, client_speaker: str) -> str:
152
+ """Generate a preliminary psychology report for the client."""
153
+
154
+ # Extract client's text only
155
+ lines = transcript.split('\n')
156
+ client_text = []
157
+ current_speaker = None
158
+
159
+ for line in lines:
160
+ # Check if this is a speaker line like "[00:00 → 00:05] Kişi 1:"
161
+ speaker_match = re.search(r'\] (Kişi \d+):', line)
162
+ if speaker_match:
163
+ current_speaker = speaker_match.group(1)
164
+ elif current_speaker == client_speaker and line.strip():
165
+ # This is the client's text
166
+ if not line.startswith('[') and not line.startswith('═') and not line.startswith('─') and not line.startswith('📊'):
167
+ client_text.append(line.strip())
168
+
169
+ client_full_text = ' '.join(client_text)
170
+
171
+ if not client_full_text:
172
+ return "⚠️ Seçilen kişiye ait metin bulunamadı."
173
+
174
+ # Analyze emotions
175
+ emotion_analysis = analyze_emotions(client_full_text)
176
+
177
+ # Get word frequency
178
+ word_freq = get_word_frequency(client_full_text)
179
+
180
+ # Calculate speaking stats
181
+ word_count = len(client_full_text.split())
182
+ sentence_count = len(re.findall(r'[.!?]+', client_full_text)) or 1
183
+ avg_sentence_length = word_count / sentence_count
184
+
185
+ # Find dominant emotion
186
+ dominant_emotion = max(emotion_analysis['percentages'], key=emotion_analysis['percentages'].get)
187
+ dominant_percentage = emotion_analysis['percentages'][dominant_emotion]
188
+
189
+ # Build report
190
+ report = f"""
191
+ ═══════════════════════════════════════════════════
192
+ 🧠 PSİKOLOJİK ÖN RAPOR - {client_speaker}
193
+ ═══════════════════════════════════════════════════
194
+
195
+ 📊 GENEL İSTATİSTİKLER
196
+ ───────────────────────────────────
197
+ • Toplam kelime sayısı: {word_count}
198
+ • Cümle sayısı: {sentence_count}
199
+ • Ortalama cümle uzunluğu: {avg_sentence_length:.1f} kelime
200
+ • Duygusal ifade sayısı: {emotion_analysis['total_emotional_words']}
201
+
202
+ 🎭 DUYGU ANALİZİ
203
+ ───────────────────────────────────
204
+ """
205
+
206
+ # Sort emotions by percentage
207
+ sorted_emotions = sorted(emotion_analysis['percentages'].items(), key=lambda x: x[1], reverse=True)
208
+
209
+ for emotion, percentage in sorted_emotions:
210
+ if percentage > 0:
211
+ bar_length = int(percentage / 5) # Scale to max 20 chars
212
+ bar = "█" * bar_length + "░" * (20 - bar_length)
213
+ matched = ", ".join(emotion_analysis['matched_words'][emotion][:5])
214
+ report += f"• {emotion}: {bar} {percentage:.1f}%\n"
215
+ if matched:
216
+ report += f" └─ Anahtar kelimeler: {matched}\n"
217
+
218
+ if emotion_analysis['total_emotional_words'] > 0:
219
+ report += f"\n🔍 Baskın Duygu: {dominant_emotion} ({dominant_percentage:.1f}%)\n"
220
+ else:
221
+ report += "\n🔍 Belirgin duygusal ifade tespit edilemedi.\n"
222
+
223
+ report += """
224
+ 📝 EN SIK KULLANILAN KELİMELER
225
+ ───────────────────────────────────
226
+ """
227
+
228
+ for i, (word, count) in enumerate(word_freq[:10], 1):
229
+ report += f"{i:2}. {word}: {count} kez\n"
230
+
231
+ # Add interpretation notes
232
+ report += """
233
+ 💡 YORUMLAMA NOTLARI
234
+ ───────────────────────────────────
235
+ """
236
+
237
+ if dominant_percentage > 40 and emotion_analysis['total_emotional_words'] > 3:
238
+ if dominant_emotion == "Üzüntü":
239
+ report += "• Danışan belirgin düzeyde üzüntü/depresif belirtiler göstermektedir.\n"
240
+ report += "• Kayıp, yalnızlık veya değersizlik temaları değerlendirilmelidir.\n"
241
+ elif dominant_emotion == "Kaygı/Anksiyete":
242
+ report += "• Danışan kaygı ve endişe belirtileri sergilemektedir.\n"
243
+ report += "• Belirsizlik toleransı ve güvenlik ihtiyacı değerlendirilmelidir.\n"
244
+ elif dominant_emotion == "Öfke":
245
+ report += "• Danışan öfke ve kızgınlık ifadeleri kullanmaktadır.\n"
246
+ report += "• Hayal kırıklığı kaynakları ve sınır ihlalleri değerlendirilmelidir.\n"
247
+ elif dominant_emotion == "Korku":
248
+ report += "• Danışan korku ve tehdit algısı belirtileri göstermektedir.\n"
249
+ report += "• Güvenlik duygusu ve travma geçmişi değerlendirilmelidir.\n"
250
+ elif dominant_emotion == "Mutluluk":
251
+ report += "• Danışan olumlu duygular ifade etmektedir.\n"
252
+ report += "• Kaynak ve sürdürücü faktörler değerlendirilmelidir.\n"
253
+ else:
254
+ report += "• Belirgin bir duygusal örüntü tespit edilemedi.\n"
255
+ report += "• Daha uzun görüşme örnekleri daha güvenilir analiz sağlayabilir.\n"
256
+
257
+ if avg_sentence_length > 15:
258
+ report += "• Uzun cümleler: Detaylı anlatım eğilimi veya düşünce karmaşası olabilir.\n"
259
+ elif avg_sentence_length < 5:
260
+ report += "• Kısa cümleler: Ketum davranış veya iletişim güçlüğü olabilir.\n"
261
+
262
+ report += """
263
+ ───────────────────────────────────
264
+ ⚠️ NOT: Bu analiz otomatik olarak oluşturulmuştur ve
265
+ profesyonel klinik değerlendirmenin yerini alamaz.
266
+ ═══════════════════════════════════════════════════
267
+ """
268
+
269
+ return report
270
 
271
 
272
  def transcribe_with_diarization(audio_path: str) -> tuple:
 
312
  # Step 2: Transcribe each segment
313
  print("🎙️ Transkripsiyon başlıyor...")
314
  segments, info = whisper_model.transcribe(audio_path, language="tr", beam_size=5)
315
+ whisper_segments = list(segments)
316
 
317
  # Track which whisper segments have been used
318
  used_whisper_indices = set()
 
325
  for start, end, speaker in diarization_segments:
326
  speaker_label = format_speaker_label(speaker)
327
 
 
328
  if speaker_label not in speaker_times:
329
  speaker_times[speaker_label] = 0
330
  speaker_times[speaker_label] += (end - start)
331
 
 
 
332
  segment_text = []
333
  for idx, ws in enumerate(whisper_segments):
334
  if idx in used_whisper_indices:
335
  continue
 
336
  ws_midpoint = (ws.start + ws.end) / 2
337
  if start <= ws_midpoint <= end:
338
  segment_text.append(ws.text)
 
344
  timestamp_end = format_timestamp(end)
345
  transcript_parts.append(f"[{timestamp_start} → {timestamp_end}] {speaker_label}:\n{text}\n")
346
 
 
347
  header = """═══════════════════════════════════════════════════
348
  📋 GÖRÜŞME TRANSKRİPTİ
349
  ═══════════════════════════════════════════════════
 
352
 
353
  body = "\n".join(transcript_parts)
354
 
 
355
  elapsed = time.time() - start_time
356
  total_time = info.duration
357
 
 
372
 
373
  full_result = header + body + stats
374
 
 
375
  txt_file = tempfile.NamedTemporaryFile(
376
  mode='w',
377
  suffix='.txt',
 
395
  return f"❌ Beklenmeyen hata: {str(e)}", None
396
 
397
 
398
+ def analyze_client(transcript: str, client_selection: str):
399
+ """Analyze the selected client's speech."""
400
+ if not transcript or transcript.startswith("⚠️") or transcript.startswith("❌"):
401
+ return "⚠️ Önce bir transkript oluşturun."
402
+
403
+ if not client_selection:
404
+ return "⚠️ Lütfen danışanı seçin."
405
+
406
+ report = generate_psychology_report(transcript, client_selection)
407
+
408
+ # Create downloadable report
409
+ report_file = tempfile.NamedTemporaryFile(
410
+ mode='w',
411
+ suffix='_rapor.txt',
412
+ delete=False,
413
+ encoding='utf-8'
414
+ )
415
+ report_file.write(report)
416
+ report_file.close()
417
+
418
+ return report, report_file.name
419
+
420
+
421
  # ==================== GRADIO UI ====================
422
+ with gr.Blocks(title="Görüşme Transkripsiyon & Analiz") as demo:
423
 
424
  gr.HTML("""
425
  <style>
426
  footer { display: none !important; }
427
+ .gradio-container { max-width: 1000px !important; margin: auto !important; }
428
  </style>
429
  <div style="text-align: center; padding: 40px 20px 30px;
430
  background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%);
431
  border-radius: 20px; margin-bottom: 24px; color: white;">
432
  <h1 style="font-size: 2.2rem; font-weight: 700; margin: 0 0 8px 0;">
433
+ 🎙️ Görüşme Transkripsiyon & Analiz
434
  </h1>
435
  <p style="font-size: 1rem; opacity: 0.95; margin: 0;">
436
+ Danışman-Danışan görüşmelerini yazıya dökün ve psikolojik analiz yapın
437
  </p>
438
  </div>
439
  """)
440
 
441
+ with gr.Tabs():
442
+ # Tab 1: Transkripsiyon
443
+ with gr.TabItem("📝 Transkripsiyon"):
444
+ with gr.Row():
445
+ with gr.Column():
446
+ gr.HTML('<div style="font-weight: 600; margin-bottom: 12px;">📤 Ses Dosyası</div>')
447
+
448
+ audio_input = gr.Audio(
449
+ label="Görüşme Kaydı",
450
+ type="filepath",
451
+ sources=["upload", "microphone"]
452
+ )
453
+
454
+ submit_btn = gr.Button(
455
+ "🚀 Transkripsiyon Başlat",
456
+ variant="primary",
457
+ size="lg"
458
+ )
459
+
460
+ gr.HTML("""
461
+ <div style="background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
462
+ border: 1px solid #7dd3fc; border-radius: 12px;
463
+ padding: 16px 20px; margin-top: 16px;">
464
+ <p style="margin: 0; color: #0369a1; font-size: 14px;">
465
+ ℹ️ <strong>Nasıl Çalışır:</strong><br>
466
+ 1. Ses dosyasını yükleyin<br>
467
+ 2. AI konuşmacıları ayırır<br>
468
+ 3. Transkript oluşturulur<br>
469
+ 4. Analiz sekmesinde danışanı seçip analiz yapın
470
+ </p>
471
+ </div>
472
+ """)
473
 
474
+ with gr.Row():
475
+ with gr.Column():
476
+ gr.HTML('<div style="font-weight: 600; margin-bottom: 12px;">📝 Transkript Sonucu</div>')
477
+
478
+ output_text = gr.Textbox(
479
+ label="",
480
+ placeholder="Transkript burada görünecek...",
481
+ lines=20,
482
+ interactive=False
483
+ )
484
+
485
+ download_file = gr.File(
486
+ label="📥 Transkripti İndir (.txt)"
487
+ )
488
+
489
+ # Tab 2: Psikolojik Analiz
490
+ with gr.TabItem("🧠 Psikolojik Analiz"):
491
  gr.HTML("""
492
+ <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
493
+ border: 1px solid #f59e0b; border-radius: 12px;
494
+ padding: 16px 20px; margin-bottom: 16px;">
495
+ <p style="margin: 0; color: #92400e; font-size: 14px;">
496
+ 🧠 <strong>Psikolojik Ön Rapor:</strong> Bu analiz danışanın konuşmasındaki
497
+ duygusal ifadeleri, en sık kullandığı kelimeleri ve genel konuşma kalıplarını
498
+ değerlendirir. Profesyonel klinik değerlendirmenin yerini almaz.
 
499
  </p>
500
  </div>
501
  """)
 
 
 
 
 
 
 
 
 
 
 
502
 
503
+ with gr.Row():
504
+ with gr.Column(scale=1):
505
+ client_selector = gr.Radio(
506
+ label="🎯 Danışanı Seçin",
507
+ choices=["Kişi 1", "Kişi 2"],
508
+ value="Kişi 2",
509
+ info="Hangi kişi danışan?"
510
+ )
511
+
512
+ analyze_btn = gr.Button(
513
+ "🔍 Analiz Et",
514
+ variant="primary",
515
+ size="lg"
516
+ )
517
+
518
+ with gr.Column(scale=2):
519
+ analysis_output = gr.Textbox(
520
+ label="📊 Analiz Raporu",
521
+ placeholder="Analiz raporu burada görünecek...",
522
+ lines=25,
523
+ interactive=False
524
+ )
525
+
526
+ report_download = gr.File(
527
+ label="📥 Raporu İndir (.txt)"
528
+ )
529
 
530
  # Features
531
  gr.HTML("""
532
+ <div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-top: 24px;">
533
  <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
534
  <div style="font-size: 24px; margin-bottom: 6px;">🎭</div>
535
+ <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Konuşmacı Ayrımı</div>
536
  </div>
537
  <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
538
  <div style="font-size: 24px; margin-bottom: 6px;">⏱️</div>
539
+ <div style="font-size: 11px; color: #6b7280; font-weight: 500;">Zaman Damgası</div>
540
  </div>
541
  <div style="text-align: center; padding: 16px; background: #f9fafb; border-radius: 12px;">
542
  <div style="font-size: 24px; margin-bottom: 6px;">🔒</div>
543
+ <div style="font-size: 11px; color: #6b7280; font-weight: 500;">%100 Local</div>
544
  </div>
545
+ <div style="text-align: center; padding: 16px; background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%); border-radius: 12px;">
546
+ <div style="font-size: 24px; margin-bottom: 6px;">🧠</div>
547
+ <div style="font-size: 11px; color: #5b21b6; font-weight: 500;">Duygu Analizi</div>
548
+ </div>
549
+ <div style="text-align: center; padding: 16px; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 12px;">
550
+ <div style="font-size: 24px; margin-bottom: 6px;">📊</div>
551
+ <div style="font-size: 11px; color: #92400e; font-weight: 500;">Ön Rapor</div>
552
  </div>
553
  </div>
554
  """)
555
 
 
556
  gr.HTML("""
557
  <div style="background: #ecfdf5; border: 1px solid #6ee7b7; border-radius: 8px;
558
  padding: 12px 16px; margin-top: 16px;">
559
  <p style="margin: 0; color: #047857; font-size: 13px;">
560
  🔒 <strong>Gizlilik:</strong> Tüm işlemler yerel olarak yapılır.
561
+ Ses dosyalarınız ve analizler hiçbir sunucuya gönderilmez.
562
  </p>
563
  </div>
564
  """)
565
 
 
566
  gr.HTML("""
567
  <div style="text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px;">
568
+ <p>Powered by Faster-Whisper & Pyannote-Audio • Psikolog Asistanı</p>
569
  </div>
570
  """)
571
 
 
575
  inputs=[audio_input],
576
  outputs=[output_text, download_file]
577
  )
578
+
579
+ analyze_btn.click(
580
+ fn=analyze_client,
581
+ inputs=[output_text, client_selector],
582
+ outputs=[analysis_output, report_download]
583
+ )
584
 
585
  # Launch
586
  if __name__ == "__main__":