YouTube Thumbnail Organizer

Organize and manage YouTube video thumbnails with sorting and filtering options

Size

14.5 KB

Version

1.0.1

Created

Mar 16, 2026

Updated

about 1 month ago

1// ==UserScript==
2// @name		YouTube Thumbnail Organizer
3// @description		Organize and manage YouTube video thumbnails with sorting and filtering options
4// @version		1.0.1
5// @match		https://*.youtube.com/*
6// @icon		https://www.youtube.com/s/desktop/c9b3ffed/img/favicon_32x32.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('YouTube Thumbnail Organizer initialized');
12
13    // Add custom styles
14    TM_addStyle(`
15        #thumbnail-organizer-panel {
16            position: fixed;
17            top: 80px;
18            right: 20px;
19            width: 320px;
20            background: #0f0f0f;
21            border: 1px solid #3f3f3f;
22            border-radius: 12px;
23            padding: 16px;
24            z-index: 9999;
25            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
26            color: #fff;
27            font-family: "Roboto", "Arial", sans-serif;
28            max-height: 80vh;
29            overflow-y: auto;
30        }
31
32        #thumbnail-organizer-panel.hidden {
33            display: none;
34        }
35
36        .organizer-header {
37            display: flex;
38            justify-content: space-between;
39            align-items: center;
40            margin-bottom: 16px;
41            padding-bottom: 12px;
42            border-bottom: 1px solid #3f3f3f;
43        }
44
45        .organizer-header h3 {
46            margin: 0;
47            font-size: 16px;
48            font-weight: 500;
49            color: #fff;
50        }
51
52        .organizer-close {
53            background: none;
54            border: none;
55            color: #aaa;
56            font-size: 24px;
57            cursor: pointer;
58            padding: 0;
59            width: 32px;
60            height: 32px;
61            display: flex;
62            align-items: center;
63            justify-content: center;
64            border-radius: 50%;
65            transition: background 0.2s;
66        }
67
68        .organizer-close:hover {
69            background: #3f3f3f;
70            color: #fff;
71        }
72
73        .organizer-controls {
74            display: flex;
75            flex-direction: column;
76            gap: 12px;
77            margin-bottom: 16px;
78        }
79
80        .organizer-control-group {
81            display: flex;
82            flex-direction: column;
83            gap: 6px;
84        }
85
86        .organizer-control-group label {
87            font-size: 13px;
88            color: #aaa;
89            font-weight: 500;
90        }
91
92        .organizer-select, .organizer-button {
93            background: #272727;
94            border: 1px solid #3f3f3f;
95            color: #fff;
96            padding: 10px 12px;
97            border-radius: 8px;
98            font-size: 14px;
99            cursor: pointer;
100            transition: all 0.2s;
101        }
102
103        .organizer-select:hover, .organizer-button:hover {
104            background: #3f3f3f;
105            border-color: #5f5f5f;
106        }
107
108        .organizer-button {
109            font-weight: 500;
110            text-align: center;
111        }
112
113        .organizer-button.primary {
114            background: #3ea6ff;
115            border-color: #3ea6ff;
116        }
117
118        .organizer-button.primary:hover {
119            background: #4db3ff;
120            border-color: #4db3ff;
121        }
122
123        .thumbnail-stats {
124            background: #272727;
125            padding: 12px;
126            border-radius: 8px;
127            font-size: 13px;
128            line-height: 1.6;
129        }
130
131        .thumbnail-stats div {
132            display: flex;
133            justify-content: space-between;
134            margin-bottom: 6px;
135        }
136
137        .thumbnail-stats div:last-child {
138            margin-bottom: 0;
139        }
140
141        .thumbnail-stats span:first-child {
142            color: #aaa;
143        }
144
145        .thumbnail-stats span:last-child {
146            color: #fff;
147            font-weight: 500;
148        }
149
150        #thumbnail-organizer-toggle {
151            position: fixed;
152            top: 80px;
153            right: 20px;
154            background: #3ea6ff;
155            border: none;
156            color: #fff;
157            padding: 12px 16px;
158            border-radius: 8px;
159            cursor: pointer;
160            font-size: 14px;
161            font-weight: 500;
162            z-index: 9998;
163            box-shadow: 0 2px 8px rgba(62, 166, 255, 0.3);
164            transition: all 0.2s;
165            font-family: "Roboto", "Arial", sans-serif;
166        }
167
168        #thumbnail-organizer-toggle:hover {
169            background: #4db3ff;
170            box-shadow: 0 4px 12px rgba(62, 166, 255, 0.4);
171            transform: translateY(-1px);
172        }
173
174        .thumbnail-highlight {
175            outline: 3px solid #3ea6ff !important;
176            outline-offset: 2px;
177            border-radius: 8px;
178        }
179
180        .thumbnail-grid-view ytd-compact-video-renderer,
181        .thumbnail-grid-view ytd-video-renderer {
182            display: inline-block !important;
183            width: calc(50% - 8px) !important;
184            margin: 4px !important;
185            vertical-align: top !important;
186        }
187    `);
188
189    // Create toggle button
190    function createToggleButton() {
191        const toggleBtn = document.createElement('button');
192        toggleBtn.id = 'thumbnail-organizer-toggle';
193        toggleBtn.textContent = '🖼️ Thumbnail Organizer';
194        toggleBtn.addEventListener('click', togglePanel);
195        document.body.appendChild(toggleBtn);
196        console.log('Toggle button created');
197    }
198
199    // Create organizer panel
200    function createOrganizerPanel() {
201        const panel = document.createElement('div');
202        panel.id = 'thumbnail-organizer-panel';
203        panel.className = 'hidden';
204        
205        panel.innerHTML = `
206            <div class="organizer-header">
207                <h3>🖼️ Thumbnail Organizer</h3>
208                <button class="organizer-close">×</button>
209            </div>
210            
211            <div class="organizer-controls">
212                <div class="organizer-control-group">
213                    <label>Sort Thumbnails By:</label>
214                    <select class="organizer-select" id="sort-option">
215                        <option value="default">Default Order</option>
216                        <option value="size">Size (Largest First)</option>
217                        <option value="position">Position (Top to Bottom)</option>
218                    </select>
219                </div>
220                
221                <div class="organizer-control-group">
222                    <label>View Mode:</label>
223                    <select class="organizer-select" id="view-mode">
224                        <option value="default">Default View</option>
225                        <option value="grid">Grid View</option>
226                        <option value="highlight">Highlight All</option>
227                    </select>
228                </div>
229                
230                <button class="organizer-button primary" id="download-thumbnails">
231                    📥 Download All Thumbnails
232                </button>
233                
234                <button class="organizer-button" id="refresh-stats">
235                    🔄 Refresh Stats
236                </button>
237            </div>
238            
239            <div class="thumbnail-stats" id="thumbnail-stats">
240                <div>
241                    <span>Total Thumbnails:</span>
242                    <span id="total-count">0</span>
243                </div>
244                <div>
245                    <span>Visible Thumbnails:</span>
246                    <span id="visible-count">0</span>
247                </div>
248                <div>
249                    <span>Current Page:</span>
250                    <span id="current-page">Watch</span>
251                </div>
252            </div>
253        `;
254        
255        document.body.appendChild(panel);
256        console.log('Organizer panel created');
257        
258        // Add event listeners
259        panel.querySelector('.organizer-close').addEventListener('click', togglePanel);
260        panel.querySelector('#sort-option').addEventListener('change', handleSortChange);
261        panel.querySelector('#view-mode').addEventListener('change', handleViewModeChange);
262        panel.querySelector('#download-thumbnails').addEventListener('click', downloadAllThumbnails);
263        panel.querySelector('#refresh-stats').addEventListener('click', updateStats);
264        
265        // Initial stats update
266        updateStats();
267    }
268
269    // Toggle panel visibility
270    function togglePanel() {
271        const panel = document.getElementById('thumbnail-organizer-panel');
272        const toggleBtn = document.getElementById('thumbnail-organizer-toggle');
273        
274        if (panel.classList.contains('hidden')) {
275            panel.classList.remove('hidden');
276            toggleBtn.style.display = 'none';
277            updateStats();
278        } else {
279            panel.classList.add('hidden');
280            toggleBtn.style.display = 'block';
281        }
282    }
283
284    // Get all thumbnails
285    function getAllThumbnails() {
286        const thumbnails = document.querySelectorAll('ytd-thumbnail img, ytd-video-preview img');
287        console.log(`Found ${thumbnails.length} thumbnails`);
288        return Array.from(thumbnails);
289    }
290
291    // Update statistics
292    function updateStats() {
293        const thumbnails = getAllThumbnails();
294        const visibleThumbnails = thumbnails.filter(thumb => {
295            const rect = thumb.getBoundingClientRect();
296            return rect.width > 0 && rect.height > 0;
297        });
298        
299        document.getElementById('total-count').textContent = thumbnails.length;
300        document.getElementById('visible-count').textContent = visibleThumbnails.length;
301        
302        // Detect current page type
303        const url = window.location.href;
304        let pageType = 'Unknown';
305        if (url.includes('/watch')) pageType = 'Watch';
306        else if (url.includes('/results')) pageType = 'Search';
307        else if (url.includes('/channel') || url.includes('/@')) pageType = 'Channel';
308        else if (url === 'https://www.youtube.com/' || url === 'https://www.youtube.com') pageType = 'Home';
309        
310        document.getElementById('current-page').textContent = pageType;
311        
312        console.log(`Stats updated: ${thumbnails.length} total, ${visibleThumbnails.length} visible`);
313    }
314
315    // Handle sort change
316    function handleSortChange(event) {
317        const sortOption = event.target.value;
318        console.log(`Sort option changed to: ${sortOption}`);
319        
320        const thumbnails = getAllThumbnails();
321        const containers = thumbnails.map(thumb => {
322            let container = thumb.closest('ytd-compact-video-renderer, ytd-video-renderer, ytd-grid-video-renderer, ytd-rich-item-renderer');
323            return container;
324        }).filter(c => c !== null);
325        
326        if (sortOption === 'default') {
327            // Reload page to restore default order
328            console.log('Restoring default order');
329            return;
330        }
331        
332        let sortedContainers = [...containers];
333        
334        if (sortOption === 'size') {
335            sortedContainers.sort((a, b) => {
336                const imgA = a.querySelector('img');
337                const imgB = b.querySelector('img');
338                const sizeA = imgA ? imgA.naturalWidth * imgA.naturalHeight : 0;
339                const sizeB = imgB ? imgB.naturalWidth * imgB.naturalHeight : 0;
340                return sizeB - sizeA;
341            });
342        } else if (sortOption === 'position') {
343            sortedContainers.sort((a, b) => {
344                const rectA = a.getBoundingClientRect();
345                const rectB = b.getBoundingClientRect();
346                return rectA.top - rectB.top;
347            });
348        }
349        
350        // Reorder elements
351        const parent = containers[0]?.parentElement;
352        if (parent) {
353            sortedContainers.forEach(container => {
354                parent.appendChild(container);
355            });
356            console.log('Thumbnails reordered');
357        }
358    }
359
360    // Handle view mode change
361    function handleViewModeChange(event) {
362        const viewMode = event.target.value;
363        console.log(`View mode changed to: ${viewMode}`);
364        
365        const thumbnails = getAllThumbnails();
366        
367        // Remove all previous effects
368        thumbnails.forEach(thumb => thumb.classList.remove('thumbnail-highlight'));
369        document.body.classList.remove('thumbnail-grid-view');
370        
371        if (viewMode === 'highlight') {
372            thumbnails.forEach(thumb => thumb.classList.add('thumbnail-highlight'));
373            console.log('Highlighted all thumbnails');
374        } else if (viewMode === 'grid') {
375            document.body.classList.add('thumbnail-grid-view');
376            console.log('Applied grid view');
377        }
378    }
379
380    // Download all thumbnails
381    async function downloadAllThumbnails() {
382        console.log('Starting thumbnail download...');
383        const thumbnails = getAllThumbnails();
384        
385        if (thumbnails.length === 0) {
386            alert('No thumbnails found on this page!');
387            return;
388        }
389        
390        let downloadCount = 0;
391        
392        for (let i = 0; i < thumbnails.length; i++) {
393            const thumb = thumbnails[i];
394            const src = thumb.src || thumb.getAttribute('src');
395            
396            if (src && !src.includes('data:image')) {
397                try {
398                    // Get high quality version
399                    const highQualitySrc = src.replace(/=s\d+-/, '=s1920-').replace(/\/hqdefault\./, '/maxresdefault.');
400                    
401                    await GM.openInTab(highQualitySrc, true);
402                    downloadCount++;
403                    
404                    // Small delay to avoid overwhelming the browser
405                    await new Promise(resolve => setTimeout(resolve, 500));
406                } catch (error) {
407                    console.error('Error downloading thumbnail:', error);
408                }
409            }
410        }
411        
412        alert(`Opened ${downloadCount} thumbnails in new tabs. Right-click and save each image.`);
413        console.log(`Opened ${downloadCount} thumbnails`);
414    }
415
416    // Initialize the extension
417    function init() {
418        console.log('Initializing YouTube Thumbnail Organizer...');
419        
420        // Wait for page to be ready
421        if (document.body) {
422            createToggleButton();
423            createOrganizerPanel();
424            
425            // Update stats when thumbnails load
426            const observer = new MutationObserver(() => {
427                updateStats();
428            });
429            
430            observer.observe(document.body, {
431                childList: true,
432                subtree: true
433            });
434            
435            console.log('YouTube Thumbnail Organizer ready!');
436        } else {
437            setTimeout(init, 1000);
438        }
439    }
440
441    // Start the extension
442    TM_runBody(init);
443})();