Screenshot for YouTube™ | 4K Frame & Thumbnail Cap
+1
-1
| { | ||
| "manifest_version": 2, | ||
| "name": "YouTube Screenshot Tool", | ||
| "version": "1.2.1", | ||
| "version": "1.2.2", | ||
| "description": "Extract high-quality frames from YouTube videos without UI elements", | ||
@@ -6,0 +6,0 @@ "default_locale": "en", |
+39
-9
@@ -61,3 +61,3 @@ // YouTube Screenshot Tool - Popup Script | ||
| toolsLabel: "Tools & Settings", | ||
| openThumbnailTool: "Open Thumbnail Tool", | ||
| openThumbnailTool: "Open Thumbnail", | ||
| shareMessage: "I'm loving this YouTube Screenshot Tool! It helps me extract high-quality frames. Try it: " + STORE_URL, | ||
@@ -83,3 +83,3 @@ copied: "Copied! ✅" | ||
| toolsLabel: "الأدوات والإعدادات", | ||
| openThumbnailTool: "فتح أداة الصور المصغرة", | ||
| openThumbnailTool: "فتح الصورة المصغرة", | ||
| shareMessage: "أداة رائعة لالتقاط الصور من يوتيوب بجودة عالية! جربها: " + STORE_URL, | ||
@@ -132,4 +132,4 @@ copied: "تم النسخ! ✅" | ||
| const tabs = await browserAPI.tabs.query({ active: true, currentWindow: true }); | ||
| if (tabs.length > 0 && tabs[0].url && tabs[0].url.includes('youtube.com/watch')) { | ||
| videoUrlDisplay.textContent = tabs[0].url; // Changed to textContent for span | ||
| if (tabs.length > 0 && tabs[0].url && (tabs[0].url.includes('youtube.com/watch') || tabs[0].url.includes('youtu.be/'))) { | ||
| videoUrlDisplay.textContent = tabs[0].url; | ||
| openThumbnailToolBtn.disabled = false; | ||
@@ -143,3 +143,3 @@ openThumbnailToolBtn.classList.remove('disabled'); | ||
| // Remove old listeners to avoid duplicates (though DOMContentLoaded runs once) | ||
| // Remove old listeners to avoid duplicates | ||
| const newBtn = openThumbnailToolBtn.cloneNode(true); | ||
@@ -150,6 +150,10 @@ openThumbnailToolBtn.parentNode.replaceChild(newBtn, openThumbnailToolBtn); | ||
| const url = videoUrlDisplay.textContent; | ||
| if (url && url.includes('youtube.com')) { | ||
| // Open external tool with the video URL as a parameter | ||
| const thumbUrl = `https://thumz.vercel.app/?video=${encodeURIComponent(url)}`; | ||
| const videoId = extractVideoId(url); | ||
| if (videoId) { | ||
| // Open the maximum resolution thumbnail directly | ||
| const thumbUrl = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`; | ||
| browserAPI.tabs.create({ url: thumbUrl }); | ||
| } else { | ||
| showStatus('Could not extract video ID', 'error'); | ||
| } | ||
@@ -163,2 +167,9 @@ }); | ||
| // Helper: Extract Video ID | ||
| function extractVideoId(url) { | ||
| const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; | ||
| const match = url.match(regExp); | ||
| return (match && match[2].length === 11) ? match[2] : null; | ||
| } | ||
| // Update shortcut display | ||
@@ -186,3 +197,22 @@ async function updateShortcutDisplay() { | ||
| changeShortcutBtn.addEventListener('click', () => { | ||
| browserAPI.tabs.create({ url: 'chrome://extensions/shortcuts' }); | ||
| if (navigator.userAgent.toLowerCase().includes('firefox')) { | ||
| const currentLang = document.querySelector('.language-btn.active').dataset.lang || 'en'; | ||
| const messages = { | ||
| en: "To change shortcuts in Firefox: Go to Add-ons (Ctrl+Shift+A) > Gear Icon > Manage Extension Shortcuts", | ||
| ar: "لتغيير الاختصارات في فايرفوكس: اذهب إلى الإضافات (Ctrl+Shift+A) > أيقونة الترس > إدارة اختصارات الإضافات" | ||
| }; | ||
| // Show message with longer duration for readability | ||
| const statusElement = document.getElementById('statusMessage'); | ||
| if (statusElement) { | ||
| statusElement.textContent = messages[currentLang]; | ||
| statusElement.className = 'status-message info'; | ||
| statusElement.style.display = 'block'; | ||
| setTimeout(() => { | ||
| statusElement.style.display = 'none'; | ||
| }, 8000); // 8 seconds to read | ||
| } | ||
| } else { | ||
| browserAPI.tabs.create({ url: 'chrome://extensions/shortcuts' }); | ||
| } | ||
| }); | ||
@@ -189,0 +219,0 @@ } |
-211
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>Thumz - YouTube Thumbnail Tool</title> | ||
| <style> | ||
| :root { | ||
| --primary-color: #2196F3; | ||
| --primary-hover: #1976D2; | ||
| --bg-color: #f5f7fa; | ||
| --card-bg: #ffffff; | ||
| --text-color: #333333; | ||
| --border-color: #e0e0e0; | ||
| } | ||
| body { | ||
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||
| background-color: var(--bg-color); | ||
| color: var(--text-color); | ||
| margin: 0; | ||
| padding: 0; | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| min-height: 100vh; | ||
| } | ||
| .container { | ||
| width: 100%; | ||
| max-width: 600px; | ||
| padding: 20px; | ||
| } | ||
| .card { | ||
| background-color: var(--card-bg); | ||
| border-radius: 12px; | ||
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||
| padding: 30px; | ||
| text-align: center; | ||
| } | ||
| h1 { | ||
| margin-top: 0; | ||
| color: var(--primary-color); | ||
| font-size: 24px; | ||
| margin-bottom: 20px; | ||
| } | ||
| .input-group { | ||
| margin-bottom: 20px; | ||
| text-align: left; | ||
| } | ||
| label { | ||
| display: block; | ||
| margin-bottom: 8px; | ||
| font-weight: 600; | ||
| font-size: 14px; | ||
| } | ||
| input[type="text"] { | ||
| width: 100%; | ||
| padding: 12px; | ||
| border: 1px solid var(--border-color); | ||
| border-radius: 8px; | ||
| font-size: 16px; | ||
| box-sizing: border-box; | ||
| transition: border-color 0.3s; | ||
| } | ||
| input[type="text"]:focus { | ||
| border-color: var(--primary-color); | ||
| outline: none; | ||
| } | ||
| .btn { | ||
| background-color: var(--primary-color); | ||
| color: white; | ||
| border: none; | ||
| padding: 12px 24px; | ||
| border-radius: 8px; | ||
| font-size: 16px; | ||
| font-weight: 600; | ||
| cursor: pointer; | ||
| transition: background-color 0.3s; | ||
| width: 100%; | ||
| } | ||
| .btn:hover { | ||
| background-color: var(--primary-hover); | ||
| } | ||
| .preview-area { | ||
| margin-top: 30px; | ||
| border-top: 1px solid var(--border-color); | ||
| padding-top: 20px; | ||
| display: none; | ||
| } | ||
| .preview-placeholder { | ||
| background-color: #eee; | ||
| height: 200px; | ||
| border-radius: 8px; | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| color: #888; | ||
| margin-bottom: 15px; | ||
| } | ||
| .preview-img { | ||
| max-width: 100%; | ||
| border-radius: 8px; | ||
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | ||
| } | ||
| .status-msg { | ||
| margin-top: 10px; | ||
| font-size: 14px; | ||
| color: #666; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="container"> | ||
| <div class="card"> | ||
| <h1>Thumz Downloader</h1> | ||
| <div class="input-group"> | ||
| <label for="videoUrl">YouTube Video URL / ID</label> | ||
| <input type="text" id="videoUrl" placeholder="Enter YouTube URL or ID..."> | ||
| </div> | ||
| <button id="generateBtn" class="btn">Generate Thumbnail</button> | ||
| <div id="previewArea" class="preview-area"> | ||
| <h3>Preview</h3> | ||
| <div id="imageContainer"> | ||
| <div class="preview-placeholder"> | ||
| <span>Image Preview Will Appear Here</span> | ||
| </div> | ||
| </div> | ||
| <p class="status-msg"> | ||
| Processing on external server... | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <script> | ||
| document.addEventListener('DOMContentLoaded', () => { | ||
| const videoInput = document.getElementById('videoUrl'); | ||
| const generateBtn = document.getElementById('generateBtn'); | ||
| const previewArea = document.getElementById('previewArea'); | ||
| const imageContainer = document.getElementById('imageContainer'); | ||
| // 1. Read 'video' query parameter | ||
| const urlParams = new URLSearchParams(window.location.search); | ||
| const videoParam = urlParams.get('video'); | ||
| if (videoParam) { | ||
| videoInput.value = videoParam; | ||
| // Auto-click generate if URL is present | ||
| // setTimeout(() => generateBtn.click(), 500); | ||
| } | ||
| // 2. Handle Generate Button | ||
| generateBtn.addEventListener('click', () => { | ||
| const val = videoInput.value.trim(); | ||
| if (!val) { | ||
| alert('Please enter a video URL or ID'); | ||
| return; | ||
| } | ||
| generateThumbnail(val); | ||
| }); | ||
| // 3. Generation Logic | ||
| function generateThumbnail(videoValue) { | ||
| previewArea.style.display = 'block'; | ||
| // Extract ID | ||
| let videoId = videoValue; | ||
| const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; | ||
| const match = videoValue.match(regExp); | ||
| if (match && match[7].length == 11) { | ||
| videoId = match[7]; | ||
| } | ||
| console.log('Processing video ID:', videoId); | ||
| // Display High Res Thumbnail directly from YouTube (Client-side) | ||
| // This is safe on a standard web page (not extension popup) | ||
| const maxResUrl = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`; | ||
| imageContainer.innerHTML = ` | ||
| <img src="${maxResUrl}" class="preview-img" alt="Thumbnail"> | ||
| <div style="margin-top: 15px;"> | ||
| <a href="${maxResUrl}" download="thumbnail.jpg" target="_blank" class="btn" style="display: inline-block; text-decoration: none; width: auto;">Download HD Image</a> | ||
| </div> | ||
| `; | ||
| } | ||
| }); | ||
| </script> | ||
| </body> | ||
| </html> |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet