dwishank commited on
Commit
ad814c6
·
verified ·
1 Parent(s): 2bb73b1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +215 -0
app.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ from transformers import pipeline
4
+ import os
5
+
6
+ print("Loading Whisper transcription model...")
7
+ transcriber = pipeline(
8
+ "automatic-speech-recognition",
9
+ model="openai/whisper-base",
10
+ chunk_length_s=30,
11
+ stride_length_s=5,
12
+ return_timestamps=False,
13
+ device=0 if torch.cuda.is_available() else -1,
14
+ )
15
+
16
+ print("Loading summarization model...")
17
+ summarizer = pipeline(
18
+ "summarization",
19
+ model="sshleifer/distilbart-cnn-12-6",
20
+ device=0 if torch.cuda.is_available() else -1,
21
+ )
22
+
23
+ print("Models ready.")
24
+
25
+
26
+ def transcribe_audio(audio_path):
27
+ result = transcriber(audio_path)
28
+ return result["text"].strip()
29
+
30
+
31
+ def chunk_text(text, max_tokens=900):
32
+ words = text.split()
33
+ chunks, current = [], []
34
+ for word in words:
35
+ current.append(word)
36
+ if len(current) >= max_tokens:
37
+ chunks.append(" ".join(current))
38
+ current = []
39
+ if current:
40
+ chunks.append(" ".join(current))
41
+ return chunks
42
+
43
+
44
+ def summarize_transcript(transcript):
45
+ if not transcript.strip():
46
+ return "No transcript available to summarize."
47
+
48
+ word_count = len(transcript.split())
49
+
50
+ if word_count <= 900:
51
+ result = summarizer(
52
+ transcript,
53
+ max_length=200,
54
+ min_length=60,
55
+ do_sample=False,
56
+ )
57
+ return result[0]["summary_text"]
58
+
59
+ chunks = chunk_text(transcript, max_tokens=900)
60
+ chunk_summaries = []
61
+ for chunk in chunks:
62
+ r = summarizer(chunk, max_length=150, min_length=40, do_sample=False)
63
+ chunk_summaries.append(r[0]["summary_text"])
64
+
65
+ combined = " ".join(chunk_summaries)
66
+ if len(combined.split()) > 900:
67
+ combined = " ".join(combined.split()[:900])
68
+
69
+ final = summarizer(combined, max_length=250, min_length=80, do_sample=False)
70
+ return final[0]["summary_text"]
71
+
72
+
73
+ def extract_action_items(transcript):
74
+ action_keywords = [
75
+ "will ", "should ", "need to ", "must ", "action:",
76
+ "todo:", "follow up", "follow-up", "assign", "deadline",
77
+ "by next", "responsible", "let's ", "we'll ", "i'll ", "you'll ",
78
+ ]
79
+ sentences = [
80
+ s.strip()
81
+ for s in transcript.replace("\n", " ").split(".")
82
+ if len(s.strip()) > 15
83
+ ]
84
+ actions = []
85
+ for sentence in sentences:
86
+ lower = sentence.lower()
87
+ if any(kw in lower for kw in action_keywords):
88
+ actions.append(f"• {sentence.strip()}.")
89
+
90
+ if not actions:
91
+ return "No specific action items detected."
92
+ return "\n".join(actions[:10])
93
+
94
+
95
+ def extract_key_topics(summary):
96
+ stop_words = {
97
+ "the", "a", "an", "is", "are", "was", "were", "be", "been",
98
+ "being", "have", "has", "had", "do", "does", "did", "will",
99
+ "would", "could", "should", "may", "might", "shall", "can",
100
+ "and", "but", "or", "nor", "so", "yet", "both", "either",
101
+ "neither", "not", "only", "own", "same", "than", "too", "very",
102
+ "just", "because", "as", "until", "while", "of", "in", "on",
103
+ "at", "by", "for", "with", "about", "into", "through", "during",
104
+ "before", "after", "to", "from", "up", "down", "out", "this",
105
+ "that", "these", "those", "it", "its", "they", "their", "there",
106
+ "we", "our", "you", "your", "he", "she", "his", "her", "also",
107
+ "if", "any", "then", "what", "which", "who", "how", "all", "each",
108
+ }
109
+ words = summary.lower().split()
110
+ freq = {}
111
+ for w in words:
112
+ w_clean = w.strip(".,!?;:()'\"")
113
+ if w_clean and w_clean not in stop_words and len(w_clean) > 3:
114
+ freq[w_clean] = freq.get(w_clean, 0) + 1
115
+
116
+ top = sorted(freq, key=freq.get, reverse=True)[:8]
117
+ if not top:
118
+ return "Topics could not be extracted."
119
+ return " • ".join(t.title() for t in top)
120
+
121
+
122
+ def analyze_meeting(audio_file):
123
+ if audio_file is None:
124
+ return ("Please upload an audio file.", "", "", "", "")
125
+
126
+ try:
127
+ transcript = transcribe_audio(audio_file)
128
+ if not transcript:
129
+ return ("Transcription produced no text. Try a clearer audio file.", "", "", "", "")
130
+
131
+ summary = summarize_transcript(transcript)
132
+ actions = extract_action_items(transcript)
133
+ topics = extract_key_topics(summary)
134
+
135
+ word_count = len(transcript.split())
136
+ stats = f"📊 {word_count} words transcribed | ~{word_count // 130 + 1} min read"
137
+
138
+ return transcript, summary, actions, topics, stats
139
+
140
+ except Exception as e:
141
+ return (f"Error during processing: {str(e)}", "", "", "", "")
142
+
143
+
144
+ with gr.Blocks(
145
+ title="Meeting Audio Analyzer",
146
+ theme=gr.themes.Soft(),
147
+ css="""
148
+ #title { text-align: center; margin-bottom: 0.5rem; }
149
+ #subtitle { text-align: center; color: #666; margin-bottom: 1.5rem; font-size: 0.95rem; }
150
+ footer { display: none !important; }
151
+ """,
152
+ ) as demo:
153
+
154
+ gr.Markdown("# Meeting Audio Analyzer", elem_id="title")
155
+ gr.Markdown(
156
+ "Upload a meeting recording — get a full transcript, summary, action items, and key topics.",
157
+ elem_id="subtitle",
158
+ )
159
+
160
+ with gr.Row():
161
+ with gr.Column(scale=1):
162
+ audio_input = gr.Audio(
163
+ label="Upload Meeting Audio",
164
+ type="filepath",
165
+ sources=["upload"],
166
+ )
167
+ analyze_btn = gr.Button("Analyze Meeting", variant="primary", size="lg")
168
+ stats_out = gr.Markdown(value="", label="")
169
+
170
+ with gr.Column(scale=2):
171
+ with gr.Tabs():
172
+ with gr.TabItem("Summary"):
173
+ summary_out = gr.Textbox(
174
+ label="Meeting Summary",
175
+ lines=8,
176
+ interactive=False,
177
+ placeholder="Summary will appear here after analysis...",
178
+ )
179
+ with gr.TabItem("Action Items"):
180
+ actions_out = gr.Textbox(
181
+ label="Action Items",
182
+ lines=8,
183
+ interactive=False,
184
+ placeholder="Action items will appear here...",
185
+ )
186
+ with gr.TabItem("Key Topics"):
187
+ topics_out = gr.Textbox(
188
+ label="Key Topics",
189
+ lines=3,
190
+ interactive=False,
191
+ placeholder="Key topics will appear here...",
192
+ )
193
+ with gr.TabItem("Full Transcript"):
194
+ transcript_out = gr.Textbox(
195
+ label="Full Transcript",
196
+ lines=15,
197
+ interactive=False,
198
+ placeholder="Full transcript will appear here...",
199
+ )
200
+
201
+ analyze_btn.click(
202
+ fn=analyze_meeting,
203
+ inputs=[audio_input],
204
+ outputs=[transcript_out, summary_out, actions_out, topics_out, stats_out],
205
+ show_progress=True,
206
+ )
207
+
208
+ gr.Markdown(
209
+ "Models: [Whisper Base](https://huggingface.co/openai/whisper-base) · "
210
+ "[DistilBART CNN](https://huggingface.co/sshleifer/distilbart-cnn-12-6) — "
211
+ "runs fully locally, no API keys needed."
212
+ )
213
+
214
+ if __name__ == "__main__":
215
+ demo.launch()