Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -12,7 +12,7 @@ import psutil
|
|
12 |
|
13 |
st.set_page_config(layout="wide")
|
14 |
|
15 |
-
# Updated CSS with video styling
|
16 |
st.markdown("""
|
17 |
<style>
|
18 |
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
|
@@ -190,7 +190,743 @@ st.markdown("""
|
|
190 |
font-family: 'Poppins', sans-serif;
|
191 |
}
|
192 |
|
193 |
-
/* Video player styling
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
video {
|
195 |
display: block;
|
196 |
width: 350px !important;
|
@@ -591,23 +1327,37 @@ def main():
|
|
591 |
st.markdown("### Search Subtitles")
|
592 |
search_query = st.text_input("Search subtitles...", value=st.session_state['search_query'], key="search_input")
|
593 |
st.session_state['search_query'] = search_query.lower()
|
|
|
|
|
594 |
st.markdown(f"### {st.session_state['language']} Transcript")
|
|
|
595 |
for text, start, end in st.session_state['primary_transcript']:
|
596 |
display_text = text.lower()
|
597 |
if not search_query or search_query in display_text:
|
|
|
598 |
label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
|
599 |
if st.button(label, key=f"primary_{start}"):
|
600 |
st.session_state['current_time'] = start
|
601 |
st.rerun()
|
|
|
|
|
|
|
|
|
602 |
if st.session_state['english_transcript']:
|
603 |
st.markdown("### English Translation")
|
|
|
604 |
for text, start, end in st.session_state['english_transcript']:
|
605 |
display_text = text.lower()
|
606 |
if not search_query or search_query in display_text:
|
|
|
607 |
label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
|
608 |
if st.button(label, key=f"english_{start}"):
|
609 |
st.session_state['current_time'] = start
|
610 |
st.rerun()
|
|
|
|
|
|
|
|
|
611 |
if (st.session_state['language_code'] == 'en' or st.session_state['translate_to_english']) and not st.session_state['summary_generated']:
|
612 |
if st.button("Generate Summary"):
|
613 |
with st.spinner("Generating summary..."):
|
@@ -622,12 +1372,16 @@ def main():
|
|
622 |
if st.session_state['english_summary'] and st.session_state['summary_generated']:
|
623 |
st.markdown("### Summary")
|
624 |
st.write(st.session_state['english_summary'])
|
|
|
|
|
625 |
st.markdown("### Download Subtitles")
|
626 |
include_timeframe = st.checkbox("Include timeframe in subtitles", value=True)
|
627 |
transcript_to_download = st.session_state['primary_transcript'] or st.session_state['english_transcript']
|
628 |
if transcript_to_download:
|
629 |
srt_content = generate_srt(transcript_to_download, include_timeframe)
|
630 |
st.download_button(label="Download Subtitles (SRT)", data=srt_content, file_name="subtitles.srt", mime="text/plain")
|
|
|
|
|
631 |
st.markdown("### Edit Subtitles")
|
632 |
transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
|
633 |
if transcript_to_edit and st.button("Delete Subtitles"):
|
@@ -663,7 +1417,6 @@ def main():
|
|
663 |
|
664 |
if st.session_state['app_state'] == 'results' and st.session_state['edited_video_path']:
|
665 |
st.markdown("### Edited Video")
|
666 |
-
# Center the edited video
|
667 |
st.markdown('<div style="display: flex; justify-content: center;">', unsafe_allow_html=True)
|
668 |
st.video(st.session_state['edited_video_path'])
|
669 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
12 |
|
13 |
st.set_page_config(layout="wide")
|
14 |
|
15 |
+
# Updated CSS with video styling
|
16 |
st.markdown("""
|
17 |
<style>
|
18 |
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
|
|
|
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)
|
269 |
+
return f"{minutes}:{secs:02d}"
|
270 |
+
|
271 |
+
def seconds_to_srt_time(seconds):
|
272 |
+
hours = int(seconds // 3600)
|
273 |
+
minutes = int((seconds % 3600) // 60)
|
274 |
+
secs = int(seconds % 60)
|
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
|
281 |
+
self.status_text = None
|
282 |
+
def init_progress(self):
|
283 |
+
self.progress_bar = st.progress(0.0)
|
284 |
+
self.status_text = st.empty()
|
285 |
+
def update(self, progress: float, status: str):
|
286 |
+
progress = max(0.0, min(1.0, progress))
|
287 |
+
if self.progress_bar is not None:
|
288 |
+
self.progress_bar.progress(progress)
|
289 |
+
if self.status_text is not None:
|
290 |
+
self.status_text.text(status)
|
291 |
+
|
292 |
+
@st.cache_resource
|
293 |
+
def load_model(language='en', summarizer_type='bart'):
|
294 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
295 |
+
if language == 'ur':
|
296 |
+
processor = AutoProcessor.from_pretrained("GogetaBlueMUI/whisper-medium-ur-fleurs-v2")
|
297 |
+
model = AutoModelForSpeechSeq2Seq.from_pretrained("GogetaBlueMUI/whisper-medium-ur-fleurs-v2").to(device)
|
298 |
+
else:
|
299 |
+
processor = AutoProcessor.from_pretrained("openai/whisper-small")
|
300 |
+
model = AutoModelForSpeechSeq2Seq.from_pretrained("openai/whisper-small").to(device)
|
301 |
+
if device.type == "cuda":
|
302 |
+
model = model.half()
|
303 |
+
if summarizer_type == 'bart':
|
304 |
+
sum_tokenizer = AutoTokenizer.from_pretrained("facebook/bart-large-cnn")
|
305 |
+
sum_model = AutoModelForSeq2SeqLM.from_pretrained("facebook/bart-large-cnn").to(device)
|
306 |
+
else:
|
307 |
+
sum_tokenizer = AutoTokenizer.from_pretrained("pszemraj/led-large-book-summary")
|
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")
|
318 |
+
input_features = inputs.input_features.to(device)
|
319 |
+
if model.dtype == torch.float16:
|
320 |
+
input_features = input_features.half()
|
321 |
+
generate_kwargs = {
|
322 |
+
"task": task,
|
323 |
+
"language": "urdu" if language == "ur" else language,
|
324 |
+
"max_new_tokens": 128,
|
325 |
+
"return_timestamps": True
|
326 |
+
}
|
327 |
+
try:
|
328 |
+
with torch.no_grad():
|
329 |
+
outputs = model.generate(input_features, **generate_kwargs)
|
330 |
+
text = processor.decode(outputs[0], skip_special_tokens=True)
|
331 |
+
return [(text, start_time, start_time + len(audio) / sr)]
|
332 |
+
except Exception as e:
|
333 |
+
st.error(f"Transcription error: {str(e)}")
|
334 |
+
return [(f"Error: {str(e)}", start_time, start_time + len(audio) / sr)]
|
335 |
+
|
336 |
+
def process_chunks(chunks, sr, processor, model, device, language, chunk_duration, task="transcribe", transcript_file="temp_transcript.json"):
|
337 |
+
transcript = []
|
338 |
+
chunk_start = 0
|
339 |
+
total_chunks = len(chunks)
|
340 |
+
progress_bar = st.progress(0)
|
341 |
+
status_text = st.empty()
|
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:
|
352 |
+
json.dump(transcript, f, ensure_ascii=False)
|
353 |
+
chunk_start += chunk_duration
|
354 |
+
progress_bar.progress((i + 1) / total_chunks)
|
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 |
+
|
362 |
+
def summarize_text(text, tokenizer, model, device, summarizer_type='bart'):
|
363 |
+
if summarizer_type == 'bart':
|
364 |
+
max_input_length = 1024
|
365 |
+
max_summary_length = 150
|
366 |
+
chunk_size = 512
|
367 |
+
else:
|
368 |
+
max_input_length = 16384
|
369 |
+
max_summary_length = 512
|
370 |
+
chunk_size = 8192
|
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:
|
378 |
+
summaries = []
|
379 |
+
if num_tokens <= max_input_length:
|
380 |
+
truncated_inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=max_input_length).to(device)
|
381 |
+
with torch.no_grad():
|
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]
|
389 |
+
chunk_input_ids = torch.tensor([chunk_tokens]).to(device)
|
390 |
+
with torch.no_grad():
|
391 |
+
summary_ids = model.generate(chunk_input_ids, num_beams=4, max_length=max_summary_length // 2, min_length=25, early_stopping=True, temperature=0.7)
|
392 |
+
summaries.append(tokenizer.decode(summary_ids[0], skip_special_tokens=True))
|
393 |
+
combined_summary = " ".join(summaries)
|
394 |
+
combined_inputs = tokenizer(combined_summary, return_tensors="pt", truncation=True, max_length=max_input_length).to(device)
|
395 |
+
with torch.no_grad():
|
396 |
+
final_summary_ids = model.generate(combined_inputs["input_ids"], num_beams=4, max_length=max_summary_length, min_length=50, early_stopping=True, temperature=0.7)
|
397 |
+
summaries = [tokenizer.decode(final_summary_ids[0], skip_special_tokens=True)]
|
398 |
+
return " ".join(summaries)
|
399 |
+
except Exception as e:
|
400 |
+
st.error(f"Summarization error: {str(e)}")
|
401 |
+
return f"Error: {str(e)}"
|
402 |
+
|
403 |
+
def save_uploaded_file(uploaded_file):
|
404 |
+
try:
|
405 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file:
|
406 |
+
tmp_file.write(uploaded_file.read())
|
407 |
+
return tmp_file.name
|
408 |
+
except Exception as e:
|
409 |
+
st.error(f"Error saving uploaded file: {str(e)}")
|
410 |
+
return None
|
411 |
+
|
412 |
+
def merge_intervals(intervals):
|
413 |
+
if not intervals:
|
414 |
+
return []
|
415 |
+
intervals.sort(key=lambda x: x[0])
|
416 |
+
merged = [intervals[0]]
|
417 |
+
for current in intervals[1:]:
|
418 |
+
previous = merged[-1]
|
419 |
+
if previous[1] >= current[0]:
|
420 |
+
merged[-1] = (previous[0], max(previous[1], current[1]))
|
421 |
+
else:
|
422 |
+
merged.append(current)
|
423 |
+
return merged
|
424 |
+
|
425 |
+
def create_edited_video(video_path, transcript, keep_indices):
|
426 |
+
try:
|
427 |
+
intervals_to_keep = [(transcript[i][1], transcript[i][2]) for i in keep_indices]
|
428 |
+
merged_intervals = merge_intervals(intervals_to_keep)
|
429 |
+
temp_files = []
|
430 |
+
for j, (start, end) in enumerate(merged_intervals):
|
431 |
+
temp_file = f"temp_{j}.mp4"
|
432 |
+
ffmpeg.input(video_path, ss=start, to=end).output(temp_file, c='copy').run(overwrite_output=True, quiet=True)
|
433 |
+
temp_files.append(temp_file)
|
434 |
+
with open("list.txt", "w") as f:
|
435 |
+
for temp_file in temp_files:
|
436 |
+
f.write(f"file '{temp_file}'\n")
|
437 |
+
edited_video_path = "edited_video.mp4"
|
438 |
+
ffmpeg.input('list.txt', format='concat', safe=0).output(edited_video_path, c='copy').run(overwrite_output=True, quiet=True)
|
439 |
+
for temp_file in temp_files:
|
440 |
+
if os.path.exists(temp_file):
|
441 |
+
os.remove(temp_file)
|
442 |
+
if os.path.exists("list.txt"):
|
443 |
+
os.remove("list.txt")
|
444 |
+
return edited_video_path
|
445 |
+
except Exception as e:
|
446 |
+
st.error(f"Error creating edited video: {str(e)}")
|
447 |
+
return None
|
448 |
+
|
449 |
+
def generate_srt(transcript, include_timeframe=True):
|
450 |
+
srt_content = ""
|
451 |
+
for text, start, end in transcript:
|
452 |
+
if include_timeframe:
|
453 |
+
start_time = seconds_to_srt_time(start)
|
454 |
+
end_time = seconds_to_srt_time(end)
|
455 |
+
srt_content += f"{start_time} --> {end_time}\n{text}\n\n"
|
456 |
+
else:
|
457 |
+
srt_content += f"{text}\n\n"
|
458 |
+
return srt_content
|
459 |
+
|
460 |
+
# Main Function with Centered Video Display
|
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></ Stanislaus
|
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 variables
|
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:
|
578 |
+
if os.path.exists(audio_path):
|
579 |
+
os.remove(audio_path)
|
580 |
+
for temp_file in ["temp_primary_transcript.json", "temp_english_transcript.json"]:
|
581 |
+
if os.path.exists(temp_file):
|
582 |
+
os.remove(temp_file)
|
583 |
+
|
584 |
+
if st.session_state['app_state'] == 'results':
|
585 |
+
# Center the original video
|
586 |
+
st.markdown('<div style="display: flex; justify-content: center;">', unsafe_allow_html=True)
|
587 |
+
st.video(st.session_state['video_path'], start_time=st.session_state['current_time'])
|
588 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
589 |
+
|
590 |
+
st.session_state['show_timeframe'] = st.checkbox("Show timeframe in transcript", value=st.session_state['show_timeframe'])
|
591 |
+
st.markdown("### Search Subtitles")
|
592 |
+
search_query = st.text_input("Search subtitles...", value=st.session_state['search_query'], key="search_input")
|
593 |
+
st.session_state['search_query'] = search_query.lower()
|
594 |
+
|
595 |
+
# Primary Transcript
|
596 |
+
st.markdown(f"### {st.session_state['language']} Transcript")
|
597 |
+
primary_matches = 0
|
598 |
+
for text, start, end in st.session_state['primary_transcript']:
|
599 |
+
display_text = text.lower()
|
600 |
+
if not search_query or search_query in display_text:
|
601 |
+
primary_matches += 1
|
602 |
+
label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
|
603 |
+
if st.button(label, key=f"primary_{start}"):
|
604 |
+
st.session_state['current_time'] = start
|
605 |
+
st.rerun()
|
606 |
+
if primary_matches == 0 and search_query:
|
607 |
+
st.info("No matches found in primary transcript for the search query.")
|
608 |
+
|
609 |
+
# English Transcript (if available)
|
610 |
+
if st.session_state['english_transcript']:
|
611 |
+
st.markdown("### English Translation")
|
612 |
+
english_matches = 0
|
613 |
+
for text, start, end in st.session_state['english_transcript']:
|
614 |
+
display_text = text.lower()
|
615 |
+
if not search_query or search_query in display_text:
|
616 |
+
english_matches += 1
|
617 |
+
label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
|
618 |
+
if st.button(label, key=f"english_{start}"):
|
619 |
+
st.session_state['current_time'] = start
|
620 |
+
st.rerun()
|
621 |
+
if english_matches == 0 and search_query:
|
622 |
+
st.info("No matches found in English transcript for the search query.")
|
623 |
+
|
624 |
+
# Summary Generation
|
625 |
+
if (st.session_state['language_code'] == 'en' or st.session_state['translate_to_english']) and not st.session_state['summary_generated']:
|
626 |
+
if st.button("Generate Summary"):
|
627 |
+
with st.spinner("Generating summary..."):
|
628 |
+
try:
|
629 |
+
_, _, sum_tokenizer, sum_model, device = load_model(st.session_state['language_code'], st.session_state['summarizer_type'])
|
630 |
+
full_text = " ".join([text for text, _, _ in (st.session_state['english_transcript'] or st.session_state['primary_transcript'])])
|
631 |
+
english_summary = summarize_text(full_text, sum_tokenizer, sum_model, device, st.session_state['summarizer_type'])
|
632 |
+
st.session_state['english_summary'] = english_summary
|
633 |
+
st.session_state['summary_generated'] = True
|
634 |
+
except Exception as e:
|
635 |
+
st.error(f"Summary generation failed: {str(e)}")
|
636 |
+
if st.session_state['english_summary'] and st.session_state['summary_generated']:
|
637 |
+
st.markdown("### Summary")
|
638 |
+
st.write(st.session_state['english_summary'])
|
639 |
+
|
640 |
+
# Download Subtitles
|
641 |
+
st.markdown("### Download Subtitles")
|
642 |
+
include_timeframe = st.checkbox("Include timeframe in subtitles", value=True)
|
643 |
+
transcript_to_download = st.session_state['primary_transcript'] or st.session_state['english_transcript']
|
644 |
+
if transcript_to_download:
|
645 |
+
srt_content = generate_srt(transcript_to_download, include_timeframe)
|
646 |
+
st.download_button(label="Download Subtitles (SRT)", data=srt_content, file_name="subtitles.srt", mime="text/plain")
|
647 |
+
|
648 |
+
# Edit Subtitles
|
649 |
+
st.markdown("### Edit Subtitles")
|
650 |
+
transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
|
651 |
+
if transcript_to_edit and st.button("Delete Subtitles"):
|
652 |
+
st.session_state['app_state'] = 'editing'
|
653 |
+
st.rerun()
|
654 |
+
|
655 |
+
if st.session_state['app_state'] == 'editing':
|
656 |
+
st.markdown("### Delete Subtitles")
|
657 |
+
transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
|
658 |
+
for i, (text, start, end) in enumerate(transcript_to_edit):
|
659 |
+
st.write(f"{i}: [{format_time(start)} - {format_time(end)}] {text}")
|
660 |
+
indices_input = st.text_input("Enter the indices of subtitles to delete (comma-separated, e.g., 0,1,3):")
|
661 |
+
if st.button("Confirm Deletion"):
|
662 |
+
try:
|
663 |
+
delete_indices = [int(idx.strip()) for idx in indices_input.split(',') if idx.strip()]
|
664 |
+
delete_indices = [idx for idx in delete_indices if 0 <= idx < len(transcript_to_edit)]
|
665 |
+
keep_indices = [i for i in range(len(transcript_to_edit)) if i not in delete_indices]
|
666 |
+
if not keep_indices:
|
667 |
+
st.error("All subtitles are deleted. No video to generate.")
|
668 |
+
else:
|
669 |
+
edited_video_path = create_edited_video(st.session_state['video_path'], transcript_to_edit, keep_indices)
|
670 |
+
if edited_video_path:
|
671 |
+
st.session_state['edited_video_path'] = edited_video_path
|
672 |
+
st.session_state['app_state'] = 'results'
|
673 |
+
st.rerun()
|
674 |
+
except ValueError:
|
675 |
+
st.error("Invalid input. Please enter comma-separated integers.")
|
676 |
+
except Exception as e:
|
677 |
+
st.error(f"Error during video editing: {str(e)}")
|
678 |
+
if st.button("Cancel Deletion"):
|
679 |
+
st.session_state['app_state'] = 'results'
|
680 |
+
st.rerun()
|
681 |
+
|
682 |
+
if st.session_state['app_state'] == 'results' and st.session_state['edited_video_path']:
|
683 |
+
st.markdown("### Edited Video")
|
684 |
+
st.markdown('<div style="display: flex; justify-content: center;">', unsafe_allow_html=True)
|
685 |
+
st.video(st.session_state['edited_video_path'])
|
686 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
687 |
+
with open(st.session_state['edited_video_path'], "rb") as file:
|
688 |
+
st.download_button(label="Download Edited Video", data=file, file_name="edited_video.mp4", mime="video/mp4")
|
689 |
+
|
690 |
+
if st.session_state.get('video_path') and st.button("Reset"):
|
691 |
+
if st.session_state['video_path'] and os.path.exists(st.session_state['video_path']):
|
692 |
+
os.remove(st.session_state['video_path'])
|
693 |
+
if st.session_state['edited_video_path'] and os.path.exists(st.session_state['edited_video_path']):
|
694 |
+
os.remove(st.session_state['edited_video_path'])
|
695 |
+
st.session_state.clear()
|
696 |
+
st.rerun()
|
697 |
+
|
698 |
+
st.markdown("""
|
699 |
+
<div style='text-align: center;'>
|
700 |
+
<h2 style='color: black'>Why VidEp Stands Out</h2>
|
701 |
+
</div>
|
702 |
+
<div class="feature-box">
|
703 |
+
<div class="feature"><i class="fas fa-cloud-upload-alt"></i><br>Cloud Upload</div>
|
704 |
+
<div class="feature"><i class="fas fa-search"></i><br>Smart Search</div>
|
705 |
+
<div class="feature"><i class="fas fa-edit"></i><br>Easy Editing</div>
|
706 |
+
<div class="feature"><i class="fas fa-file-alt"></i><br>AI Summary</div>
|
707 |
+
</div>
|
708 |
+
""", unsafe_allow_html=True)
|
709 |
+
|
710 |
+
st.markdown("""
|
711 |
+
<div id="about" class="about-section" style="padding: 3rem 2rem; background: write a complete Streamlit application that includes all the fixes and improvements for the search function, ensuring it works as expected. Below is the complete corrected code with the search function fixed, along with explanations of the changes made.
|
712 |
+
|
713 |
+
---
|
714 |
+
|
715 |
+
### Explanation of Fixes and Improvements
|
716 |
+
|
717 |
+
1. **Search Query Handling**:
|
718 |
+
- The search query is captured using `st.text_input`, and the value is stored in `st.session_state['search_query']` as a lowercase string to ensure case-insensitive searching.
|
719 |
+
- The transcript display logic now checks if the search query is empty or if it exists within the lowercase version of each transcript segment’s text (`display_text`).
|
720 |
+
- A counter (`primary_matches` and `english_matches`) is introduced to track the number of matching segments. If no matches are found and a search query is provided, an `st.info` message is displayed to inform the user.
|
721 |
+
|
722 |
+
2. **Case-Insensitive Search**:
|
723 |
+
- Both the search query and the transcript text are converted to lowercase before comparison, ensuring the search is case-insensitive (`search_query.lower()` and `text.lower()`).
|
724 |
+
|
725 |
+
3. **UI Feedback**:
|
726 |
+
- When no matches are found for the search query, a message like "No matches found in primary transcript for the search query" is displayed using `st.info`. This improves user experience by providing clear feedback.
|
727 |
+
|
728 |
+
4. **Streamlit Rerun**:
|
729 |
+
- The search input is tied to `st.session_state['search_query']`, and Streamlit’s reactivity ensures that changing the search query updates the displayed transcript segments automatically without requiring an explicit rerun (unless a button is clicked for navigation).
|
730 |
+
|
731 |
+
5. **Code Organization**:
|
732 |
+
- The transcript display logic is split clearly between primary and English transcripts, with separate match counters to handle each case independently.
|
733 |
+
- The code remains modular, with no changes to other functionalities like transcription, summarization, or video editing, ensuring the fix is isolated to the search function.
|
734 |
+
|
735 |
+
---
|
736 |
+
|
737 |
+
### Complete Corrected Code
|
738 |
+
|
739 |
+
```python
|
740 |
+
import streamlit as st
|
741 |
+
import tempfile
|
742 |
+
import os
|
743 |
+
import torch
|
744 |
+
from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq, AutoTokenizer, AutoModelForSeq2SeqLM
|
745 |
+
import librosa
|
746 |
+
import numpy as np
|
747 |
+
import ffmpeg
|
748 |
+
import time
|
749 |
+
import json
|
750 |
+
import psutil
|
751 |
+
|
752 |
+
st.set_page_config(layout="wide")
|
753 |
+
|
754 |
+
# Updated CSS with video styling
|
755 |
+
st.markdown("""
|
756 |
+
<style>
|
757 |
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
|
758 |
+
|
759 |
+
.stApp {
|
760 |
+
background-color: #ffffff;
|
761 |
+
font-family: 'Poppins', sans-serif;
|
762 |
+
color: #1a1a1a;
|
763 |
+
}
|
764 |
+
|
765 |
+
/* Hide Streamlit's default elements */
|
766 |
+
[data-testid="stToolbar"], [data-testid="stDecoration"], [data-testid="stStatusWidget"] {
|
767 |
+
display: none;
|
768 |
+
}
|
769 |
+
|
770 |
+
/* Header */
|
771 |
+
.header {
|
772 |
+
background: #ffffff;
|
773 |
+
padding: 1rem 2rem;
|
774 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
775 |
+
display: flex;
|
776 |
+
justify-content: space-between;
|
777 |
+
align-items: center;
|
778 |
+
position: sticky;
|
779 |
+
top: 0;
|
780 |
+
z-index: 100;
|
781 |
+
}
|
782 |
+
.logo img {
|
783 |
+
height: 60px;
|
784 |
+
width: auto;
|
785 |
+
}
|
786 |
+
.navbar {
|
787 |
+
list-style: none;
|
788 |
+
display: flex;
|
789 |
+
gap: 1.5rem;
|
790 |
+
margin: 0;
|
791 |
+
}
|
792 |
+
.navbar li a {
|
793 |
+
text-decoration: none;
|
794 |
+
font-size: 28px;
|
795 |
+
font-weight: bold;
|
796 |
+
color: # = 1.0rem;
|
797 |
+
position: relative;
|
798 |
+
padding: 10px 15px;
|
799 |
+
transition: text-shadow 0.3s ease-in-out;
|
800 |
+
text-shadow: 5px 5px 12px rgba(0, 0, 0, 0.5);
|
801 |
+
}
|
802 |
+
.navbar li a:hover {
|
803 |
+
color: #ff6f61;
|
804 |
+
}
|
805 |
+
|
806 |
+
/* Hero Section */
|
807 |
+
.hero {
|
808 |
+
background: linear-gradient(to right, #2b5876, #4e4376);
|
809 |
+
background-size: cover;
|
810 |
+
color: #ffffff;
|
811 |
+
padding: 2rem 2rem;
|
812 |
+
border-radius: 1rem;
|
813 |
+
text-align: center;
|
814 |
+
margin: 2rem 0;
|
815 |
+
max-height: 200px;
|
816 |
+
}
|
817 |
+
.hero h esque
|
818 |
+
}
|
819 |
+
.hero p {
|
820 |
+
font-size: 1.2rem;
|
821 |
+
font-weight: 300;
|
822 |
+
}
|
823 |
+
|
824 |
+
/* Feature Section */
|
825 |
+
.feature-box {
|
826 |
+
display: flex;
|
827 |
+
justify-content: center;
|
828 |
+
gap: 1.5rem;
|
829 |
+
margin: 3rem 0;
|
830 |
+
flex-wrap: wrap;
|
831 |
+
}
|
832 |
+
.feature {
|
833 |
+
background: #f8f9fa;
|
834 |
+
padding: 1.5rem;
|
835 |
+
border-radius: 1rem;
|
836 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
837 |
+
width: 200px;
|
838 |
+
text-align: center;
|
839 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
840 |
+
}
|
841 |
+
.feature:hover {
|
842 |
+
transform: translateY(-8px) scale(1.03);
|
843 |
+
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25);
|
844 |
+
transition: all 0.3s ease;
|
845 |
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
846 |
+
background-color: #fff;
|
847 |
+
filter: brightness(1.05);
|
848 |
+
z-index: 10;
|
849 |
+
}
|
850 |
+
.feature i {
|
851 |
+
font-size: 1.5rem;
|
852 |
+
color: #2196f3;
|
853 |
+
margin-bottom: 0.5rem;
|
854 |
+
}
|
855 |
+
|
856 |
+
/* Plans Section */
|
857 |
+
.plans {
|
858 |
+
padding: 3rem 2rem;
|
859 |
+
background: #f1f4f8;
|
860 |
+
border-radius: 1rem;
|
861 |
+
}
|
862 |
+
.plan-box {
|
863 |
+
display: flex;
|
864 |
+
justify-content: center;
|
865 |
+
gap: 1.5rem;
|
866 |
+
flex-wrap: wrap;
|
867 |
+
}
|
868 |
+
.plan {
|
869 |
+
background: #ffffff;
|
870 |
+
padding: 2rem;
|
871 |
+
border-radius: 1rem;
|
872 |
+
width: 250px;
|
873 |
+
text-align: center;
|
874 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
875 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
876 |
+
border-top: 4px solid #28a745;
|
877 |
+
height: 290px;
|
878 |
+
padding-top: 10px;
|
879 |
+
}
|
880 |
+
.plan:hover {
|
881 |
+
transform: translateY(-5px);
|
882 |
+
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
|
883 |
+
}
|
884 |
+
.plan h3 {
|
885 |
+
font-size: 1.5rem;
|
886 |
+
margin-bottom: 0.5rem;
|
887 |
+
}
|
888 |
+
.plan.free { border-top: 4px solid #28a745; }
|
889 |
+
.plan.premium { border-top: 4px solid #ff6f61; }
|
890 |
+
.plan.business { border-top: 4px solid #2196f3; }
|
891 |
+
|
892 |
+
/* Buttons */
|
893 |
+
.stButton>button {
|
894 |
+
background: linear-gradient(135deg, #ff6f61, #ff8a65) !important;
|
895 |
+
color: #ffffff !important;
|
896 |
+
font-weight: 600 !important;
|
897 |
+
padding: 0.75rem 1.5rem !important;
|
898 |
+
border-radius: 0.5rem !important;
|
899 |
+
border: none !important;
|
900 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease !important;
|
901 |
+
}
|
902 |
+
.stButton>button:hover {
|
903 |
+
transform: translateY(-2px) !important;
|
904 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2) !important;
|
905 |
+
}
|
906 |
+
|
907 |
+
/* File Uploader */
|
908 |
+
.uploadedFile {
|
909 |
+
border: 2px dashed #2196f3;
|
910 |
+
border-radius: 1rem;
|
911 |
+
padding: 2rem;
|
912 |
+
background: #f8f9fa;
|
913 |
+
margin: 2rem 0;
|
914 |
+
}
|
915 |
+
|
916 |
+
/* Progress Bar */
|
917 |
+
.stProgress > div > div {
|
918 |
+
background: linear-gradient(90deg, #2196f3, #4fc3f7) !important;
|
919 |
+
}
|
920 |
+
|
921 |
+
/* Text Area */
|
922 |
+
.stTextArea textarea {
|
923 |
+
border-radius: 0.5rem;
|
924 |
+
border: 1px solid #e0e0e0;
|
925 |
+
padding: 1rem;
|
926 |
+
font-family: 'Poppins', sans-serif;
|
927 |
+
}
|
928 |
+
|
929 |
+
/* Video player styling */
|
930 |
video {
|
931 |
display: block;
|
932 |
width: 350px !important;
|
|
|
1327 |
st.markdown("### Search Subtitles")
|
1328 |
search_query = st.text_input("Search subtitles...", value=st.session_state['search_query'], key="search_input")
|
1329 |
st.session_state['search_query'] = search_query.lower()
|
1330 |
+
|
1331 |
+
# Primary Transcript
|
1332 |
st.markdown(f"### {st.session_state['language']} Transcript")
|
1333 |
+
primary_matches = 0
|
1334 |
for text, start, end in st.session_state['primary_transcript']:
|
1335 |
display_text = text.lower()
|
1336 |
if not search_query or search_query in display_text:
|
1337 |
+
primary_matches += 1
|
1338 |
label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
|
1339 |
if st.button(label, key=f"primary_{start}"):
|
1340 |
st.session_state['current_time'] = start
|
1341 |
st.rerun()
|
1342 |
+
if primary_matches == 0 and search_query:
|
1343 |
+
st.info("No matches found in primary transcript for the search query.")
|
1344 |
+
|
1345 |
+
# English Transcript (if available)
|
1346 |
if st.session_state['english_transcript']:
|
1347 |
st.markdown("### English Translation")
|
1348 |
+
english_matches = 0
|
1349 |
for text, start, end in st.session_state['english_transcript']:
|
1350 |
display_text = text.lower()
|
1351 |
if not search_query or search_query in display_text:
|
1352 |
+
english_matches += 1
|
1353 |
label = f"[{format_time(start)} - {format_time(end)}] {text}" if st.session_state['show_timeframe'] else text
|
1354 |
if st.button(label, key=f"english_{start}"):
|
1355 |
st.session_state['current_time'] = start
|
1356 |
st.rerun()
|
1357 |
+
if english_matches == 0 and search_query:
|
1358 |
+
st.info("No matches found in English transcript for the search query.")
|
1359 |
+
|
1360 |
+
# Summary Generation
|
1361 |
if (st.session_state['language_code'] == 'en' or st.session_state['translate_to_english']) and not st.session_state['summary_generated']:
|
1362 |
if st.button("Generate Summary"):
|
1363 |
with st.spinner("Generating summary..."):
|
|
|
1372 |
if st.session_state['english_summary'] and st.session_state['summary_generated']:
|
1373 |
st.markdown("### Summary")
|
1374 |
st.write(st.session_state['english_summary'])
|
1375 |
+
|
1376 |
+
# Download Subtitles
|
1377 |
st.markdown("### Download Subtitles")
|
1378 |
include_timeframe = st.checkbox("Include timeframe in subtitles", value=True)
|
1379 |
transcript_to_download = st.session_state['primary_transcript'] or st.session_state['english_transcript']
|
1380 |
if transcript_to_download:
|
1381 |
srt_content = generate_srt(transcript_to_download, include_timeframe)
|
1382 |
st.download_button(label="Download Subtitles (SRT)", data=srt_content, file_name="subtitles.srt", mime="text/plain")
|
1383 |
+
|
1384 |
+
# Edit Subtitles
|
1385 |
st.markdown("### Edit Subtitles")
|
1386 |
transcript_to_edit = st.session_state['primary_transcript'] or st.session_state['english_transcript']
|
1387 |
if transcript_to_edit and st.button("Delete Subtitles"):
|
|
|
1417 |
|
1418 |
if st.session_state['app_state'] == 'results' and st.session_state['edited_video_path']:
|
1419 |
st.markdown("### Edited Video")
|
|
|
1420 |
st.markdown('<div style="display: flex; justify-content: center;">', unsafe_allow_html=True)
|
1421 |
st.video(st.session_state['edited_video_path'])
|
1422 |
st.markdown('</div>', unsafe_allow_html=True)
|