clementBE commited on
Commit
5ce0986
·
verified ·
1 Parent(s): 39d879d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -84
app.py CHANGED
@@ -3,127 +3,146 @@ import torch
3
  from transformers import pipeline
4
  from docx import Document
5
  import datetime
6
- import time
7
  from pydub import AudioSegment
8
  import os
9
 
10
  # --- Model setup ---
11
- MODEL_ID = "openai/whisper-tiny" # Tiny Whisper model
12
  FRENCH_LANGUAGE = "fr"
13
- CHUNK_LENGTH_SEC = 600 # 10 minutes
14
 
15
- # Cache for the pipeline
16
  model_cache = {}
17
 
18
- def get_model_pipeline(progress=None):
19
  if "tiny_fr" not in model_cache:
20
- if progress: progress(0, desc="🚀 Loading model...")
21
  model_cache["tiny_fr"] = pipeline(
22
  "automatic-speech-recognition",
23
  model=MODEL_ID,
24
- device=-1, # Force CPU
 
25
  chunk_length_s=30
26
  )
27
- if progress: progress(1.0, desc="✅ Model loaded!")
28
  return model_cache["tiny_fr"]
29
 
30
- # --- Audio slicing ---
31
- def slice_audio(audio_path, chunk_length_sec=CHUNK_LENGTH_SEC):
32
  audio = AudioSegment.from_file(audio_path)
33
- chunks = []
34
- for i in range(0, len(audio), chunk_length_sec * 1000): # pydub uses milliseconds
35
- chunks.append(audio[i:i + chunk_length_sec * 1000])
36
- return chunks
37
 
38
- def export_chunks(chunks, base_name="slice"):
39
  paths = []
40
  for idx, chunk in enumerate(chunks):
41
- file_path = f"{base_name}_{idx+1}.wav"
42
- chunk.export(file_path, format="wav")
43
- paths.append(file_path)
44
  return paths
45
 
46
- # --- Output functions ---
47
- def create_vtt(segments, file_path):
48
- with open(file_path, "w", encoding="utf-8") as f:
49
- f.write("WEBVTT\n\n")
50
- for i, s in enumerate(segments):
51
- start = str(datetime.timedelta(seconds=int(s.get("start", 0))))
52
- end = str(datetime.timedelta(seconds=int(s.get("end", 0))))
53
- f.write(f"{i+1}\n{start} --> {end}\n{s.get('text','').strip()}\n\n")
54
-
55
- def create_docx(segments, file_path, with_timestamps=True):
56
  doc = Document()
57
- doc.add_heading("Transcription", 0)
58
- if with_timestamps:
59
- for s in segments:
60
- start = str(datetime.timedelta(seconds=int(s.get("start", 0))))
61
- end = str(datetime.timedelta(seconds=int(s.get("end", 0))))
62
- doc.add_paragraph(f"[{start} - {end}] {s.get('text','').strip()}")
63
- else:
64
- doc.add_paragraph(" ".join([s.get("text",'').strip() for s in segments]))
 
65
  doc.save(file_path)
66
 
67
- # --- Transcription function for slices ---
68
- def transcribe_slice(audio_file, vtt=True, docx_ts=False, docx_no_ts=True, progress=gr.Progress()):
69
- if audio_file is None:
70
- return None, None, "Please upload an audio file."
71
-
72
- start_time = time.time()
73
- pipe = get_model_pipeline(progress)
74
-
75
- # Split audio into 10-minute slices
76
- chunks = slice_audio(audio_file)
77
- chunk_files = export_chunks(chunks, "slice")
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  full_text = ""
80
- outputs = {}
81
  all_segments = []
82
 
83
- for idx, chunk_file in enumerate(chunk_files):
84
- progress((idx+1)/len(chunk_files), desc=f"🎤 Transcribing slice {idx+1}/{len(chunk_files)}...")
85
- result = pipe(chunk_file, return_timestamps=True, generate_kwargs={"language": FRENCH_LANGUAGE})
86
  segments = result.get("chunks", [])
87
  all_segments.extend(segments)
88
  full_text += result["text"] + "\n"
89
 
90
- progress(0.85, desc="📝 Generating files...")
 
 
 
 
 
 
91
 
92
- if vtt:
93
- outputs["VTT"] = "transcription.vtt"
94
- create_vtt(all_segments, outputs["VTT"])
95
- if docx_ts:
96
- outputs["DOCX_TS"] = "transcription_with_timestamps.docx"
97
- create_docx(all_segments, outputs["DOCX_TS"], True)
98
- if docx_no_ts:
99
- outputs["DOCX_NO_TS"] = "transcription_without_timestamps.docx"
100
- create_docx(all_segments, outputs["DOCX_NO_TS"], False)
101
 
102
- total_time = time.time() - start_time
103
- return full_text, list(outputs.values()), f"✅ Done! Took {total_time:.2f} seconds."
 
 
 
104
 
105
  # --- Gradio UI ---
106
- with gr.Blocks(title="Tiny French Whisper CPU with 10-min Slices") as demo:
107
- gr.Markdown("# 🎙️ Tiny Whisper - French CPU Transcription")
108
- gr.Markdown("Upload your audio file and get a transcription sliced into 10-minute segments, optimized for French (CPU).")
109
-
110
- with gr.Row():
111
- audio_input = gr.Audio(label="Audio File", type="filepath") # Fixed for latest Gradio
112
- with gr.Column():
113
- vtt_cb = gr.Checkbox(label="VTT", value=True)
114
- docx_ts_cb = gr.Checkbox(label="DOCX (with timestamps)", value=False)
115
- docx_no_ts_cb = gr.Checkbox(label="DOCX (without timestamps)", value=True)
116
- transcribe_btn = gr.Button("Transcribe", variant="primary")
117
- status_out = gr.Textbox(label="Status", interactive=False)
118
-
119
- transcription_out = gr.Textbox(label="Full Transcription", lines=10)
120
- files_out = gr.Files(label="Download Transcripts")
121
-
122
- transcribe_btn.click(
123
- fn=transcribe_slice,
124
- inputs=[audio_input, vtt_cb, docx_ts_cb, docx_no_ts_cb],
125
- outputs=[transcription_out, files_out, status_out]
126
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
  if __name__ == "__main__":
129
  demo.launch()
 
3
  from transformers import pipeline
4
  from docx import Document
5
  import datetime
 
6
  from pydub import AudioSegment
7
  import os
8
 
9
  # --- Model setup ---
10
+ MODEL_ID = "openai/whisper-tiny"
11
  FRENCH_LANGUAGE = "fr"
12
+ CHUNK_LENGTH_SEC = 600
13
 
 
14
  model_cache = {}
15
 
16
+ def get_model_pipeline():
17
  if "tiny_fr" not in model_cache:
18
+ device = 0 if torch.cuda.is_available() else -1
19
  model_cache["tiny_fr"] = pipeline(
20
  "automatic-speech-recognition",
21
  model=MODEL_ID,
22
+ device=device,
23
+ torch_dtype=torch.float16 if device >= 0 else torch.float32,
24
  chunk_length_s=30
25
  )
 
26
  return model_cache["tiny_fr"]
27
 
28
+ # --- Audio slicing (optional for GPU) ---
29
+ def slice_audio(audio_path):
30
  audio = AudioSegment.from_file(audio_path)
31
+ slices = []
32
+ for i in range(0, len(audio), CHUNK_LENGTH_SEC*1000):
33
+ slices.append(audio[i:i+CHUNK_LENGTH_SEC*1000])
34
+ return slices
35
 
36
+ def export_chunks(chunks):
37
  paths = []
38
  for idx, chunk in enumerate(chunks):
39
+ path = f"slice_{idx+1}.wav"
40
+ chunk.export(path, format="wav")
41
+ paths.append(path)
42
  return paths
43
 
44
+ # --- DOCX helpers ---
45
+ def create_docx_from_segments(segments, file_path, with_timestamps=True):
 
 
 
 
 
 
 
 
46
  doc = Document()
47
+ doc.add_heading("Full Transcription", 0)
48
+ for seg in segments:
49
+ text = seg.get("text", "").strip()
50
+ start = str(datetime.timedelta(seconds=int(seg.get("start", 0))))
51
+ end = str(datetime.timedelta(seconds=int(seg.get("end", 0))))
52
+ if with_timestamps:
53
+ doc.add_paragraph(f"[{start} - {end}] {text}")
54
+ else:
55
+ doc.add_paragraph(text)
56
  doc.save(file_path)
57
 
58
+ def create_docx_from_text(text_list, file_path):
59
+ doc = Document()
60
+ doc.add_heading("Patchwork Summary", 0)
61
+ for text in text_list:
62
+ doc.add_paragraph(text)
63
+ doc.save(file_path)
 
 
 
 
 
64
 
65
+ # --- Step 1: Slice audio ---
66
+ def generate_slice_options(audio_file):
67
+ slices = slice_audio(audio_file)
68
+ chunk_files = export_chunks(slices)
69
+ options = []
70
+ for idx, chunk in enumerate(slices):
71
+ start_min = idx*10
72
+ end_min = start_min + min(10, len(chunk)//1000//60)
73
+ options.append(f"Slice {idx+1}: {start_min}-{end_min} min")
74
+ return options, chunk_files
75
+
76
+ # --- Step 2: Transcribe selected slices ---
77
+ def transcribe_selected_slices(selected_slices, chunk_files):
78
+ if not selected_slices:
79
+ return "", "Please select at least one slice.", [], []
80
+
81
+ pipe = get_model_pipeline()
82
  full_text = ""
 
83
  all_segments = []
84
 
85
+ for sel in selected_slices:
86
+ idx = int(sel.split()[1].replace(":", "")) - 1
87
+ result = pipe(chunk_files[idx], return_timestamps=True, generate_kwargs={"language": FRENCH_LANGUAGE})
88
  segments = result.get("chunks", [])
89
  all_segments.extend(segments)
90
  full_text += result["text"] + "\n"
91
 
92
+ sentences = [s.get("text","").strip() for s in all_segments if s.get("text","").strip()]
93
+ return full_text, "✅ Transcription complete!", sentences, all_segments
94
+
95
+ # --- Step 3: Generate DOCX ---
96
+ def generate_docs(all_segments, selected_sentences):
97
+ full_docx_path = "full_transcription.docx"
98
+ create_docx_from_segments(all_segments, full_docx_path, with_timestamps=True)
99
 
100
+ patchwork_docx_path = None
101
+ if selected_sentences:
102
+ patchwork_docx_path = "patchwork_summary.docx"
103
+ create_docx_from_text(selected_sentences, patchwork_docx_path)
 
 
 
 
 
104
 
105
+ files = [full_docx_path]
106
+ if patchwork_docx_path:
107
+ files.append(patchwork_docx_path)
108
+
109
+ return files, "✅ DOCX files generated!"
110
 
111
  # --- Gradio UI ---
112
+ with gr.Blocks(title="Tiny Whisper GPU French Transcription") as demo:
113
+ gr.Markdown("## 🎙️ GPU-Optimized Tiny Whisper - French Transcription with Summary")
114
+
115
+ with gr.Tab("Step 1: Upload & Slice Audio"):
116
+ audio_input = gr.Audio(label="Upload Audio", type="filepath")
117
+ generate_slices_btn = gr.Button("Generate Slices")
118
+ slices_output = gr.CheckboxGroup(label="Select slices to transcribe")
119
+ chunk_files_state = gr.State()
120
+ generate_slices_btn.click(
121
+ generate_slice_options,
122
+ inputs=[audio_input],
123
+ outputs=[slices_output, chunk_files_state]
124
+ )
125
+
126
+ with gr.Tab("Step 2: Transcribe Selected Slices"):
127
+ transcribe_btn = gr.Button("Transcribe Selected Slices")
128
+ transcription_output = gr.Textbox(label="Full Transcription", lines=10)
129
+ sentence_selector = gr.CheckboxGroup(label="Select sentences for patchwork")
130
+ status_transcription = gr.Textbox(label="Status", interactive=False)
131
+ transcribe_btn.click(
132
+ transcribe_selected_slices,
133
+ inputs=[slices_output, chunk_files_state],
134
+ outputs=[transcription_output, status_transcription, sentence_selector, gr.State()]
135
+ )
136
+
137
+ with gr.Tab("Step 3: Generate DOCX"):
138
+ generate_docs_btn = gr.Button("Generate DOCX Files")
139
+ docs_output = gr.Files(label="Download DOCX files")
140
+ docs_status = gr.Textbox(label="Status", interactive=False)
141
+ generate_docs_btn.click(
142
+ generate_docs,
143
+ inputs=[gr.State(), sentence_selector],
144
+ outputs=[docs_output, docs_status]
145
+ )
146
 
147
  if __name__ == "__main__":
148
  demo.launch()