Spaces:
Sleeping
Sleeping
File size: 12,293 Bytes
6f9ac43 26b8f56 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
"""
Video Keyword Finder with Gradio Interface
========================================
A Python script that finds and timestamps specific keywords within video files.
It transcribes the audio using Whisper AI and finds all occurrences of specified keywords
with their timestamps and surrounding context. Supports both local video files and YouTube URLs.
Requirements
-----------
- Python 3.8 or higher
- ffmpeg (must be installed and accessible in system PATH)
- GPU recommended but not required
"""
import whisper_timestamped as whisper
import datetime
import os
import tempfile
import yt_dlp
import re
import logging
import math
import subprocess
import glob
import gradio as gr
# Set up logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler()
]
)
def parse_time(time_str):
"""Convert time string (HH:MM:SS) to seconds"""
if not time_str or time_str.strip() == "":
return None
try:
time_parts = list(map(int, time_str.split(':')))
if len(time_parts) == 3: # HH:MM:SS
return time_parts[0] * 3600 + time_parts[1] * 60 + time_parts[2]
elif len(time_parts) == 2: # MM:SS
return time_parts[0] * 60 + time_parts[1]
else:
return int(time_str) # Just seconds
except:
raise ValueError("Time must be in HH:MM:SS, MM:SS, or seconds format")
def format_time(seconds):
"""Convert seconds to HH:MM:SS format"""
return str(datetime.timedelta(seconds=int(seconds)))
def is_youtube_url(url):
"""Check if the provided string is a YouTube URL"""
if not url or url.strip() == "":
return False
youtube_regex = (
r'(https?://)?(www\.)?'
r'(youtube|youtu|youtube-nocookie)\.(com|be)/'
r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
)
return bool(re.match(youtube_regex, url))
def download_youtube_video(url):
"""Download YouTube video and return path to temporary file"""
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, 'youtube_video.mp4')
ydl_opts = {
'format': 'best[ext=mp4]',
'outtmpl': temp_file,
'quiet': False,
'progress_hooks': [lambda d: logging.info(f"Download progress: {d.get('status', 'unknown')}")],
'socket_timeout': 30,
}
try:
logging.info(f"Starting download of YouTube video: {url}")
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=True)
logging.info(f"Video info extracted: {info.get('title', 'Unknown title')}")
if os.path.exists(temp_file):
file_size = os.path.getsize(temp_file)
logging.info(f"Download complete. File size: {file_size / (1024*1024):.2f} MB")
return temp_file
else:
raise Exception("Download completed but file not found")
except Exception as e:
logging.error(f"Error downloading YouTube video: {str(e)}")
raise
def get_video_duration(video_path):
"""Get video duration using ffprobe"""
cmd = [
'ffprobe',
'-v', 'error',
'-show_entries', 'format=duration',
'-of', 'default=noprint_wrappers=1:nokey=1',
video_path
]
try:
output = subprocess.check_output(cmd).decode().strip()
return float(output)
except subprocess.CalledProcessError as e:
logging.error(f"Error getting video duration: {str(e)}")
raise
def split_video(video_path, segment_duration=120):
"""Split video into segments using ffmpeg"""
try:
temp_dir = tempfile.gettempdir()
segment_pattern = os.path.join(temp_dir, 'segment_%03d.mp4')
# Remove any existing segments
for old_segment in glob.glob(os.path.join(temp_dir, 'segment_*.mp4')):
try:
os.remove(old_segment)
except:
pass
# Split video into segments
cmd = [
'ffmpeg',
'-i', video_path,
'-f', 'segment',
'-segment_time', str(segment_duration),
'-c', 'copy',
'-reset_timestamps', '1',
segment_pattern
]
logging.info("Splitting video into segments...")
subprocess.run(cmd, check=True, capture_output=True)
# Get list of generated segments
segments = sorted(glob.glob(os.path.join(temp_dir, 'segment_*.mp4')))
logging.info(f"Created {len(segments)} segments")
return segments
except Exception as e:
logging.error(f"Error splitting video: {str(e)}")
raise
def process_segments(segments, keywords):
"""Process each segment sequentially"""
results = {keyword: [] for keyword in keywords}
# Load whisper model once
logging.info("Loading Whisper model...")
model = whisper.load_model("base")
for i, segment_path in enumerate(segments):
try:
segment_num = int(re.search(r'segment_(\d+)', segment_path).group(1))
start_time = segment_num * 120 # Each segment is 120 seconds
logging.info(f"Processing segment {i+1}/{len(segments)} (starting at {format_time(start_time)})")
# Transcribe segment
audio = whisper.load_audio(segment_path)
transcription = whisper.transcribe(model, audio)
# Process results
for segment in transcription['segments']:
text = segment['text'].lower()
timestamp = segment['start'] + start_time # Adjust timestamp relative to full video
for keyword in keywords:
if keyword.lower() in text:
results[keyword].append((
timestamp,
segment['text']
))
logging.info(f"Found keyword '{keyword}' at {format_time(timestamp)}: {segment['text']}")
except Exception as e:
logging.error(f"Error processing segment {segment_path}: {str(e)}")
continue
finally:
# Clean up segment file
try:
os.remove(segment_path)
except:
pass
return results
def find_keywords_in_video(video_path, keywords, begin_time=None, end_time=None):
"""Find timestamps for keywords in video transcription"""
try:
logging.info(f"Processing video: {video_path}")
logging.info(f"Searching for keywords: {keywords}")
# Convert keywords string to list and clean up
if isinstance(keywords, str):
keywords = [k.strip() for k in keywords.split(',')]
# Get video duration
duration = get_video_duration(video_path)
logging.info(f"Video duration: {duration:.2f} seconds")
# Set time bounds
start = parse_time(begin_time) if begin_time else 0
end = min(parse_time(end_time) if end_time else duration, duration)
if start is not None and end is not None and start >= end:
raise ValueError("End time must be greater than start time")
# Split video into segments
segment_duration = 120 # 2 minutes per segment
segments = split_video(video_path, segment_duration)
# Process segments sequentially
results = process_segments(segments, keywords)
return results
except Exception as e:
logging.error(f"Error processing video: {str(e)}")
raise
def process_video(video_file, youtube_url, keywords_input, start_time="0:00", end_time=None):
"""
Process video file or YouTube URL and find keywords
"""
try:
# Convert keywords string to list
keywords = [k.strip() for k in keywords_input.split(',') if k.strip()]
if not keywords:
return "Please enter at least one keyword (separated by commas)"
# Handle input source
if youtube_url and youtube_url.strip():
if not is_youtube_url(youtube_url):
return "Invalid YouTube URL. Please provide a valid URL."
video_path = download_youtube_video(youtube_url)
cleanup_needed = True
elif video_file is not None:
video_path = video_file
cleanup_needed = False
else:
return "Please provide either a video file or a YouTube URL"
try:
# Find keywords
results = find_keywords_in_video(
video_path=video_path,
keywords=keywords,
begin_time=start_time if start_time else None,
end_time=end_time if end_time else None
)
# Format results
output = []
total_matches = sum(len(matches) for matches in results.values())
output.append(f"Total matches found: {total_matches}\n")
if total_matches == 0:
output.append("No matches found for any keywords.")
else:
for keyword, matches in results.items():
if matches:
output.append(f"\nResults for '{keyword}':")
for timestamp, context in matches:
output.append(f"[{format_time(timestamp)}] {context}")
else:
output.append(f"\nNo occurrences found for '{keyword}'")
return "\n".join(output)
finally:
# Cleanup temporary files
if cleanup_needed and os.path.exists(video_path):
try:
os.remove(video_path)
except:
pass
except Exception as e:
logging.error(f"Error processing video: {str(e)}")
return f"Error processing video: {str(e)}"
# Create Gradio interface
with gr.Blocks(title="Video Keyword Finder", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🎥 Video Keyword Finder
Find timestamps for specific keywords in your videos using AI transcription.
Upload a video file or provide a YouTube URL, then enter the keywords you want to find.
""")
with gr.Row():
with gr.Column():
video_input = gr.File(
label="Upload Video File",
file_types=["video"],
type="filepath"
)
youtube_url = gr.Textbox(
label="Or Enter YouTube URL",
placeholder="https://www.youtube.com/watch?v=..."
)
keywords = gr.Textbox(
label="Keywords (comma-separated)",
placeholder="enter, your, keywords, here"
)
with gr.Row():
start_time = gr.Textbox(
label="Start Time (HH:MM:SS)",
placeholder="0:00",
value="0:00"
)
end_time = gr.Textbox(
label="End Time (HH:MM:SS)",
placeholder="Optional"
)
submit_btn = gr.Button("Find Keywords", variant="primary")
with gr.Column():
output = gr.Textbox(
label="Results",
placeholder="Keywords and timestamps will appear here...",
lines=20
)
gr.Markdown("""
### Instructions:
1. Upload a video file or paste a YouTube URL
2. Enter keywords separated by commas (e.g., "hello, world, python")
3. Optionally set start and end times (format: HH:MM:SS)
4. Click "Find Keywords" and wait for results
Note: Processing time depends on video length. A 1-hour video typically takes 15-30 minutes.
""")
submit_btn.click(
fn=process_video,
inputs=[video_input, youtube_url, keywords, start_time, end_time],
outputs=output
)
if __name__ == "__main__":
demo.launch(share=True, debug=True)
|