Vadym Myroshnyk commited on
Commit
83b23d4
·
1 Parent(s): e19b2fa
Files changed (4) hide show
  1. app.py +39 -79
  2. audio_files.py +12 -0
  3. check.py +38 -0
  4. highlighter.py +31 -0
app.py CHANGED
@@ -1,84 +1,27 @@
1
  import os
2
 
3
  import gradio as gr
4
- from rapidfuzz import fuzz, process
5
-
6
-
7
- def highlight_fuzzy_diff(user_text, original_text):
8
- user_words = user_text.strip().split()
9
- original_words = original_text.strip().split()
10
- highlighted = []
11
- used_indices = set()
12
-
13
- for word in user_words:
14
- match, score, idx = process.extractOne(word, original_words, scorer=fuzz.ratio)
15
- if score > 85 and idx not in used_indices:
16
- highlighted.append(
17
- f"<span style='background-color:#f0f0f0; color:#333; padding:4px 10px; "
18
- f"margin:4px; border-radius:999px; font-weight:500; display:inline-block;'>{word}</span>"
19
- )
20
- used_indices.add(idx)
21
- else:
22
- highlighted.append(
23
- f"<span style='background-color:#e6ffe6; color:#006600; padding:4px 10px; "
24
- f"margin:4px; border-radius:999px; font-weight:500; display:inline-block;'>{word}</span>"
25
- )
26
-
27
- for i, word in enumerate(original_words):
28
- if i not in used_indices:
29
- highlighted.append(
30
- f"<span style='background-color:#ffe6e6; color:#990000; padding:4px 10px; "
31
- f"margin:4px; border-radius:999px; font-weight:500; display:inline-block;'>{word}</span>"
32
- )
33
-
34
- return " ".join(highlighted)
35
-
36
-
37
- def check_transcription(file_name, user_text):
38
- audio_path = os.path.join("audio", file_name)
39
- transcript_path = audio_path.replace(".mp3", ".txt").replace("audio", "transcripts")
40
-
41
- with open(transcript_path, 'r') as f:
42
- original = f.read().strip()
43
-
44
- diff_html = highlight_fuzzy_diff(user_text, original)
45
- score = round(fuzz.ratio(user_text, original))
46
-
47
- if score > 90:
48
- emoji = "✅"
49
- comment = "Great job! Keep it up."
50
- elif score > 70:
51
- emoji = "⚠️"
52
- comment = "Not bad, but pay attention to some words."
53
- else:
54
- emoji = "❌"
55
- comment = "Try again. There are too many mistakes."
56
-
57
- simple_feedback = f"""
58
- <div style='font-size: 16px; margin-bottom: 6px;'>
59
- Similarity: <b>{score}%</b> {emoji}
60
- </div>
61
- <div style='margin-top: 4px;'>{comment}</div>
62
- """
63
-
64
- return audio_path, original, (
65
- "<div style='margin-bottom: 10px;'><b>⬜ Extra / incorrect words</b>, "
66
- "<b>🟥 Missing words</b>, <b>🟩 Correct words</b></div>" + diff_html
67
- ), simple_feedback
68
-
69
-
70
- # === LOAD AUDIO FILES ===
71
- audio_dir = "audio"
72
- audio_list = [f for f in os.listdir(audio_dir) if f.endswith(".mp3")]
73
- default_file = audio_list[0] if audio_list else None
74
-
75
- # === GRADIO INTERFACE ===
76
  with gr.Blocks(theme="soft") as demo:
77
  gr.Markdown("## 🎧 Modals & Conditionals Bootcamp")
78
 
79
  with gr.Row():
80
- selected_file = gr.Dropdown(audio_list, label="Select an audio file", value=default_file)
81
- audio_player = gr.Audio(label="Audio preview", type="filepath", value=os.path.join("audio", default_file))
 
 
 
 
 
 
82
 
83
  user_input = gr.Textbox(label="Your transcription", lines=6)
84
  btn = gr.Button("Check")
@@ -88,13 +31,30 @@ with gr.Blocks(theme="soft") as demo:
88
  feedback_output = gr.HTML(label="Feedback and score")
89
 
90
 
91
- def update_audio(file_name):
92
- return gr.update(value=os.path.join("audio", file_name))
 
 
 
 
 
 
 
 
 
 
 
93
 
94
 
95
- selected_file.change(fn=update_audio, inputs=selected_file, outputs=audio_player)
 
 
 
96
 
97
- btn.click(fn=check_transcription, inputs=[selected_file, user_input],
98
- outputs=[audio_player, original_output, diff_output, feedback_output])
 
 
 
99
 
100
  demo.launch()
 
1
  import os
2
 
3
  import gradio as gr
4
+
5
+ from audio_files import get_audio_structure
6
+ from check import check_transcription
7
+
8
+ audio_structure = get_audio_structure()
9
+ week_options = list(audio_structure.keys())
10
+ default_week = week_options[0]
11
+ default_file = audio_structure[default_week][0] if audio_structure[default_week] else ""
12
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  with gr.Blocks(theme="soft") as demo:
14
  gr.Markdown("## 🎧 Modals & Conditionals Bootcamp")
15
 
16
  with gr.Row():
