// ==UserScript== // @name 视频压缩器 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 通用视频压缩工具 - 选择本地视频文件进行压缩处理 // @author WorkBuddy // @match *://*/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // ==================== 配置区 ==================== const CONFIG = { // 压缩质量: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow preset: 'medium', // 输出格式 outputFormat: 'mp4', // 默认视频编码 videoCodec: 'libx264', // 默认音频编码 audioCodec: 'aac', // 视频码率 (空则自动) videoBitrate: '', // 音频码率 (空则自动) audioBitrate: '128k', // crf 值 0-51,越高质量越差文件越小,23-28 是常用范围 crf: 28, // 是否移除音频 removeAudio: false }; // ==================== 样式 ==================== const STYLE = ` .vc-float-btn { position: fixed; bottom: 20px; right: 20px; width: 56px; height: 56px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4); cursor: pointer; z-index: 2147483647; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; border: none; } .vc-float-btn:hover { transform: scale(1.1); box-shadow: 0 6px 30px rgba(102, 126, 234, 0.6); } .vc-float-btn svg { width: 28px; height: 28px; fill: white; } .vc-panel { position: fixed; bottom: 90px; right: 20px; width: 380px; max-height: 80vh; background: #1a1a2e; border-radius: 16px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: none; overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.1); } .vc-panel.show { display: block; animation: vc-slide-up 0.3s ease; } @keyframes vc-slide-up { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .vc-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 16px 20px; color: white; display: flex; justify-content: space-between; align-items: center; } .vc-header h3 { margin: 0; font-size: 16px; font-weight: 600; } .vc-close { background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 1; opacity: 0.8; transition: opacity 0.2s; } .vc-close:hover { opacity: 1; } .vc-body { padding: 20px; color: #e0e0e0; max-height: calc(80vh - 60px); overflow-y: auto; } .vc-drop-zone { border: 2px dashed #4a4a6a; border-radius: 12px; padding: 40px 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; margin-bottom: 16px; } .vc-drop-zone:hover, .vc-drop-zone.dragover { border-color: #667eea; background: rgba(102, 126, 234, 0.1); } .vc-drop-zone svg { width: 48px; height: 48px; fill: #667eea; margin-bottom: 12px; } .vc-drop-zone p { margin: 0; color: #888; font-size: 14px; } .vc-drop-zone .hint { font-size: 12px; color: #666; margin-top: 8px; } .vc-file-input { display: none; } .vc-file-info { background: rgba(102, 126, 234, 0.1); border-radius: 8px; padding: 12px; margin-bottom: 16px; display: none; } .vc-file-info.show { display: block; } .vc-file-name { font-weight: 600; color: #fff; word-break: break-all; margin-bottom: 4px; } .vc-file-size { font-size: 12px; color: #888; } .vc-options { margin-bottom: 16px; } .vc-option-row { display: flex; align-items: center; margin-bottom: 12px; } .vc-option-row label { flex: 0 0 100px; font-size: 13px; color: #aaa; } .vc-option-row select, .vc-option-row input { flex: 1; background: #2a2a3e; border: 1px solid #3a3a5a; border-radius: 6px; padding: 8px 12px; color: #fff; font-size: 13px; } .vc-option-row select:focus, .vc-option-row input:focus { outline: none; border-color: #667eea; } .vc-checkbox-row { display: flex; align-items: center; margin-bottom: 12px; } .vc-checkbox-row input[type="checkbox"] { margin-right: 8px; width: 16px; height: 16px; accent-color: #667eea; } .vc-checkbox-row label { font-size: 13px; color: #aaa; } .vc-compress-btn { width: 100%; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 8px; color: white; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; } .vc-compress-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); } .vc-compress-btn:disabled { opacity: 0.5; cursor: not-allowed; } .vc-progress { display: none; margin-top: 16px; } .vc-progress.show { display: block; } .vc-progress-bar { height: 8px; background: #2a2a3e; border-radius: 4px; overflow: hidden; margin-bottom: 8px; } .vc-progress-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); width: 0%; transition: width 0.3s ease; } .vc-progress-text { font-size: 12px; color: #888; text-align: center; } .vc-status { padding: 12px; border-radius: 8px; margin-top: 16px; display: none; } .vc-status.show { display: block; } .vc-status.success { background: rgba(76, 175, 80, 0.2); color: #81c784; } .vc-status.error { background: rgba(244, 67, 54, 0.2); color: #e57373; } .vc-download-btn { display: block; width: 100%; padding: 10px; margin-top: 12px; background: #4caf50; border: none; border-radius: 6px; color: white; font-size: 13px; font-weight: 600; cursor: pointer; text-align: center; text-decoration: none; } .vc-download-btn:hover { background: #45a049; } .vc-ffmpeg-status { font-size: 11px; color: #666; margin-top: 8px; text-align: center; } .vc-ffmpeg-status.loading { color: #667eea; } .vc-original-info { display: flex; justify-content: space-between; font-size: 12px; color: #888; margin-top: 4px; } `; // ==================== 初始化 ==================== let ffmpeg = null; let ffmpegLoaded = false; let isCompressing = false; function init() { addStyles(); createUI(); loadFFmpeg(); } function addStyles() { const styleEl = document.createElement('style'); styleEl.textContent = STYLE; document.head.appendChild(styleEl); } function createUI() { // 悬浮按钮 const floatBtn = document.createElement('button'); floatBtn.className = 'vc-float-btn'; floatBtn.innerHTML = ` `; floatBtn.title = '视频压缩器'; floatBtn.onclick = togglePanel; document.body.appendChild(floatBtn); // 主面板 const panel = document.createElement('div'); panel.className = 'vc-panel'; panel.id = 'vc-panel'; panel.innerHTML = `

