Live Video Description Tool

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})();