17
+ week_dropdown = gr.Dropdown(label="Select a week", choices=week_options, value=default_week, scale=1)
18
+ lesson_dropdown = gr.Dropdown(
19
+ label="Select a lesson",
20
+ choices=[os.path.basename(f) for f in audio_structure[default_week]],
21
+ value=os.path.basename(default_file),
22
+ scale=1
23
+ )
24
+ audio_player = gr.Audio(label="Audio preview", type="filepath", value=default_file, scale=3)
25
 
26
  user_input = gr.Textbox(label="Your transcription", lines=6)
27
  btn = gr.Button("Check")
 
31
  feedback_output = gr.HTML(label="Feedback and score")
32
 
33
 
34
+ def update_lessons(week):
35
+ lessons = [os.path.basename(f) for f in audio_structure[week]]
36
+ return gr.update(choices=lessons, value=lessons[0])
37
+
38
+
39
+ def get_audio_path(week, lesson):
40
+ folder = week.lower().replace(" ", "_") # e.g. "Week 1" -> "week_1"
41
+ return os.path.join("audio", folder, lesson)
42
+
43
+
44
+ def check_transcription_wrapper(week, lesson, user_text):
45
+ full_path = get_audio_path(week, lesson)
46
+ return check_transcription(full_path, user_text)
47
 
48
 
49
+ # === EVENTS ===
50
+ week_dropdown.change(fn=update_lessons, inputs=week_dropdown, outputs=lesson_dropdown)
51
+ lesson_dropdown.change(fn=lambda w, l: get_audio_path(w, l), inputs=[week_dropdown, lesson_dropdown],
52
+ outputs=audio_player)
53
 
54
+ btn.click(
55
+ fn=check_transcription_wrapper,
56
+ inputs=[week_dropdown, lesson_dropdown, user_input],
57
+ outputs=[audio_player, original_output, diff_output, feedback_output]
58
+ )
59
 
60
  demo.launch()
audio_files.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+
4
+ def get_audio_structure(audio_dir="audio"):
5
+ structure = {}
6
+ for week_folder in sorted(os.listdir(audio_dir)):
7
+ folder_path = os.path.join(audio_dir, week_folder)
8
+ if os.path.isdir(folder_path) and week_folder.lower().startswith("week_"):
9
+ label = week_folder.replace("_", " ").title()
10
+ files = [f for f in sorted(os.listdir(folder_path)) if f.endswith(".mp3")]
11
+ structure[label] = [os.path.join(folder_path, f) for f in files]
12
+ return structure
check.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from rapidfuzz import fuzz
4
+
5
+ from highlighter import highlight_fuzzy_diff
6
+
7
+
8
+ def check_transcription(file_name, user_text):
9
+ audio_path = os.path.join("audio", file_name)
10
+ transcript_path = audio_path.replace(".mp3", ".txt").replace("audio", "transcripts")
11
+
12
+ with open(transcript_path, 'r') as f:
13
+ original = f.read().strip()
14
+
15
+ diff_html = highlight_fuzzy_diff(user_text, original)
16
+ score = round(fuzz.ratio(user_text, original))
17
+
18
+ if score > 90:
19
+ emoji = "✅"
20
+ comment = "Great job! Keep it up."
21
+ elif score > 70:
22
+ emoji = "⚠️"
23
+ comment = "Not bad, but pay attention to some words."
24
+ else:
25
+ emoji = "❌"
26
+ comment = "Try again. There are too many mistakes."
27
+
28
+ simple_feedback = f"""
29
+ <div style='font-size: 16px; margin-bottom: 6px;'>
30
+ Similarity: <b>{score}%</b> {emoji}
31
+ </div>
32
+ <div style='margin-top: 4px;'>{comment}</div>
33
+ """
34
+
35
+ return audio_path, original, (
36
+ "<div style='margin-bottom: 10px;'><b>⬜ Extra / incorrect words</b>, "
37
+ "<b>🟥 Missing words</b>, <b>🟩 Correct words</b></div>" + diff_html
38
+ ), simple_feedback
highlighter.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from rapidfuzz import fuzz, process
2
+
3
+
4
+ def highlight_fuzzy_diff(user_text, original_text):
5
+ user_words = user_text.strip().split()
6
+ original_words = original_text.strip().split()
7
+ highlighted = []
8
+ used_indices = set()
9
+
10
+ for word in user_words:
11
+ match, score, idx = process.extractOne(word, original_words, scorer=fuzz.ratio)
12
+ if score > 85 and idx not in used_indices:
13
+ highlighted.append(
14
+ f"<span style='background-color:#f0f0f0; color:#333; padding:4px 10px; "
15
+ f"margin:4px; border-radius:999px; font-weight:500; display:inline-block;'>{word}</span>"
16
+ )
17
+ used_indices.add(idx)
18
+ else:
19
+ highlighted.append(
20
+ f"<span style='background-color:#e6ffe6; color:#006600; padding:4px 10px; "
21
+ f"margin:4px; border-radius:999px; font-weight:500; display:inline-block;'>{word}</span>"
22
+ )
23
+
24
+ for i, word in enumerate(original_words):
25
+ if i not in used_indices:
26
+ highlighted.append(
27
+ f"<span style='background-color:#ffe6e6; color:#990000; padding:4px 10px; "
28
+ f"margin:4px; border-radius:999px; font-weight:500; display:inline-block;'>{word}</span>"
29
+ )
30
+
31
+ return " ".join(highlighted)