Automatically describes live video content every 10 seconds using AI
Size
11.8 KB
Version
1.0.1
Created
Mar 12, 2026
Updated
9 days ago
1// ==UserScript==
2// @name Live Video Description Tool
3// @description Automatically describes live video content every 10 seconds using AI
4// @version 1.0.1
5// @match https://*.youtube.com/*
6// @match https://*.twitch.tv/*
7// @match https://*.vimeo.com/*
8// @match https://*.dailymotion.com/*
9// @match https://*.facebook.com/*
10// @match https://*.instagram.com/*
11// @match https://*.tiktok.com/*
12// @grant GM.getValue
13// @grant GM.setValue
14// ==/UserScript==
15(function() {
16 'use strict';
17
18 let descriptionInterval = null;
19 let currentVideoUrl = null;
20 let panel = null;
21 let descriptionList = null;
22
23 // Debounce function for performance
24 function debounce(func, wait) {
25 let timeout;
26 return function executedFunction(...args) {
27 const later = () => {
28 clearTimeout(timeout);
29 func(...args);
30 };
31 clearTimeout(timeout);
32 timeout = setTimeout(later, wait);
33 };
34 }
35
36 // Create moveable UI panel
37 function createPanel() {
38 if (panel) return;
39
40 panel = document.createElement('div');
41 panel.id = 'video-description-panel';
42 panel.style.cssText = `
43 position: fixed;
44 top: 100px;
45 right: 20px;
46 width: 350px;
47 max-height: 500px;
48 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
49 border-radius: 12px;
50 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
51 z-index: 999999;
52 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
53 overflow: hidden;
54 cursor: move;
55 `;
56
57 // Create header
58 const header = document.createElement('div');
59 header.style.cssText = `
60 background: rgba(0, 0, 0, 0.2);
61 padding: 15px;
62 color: #ffffff;
63 font-weight: 600;
64 font-size: 16px;
65 display: flex;
66 justify-content: space-between;
67 align-items: center;
68 cursor: move;
69 `;
70 header.textContent = 'π₯ Live Video Descriptions';
71
72 // Create close button
73 const closeBtn = document.createElement('button');
74 closeBtn.textContent = 'Γ';
75 closeBtn.style.cssText = `
76 background: rgba(255, 255, 255, 0.2);
77 border: none;
78 color: #ffffff;
79 font-size: 24px;
80 width: 30px;
81 height: 30px;
82 border-radius: 50%;
83 cursor: pointer;
84 display: flex;
85 align-items: center;
86 justify-content: center;
87 transition: background 0.2s;
88 `;
89 closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.3)';
90 closeBtn.onmouseout = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.2)';
91 closeBtn.onclick = (e) => {
92 e.stopPropagation();
93 panel.remove();
94 panel = null;
95 stopDescribing();
96 };
97 header.appendChild(closeBtn);
98
99 // Create content area
100 const content = document.createElement('div');
101 content.style.cssText = `
102 padding: 15px;
103 max-height: 420px;
104 overflow-y: auto;
105 background: #ffffff;
106 `;
107
108 // Create status indicator
109 const statusDiv = document.createElement('div');
110 statusDiv.id = 'description-status';
111 statusDiv.style.cssText = `
112 padding: 10px;
113 background: #f0f0f0;
114 border-radius: 8px;
115 margin-bottom: 15px;
116 font-size: 14px;
117 color: #333333;
118 text-align: center;
119 `;
120 statusDiv.textContent = 'β³ Initializing...';
121
122 // Create description list
123 descriptionList = document.createElement('div');
124 descriptionList.id = 'description-list';
125 descriptionList.style.cssText = `
126 display: flex;
127 flex-direction: column;
128 gap: 10px;
129 `;
130
131 content.appendChild(statusDiv);
132 content.appendChild(descriptionList);
133 panel.appendChild(header);
134 panel.appendChild(content);
135 document.body.appendChild(panel);
136
137 // Make panel draggable
138 makeDraggable(panel, header);
139
140 console.log('Video description panel created');
141 }
142
143 // Make element draggable
144 function makeDraggable(element, handle) {
145 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
146
147 handle.onmousedown = dragMouseDown;
148
149 function dragMouseDown(e) {
150 e.preventDefault();
151 pos3 = e.clientX;
152 pos4 = e.clientY;
153 document.onmouseup = closeDragElement;
154 document.onmousemove = elementDrag;
155 }
156
157 function elementDrag(e) {
158 e.preventDefault();
159 pos1 = pos3 - e.clientX;
160 pos2 = pos4 - e.clientY;
161 pos3 = e.clientX;
162 pos4 = e.clientY;
163 element.style.top = (element.offsetTop - pos2) + "px";
164 element.style.left = (element.offsetLeft - pos1) + "px";
165 element.style.right = 'auto';
166 }
167
168 function closeDragElement() {
169 document.onmouseup = null;
170 document.onmousemove = null;
171 }
172 }
173
174 // Update status message
175 function updateStatus(message, isError = false) {
176 const statusDiv = document.getElementById('description-status');
177 if (statusDiv) {
178 statusDiv.textContent = message;
179 statusDiv.style.background = isError ? '#ffebee' : '#f0f0f0';
180 statusDiv.style.color = isError ? '#c62828' : '#333333';
181 }
182 }
183
184 // Add description to the list
185 function addDescription(description, timestamp) {
186 if (!descriptionList) return;
187
188 const descItem = document.createElement('div');
189 descItem.style.cssText = `
190 padding: 12px;
191 background: #f8f9fa;
192 border-left: 4px solid #667eea;
193 border-radius: 6px;
194 font-size: 13px;
195 color: #333333;
196 line-height: 1.5;
197 `;
198
199 const timeLabel = document.createElement('div');
200 timeLabel.style.cssText = `
201 font-size: 11px;
202 color: #666666;
203 margin-bottom: 5px;
204 font-weight: 600;
205 `;
206 timeLabel.textContent = timestamp;
207
208 const descText = document.createElement('div');
209 descText.textContent = description;
210
211 descItem.appendChild(timeLabel);
212 descItem.appendChild(descText);
213 descriptionList.insertBefore(descItem, descriptionList.firstChild);
214
215 console.log('Description added:', description);
216 }
217
218 // Find video element on the page
219 function findVideoElement() {
220 const videoSelectors = [
221 'video[src*="blob"]',
222 'video[src*="m3u8"]',
223 'video.html5-main-video',
224 'video.video-stream',
225 'video[class*="player"]',
226 'video[class*="video"]',
227 'video'
228 ];
229
230 for (const selector of videoSelectors) {
231 const video = document.querySelector(selector);
232 if (video && video.readyState >= 2) {
233 console.log('Video element found:', selector);
234 return video;
235 }
236 }
237
238 console.log('No video element found');
239 return null;
240 }
241
242 // Capture video frame as base64 image
243 function captureVideoFrame(video) {
244 try {
245 const canvas = document.createElement('canvas');
246 canvas.width = video.videoWidth || 640;
247 canvas.height = video.videoHeight || 480;
248 const ctx = canvas.getContext('2d');
249 ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
250 const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
251 console.log('Video frame captured successfully');
252 return dataUrl;
253 } catch (error) {
254 console.error('Error capturing video frame:', error);
255 return null;
256 }
257 }
258
259 // Describe video frame using AI
260 async function describeVideoFrame() {
261 const video = findVideoElement();
262
263 if (!video) {
264 updateStatus('β οΈ No video found on this page', true);
265 return;
266 }
267
268 if (video.paused) {
269 updateStatus('βΈοΈ Video is paused');
270 return;
271 }
272
273 updateStatus('π Analyzing video...');
274
275 try {
276 const frameData = captureVideoFrame(video);
277
278 if (!frameData) {
279 updateStatus('β Could not capture video frame', true);
280 return;
281 }
282
283 // Use AI to describe the video frame
284 const prompt = `You are analyzing a frame from a live video stream. Describe what you see in this image in 1-2 concise sentences. Focus on the main subjects, actions, and important visual elements. Be specific and descriptive.`;
285
286 const description = await RM.aiCall(prompt + '\n\nImage data: ' + frameData);
287
288 const now = new Date();
289 const timestamp = now.toLocaleTimeString();
290
291 addDescription(description, timestamp);
292 updateStatus('β
Active - Next update in 10s');
293
294 console.log('Video described successfully at', timestamp);
295 } catch (error) {
296 console.error('Error describing video:', error);
297 updateStatus('β Error analyzing video', true);
298 }
299 }
300
301 // Start describing video every 10 seconds
302 function startDescribing() {
303 if (descriptionInterval) {
304 console.log('Description already running');
305 return;
306 }
307
308 console.log('Starting video description');
309 createPanel();
310
311 // Describe immediately
312 describeVideoFrame();
313
314 // Then describe every 10 seconds
315 descriptionInterval = setInterval(() => {
316 describeVideoFrame();
317 }, 10000);
318 }
319
320 // Stop describing
321 function stopDescribing() {
322 if (descriptionInterval) {
323 clearInterval(descriptionInterval);
324 descriptionInterval = null;
325 console.log('Video description stopped');
326 }
327 }
328
329 // Reset when new video starts
330 function resetOnNewVideo() {
331 const currentUrl = window.location.href;
332
333 if (currentUrl !== currentVideoUrl) {
334 console.log('New video detected, resetting...');
335 currentVideoUrl = currentUrl;
336
337 // Clear existing descriptions
338 if (descriptionList) {
339 descriptionList.innerHTML = '';
340 }
341
342 // Restart describing
343 stopDescribing();
344 setTimeout(() => {
345 startDescribing();
346 }, 2000);
347 }
348 }
349
350 // Monitor for video changes
351 const debouncedReset = debounce(resetOnNewVideo, 1000);
352
353 // Watch for URL changes (for single-page apps like YouTube)
354 let lastUrl = window.location.href;
355 new MutationObserver(() => {
356 const currentUrl = window.location.href;
357 if (currentUrl !== lastUrl) {
358 lastUrl = currentUrl;
359 debouncedReset();
360 }
361 }).observe(document.body, { childList: true, subtree: true });
362
363 // Initialize
364 function init() {
365 console.log('Live Video Description Tool initialized');
366
367 // Wait for page to load
368 if (document.readyState === 'loading') {
369 document.addEventListener('DOMContentLoaded', () => {
370 setTimeout(startDescribing, 3000);
371 });
372 } else {
373 setTimeout(startDescribing, 3000);
374 }
375
376 // Store current URL
377 currentVideoUrl = window.location.href;
378 }
379
380 // Start the extension
381 init();
382})();