|
|
| import spaces |
| import gradio as gr |
| import pandas as pd |
| import yt_dlp |
| import os |
| from semantic_chunkers import StatisticalChunker |
| from semantic_router.encoders import HuggingFaceEncoder |
| from faster_whisper import WhisperModel |
| import io |
|
|
| |
| def download_youtube_audio(url, preferred_quality="192"): |
| ydl_opts = { |
| 'format': 'bestaudio/best', |
| 'postprocessors': [{ |
| 'key': 'FFmpegExtractAudio', |
| 'preferredcodec': 'mp3', |
| 'preferredquality': preferred_quality, |
| }], |
| 'outtmpl': '-', |
| } |
|
|
| try: |
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: |
| info_dict = ydl.extract_info(url, download=False) |
| video_title = info_dict.get('title', None) |
| print(f"Downloading audio for: {video_title}") |
|
|
| |
| audio_buffer = io.BytesIO() |
| ydl.download([url], audio_buffer) |
| audio_buffer.seek(0) |
| print("Audio download complete") |
| return audio_buffer |
|
|
| except yt_dlp.utils.DownloadError as e: |
| print(f"Error downloading audio: {e}") |
| return None |
|
|
| |
| @spaces.GPU |
| def transcribe(audio_buffer, model_name="medium"): |
| model = WhisperModel(model_name) |
| print("Reading audio buffer") |
| |
| |
| segments, info = model.transcribe(audio_buffer) |
| return segments |
|
|
| |
| @spaces.GPU |
| def process_segments(segments): |
| result = {} |
| print("Processing...") |
| for i, segment in enumerate(segments): |
| chunk_id = f"chunk_{i}" |
| result[chunk_id] = { |
| 'chunk_id': segment.id, |
| 'chunk_length': segment.end - segment.start, |
| 'text': segment.text, |
| 'start_time': segment.start, |
| 'end_time': segment.end |
| } |
| df = pd.DataFrame.from_dict(result, orient='index') |
| df.to_csv('final.csv') |
| return df |
|
|
| |
| @spaces.GPU |
| def generate_transcript(youtube_url, model_name="large-v3"): |
| audio_buffer = download_youtube_audio(youtube_url) |
| if audio_buffer is None: |
| return "Error downloading audio" |
| |
| segments = transcribe(audio_buffer, model_name) |
| df = process_segments(segments) |
| |
| lis = list(df['text']) |
| encoder = HuggingFaceEncoder(name="sentence-transformers/all-MiniLM-L6-v2") |
| chunker = StatisticalChunker(encoder=encoder, dynamic_threshold=True, min_split_tokens=30, max_split_tokens=40, window_size=2, enable_statistics=False) |
| chunks = chunker._chunk(lis) |
| |
| row_index = 0 |
| for i in range(len(chunks)): |
| for j in range(len(chunks[i].splits)): |
| df.at[row_index, 'chunk_id2'] = f'chunk_{i}' |
| row_index += 1 |
| |
| grouped = df.groupby('chunk_id2').agg({ |
| 'start_time': 'min', |
| 'end_time': 'max', |
| 'text': lambda x: ' '.join(x), |
| 'chunk_id': list |
| }).reset_index() |
| |
| grouped = grouped.rename(columns={'chunk_id': 'chunk_ids'}) |
| grouped['chunk_length'] = grouped['end_time'] - grouped['start_time'] |
| grouped['chunk_id'] = grouped['chunk_id2'] |
| grouped = grouped.drop(columns=['chunk_id2', 'chunk_ids']) |
| grouped.to_csv('final.csv') |
| df = pd.read_csv("final.csv") |
| transcripts = df.to_dict(orient='records') |
| |
| return transcripts |
|
|
| |
| def download_video(youtube_url): |
| ydl_opts = { |
| 'format': 'mp4', |
| 'outtmpl': 'downloaded_video.mp4', |
| 'quiet': True |
| } |
|
|
| with yt_dlp.YoutubeDL({'quiet': True}) as ydl: |
| info_dict = ydl.extract_info(youtube_url, download=False) |
| video_path = 'downloaded_video.mp4' |
|
|
| if not os.path.exists(video_path): |
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: |
| ydl.download([youtube_url]) |
|
|
| transcripts = generate_transcript(youtube_url) |
| transcript_html = "" |
| for t in transcripts: |
| transcript_html += f'<div class="transcript-block"><a href="#" onclick="var video = document.getElementById(\'video-player\').querySelector(\'video\'); video.currentTime={t["start_time"]}; return false;">' \ |
| f'[{t["start_time"]:.2f} - {t["end_time"]:.2f}]<br>{t["text"]}</a></div>' |
| |
| return video_path, transcript_html |
|
|
| |
| def search_transcript(keyword): |
| transcripts = pd.read_csv("final.csv").to_dict(orient='records') |
| search_results = "" |
| for t in transcripts: |
| if keyword.lower() in t['text'].lower(): |
| search_results += f'<div class="transcript-block"><a href="#" onclick="var video = document.getElementById(\'video-player\').querySelector(\'video\'); video.currentTime={t["start_time"]}; return false;">' \ |
| f'[{t["start_time"]:.2f} - {t["end_time"]:.2f}]<br>{t["text"]}</a></div>' |
| return search_results |
|
|
| |
| css = """ |
| .fixed-video { width: 480px !important; height: 270px !important; } |
| .fixed-transcript { width: 480px !important; height: 270px !important; overflow-y: auto; } |
| .transcript-block { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px; background-color: #f9f9f9; } |
| .transcript-block a { text-decoration: none; color: #007bff; } |
| .transcript-block a:hover { text-decoration: underline; } |
| """ |
|
|
| |
| with gr.Blocks(css=css) as demo: |
| gr.Markdown("# YouTube Video Player with Clickable Transcript") |
|
|
| with gr.Row(): |
| youtube_url = gr.Textbox(label="YouTube URL", placeholder="Enter YouTube video link here") |
| download_button = gr.Button("Download and Display Transcript") |
| |
| with gr.Row(): |
| video = gr.Video(label="Video Player", elem_id="video-player", elem_classes="fixed-video") |
| transcript_display = gr.HTML(label="Transcript", elem_classes="fixed-transcript") |
|
|
| with gr.Row(): |
| search_box = gr.Textbox(label="Search Transcript", placeholder="Enter keyword to search") |
| search_button = gr.Button("Search") |
| search_results_display = gr.HTML(label="Search Results", elem_classes="fixed-transcript") |
|
|
| |
| def display_transcript(youtube_url): |
| video_path, transcript_html = download_video(youtube_url) |
| return video_path, transcript_html |
|
|
| download_button.click(fn=display_transcript, inputs=youtube_url, outputs=[video, transcript_display]) |
|
|
| |
| search_button.click(fn=search_transcript, inputs=search_box, outputs=search_results_display) |
|
|
| |
| demo.launch() |
|
|