abdullah090809 commited on
Commit
232ab6c
Β·
verified Β·
1 Parent(s): 3fe0ce0

Made changes to the UI

Browse files
Files changed (1) hide show
  1. app.py +1565 -512
app.py CHANGED
@@ -10,259 +10,1015 @@ import time
10
  import json
11
  import psutil
12
 
13
- st.set_page_config(layout="wide")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- # CSS Styling
16
  st.markdown("""
 
 
 
 
17
  <style>
18
- @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
 
20
  .stApp {
21
- background-color: #ffffff;
22
- font-family: 'Poppins', sans-serif;
23
- color: #1a1a1a;
24
- }
25
-
26
- /* Hide Streamlit's default elements */
27
- [data-testid="stToolbar"], [data-testid="stDecoration"], [data-testid="stStatusWidget"] {
28
- display: none;
29
- }
30
-
31
- /* Header */
32
- .header {
33
- background: #ffffff;
34
- padding: 1rem 2rem;
35
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
36
- display: flex;
37
- justify-content: space-between;
38
- align-items: center;
39
- position: sticky;
40
- top: 0;
41
- z-index: 100;
42
- }
43
- .logo img {
44
- height: 60px;
45
- width: auto;
46
- }
47
- .navbar {
48
- list-style: none;
49
- display: flex;
50
- gap: 1.5rem;
51
- margin: 0;
52
- }
53
- .navbar li a {
54
- text-decoration: none;
55
- font-size: 28px;
56
- font-weight: bold;
57
- color: #060404;
58
- position: relative;
59
- padding: 10px 15px;
60
- transition: text-shadow 0.3s ease-in-out;
61
- text-shadow: 5px 5px 12px rgba(0, 0, 0, 0.5);
62
- }
63
- .navbar li a:hover {
64
- color: #ff6f61;
65
- }
66
-
67
- /* Hero Section */
68
- .hero {
69
- background: linear-gradient(to right, #2b5876, #4e4376);
70
- background-size: cover;
71
- color: #ffffff;
72
- padding: 2rem 2rem;
73
- border-radius: 1rem;
74
- text-align: center;
75
- margin: 2rem 0;
76
- max-height: 200px;
77
- }
78
- .hero h1 {
79
- font-size: 2.5rem;
80
- font-weight: 700;
81
- margin-bottom: 0.5rem;
82
- }
83
- .hero p {
84
- font-size: 1.2rem;
85
- font-weight: 300;
86
- }
87
-
88
- /* Feature Section */
89
- .feature-box {
90
- display: flex;
91
- justify-content: center;
92
- gap: 1.5rem;
93
- margin: 3rem 0;
94
- flex-wrap: wrap;
95
- }
96
- .feature {
97
- background: #f8f9fa;
98
- padding: 1.5rem;
99
- border-radius: 1rem;
100
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
101
- width: 200px;
102
- text-align: center;
103
- transition: transform 0.3s ease, box-shadow 0.3s ease;
104
- }
105
- .feature:hover {
106
- transform: translateY(-8px) scale(1.03);
107
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25);
108
- transition: all 0.3s ease;
109
- border: 1px solid rgba(0, 0, 0, 0.1);
110
- background-color: #fff;
111
- filter: brightness(1.05);
112
- z-index: 10;
113
- }
114
- .feature i {
115
- font-size: 1.5rem;
116
- color: #2196f3;
117
- margin-bottom: 0.5rem;
118
- }
119
-
120
- /* Plans Section */
121
- .plans {
122
- padding: 3rem 2rem;
123
- background: #f1f4f8;
124
- border-radius: 1rem;
125
- }
126
- .plan-box {
127
- display: flex;
128
- justify-content: center;
129
- gap: 1.5rem;
130
- flex-wrap: wrap;
131
- }
132
- .plan {
133
- background: #ffffff;
134
- padding: 2rem;
135
- border-radius: 1rem;
136
- width: 250px;
137
- text-align: center;
138
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
139
- transition: transform 0.3s ease, box-shadow 0.3s ease;
140
- border-top: 4px solid #28a745;
141
- height: 290px;
142
- padding-top: 10px;
143
- }
144
- .plan:hover {
145
- transform: translateY(-5px);
146
- box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
147
- }
148
- .plan h3 {
149
- font-size: 1.5rem;
150
- margin-bottom: 0.5rem;
151
- }
152
- .plan.free { border-top: 4px solid #28a745; }
153
- .plan.premium { border-top: 4px solid #ff6f61; }
154
- .plan.business { border-top: 4px solid #2196f3; }
155
-
156
- /* Buttons */
157
- .stButton>button {
158
- background: linear-gradient(135deg, #ff6f61, #ff8a65) !important;
159
- color: #ffffff !important;
160
- font-weight: 600 !important;
161
- padding: 0.75rem 1.5rem !important;
162
- border-radius: 0.5rem !important;
163
- border: none !important;
164
- transition: transform 0.2s ease, box-shadow 0.2s ease !important;
165
- }
166
- .stButton>button:hover {
167
- transform: translateY(-2px) !important;
168
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2) !important;
169
- }
170
-
171
- /* File Uploader */
172
- .uploadedFile {
173
- border: 2px dashed #2196f3;
174
- border-radius: 1rem;
175
- padding: 2rem;
176
- background: #f8f9fa;
177
- margin: 2rem 0;
178
- }
179
-
180
- /* Progress Bar */
181
  .stProgress > div > div {
182
- background: linear-gradient(90deg, #2196f3, #4fc3f7) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  }
184
 
185
- /* Text Area */
186
- .stTextArea textarea {
187
- border-radius: 0.5rem;
188
- border: 1px solid #e0e0e0;
189
- padding: 1rem;
190
- font-family: 'Poppins', sans-serif;
191
  }
 
 
 
 
192
 
193
- /* Video player styling */
194
  video {
195
- display: block;
196
- width: 350px !important;
197
- height: 500px !important;
198
- object-fit: contain;
199
- margin: 0 auto;
200
- border: 3px solid #2196f3;
201
- border-radius: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  }
203
 
204
  /* Footer */
205
- footer {
206
- background: #1a1a1a;
207
- color: #ffffff;
208
- padding: 3rem 2rem;
209
- margin-top: 3rem;
210
- border-radius: 1rem 1rem 0 0;
211
- }
212
- .footer-container {
213
- display: flex;
214
- justify-content: space-around;
215
- gap: 2rem;
216
- flex-wrap: wrap;
217
- }
218
- .footer-section h4 {
219
- font-size: 1.8rem;
220
- margin-bottom: 1rem;
221
- }
222
- .footer-section ul {
223
- list-style: none;
224
- padding: 0;
225
- }
226
- .footer-section ul li a {
227
- color: #bbbbbb;
228
- text-decoration: none;
229
- font-size: 1.6rem;
230
- transition: color 0.3s ease;
231
- }
232
- .footer-section ul li a:hover {
233
- color: #ff6f61;
234
  }
 
 
 
 
 
 
 
 
 
235
  .footer-bottom {
236
- margin-top: 2rem;
237
- font-size: 0.9rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  }
239
 
240
- /* Responsive Design */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  @media (max-width: 768px) {
242
- .header {
243
- flex-direction: column;
244
- gap: 1rem;
245
- }
246
- .navbar {
247
- flex-direction: column;
248
- gap: 0.5rem;
249
- }
250
- .hero h1 {
251
- font-size: 1.8rem;
252
- }
253
- .hero p {
254
- font-size: 1rem;
255
- }
256
- .feature, .plan {
257
- width: 100%;
258
- max-width: 300px;
259
- }
260
  }
261
  </style>
262
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
263
  """, unsafe_allow_html=True)
264
 
265
- # Function Definitions
 
 
266
  def format_time(seconds):
267
  minutes = int(seconds // 60)
268
  secs = int(seconds % 60)
@@ -275,6 +1031,9 @@ def seconds_to_srt_time(seconds):
275
  millis = int((seconds - int(seconds)) * 1000)
276
  return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
277
 
 
 
 
278
  class TranscriptionProgress:
279
  def __init__(self):
280
  self.progress_bar = None
@@ -308,10 +1067,10 @@ def load_model(language='en', summarizer_type='bart'):
308
  sum_model = AutoModelForSeq2SeqLM.from_pretrained("pszemraj/led-large-book-summary").to(device)
309
  return processor, model, sum_tokenizer, sum_model, device
310
 
 
311
  def split_audio_into_chunks(audio, sr, chunk_duration):
312
  chunk_samples = int(chunk_duration * sr)
313
- chunks = [audio[start:start + chunk_samples] for start in range(0, len(audio), chunk_samples)]
314
- return chunks
315
 
316
  def transcribe_audio(audio, sr, processor, model, device, start_time, language, task="transcribe"):
317
  inputs = processor(audio, sampling_rate=sr, return_tensors="pt")
@@ -342,10 +1101,12 @@ def process_chunks(chunks, sr, processor, model, device, language, chunk_duratio
342
  if os.path.exists(transcript_file):
343
  os.remove(transcript_file)
344
  for i, chunk in enumerate(chunks):
345
- status_text.text(f"Processing chunk {i+1}/{total_chunks}...")
 
 
 
 
346
  try:
347
- memory = psutil.virtual_memory()
348
- st.write(f"Memory usage: {memory.percent}% (Chunk {i+1}/{total_chunks})")
349
  chunk_transcript = transcribe_audio(chunk, sr, processor, model, device, chunk_start, language, task)
350
  transcript.extend(chunk_transcript)
351
  with open(transcript_file, "w", encoding="utf-8") as f:
@@ -355,7 +1116,7 @@ def process_chunks(chunks, sr, processor, model, device, language, chunk_duratio
355
  except Exception as e:
356
  st.error(f"Error processing chunk {i+1}: {str(e)}")
357
  break
358
- status_text.text("Processing complete!")
359
  progress_bar.empty()
360
  return transcript
361
 
@@ -371,7 +1132,6 @@ def summarize_text(text, tokenizer, model, device, summarizer_type='bart'):
371
  inputs = tokenizer(text, return_tensors="pt", truncation=False)
372
  input_ids = inputs["input_ids"].to(device)
373
  num_tokens = input_ids.shape[1]
374
- st.write(f"Number of tokens in input: {num_tokens}")
375
  if num_tokens < 50:
376
  return "Transcript too short to summarize effectively."
377
  try:
@@ -382,7 +1142,6 @@ def summarize_text(text, tokenizer, model, device, summarizer_type='bart'):
382
  summary_ids = model.generate(truncated_inputs["input_ids"], num_beams=4, max_length=max_summary_length, min_length=50, early_stopping=True, temperature=0.7)
383
  summaries.append(tokenizer.decode(summary_ids[0], skip_special_tokens=True))
384
  else:
385
- st.write(f"Transcript exceeds {max_input_length} tokens. Processing in chunks...")
386
  tokens = input_ids[0].tolist()
387
  for i in range(0, num_tokens, chunk_size):
388
  chunk_tokens = tokens[i:i + chunk_size]
@@ -457,121 +1216,221 @@ def generate_srt(transcript, include_timeframe=True):
457
  srt_content += f"{text}\n\n"
458
  return srt_content
459
 
460
- # Main Function
 
 
461
  def main():
 
 
462
  st.markdown("""
463
- <div class="header">
464
- <div class="logo">
465
- <img src="https://i.postimg.cc/wvFfzx5h/VIDEpp.png">
466
- </div>
467
- <ul class="navbar">
 
 
468
  <li><a href="#home">Home</a></li>
469
- <li><a href="#upload">Upload Video</a></li>
470
- <li><a href="#about">About Us</a></li>
471
- <li><a href="#contact">Contact Us</a></li>
472
  </ul>
473
  </div>
474
  """, unsafe_allow_html=True)
475
 
 
 
476
  st.markdown("""
477
- <div id="home" class="hero">
478
- <h2>VidEp – Revolutionizing Video Subtitle Editing with AI</h2>
479
- <p>Upload, transcribe, edit subtitles, and summarize videos effortlessly.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  </div>
481
  """, unsafe_allow_html=True)
482
 
483
- # Initialize session state
484
- if 'app_state' not in st.session_state:
485
- st.session_state['app_state'] = 'upload'
486
- if 'video_path' not in st.session_state:
487
- st.session_state['video_path'] = None
488
- if 'primary_transcript' not in st.session_state:
489
- st.session_state['primary_transcript'] = None
490
- if 'english_transcript' not in st.session_state:
491
- st.session_state['english_transcript'] = None
492
- if 'english_summary' not in st.session_state:
493
- st.session_state['english_summary'] = None
494
- if 'language' not in st.session_state:
495
- st.session_state['language'] = None
496
- if 'language_code' not in st.session_state:
497
- st.session_state['language_code'] = None
498
- if 'translate_to_english' not in st.session_state:
499
- st.session_state['translate_to_english'] = False
500
- if 'summarizer_type' not in st.session_state:
501
- st.session_state['summarizer_type'] = None
502
- if 'summary_generated' not in st.session_state:
503
- st.session_state['summary_generated'] = False
504
- if 'current_time' not in st.session_state:
505
- st.session_state['current_time'] = 0
506
- if 'edited_video_path' not in st.session_state:
507
- st.session_state['edited_video_path'] = None
508
- if 'search_query' not in st.session_state:
509
- st.session_state['search_query'] = ""
510
- if 'show_timeframe' not in st.session_state:
511
- st.session_state['show_timeframe'] = True
512
 
 
513
  if st.session_state['app_state'] == 'upload':
514
- st.markdown("<div id='upload'></div>", unsafe_allow_html=True)
515
- st.markdown("<h3 style='text-align: center; color: black;'>Upload Your Video</h3>", unsafe_allow_html=True)
516
- with st.form(key="upload_form"):
517
- uploaded_file = st.file_uploader("Choose a video file", type=["mp4"], label_visibility="collapsed")
518
- if st.form_submit_button("Upload") and uploaded_file:
519
- video_path = save_uploaded_file(uploaded_file)
520
- if video_path:
521
- st.session_state['video_path'] = video_path
522
- st.session_state['app_state'] = 'processing'
523
- st.write(f"Uploaded file: {uploaded_file.name}")
524
- st.rerun()
525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  if st.session_state['app_state'] == 'processing':
527
- with st.form(key="processing_form"):
528
- language = st.selectbox("Select language", ["English", "Urdu"], key="language_select")
529
- language_code = "en" if language == "English" else "ur"
530
- st.session_state['language'] = language
531
- st.session_state['language_code'] = language_code
532
- chunk_duration = st.number_input("Duration per chunk (seconds):", min_value=1.0, step=0.1, value=10.0)
533
- if language_code == "ur":
534
- translate_to_english = st.checkbox("Generate English translation", key="translate_checkbox")
535
- st.session_state['translate_to_english'] = translate_to_english
536
- else:
537
- st.session_state['translate_to_english'] = False
538
- if st.form_submit_button("Process"):
539
- with st.spinner("Processing video..."):
540
- start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  try:
542
- st.write("Extracting audio...")
543
  audio_path = "processed_audio.wav"
544
- ffmpeg.input(st.session_state['video_path']).output(audio_path, ac=1, ar=16000).run(overwrite_output=True, quiet=True)
 
 
545
  audio, sr = librosa.load(audio_path, sr=16000)
546
  audio = np.nan_to_num(audio, nan=0.0, posinf=0.0, neginf=0.0)
547
  audio_duration = len(audio) / sr
548
- st.write(f"Audio duration: {audio_duration:.2f} seconds")
549
  if audio_duration < 5:
550
- st.error("Audio too short (< 5s). Upload a longer video.")
551
- return
 
552
  summarizer_type = 'bart' if audio_duration <= 300 else 'led'
553
- st.write(f"Using summarizer: {summarizer_type}")
554
  st.session_state['summarizer_type'] = summarizer_type
555
- st.write("Loading models...")
 
 
 
 
 
 
 
 
 
556
  processor, model, sum_tokenizer, sum_model, device = load_model(language_code, summarizer_type)
557
- st.write("Splitting audio into chunks...")
 
558
  chunks = split_audio_into_chunks(audio, sr, chunk_duration)
559
- st.write(f"Number of chunks: {len(chunks)}")
560
- st.write("Transcribing audio...")
561
- primary_transcript = process_chunks(chunks, sr, processor, model, device, language_code, chunk_duration, task="transcribe", transcript_file="temp_primary_transcript.json")
 
 
 
 
562
  english_transcript = None
563
  if st.session_state['translate_to_english'] and language_code == "ur":
564
- st.write("Translating to English...")
565
  processor, model, _, _, device = load_model('en', summarizer_type)
566
- english_transcript = process_chunks(chunks, sr, processor, model, device, 'ur', chunk_duration, task="translate", transcript_file="temp_english_transcript.json")
 
 
 
 
 
 
567
  st.session_state.update({
568
  'primary_transcript': primary_transcript,
569
  'english_transcript': english_transcript,
570
  'summary_generated': False,
571
  'app_state': 'results'
572
  })
573
- st.write("Processing completed successfully!")
 
574
  st.rerun()
 
575
  except Exception as e:
576
  st.error(f"Processing failed: {str(e)}")
577
  finally:
@@ -581,253 +1440,446 @@ def main():
581
  if os.path.exists(temp_file):
582
  os.remove(temp_file)
583
 
 
 
 
584
  if st.session_state['app_state'] == 'results':
585
- st.markdown('<div style="display: flex; justify-content: center;">', unsafe_allow_html=True)
586
- st.video(st.session_state['video_path'], start_time=st.session_state['current_time'])
587
- st.markdown('</div>', unsafe_allow_html=True)
588
-
589
- st.session_state['show_timeframe'] = st.checkbox("Show timeframe in transcript", value=st.session_state['show_timeframe'])
590
- st.markdown("### Search Subtitles")
591
-
592
- # Callback to handle search query updates
593
- def update_search_query():
594
- st.session_state['search_query'] = st.session_state.get('search_input', '').lower().strip()
595
-
596
- # Text input with on_change callback
597
- st.text_input("Search subtitles...", value=st.session_state['search_query'], key="search_input", on_change=update_search_query)
598
-
599
- # Primary Transcript
600
- st.markdown(f"### {st.session_state['language']} Transcript")
601
- primary_matches = 0
602
- for text, start, end in st.session_state['primary_transcript']:
603
- display_text = text.lower() # Case-insensitive comparison
604
- if not st.session_state['search_query'] or st.session_state['search_query'] in display_text:
605
- primary_matches += 1
606
- label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
607
- if st.button(label, key=f"primary_{start}"):
608
- st.session_state['current_time'] = start
609
- st.rerun()
610
- if primary_matches == 0 and st.session_state['search_query']:
611
- st.info("No matches found in primary transcript for the search query.")
612
-
613
- # English Transcript
614
- if st.session_state['english_transcript']:
615
- st.markdown("### English Translation")
616
- english_matches = 0
617
- for text, start, end in st.session_state['english_transcript']:
618
- display_text = text.lower() # Case-insensitive comparison
619
- if not st.session_state['search_query'] or st.session_state['search_query'] in display_text:
620
- english_matches += 1
621
- label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
622
- if st.button(label, key=f"english_{start}"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  st.session_state['current_time'] = start
624
  st.rerun()
625
- if english_matches == 0 and st.session_state['search_query']:
626
- st.info("No matches found in English transcript for the search query.")
627
 
628
- # Summary Generation
629
- if (st.session_state['language_code'] == 'en' or st.session_state['translate_to_english']) and not st.session_state['summary_generated']:
630
- if st.button("Generate Summary"):
631
- with st.spinner("Generating summary..."):
632
- try:
633
- _, _, sum_tokenizer, sum_model, device = load_model(st.session_state['language_code'], st.session_state['summarizer_type'])
634
- full_text = " ".join([text for text, _, _ in (st.session_state['english_transcript'] or st.session_state['primary_transcript'])])
635
- english_summary = summarize_text(full_text, sum_tokenizer, sum_model, device, st.session_state['summarizer_type'])
636
- st.session_state['english_summary'] = english_summary
637
- st.session_state['summary_generated'] = True
638
- except Exception as e:
639
- st.error(f"Summary generation failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
640
  if st.session_state['english_summary'] and st.session_state['summary_generated']:
641
- st.markdown("### Summary")
642
- st.write(st.session_state['english_summary'])
643
-
644
- # Download Subtitles
645
- st.markdown("### Download Subtitles")
646
- include_timeframe = st.checkbox("Include timeframe in subtitles", value=True)
647
- transcript_to_download = st.session_state['primary_transcript'] or st.session_state['english_transcript']
648
- if transcript_to_download:
649
- srt_content = generate_srt(transcript_to_download, include_timeframe)
650
- st.download_button(label="Download Subtitles (SRT)", data=srt_content, file_name="subtitles.srt", mime="text/plain")
651
-
652
- # Edit Subtitles
653
- st.markdown("### Edit Subtitles")
654
- transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
655
- if transcript_to_edit and st.button("Delete Subtitles"):
656
- st.session_state['app_state'] = 'editing'
657
- st.rerun()
658
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  if st.session_state['app_state'] == 'editing':
660
- st.markdown("### Delete Subtitles")
 
 
 
 
 
 
661
  transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
 
 
 
662
  for i, (text, start, end) in enumerate(transcript_to_edit):
663
- st.write(f"{i}: [{format_time(start)} - {format_time(end)}] {text}")
664
- indices_input = st.text_input("Enter the indices of subtitles to delete (comma-separated, e.g., 0,1,3):")
665
- if st.button("Confirm Deletion"):
666
- try:
667
- delete_indices = [int(idx.strip()) for idx in indices_input.split(',') if idx.strip()]
668
- delete_indices = [idx for idx in delete_indices if 0 <= idx < len(transcript_to_edit)]
669
- keep_indices = [i for i in range(len(transcript_to_edit)) if i not in delete_indices]
670
- if not keep_indices:
671
- st.error("All subtitles are deleted. No video to generate.")
672
- else:
673
- edited_video_path = create_edited_video(st.session_state['video_path'], transcript_to_edit, keep_indices)
674
- if edited_video_path:
675
- st.session_state['edited_video_path'] = edited_video_path
676
- st.session_state['app_state'] = 'results'
677
- st.rerun()
678
- except ValueError:
679
- st.error("Invalid input. Please enter comma-separated integers.")
680
- except Exception as e:
681
- st.error(f"Error during video editing: {str(e)}")
682
- if st.button("Cancel Deletion"):
683
- st.session_state['app_state'] = 'results'
684
- st.rerun()
685
-
686
- if st.session_state['app_state'] == 'results' and st.session_state['edited_video_path']:
687
- st.markdown("### Edited Video")
688
- st.markdown('<div style="display: flex; justify-content: center;">', unsafe_allow_html=True)
689
- st.video(st.session_state['edited_video_path'])
690
  st.markdown('</div>', unsafe_allow_html=True)
691
- with open(st.session_state['edited_video_path'], "rb") as file:
692
- st.download_button(label="Download Edited Video", data=file, file_name="edited_video.mp4", mime="video/mp4")
693
 
694
- if st.session_state.get('video_path') and st.button("Reset"):
695
- if st.session_state['video_path'] and os.path.exists(st.session_state['video_path']):
696
- os.remove(st.session_state['video_path'])
697
- if st.session_state['edited_video_path'] and os.path.exists(st.session_state['edited_video_path']):
698
- os.remove(st.session_state['edited_video_path'])
699
- st.session_state.clear()
700
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
702
  st.markdown("""
703
- <div style='text-align: center;'>
704
- <h2 style='color: black'>Why VidEp Stands Out</h2>
 
705
  </div>
706
- <div class="feature-box">
707
- <div class="feature"><i class="fas fa-cloud-upload-alt"></i><br>Cloud Upload</div>
708
- <div class="feature"><i class="fas fa-search"></i><br>Smart Search</div>
709
- <div class="feature"><i class="fas fa-edit"></i><br>Easy Editing</div>
710
- <div class="feature"><i class="fas fa-file-alt"></i><br>AI Summary</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
711
  </div>
712
  """, unsafe_allow_html=True)
713
 
 
 
714
  st.markdown("""
715
- <div id="about" class="about-section" style="padding: 3rem 2rem; background: #f8f9fa; border-radius: 1rem; margin: 2rem 0;">
716
- <h2 style="text-align: center; color: black; margin-bottom: 2rem;">About VidEp</h2>
717
- <div style="display: flex; align-items: center; gap: 2rem; flex-wrap: wrap;">
718
- <div style="flex: 1; min-width: 300px;">
719
- <img src="https://i.postimg.cc/g0z3WVgT/about.jpg" style="width: 100%; height: auto; border-radius: 1rem;" alt="About VidEp">
720
- </div>
721
- <div style="flex: 2; min-width: 300px;">
722
- <h3 style="color:grey;">Our Mission</h3>
723
- <p>VidEp aims to revolutionize how creators and professionals work with video content by providing state-of-the-art AI-powered tools for transcription, translation, and summarization.</p>
724
- <h3 style="color:grey;">What We Do</h3>
725
- <p>Our platform combines the latest advancements in speech recognition and natural language processing to automatically transcribe videos in multiple languages, generate accurate translations, and create concise summaries of content.</p>
726
- <h3 style="color:grey;">Why Choose Us</h3>
727
- <ul>
728
- <li>Advanced AI models for superior accuracy</li>
729
- <li>Multi-language support including English and Urdu</li>
730
- <li>Easy-to-use interface for editing and managing subtitles</li>
731
- <li>Smart search functionality to quickly find content</li>
732
- <li>Seamless video editing based on transcripts</li>
733
- </ul>
734
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
735
  </div>
736
  </div>
737
  """, unsafe_allow_html=True)
738
 
 
 
739
  st.markdown("""
740
- <div id="contact" class="contact-section" style="padding: 3rem 2rem; background: #f1f4f8; border-radius: 1rem; margin: 2rem 0;">
741
- <h2 style="text-align: center; color: black; margin-bottom: 2rem;">Contact Us</h2>
742
- <div style="max-width: 600px; margin: 0 auto;">
743
- <div style="margin-bottom: 1rem;">
744
- <label for="email" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">Email</label>
745
- <input type="email" id="email" placeholder="Your email address" style="width: 100%; padding: 0.75rem; border-radius: 0.5rem; border: 1px solid #e0e0e0;">
746
  </div>
747
- <div style="margin-bottom: 1rem;">
748
- <label for="message" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">Message</label>
749
- <textarea id="message" rows="5" placeholder="Your message" style="width: 100%; padding: 0.75rem; border-radius: 0.5rem; border: 1px solid #e0e0e0;"></textarea>
 
 
 
 
 
 
 
 
 
750
  </div>
751
- <button onclick="alert('Message sent successfully!')" style="background: linear-gradient(135deg, #ff6f61, #ff8a65); color: white; font-weight: 600; padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: none; cursor: pointer; width: 100%;">Send Message</button>
752
  </div>
753
  </div>
754
  """, unsafe_allow_html=True)
755
 
 
 
756
  st.markdown("""
757
- <div class="plans">
758
- <h2 style="text-align: center; margin-bottom: 2rem; color: black;">Choose Your Plan</h2>
759
- <div class="plan-box">
760
- <div class="plan free" style="background: linear-gradient(135deg, #299f45, #185726); padding-bottom: 0px">
761
- <h3 style="color: white;">Free</h3>
762
- <p><strong>$0</strong> / month</p>
763
- <p>Basic video transcription</p>
764
- <p>English only</p>
765
- <p>Max 5 minutes video</p>
766
- <p>No summarization</p>
767
- </div>
768
- <div class="plan premium" style="background-color:#a32b2d">
769
- <h3 style="color: white;">Premium</h3>
770
- <p><strong>$19</strong> / month</p>
771
- <p>Advanced transcription</p>
772
- <p>Multiple languages</p>
773
- <p>Max 30 minutes video</p>
774
- <p>AI summarization</p>
775
- </div>
776
- <div class="plan business" style="background-color:#396ca3">
777
- <h3 style="color: white;">Business</h3>
778
- <p><strong>$49</strong> / month</p>
779
- <p>Enterprise-grade transcription</p>
780
- <p>All languages</p>
781
- <p>Unlimited video length</p>
782
- </div>
783
  </div>
784
  </div>
785
  """, unsafe_allow_html=True)
786
 
 
787
  st.markdown("""
788
- <footer>
789
- <div class="footer-container">
790
- <div class="footer-section">
791
- <h4 style="margin-left:20px">Company Info</h4>
792
- <ul>
793
- <li><a href="#about-us">About Us</a></li>
794
- <li><a href="#privacy">Privacy Policy</a></li>
795
- <li><a href="#terms">Terms</a></li>
796
- </ul>
797
  </div>
798
- <div class="footer-section">
799
- <h4 style="margin-left:20px">Links</h4>
800
  <ul>
801
  <li><a href="#home">Home</a></li>
802
  <li><a href="#upload">Upload</a></li>
803
  <li><a href="#about">About</a></li>
804
- <li><a href="#contact">Contact</a></li>
805
  </ul>
806
  </div>
807
- <div class="footer-section">
808
- <h4 style="margin-left:20px">Legal</h4>
809
  <ul>
810
  <li><a href="#">Terms of Service</a></li>
811
  <li><a href="#">Privacy Policy</a></li>
812
  <li><a href="#">Cookie Policy</a></li>
813
  </ul>
814
  </div>
 
 
 
 
 
 
 
 
815
  </div>
816
- <div class="footer-bottom" style="justify-content: center; text-align: center; border-top: 1px solid white; padding-top:20px; padding-bottom: 10px;">
817
- <p style="font-size: 20px">Β© 2025 VidEp. All rights reserved.</p>
 
818
  </div>
819
- </footer>
820
 
821
  <script>
822
  document.addEventListener('DOMContentLoaded', function() {
823
- const navLinks = document.querySelectorAll('.navbar a');
824
- navLinks.forEach(link => {
825
- link.addEventListener('click', function(e) {
826
- e.preventDefault();
827
- const targetId = this.getAttribute('href');
828
- const targetElement = document.querySelector(targetId);
829
- if (targetElement) {
830
- targetElement.scrollIntoView({behavior: 'smooth'});
831
  }
832
  });
833
  });
@@ -835,5 +1887,6 @@ def main():
835
  </script>
836
  """, unsafe_allow_html=True)
837
 
 
838
  if __name__ == "__main__":
839
  main()
 
10
  import json
11
  import psutil
12
 
13
+ st.set_page_config(
14
+ layout="wide",
15
+ page_title="VidEp β€” AI Video Intelligence",
16
+ page_icon="🎬",
17
+ initial_sidebar_state="collapsed"
18
+ )
19
+
20
+ # ─── DESIGN SYSTEM ────────────────────────────────────────────────────────────
21
+ # Palette:
22
+ # --bg-base: #0d1117 (near-black navy)
23
+ # --bg-surface: #161b22 (card surface)
24
+ # --bg-elevated: #21262d (elevated / hover)
25
+ # --border: #30363d (subtle borders)
26
+ # --accent: #3b82f6 (electric blue)
27
+ # --accent-light: #60a5fa
28
+ # --accent-glow: rgba(59,130,246,0.15)
29
+ # --text-primary: #f0f6fc
30
+ # --text-secondary:#8b949e
31
+ # --text-muted: #484f58
32
+ # --success: #3fb950
33
+ # --warning: #d29922
34
+ # --danger: #f85149
35
+ # Typography: Inter (Google Fonts)
36
+ # Signature: animated waveform SVG in hero
37
 
 
38
  st.markdown("""
39
+ <link rel="preconnect" href="https://fonts.googleapis.com">
40
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
41
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
42
+
43
  <style>
44
+ /* ─── RESET & BASE ─────────────────────────────────────── */
45
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
46
+
47
+ :root {
48
+ --bg-base: #0d1117;
49
+ --bg-surface: #161b22;
50
+ --bg-elevated: #21262d;
51
+ --border: #30363d;
52
+ --border-light: #21262d;
53
+ --accent: #3b82f6;
54
+ --accent-light: #60a5fa;
55
+ --accent-dim: #1d4ed8;
56
+ --accent-glow: rgba(59,130,246,0.15);
57
+ --accent-glow2: rgba(59,130,246,0.08);
58
+ --text-primary: #f0f6fc;
59
+ --text-secondary:#8b949e;
60
+ --text-muted: #484f58;
61
+ --success: #3fb950;
62
+ --success-glow: rgba(63,185,80,0.15);
63
+ --warning: #d29922;
64
+ --danger: #f85149;
65
+ --danger-glow: rgba(248,81,73,0.15);
66
+ --radius-sm: 6px;
67
+ --radius-md: 10px;
68
+ --radius-lg: 16px;
69
+ --radius-xl: 24px;
70
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.4);
71
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.5);
72
+ --shadow-lg: 0 8px 32px rgba(0,0,0,0.6);
73
+ --font: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
74
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
75
+ }
76
 
77
+ /* ─── STREAMLIT OVERRIDES ──────────────────────────────── */
78
  .stApp {
79
+ background: var(--bg-base) !important;
80
+ font-family: var(--font) !important;
81
+ color: var(--text-primary) !important;
82
+ }
83
+ .stApp > header { display: none !important; }
84
+ [data-testid="stToolbar"],
85
+ [data-testid="stDecoration"],
86
+ [data-testid="stStatusWidget"],
87
+ [data-testid="collapsedControl"],
88
+ #MainMenu { display: none !important; }
89
+ [data-testid="stSidebar"] { display: none !important; }
90
+ .block-container {
91
+ max-width: 1200px !important;
92
+ padding: 0 24px 80px !important;
93
+ margin: 0 auto !important;
94
+ }
95
+ section[data-testid="stFileUploadDropzone"] {
96
+ background: var(--bg-surface) !important;
97
+ border: 2px dashed var(--border) !important;
98
+ border-radius: var(--radius-lg) !important;
99
+ transition: border-color 0.2s, background 0.2s !important;
100
+ }
101
+ section[data-testid="stFileUploadDropzone"]:hover {
102
+ border-color: var(--accent) !important;
103
+ background: var(--accent-glow2) !important;
104
+ }
105
+ .stTextInput > div > div > input,
106
+ .stNumberInput > div > div > input,
107
+ .stSelectbox > div > div {
108
+ background: var(--bg-surface) !important;
109
+ border: 1px solid var(--border) !important;
110
+ border-radius: var(--radius-md) !important;
111
+ color: var(--text-primary) !important;
112
+ font-family: var(--font) !important;
113
+ font-size: 14px !important;
114
+ padding: 10px 14px !important;
115
+ transition: border-color 0.2s, box-shadow 0.2s !important;
116
+ }
117
+ .stTextInput > div > div > input:focus,
118
+ .stNumberInput > div > div > input:focus {
119
+ border-color: var(--accent) !important;
120
+ box-shadow: 0 0 0 3px var(--accent-glow) !important;
121
+ outline: none !important;
122
+ }
123
+ .stTextInput label, .stNumberInput label,
124
+ .stSelectbox label, .stCheckbox label,
125
+ .stFileUploader label {
126
+ color: var(--text-secondary) !important;
127
+ font-size: 13px !important;
128
+ font-weight: 500 !important;
129
+ letter-spacing: 0.02em !important;
130
+ text-transform: uppercase !important;
131
+ margin-bottom: 6px !important;
132
+ }
133
+ .stCheckbox > label {
134
+ text-transform: none !important;
135
+ font-size: 14px !important;
136
+ color: var(--text-primary) !important;
137
+ }
138
+ .stCheckbox > label > span { color: var(--text-primary) !important; }
139
+ [data-testid="stCheckbox"] span { color: var(--text-primary) !important; }
140
+
141
+ /* ─── BUTTONS ──────────────────────────────────────────── */
142
+ .stButton > button {
143
+ background: var(--accent) !important;
144
+ color: #fff !important;
145
+ font-family: var(--font) !important;
146
+ font-weight: 600 !important;
147
+ font-size: 14px !important;
148
+ padding: 10px 20px !important;
149
+ border-radius: var(--radius-md) !important;
150
+ border: none !important;
151
+ letter-spacing: 0.01em !important;
152
+ cursor: pointer !important;
153
+ transition: background 0.15s, transform 0.1s, box-shadow 0.15s !important;
154
+ box-shadow: 0 1px 4px rgba(59,130,246,0.3) !important;
155
+ white-space: nowrap !important;
156
+ }
157
+ .stButton > button:hover {
158
+ background: var(--accent-light) !important;
159
+ transform: translateY(-1px) !important;
160
+ box-shadow: 0 4px 16px rgba(59,130,246,0.4) !important;
161
+ }
162
+ .stButton > button:active {
163
+ transform: translateY(0) !important;
164
+ box-shadow: none !important;
165
+ }
166
+ /* Ghost/secondary variant via class trick on wrapping div */
167
+ .btn-ghost .stButton > button {
168
+ background: transparent !important;
169
+ border: 1px solid var(--border) !important;
170
+ color: var(--text-secondary) !important;
171
+ box-shadow: none !important;
172
+ }
173
+ .btn-ghost .stButton > button:hover {
174
+ border-color: var(--accent) !important;
175
+ color: var(--accent-light) !important;
176
+ background: var(--accent-glow2) !important;
177
+ }
178
+ .btn-danger .stButton > button {
179
+ background: transparent !important;
180
+ border: 1px solid var(--danger) !important;
181
+ color: var(--danger) !important;
182
+ box-shadow: none !important;
183
+ }
184
+ .btn-danger .stButton > button:hover {
185
+ background: var(--danger-glow) !important;
186
+ transform: translateY(-1px) !important;
187
+ }
188
+
189
+ /* ─── TRANSCRIPT SUBTITLE BUTTONS ──────────────────────── */
190
+ div[data-testid="stVerticalBlock"] > div > div > .stButton > button {
191
+ background: var(--bg-surface) !important;
192
+ border: 1px solid var(--border) !important;
193
+ color: var(--text-primary) !important;
194
+ font-weight: 400 !important;
195
+ font-size: 13px !important;
196
+ text-align: left !important;
197
+ width: 100% !important;
198
+ padding: 12px 16px !important;
199
+ border-radius: var(--radius-md) !important;
200
+ box-shadow: none !important;
201
+ transition: border-color 0.15s, background 0.15s !important;
202
+ font-family: var(--font-mono) !important;
203
+ }
204
+ div[data-testid="stVerticalBlock"] > div > div > .stButton > button:hover {
205
+ background: var(--bg-elevated) !important;
206
+ border-color: var(--accent) !important;
207
+ color: var(--accent-light) !important;
208
+ transform: none !important;
209
+ }
210
+
211
+ /* ─── PROGRESS BAR ─────────────────────────────────────── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  .stProgress > div > div {
213
+ background: linear-gradient(90deg, var(--accent), var(--accent-light)) !important;
214
+ border-radius: 99px !important;
215
+ }
216
+ .stProgress > div {
217
+ background: var(--bg-elevated) !important;
218
+ border-radius: 99px !important;
219
+ }
220
+
221
+ /* ─── SPINNER ──────────────────────────────────────────── */
222
+ .stSpinner > div { border-top-color: var(--accent) !important; }
223
+
224
+ /* ─── SELECT BOX ───────────────────────────────────────── */
225
+ [data-testid="stSelectbox"] > div > div {
226
+ background: var(--bg-surface) !important;
227
+ border-color: var(--border) !important;
228
+ color: var(--text-primary) !important;
229
+ }
230
+ [data-testid="stSelectbox"] svg { fill: var(--text-secondary) !important; }
231
+
232
+ /* ─── DOWNLOAD BUTTON ──────────────────────────────────── */
233
+ .stDownloadButton > button {
234
+ background: transparent !important;
235
+ border: 1px solid var(--success) !important;
236
+ color: var(--success) !important;
237
+ font-family: var(--font) !important;
238
+ font-weight: 600 !important;
239
+ font-size: 13px !important;
240
+ padding: 9px 18px !important;
241
+ border-radius: var(--radius-md) !important;
242
+ cursor: pointer !important;
243
+ transition: background 0.15s, box-shadow 0.15s !important;
244
+ }
245
+ .stDownloadButton > button:hover {
246
+ background: var(--success-glow) !important;
247
+ box-shadow: 0 0 12px var(--success-glow) !important;
248
  }
249
 
250
+ /* ─── ALERTS / INFO ────────────────────────────────────── */
251
+ .stAlert {
252
+ background: var(--bg-surface) !important;
253
+ border-radius: var(--radius-md) !important;
254
+ border: 1px solid var(--border) !important;
255
+ color: var(--text-primary) !important;
256
  }
257
+ [data-testid="stInfo"] { border-left: 3px solid var(--accent) !important; }
258
+ [data-testid="stSuccess"] { border-left: 3px solid var(--success) !important; }
259
+ [data-testid="stError"] { border-left: 3px solid var(--danger) !important; }
260
+ [data-testid="stWarning"] { border-left: 3px solid var(--warning) !important; }
261
 
262
+ /* ─── VIDEO ────────────────────────────────────────────── */
263
  video {
264
+ border-radius: var(--radius-lg) !important;
265
+ border: 1px solid var(--border) !important;
266
+ max-width: 100% !important;
267
+ }
268
+
269
+ /* ─── FORM ─────────────────────────────────────────────── */
270
+ [data-testid="stForm"] {
271
+ background: var(--bg-surface) !important;
272
+ border: 1px solid var(--border) !important;
273
+ border-radius: var(--radius-xl) !important;
274
+ padding: 32px !important;
275
+ }
276
+
277
+ /* ─── MARKDOWN HEADINGS ─────────────────────────────────── */
278
+ .stMarkdown h1, .stMarkdown h2, .stMarkdown h3 {
279
+ color: var(--text-primary) !important;
280
+ font-family: var(--font) !important;
281
+ }
282
+
283
+ /* ─── CUSTOM COMPONENTS ─────────────────────────────────── */
284
+
285
+ /* Topnav */
286
+ .videp-nav {
287
+ display: flex;
288
+ align-items: center;
289
+ justify-content: space-between;
290
+ padding: 0 0 0 0;
291
+ height: 60px;
292
+ border-bottom: 1px solid var(--border);
293
+ margin-bottom: 0;
294
+ position: sticky;
295
+ top: 0;
296
+ z-index: 200;
297
+ background: rgba(13,17,23,0.92);
298
+ backdrop-filter: blur(12px);
299
+ -webkit-backdrop-filter: blur(12px);
300
+ }
301
+ .videp-logo {
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 10px;
305
+ text-decoration: none;
306
+ }
307
+ .videp-logo-icon {
308
+ width: 34px;
309
+ height: 34px;
310
+ background: linear-gradient(135deg, var(--accent), #8b5cf6);
311
+ border-radius: 8px;
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ font-size: 16px;
316
+ }
317
+ .videp-logo-text {
318
+ font-size: 17px;
319
+ font-weight: 700;
320
+ color: var(--text-primary);
321
+ letter-spacing: -0.01em;
322
+ }
323
+ .videp-logo-badge {
324
+ font-size: 10px;
325
+ font-weight: 600;
326
+ background: var(--accent-glow);
327
+ color: var(--accent-light);
328
+ border: 1px solid rgba(59,130,246,0.3);
329
+ border-radius: 4px;
330
+ padding: 2px 6px;
331
+ letter-spacing: 0.05em;
332
+ }
333
+ .videp-nav-links {
334
+ display: flex;
335
+ gap: 4px;
336
+ list-style: none;
337
+ }
338
+ .videp-nav-links li a {
339
+ color: var(--text-secondary);
340
+ text-decoration: none;
341
+ font-size: 14px;
342
+ font-weight: 500;
343
+ padding: 6px 12px;
344
+ border-radius: var(--radius-sm);
345
+ transition: color 0.15s, background 0.15s;
346
+ }
347
+ .videp-nav-links li a:hover {
348
+ color: var(--text-primary);
349
+ background: var(--bg-elevated);
350
+ }
351
+
352
+ /* Hero */
353
+ .videp-hero {
354
+ padding: 72px 0 64px;
355
+ text-align: center;
356
+ position: relative;
357
+ overflow: hidden;
358
+ }
359
+ .videp-hero-eyebrow {
360
+ display: inline-flex;
361
+ align-items: center;
362
+ gap: 6px;
363
+ font-size: 12px;
364
+ font-weight: 600;
365
+ color: var(--accent-light);
366
+ letter-spacing: 0.08em;
367
+ text-transform: uppercase;
368
+ background: var(--accent-glow);
369
+ border: 1px solid rgba(59,130,246,0.25);
370
+ border-radius: 99px;
371
+ padding: 5px 14px;
372
+ margin-bottom: 24px;
373
+ }
374
+ .videp-hero h1 {
375
+ font-size: clamp(32px, 5vw, 56px);
376
+ font-weight: 800;
377
+ color: var(--text-primary);
378
+ letter-spacing: -0.03em;
379
+ line-height: 1.1;
380
+ margin-bottom: 20px;
381
+ }
382
+ .videp-hero h1 .gradient-text {
383
+ background: linear-gradient(135deg, var(--accent-light), #a78bfa);
384
+ -webkit-background-clip: text;
385
+ -webkit-text-fill-color: transparent;
386
+ background-clip: text;
387
+ }
388
+ .videp-hero p {
389
+ font-size: 18px;
390
+ color: var(--text-secondary);
391
+ max-width: 520px;
392
+ margin: 0 auto 40px;
393
+ line-height: 1.6;
394
+ font-weight: 400;
395
+ }
396
+ /* Signature: animated waveform */
397
+ .videp-waveform {
398
+ display: flex;
399
+ align-items: center;
400
+ justify-content: center;
401
+ gap: 3px;
402
+ height: 40px;
403
+ margin: 0 auto 40px;
404
+ }
405
+ .videp-waveform span {
406
+ display: block;
407
+ width: 3px;
408
+ background: var(--accent);
409
+ border-radius: 99px;
410
+ animation: wave 1.4s ease-in-out infinite;
411
+ opacity: 0.7;
412
+ }
413
+ .videp-waveform span:nth-child(1) { height: 8px; animation-delay: 0.0s; }
414
+ .videp-waveform span:nth-child(2) { height: 16px; animation-delay: 0.1s; }
415
+ .videp-waveform span:nth-child(3) { height: 28px; animation-delay: 0.2s; }
416
+ .videp-waveform span:nth-child(4) { height: 20px; animation-delay: 0.3s; }
417
+ .videp-waveform span:nth-child(5) { height: 36px; animation-delay: 0.0s; }
418
+ .videp-waveform span:nth-child(6) { height: 24px; animation-delay: 0.15s; }
419
+ .videp-waveform span:nth-child(7) { height: 32px; animation-delay: 0.25s; }
420
+ .videp-waveform span:nth-child(8) { height: 18px; animation-delay: 0.05s; }
421
+ .videp-waveform span:nth-child(9) { height: 28px; animation-delay: 0.35s; }
422
+ .videp-waveform span:nth-child(10) { height: 12px; animation-delay: 0.1s; }
423
+ .videp-waveform span:nth-child(11) { height: 22px; animation-delay: 0.2s; }
424
+ .videp-waveform span:nth-child(12) { height: 34px; animation-delay: 0.0s; }
425
+ .videp-waveform span:nth-child(13) { height: 20px; animation-delay: 0.15s; }
426
+ .videp-waveform span:nth-child(14) { height: 28px; animation-delay: 0.3s; }
427
+ .videp-waveform span:nth-child(15) { height: 14px; animation-delay: 0.05s; }
428
+ @keyframes wave {
429
+ 0%, 100% { transform: scaleY(1); opacity: 0.5; }
430
+ 50% { transform: scaleY(1.8); opacity: 1; }
431
+ }
432
+
433
+ /* Feature pills */
434
+ .videp-pill-row {
435
+ display: flex;
436
+ flex-wrap: wrap;
437
+ gap: 10px;
438
+ justify-content: center;
439
+ margin-bottom: 48px;
440
+ }
441
+ .videp-pill {
442
+ display: inline-flex;
443
+ align-items: center;
444
+ gap: 7px;
445
+ font-size: 13px;
446
+ font-weight: 500;
447
+ color: var(--text-secondary);
448
+ background: var(--bg-surface);
449
+ border: 1px solid var(--border);
450
+ border-radius: 99px;
451
+ padding: 7px 14px;
452
+ transition: color 0.15s, border-color 0.15s;
453
+ }
454
+ .videp-pill:hover { color: var(--text-primary); border-color: var(--accent); }
455
+ .videp-pill i { color: var(--accent); font-size: 12px; }
456
+
457
+ /* Section heading */
458
+ .section-heading {
459
+ font-size: 11px;
460
+ font-weight: 700;
461
+ color: var(--text-muted);
462
+ letter-spacing: 0.1em;
463
+ text-transform: uppercase;
464
+ margin-bottom: 20px;
465
+ display: flex;
466
+ align-items: center;
467
+ gap: 10px;
468
+ }
469
+ .section-heading::after {
470
+ content: '';
471
+ flex: 1;
472
+ height: 1px;
473
+ background: var(--border);
474
+ }
475
+
476
+ /* Upload zone */
477
+ .upload-zone-label {
478
+ text-align: center;
479
+ padding: 8px 0 16px;
480
+ }
481
+ .upload-zone-label p {
482
+ font-size: 14px;
483
+ color: var(--text-secondary);
484
+ margin-top: 8px;
485
+ }
486
+ .upload-zone-label .upload-icon {
487
+ font-size: 32px;
488
+ margin-bottom: 10px;
489
+ display: block;
490
+ opacity: 0.5;
491
+ }
492
+
493
+ /* Config card */
494
+ .config-grid {
495
+ display: grid;
496
+ grid-template-columns: 1fr 1fr;
497
+ gap: 16px;
498
+ }
499
+
500
+ /* Transcript area */
501
+ .transcript-section {
502
+ background: var(--bg-surface);
503
+ border: 1px solid var(--border);
504
+ border-radius: var(--radius-xl);
505
+ padding: 24px;
506
+ margin-top: 20px;
507
+ }
508
+ .transcript-header {
509
+ display: flex;
510
+ align-items: center;
511
+ justify-content: space-between;
512
+ margin-bottom: 18px;
513
+ }
514
+ .transcript-title {
515
+ font-size: 15px;
516
+ font-weight: 600;
517
+ color: var(--text-primary);
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 8px;
521
+ }
522
+ .transcript-badge {
523
+ font-size: 11px;
524
+ font-weight: 600;
525
+ padding: 3px 8px;
526
+ border-radius: 99px;
527
+ background: var(--accent-glow);
528
+ color: var(--accent-light);
529
+ border: 1px solid rgba(59,130,246,0.2);
530
+ }
531
+
532
+ /* Search box */
533
+ .search-wrap {
534
+ position: relative;
535
+ margin-bottom: 16px;
536
+ }
537
+ .search-wrap .stTextInput > div > div > input {
538
+ padding-left: 40px !important;
539
+ }
540
+
541
+ /* Subtitle chip */
542
+ .subtitle-chip {
543
+ display: flex;
544
+ align-items: center;
545
+ gap: 12px;
546
+ padding: 10px 14px;
547
+ border-radius: var(--radius-md);
548
+ border: 1px solid var(--border-light);
549
+ background: var(--bg-base);
550
+ margin-bottom: 6px;
551
+ cursor: pointer;
552
+ transition: border-color 0.15s, background 0.15s;
553
+ }
554
+ .subtitle-chip:hover {
555
+ border-color: var(--accent);
556
+ background: var(--accent-glow2);
557
+ }
558
+ .subtitle-timestamp {
559
+ font-family: var(--font-mono);
560
+ font-size: 11px;
561
+ color: var(--accent-light);
562
+ white-space: nowrap;
563
+ min-width: 90px;
564
+ background: var(--accent-glow);
565
+ padding: 3px 7px;
566
+ border-radius: 4px;
567
+ }
568
+ .subtitle-text {
569
+ font-size: 13px;
570
+ color: var(--text-secondary);
571
+ line-height: 1.5;
572
+ }
573
+
574
+ /* Summary box */
575
+ .summary-card {
576
+ background: var(--bg-surface);
577
+ border: 1px solid var(--border);
578
+ border-left: 3px solid var(--accent);
579
+ border-radius: var(--radius-lg);
580
+ padding: 24px;
581
+ margin-top: 20px;
582
+ }
583
+ .summary-card p {
584
+ font-size: 15px;
585
+ color: var(--text-secondary);
586
+ line-height: 1.7;
587
+ }
588
+
589
+ /* Stats row */
590
+ .stats-row {
591
+ display: flex;
592
+ gap: 16px;
593
+ margin-bottom: 24px;
594
+ flex-wrap: wrap;
595
+ }
596
+ .stat-card {
597
+ flex: 1;
598
+ min-width: 140px;
599
+ background: var(--bg-surface);
600
+ border: 1px solid var(--border);
601
+ border-radius: var(--radius-lg);
602
+ padding: 18px 20px;
603
+ }
604
+ .stat-label {
605
+ font-size: 11px;
606
+ font-weight: 600;
607
+ color: var(--text-muted);
608
+ letter-spacing: 0.08em;
609
+ text-transform: uppercase;
610
+ margin-bottom: 8px;
611
+ }
612
+ .stat-value {
613
+ font-size: 24px;
614
+ font-weight: 700;
615
+ color: var(--text-primary);
616
+ font-variant-numeric: tabular-nums;
617
+ }
618
+ .stat-value.accent { color: var(--accent-light); }
619
+
620
+ /* Processing card */
621
+ .processing-card {
622
+ background: var(--bg-surface);
623
+ border: 1px solid var(--border);
624
+ border-radius: var(--radius-xl);
625
+ padding: 32px;
626
+ }
627
+
628
+ /* Step indicator */
629
+ .step-row {
630
+ display: flex;
631
+ gap: 12px;
632
+ margin-bottom: 32px;
633
+ overflow-x: auto;
634
+ padding-bottom: 4px;
635
+ }
636
+ .step {
637
+ display: flex;
638
+ align-items: center;
639
+ gap: 8px;
640
+ font-size: 13px;
641
+ font-weight: 500;
642
+ white-space: nowrap;
643
+ color: var(--text-muted);
644
+ }
645
+ .step.active { color: var(--accent-light); }
646
+ .step.done { color: var(--success); }
647
+ .step-dot {
648
+ width: 20px;
649
+ height: 20px;
650
+ border-radius: 50%;
651
+ border: 2px solid currentColor;
652
+ display: flex;
653
+ align-items: center;
654
+ justify-content: center;
655
+ font-size: 10px;
656
+ flex-shrink: 0;
657
+ }
658
+ .step.active .step-dot { background: var(--accent); border-color: var(--accent); color: #fff; }
659
+ .step.done .step-dot { background: var(--success); border-color: var(--success); color: #fff; }
660
+ .step-sep { color: var(--text-muted); font-size: 12px; }
661
+
662
+ /* Features section */
663
+ .features-grid {
664
+ display: grid;
665
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
666
+ gap: 16px;
667
+ margin: 48px 0;
668
+ }
669
+ .feature-card {
670
+ background: var(--bg-surface);
671
+ border: 1px solid var(--border);
672
+ border-radius: var(--radius-lg);
673
+ padding: 24px;
674
+ transition: border-color 0.2s, transform 0.2s;
675
+ }
676
+ .feature-card:hover {
677
+ border-color: rgba(59,130,246,0.4);
678
+ transform: translateY(-2px);
679
+ }
680
+ .feature-icon {
681
+ width: 40px;
682
+ height: 40px;
683
+ background: var(--accent-glow);
684
+ border-radius: var(--radius-md);
685
+ display: flex;
686
+ align-items: center;
687
+ justify-content: center;
688
+ font-size: 18px;
689
+ margin-bottom: 14px;
690
+ color: var(--accent-light);
691
+ }
692
+ .feature-card h4 {
693
+ font-size: 14px;
694
+ font-weight: 600;
695
+ color: var(--text-primary);
696
+ margin-bottom: 6px;
697
+ }
698
+ .feature-card p {
699
+ font-size: 13px;
700
+ color: var(--text-secondary);
701
+ line-height: 1.5;
702
+ }
703
+
704
+ /* Plans */
705
+ .plans-section {
706
+ margin: 48px 0;
707
+ }
708
+ .plans-grid {
709
+ display: grid;
710
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
711
+ gap: 16px;
712
+ margin-top: 32px;
713
+ }
714
+ .plan-card {
715
+ background: var(--bg-surface);
716
+ border: 1px solid var(--border);
717
+ border-radius: var(--radius-xl);
718
+ padding: 28px;
719
+ position: relative;
720
+ transition: transform 0.2s, border-color 0.2s;
721
+ }
722
+ .plan-card:hover { transform: translateY(-3px); border-color: var(--accent); }
723
+ .plan-card.featured {
724
+ border-color: var(--accent);
725
+ background: linear-gradient(180deg, rgba(59,130,246,0.06) 0%, var(--bg-surface) 100%);
726
+ }
727
+ .plan-badge {
728
+ position: absolute;
729
+ top: -10px;
730
+ left: 50%;
731
+ transform: translateX(-50%);
732
+ font-size: 10px;
733
+ font-weight: 700;
734
+ letter-spacing: 0.08em;
735
+ text-transform: uppercase;
736
+ background: var(--accent);
737
+ color: #fff;
738
+ padding: 3px 10px;
739
+ border-radius: 99px;
740
+ }
741
+ .plan-name {
742
+ font-size: 14px;
743
+ font-weight: 700;
744
+ color: var(--text-secondary);
745
+ letter-spacing: 0.05em;
746
+ text-transform: uppercase;
747
+ margin-bottom: 12px;
748
+ }
749
+ .plan-price {
750
+ font-size: 38px;
751
+ font-weight: 800;
752
+ color: var(--text-primary);
753
+ letter-spacing: -0.02em;
754
+ line-height: 1;
755
+ margin-bottom: 4px;
756
+ }
757
+ .plan-price span {
758
+ font-size: 14px;
759
+ font-weight: 400;
760
+ color: var(--text-muted);
761
+ }
762
+ .plan-desc {
763
+ font-size: 13px;
764
+ color: var(--text-secondary);
765
+ margin: 16px 0;
766
+ padding-top: 16px;
767
+ border-top: 1px solid var(--border);
768
+ line-height: 1.6;
769
+ }
770
+ .plan-feature {
771
+ display: flex;
772
+ align-items: center;
773
+ gap: 8px;
774
+ font-size: 13px;
775
+ color: var(--text-secondary);
776
+ margin-bottom: 8px;
777
+ }
778
+ .plan-feature i { color: var(--success); font-size: 12px; flex-shrink: 0; }
779
+
780
+ /* About section */
781
+ .about-section {
782
+ background: var(--bg-surface);
783
+ border: 1px solid var(--border);
784
+ border-radius: var(--radius-xl);
785
+ padding: 48px;
786
+ margin: 48px 0;
787
+ }
788
+ .about-grid {
789
+ display: grid;
790
+ grid-template-columns: 1fr 2fr;
791
+ gap: 48px;
792
+ align-items: center;
793
+ }
794
+ .about-img {
795
+ width: 100%;
796
+ border-radius: var(--radius-lg);
797
+ border: 1px solid var(--border);
798
+ }
799
+ .about-heading {
800
+ font-size: 28px;
801
+ font-weight: 800;
802
+ color: var(--text-primary);
803
+ letter-spacing: -0.02em;
804
+ margin-bottom: 16px;
805
+ }
806
+ .about-text {
807
+ font-size: 15px;
808
+ color: var(--text-secondary);
809
+ line-height: 1.7;
810
+ margin-bottom: 20px;
811
+ }
812
+ .about-list { list-style: none; padding: 0; }
813
+ .about-list li {
814
+ display: flex;
815
+ align-items: center;
816
+ gap: 10px;
817
+ font-size: 14px;
818
+ color: var(--text-secondary);
819
+ margin-bottom: 10px;
820
+ }
821
+ .about-list li i { color: var(--accent-light); font-size: 13px; }
822
+
823
+ /* Contact section */
824
+ .contact-section {
825
+ margin: 48px 0;
826
+ }
827
+ .contact-card {
828
+ background: var(--bg-surface);
829
+ border: 1px solid var(--border);
830
+ border-radius: var(--radius-xl);
831
+ padding: 40px;
832
+ max-width: 560px;
833
+ margin: 0 auto;
834
+ }
835
+ .contact-input {
836
+ width: 100%;
837
+ background: var(--bg-base);
838
+ border: 1px solid var(--border);
839
+ border-radius: var(--radius-md);
840
+ padding: 12px 16px;
841
+ color: var(--text-primary);
842
+ font-family: var(--font);
843
+ font-size: 14px;
844
+ margin-bottom: 14px;
845
+ outline: none;
846
+ transition: border-color 0.2s, box-shadow 0.2s;
847
+ }
848
+ .contact-input:focus {
849
+ border-color: var(--accent);
850
+ box-shadow: 0 0 0 3px var(--accent-glow);
851
+ }
852
+ .contact-textarea {
853
+ resize: vertical;
854
+ min-height: 120px;
855
+ }
856
+ .contact-btn {
857
+ width: 100%;
858
+ background: var(--accent);
859
+ color: #fff;
860
+ font-family: var(--font);
861
+ font-weight: 600;
862
+ font-size: 14px;
863
+ padding: 12px;
864
+ border-radius: var(--radius-md);
865
+ border: none;
866
+ cursor: pointer;
867
+ transition: background 0.15s, transform 0.1s;
868
+ }
869
+ .contact-btn:hover { background: var(--accent-light); transform: translateY(-1px); }
870
+ .contact-label {
871
+ display: block;
872
+ font-size: 12px;
873
+ font-weight: 600;
874
+ color: var(--text-muted);
875
+ letter-spacing: 0.06em;
876
+ text-transform: uppercase;
877
+ margin-bottom: 6px;
878
  }
879
 
880
  /* Footer */
881
+ .videp-footer {
882
+ background: var(--bg-surface);
883
+ border-top: 1px solid var(--border);
884
+ padding: 48px 0 32px;
885
+ margin-top: 80px;
886
+ }
887
+ .footer-grid {
888
+ display: grid;
889
+ grid-template-columns: 2fr 1fr 1fr 1fr;
890
+ gap: 40px;
891
+ margin-bottom: 40px;
892
+ }
893
+ .footer-brand p {
894
+ font-size: 13px;
895
+ color: var(--text-secondary);
896
+ line-height: 1.6;
897
+ margin-top: 12px;
898
+ max-width: 260px;
899
+ }
900
+ .footer-col h5 {
901
+ font-size: 11px;
902
+ font-weight: 700;
903
+ color: var(--text-muted);
904
+ letter-spacing: 0.1em;
905
+ text-transform: uppercase;
906
+ margin-bottom: 14px;
 
 
 
907
  }
908
+ .footer-col ul { list-style: none; padding: 0; }
909
+ .footer-col ul li { margin-bottom: 8px; }
910
+ .footer-col ul li a {
911
+ font-size: 13px;
912
+ color: var(--text-secondary);
913
+ text-decoration: none;
914
+ transition: color 0.15s;
915
+ }
916
+ .footer-col ul li a:hover { color: var(--text-primary); }
917
  .footer-bottom {
918
+ border-top: 1px solid var(--border);
919
+ padding-top: 24px;
920
+ display: flex;
921
+ align-items: center;
922
+ justify-content: space-between;
923
+ flex-wrap: wrap;
924
+ gap: 12px;
925
+ }
926
+ .footer-bottom p {
927
+ font-size: 12px;
928
+ color: var(--text-muted);
929
+ }
930
+
931
+ /* Divider */
932
+ .divider {
933
+ height: 1px;
934
+ background: var(--border);
935
+ margin: 40px 0;
936
  }
937
 
938
+ /* Section title */
939
+ .section-title {
940
+ font-size: 28px;
941
+ font-weight: 800;
942
+ color: var(--text-primary);
943
+ letter-spacing: -0.02em;
944
+ margin-bottom: 8px;
945
+ }
946
+ .section-subtitle {
947
+ font-size: 15px;
948
+ color: var(--text-secondary);
949
+ margin-bottom: 32px;
950
+ }
951
+
952
+ /* Delete/Edit view */
953
+ .edit-row {
954
+ display: flex;
955
+ align-items: flex-start;
956
+ gap: 12px;
957
+ padding: 12px 14px;
958
+ border: 1px solid var(--border-light);
959
+ border-radius: var(--radius-md);
960
+ background: var(--bg-base);
961
+ margin-bottom: 6px;
962
+ }
963
+ .edit-index {
964
+ font-family: var(--font-mono);
965
+ font-size: 11px;
966
+ color: var(--text-muted);
967
+ min-width: 28px;
968
+ padding-top: 2px;
969
+ }
970
+ .edit-time {
971
+ font-family: var(--font-mono);
972
+ font-size: 11px;
973
+ color: var(--accent-light);
974
+ min-width: 110px;
975
+ background: var(--accent-glow);
976
+ padding: 3px 7px;
977
+ border-radius: 4px;
978
+ white-space: nowrap;
979
+ }
980
+ .edit-text {
981
+ font-size: 13px;
982
+ color: var(--text-secondary);
983
+ flex: 1;
984
+ line-height: 1.5;
985
+ }
986
+
987
+ /* Memory/Loading chip */
988
+ .mem-chip {
989
+ display: inline-flex;
990
+ align-items: center;
991
+ gap: 6px;
992
+ font-size: 12px;
993
+ color: var(--text-muted);
994
+ background: var(--bg-elevated);
995
+ border: 1px solid var(--border);
996
+ border-radius: 99px;
997
+ padding: 4px 10px;
998
+ font-family: var(--font-mono);
999
+ }
1000
+
1001
+ /* Responsive */
1002
  @media (max-width: 768px) {
1003
+ .videp-nav-links { display: none; }
1004
+ .about-grid { grid-template-columns: 1fr; }
1005
+ .footer-grid { grid-template-columns: 1fr 1fr; }
1006
+ .config-grid { grid-template-columns: 1fr; }
1007
+ .stats-row { gap: 10px; }
1008
+ .videp-hero h1 { font-size: 32px; }
1009
+ .videp-hero p { font-size: 15px; }
1010
+ }
1011
+ @media (max-width: 480px) {
1012
+ .footer-grid { grid-template-columns: 1fr; }
1013
+ .plans-grid { grid-template-columns: 1fr; }
1014
+ .block-container { padding: 0 16px 60px !important; }
 
 
 
 
 
 
1015
  }
1016
  </style>
 
1017
  """, unsafe_allow_html=True)
1018
 
1019
+
1020
+ # ─── UTILITIES ─────────────────────────────────────────────────────────────────
1021
+
1022
  def format_time(seconds):
1023
  minutes = int(seconds // 60)
1024
  secs = int(seconds % 60)
 
1031
  millis = int((seconds - int(seconds)) * 1000)
1032
  return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
1033
 
1034
+
1035
+ # ─── MODEL LOADING ──────────────────────────────────────────────────────────────
1036
+
1037
  class TranscriptionProgress:
1038
  def __init__(self):
1039
  self.progress_bar = None
 
1067
  sum_model = AutoModelForSeq2SeqLM.from_pretrained("pszemraj/led-large-book-summary").to(device)
1068
  return processor, model, sum_tokenizer, sum_model, device
1069
 
1070
+
1071
  def split_audio_into_chunks(audio, sr, chunk_duration):
1072
  chunk_samples = int(chunk_duration * sr)
1073
+ return [audio[start:start + chunk_samples] for start in range(0, len(audio), chunk_samples)]
 
1074
 
1075
  def transcribe_audio(audio, sr, processor, model, device, start_time, language, task="transcribe"):
1076
  inputs = processor(audio, sampling_rate=sr, return_tensors="pt")
 
1101
  if os.path.exists(transcript_file):
1102
  os.remove(transcript_file)
1103
  for i, chunk in enumerate(chunks):
1104
+ status_text.markdown(
1105
+ f'<span class="mem-chip"><i class="fas fa-microchip"></i> Chunk {i+1} / {total_chunks} &nbsp;Β·&nbsp; '
1106
+ f'{psutil.virtual_memory().percent:.1f}% RAM</span>',
1107
+ unsafe_allow_html=True
1108
+ )
1109
  try:
 
 
1110
  chunk_transcript = transcribe_audio(chunk, sr, processor, model, device, chunk_start, language, task)
1111
  transcript.extend(chunk_transcript)
1112
  with open(transcript_file, "w", encoding="utf-8") as f:
 
1116
  except Exception as e:
1117
  st.error(f"Error processing chunk {i+1}: {str(e)}")
1118
  break
1119
+ status_text.empty()
1120
  progress_bar.empty()
1121
  return transcript
1122
 
 
1132
  inputs = tokenizer(text, return_tensors="pt", truncation=False)
1133
  input_ids = inputs["input_ids"].to(device)
1134
  num_tokens = input_ids.shape[1]
 
1135
  if num_tokens < 50:
1136
  return "Transcript too short to summarize effectively."
1137
  try:
 
1142
  summary_ids = model.generate(truncated_inputs["input_ids"], num_beams=4, max_length=max_summary_length, min_length=50, early_stopping=True, temperature=0.7)
1143
  summaries.append(tokenizer.decode(summary_ids[0], skip_special_tokens=True))
1144
  else:
 
1145
  tokens = input_ids[0].tolist()
1146
  for i in range(0, num_tokens, chunk_size):
1147
  chunk_tokens = tokens[i:i + chunk_size]
 
1216
  srt_content += f"{text}\n\n"
1217
  return srt_content
1218
 
1219
+
1220
+ # ─── MAIN ───────────────────────────────────────────────────────────────────────
1221
+
1222
  def main():
1223
+
1224
+ # ── NAV ─────────────────────────────────────────────────────────────────────
1225
  st.markdown("""
1226
+ <div class="videp-nav">
1227
+ <a href="#home" class="videp-logo">
1228
+ <div class="videp-logo-icon">🎬</div>
1229
+ <span class="videp-logo-text">VidEp</span>
1230
+ <span class="videp-logo-badge">BETA</span>
1231
+ </a>
1232
+ <ul class="videp-nav-links">
1233
  <li><a href="#home">Home</a></li>
1234
+ <li><a href="#upload">Upload</a></li>
1235
+ <li><a href="#about">About</a></li>
1236
+ <li><a href="#contact">Contact</a></li>
1237
  </ul>
1238
  </div>
1239
  """, unsafe_allow_html=True)
1240
 
1241
+ # ── HERO ────────────────────────────────────────────────────────────────────
1242
+ st.markdown('<div id="home"></div>', unsafe_allow_html=True)
1243
  st.markdown("""
1244
+ <div class="videp-hero">
1245
+ <div class="videp-hero-eyebrow"><i class="fas fa-bolt"></i> Powered by Whisper &amp; BART</div>
1246
+ <h1>Video intelligence,<br><span class="gradient-text">zero friction</span></h1>
1247
+ <p>Transcribe, translate, and summarize any video in seconds. Edit subtitles, search content, and export β€” all in one place.</p>
1248
+ <div class="videp-waveform">
1249
+ <span></span><span></span><span></span><span></span><span></span>
1250
+ <span></span><span></span><span></span><span></span><span></span>
1251
+ <span></span><span></span><span></span><span></span><span></span>
1252
+ </div>
1253
+ <div class="videp-pill-row">
1254
+ <div class="videp-pill"><i class="fas fa-language"></i> English &amp; Urdu</div>
1255
+ <div class="videp-pill"><i class="fas fa-search"></i> Smart Search</div>
1256
+ <div class="videp-pill"><i class="fas fa-file-alt"></i> AI Summary</div>
1257
+ <div class="videp-pill"><i class="fas fa-cut"></i> Video Editing</div>
1258
+ <div class="videp-pill"><i class="fas fa-download"></i> SRT Export</div>
1259
+ </div>
1260
  </div>
1261
  """, unsafe_allow_html=True)
1262
 
1263
+ # ── SESSION STATE ────────────────────────────────────────────────────────────
1264
+ defaults = {
1265
+ 'app_state': 'upload',
1266
+ 'video_path': None,
1267
+ 'primary_transcript': None,
1268
+ 'english_transcript': None,
1269
+ 'english_summary': None,
1270
+ 'language': None,
1271
+ 'language_code': None,
1272
+ 'translate_to_english': False,
1273
+ 'summarizer_type': None,
1274
+ 'summary_generated': False,
1275
+ 'current_time': 0,
1276
+ 'edited_video_path': None,
1277
+ 'search_query': "",
1278
+ 'show_timeframe': True,
1279
+ }
1280
+ for k, v in defaults.items():
1281
+ if k not in st.session_state:
1282
+ st.session_state[k] = v
 
 
 
 
 
 
 
 
 
1283
 
1284
+ # ── UPLOAD ───────────────────────────────────────────────────────────────────
1285
  if st.session_state['app_state'] == 'upload':
1286
+ st.markdown('<div id="upload"></div>', unsafe_allow_html=True)
1287
+
1288
+ col_l, col_c, col_r = st.columns([1, 2, 1])
1289
+ with col_c:
1290
+ st.markdown('<div class="section-heading">Get started</div>', unsafe_allow_html=True)
1291
+ st.markdown("""
1292
+ <div style="text-align:center; margin-bottom:8px;">
1293
+ <div class="section-title">Upload your video</div>
1294
+ <div class="section-subtitle">MP4 files supported Β· Transcription begins automatically</div>
1295
+ </div>
1296
+ """, unsafe_allow_html=True)
1297
 
1298
+ with st.form(key="upload_form"):
1299
+ st.markdown("""
1300
+ <div class="upload-zone-label">
1301
+ <span class="upload-icon">πŸ“Ή</span>
1302
+ <p>Drag and drop or click to browse</p>
1303
+ </div>
1304
+ """, unsafe_allow_html=True)
1305
+ uploaded_file = st.file_uploader(
1306
+ "Choose a video file",
1307
+ type=["mp4"],
1308
+ label_visibility="collapsed"
1309
+ )
1310
+ st.markdown("<br>", unsafe_allow_html=True)
1311
+ submitted = st.form_submit_button("Upload & Continue β†’", use_container_width=True)
1312
+ if submitted and uploaded_file:
1313
+ with st.spinner("Saving file…"):
1314
+ video_path = save_uploaded_file(uploaded_file)
1315
+ if video_path:
1316
+ st.session_state['video_path'] = video_path
1317
+ st.session_state['app_state'] = 'processing'
1318
+ st.rerun()
1319
+ elif submitted:
1320
+ st.warning("Please select a video file first.")
1321
+
1322
+ # ── PROCESSING CONFIG ────────────────────────────────────────────────────────
1323
  if st.session_state['app_state'] == 'processing':
1324
+
1325
+ col_l, col_c, col_r = st.columns([1, 2, 1])
1326
+ with col_c:
1327
+ st.markdown("""
1328
+ <div style="margin-bottom:24px;">
1329
+ <div class="section-title">Configure &amp; Process</div>
1330
+ <div class="section-subtitle">Set transcription options, then run the pipeline.</div>
1331
+ </div>
1332
+ """, unsafe_allow_html=True)
1333
+
1334
+ with st.form(key="processing_form"):
1335
+ language = st.selectbox(
1336
+ "Transcription Language",
1337
+ ["English", "Urdu"]
1338
+ )
1339
+ language_code = "en" if language == "English" else "ur"
1340
+ st.session_state['language'] = language
1341
+ st.session_state['language_code'] = language_code
1342
+
1343
+ chunk_duration = st.number_input(
1344
+ "Chunk duration (seconds)",
1345
+ min_value=1.0,
1346
+ step=0.5,
1347
+ value=10.0,
1348
+ help="Shorter chunks = more granular subtitles, longer processing time."
1349
+ )
1350
+
1351
+ if language_code == "ur":
1352
+ translate_to_english = st.checkbox(
1353
+ "Also generate English translation",
1354
+ )
1355
+ st.session_state['translate_to_english'] = translate_to_english
1356
+ else:
1357
+ st.session_state['translate_to_english'] = False
1358
+
1359
+ st.markdown("<br>", unsafe_allow_html=True)
1360
+ process_btn = st.form_submit_button("Run Transcription β†’", use_container_width=True)
1361
+
1362
+ if process_btn:
1363
+ st.markdown('<div class="processing-card">', unsafe_allow_html=True)
1364
+
1365
+ # Step indicator
1366
+ st.markdown("""
1367
+ <div class="step-row">
1368
+ <div class="step done"><div class="step-dot"><i class="fas fa-check" style="font-size:8px"></i></div>Upload</div>
1369
+ <span class="step-sep">β€Ί</span>
1370
+ <div class="step active"><div class="step-dot">2</div>Processing</div>
1371
+ <span class="step-sep">β€Ί</span>
1372
+ <div class="step"><div class="step-dot">3</div>Results</div>
1373
+ </div>
1374
+ """, unsafe_allow_html=True)
1375
+
1376
+ with st.spinner("Extracting audio…"):
1377
  try:
 
1378
  audio_path = "processed_audio.wav"
1379
+ ffmpeg.input(st.session_state['video_path']).output(
1380
+ audio_path, ac=1, ar=16000
1381
+ ).run(overwrite_output=True, quiet=True)
1382
  audio, sr = librosa.load(audio_path, sr=16000)
1383
  audio = np.nan_to_num(audio, nan=0.0, posinf=0.0, neginf=0.0)
1384
  audio_duration = len(audio) / sr
1385
+
1386
  if audio_duration < 5:
1387
+ st.error("Audio is too short (< 5 seconds). Please upload a longer video.")
1388
+ st.stop()
1389
+
1390
  summarizer_type = 'bart' if audio_duration <= 300 else 'led'
 
1391
  st.session_state['summarizer_type'] = summarizer_type
1392
+
1393
+ col1, col2, col3 = st.columns(3)
1394
+ with col1:
1395
+ st.markdown(f'<div class="stat-card"><div class="stat-label">Duration</div><div class="stat-value accent">{int(audio_duration)}s</div></div>', unsafe_allow_html=True)
1396
+ with col2:
1397
+ st.markdown(f'<div class="stat-card"><div class="stat-label">Summarizer</div><div class="stat-value" style="font-size:18px">{summarizer_type.upper()}</div></div>', unsafe_allow_html=True)
1398
+ with col3:
1399
+ st.markdown(f'<div class="stat-card"><div class="stat-label">Device</div><div class="stat-value" style="font-size:18px">{"GPU" if torch.cuda.is_available() else "CPU"}</div></div>', unsafe_allow_html=True)
1400
+
1401
+ st.info("Loading AI models… This may take a moment on first run.")
1402
  processor, model, sum_tokenizer, sum_model, device = load_model(language_code, summarizer_type)
1403
+
1404
+ st.markdown('<div class="section-heading">Transcribing</div>', unsafe_allow_html=True)
1405
  chunks = split_audio_into_chunks(audio, sr, chunk_duration)
1406
+ primary_transcript = process_chunks(
1407
+ chunks, sr, processor, model, device,
1408
+ language_code, chunk_duration,
1409
+ task="transcribe",
1410
+ transcript_file="temp_primary_transcript.json"
1411
+ )
1412
+
1413
  english_transcript = None
1414
  if st.session_state['translate_to_english'] and language_code == "ur":
1415
+ st.markdown('<div class="section-heading">Translating to English</div>', unsafe_allow_html=True)
1416
  processor, model, _, _, device = load_model('en', summarizer_type)
1417
+ english_transcript = process_chunks(
1418
+ chunks, sr, processor, model, device,
1419
+ 'ur', chunk_duration,
1420
+ task="translate",
1421
+ transcript_file="temp_english_transcript.json"
1422
+ )
1423
+
1424
  st.session_state.update({
1425
  'primary_transcript': primary_transcript,
1426
  'english_transcript': english_transcript,
1427
  'summary_generated': False,
1428
  'app_state': 'results'
1429
  })
1430
+ st.success("Processing complete! Loading results…")
1431
+ time.sleep(0.8)
1432
  st.rerun()
1433
+
1434
  except Exception as e:
1435
  st.error(f"Processing failed: {str(e)}")
1436
  finally:
 
1440
  if os.path.exists(temp_file):
1441
  os.remove(temp_file)
1442
 
1443
+ st.markdown('</div>', unsafe_allow_html=True)
1444
+
1445
+ # ── RESULTS ──────────────────────────────────────────────────────────────────
1446
  if st.session_state['app_state'] == 'results':
1447
+
1448
+ # Step indicator
1449
+ st.markdown("""
1450
+ <div class="step-row" style="margin:20px 0 28px;">
1451
+ <div class="step done"><div class="step-dot"><i class="fas fa-check" style="font-size:8px"></i></div>Upload</div>
1452
+ <span class="step-sep">β€Ί</span>
1453
+ <div class="step done"><div class="step-dot"><i class="fas fa-check" style="font-size:8px"></i></div>Processing</div>
1454
+ <span class="step-sep">β€Ί</span>
1455
+ <div class="step active"><div class="step-dot">3</div>Results</div>
1456
+ </div>
1457
+ """, unsafe_allow_html=True)
1458
+
1459
+ # Two-column layout: video + controls
1460
+ left_col, right_col = st.columns([1, 1], gap="large")
1461
+
1462
+ with left_col:
1463
+ st.markdown('<div class="section-heading">Preview</div>', unsafe_allow_html=True)
1464
+ st.video(st.session_state['video_path'], start_time=st.session_state['current_time'])
1465
+
1466
+ # Stats
1467
+ if st.session_state['primary_transcript']:
1468
+ n = len(st.session_state['primary_transcript'])
1469
+ total_dur = st.session_state['primary_transcript'][-1][2] if n > 0 else 0
1470
+ st.markdown(f"""
1471
+ <div class="stats-row" style="margin-top:16px;">
1472
+ <div class="stat-card">
1473
+ <div class="stat-label">Segments</div>
1474
+ <div class="stat-value accent">{n}</div>
1475
+ </div>
1476
+ <div class="stat-card">
1477
+ <div class="stat-label">Duration</div>
1478
+ <div class="stat-value" style="font-size:20px">{format_time(total_dur)}</div>
1479
+ </div>
1480
+ <div class="stat-card">
1481
+ <div class="stat-label">Language</div>
1482
+ <div class="stat-value" style="font-size:16px">{st.session_state['language']}</div>
1483
+ </div>
1484
+ </div>
1485
+ """, unsafe_allow_html=True)
1486
+
1487
+ # Actions
1488
+ st.markdown('<div class="section-heading" style="margin-top:24px;">Actions</div>', unsafe_allow_html=True)
1489
+
1490
+ inc_tf = st.checkbox("Include timestamps in export", value=True)
1491
+ transcript_to_dl = st.session_state['primary_transcript'] or st.session_state['english_transcript']
1492
+ if transcript_to_dl:
1493
+ srt_content = generate_srt(transcript_to_dl, inc_tf)
1494
+ st.download_button(
1495
+ label="⬇ Download Subtitles (.srt)",
1496
+ data=srt_content,
1497
+ file_name="subtitles.srt",
1498
+ mime="text/plain",
1499
+ use_container_width=True
1500
+ )
1501
+
1502
+ st.markdown("<br>", unsafe_allow_html=True)
1503
+ transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
1504
+ if transcript_to_edit:
1505
+ with st.container():
1506
+ st.markdown('<div class="btn-ghost">', unsafe_allow_html=True)
1507
+ if st.button("βœ‚ Edit & Remove Segments", use_container_width=True):
1508
+ st.session_state['app_state'] = 'editing'
1509
+ st.rerun()
1510
+ st.markdown('</div>', unsafe_allow_html=True)
1511
+
1512
+ # Summary
1513
+ if (st.session_state['language_code'] == 'en' or st.session_state['translate_to_english']) \
1514
+ and not st.session_state['summary_generated']:
1515
+ st.markdown("<br>", unsafe_allow_html=True)
1516
+ if st.button("✦ Generate AI Summary", use_container_width=True):
1517
+ with st.spinner("Summarizing transcript…"):
1518
+ try:
1519
+ _, _, sum_tokenizer, sum_model, device = load_model(
1520
+ st.session_state['language_code'],
1521
+ st.session_state['summarizer_type']
1522
+ )
1523
+ full_text = " ".join([
1524
+ t for t, _, _ in
1525
+ (st.session_state['english_transcript'] or st.session_state['primary_transcript'])
1526
+ ])
1527
+ english_summary = summarize_text(
1528
+ full_text, sum_tokenizer, sum_model, device,
1529
+ st.session_state['summarizer_type']
1530
+ )
1531
+ st.session_state['english_summary'] = english_summary
1532
+ st.session_state['summary_generated'] = True
1533
+ st.rerun()
1534
+ except Exception as e:
1535
+ st.error(f"Summary generation failed: {str(e)}")
1536
+
1537
+ with right_col:
1538
+ st.markdown('<div class="section-heading">Transcript</div>', unsafe_allow_html=True)
1539
+
1540
+ st.session_state['show_timeframe'] = st.checkbox(
1541
+ "Show timestamps", value=st.session_state['show_timeframe']
1542
+ )
1543
+
1544
+ def update_search():
1545
+ st.session_state['search_query'] = st.session_state.get('search_input', '').lower().strip()
1546
+
1547
+ st.text_input(
1548
+ "Search subtitles…",
1549
+ value=st.session_state['search_query'],
1550
+ key="search_input",
1551
+ on_change=update_search,
1552
+ placeholder="Type to filter segments…"
1553
+ )
1554
+
1555
+ # Primary transcript
1556
+ st.markdown(f"""
1557
+ <div class="transcript-header" style="margin-top:16px;">
1558
+ <div class="transcript-title">
1559
+ <i class="fas fa-closed-captioning" style="color:var(--accent)"></i>
1560
+ {st.session_state['language']} Transcript
1561
+ </div>
1562
+ <span class="transcript-badge">{len(st.session_state['primary_transcript'])} segments</span>
1563
+ </div>
1564
+ """, unsafe_allow_html=True)
1565
+
1566
+ primary_matches = 0
1567
+ for text, start, end in st.session_state['primary_transcript']:
1568
+ if not st.session_state['search_query'] or st.session_state['search_query'] in text.lower():
1569
+ primary_matches += 1
1570
+ if st.session_state['show_timeframe']:
1571
+ label = f"[{format_time(start)}–{format_time(end)}] {text}"
1572
+ else:
1573
+ label = text
1574
+ if st.button(label, key=f"primary_{start}"):
1575
  st.session_state['current_time'] = start
1576
  st.rerun()
 
 
1577
 
1578
+ if primary_matches == 0 and st.session_state['search_query']:
1579
+ st.info("No matches found in this transcript.")
1580
+
1581
+ # English translation
1582
+ if st.session_state['english_transcript']:
1583
+ st.markdown("""
1584
+ <div class="transcript-header" style="margin-top:28px;">
1585
+ <div class="transcript-title">
1586
+ <i class="fas fa-globe" style="color:#a78bfa"></i>
1587
+ English Translation
1588
+ </div>
1589
+ </div>
1590
+ """, unsafe_allow_html=True)
1591
+ for text, start, end in st.session_state['english_transcript']:
1592
+ if not st.session_state['search_query'] or st.session_state['search_query'] in text.lower():
1593
+ if st.session_state['show_timeframe']:
1594
+ label = f"[{format_time(start)}–{format_time(end)}] {text}"
1595
+ else:
1596
+ label = text
1597
+ if st.button(label, key=f"english_{start}"):
1598
+ st.session_state['current_time'] = start
1599
+ st.rerun()
1600
+
1601
+ # Summary card (full width)
1602
  if st.session_state['english_summary'] and st.session_state['summary_generated']:
1603
+ st.markdown('<div class="divider"></div>', unsafe_allow_html=True)
1604
+ st.markdown("""
1605
+ <div class="transcript-title" style="margin-bottom:14px;">
1606
+ <i class="fas fa-stars" style="color:var(--accent)"></i> AI Summary
1607
+ </div>
1608
+ """, unsafe_allow_html=True)
1609
+ st.markdown(
1610
+ f'<div class="summary-card"><p>{st.session_state["english_summary"]}</p></div>',
1611
+ unsafe_allow_html=True
1612
+ )
 
 
 
 
 
 
 
1613
 
1614
+ # Edited video
1615
+ if st.session_state['edited_video_path']:
1616
+ st.markdown('<div class="divider"></div>', unsafe_allow_html=True)
1617
+ st.markdown("""
1618
+ <div class="section-title" style="margin-bottom:16px;">Edited Video</div>
1619
+ """, unsafe_allow_html=True)
1620
+ col_v, col_d = st.columns([2, 1])
1621
+ with col_v:
1622
+ st.video(st.session_state['edited_video_path'])
1623
+ with col_d:
1624
+ with open(st.session_state['edited_video_path'], "rb") as file:
1625
+ st.download_button(
1626
+ label="⬇ Download Edited Video",
1627
+ data=file,
1628
+ file_name="edited_video.mp4",
1629
+ mime="video/mp4",
1630
+ use_container_width=True
1631
+ )
1632
+
1633
+ # ── EDITING ──────────────────────────────────────────────────────────────────
1634
  if st.session_state['app_state'] == 'editing':
1635
+ st.markdown("""
1636
+ <div style="margin-bottom:28px;">
1637
+ <div class="section-title">Remove Segments</div>
1638
+ <div class="section-subtitle">Enter segment indices to delete, then confirm. The video will be re-cut accordingly.</div>
1639
+ </div>
1640
+ """, unsafe_allow_html=True)
1641
+
1642
  transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
1643
+
1644
+ # Render transcript list
1645
+ st.markdown('<div class="transcript-section">', unsafe_allow_html=True)
1646
  for i, (text, start, end) in enumerate(transcript_to_edit):
1647
+ st.markdown(f"""
1648
+ <div class="edit-row">
1649
+ <span class="edit-index">#{i}</span>
1650
+ <span class="edit-time">{format_time(start)} – {format_time(end)}</span>
1651
+ <span class="edit-text">{text}</span>
1652
+ </div>
1653
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1654
  st.markdown('</div>', unsafe_allow_html=True)
 
 
1655
 
1656
+ st.markdown("<br>", unsafe_allow_html=True)
1657
+ indices_input = st.text_input(
1658
+ "Indices to delete",
1659
+ placeholder="e.g. 0, 2, 5",
1660
+ help="Comma-separated segment numbers. Use the # values shown above."
1661
+ )
1662
+
1663
+ col_a, col_b, _ = st.columns([1, 1, 2])
1664
+ with col_a:
1665
+ if st.button("Confirm Deletion", use_container_width=True):
1666
+ try:
1667
+ delete_indices = [int(idx.strip()) for idx in indices_input.split(',') if idx.strip()]
1668
+ delete_indices = [idx for idx in delete_indices if 0 <= idx < len(transcript_to_edit)]
1669
+ keep_indices = [i for i in range(len(transcript_to_edit)) if i not in delete_indices]
1670
+ if not keep_indices:
1671
+ st.error("All segments would be removed. Please keep at least one.")
1672
+ else:
1673
+ with st.spinner("Re-cutting video…"):
1674
+ edited_video_path = create_edited_video(
1675
+ st.session_state['video_path'], transcript_to_edit, keep_indices
1676
+ )
1677
+ if edited_video_path:
1678
+ st.session_state['edited_video_path'] = edited_video_path
1679
+ st.session_state['app_state'] = 'results'
1680
+ st.rerun()
1681
+ except ValueError:
1682
+ st.error("Invalid input β€” please enter comma-separated integers.")
1683
+ except Exception as e:
1684
+ st.error(f"Error during video editing: {str(e)}")
1685
+
1686
+ with col_b:
1687
+ st.markdown('<div class="btn-ghost">', unsafe_allow_html=True)
1688
+ if st.button("Cancel", use_container_width=True):
1689
+ st.session_state['app_state'] = 'results'
1690
+ st.rerun()
1691
+ st.markdown('</div>', unsafe_allow_html=True)
1692
 
1693
+ # ── RESET ────────────────────────────────────────────────────────────────────
1694
+ if st.session_state.get('video_path'):
1695
+ st.markdown('<div class="divider"></div>', unsafe_allow_html=True)
1696
+ col_r, _ = st.columns([1, 3])
1697
+ with col_r:
1698
+ st.markdown('<div class="btn-danger">', unsafe_allow_html=True)
1699
+ if st.button("↩ Start Over", use_container_width=True):
1700
+ if st.session_state['video_path'] and os.path.exists(st.session_state['video_path']):
1701
+ os.remove(st.session_state['video_path'])
1702
+ if st.session_state['edited_video_path'] and os.path.exists(st.session_state['edited_video_path']):
1703
+ os.remove(st.session_state['edited_video_path'])
1704
+ st.session_state.clear()
1705
+ st.rerun()
1706
+ st.markdown('</div>', unsafe_allow_html=True)
1707
+
1708
+ # ── FEATURES SECTION ─────────────────────────────────────────────────────────
1709
+ st.markdown('<div class="divider"></div>', unsafe_allow_html=True)
1710
  st.markdown("""
1711
+ <div style="text-align:center; margin-bottom:32px;">
1712
+ <div class="section-title">Everything you need</div>
1713
+ <div class="section-subtitle">One platform for all your video transcription needs.</div>
1714
  </div>
1715
+ <div class="features-grid">
1716
+ <div class="feature-card">
1717
+ <div class="feature-icon"><i class="fas fa-cloud-upload-alt"></i></div>
1718
+ <h4>Simple Upload</h4>
1719
+ <p>Drop any MP4 and start processing immediately. No account setup needed.</p>
1720
+ </div>
1721
+ <div class="feature-card">
1722
+ <div class="feature-icon"><i class="fas fa-search"></i></div>
1723
+ <h4>Smart Search</h4>
1724
+ <p>Instantly find any word or phrase across your entire transcript.</p>
1725
+ </div>
1726
+ <div class="feature-card">
1727
+ <div class="feature-icon"><i class="fas fa-cut"></i></div>
1728
+ <h4>Segment Editing</h4>
1729
+ <p>Remove unwanted segments and export a clean, trimmed video.</p>
1730
+ </div>
1731
+ <div class="feature-card">
1732
+ <div class="feature-icon"><i class="fas fa-file-alt"></i></div>
1733
+ <h4>AI Summary</h4>
1734
+ <p>Get a concise summary of any video powered by BART and LED models.</p>
1735
+ </div>
1736
+ <div class="feature-card">
1737
+ <div class="feature-icon"><i class="fas fa-language"></i></div>
1738
+ <h4>Multi-Language</h4>
1739
+ <p>Full support for English and Urdu, with automatic translation.</p>
1740
+ </div>
1741
+ <div class="feature-card">
1742
+ <div class="feature-icon"><i class="fas fa-download"></i></div>
1743
+ <h4>SRT Export</h4>
1744
+ <p>Download subtitle files compatible with any video player or editor.</p>
1745
+ </div>
1746
  </div>
1747
  """, unsafe_allow_html=True)
1748
 
1749
+ # ── PLANS ────────────────────────────────────────────────────────────────────
1750
+ st.markdown('<div class="divider"></div>', unsafe_allow_html=True)
1751
  st.markdown("""
1752
+ <div style="text-align:center; margin-bottom:32px;">
1753
+ <div class="section-title">Simple pricing</div>
1754
+ <div class="section-subtitle">Start free. Scale when you need to.</div>
1755
+ </div>
1756
+ <div class="plans-grid">
1757
+ <div class="plan-card">
1758
+ <div class="plan-name">Free</div>
1759
+ <div class="plan-price">$0<span> / month</span></div>
1760
+ <div class="plan-desc">Perfect for trying out the platform.</div>
1761
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> Basic transcription</div>
1762
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> English only</div>
1763
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> Up to 5 min video</div>
1764
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> SRT export</div>
1765
+ </div>
1766
+ <div class="plan-card featured">
1767
+ <div class="plan-badge">Most popular</div>
1768
+ <div class="plan-name">Premium</div>
1769
+ <div class="plan-price">$19<span> / month</span></div>
1770
+ <div class="plan-desc">For creators and professionals.</div>
1771
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> Advanced transcription</div>
1772
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> English + Urdu</div>
1773
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> Up to 30 min video</div>
1774
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> AI summarization</div>
1775
+ </div>
1776
+ <div class="plan-card">
1777
+ <div class="plan-name">Business</div>
1778
+ <div class="plan-price">$49<span> / month</span></div>
1779
+ <div class="plan-desc">Enterprise-grade for teams.</div>
1780
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> Unlimited video length</div>
1781
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> All languages</div>
1782
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> Priority processing</div>
1783
+ <div class="plan-feature"><i class="fas fa-check-circle"></i> API access</div>
1784
  </div>
1785
  </div>
1786
  """, unsafe_allow_html=True)
1787
 
1788
+ # ── ABOUT ────────────────────────────────────────────────────────────────────
1789
+ st.markdown('<div id="about" class="divider"></div>', unsafe_allow_html=True)
1790
  st.markdown("""
1791
+ <div class="about-section">
1792
+ <div class="about-grid">
1793
+ <div>
1794
+ <img src="https://i.postimg.cc/g0z3WVgT/about.jpg" class="about-img" alt="About VidEp">
 
 
1795
  </div>
1796
+ <div>
1797
+ <div style="font-size:11px; font-weight:700; color:var(--accent-light); letter-spacing:0.1em; text-transform:uppercase; margin-bottom:12px;">About VidEp</div>
1798
+ <div class="about-heading">Built for the way people actually work with video</div>
1799
+ <p class="about-text">VidEp combines state-of-the-art speech recognition from OpenAI Whisper with powerful summarization models to give creators, journalists, and researchers an effortless way to unlock the content inside their videos.</p>
1800
+ <p class="about-text">We handle the heavy lifting β€” you focus on the insights.</p>
1801
+ <ul class="about-list">
1802
+ <li><i class="fas fa-check"></i> OpenAI Whisper for superior accuracy</li>
1803
+ <li><i class="fas fa-check"></i> Multi-language support: English and Urdu</li>
1804
+ <li><i class="fas fa-check"></i> BART and LED for intelligent summarization</li>
1805
+ <li><i class="fas fa-check"></i> Non-destructive segment editing with FFmpeg</li>
1806
+ <li><i class="fas fa-check"></i> Works entirely in your browser β€” no data stored</li>
1807
+ </ul>
1808
  </div>
 
1809
  </div>
1810
  </div>
1811
  """, unsafe_allow_html=True)
1812
 
1813
+ # ── CONTACT ──────────────────────────────────────────────────────────────────
1814
+ st.markdown('<div id="contact" class="divider"></div>', unsafe_allow_html=True)
1815
  st.markdown("""
1816
+ <div class="contact-section">
1817
+ <div style="text-align:center; margin-bottom:32px;">
1818
+ <div class="section-title">Get in touch</div>
1819
+ <div class="section-subtitle">Have a question or feedback? We'd love to hear from you.</div>
1820
+ </div>
1821
+ <div class="contact-card">
1822
+ <label class="contact-label">Email</label>
1823
+ <input class="contact-input" type="email" placeholder="your@email.com">
1824
+ <label class="contact-label">Subject</label>
1825
+ <input class="contact-input" type="text" placeholder="What's this about?">
1826
+ <label class="contact-label">Message</label>
1827
+ <textarea class="contact-input contact-textarea" placeholder="Tell us more…"></textarea>
1828
+ <button class="contact-btn" onclick="alert('Message sent!')">Send Message β†’</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
1829
  </div>
1830
  </div>
1831
  """, unsafe_allow_html=True)
1832
 
1833
+ # ── FOOTER ───────────────────────────────────────────────────────────────────
1834
  st.markdown("""
1835
+ <div class="videp-footer">
1836
+ <div class="footer-grid">
1837
+ <div class="footer-brand">
1838
+ <div class="videp-logo">
1839
+ <div class="videp-logo-icon">🎬</div>
1840
+ <span class="videp-logo-text">VidEp</span>
1841
+ </div>
1842
+ <p>AI-powered video transcription, translation, and summarization β€” built for everyone.</p>
 
1843
  </div>
1844
+ <div class="footer-col">
1845
+ <h5>Product</h5>
1846
  <ul>
1847
  <li><a href="#home">Home</a></li>
1848
  <li><a href="#upload">Upload</a></li>
1849
  <li><a href="#about">About</a></li>
 
1850
  </ul>
1851
  </div>
1852
+ <div class="footer-col">
1853
+ <h5>Legal</h5>
1854
  <ul>
1855
  <li><a href="#">Terms of Service</a></li>
1856
  <li><a href="#">Privacy Policy</a></li>
1857
  <li><a href="#">Cookie Policy</a></li>
1858
  </ul>
1859
  </div>
1860
+ <div class="footer-col">
1861
+ <h5>Contact</h5>
1862
+ <ul>
1863
+ <li><a href="#contact">Email Us</a></li>
1864
+ <li><a href="#">Twitter</a></li>
1865
+ <li><a href="#">GitHub</a></li>
1866
+ </ul>
1867
+ </div>
1868
  </div>
1869
+ <div class="footer-bottom">
1870
+ <p>Β© 2025 VidEp. All rights reserved.</p>
1871
+ <p>Made with β™₯ using Streamlit &amp; Whisper</p>
1872
  </div>
1873
+ </div>
1874
 
1875
  <script>
1876
  document.addEventListener('DOMContentLoaded', function() {
1877
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
1878
+ anchor.addEventListener('click', function(e) {
1879
+ const target = document.querySelector(this.getAttribute('href'));
1880
+ if (target) {
1881
+ e.preventDefault();
1882
+ target.scrollIntoView({ behavior: 'smooth' });
 
 
1883
  }
1884
  });
1885
  });
 
1887
  </script>
1888
  """, unsafe_allow_html=True)
1889
 
1890
+
1891
  if __name__ == "__main__":
1892
  main()