Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import whisper | |
| from transformers import AutoTokenizer, AutoModelForSeq2SeqLM | |
| import textwrap | |
| # ========================= | |
| # 1. PAGE CONFIG | |
| # ========================= | |
| st.set_page_config( | |
| page_title="AI Meeting & Lecture Summarizer", | |
| page_icon="π§ ", | |
| layout="wide" | |
| ) | |
| st.title("π§ AI Meeting & Lecture Summarizer") | |
| st.write( | |
| """ | |
| Upload a **recorded class / meeting audio file** or paste a **transcript**, and this app will: | |
| - β Transcribe (if audio) | |
| - π Summarize the content | |
| - β Extract **Action Items** | |
| - π Extract **Key Points / Decisions** | |
| """ | |
| ) | |
| # ========================= | |
| # 2. LOAD MODELS (CACHED) | |
| # ========================= | |
| def load_whisper_model(model_name: str = "small"): | |
| """ | |
| Load Whisper ASR model once and cache it. | |
| """ | |
| asr_model = whisper.load_model(model_name) | |
| return asr_model | |
| def load_summarization_model(): | |
| """ | |
| Load BART summarization model and tokenizer once and cache them. | |
| """ | |
| model_name = "facebook/bart-large-cnn" | |
| tokenizer = AutoTokenizer.from_pretrained(model_name) | |
| model = AutoModelForSeq2SeqLM.from_pretrained(model_name) | |
| return tokenizer, model | |
| tokenizer, summarizer_model = load_summarization_model() | |
| # ========================= | |
| # 3. CORE SUMMARIZATION FUNCTIONS | |
| # ========================= | |
| def summarize_text(text, max_len=120, min_len=40): | |
| """ | |
| Summarize a given text using the BART model. | |
| """ | |
| # 1. Tokenize text | |
| inputs = tokenizer( | |
| text, | |
| max_length=1024, # BART max input length | |
| truncation=True, | |
| return_tensors="pt" | |
| ) | |
| # 2. Generate summary token IDs | |
| summary_ids = summarizer_model.generate( | |
| inputs["input_ids"], | |
| num_beams=5, | |
| length_penalty=1.2, | |
| no_repeat_ngram_size=3, | |
| max_length=max_len, | |
| min_length=min_len, | |
| temperature=0.7, | |
| top_k=50, | |
| top_p=0.95, | |
| early_stopping=True, | |
| ) | |
| # 3. Decode token IDs to text | |
| summary = tokenizer.decode(summary_ids[0], skip_special_tokens=True) | |
| return summary | |
| def summarize_long_text(text, max_chunk_chars=3000, max_len=120, min_len=40): | |
| """ | |
| Handle long transcripts by: | |
| 1. Splitting into chunks | |
| 2. Summarizing each chunk | |
| 3. Summarizing the combined summaries | |
| """ | |
| if len(text) <= max_chunk_chars: | |
| return summarize_text(text, max_len=max_len, min_len=min_len) | |
| # Split by lines into manageable chunks | |
| chunks = [] | |
| current_chunk = "" | |
| for line in text.splitlines(): | |
| if len(current_chunk) + len(line) + 1 <= max_chunk_chars: | |
| current_chunk += " " + line | |
| else: | |
| chunks.append(current_chunk.strip()) | |
| current_chunk = line | |
| if current_chunk.strip(): | |
| chunks.append(current_chunk.strip()) | |
| partial_summaries = [] | |
| for i, chunk in enumerate(chunks, start=1): | |
| with st.spinner(f"Summarizing chunk {i}/{len(chunks)} (length={len(chunk)} chars)..."): | |
| part = summarize_text(chunk, max_len=max_len, min_len=min_len) | |
| partial_summaries.append(part) | |
| # Combine partial summaries and summarize again | |
| combined = " ".join(partial_summaries) | |
| final_summary = summarize_text(combined, max_len=max_len, min_len=min_len) | |
| return final_summary | |
| def extract_action_items(transcript_text: str) -> str: | |
| """ | |
| Use summarization to extract action items via an instruction-style prompt. | |
| """ | |
| prompt = ( | |
| "You are an assistant that reads a meeting or class transcript and extracts ACTION ITEMS.\n" | |
| "Action items are specific tasks that someone needs to do in the future.\n\n" | |
| "Transcript:\n" | |
| f"{transcript_text}\n\n" | |
| "Now list the ACTION ITEMS as clear bullet points:\n" | |
| "- " | |
| ) | |
| action_items_summary = summarize_long_text( | |
| prompt, | |
| max_chunk_chars=3000, | |
| max_len=200, | |
| min_len=60 | |
| ) | |
| return action_items_summary | |
| def extract_key_points(transcript_text: str) -> str: | |
| """ | |
| Use summarization to extract key points and decisions via an instruction-style prompt. | |
| """ | |
| prompt = ( | |
| "You are an assistant that reads a meeting or lecture transcript and extracts the KEY POINTS.\n" | |
| "Key points are the most important ideas, topics discussed, and decisions made.\n\n" | |
| "Transcript:\n" | |
| f"{transcript_text}\n\n" | |
| "Now summarize the KEY POINTS and DECISIONS as bullet points:\n" | |
| "- " | |
| ) | |
| key_points_summary = summarize_long_text( | |
| prompt, | |
| max_chunk_chars=3000, | |
| max_len=220, | |
| min_len=80 | |
| ) | |
| return key_points_summary | |
| # ========================= | |
| # 4. SIDEBAR: INPUT MODE | |
| # ========================= | |
| st.sidebar.header("Input Options") | |
| input_mode = st.sidebar.radio( | |
| "Choose input type:", | |
| ["Upload Audio File", "Paste Transcript Text"] | |
| ) | |
| # ========================= | |
| # 5. MAIN LOGIC | |
| # ========================= | |
| transcript_text = None | |
| if input_mode == "Upload Audio File": | |
| st.subheader("π Upload your meeting / lecture recording") | |
| audio_file = st.file_uploader( | |
| "Upload an audio/video file (mp3, wav, m4a, mp4, etc.)", | |
| type=["mp3", "wav", "m4a", "mp4"] | |
| ) | |
| if audio_file is not None: | |
| st.audio(audio_file) | |
| if st.button("Transcribe & Summarize"): | |
| with st.spinner("Loading Whisper model and transcribing audio..."): | |
| asr_model = load_whisper_model("small") | |
| # Save uploaded file to disk for Whisper | |
| temp_filename = "temp_uploaded_audio" | |
| with open(temp_filename, "wb") as f: | |
| f.write(audio_file.read()) | |
| # Transcribe | |
| result = asr_model.transcribe(temp_filename, language="en") | |
| transcript_text = result["text"] | |
| st.success("β Transcription complete!") | |
| st.write("### π Transcript (preview)") | |
| st.write(textwrap.shorten(transcript_text, width=1000, placeholder=" ...")) | |
| elif input_mode == "Paste Transcript Text": | |
| st.subheader("π Paste your transcript text") | |
| transcript_text = st.text_area( | |
| "Paste the full transcript here (meeting, class, lecture, etc.)", | |
| height=250 | |
| ) | |
| if transcript_text.strip() == "": | |
| transcript_text = None | |
| # ========================= | |
| # 6. RUN SUMMARIZATION ONCE WE HAVE TRANSCRIPT | |
| # ========================= | |
| if transcript_text: | |
| st.markdown("---") | |
| st.subheader("π§ AI Analysis") | |
| if st.button("Generate Summary & Action Items"): | |
| with st.spinner("Summarizing the transcript..."): | |
| main_summary = summarize_long_text(transcript_text) | |
| with st.spinner("Extracting action items..."): | |
| action_items = extract_action_items(transcript_text) | |
| with st.spinner("Extracting key points..."): | |
| key_points = extract_key_points(transcript_text) | |
| # Display results | |
| st.markdown("### π Main Summary") | |
| st.write(main_summary) | |
| st.markdown("### β Action Items") | |
| st.write(action_items) | |
| st.markdown("### π Key Points & Decisions") | |
| st.write(key_points) | |
| else: | |
| st.info("π Upload an audio file or paste a transcript to get started.") | |