File size: 4,814 Bytes
a41f928
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import gradio as gr
from faster_whisper import WhisperModel
from docx import Document
import tempfile
import os

# Load model once at startup (cached after first run)
print("Loading Whisper model...")
model = WhisperModel("medium", compute_type="int8")
print("Model ready!")


def build_paragraphs(segments, pause_threshold=1.5):
    """
    Group Whisper segments into paragraphs.
    A new paragraph starts whenever the gap between the end of one segment
    and the start of the next exceeds `pause_threshold` seconds.
    """
    paragraphs = []
    current_sentences = []
    prev_end = None

    for seg in segments:
        if prev_end is not None and (seg.start - prev_end) >= pause_threshold:
            # Long pause → flush current paragraph
            if current_sentences:
                paragraphs.append(" ".join(current_sentences).strip())
                current_sentences = []
        current_sentences.append(seg.text.strip())
        prev_end = seg.end

    # Flush remaining
    if current_sentences:
        paragraphs.append(" ".join(current_sentences).strip())

    return paragraphs


def transcribe_audio(audio_file, output_format):
    """Transcribe an uploaded audio file and return a downloadable file + preview text."""
    if audio_file is None:
        return None, "⚠️ Please upload an audio file."

    # Transcribe — consume iterator once
    segments, _ = model.transcribe(audio_file)
    paragraphs = build_paragraphs(list(segments))

    base_name = os.path.splitext(os.path.basename(audio_file))[0]

    if output_format == "Word (.docx)":
        # ── Build Word document ───────────────────────────────────────────────
        doc = Document()
        doc.add_heading("Lecture Transcript", level=1)
        for para in paragraphs:
            doc.add_paragraph(para)

        tmp = tempfile.NamedTemporaryFile(
            delete=False,
            suffix=".docx",
            prefix=f"{base_name}_transcript_",
        )
        tmp.close()
        doc.save(tmp.name)
        preview = "\n\n".join(paragraphs)
        return tmp.name, preview

    else:
        # ── Plain text ────────────────────────────────────────────────────────
        full_text = "\n\n".join(paragraphs)
        tmp = tempfile.NamedTemporaryFile(
            delete=False,
            suffix=".txt",
            prefix=f"{base_name}_transcript_",
            mode="w",
            encoding="utf-8",
        )
        tmp.write(full_text)
        tmp.close()
        return tmp.name, full_text


# ── Gradio UI ─────────────────────────────────────────────────────────────────
with gr.Blocks(title="🎙️ Lecture Transcriber") as demo:

    gr.HTML("""
        <div style="text-align:center; margin-bottom: 4px;"><h1>🎙️ Lecture Transcriber</h1></div>
        <div style="text-align:center; color:#888; margin-bottom:24px; font-size:0.95em;">
            Upload an <b>.mp3</b> or <b>.m4a</b> audio file and get a formatted transcript instantly.
        </div>
    """)

    with gr.Row():
        with gr.Column(scale=1):
            audio_input = gr.Audio(
                label="Upload Audio File",
                type="filepath",
                sources=["upload"],
            )
            format_radio = gr.Radio(
                choices=["Plain Text (.txt)", "Word (.docx)"],
                value="Plain Text (.txt)",
                label="Output Format",
            )
            transcribe_btn = gr.Button("✨ Transcribe", variant="primary", size="lg")

        with gr.Column(scale=1):
            text_output = gr.Textbox(
                label="Transcript Preview",
                placeholder="Your transcript will appear here, split into paragraphs by natural pauses...",
                lines=16,
            )
            file_output = gr.File(label="⬇️ Download Transcript")

    transcribe_btn.click(
        fn=transcribe_audio,
        inputs=[audio_input, format_radio],
        outputs=[file_output, text_output],
        show_progress="full",
    )

    gr.HTML("""
        <div style="text-align:center; margin-top:16px; color:#aaa; font-size:0.85em;">
            Powered by <a href="https://github.com/SYSTRAN/faster-whisper" target="_blank">faster-whisper</a>
            · Paragraphs split by natural pauses · Runs 100% locally, no data stored.
        </div>
    """)

demo.launch(
    theme=gr.themes.Soft(primary_hue="violet"),
    css="""
    footer { display: none !important; }
    """,
)