您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
适用于大多数网站的视频和音频提取和批量下载工具,轻松抓取网页中的媒体资源
// ==UserScript== // @name 网页媒体提取和批量下载工具 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 适用于大多数网站的视频和音频提取和批量下载工具,轻松抓取网页中的媒体资源 // @author shenfangda // @match *://*/* // @grant GM_download // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setClipboard // @connect * // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js // ==/UserScript== (function() { 'use strict'; // 配置 const config = { // 默认设置 defaultSettings: { minWidth: 200, minHeight: 150, maxSize: 500, // MB formatFilter: ['mp4', 'webm', 'ogg', 'mp3', 'wav', 'm4a'], qualityThreshold: 0.5, customRules: [] }, // 特殊网站规则 siteRules: { 'youtube.com': { selector: 'video', exclude: [] }, 'bilibili.com': { selector: 'video', exclude: [] }, 'vimeo.com': { selector: 'video', exclude: [] } } }; // 主要功能类 class MediaExtractor { constructor() { this.media = []; this.settings = {...config.defaultSettings}; this.init(); } init() { console.log('媒体提取工具已启动'); this.createUI(); this.bindEvents(); } // 创建用户界面 createUI() { GM_addStyle(` #media-extractor-panel { position: fixed; top: 20px; right: 20px; width: 350px; max-height: 80vh; background: #fff; border: 1px solid #ccc; border-radius: 5px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; font-family: Arial, sans-serif; font-size: 14px; overflow: hidden; display: none; } #media-extractor-panel-header { background: #e74c3c; color: white; padding: 10px 15px; cursor: move; display: flex; justify-content: space-between; align-items: center; } #media-extractor-panel-title { font-weight: bold; font-size: 16px; } #media-extractor-panel-close { background: none; border: none; color: white; font-size: 20px; cursor: pointer; } #media-extractor-panel-content { padding: 15px; overflow-y: auto; max-height: calc(80vh - 50px); } .media-extractor-section { margin-bottom: 15px; } .media-extractor-section-title { font-weight: bold; margin-bottom: 8px; color: #333; border-bottom: 1px solid #eee; padding-bottom: 4px; } #media-extractor-media-container { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 10px; } .media-extractor-media-item { position: relative; border: 1px solid #ddd; border-radius: 4px; overflow: hidden; cursor: pointer; } .media-extractor-media-item video, .media-extractor-media-item audio { width: 100%; height: 80px; display: block; } .media-extractor-media-item img { width: 100%; height: 80px; object-fit: cover; display: block; } .media-extractor-media-item.selected { border-color: #e74c3c; box-shadow: 0 0 0 2px rgba(231, 76, 60, 0.3); } .media-extractor-media-info { padding: 5px; font-size: 12px; background: rgba(0,0,0,0.7); color: white; position: absolute; bottom: 0; left: 0; right: 0; } .media-extractor-controls { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; } .media-extractor-btn { padding: 8px 12px; background: #e74c3c; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; flex: 1; min-width: 100px; } .media-extractor-btn:hover { background: #c0392b; } .media-extractor-btn:disabled { background: #ccc; cursor: not-allowed; } .media-extractor-btn.secondary { background: #f0f0f0; color: #333; } .media-extractor-btn.secondary:hover { background: #e0e0e0; } .media-extractor-progress { height: 6px; background: #f0f0f0; border-radius: 3px; margin: 10px 0; overflow: hidden; display: none; } .media-extractor-progress-bar { height: 100%; background: #e74c3c; width: 0%; transition: width 0.3s; } .media-extractor-stats { font-size: 13px; color: #666; margin: 10px 0; } .media-extractor-settings { background: #f9f9f9; padding: 10px; border-radius: 4px; margin-top: 10px; } .media-extractor-setting-item { margin-bottom: 8px; display: flex; align-items: center; } .media-extractor-setting-item label { flex: 1; font-size: 13px; } .media-extractor-setting-item input { width: 60px; } #media-extractor-toggle-btn { position: fixed; top: 20px; right: 20px; width: 40px; height: 40px; background: #e74c3c; color: white; border: none; border-radius: 50%; box-shadow: 0 2px 10px rgba(0,0,0,0.2); cursor: pointer; z-index: 9999; font-size: 20px; display: flex; align-items: center; justify-content: center; } #media-extractor-notification { position: fixed; top: 20px; right: 70px; background: #e74c3c; color: white; padding: 10px 15px; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 10001; display: none; } .media-extractor-type-icon { position: absolute; top: 5px; right: 5px; background: rgba(0,0,0,0.5); color: white; padding: 2px 5px; border-radius: 3px; font-size: 10px; } `); // 创建主面板 const panel = document.createElement('div'); panel.id = 'media-extractor-panel'; panel.innerHTML = ` <div id="media-extractor-panel-header"> <div id="media-extractor-panel-title">媒体提取工具</div> <button id="media-extractor-panel-close">×</button> </div> <div id="media-extractor-panel-content"> <div class="media-extractor-section"> <div class="media-extractor-section-title">操作</div> <div class="media-extractor-controls"> <button id="media-extractor-scan-btn" class="media-extractor-btn">扫描媒体</button> <button id="media-extractor-select-all-btn" class="media-extractor-btn secondary">全选</button> <button id="media-extractor-deselect-all-btn" class="media-extractor-btn secondary">取消</button> </div> </div> <div class="media-extractor-section"> <div class="media-extractor-section-title">统计信息</div> <div id="media-extractor-stats" class="media-extractor-stats"> 未扫描媒体资源 </div> </div> <div class="media-extractor-section"> <div class="media-extractor-section-title">媒体预览</div> <div id="media-extractor-media-container"> <div style="grid-column: 1 / -1; text-align: center; padding: 20px; color: #999;"> 点击"扫描媒体"开始查找页面中的视频和音频 </div> </div> </div> <div class="media-extractor-section"> <div class="media-extractor-section-title">下载选项</div> <div class="media-extractor-controls"> <button id="media-extractor-download-selected-btn" class="media-extractor-btn" disabled>下载选中</button> <button id="media-extractor-download-zip-btn" class="media-extractor-btn" disabled>打包下载</button> </div> </div> <div class="media-extractor-progress"> <div class="media-extractor-progress-bar"></div> </div> <div class="media-extractor-section"> <div class="media-extractor-section-title">设置</div> <div class="media-extractor-settings"> <div class="media-extractor-setting-item"> <label>最小宽度:</label> <input type="number" id="setting-min-width" value="${this.settings.minWidth}" min="50"> </div> <div class="media-extractor-setting-item"> <label>最小高度:</label> <input type="number" id="setting-min-height" value="${this.settings.minHeight}" min="50"> </div> </div> </div> </div> `; document.body.appendChild(panel); // 创建切换按钮 const toggleBtn = document.createElement('button'); toggleBtn.id = 'media-extractor-toggle-btn'; toggleBtn.innerHTML = '🎵'; document.body.appendChild(toggleBtn); // 创建通知元素 const notification = document.createElement('div'); notification.id = 'media-extractor-notification'; document.body.appendChild(notification); } // 绑定事件 bindEvents() { // 切换面板显示 document.getElementById('media-extractor-toggle-btn').addEventListener('click', () => { const panel = document.getElementById('media-extractor-panel'); panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; }); // 关闭面板 document.getElementById('media-extractor-panel-close').addEventListener('click', () => { document.getElementById('media-extractor-panel').style.display = 'none'; }); // 拖拽面板 this.makeDraggable(document.getElementById('media-extractor-panel-header'), document.getElementById('media-extractor-panel')); // 扫描媒体 document.getElementById('media-extractor-scan-btn').addEventListener('click', () => { this.scanMedia(); }); // 全选 document.getElementById('media-extractor-select-all-btn').addEventListener('click', () => { this.selectAllMedia(); }); // 取消全选 document.getElementById('media-extractor-deselect-all-btn').addEventListener('click', () => { this.deselectAllMedia(); }); // 下载选中 document.getElementById('media-extractor-download-selected-btn').addEventListener('click', () => { this.downloadSelectedMedia(); }); // 打包下载 document.getElementById('media-extractor-download-zip-btn').addEventListener('click', () => { this.downloadAsZip(); }); // 设置变更 document.getElementById('setting-min-width').addEventListener('change', (e) => { this.settings.minWidth = parseInt(e.target.value) || 200; }); document.getElementById('setting-min-height').addEventListener('change', (e) => { this.settings.minHeight = parseInt(e.target.value) || 150; }); } // 使面板可拖拽 makeDraggable(header, panel) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; header.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // 获取鼠标位置 pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // 计算新位置 pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 设置元素新位置 panel.style.top = (panel.offsetTop - pos2) + "px"; panel.style.left = (panel.offsetLeft - pos1) + "px"; } function closeDragElement() { // 停止移动 document.onmouseup = null; document.onmousemove = null; } } // 扫描页面中的媒体 scanMedia() { this.showNotification('正在扫描媒体资源...'); this.media = []; // 获取当前网站规则 const hostname = window.location.hostname; let rule = null; for (const site in config.siteRules) { if (hostname.includes(site)) { rule = config.siteRules[site]; break; } } // 查找所有视频元素 const videoElements = document.querySelectorAll('video'); const audioElements = document.querySelectorAll('audio'); // 处理视频元素 videoElements.forEach((video, index) => { try { // 获取视频源 let src = ''; if (video.src) { src = video.src; } else if (video.querySelector('source')) { src = video.querySelector('source').src; } // 获取视频尺寸 const width = video.videoWidth || video.width || 0; const height = video.videoHeight || video.height || 0; // 过滤条件 if (!src || src.startsWith('data:')) return; if (width < this.settings.minWidth || height < this.settings.minHeight) return; this.media.push({ id: 'video_' + index, src: src, width: width, height: height, duration: video.duration || 0, type: 'video', element: video, selected: false }); } catch (e) { console.warn('处理视频时出错:', e); } }); // 处理音频元素 audioElements.forEach((audio, index) => { try { // 获取音频源 let src = ''; if (audio.src) { src = audio.src; } else if (audio.querySelector('source')) { src = audio.querySelector('source').src; } // 过滤条件 if (!src || src.startsWith('data:')) return; this.media.push({ id: 'audio_' + index, src: src, width: 0, height: 0, duration: audio.duration || 0, type: 'audio', element: audio, selected: false }); } catch (e) { console.warn('处理音频时出错:', e); } }); // 查找媒体链接 this.findMediaLinks(); // 去重 const uniqueMedia = []; const seenUrls = new Set(); this.media.forEach(media => { if (!seenUrls.has(media.src)) { seenUrls.add(media.src); uniqueMedia.push(media); } }); this.media = uniqueMedia; // 更新UI this.updateMediaList(); this.updateStats(); this.showNotification(`找到 ${this.media.length} 个媒体资源`); } // 查找页面中的媒体链接 findMediaLinks() { const links = document.querySelectorAll('a[href]'); links.forEach((link, index) => { const href = link.href; if (!href) return; // 检查链接是否指向媒体文件 const isMediaLink = this.isMediaUrl(href); if (isMediaLink) { // 检查是否已存在 const exists = this.media.some(m => m.src === href); if (!exists) { this.media.push({ id: 'link_' + index, src: href, width: 0, height: 0, duration: 0, type: this.getMediaType(href), element: link, selected: false }); } } }); } // 判断URL是否为媒体文件 isMediaUrl(url) { const mediaExtensions = [ 'mp4', 'webm', 'ogg', 'mp3', 'wav', 'm4a', 'flv', 'avi', 'mov', 'wmv', 'mkv', 'aac', 'flac' ]; try { const urlObj = new URL(url); const pathname = urlObj.pathname.toLowerCase(); return mediaExtensions.some(ext => pathname.endsWith('.' + ext)); } catch (e) { return false; } } // 根据URL获取媒体类型 getMediaType(url) { const videoExtensions = ['mp4', 'webm', 'ogg', 'flv', 'avi', 'mov', 'wmv', 'mkv']; const audioExtensions = ['mp3', 'wav', 'm4a', 'aac', 'flac']; try { const urlObj = new URL(url); const pathname = urlObj.pathname.toLowerCase(); const ext = pathname.split('.').pop(); if (videoExtensions.includes(ext)) { return 'video'; } else if (audioExtensions.includes(ext)) { return 'audio'; } return 'unknown'; } catch (e) { return 'unknown'; } } // 更新媒体列表 updateMediaList() { const container = document.getElementById('media-extractor-media-container'); container.innerHTML = ''; if (this.media.length === 0) { container.innerHTML = '<div style="grid-column: 1 / -1; text-align: center; padding: 20px; color: #999;">未找到符合条件的媒体资源</div>'; return; } this.media.forEach(media => { const item = document.createElement('div'); item.className = 'media-extractor-media-item'; item.dataset.id = media.id; // 根据类型创建不同的预览 if (media.type === 'video') { item.innerHTML = ` <div style="position: relative; width: 100%; height: 80px; background: #333; display: flex; align-items: center; justify-content: center;"> <div style="color: white; font-size: 24px;">▶</div> <div class="media-extractor-type-icon">视频</div> </div> <div class="media-extractor-media-info">${media.width}×${media.height}</div> `; } else if (media.type === 'audio') { item.innerHTML = ` <div style="position: relative; width: 100%; height: 80px; background: #3498db; display: flex; align-items: center; justify-content: center;"> <div style="color: white; font-size: 24px;">♪</div> <div class="media-extractor-type-icon">音频</div> </div> <div class="media-extractor-media-info">${this.formatDuration(media.duration)}</div> `; } else { item.innerHTML = ` <div style="position: relative; width: 100%; height: 80px; background: #95a5a6; display: flex; align-items: center; justify-content: center;"> <div style="color: white; font-size: 24px;">?</div> <div class="media-extractor-type-icon">未知</div> </div> `; } if (media.selected) { item.classList.add('selected'); } item.addEventListener('click', (e) => { e.stopPropagation(); this.toggleMediaSelection(media.id); }); container.appendChild(item); }); // 更新按钮状态 document.getElementById('media-extractor-download-selected-btn').disabled = this.media.filter(m => m.selected).length === 0; document.getElementById('media-extractor-download-zip-btn').disabled = this.media.length === 0; } // 格式化时长 formatDuration(seconds) { if (!seconds || isNaN(seconds)) return '未知时长'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; } // 切换媒体选择状态 toggleMediaSelection(id) { const media = this.media.find(m => m.id === id); if (media) { media.selected = !media.selected; const item = document.querySelector(`.media-extractor-media-item[data-id="${id}"]`); if (item) { if (media.selected) { item.classList.add('selected'); } else { item.classList.remove('selected'); } } // 更新按钮状态 document.getElementById('media-extractor-download-selected-btn').disabled = this.media.filter(m => m.selected).length === 0; } } // 全选媒体 selectAllMedia() { this.media.forEach(media => { media.selected = true; }); this.updateMediaList(); } // 取消全选媒体 deselectAllMedia() { this.media.forEach(media => { media.selected = false; }); this.updateMediaList(); } // 更新统计信息 updateStats() { const selectedCount = this.media.filter(m => m.selected).length; const stats = document.getElementById('media-extractor-stats'); stats.textContent = `共找到 ${this.media.length} 个媒体资源,已选择 ${selectedCount} 个`; } // 下载选中媒体 async downloadSelectedMedia() { const selectedMedia = this.media.filter(m => m.selected); if (selectedMedia.length === 0) { this.showNotification('请先选择要下载的媒体资源'); return; } this.showProgress(0); for (let i = 0; i < selectedMedia.length; i++) { const media = selectedMedia[i]; try { const filename = this.getFileName(media.src, media.type); await this.downloadMedia(media.src, filename); this.showProgress(((i + 1) / selectedMedia.length) * 100); } catch (e) { console.error('下载媒体失败:', e); } } this.hideProgress(); this.showNotification(`下载完成,共下载 ${selectedMedia.length} 个媒体资源`); } // 打包下载为ZIP async downloadAsZip() { if (this.media.length === 0) { this.showNotification('没有媒体资源可以下载'); return; } this.showProgress(0); try { const zip = new JSZip(); const videoFolder = zip.folder("videos"); const audioFolder = zip.folder("audios"); const selectedMedia = this.media.filter(m => m.selected).length > 0 ? this.media.filter(m => m.selected) : this.media; for (let i = 0; i < selectedMedia.length; i++) { const media = selectedMedia[i]; try { const blob = await this.fetchMediaAsBlob(media.src); const filename = this.getFileName(media.src, media.type); if (media.type === 'video') { videoFolder.file(filename, blob); } else if (media.type === 'audio') { audioFolder.file(filename, blob); } else { zip.file(filename, blob); } this.showProgress(((i + 1) / selectedMedia.length) * 100); } catch (e) { console.error('添加媒体到ZIP失败:', e); } } const content = await zip.generateAsync({type: "blob"}); saveAs(content, `media_${new Date().getTime()}.zip`); this.hideProgress(); this.showNotification(`ZIP打包完成,共包含 ${selectedMedia.length} 个媒体资源`); } catch (e) { this.hideProgress(); this.showNotification('打包下载失败: ' + e.message); console.error('ZIP打包失败:', e); } } // 下载单个媒体 downloadMedia(url, filename) { return new Promise((resolve, reject) => { GM_download({ url: url, name: filename, onload: () => resolve(), onerror: (error) => reject(error) }); }); } // 获取媒体Blob fetchMediaAsBlob(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, responseType: "blob", onload: function(response) { resolve(response.response); }, onerror: function(error) { reject(error); } }); }); } // 获取文件名 getFileName(url, type) { try { const urlObj = new URL(url); const pathname = urlObj.pathname; const filename = pathname.split('/').pop(); if (filename) { return filename; } } catch (e) { // URL解析失败 } // 生成默认文件名 const timestamp = new Date().getTime(); const ext = type === 'video' ? 'mp4' : 'mp3'; return `media_${timestamp}.${ext}`; } // 显示进度条 showProgress(percent) { const progress = document.querySelector('.media-extractor-progress'); const bar = document.querySelector('.media-extractor-progress-bar'); progress.style.display = 'block'; bar.style.width = percent + '%'; } // 隐藏进度条 hideProgress() { const progress = document.querySelector('.media-extractor-progress'); progress.style.display = 'none'; } // 显示通知 showNotification(message) { const notification = document.getElementById('media-extractor-notification'); notification.textContent = message; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); } } // 初始化插件 new MediaExtractor(); })();