🎬 视频压缩器

点击选择视频文件

或拖拽视频到此处

准备中...
`; document.body.appendChild(panel); // 事件绑定 const dropZone = document.getElementById('vc-drop-zone'); const fileInput = document.getElementById('vc-file-input'); dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('dragover'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0 && files[0].type.startsWith('video/')) { handleFileSelect(files[0]); } }); fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { handleFileSelect(e.target.files[0]); } }); document.getElementById('vc-compress-btn').addEventListener('click', startCompress); } function togglePanel() { const panel = document.getElementById('vc-panel'); panel.classList.toggle('show'); } let selectedFile = null; function handleFileSelect(file) { selectedFile = file; const fileInfo = document.getElementById('vc-file-info'); const fileName = document.getElementById('vc-file-name'); const fileSize = document.getElementById('vc-file-size'); const compressBtn = document.getElementById('vc-compress-btn'); fileName.textContent = file.name; fileSize.textContent = formatFileSize(file.size); fileInfo.classList.add('show'); compressBtn.disabled = !ffmpegLoaded; // 清除之前的结果 hideStatus(); } function formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'; } // ==================== FFmpeg 加载 ==================== async function loadFFmpeg() { const statusEl = document.getElementById('vc-ffmpeg-status'); const btn = document.getElementById('vc-compress-btn'); if (statusEl) { statusEl.textContent = '正在加载视频处理引擎...'; statusEl.classList.add('loading'); } try { // 动态加载 ffmpeg.wasm const { FFmpeg } = await import('https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.12.7/+esm'); const { fetchFile } = await import('https://cdn.jsdelivr.net/npm/@ffmpeg/util@0.12.1/+esm'); ffmpeg = new FFmpeg(); ffmpeg.on('progress', ({ progress }) => { const fill = document.getElementById('vc-progress-fill'); const text = document.getElementById('vc-progress-text'); if (fill) fill.style.width = Math.round(progress * 100) + '%'; if (text) text.textContent = `处理中: ${Math.round(progress * 100)}%`; }); ffmpeg.on('log', ({ message }) => { console.log('[FFmpeg]', message); }); await ffmpeg.load({ coreURL: 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm/ffmpeg-core.js', wasmURL: 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm/ffmpeg-core.wasm' }); ffmpegLoaded = true; if (statusEl) { statusEl.textContent = '✓ 引擎加载完成,可以开始压缩'; statusEl.classList.remove('loading'); statusEl.style.color = '#4caf50'; } if (btn && selectedFile) btn.disabled = false; } catch (error) { console.error('FFmpeg load error:', error); if (statusEl) { statusEl.textContent = '引擎加载失败,请刷新页面重试'; statusEl.classList.remove('loading'); } } } // ==================== 视频压缩 ==================== async function startCompress() { if (!selectedFile || !ffmpegLoaded || isCompressing) return; isCompressing = true; const btn = document.getElementById('vc-compress-btn'); const progress = document.getElementById('vc-progress'); const fill = document.getElementById('vc-progress-fill'); const text = document.getElementById('vc-progress-text'); const status = document.getElementById('vc-status'); const statusText = document.getElementById('vc-ffmpeg-status'); btn.disabled = true; btn.textContent = '压缩中...'; progress.classList.add('show'); fill.style.width = '0%'; text.textContent = '读取文件...'; hideStatus(); if (statusText) { statusText.textContent = ''; statusText.classList.remove('loading'); } try { const { fetchFile } = await import('https://cdn.jsdelivr.net/npm/@ffmpeg/util@0.12.1/+esm'); // 写入输入文件 text.textContent = '读取视频文件...'; const inputName = 'input' + getExtension(selectedFile.name); await ffmpeg.writeFile(inputName, await fetchFile(selectedFile)); // 构建 ffmpeg 命令 const format = document.getElementById('vc-format').value; const preset = document.getElementById('vc-preset').value; const crf = document.getElementById('vc-crf').value; const removeAudio = document.getElementById('vc-remove-audio').checked; const outputName = 'output.' + format; const args = ['-i', inputName]; // 视频编码设置 if (format === 'mp4') { args.push('-c:v', 'libx264', '-preset', preset, '-crf', crf); } else if (format === 'webm') { args.push('-c:v', 'libvpx-vp9', '-crf', crf, '-b:v', '0'); } else if (format === 'mkv') { args.push('-c:v', 'libx264', '-preset', preset, '-crf', crf); } // 音频设置 if (removeAudio) { args.push('-an'); } else { if (format === 'mp4') { args.push('-c:a', 'aac', '-b:a', '128k'); } else { args.push('-c:a', 'libopus', '-b:a', '128k'); } } args.push('-y', outputName); text.textContent = '正在压缩视频...'; await ffmpeg.exec(args); // 读取输出文件 text.textContent = '生成压缩文件...'; const data = await ffmpeg.readFile(outputName); const blob = new Blob([data.buffer], { type: getMimeType(format) }); const url = URL.createObjectURL(blob); const originalSize = selectedFile.size; const compressedSize = blob.size; const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(1); // 显示成功状态 status.className = 'vc-status show success'; status.innerHTML = `
🎉
压缩完成!
原始大小: ${formatFileSize(originalSize)} → 压缩后: ${formatFileSize(compressedSize)}
体积减少 ${ratio}%
⬇️ 下载压缩后的视频 `; text.textContent = '完成!'; } catch (error) { console.error('Compression error:', error); status.className = 'vc-status show error'; status.innerHTML = `
压缩失败
${error.message || '未知错误'}
`; text.textContent = '失败'; } finally { isCompressing = false; btn.disabled = false; btn.textContent = '开始压缩'; } } function hideStatus() { const status = document.getElementById('vc-status'); status.className = 'vc-status'; } function getExtension(filename) { return '.' + filename.split('.').pop().toLowerCase(); } function getMimeType(format) { const mimeTypes = { 'mp4': 'video/mp4', 'webm': 'video/webm', 'mkv': 'video/x-matroska' }; return mimeTypes[format] || 'video/mp4'; } // ==================== 启动 ==================== if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();