hlevring commited on
Commit
44ae928
·
1 Parent(s): 559a1c3

Add timestamp code

Browse files
README.md CHANGED
@@ -4,10 +4,8 @@ emoji: 🏃
4
  colorFrom: red
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.9.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: 'Space to test word timestamps in transcript '
11
  ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
4
  colorFrom: red
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.49.1
8
  app_file: app.py
9
  pinned: false
10
+ short_description: 'Testing word timestamps from any transcript'
11
  ---
 
 
app.py CHANGED
@@ -1,7 +1,1091 @@
 
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
5
 
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  demo.launch()
 
1
+ # Timestamps Tester - Test word timestamps from any transcript
2
  import gradio as gr
3
+ import soundfile as sf
4
+ import numpy as np
5
+ import base64
6
+ import io
7
+ import json
8
+ import time
9
+ import traceback
10
+ from ten_vad import TenVad
11
+ import matplotlib
12
+ matplotlib.use('Agg')
13
+ import matplotlib.pyplot as plt
14
 
 
 
15
 
16
+ def detect_silence_periods(audio_data, sample_rate, prob_threshold=0.5, min_off_ms=48, min_on_ms=64):
17
+ """Run TEN VAD to detect silence periods in audio.
18
+
19
+ Args:
20
+ audio_data: numpy array of audio samples (float, mono, 16kHz)
21
+ sample_rate: sample rate (must be 16000)
22
+ prob_threshold: VAD probability threshold (0.0-1.0), higher = less sensitive
23
+ min_off_ms: Minimum silence duration in ms - shorter silences are filled in as voice
24
+ min_on_ms: Minimum voice duration in ms - shorter voice bursts are removed
25
+
26
+ Returns:
27
+ List of dicts with 'start' and 'end' times for each silence period
28
+ """
29
+ TARGET_SR = 16000 # TEN VAD requires 16kHz
30
+ HOP_SIZE = 256 # 16ms at 16kHz
31
+ FRAME_MS = 16.0 # Each frame is 16ms
32
+
33
+ print(f"[VAD] Settings: prob_threshold={prob_threshold}, min_off_ms={min_off_ms}, min_on_ms={min_on_ms}")
34
+
35
+ if sample_rate != TARGET_SR:
36
+ print(f"[VAD] Warning: Expected 16kHz audio, got {sample_rate}Hz")
37
+
38
+ # Convert float audio to int16 (TEN VAD expects int16)
39
+ if audio_data.dtype == np.float32 or audio_data.dtype == np.float64:
40
+ audio_int16 = (audio_data * 32767).astype(np.int16)
41
+ else:
42
+ audio_int16 = audio_data.astype(np.int16)
43
+
44
+ # Create VAD instance
45
+ vad = TenVad(hop_size=HOP_SIZE, threshold=prob_threshold)
46
+
47
+ frame_duration = HOP_SIZE / TARGET_SR # 0.016s = 16ms
48
+
49
+ # Process frame by frame and collect raw flags
50
+ num_frames = len(audio_int16) // HOP_SIZE
51
+
52
+ # Use list for mutable flags (will be modified by post-processing)
53
+ is_voice = [0] * num_frames
54
+
55
+ for i in range(num_frames):
56
+ frame_start = i * HOP_SIZE
57
+ frame = audio_int16[frame_start:frame_start + HOP_SIZE]
58
+
59
+ result = vad.process(frame)
60
+ # TEN VAD returns tuple: (probability, flag) or has .flag attribute
61
+ if isinstance(result, tuple):
62
+ flag = result[1] # (probability, flag)
63
+ else:
64
+ flag = result.flag
65
+ is_voice[i] = flag
66
+
67
+ # Convert ms thresholds to frame counts
68
+ min_off_frames = int(min_off_ms / FRAME_MS + 0.1)
69
+ min_on_frames = int(min_on_ms / FRAME_MS + 0.1)
70
+
71
+ # Post-processing loop (matches VadPipeline.cpp logic)
72
+ while True:
73
+ # Pass 1: Fill in short silence gaps (minOff)
74
+ # If silence duration <= min_off_frames, convert to voice
75
+ if min_off_frames > 0:
76
+ start_off = -1
77
+ for i in range(num_frames):
78
+ if is_voice[i]: # Voice detected
79
+ if start_off >= 0 and (i - start_off) <= min_off_frames:
80
+ # Short silence gap - fill it in as voice
81
+ for j in range(start_off, i):
82
+ is_voice[j] = 1
83
+ start_off = -1
84
+ elif start_off < 0:
85
+ start_off = i
86
+
87
+ # Pass 2: Remove short voice bursts (minOn)
88
+ # If voice duration <= min_on_frames, convert to silence
89
+ changed = False
90
+ if min_on_frames > 0:
91
+ start_on = -1
92
+ for i in range(num_frames):
93
+ if not is_voice[i]: # Silence detected
94
+ if start_on >= 0 and (i - start_on) <= min_on_frames:
95
+ # Short voice burst - remove it
96
+ changed = True
97
+ for j in range(start_on, i):
98
+ is_voice[j] = 0
99
+ start_on = -1
100
+ elif start_on < 0:
101
+ start_on = i
102
+
103
+ # Handle case where audio ends with short voice burst
104
+ if start_on >= 0 and (num_frames - start_on) <= min_on_frames:
105
+ changed = True
106
+ for j in range(start_on, num_frames):
107
+ is_voice[j] = 0
108
+
109
+ # Exit loop if no changes or minOff is disabled
110
+ if not changed or min_off_frames == 0:
111
+ break
112
+
113
+ # Convert frame flags to silence periods
114
+ silence_periods = []
115
+ in_silence = False
116
+ silence_start = 0.0
117
+
118
+ for i in range(num_frames):
119
+ current_time = i * frame_duration
120
+ if is_voice[i]:
121
+ # Voice frame
122
+ if in_silence:
123
+ # End of silence period
124
+ silence_periods.append({
125
+ 'start': round(silence_start, 3),
126
+ 'end': round(current_time, 3)
127
+ })
128
+ in_silence = False
129
+ else:
130
+ # Silence frame
131
+ if not in_silence:
132
+ # Start of silence period
133
+ silence_start = current_time
134
+ in_silence = True
135
+
136
+ # Handle case where audio ends in silence
137
+ if in_silence:
138
+ silence_periods.append({
139
+ 'start': round(silence_start, 3),
140
+ 'end': round(num_frames * frame_duration, 3)
141
+ })
142
+
143
+ return silence_periods
144
+
145
+
146
+ def print_speech_silence_log(timestamps_data, silence_periods):
147
+ """Print interleaved speech and silence log sorted by start time."""
148
+
149
+ # Build unified list
150
+ entries = []
151
+
152
+ # Add speech entries (word timestamps)
153
+ for item in timestamps_data:
154
+ entries.append({
155
+ 'type': 'speech',
156
+ 'start': item['start'],
157
+ 'end': item['end'],
158
+ 'word': item['word']
159
+ })
160
+
161
+ # Add silence entries
162
+ for item in silence_periods:
163
+ entries.append({
164
+ 'type': 'silence',
165
+ 'start': item['start'],
166
+ 'end': item['end']
167
+ })
168
+
169
+ # Sort by start time
170
+ entries.sort(key=lambda x: x['start'])
171
+
172
+ # Print log
173
+ print("\n=== SPEECH & SILENCE LOG ===")
174
+ for entry in entries:
175
+ if entry['type'] == 'speech':
176
+ print(f"[Speech] [{entry['start']:.3f}-{entry['end']:.3f}] {entry['word']}")
177
+ else:
178
+ duration_ms = int((entry['end'] - entry['start']) * 1000)
179
+ print(f"[Silence] [{entry['start']:.3f}-{entry['end']:.3f}] [{duration_ms}ms]")
180
+
181
+ # Calculate summary
182
+ total_silence = sum(p['end'] - p['start'] for p in silence_periods)
183
+ print(f"\n=== SUMMARY ===")
184
+ print(f"Words: {len(timestamps_data)}, Silence periods: {len(silence_periods)}, Total silence: {total_silence:.2f}s")
185
+ print("=" * 30 + "\n")
186
+
187
+
188
+ def parse_transcript_file(file_path):
189
+ """Parse a transcript JSON file and extract word timestamps.
190
+
191
+ Supports three formats:
192
+ - Format 1: segments[].words[] with {start, end, word}
193
+ - Format 2: Top-level words[] with {start, end, word}
194
+ - Format 3: segments[] with {start, end, text} (text treated as single word)
195
+
196
+ Args:
197
+ file_path: Path to the JSON transcript file
198
+
199
+ Returns:
200
+ Tuple of (full_text, timestamps_data) where timestamps_data is list of
201
+ {word, start, end} dicts
202
+
203
+ Raises:
204
+ ValueError if format not recognized
205
+ """
206
+ with open(file_path, 'r', encoding='utf-8') as f:
207
+ data = json.load(f)
208
+
209
+ timestamps_data = []
210
+ full_text_parts = []
211
+
212
+ # Try Format 1: segments[].words[] with {start, end, word}
213
+ if 'segments' in data and len(data['segments']) > 0:
214
+ first_segment = data['segments'][0]
215
+ if 'words' in first_segment and isinstance(first_segment['words'], list):
216
+ # Format 1: Nested words inside segments
217
+ print("[IMPORT] Detected Format 1: segments[].words[]")
218
+ for segment in data['segments']:
219
+ for word_entry in segment.get('words', []):
220
+ word = word_entry.get('word', '').strip()
221
+ if word:
222
+ timestamps_data.append({
223
+ 'word': word,
224
+ 'start': float(word_entry.get('start', 0)),
225
+ 'end': float(word_entry.get('end', 0))
226
+ })
227
+ full_text_parts.append(word)
228
+ return ' '.join(full_text_parts), timestamps_data
229
+
230
+ # Try Format 3: segments[] with {start, end, text} (text as word)
231
+ if 'text' in first_segment and 'start' in first_segment and 'end' in first_segment:
232
+ print("[IMPORT] Detected Format 3: segments[] with {start, end, text}")
233
+ for segment in data['segments']:
234
+ text = segment.get('text', '').strip()
235
+ if text:
236
+ timestamps_data.append({
237
+ 'word': text,
238
+ 'start': float(segment.get('start', 0)),
239
+ 'end': float(segment.get('end', 0))
240
+ })
241
+ full_text_parts.append(text)
242
+ # Use top-level text if available, otherwise join segments
243
+ full_text = data.get('text', ' '.join(full_text_parts))
244
+ return full_text, timestamps_data
245
+
246
+ # Try Format 2: Top-level words[] with {start, end, word}
247
+ if 'words' in data and isinstance(data['words'], list):
248
+ print("[IMPORT] Detected Format 2: words[]")
249
+ for word_entry in data['words']:
250
+ word = word_entry.get('word', '').strip()
251
+ if word:
252
+ timestamps_data.append({
253
+ 'word': word,
254
+ 'start': float(word_entry.get('start', 0)),
255
+ 'end': float(word_entry.get('end', 0))
256
+ })
257
+ full_text_parts.append(word)
258
+ # Use top-level text if available, otherwise join words
259
+ full_text = data.get('text', ' '.join(full_text_parts))
260
+ return full_text, timestamps_data
261
+
262
+ raise ValueError("Unrecognized transcript format. Expected segments[].words[], words[], or segments[] with {start, end, text}")
263
+
264
+
265
+ def load_transcript(audio, transcript_file, prob_threshold=0.5, min_off_ms=48, min_on_ms=64):
266
+ """Load external transcript and run VAD on audio.
267
+
268
+ Args:
269
+ audio: Path to audio file
270
+ transcript_file: Path to JSON transcript file
271
+ prob_threshold: VAD probability threshold
272
+ min_off_ms: Minimum silence duration in ms
273
+ min_on_ms: Minimum voice duration in ms
274
+
275
+ Returns:
276
+ Tuple of (text, timestamps_data, audio_data_tuple, raw_text, export_metadata, silence_periods)
277
+ """
278
+ try:
279
+ # Check if audio is provided
280
+ if audio is None:
281
+ return "No audio provided. Please upload audio first.", [], None, "", {}, []
282
+
283
+ # Check if transcript file is provided
284
+ if transcript_file is None:
285
+ return "No transcript file provided.", [], None, "", {}, []
286
+
287
+ # Parse the transcript file
288
+ try:
289
+ text, timestamps_data = parse_transcript_file(transcript_file)
290
+ print(f"[IMPORT] Loaded {len(timestamps_data)} word timestamps from transcript")
291
+ except Exception as e:
292
+ return f"Error parsing transcript file: {str(e)}", [], None, "", {}, []
293
+
294
+ # Preprocess audio: convert to mono if stereo
295
+ audio_data, sample_rate = sf.read(audio)
296
+
297
+ # Convert stereo to mono by averaging channels
298
+ if len(audio_data.shape) > 1 and audio_data.shape[1] == 2:
299
+ audio_data = np.mean(audio_data, axis=1)
300
+
301
+ # Resample to 16kHz if needed (required by TEN VAD)
302
+ TARGET_SR = 16000
303
+ if sample_rate != TARGET_SR:
304
+ duration = len(audio_data) / sample_rate
305
+ new_length = int(duration * TARGET_SR)
306
+ x_old = np.linspace(0, duration, len(audio_data), endpoint=False)
307
+ x_new = np.linspace(0, duration, new_length, endpoint=False)
308
+ audio_data = np.interp(x_new, x_old, audio_data).astype(np.float32)
309
+ print(f"[AUDIO] Resampled from {sample_rate}Hz to {TARGET_SR}Hz")
310
+ sample_rate = TARGET_SR
311
+
312
+ # Run VAD to detect silence periods
313
+ silence_periods = []
314
+ try:
315
+ silence_periods = detect_silence_periods(audio_data, sample_rate, prob_threshold, min_off_ms, min_on_ms)
316
+ print_speech_silence_log(timestamps_data, silence_periods)
317
+ except Exception as e:
318
+ print(f"[VAD] Error during silence detection: {str(e)}\n{traceback.format_exc()}")
319
+
320
+ # Calculate audio duration
321
+ audio_duration = len(audio_data) / sample_rate
322
+
323
+ # Build export metadata
324
+ export_metadata = {
325
+ 'model': 'imported-transcript',
326
+ 'audio_duration': round(audio_duration, 2),
327
+ 'word_count': len(timestamps_data),
328
+ 'token_count': 0,
329
+ 'hypothesis_score': None,
330
+ 'frame_duration': None
331
+ }
332
+
333
+ # Return text, timestamps, audio data, raw_text (same as text for imports), export metadata, and silence periods
334
+ return text, timestamps_data, (audio_data, sample_rate), text, export_metadata, silence_periods
335
+
336
+ except Exception as e:
337
+ return f"Error loading transcript: {str(e)}\n{traceback.format_exc()}", [], None, "", {}, []
338
+
339
+
340
+ def extract_audio_segment(audio_state, intervals, current_window=None):
341
+ """Fast audio extraction from memory with waveform visualization.
342
+
343
+ Args:
344
+ audio_state: Tuple of (audio_data, sample_rate)
345
+ intervals: List of (start_time, end_time) tuples to play
346
+ current_window: Dict with 'start' and 'end' of current waveform window, or None
347
+
348
+ Returns:
349
+ Tuple of (html_output, new_window_state)
350
+ """
351
+ # Wrapper to ensure controls never collapse
352
+ def wrap_output(content, window_state=None):
353
+ return f'<div style="min-height: 200px;">{content}</div>', window_state
354
+
355
+ try:
356
+ if audio_state is None:
357
+ return wrap_output("<p style='color: red; padding: 20px;'>No audio loaded. Please upload audio and load a transcript first.</p>")
358
+
359
+ if not intervals:
360
+ return wrap_output("<p style='color: red; padding: 20px;'>No intervals provided.</p>")
361
+
362
+ audio_data, sample_rate = audio_state
363
+ audio_duration = len(audio_data) / sample_rate
364
+
365
+ # Calculate overall bounds from all intervals
366
+ overall_start = min(iv[0] for iv in intervals)
367
+ overall_end = max(iv[1] for iv in intervals)
368
+
369
+ # Default context padding is 160ms
370
+ DEFAULT_PADDING = 0.16
371
+
372
+ # Determine if we need to redraw the waveform or just update the shaded area
373
+ need_redraw = True
374
+ if current_window is not None:
375
+ # Check if ALL intervals fit within the current window
376
+ if overall_start >= current_window['start'] and overall_end <= current_window['end']:
377
+ need_redraw = False
378
+ # Reuse the current window boundaries
379
+ padded_start = current_window['start']
380
+ padded_end = current_window['end']
381
+
382
+ if need_redraw:
383
+ # Calculate new window with ±160ms padding around overall bounds
384
+ padded_start = max(0, overall_start - DEFAULT_PADDING)
385
+ padded_end = min(audio_duration, overall_end + DEFAULT_PADDING)
386
+
387
+ # Extract padded segment for waveform visualization
388
+ start_sample_padded = int(padded_start * sample_rate)
389
+ end_sample_padded = int(padded_end * sample_rate)
390
+ segment_for_waveform = audio_data[start_sample_padded:end_sample_padded]
391
+
392
+ # Generate waveform visualization with padded segment (reduced height)
393
+ fig, ax = plt.subplots(figsize=(12, 2.25))
394
+
395
+ # Downsample for visualization using block averaging (more accurate than skipping)
396
+ max_points = 8000
397
+ if len(segment_for_waveform) > max_points:
398
+ # Reshape into blocks and take mean of each block
399
+ # Pad to multiple of block_size to avoid losing end samples
400
+ block_size = len(segment_for_waveform) // max_points
401
+ remainder = len(segment_for_waveform) % block_size
402
+ if remainder > 0:
403
+ # Pad with the last value to make it divisible
404
+ padding_needed = block_size - remainder
405
+ segment_padded = np.pad(segment_for_waveform, (0, padding_needed), mode='edge')
406
+ else:
407
+ segment_padded = segment_for_waveform
408
+ segment_vis = segment_padded.reshape(-1, block_size).mean(axis=1)
409
+ # Generate matching time points spanning the FULL padded range
410
+ times_vis = np.linspace(padded_start, padded_end, len(segment_vis))
411
+ else:
412
+ segment_vis = segment_for_waveform
413
+ times_vis = np.linspace(padded_start, padded_end, len(segment_for_waveform))
414
+
415
+ ax.plot(times_vis, segment_vis, linewidth=0.5, color='#666')
416
+ ax.fill_between(times_vis, segment_vis, alpha=0.3, color='#ccc')
417
+
418
+ # Highlight context areas (gray) - areas outside all playback regions
419
+ # First, shade the entire padded area as context
420
+ ax.axvspan(padded_start, padded_end, alpha=0.1, color='#888', label='Context (not played)')
421
+
422
+ # Then highlight each playback interval in green (overwrites context shading)
423
+ for i, (start_time, end_time) in enumerate(intervals):
424
+ label = 'Playback region' if i == 0 else None
425
+ ax.axvspan(start_time, end_time, alpha=0.3, color='#4CAF50', label=label)
426
+
427
+ ax.set_xlabel('Time (seconds)', fontsize=10)
428
+ ax.set_ylabel('Amplitude', fontsize=10)
429
+
430
+ # Calculate context on each side in ms
431
+ left_context_ms = int((overall_start - padded_start) * 1000)
432
+ right_context_ms = int((padded_end - overall_end) * 1000)
433
+
434
+ # Format context string - symmetric or asymmetric
435
+ if left_context_ms == right_context_ms:
436
+ context_str = f'(±{left_context_ms}ms context)'
437
+ else:
438
+ context_str = f'(-{left_context_ms}ms / +{right_context_ms}ms context)'
439
+
440
+ # Build title showing intervals
441
+ if len(intervals) == 1:
442
+ interval_str = f'{intervals[0][0]:.3f}s – {intervals[0][1]:.3f}s'
443
+ else:
444
+ interval_str = f'{len(intervals)} intervals: {overall_start:.3f}s – {overall_end:.3f}s'
445
+
446
+ ax.set_title(f'Audio Segment: {interval_str} {context_str}', fontsize=11)
447
+ ax.legend(fontsize=9)
448
+ ax.grid(True, alpha=0.3)
449
+
450
+ # Convert plot to base64 image
451
+ buf = io.BytesIO()
452
+ plt.tight_layout()
453
+ plt.savefig(buf, format='png', dpi=100)
454
+ buf.seek(0)
455
+ img_base64 = base64.b64encode(buf.read()).decode()
456
+ plt.close(fig)
457
+
458
+ # Extract and concatenate all interval segments for playback
459
+ audio_segments = []
460
+ SILENCE_GAP_MS = 50 # Gap between intervals in ms
461
+ silence_samples = int(SILENCE_GAP_MS / 1000 * sample_rate)
462
+ silence_gap = np.zeros(silence_samples, dtype=audio_data.dtype)
463
+
464
+ for i, (start_time, end_time) in enumerate(intervals):
465
+ start_sample = int(start_time * sample_rate)
466
+ end_sample = int(end_time * sample_rate)
467
+ audio_segments.append(audio_data[start_sample:end_sample])
468
+ # Add silence gap between intervals (not after the last one)
469
+ if i < len(intervals) - 1:
470
+ audio_segments.append(silence_gap)
471
+
472
+ combined_playback = np.concatenate(audio_segments)
473
+
474
+ # Convert PLAYBACK segments (concatenated) to base64 WAV
475
+ audio_buf = io.BytesIO()
476
+ sf.write(audio_buf, combined_playback, sample_rate, format='WAV')
477
+ audio_buf.seek(0)
478
+ audio_base64 = base64.b64encode(audio_buf.read()).decode()
479
+ audio_data_url = f"data:audio/wav;base64,{audio_base64}"
480
+
481
+ # Add unique ID to force Gradio to re-render (triggers autoplay)
482
+ unique_id = int(time.time() * 1000)
483
+
484
+ # Calculate context on each side in ms for the info text
485
+ left_context_ms = int((overall_start - padded_start) * 1000)
486
+ right_context_ms = int((padded_end - overall_end) * 1000)
487
+
488
+ # Format context string - symmetric or asymmetric
489
+ if left_context_ms == right_context_ms:
490
+ context_info = f'±{left_context_ms}ms'
491
+ else:
492
+ context_info = f'-{left_context_ms}ms / +{right_context_ms}ms'
493
+
494
+ # Calculate total playback duration
495
+ total_duration_ms = sum((end - start) * 1000 for start, end in intervals)
496
+ if len(intervals) > 1:
497
+ total_duration_ms += SILENCE_GAP_MS * (len(intervals) - 1) # Include gaps
498
+
499
+ # Build segment info text
500
+ if len(intervals) == 1:
501
+ segment_info = f'{intervals[0][0]:.3f}s – {intervals[0][1]:.3f}s'
502
+ else:
503
+ segment_info = f'{len(intervals)} intervals'
504
+
505
+ # Create HTML with waveform and native audio controls
506
+ html_output = f'''
507
+ <div style="margin: 10px 0;" data-render-id="{unique_id}">
508
+ <img src="data:image/png;base64,{img_base64}" style="width: 100%; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
509
+
510
+ <div style="margin-top: 10px; display: flex; align-items: center; gap: 15px;">
511
+ <audio id="segment-audio" controls autoplay style="flex: 1;">
512
+ <source src="{audio_data_url}" type="audio/wav">
513
+ </audio>
514
+ </div>
515
+
516
+ <div style="margin-top: 8px; text-align: center;">
517
+ <span style="font-size: 14px; font-weight: bold; color: #333;">
518
+ Segment: {segment_info}
519
+ </span>
520
+ <span style="font-size: 12px; color: #666; margin-left: 15px;">
521
+ Duration: {total_duration_ms:.0f}ms | Context shown: {context_info}
522
+ </span>
523
+ </div>
524
+ </div>
525
+ '''
526
+
527
+ # Return HTML and new window state
528
+ new_window = {'start': padded_start, 'end': padded_end}
529
+ return wrap_output(html_output, new_window)
530
+
531
+ except Exception as e:
532
+ return wrap_output(f"<pre style='padding: 20px;'>Error: {str(e)}\n{traceback.format_exc()}</pre>", current_window)
533
+
534
+
535
+ def build_timestamps_iframe_html(entries_json, export_json_str):
536
+ """Build the interactive word timestamps iframe HTML.
537
+
538
+ Args:
539
+ entries_json: JSON string of word/silence entries
540
+ export_json_str: JSON string of full export data for download
541
+
542
+ Returns:
543
+ Complete iframe HTML for embedding in Gradio
544
+ """
545
+ iframe_html = f'''
546
+ <!DOCTYPE html>
547
+ <html>
548
+ <head>
549
+ <style>
550
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
551
+ body {{ font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 10px; background: #f9f9f9; }}
552
+ h3 {{ margin-bottom: 8px; font-size: 16px; }}
553
+ .help {{ font-size: 11px; color: #666; margin-bottom: 10px; }}
554
+ .container {{ max-height: 180px; overflow-y: auto; background: #fff; border-radius: 8px; padding: 8px; border: 1px solid #ddd; }}
555
+ .word-btn {{
556
+ display: inline-block;
557
+ background: #e8f4f8;
558
+ padding: 5px 10px;
559
+ margin: 3px;
560
+ border-radius: 4px;
561
+ cursor: pointer;
562
+ border: 1px solid #cde;
563
+ font-size: 13px;
564
+ transition: all 0.15s;
565
+ }}
566
+ .word-btn:hover {{ background: #c5e5f5; }}
567
+ .word-btn.selected {{ background: #4CAF50; color: white; border-color: #3a9; }}
568
+ .silence-btn {{
569
+ display: inline-block;
570
+ background: #ffe4c4;
571
+ padding: 5px 8px;
572
+ margin: 3px;
573
+ border-radius: 4px;
574
+ cursor: pointer;
575
+ border: 1px solid #dca;
576
+ font-size: 11px;
577
+ transition: all 0.15s;
578
+ }}
579
+ .silence-btn:hover {{ background: #ffd4a4; }}
580
+ .silence-btn.selected {{ background: #ff9800; color: white; border-color: #e68a00; }}
581
+ .checkbox-container {{
582
+ display: inline-flex;
583
+ align-items: center;
584
+ margin-left: 15px;
585
+ font-size: 12px;
586
+ cursor: pointer;
587
+ }}
588
+ .checkbox-container input {{
589
+ margin-right: 5px;
590
+ cursor: pointer;
591
+ }}
592
+ .checkbox-container:hover {{
593
+ color: #0066cc;
594
+ }}
595
+ .time {{ color: #0066cc; font-size: 10px; font-weight: bold; }}
596
+ .silence-time {{ color: #996600; font-size: 10px; font-weight: bold; }}
597
+ .duration {{ color: #666; font-size: 10px; margin-left: 3px; }}
598
+ .word {{ margin-left: 4px; }}
599
+ </style>
600
+ </head>
601
+ <body>
602
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
603
+ <div style="display: flex; align-items: center;">
604
+ <h3 style="margin: 0;">Word Timestamps</h3>
605
+ <label class="checkbox-container" title="Extends word end times toward midpoint of gap to next word (max 120ms). Helps capture word endings that may be cut off.">
606
+ <input type="checkbox" id="adjust-intervals">
607
+ Apply Time Interval Adjustment
608
+ </label>
609
+ </div>
610
+ <a href="#" id="download-json" style="font-size: 12px; color: #0066cc; text-decoration: none;">📥 Download JSON</a>
611
+ </div>
612
+ <script>var exportJsonStr = {json.dumps(export_json_str)};</script>
613
+ <p class="help"><b>Click</b> = select &nbsp;|&nbsp; <b>Ctrl+Click</b> = toggle &nbsp;|&nbsp; <b>Shift+Click</b> = range &nbsp;&nbsp;&nbsp; <span style="background: #ffe4c4; padding: 2px 8px; border-radius: 3px; border: 1px solid #dca;"></span> = detected non speech</p>
614
+ <div class="container" id="words"></div>
615
+ <script>
616
+ var entries = {entries_json};
617
+ var container = document.getElementById('words');
618
+
619
+ // Merge consecutive silence periods (no word between them)
620
+ function mergeConsecutiveSilences(entryList) {{
621
+ var merged = [];
622
+ var pendingSilence = null;
623
+
624
+ entryList.forEach(function(entry) {{
625
+ if (entry.type === 'silence') {{
626
+ if (pendingSilence === null) {{
627
+ pendingSilence = {{ type: 'silence', start: entry.start, end: entry.end }};
628
+ }} else {{
629
+ pendingSilence.end = entry.end;
630
+ }}
631
+ }} else {{
632
+ if (pendingSilence !== null) {{
633
+ merged.push(pendingSilence);
634
+ pendingSilence = null;
635
+ }}
636
+ merged.push(entry);
637
+ }}
638
+ }});
639
+
640
+ if (pendingSilence !== null) {{
641
+ merged.push(pendingSilence);
642
+ }}
643
+
644
+ return merged;
645
+ }}
646
+
647
+ entries = mergeConsecutiveSilences(entries);
648
+
649
+ var words = entries.filter(function(e) {{ return e.type === 'word'; }});
650
+ var silences = entries.filter(function(e) {{ return e.type === 'silence'; }});
651
+
652
+ function calculateAdjustedEnd(wordIndex) {{
653
+ var word = words[wordIndex];
654
+ var nextWord = words[wordIndex + 1];
655
+
656
+ if (!nextWord) return word.end;
657
+
658
+ var gap = nextWord.start - word.end;
659
+ var extension = Math.min(gap / 2, 0.12);
660
+
661
+ return word.end + extension;
662
+ }}
663
+
664
+ var adjustedEnds = words.map(function(w, i) {{ return calculateAdjustedEnd(i); }});
665
+
666
+ var lastClickedIndex = -1;
667
+
668
+ function getAllButtons() {{
669
+ return Array.from(container.querySelectorAll('.word-btn, .silence-btn'));
670
+ }}
671
+
672
+ function handleItemClick(btn, e) {{
673
+ var allBtns = getAllButtons();
674
+ var clickedIndex = allBtns.indexOf(btn);
675
+
676
+ if (e.shiftKey && lastClickedIndex >= 0) {{
677
+ var start = Math.min(lastClickedIndex, clickedIndex);
678
+ var end = Math.max(lastClickedIndex, clickedIndex);
679
+ allBtns.forEach(function(b, i) {{
680
+ if (i >= start && i <= end) {{
681
+ b.classList.add('selected');
682
+ }}
683
+ }});
684
+ }} else if (e.ctrlKey) {{
685
+ btn.classList.toggle('selected');
686
+ }} else {{
687
+ allBtns.forEach(function(b) {{ b.classList.remove('selected'); }});
688
+ btn.classList.add('selected');
689
+ }}
690
+
691
+ lastClickedIndex = clickedIndex;
692
+ updateInterval();
693
+ }}
694
+
695
+ var wordIndex = 0;
696
+ entries.forEach(function(entry, i) {{
697
+ var btn = document.createElement('span');
698
+
699
+ if (entry.type === 'word') {{
700
+ var wi = wordIndex;
701
+ btn.className = 'word-btn';
702
+ btn.dataset.origS = entry.start;
703
+ btn.dataset.origE = entry.end;
704
+ btn.dataset.adjE = adjustedEnds[wi];
705
+ btn.dataset.s = entry.start;
706
+ btn.dataset.e = entry.end;
707
+ btn.dataset.word = entry.word;
708
+ btn.innerHTML = '<span class="time">[' + entry.start.toFixed(3) + '-' + entry.end.toFixed(3) + 's]</span><span class="word"> ' + entry.word + '</span>';
709
+
710
+ btn.onclick = function(e) {{ handleItemClick(this, e); }};
711
+ wordIndex++;
712
+ }} else {{
713
+ btn.className = 'silence-btn';
714
+ btn.dataset.s = entry.start;
715
+ btn.dataset.e = entry.end;
716
+ var durationMs = Math.round((entry.end - entry.start) * 1000);
717
+ btn.innerHTML = '<span class="silence-time">[' + entry.start.toFixed(3) + '-' + entry.end.toFixed(3) + 's]</span><span class="duration">' + durationMs + 'ms</span>';
718
+ btn.onclick = function(e) {{ handleItemClick(this, e); }};
719
+ }}
720
+
721
+ container.appendChild(btn);
722
+
723
+ // Add vertical space after sentence-ending punctuation
724
+ if (entry.type === 'word') {{
725
+ var lastChar = entry.word.slice(-1);
726
+ if (lastChar === '.' || lastChar === '!' || lastChar === '?') {{
727
+ var spacer = document.createElement('div');
728
+ spacer.style.height = '15px';
729
+ container.appendChild(spacer);
730
+ }}
731
+ }}
732
+ }});
733
+
734
+ function updateWordLabels() {{
735
+ var adjusted = document.getElementById('adjust-intervals').checked;
736
+ document.querySelectorAll('.word-btn').forEach(function(btn) {{
737
+ var s = parseFloat(btn.dataset.origS);
738
+ var e = adjusted ? parseFloat(btn.dataset.adjE) : parseFloat(btn.dataset.origE);
739
+ btn.dataset.s = s;
740
+ btn.dataset.e = e;
741
+ btn.innerHTML = '<span class="time">[' + s.toFixed(3) + '-' + e.toFixed(3) + 's]</span><span class="word"> ' + btn.dataset.word + '</span>';
742
+ }});
743
+ updateInterval();
744
+ }}
745
+
746
+ document.getElementById('adjust-intervals').addEventListener('change', updateWordLabels);
747
+
748
+ function updateInterval() {{
749
+ var sel = Array.from(document.querySelectorAll('.word-btn.selected, .silence-btn.selected'));
750
+ if (sel.length === 0) return;
751
+
752
+ // Sort selected items by start time
753
+ sel.sort(function(a, b) {{
754
+ return parseFloat(a.dataset.s) - parseFloat(b.dataset.s);
755
+ }});
756
+
757
+ // Group into contiguous intervals based on DOM adjacency
758
+ var allBtns = getAllButtons();
759
+ var intervals = [];
760
+ var currentStart = null;
761
+ var currentEnd = null;
762
+ var lastIndex = -2;
763
+
764
+ sel.forEach(function(btn) {{
765
+ var idx = allBtns.indexOf(btn);
766
+ var s = parseFloat(btn.dataset.s);
767
+ var e = parseFloat(btn.dataset.e);
768
+
769
+ if (currentStart === null) {{
770
+ // First item
771
+ currentStart = s;
772
+ currentEnd = e;
773
+ lastIndex = idx;
774
+ }} else if (idx === lastIndex + 1) {{
775
+ // Adjacent item - extend current interval
776
+ currentEnd = e;
777
+ lastIndex = idx;
778
+ }} else {{
779
+ // Non-adjacent - save current interval, start new one
780
+ intervals.push(currentStart.toFixed(3) + '-' + currentEnd.toFixed(3));
781
+ currentStart = s;
782
+ currentEnd = e;
783
+ lastIndex = idx;
784
+ }}
785
+ }});
786
+
787
+ // Don't forget the last interval
788
+ if (currentStart !== null) {{
789
+ intervals.push(currentStart.toFixed(3) + '-' + currentEnd.toFixed(3));
790
+ }}
791
+
792
+ var intervalStr = intervals.join(' , ');
793
+ try {{
794
+ var boxes = parent.document.querySelectorAll('input[data-testid="textbox"], textarea');
795
+ boxes.forEach(function(box) {{
796
+ if (box.placeholder && box.placeholder.indexOf('start-end') !== -1) {{
797
+ box.value = intervalStr;
798
+ box.dispatchEvent(new Event('input', {{bubbles: true}}));
799
+ }}
800
+ }});
801
+ }} catch(err) {{ console.log('Could not update parent:', err); }}
802
+ }}
803
+
804
+ function highlightFromInterval(intervalStr) {{
805
+ if (!intervalStr) return;
806
+
807
+ // Parse multiple intervals separated by commas
808
+ var intervalParts = intervalStr.split(',');
809
+ var intervals = [];
810
+
811
+ intervalParts.forEach(function(part) {{
812
+ part = part.trim();
813
+ var match = part.match(/^([\d.]+)\s*-\s*([\d.]+)$/);
814
+ if (match) {{
815
+ var s = parseFloat(match[1]);
816
+ var e = parseFloat(match[2]);
817
+ if (!isNaN(s) && !isNaN(e)) {{
818
+ intervals.push({{start: s, end: e}});
819
+ }}
820
+ }}
821
+ }});
822
+
823
+ if (intervals.length === 0) return;
824
+
825
+ document.querySelectorAll('.word-btn').forEach(function(btn) {{
826
+ var ws = parseFloat(btn.dataset.s);
827
+ var we = parseFloat(btn.dataset.e);
828
+ var itemDuration = we - ws;
829
+ var isSelected = false;
830
+
831
+ // Check if this word overlaps with any interval
832
+ intervals.forEach(function(iv) {{
833
+ var overlapStart = Math.max(ws, iv.start);
834
+ var overlapEnd = Math.min(we, iv.end);
835
+ var overlap = Math.max(0, overlapEnd - overlapStart);
836
+ if (itemDuration > 0 && (overlap / itemDuration) > 0.5) {{
837
+ isSelected = true;
838
+ }}
839
+ }});
840
+
841
+ if (isSelected) {{
842
+ btn.classList.add('selected');
843
+ }} else {{
844
+ btn.classList.remove('selected');
845
+ }}
846
+ }});
847
+ }}
848
+
849
+ function setupParentWatcher() {{
850
+ try {{
851
+ var boxes = parent.document.querySelectorAll('input[data-testid="textbox"], textarea');
852
+ boxes.forEach(function(box) {{
853
+ if (box.placeholder && box.placeholder.indexOf('start-end') !== -1) {{
854
+ box.addEventListener('blur', function() {{
855
+ highlightFromInterval(this.value);
856
+ }});
857
+ box.addEventListener('keydown', function(e) {{
858
+ if (e.key === 'Enter') {{
859
+ highlightFromInterval(this.value);
860
+ }}
861
+ }});
862
+ }}
863
+ }});
864
+ }} catch(err) {{ console.log('Could not setup parent watcher:', err); }}
865
+ }}
866
+
867
+ setTimeout(setupParentWatcher, 500);
868
+
869
+ document.getElementById('download-json').onclick = function(e) {{
870
+ e.preventDefault();
871
+ var dataUrl = 'data:application/json;charset=utf-8,' + encodeURIComponent(exportJsonStr);
872
+ var a = document.createElement('a');
873
+ a.href = dataUrl;
874
+ a.download = 'transcript.json';
875
+ document.body.appendChild(a);
876
+ a.click();
877
+ document.body.removeChild(a);
878
+ return false;
879
+ }};
880
+ </script>
881
+ </body>
882
+ </html>
883
+ '''
884
+ iframe_srcdoc = iframe_html.replace('"', '&quot;')
885
+ return f'''
886
+ <iframe srcdoc="{iframe_srcdoc}" style="width: 100%; height: 250px; border: none; border-radius: 8px;"></iframe>
887
+ '''
888
+
889
+
890
+ with gr.Blocks() as demo:
891
+ gr.Markdown(
892
+ """# Timestamps Tester"""
893
+ )
894
+ gr.Markdown(
895
+ """Testing word timestamps from any transcript. Upload audio and a transcript JSON in one of the supported formats. See [examples of supported formats here](https://drive.google.com/drive/folders/1qrjfHjfssAZQIvSLJi36rLjpuqw3eOjj)."""
896
+ )
897
+
898
+ # State to store audio data in memory for fast extraction
899
+ audio_state = gr.State()
900
+ timestamps_state = gr.State([]) # Store timestamps for dropdown
901
+
902
+ audio_input = gr.Audio(
903
+ type="filepath",
904
+ label="Upload or record your audio",
905
+ sources=["upload", "microphone"],
906
+ format="wav"
907
+ )
908
+
909
+ # VAD Controls - inline labels with number inputs
910
+ with gr.Row():
911
+ gr.Markdown("**VAD: Probability Threshold**")
912
+ vad_prob_threshold = gr.Number(
913
+ show_label=False,
914
+ value=0.5,
915
+ minimum=0.0,
916
+ maximum=1.0,
917
+ step=0.05,
918
+ scale=0,
919
+ min_width=80
920
+ )
921
+ gr.Markdown("**Min Voice Off (ms)**")
922
+ vad_min_off = gr.Number(
923
+ show_label=False,
924
+ value=48,
925
+ minimum=16,
926
+ maximum=1000,
927
+ step=16,
928
+ scale=0,
929
+ min_width=80
930
+ )
931
+ gr.Markdown("**Min Voice On (ms)**")
932
+ vad_min_on = gr.Number(
933
+ show_label=False,
934
+ value=64,
935
+ minimum=16,
936
+ maximum=1000,
937
+ step=16,
938
+ scale=0,
939
+ min_width=80
940
+ )
941
+
942
+ transcript_file_input = gr.File(
943
+ label="Load Transcript (JSON)",
944
+ file_types=[".json"]
945
+ )
946
+
947
+ transcription_output = gr.Textbox(label="Transcription", lines=5)
948
+
949
+ timestamps_output = gr.HTML(label="Word Timestamps")
950
+
951
+ # Time interval input - directly under timestamps (single row, no label)
952
+ with gr.Row():
953
+ time_input = gr.Textbox(
954
+ label="",
955
+ show_label=False,
956
+ container=False,
957
+ placeholder="Time interval(s): start-end (e.g., 0.56-1.20 or 0.5-1.2 , 3.0-4.5)",
958
+ scale=3,
959
+ elem_id="time-interval-box"
960
+ )
961
+ play_interval_button = gr.Button("▶ Play Interval", scale=1)
962
+
963
+ # Track last played interval for smart replay
964
+ last_interval_state = gr.State("")
965
+
966
+ # Track current waveform window boundaries for smart redraw
967
+ waveform_window_state = gr.State(None)
968
+
969
+ # Waveform player - below interval controls
970
+ waveform_player = gr.HTML(label="Segment Player")
971
+
972
+ def load_transcript_and_setup(audio, transcript_file, prob_threshold, min_off_ms, min_on_ms):
973
+ """Load external transcript and setup UI."""
974
+ if transcript_file is None:
975
+ # Return empty/unchanged outputs if no file selected
976
+ return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
977
+
978
+ text, timestamps_data, audio_data, raw_text, export_metadata, silence_periods = load_transcript(
979
+ audio, transcript_file, prob_threshold, int(min_off_ms), int(min_on_ms)
980
+ )
981
+
982
+ # Check for errors
983
+ if audio_data is None:
984
+ # Error case - text contains error message
985
+ return text, "", None, [], gr.update()
986
+
987
+ # Build combined entries (words + silence) sorted by start time
988
+ entries = []
989
+ for item in timestamps_data:
990
+ entries.append({
991
+ 'type': 'word',
992
+ 'word': item['word'],
993
+ 'start': round(item['start'], 3),
994
+ 'end': round(item['end'], 3)
995
+ })
996
+ for item in silence_periods:
997
+ entries.append({
998
+ 'type': 'silence',
999
+ 'start': round(item['start'], 3),
1000
+ 'end': round(item['end'], 3)
1001
+ })
1002
+ entries.sort(key=lambda x: x['start'])
1003
+ entries_json = json.dumps(entries)
1004
+
1005
+ # Build word data as JSON for the iframe
1006
+ words_json = json.dumps([{
1007
+ 'word': item['word'],
1008
+ 'start': round(item['start'], 3),
1009
+ 'end': round(item['end'], 3)
1010
+ } for item in timestamps_data])
1011
+
1012
+ # Pre-generate full export JSON
1013
+ segments = [{
1014
+ 'word': item['word'],
1015
+ 'start': round(item['start'], 3),
1016
+ 'end': round(item['end'], 3),
1017
+ 'word_index': i
1018
+ } for i, item in enumerate(timestamps_data)]
1019
+
1020
+ export_data = {
1021
+ 'metadata': export_metadata,
1022
+ 'text': text,
1023
+ 'raw_text': raw_text,
1024
+ 'segments': segments
1025
+ }
1026
+ export_json_str = json.dumps(export_data, ensure_ascii=False, indent=2)
1027
+
1028
+ # Build iframe HTML using helper function
1029
+ timestamps_html = build_timestamps_iframe_html(entries_json, export_json_str)
1030
+
1031
+ initial_player = '''
1032
+ <div style="padding: 20px; text-align: center; background: #f5f5f5; border-radius: 8px; color: #666;">
1033
+ <p>Select words above and click <b>▶ Play Interval</b> to hear the segment</p>
1034
+ </div>
1035
+ '''
1036
+
1037
+ return text, timestamps_html, audio_data, timestamps_data, initial_player
1038
+
1039
+ def play_time_interval_fast(audio_state, time_interval, last_interval, current_window):
1040
+ """Fast extraction using preloaded audio from memory. Supports multiple intervals."""
1041
+ def wrap_error(msg):
1042
+ return f'<div style="min-height: 150px; padding: 20px; text-align: center; background: #f5f5f5; border-radius: 8px;"><p style="color: #666;">{msg}</p></div>', last_interval, current_window
1043
+
1044
+ try:
1045
+ if not time_interval or not audio_state:
1046
+ return wrap_error("No interval or audio loaded. Select words and try again.")
1047
+
1048
+ # Parse multiple intervals separated by commas
1049
+ # Format: "0.000-2.224 , 4.752-5.696 , 7.216-7.856"
1050
+ intervals = []
1051
+ interval_parts = time_interval.split(',')
1052
+
1053
+ for part in interval_parts:
1054
+ part = part.strip()
1055
+ if '-' in part:
1056
+ times = part.split('-')
1057
+ if len(times) == 2:
1058
+ try:
1059
+ start_time = float(times[0].strip())
1060
+ end_time = float(times[1].strip())
1061
+ if start_time < end_time:
1062
+ intervals.append((start_time, end_time))
1063
+ except ValueError:
1064
+ pass
1065
+
1066
+ if not intervals:
1067
+ return wrap_error("Invalid interval format. Use: start-end (e.g., 1.20-2.50) or multiple: 0.5-1.0 , 2.0-3.0")
1068
+
1069
+ # Load/reload audio segment (autoplay will replay even if same interval)
1070
+ # Pass current window state for smart redraw logic
1071
+ result_html, new_window = extract_audio_segment(audio_state, intervals, current_window)
1072
+ return result_html, time_interval, new_window
1073
+
1074
+ except Exception as e:
1075
+ return wrap_error(f"Error: {str(e)}")
1076
+
1077
+ # Load transcript file input
1078
+ transcript_file_input.change(
1079
+ fn=load_transcript_and_setup,
1080
+ inputs=[audio_input, transcript_file_input, vad_prob_threshold, vad_min_off, vad_min_on],
1081
+ outputs=[transcription_output, timestamps_output, audio_state, timestamps_state, waveform_player]
1082
+ )
1083
+
1084
+ # Play interval button
1085
+ play_interval_button.click(
1086
+ fn=play_time_interval_fast,
1087
+ inputs=[audio_state, time_input, last_interval_state, waveform_window_state],
1088
+ outputs=[waveform_player, last_interval_state, waveform_window_state]
1089
+ )
1090
+
1091
  demo.launch()
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ ffmpeg
2
+ libc++1
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ soundfile
2
+ matplotlib
3
+ ten-vad
runtime.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python-3.12
transcription_format_1.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"segments":[{"id":1,"seek":2470,"start":0.92,"end":5.86,"text":" With Canvid, every recording becomes smarter and easier to work with.","tokens":[50364,2022,1664,6833,11,633,6613,3643,20294,293,3571,281,589,365,13,50636],"temperature":0,"avg_logprob":-0.17021649568638902,"compression_ratio":1.6085271317829457,"no_speech_prob":0.37931445240974426,"words":[{"start":0.92,"end":1.76,"word":" With","probability":0.7823007702827454},{"start":1.76,"end":2.22,"word":" Canvid,","probability":0.7138348519802094},{"start":2.58,"end":2.94,"word":" every","probability":0.9812575578689575},{"start":2.94,"end":3.42,"word":" recording","probability":0.9973837733268738},{"start":3.42,"end":3.82,"word":" becomes","probability":0.9680418372154236},{"start":3.82,"end":4.32,"word":" smarter","probability":0.9817298650741577},{"start":4.32,"end":4.66,"word":" and","probability":0.9885828495025635},{"start":4.66,"end":5.08,"word":" easier","probability":0.9871406555175781},{"start":5.08,"end":5.42,"word":" to","probability":0.9985585808753967},{"start":5.42,"end":5.62,"word":" work","probability":0.9991933703422546},{"start":5.62,"end":5.86,"word":" with.","probability":0.9975357055664062}]},{"id":2,"seek":2470,"start":6.22,"end":10.62,"text":" It automatically generates a full transcript of your video, complete with time-accurate","tokens":[50636,467,6772,23815,257,1577,24444,295,428,960,11,3566,365,565,12,8476,33144,50852],"temperature":0,"avg_logprob":-0.17021649568638902,"compression_ratio":1.6085271317829457,"no_speech_prob":0.37931445240974426,"words":[{"start":6.22,"end":6.38,"word":" It","probability":0.985668420791626},{"start":6.38,"end":6.9,"word":" automatically","probability":0.9961374402046204},{"start":6.9,"end":7.6,"word":" generates","probability":0.992417573928833},{"start":7.6,"end":7.8,"word":" a","probability":0.9854802489280701},{"start":7.8,"end":7.96,"word":" full","probability":0.9979129433631897},{"start":7.96,"end":8.36,"word":" transcript","probability":0.9969101548194885},{"start":8.36,"end":8.7,"word":" of","probability":0.9967479705810547},{"start":8.7,"end":8.84,"word":" your","probability":0.992317795753479},{"start":8.84,"end":9.2,"word":" video,","probability":0.9946815371513367},{"start":9.32,"end":9.66,"word":" complete","probability":0.9576844573020935},{"start":9.66,"end":9.86,"word":" with","probability":0.9958441853523254},{"start":9.86,"end":10.18,"word":" time","probability":0.8104842305183411},{"start":10.18,"end":10.62,"word":"-accurate","probability":0.8353971242904663}]},{"id":3,"seek":2470,"start":10.62,"end":11.56,"text":" word timestamps.","tokens":[50852,1349,49108,23150,13,50930],"temperature":0,"avg_logprob":-0.17021649568638902,"compression_ratio":1.6085271317829457,"no_speech_prob":0.37931445240974426,"words":[{"start":10.62,"end":10.92,"word":" word","probability":0.7148622870445251},{"start":10.92,"end":11.56,"word":" timestamps.","probability":0.8938511908054352}]},{"id":4,"seek":2470,"start":12.14,"end":16.98,"text":" You can simply click any word to jump straight into that exact moment, perfect for precise","tokens":[50930,509,393,2935,2052,604,1349,281,3012,2997,666,300,1900,1623,11,2176,337,13600,51178],"temperature":0,"avg_logprob":-0.17021649568638902,"compression_ratio":1.6085271317829457,"no_speech_prob":0.37931445240974426,"words":[{"start":12.14,"end":12.24,"word":" You","probability":0.9906122088432312},{"start":12.24,"end":12.36,"word":" can","probability":0.9973468780517578},{"start":12.36,"end":12.66,"word":" simply","probability":0.9944220185279846},{"start":12.66,"end":12.92,"word":" click","probability":0.9955753087997437},{"start":12.92,"end":13.26,"word":" any","probability":0.9569084644317627},{"start":13.26,"end":13.56,"word":" word","probability":0.9966071844100952},{"start":13.56,"end":13.72,"word":" to","probability":0.993604838848114},{"start":13.72,"end":13.94,"word":" jump","probability":0.9978285431861877},{"start":13.94,"end":14.3,"word":" straight","probability":0.9646062850952148},{"start":14.3,"end":14.54,"word":" into","probability":0.990403413772583},{"start":14.54,"end":14.8,"word":" that","probability":0.984435498714447},{"start":14.8,"end":15.08,"word":" exact","probability":0.9961676001548767},{"start":15.08,"end":15.46,"word":" moment,","probability":0.9979813694953918},{"start":15.9,"end":16.26,"word":" perfect","probability":0.9824923276901245},{"start":16.26,"end":16.54,"word":" for","probability":0.9983140230178833},{"start":16.54,"end":16.98,"word":" precise","probability":0.996726393699646}]},{"id":5,"seek":2470,"start":16.98,"end":18.72,"text":" edits and effortless navigation.","tokens":[51178,41752,293,4630,1832,17346,13,51298],"temperature":0,"avg_logprob":-0.17021649568638902,"compression_ratio":1.6085271317829457,"no_speech_prob":0.37931445240974426,"words":[{"start":16.98,"end":17.48,"word":" edits","probability":0.9982725381851196},{"start":17.48,"end":17.7,"word":" and","probability":0.9857107996940613},{"start":17.7,"end":18.22,"word":" effortless","probability":0.9974649846553802},{"start":18.22,"end":18.72,"word":" navigation.","probability":0.9965900182723999}]},{"id":6,"seek":2470,"start":19.44,"end":24.34,"text":" Canvid's noise cancellation cleans up your audio by removing background noise and enhancing","tokens":[51298,1664,6833,311,5658,45867,16912,493,428,6278,538,12720,3678,5658,293,36579,51540],"temperature":0,"avg_logprob":-0.17021649568638902,"compression_ratio":1.6085271317829457,"no_speech_prob":0.37931445240974426,"words":[{"start":19.44,"end":20.08,"word":" Canvid's","probability":0.9171913067499796},{"start":20.08,"end":20.22,"word":" noise","probability":0.7357814311981201},{"start":20.22,"end":20.94,"word":" cancellation","probability":0.9655009508132935},{"start":20.94,"end":21.32,"word":" cleans","probability":0.9885962605476379},{"start":21.32,"end":21.52,"word":" up","probability":0.9915648698806763},{"start":21.52,"end":21.64,"word":" your","probability":0.9975951313972473},{"start":21.64,"end":22,"word":" audio","probability":0.9179521799087524},{"start":22,"end":22.22,"word":" by","probability":0.9946245551109314},{"start":22.22,"end":22.58,"word":" removing","probability":0.9975009560585022},{"start":22.58,"end":23.02,"word":" background","probability":0.99535071849823},{"start":23.02,"end":23.48,"word":" noise","probability":0.9975759387016296},{"start":23.48,"end":23.96,"word":" and","probability":0.946117639541626},{"start":23.96,"end":24.34,"word":" enhancing","probability":0.9908022284507751}]},{"id":7,"seek":2470,"start":24.34,"end":25.62,"text":" your voice in real time.","tokens":[51540,428,3177,294,957,565,13,51640],"temperature":0,"avg_logprob":-0.17021649568638902,"compression_ratio":1.6085271317829457,"no_speech_prob":0.37931445240974426,"words":[{"start":24.34,"end":24.56,"word":" your","probability":0.9974861145019531},{"start":24.56,"end":24.84,"word":" voice","probability":0.9988904595375061},{"start":24.84,"end":25.06,"word":" in","probability":0.9953886270523071},{"start":25.06,"end":25.28,"word":" real","probability":0.9973974227905273},{"start":25.28,"end":25.62,"word":" time.","probability":0.537711501121521}]},{"id":8,"seek":5468,"start":26.14,"end":31.06,"text":" You can also use AI background blur or full background removal to stay focused and fully","tokens":[50402,509,393,611,764,7318,3678,14257,420,1577,3678,17933,281,1754,5178,293,4498,50638],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":26.14,"end":26.44,"word":" You","probability":0.5414830446243286},{"start":26.44,"end":26.62,"word":" can","probability":0.9829677939414978},{"start":26.62,"end":26.84,"word":" also","probability":0.9638456702232361},{"start":26.84,"end":27.12,"word":" use","probability":0.9931013584136963},{"start":27.12,"end":27.44,"word":" AI","probability":0.9324751496315002},{"start":27.44,"end":27.86,"word":" background","probability":0.6952386498451233},{"start":27.86,"end":28.12,"word":" blur","probability":0.9974852800369263},{"start":28.12,"end":28.36,"word":" or","probability":0.9533637166023254},{"start":28.36,"end":28.56,"word":" full","probability":0.9764475226402283},{"start":28.56,"end":28.9,"word":" background","probability":0.9884167313575745},{"start":28.9,"end":29.32,"word":" removal","probability":0.9939894676208496},{"start":29.32,"end":29.76,"word":" to","probability":0.9747978448867798},{"start":29.76,"end":29.98,"word":" stay","probability":0.9970933198928833},{"start":29.98,"end":30.38,"word":" focused","probability":0.951511800289154},{"start":30.38,"end":30.82,"word":" and","probability":0.9859418272972107},{"start":30.82,"end":31.06,"word":" fully","probability":0.997599184513092}]},{"id":9,"seek":5468,"start":31.06,"end":32.6,"text":" immersed in your presentation.","tokens":[50638,35416,294,428,5860,13,50776],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":31.06,"end":31.44,"word":" immersed","probability":0.988663375377655},{"start":31.44,"end":31.78,"word":" in","probability":0.9948458671569824},{"start":31.78,"end":31.98,"word":" your","probability":0.9943114519119263},{"start":31.98,"end":32.6,"word":" presentation.","probability":0.9933533668518066}]},{"id":10,"seek":5468,"start":33.26,"end":36.16,"text":" And if you make a small mistake, no problem.","tokens":[50776,400,498,291,652,257,1359,6146,11,572,1154,13,50920],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":33.26,"end":33.82,"word":" And","probability":0.940563440322876},{"start":33.82,"end":34.16,"word":" if","probability":0.5281017422676086},{"start":34.16,"end":34.26,"word":" you","probability":0.9976662397384644},{"start":34.26,"end":34.44,"word":" make","probability":0.9972223043441772},{"start":34.44,"end":34.58,"word":" a","probability":0.995194137096405},{"start":34.58,"end":34.76,"word":" small","probability":0.9956401586532593},{"start":34.76,"end":35.12,"word":" mistake,","probability":0.9976315498352051},{"start":35.5,"end":35.76,"word":" no","probability":0.9710552096366882},{"start":35.76,"end":36.16,"word":" problem.","probability":0.9858748316764832}]},{"id":11,"seek":5468,"start":36.56,"end":40.86,"text":" With AI audio retakes, just highlight the part you want to fix, then record or type","tokens":[50920,2022,7318,6278,1533,3419,11,445,5078,264,644,291,528,281,3191,11,550,2136,420,2010,51126],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":36.56,"end":36.76,"word":" With","probability":0.9903624653816223},{"start":36.76,"end":37.1,"word":" AI","probability":0.9924909472465515},{"start":37.1,"end":37.46,"word":" audio","probability":0.8897120356559753},{"start":37.46,"end":38,"word":" retakes,","probability":0.9872439205646515},{"start":38.3,"end":38.42,"word":" just","probability":0.9755398631095886},{"start":38.42,"end":38.72,"word":" highlight","probability":0.9990407824516296},{"start":38.72,"end":38.94,"word":" the","probability":0.9959554672241211},{"start":38.94,"end":39.14,"word":" part","probability":0.9102027416229248},{"start":39.14,"end":39.3,"word":" you","probability":0.9910547137260437},{"start":39.3,"end":39.46,"word":" want","probability":0.9591130614280701},{"start":39.46,"end":39.56,"word":" to","probability":0.9967060685157776},{"start":39.56,"end":39.78,"word":" fix,","probability":0.9987348914146423},{"start":40,"end":40.2,"word":" then","probability":0.9834949374198914},{"start":40.2,"end":40.48,"word":" record","probability":0.9892204403877258},{"start":40.48,"end":40.64,"word":" or","probability":0.9840781688690186},{"start":40.64,"end":40.86,"word":" type","probability":0.9976154565811157}]},{"id":12,"seek":5468,"start":40.86,"end":41.36,"text":" your correction.","tokens":[51126,428,19984,13,51176],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":40.86,"end":40.98,"word":" your","probability":0.9869692325592041},{"start":40.98,"end":41.36,"word":" correction.","probability":0.9645764827728271}]},{"id":13,"seek":5468,"start":41.8,"end":43.82,"text":" No need to re-record the entire video.","tokens":[51176,883,643,281,319,12,38500,264,2302,960,13,51318],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":41.8,"end":42.02,"word":" No","probability":0.992559552192688},{"start":42.02,"end":42.16,"word":" need","probability":0.9987194538116455},{"start":42.16,"end":42.34,"word":" to","probability":0.9982567429542542},{"start":42.34,"end":42.5,"word":" re","probability":0.8223015666007996},{"start":42.5,"end":42.8,"word":"-record","probability":0.9892989993095398},{"start":42.8,"end":43.14,"word":" the","probability":0.9953472018241882},{"start":43.14,"end":43.44,"word":" entire","probability":0.9937765598297119},{"start":43.44,"end":43.82,"word":" video.","probability":0.9984459280967712}]},{"id":14,"seek":5468,"start":44.38,"end":50.04,"text":" Best of all, even without a live webcam, Canvid can create a realistic synthetic camera","tokens":[51318,9752,295,439,11,754,1553,257,1621,39490,11,1664,6833,393,1884,257,12465,23420,2799,51588],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":44.38,"end":44.64,"word":" Best","probability":0.9908847212791443},{"start":44.64,"end":44.82,"word":" of","probability":0.9984235763549805},{"start":44.82,"end":45.04,"word":" all,","probability":0.9989323019981384},{"start":45.32,"end":45.5,"word":" even","probability":0.9940084218978882},{"start":45.5,"end":45.88,"word":" without","probability":0.9899681806564331},{"start":45.88,"end":46.12,"word":" a","probability":0.7982197403907776},{"start":46.12,"end":46.32,"word":" live","probability":0.9628170728683472},{"start":46.32,"end":46.74,"word":" webcam,","probability":0.9965049028396606},{"start":47.3,"end":47.72,"word":" Canvid","probability":0.6870472431182861},{"start":47.72,"end":47.96,"word":" can","probability":0.9909351468086243},{"start":47.96,"end":48.32,"word":" create","probability":0.9938556551933289},{"start":48.32,"end":48.56,"word":" a","probability":0.9904357194900513},{"start":48.56,"end":48.92,"word":" realistic","probability":0.9882749319076538},{"start":48.92,"end":49.4,"word":" synthetic","probability":0.8551467061042786},{"start":49.4,"end":50.04,"word":" camera","probability":0.9845417737960815}]},{"id":15,"seek":5468,"start":50.04,"end":55.6,"text":" feed from your best previous recording, giving your videos a professional, personal touch","tokens":[51588,3154,490,428,1151,3894,6613,11,2902,428,2145,257,4843,11,2973,2557,51860],"temperature":0,"avg_logprob":-0.1665485081465348,"compression_ratio":1.6394557823129252,"no_speech_prob":0.1486709862947464,"words":[{"start":50.04,"end":50.36,"word":" feed","probability":0.9969772100448608},{"start":50.36,"end":50.88,"word":" from","probability":0.9819400906562805},{"start":50.88,"end":51.16,"word":" your","probability":0.9955233335494995},{"start":51.16,"end":51.52,"word":" best","probability":0.9430131912231445},{"start":51.52,"end":51.96,"word":" previous","probability":0.9610211849212646},{"start":51.96,"end":52.58,"word":" recording,","probability":0.9957404136657715},{"start":53,"end":53.3,"word":" giving","probability":0.9944908618927002},{"start":53.3,"end":53.48,"word":" your","probability":0.9938317537307739},{"start":53.48,"end":53.78,"word":" videos","probability":0.9474282264709473},{"start":53.78,"end":53.96,"word":" a","probability":0.9789287447929382},{"start":53.96,"end":54.4,"word":" professional,","probability":0.9963812828063965},{"start":54.78,"end":55.18,"word":" personal","probability":0.996509850025177},{"start":55.18,"end":55.6,"word":" touch","probability":0.9927807450294495}]},{"id":16,"seek":5851,"start":55.6,"end":56.32,"text":" every time.","tokens":[50368,633,565,13,50404],"temperature":0,"avg_logprob":-0.6175005435943604,"compression_ratio":0.5789473684210527,"no_speech_prob":0.3447488248348236,"words":[{"start":55.6,"end":55.98,"word":" every","probability":0.4578200876712799},{"start":55.98,"end":56.32,"word":" time.","probability":0.7729238867759705}]}]}
transcription_format_2.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"task":"transcribe","language":"en","text":"With Canvid, every recording becomes smarter and easier to work with. It automatically generates a full transcript of your video, complete with time-accurate word timestamps. You can simply click any word to jump straight into that exact moment, perfect for precise edits and effortless navigation. Canvid's noise cancellation cleans up your audio by removing background noise and enhancing your voice in real time. You can also use AI background blur or full background removal to stay focused and fully immersed in your presentation. And if you make a small mistake, no problem. With AI audio retakes, just highlight the part you want to fix, then record or type your correction. No need to rerecord the entire video. Best of all, even without a live webcam, Canvid can create a realistic synthetic camera feed from your best previous recording. giving your videos a professional, personal touch every time.","request_id":"64819cd3-863a-4d47-a01b-a56c9d16854f","words":[{"word":"With","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":1.654,"end":1.794,"retry_count":0},{"word":"Canvid,","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":1.834,"end":2.295,"retry_count":0},{"word":"every","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":2.715,"end":2.935,"retry_count":0},{"word":"recording","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":3.015,"end":3.536,"retry_count":0},{"word":"becomes","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":3.596,"end":3.936,"retry_count":0},{"word":"smarter","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":3.976,"end":4.416,"retry_count":0},{"word":"and","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":4.556,"end":4.676,"retry_count":0},{"word":"easier","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":4.817,"end":5.217,"retry_count":0},{"word":"to","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":5.297,"end":5.377,"retry_count":0},{"word":"work","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":5.457,"end":5.637,"retry_count":0},{"word":"with.","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":5.697,"end":5.897,"retry_count":0},{"word":"It","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":6.298,"end":6.378,"retry_count":0},{"word":"automatically","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":6.478,"end":7.138,"retry_count":0},{"word":"generates","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":7.219,"end":7.699,"retry_count":0},{"word":"a","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":7.739,"end":7.759,"retry_count":0},{"word":"full","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":7.819,"end":7.979,"retry_count":0},{"word":"transcript","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":8.039,"end":8.54,"retry_count":0},{"word":"of","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":8.62,"end":8.7,"retry_count":0},{"word":"your","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":8.74,"end":8.88,"retry_count":0},{"word":"video,","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":8.92,"end":9.2,"retry_count":0},{"word":"complete","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":9.34,"end":9.761,"retry_count":0},{"word":"with","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":9.781,"end":9.921,"retry_count":0},{"word":"time-accurate","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":9.961,"end":10.742,"retry_count":0},{"word":"word","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":10.802,"end":10.962,"retry_count":0},{"word":"timestamps.","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":11.002,"end":11.662,"retry_count":0},{"word":"You","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":12.163,"end":12.263,"retry_count":0},{"word":"can","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":12.263,"end":12.383,"retry_count":0},{"word":"simply","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":12.443,"end":12.743,"retry_count":0},{"word":"click","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":12.783,"end":12.983,"retry_count":0},{"word":"any","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":13.143,"end":13.284,"retry_count":0},{"word":"word","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":13.424,"end":13.624,"retry_count":0},{"word":"to","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":13.644,"end":13.704,"retry_count":0},{"word":"jump","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":13.784,"end":13.984,"retry_count":0},{"word":"straight","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":14.024,"end":14.364,"retry_count":0},{"word":"into","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":14.445,"end":14.585,"retry_count":0},{"word":"that","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":14.645,"end":14.805,"retry_count":0},{"word":"exact","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":14.865,"end":15.225,"retry_count":0},{"word":"moment,","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":15.285,"end":15.605,"retry_count":0},{"word":"perfect","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":15.986,"end":16.426,"retry_count":0},{"word":"for","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":16.446,"end":16.586,"retry_count":0},{"word":"precise","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":16.626,"end":17.147,"retry_count":0},{"word":"edits","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":17.247,"end":17.527,"retry_count":0},{"word":"and","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":17.627,"end":17.727,"retry_count":0},{"word":"effortless","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":17.807,"end":18.228,"retry_count":0},{"word":"navigation.","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":18.308,"end":18.968,"retry_count":0},{"word":"Canvid's","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":19.609,"end":20.049,"retry_count":0},{"word":"noise","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":20.089,"end":20.329,"retry_count":0},{"word":"cancellation","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":20.369,"end":21.07,"retry_count":0},{"word":"cleans","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":21.15,"end":21.43,"retry_count":0},{"word":"up","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":21.45,"end":21.53,"retry_count":0},{"word":"your","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":21.57,"end":21.691,"retry_count":0},{"word":"audio","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":21.771,"end":22.011,"retry_count":0},{"word":"by","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":22.131,"end":22.251,"retry_count":0},{"word":"removing","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":22.331,"end":22.771,"retry_count":0},{"word":"background","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":22.831,"end":23.252,"retry_count":0},{"word":"noise","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":23.312,"end":23.592,"retry_count":0},{"word":"and","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":23.892,"end":23.972,"retry_count":0},{"word":"enhancing","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":24.012,"end":24.493,"retry_count":0},{"word":"your","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":24.493,"end":24.613,"retry_count":0},{"word":"voice","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":24.633,"end":24.913,"retry_count":0},{"word":"in","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":24.993,"end":25.073,"retry_count":0},{"word":"real","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":25.073,"end":25.464,"retry_count":0},{"word":"time.","language":"en","probability":0.9532690923369119,"hallucination_score":0,"start":25.464,"end":25.854,"retry_count":0},{"word":"You","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":26.39,"end":26.49,"retry_count":0},{"word":"can","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":26.49,"end":26.61,"retry_count":0},{"word":"also","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":26.67,"end":26.87,"retry_count":0},{"word":"use","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":27.01,"end":27.17,"retry_count":0},{"word":"AI","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":27.27,"end":27.511,"retry_count":0},{"word":"background","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":27.57,"end":27.971,"retry_count":0},{"word":"blur","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":27.971,"end":28.191,"retry_count":0},{"word":"or","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":28.311,"end":28.371,"retry_count":0},{"word":"full","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":28.411,"end":28.591,"retry_count":0},{"word":"background","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":28.631,"end":29.011,"retry_count":0},{"word":"removal","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":29.031,"end":29.431,"retry_count":0},{"word":"to","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":29.692,"end":29.752,"retry_count":0},{"word":"stay","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":29.832,"end":30.032,"retry_count":0},{"word":"focused","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":30.112,"end":30.592,"retry_count":0},{"word":"and","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":30.732,"end":30.832,"retry_count":0},{"word":"fully","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":30.872,"end":31.092,"retry_count":0},{"word":"immersed","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":31.152,"end":31.572,"retry_count":0},{"word":"in","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":31.672,"end":31.752,"retry_count":0},{"word":"your","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":31.832,"end":32.013,"retry_count":0},{"word":"presentation.","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":32.073,"end":32.873,"retry_count":0},{"word":"And","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":33.633,"end":33.853,"retry_count":0},{"word":"if","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":34.074,"end":34.154,"retry_count":0},{"word":"you","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":34.194,"end":34.274,"retry_count":0},{"word":"make","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":34.314,"end":34.494,"retry_count":0},{"word":"a","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":34.534,"end":34.554,"retry_count":0},{"word":"small","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":34.594,"end":34.814,"retry_count":0},{"word":"mistake,","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":34.854,"end":35.234,"retry_count":0},{"word":"no","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":35.594,"end":35.694,"retry_count":0},{"word":"problem.","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":35.854,"end":36.255,"retry_count":0},{"word":"With","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":36.635,"end":36.775,"retry_count":0},{"word":"AI","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":36.875,"end":37.175,"retry_count":0},{"word":"audio","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":37.295,"end":37.515,"retry_count":0},{"word":"retakes,","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":37.595,"end":38.075,"retry_count":0},{"word":"just","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":38.295,"end":38.456,"retry_count":0},{"word":"highlight","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":38.495,"end":38.836,"retry_count":0},{"word":"the","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":38.856,"end":38.936,"retry_count":0},{"word":"part","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":38.976,"end":39.196,"retry_count":0},{"word":"you","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":39.236,"end":39.316,"retry_count":0},{"word":"want","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":39.336,"end":39.456,"retry_count":0},{"word":"to","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":39.476,"end":39.516,"retry_count":0},{"word":"fix,","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":39.576,"end":39.776,"retry_count":0},{"word":"then","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":40.096,"end":40.196,"retry_count":0},{"word":"record","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":40.236,"end":40.536,"retry_count":0},{"word":"or","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":40.576,"end":40.657,"retry_count":0},{"word":"type","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":40.676,"end":40.897,"retry_count":0},{"word":"your","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":40.897,"end":40.997,"retry_count":0},{"word":"correction.","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":41.017,"end":41.497,"retry_count":0},{"word":"No","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":41.877,"end":41.957,"retry_count":0},{"word":"need","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":42.077,"end":42.217,"retry_count":0},{"word":"to","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":42.257,"end":42.317,"retry_count":0},{"word":"rerecord","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":42.397,"end":42.898,"retry_count":0},{"word":"the","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":43.058,"end":43.118,"retry_count":0},{"word":"entire","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":43.178,"end":43.538,"retry_count":0},{"word":"video.","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":43.578,"end":43.818,"retry_count":0},{"word":"Best","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":44.498,"end":44.698,"retry_count":0},{"word":"of","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":44.738,"end":44.818,"retry_count":0},{"word":"all,","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":44.878,"end":45.038,"retry_count":0},{"word":"even","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":45.359,"end":45.559,"retry_count":0},{"word":"without","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":45.639,"end":45.979,"retry_count":0},{"word":"a","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":46.059,"end":46.079,"retry_count":0},{"word":"live","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":46.139,"end":46.419,"retry_count":0},{"word":"webcam,","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":46.459,"end":46.859,"retry_count":0},{"word":"Canvid","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":47.3,"end":47.78,"retry_count":0},{"word":"can","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":47.86,"end":47.98,"retry_count":0},{"word":"create","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":48.02,"end":48.42,"retry_count":0},{"word":"a","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":48.46,"end":48.48,"retry_count":0},{"word":"realistic","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":48.58,"end":49.14,"retry_count":0},{"word":"synthetic","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":49.18,"end":49.701,"retry_count":0},{"word":"camera","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":49.801,"end":50.101,"retry_count":0},{"word":"feed","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":50.201,"end":50.441,"retry_count":0},{"word":"from","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":50.701,"end":50.881,"retry_count":0},{"word":"your","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":51.001,"end":51.221,"retry_count":0},{"word":"best","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":51.361,"end":51.621,"retry_count":0},{"word":"previous","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":51.621,"end":52.162,"retry_count":0},{"word":"recording.","language":"en","probability":0.9611267416981295,"hallucination_score":0,"start":52.162,"end":52.702,"retry_count":0},{"word":"giving","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":53.123,"end":53.385,"retry_count":0},{"word":"your","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":53.385,"end":53.485,"retry_count":0},{"word":"videos","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":53.525,"end":53.867,"retry_count":0},{"word":"a","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":53.927,"end":53.947,"retry_count":0},{"word":"professional,","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":53.987,"end":54.57,"retry_count":0},{"word":"personal","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":54.851,"end":55.233,"retry_count":0},{"word":"touch","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":55.313,"end":55.614,"retry_count":0},{"word":"every","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":55.795,"end":55.996,"retry_count":0},{"word":"time.","language":"en","probability":0.9440868528107329,"hallucination_score":0,"start":55.996,"end":56.478,"retry_count":0}],"duration":59.4346875}
transcription_format_3.json ADDED
@@ -0,0 +1,899 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "text": "With CANVID, every recording becomes smarter and easier to work with. It automatically generates a full transcript of your video, complete with time -accurate word timestamps. You can simply click any word to jump straight into that exact moment, perfect for precise edits and effortless navigation. CANVID's noise cancellation cleans up your audio by removing background noise and enhancing your voice in real time. You can also use AI background blur or full background removal to stay focused and fully immersed in your presentation. And if you make a small mistake, no problem. With AI audio retakes, just highlight the part you want to fix, then record or type your correction. No need to re -record the entire video. Best of all, even without a live webcam, CANVID can create a realistic synthetic camera feed from your best previous recording, giving your videos a professional, personal touch every time.",
3
+ "segments": [
4
+ {
5
+ "start": 1.64,
6
+ "end": 1.8,
7
+ "text": "With",
8
+ "score": -0.08151979744434357
9
+ },
10
+ {
11
+ "start": 1.8,
12
+ "end": 2.5,
13
+ "text": "CANVID,",
14
+ "score": -0.610592782497406
15
+ },
16
+ {
17
+ "start": 2.5,
18
+ "end": 2.96,
19
+ "text": "every",
20
+ "score": -0.08097043633460999
21
+ },
22
+ {
23
+ "start": 2.96,
24
+ "end": 3.56,
25
+ "text": "recording",
26
+ "score": -0.05543423444032669
27
+ },
28
+ {
29
+ "start": 3.56,
30
+ "end": 3.94,
31
+ "text": "becomes",
32
+ "score": -2.1648306846618652
33
+ },
34
+ {
35
+ "start": 3.94,
36
+ "end": 4.48,
37
+ "text": "smarter",
38
+ "score": -0.387157678604126
39
+ },
40
+ {
41
+ "start": 4.48,
42
+ "end": 4.74,
43
+ "text": "and",
44
+ "score": -0.119212806224823
45
+ },
46
+ {
47
+ "start": 4.74,
48
+ "end": 5.24,
49
+ "text": "easier",
50
+ "score": -0.10670951753854752
51
+ },
52
+ {
53
+ "start": 5.24,
54
+ "end": 5.38,
55
+ "text": "to",
56
+ "score": -0.006726465187966824
57
+ },
58
+ {
59
+ "start": 5.38,
60
+ "end": 5.66,
61
+ "text": "work",
62
+ "score": -0.005453118588775396
63
+ },
64
+ {
65
+ "start": 5.66,
66
+ "end": 6.08,
67
+ "text": "with.",
68
+ "score": -0.1591891050338745
69
+ },
70
+ {
71
+ "start": 6.08,
72
+ "end": 6.42,
73
+ "text": "It",
74
+ "score": -0.01639869064092636
75
+ },
76
+ {
77
+ "start": 6.42,
78
+ "end": 7.16,
79
+ "text": "automatically",
80
+ "score": -1.1396892070770264
81
+ },
82
+ {
83
+ "start": 7.16,
84
+ "end": 7.7,
85
+ "text": "generates",
86
+ "score": -0.37663596868515015
87
+ },
88
+ {
89
+ "start": 7.7,
90
+ "end": 7.76,
91
+ "text": "a",
92
+ "score": -0.0067606340162456036
93
+ },
94
+ {
95
+ "start": 7.76,
96
+ "end": 8.0,
97
+ "text": "full",
98
+ "score": -0.7901870012283325
99
+ },
100
+ {
101
+ "start": 8.0,
102
+ "end": 8.56,
103
+ "text": "transcript",
104
+ "score": -0.6918596029281616
105
+ },
106
+ {
107
+ "start": 8.56,
108
+ "end": 8.7,
109
+ "text": "of",
110
+ "score": -0.008518156595528126
111
+ },
112
+ {
113
+ "start": 8.7,
114
+ "end": 8.88,
115
+ "text": "your",
116
+ "score": -0.07631964236497879
117
+ },
118
+ {
119
+ "start": 8.88,
120
+ "end": 9.26,
121
+ "text": "video,",
122
+ "score": -0.09540645778179169
123
+ },
124
+ {
125
+ "start": 9.26,
126
+ "end": 9.74,
127
+ "text": "complete",
128
+ "score": -0.4760415554046631
129
+ },
130
+ {
131
+ "start": 9.74,
132
+ "end": 9.92,
133
+ "text": "with",
134
+ "score": -0.17475254833698273
135
+ },
136
+ {
137
+ "start": 9.92,
138
+ "end": 10.26,
139
+ "text": "time",
140
+ "score": -0.12442675977945328
141
+ },
142
+ {
143
+ "start": 10.26,
144
+ "end": 10.74,
145
+ "text": "-accurate",
146
+ "score": -0.8242716789245605
147
+ },
148
+ {
149
+ "start": 10.74,
150
+ "end": 10.96,
151
+ "text": "word",
152
+ "score": -1.8274222612380981
153
+ },
154
+ {
155
+ "start": 10.96,
156
+ "end": 11.88,
157
+ "text": "timestamps.",
158
+ "score": -0.9761462211608887
159
+ },
160
+ {
161
+ "start": 11.88,
162
+ "end": 12.24,
163
+ "text": "You",
164
+ "score": -0.011501493863761425
165
+ },
166
+ {
167
+ "start": 12.24,
168
+ "end": 12.4,
169
+ "text": "can",
170
+ "score": -0.053069207817316055
171
+ },
172
+ {
173
+ "start": 12.4,
174
+ "end": 12.74,
175
+ "text": "simply",
176
+ "score": -1.0623061656951904
177
+ },
178
+ {
179
+ "start": 12.74,
180
+ "end": 13.04,
181
+ "text": "click",
182
+ "score": -0.16563200950622559
183
+ },
184
+ {
185
+ "start": 13.04,
186
+ "end": 13.32,
187
+ "text": "any",
188
+ "score": -0.05466760694980621
189
+ },
190
+ {
191
+ "start": 13.32,
192
+ "end": 13.6,
193
+ "text": "word",
194
+ "score": -0.0955428034067154
195
+ },
196
+ {
197
+ "start": 13.6,
198
+ "end": 13.72,
199
+ "text": "to",
200
+ "score": -0.012247011996805668
201
+ },
202
+ {
203
+ "start": 13.72,
204
+ "end": 13.98,
205
+ "text": "jump",
206
+ "score": -0.07913355529308319
207
+ },
208
+ {
209
+ "start": 13.98,
210
+ "end": 14.38,
211
+ "text": "straight",
212
+ "score": -4.182260513305664
213
+ },
214
+ {
215
+ "start": 14.38,
216
+ "end": 14.6,
217
+ "text": "into",
218
+ "score": -0.1293237805366516
219
+ },
220
+ {
221
+ "start": 14.6,
222
+ "end": 14.82,
223
+ "text": "that",
224
+ "score": -0.2660076916217804
225
+ },
226
+ {
227
+ "start": 14.82,
228
+ "end": 15.22,
229
+ "text": "exact",
230
+ "score": -0.03551250696182251
231
+ },
232
+ {
233
+ "start": 15.22,
234
+ "end": 15.78,
235
+ "text": "moment,",
236
+ "score": -0.1617247611284256
237
+ },
238
+ {
239
+ "start": 15.78,
240
+ "end": 16.4,
241
+ "text": "perfect",
242
+ "score": -0.13937878608703613
243
+ },
244
+ {
245
+ "start": 16.4,
246
+ "end": 16.58,
247
+ "text": "for",
248
+ "score": -0.015207158401608467
249
+ },
250
+ {
251
+ "start": 16.58,
252
+ "end": 17.18,
253
+ "text": "precise",
254
+ "score": -0.04675090312957764
255
+ },
256
+ {
257
+ "start": 17.18,
258
+ "end": 17.54,
259
+ "text": "edits",
260
+ "score": -2.1705334186553955
261
+ },
262
+ {
263
+ "start": 17.54,
264
+ "end": 17.74,
265
+ "text": "and",
266
+ "score": -0.04188178852200508
267
+ },
268
+ {
269
+ "start": 17.74,
270
+ "end": 18.24,
271
+ "text": "effortless",
272
+ "score": -0.9298043251037598
273
+ },
274
+ {
275
+ "start": 18.24,
276
+ "end": 19.26,
277
+ "text": "navigation.",
278
+ "score": -0.5522932410240173
279
+ },
280
+ {
281
+ "start": 19.26,
282
+ "end": 20.04,
283
+ "text": "CANVID's",
284
+ "score": -1.7257797718048096
285
+ },
286
+ {
287
+ "start": 20.04,
288
+ "end": 20.32,
289
+ "text": "noise",
290
+ "score": -1.081925392150879
291
+ },
292
+ {
293
+ "start": 20.32,
294
+ "end": 21.08,
295
+ "text": "cancellation",
296
+ "score": -0.7568238377571106
297
+ },
298
+ {
299
+ "start": 21.08,
300
+ "end": 21.4,
301
+ "text": "cleans",
302
+ "score": -0.24307142198085785
303
+ },
304
+ {
305
+ "start": 21.4,
306
+ "end": 21.52,
307
+ "text": "up",
308
+ "score": -0.04906478151679039
309
+ },
310
+ {
311
+ "start": 21.52,
312
+ "end": 21.7,
313
+ "text": "your",
314
+ "score": -0.760680079460144
315
+ },
316
+ {
317
+ "start": 21.7,
318
+ "end": 22.04,
319
+ "text": "audio",
320
+ "score": -3.6923694610595703
321
+ },
322
+ {
323
+ "start": 22.04,
324
+ "end": 22.26,
325
+ "text": "by",
326
+ "score": -0.010336088947951794
327
+ },
328
+ {
329
+ "start": 22.26,
330
+ "end": 22.76,
331
+ "text": "removing",
332
+ "score": -0.06585686653852463
333
+ },
334
+ {
335
+ "start": 22.76,
336
+ "end": 23.24,
337
+ "text": "background",
338
+ "score": -1.7343212366104126
339
+ },
340
+ {
341
+ "start": 23.24,
342
+ "end": 23.72,
343
+ "text": "noise",
344
+ "score": -0.8788498640060425
345
+ },
346
+ {
347
+ "start": 23.72,
348
+ "end": 23.96,
349
+ "text": "and",
350
+ "score": -0.03457999229431152
351
+ },
352
+ {
353
+ "start": 23.96,
354
+ "end": 24.46,
355
+ "text": "enhancing",
356
+ "score": -1.0168116092681885
357
+ },
358
+ {
359
+ "start": 24.46,
360
+ "end": 24.58,
361
+ "text": "your",
362
+ "score": -0.45429959893226624
363
+ },
364
+ {
365
+ "start": 24.58,
366
+ "end": 24.92,
367
+ "text": "voice",
368
+ "score": -0.021240152418613434
369
+ },
370
+ {
371
+ "start": 24.92,
372
+ "end": 25.06,
373
+ "text": "in",
374
+ "score": -0.01202479936182499
375
+ },
376
+ {
377
+ "start": 25.06,
378
+ "end": 25.34,
379
+ "text": "real",
380
+ "score": -0.012947533279657364
381
+ },
382
+ {
383
+ "start": 25.34,
384
+ "end": 26.06,
385
+ "text": "time.",
386
+ "score": -0.17728735506534576
387
+ },
388
+ {
389
+ "start": 26.06,
390
+ "end": 26.48,
391
+ "text": "You",
392
+ "score": -0.034842006862163544
393
+ },
394
+ {
395
+ "start": 26.48,
396
+ "end": 26.64,
397
+ "text": "can",
398
+ "score": -0.028295230120420456
399
+ },
400
+ {
401
+ "start": 26.64,
402
+ "end": 26.92,
403
+ "text": "also",
404
+ "score": -0.005661904811859131
405
+ },
406
+ {
407
+ "start": 26.92,
408
+ "end": 27.2,
409
+ "text": "use",
410
+ "score": -0.18630290031433105
411
+ },
412
+ {
413
+ "start": 27.2,
414
+ "end": 27.54,
415
+ "text": "AI",
416
+ "score": -1.145396113395691
417
+ },
418
+ {
419
+ "start": 27.54,
420
+ "end": 27.96,
421
+ "text": "background",
422
+ "score": -5.4883527755737305
423
+ },
424
+ {
425
+ "start": 27.96,
426
+ "end": 28.22,
427
+ "text": "blur",
428
+ "score": -6.778469562530518
429
+ },
430
+ {
431
+ "start": 28.22,
432
+ "end": 28.38,
433
+ "text": "or",
434
+ "score": -0.031012453138828278
435
+ },
436
+ {
437
+ "start": 28.38,
438
+ "end": 28.6,
439
+ "text": "full",
440
+ "score": -0.531334400177002
441
+ },
442
+ {
443
+ "start": 28.6,
444
+ "end": 29.0,
445
+ "text": "background",
446
+ "score": -4.3894829750061035
447
+ },
448
+ {
449
+ "start": 29.0,
450
+ "end": 29.54,
451
+ "text": "removal",
452
+ "score": -0.19233684241771698
453
+ },
454
+ {
455
+ "start": 29.54,
456
+ "end": 29.76,
457
+ "text": "to",
458
+ "score": -0.01742566004395485
459
+ },
460
+ {
461
+ "start": 29.76,
462
+ "end": 30.04,
463
+ "text": "stay",
464
+ "score": -0.05717923492193222
465
+ },
466
+ {
467
+ "start": 30.04,
468
+ "end": 30.64,
469
+ "text": "focused",
470
+ "score": -0.22116851806640625
471
+ },
472
+ {
473
+ "start": 30.64,
474
+ "end": 30.82,
475
+ "text": "and",
476
+ "score": -0.09630168229341507
477
+ },
478
+ {
479
+ "start": 30.82,
480
+ "end": 31.12,
481
+ "text": "fully",
482
+ "score": -0.08033610880374908
483
+ },
484
+ {
485
+ "start": 31.12,
486
+ "end": 31.6,
487
+ "text": "immersed",
488
+ "score": -10.911242485046387
489
+ },
490
+ {
491
+ "start": 31.6,
492
+ "end": 31.78,
493
+ "text": "in",
494
+ "score": -0.007256546523422003
495
+ },
496
+ {
497
+ "start": 31.78,
498
+ "end": 32.02,
499
+ "text": "your",
500
+ "score": -0.20859788358211517
501
+ },
502
+ {
503
+ "start": 32.02,
504
+ "end": 33.24,
505
+ "text": "presentation.",
506
+ "score": -0.0495038777589798
507
+ },
508
+ {
509
+ "start": 33.24,
510
+ "end": 33.94,
511
+ "text": "And",
512
+ "score": -0.022549446672201157
513
+ },
514
+ {
515
+ "start": 33.94,
516
+ "end": 34.16,
517
+ "text": "if",
518
+ "score": -0.0015337379882112145
519
+ },
520
+ {
521
+ "start": 34.16,
522
+ "end": 34.28,
523
+ "text": "you",
524
+ "score": -0.02903260476887226
525
+ },
526
+ {
527
+ "start": 34.28,
528
+ "end": 34.5,
529
+ "text": "make",
530
+ "score": -0.08994806557893753
531
+ },
532
+ {
533
+ "start": 34.5,
534
+ "end": 34.56,
535
+ "text": "a",
536
+ "score": -0.23658980429172516
537
+ },
538
+ {
539
+ "start": 34.56,
540
+ "end": 34.82,
541
+ "text": "small",
542
+ "score": -0.14645498991012573
543
+ },
544
+ {
545
+ "start": 34.82,
546
+ "end": 35.4,
547
+ "text": "mistake,",
548
+ "score": -0.18949539959430695
549
+ },
550
+ {
551
+ "start": 35.4,
552
+ "end": 35.76,
553
+ "text": "no",
554
+ "score": -0.017768744379281998
555
+ },
556
+ {
557
+ "start": 35.76,
558
+ "end": 36.44,
559
+ "text": "problem.",
560
+ "score": -0.05042130872607231
561
+ },
562
+ {
563
+ "start": 36.44,
564
+ "end": 36.8,
565
+ "text": "With",
566
+ "score": -0.1474059373140335
567
+ },
568
+ {
569
+ "start": 36.8,
570
+ "end": 37.22,
571
+ "text": "AI",
572
+ "score": -0.06544703245162964
573
+ },
574
+ {
575
+ "start": 37.22,
576
+ "end": 37.54,
577
+ "text": "audio",
578
+ "score": -13.273054122924805
579
+ },
580
+ {
581
+ "start": 37.54,
582
+ "end": 38.16,
583
+ "text": "retakes,",
584
+ "score": -0.5604587197303772
585
+ },
586
+ {
587
+ "start": 38.16,
588
+ "end": 38.46,
589
+ "text": "just",
590
+ "score": -0.9325882196426392
591
+ },
592
+ {
593
+ "start": 38.46,
594
+ "end": 38.82,
595
+ "text": "highlight",
596
+ "score": -2.3691790103912354
597
+ },
598
+ {
599
+ "start": 38.82,
600
+ "end": 38.94,
601
+ "text": "the",
602
+ "score": -3.0330090522766113
603
+ },
604
+ {
605
+ "start": 38.94,
606
+ "end": 39.18,
607
+ "text": "part",
608
+ "score": -0.06596189737319946
609
+ },
610
+ {
611
+ "start": 39.18,
612
+ "end": 39.28,
613
+ "text": "you",
614
+ "score": -0.24581721425056458
615
+ },
616
+ {
617
+ "start": 39.3,
618
+ "end": 39.44,
619
+ "text": "want",
620
+ "score": -20.85184097290039
621
+ },
622
+ {
623
+ "start": 39.44,
624
+ "end": 39.52,
625
+ "text": "to",
626
+ "score": -10.273269653320312
627
+ },
628
+ {
629
+ "start": 39.52,
630
+ "end": 39.9,
631
+ "text": "fix,",
632
+ "score": -1.9346206188201904
633
+ },
634
+ {
635
+ "start": 39.9,
636
+ "end": 40.2,
637
+ "text": "then",
638
+ "score": -0.6613729596138
639
+ },
640
+ {
641
+ "start": 40.2,
642
+ "end": 40.54,
643
+ "text": "record",
644
+ "score": -0.5808069705963135
645
+ },
646
+ {
647
+ "start": 40.54,
648
+ "end": 40.64,
649
+ "text": "or",
650
+ "score": -2.4585094451904297
651
+ },
652
+ {
653
+ "start": 40.64,
654
+ "end": 40.86,
655
+ "text": "type",
656
+ "score": -0.46192866563796997
657
+ },
658
+ {
659
+ "start": 40.88,
660
+ "end": 40.98,
661
+ "text": "your",
662
+ "score": -11.288326263427734
663
+ },
664
+ {
665
+ "start": 40.98,
666
+ "end": 41.66,
667
+ "text": "correction.",
668
+ "score": -0.29610687494277954
669
+ },
670
+ {
671
+ "start": 41.66,
672
+ "end": 42.0,
673
+ "text": "No",
674
+ "score": -0.011340784840285778
675
+ },
676
+ {
677
+ "start": 42.0,
678
+ "end": 42.22,
679
+ "text": "need",
680
+ "score": -0.24456846714019775
681
+ },
682
+ {
683
+ "start": 42.22,
684
+ "end": 42.34,
685
+ "text": "to",
686
+ "score": -0.08203698694705963
687
+ },
688
+ {
689
+ "start": 42.34,
690
+ "end": 42.48,
691
+ "text": "re",
692
+ "score": -0.013280842453241348
693
+ },
694
+ {
695
+ "start": 42.48,
696
+ "end": 42.96,
697
+ "text": "-record",
698
+ "score": -0.04457765817642212
699
+ },
700
+ {
701
+ "start": 42.96,
702
+ "end": 43.12,
703
+ "text": "the",
704
+ "score": -0.041737157851457596
705
+ },
706
+ {
707
+ "start": 43.12,
708
+ "end": 43.54,
709
+ "text": "entire",
710
+ "score": -0.9720736145973206
711
+ },
712
+ {
713
+ "start": 43.54,
714
+ "end": 44.14,
715
+ "text": "video.",
716
+ "score": -0.028474120423197746
717
+ },
718
+ {
719
+ "start": 44.14,
720
+ "end": 44.7,
721
+ "text": "Best",
722
+ "score": -0.5948593020439148
723
+ },
724
+ {
725
+ "start": 44.7,
726
+ "end": 44.84,
727
+ "text": "of",
728
+ "score": -0.007257105316966772
729
+ },
730
+ {
731
+ "start": 44.84,
732
+ "end": 45.18,
733
+ "text": "all,",
734
+ "score": -0.21771717071533203
735
+ },
736
+ {
737
+ "start": 45.18,
738
+ "end": 45.56,
739
+ "text": "even",
740
+ "score": -0.042969994246959686
741
+ },
742
+ {
743
+ "start": 45.56,
744
+ "end": 46.0,
745
+ "text": "without",
746
+ "score": -1.3420182466506958
747
+ },
748
+ {
749
+ "start": 46.0,
750
+ "end": 46.08,
751
+ "text": "a",
752
+ "score": -0.13044703006744385
753
+ },
754
+ {
755
+ "start": 46.08,
756
+ "end": 46.4,
757
+ "text": "live",
758
+ "score": -0.05106182396411896
759
+ },
760
+ {
761
+ "start": 46.4,
762
+ "end": 47.06,
763
+ "text": "webcam,",
764
+ "score": -5.140846252441406
765
+ },
766
+ {
767
+ "start": 47.06,
768
+ "end": 47.8,
769
+ "text": "CANVID",
770
+ "score": -0.5264605283737183
771
+ },
772
+ {
773
+ "start": 47.8,
774
+ "end": 47.98,
775
+ "text": "can",
776
+ "score": -0.5820391774177551
777
+ },
778
+ {
779
+ "start": 47.98,
780
+ "end": 48.42,
781
+ "text": "create",
782
+ "score": -0.2533574402332306
783
+ },
784
+ {
785
+ "start": 48.42,
786
+ "end": 48.5,
787
+ "text": "a",
788
+ "score": -0.009362409822642803
789
+ },
790
+ {
791
+ "start": 48.5,
792
+ "end": 49.12,
793
+ "text": "realistic",
794
+ "score": -0.3582625687122345
795
+ },
796
+ {
797
+ "start": 49.12,
798
+ "end": 49.72,
799
+ "text": "synthetic",
800
+ "score": -3.055950403213501
801
+ },
802
+ {
803
+ "start": 49.72,
804
+ "end": 50.12,
805
+ "text": "camera",
806
+ "score": -0.5360308289527893
807
+ },
808
+ {
809
+ "start": 50.12,
810
+ "end": 50.54,
811
+ "text": "feed",
812
+ "score": -0.047034528106451035
813
+ },
814
+ {
815
+ "start": 50.54,
816
+ "end": 50.92,
817
+ "text": "from",
818
+ "score": -0.00998612493276596
819
+ },
820
+ {
821
+ "start": 50.92,
822
+ "end": 51.26,
823
+ "text": "your",
824
+ "score": -1.1579375267028809
825
+ },
826
+ {
827
+ "start": 51.26,
828
+ "end": 51.64,
829
+ "text": "best",
830
+ "score": -0.008270579390227795
831
+ },
832
+ {
833
+ "start": 51.64,
834
+ "end": 52.08,
835
+ "text": "previous",
836
+ "score": -0.07053256779909134
837
+ },
838
+ {
839
+ "start": 52.08,
840
+ "end": 52.88,
841
+ "text": "recording,",
842
+ "score": -0.04316838085651398
843
+ },
844
+ {
845
+ "start": 52.88,
846
+ "end": 53.36,
847
+ "text": "giving",
848
+ "score": -0.05326300114393234
849
+ },
850
+ {
851
+ "start": 53.38,
852
+ "end": 53.5,
853
+ "text": "your",
854
+ "score": -0.6395745277404785
855
+ },
856
+ {
857
+ "start": 53.5,
858
+ "end": 53.88,
859
+ "text": "videos",
860
+ "score": -0.6830635666847229
861
+ },
862
+ {
863
+ "start": 53.88,
864
+ "end": 53.94,
865
+ "text": "a",
866
+ "score": -0.03618127107620239
867
+ },
868
+ {
869
+ "start": 53.94,
870
+ "end": 54.68,
871
+ "text": "professional,",
872
+ "score": -0.36633795499801636
873
+ },
874
+ {
875
+ "start": 54.68,
876
+ "end": 55.24,
877
+ "text": "personal",
878
+ "score": -0.6981226801872253
879
+ },
880
+ {
881
+ "start": 55.24,
882
+ "end": 55.68,
883
+ "text": "touch",
884
+ "score": -0.7542499899864197
885
+ },
886
+ {
887
+ "start": 55.68,
888
+ "end": 56.02,
889
+ "text": "every",
890
+ "score": -0.4383160471916199
891
+ },
892
+ {
893
+ "start": 56.02,
894
+ "end": 56.4,
895
+ "text": "time.",
896
+ "score": -0.043773457407951355
897
+ }
898
+ ]
899
+ }