Greasy Fork

Greasy Fork is available in English.

视频下载器

捕捉浏览器页面中的视频,支持webm转mp4格式并下载

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         视频下载器
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  捕捉浏览器页面中的视频,支持webm转mp4格式并下载
// @author       You
// @match        *://*/*
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/ffmpeg.min.js
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 视频捕捉函数
    function captureVideos() {
        const videos = [];
        const videoElements = document.querySelectorAll('video');
        
        videoElements.forEach((video, index) => {
            let videoUrl = '';
            
            // 直接获取src属性
            if (video.src && video.src !== '') {
                videoUrl = video.src;
            } 
            // 从source元素获取
            else {
                const sources = video.querySelectorAll('source');
                if (sources.length > 0) {
                    videoUrl = sources[0].src;
                }
            }
            
            // 处理blob URL
            if (videoUrl.startsWith('blob:')) {
                // 尝试从网络请求中获取原始URL
                // 这部分可能需要更复杂的处理
                console.log('发现blob URL,需要特殊处理:', videoUrl);
            }
            
            if (videoUrl) {
                videos.push({
                    id: index,
                    url: videoUrl,
                    type: getVideoType(videoUrl),
                    element: video
                });
            }
        });
        
        return videos;
    }
    
    // 获取视频类型
    function getVideoType(url) {
        if (url.includes('.mp4') || url.includes('video/mp4')) return 'mp4';
        if (url.includes('.webm') || url.includes('video/webm')) return 'webm';
        if (url.includes('.m3u8') || url.includes('application/x-mpegURL')) return 'm3u8';
        if (url.includes('.flv') || url.includes('video/x-flv')) return 'flv';
        return 'unknown';
    }
    
    // 视频格式转换函数
    async function convertVideo(videoUrl, fromType, toType = 'mp4') {
        if (fromType === toType) {
            // 如果格式已经是目标格式,直接返回原始URL
            return videoUrl;
        }
        
        if (fromType !== 'webm' || toType !== 'mp4') {
            // 目前只支持webm转mp4
            console.log('目前只支持webm转mp4格式');
            return videoUrl;
        }
        
        try {
            // 初始化FFmpeg
            const { createFFmpeg, fetchFile } = FFmpeg;
            const ffmpeg = createFFmpeg({
                log: true,
                corePath: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/ffmpeg-core.js'
            });
            
            await ffmpeg.load();
            
            // 下载视频文件
            const inputFileName = `input.${fromType}`;
            const outputFileName = `output.${toType}`;
            
            // 从URL获取视频数据
            const response = await fetch(videoUrl);
            const arrayBuffer = await response.arrayBuffer();
            
            // 写入到FFmpeg文件系统
            ffmpeg.FS('writeFile', inputFileName, new Uint8Array(arrayBuffer));
            
            // 执行转换命令
            await ffmpeg.run('-i', inputFileName, outputFileName);
            
            // 获取转换后的文件数据
            const data = ffmpeg.FS('readFile', outputFileName);
            
            // 创建Blob URL
            const blob = new Blob([data.buffer], { type: `video/${toType}` });
            const convertedUrl = URL.createObjectURL(blob);
            
            // 清理资源
            ffmpeg.FS('unlink', inputFileName);
            ffmpeg.FS('unlink', outputFileName);
            
            return convertedUrl;
        } catch (error) {
            console.error('视频转换失败:', error);
            return videoUrl; // 转换失败时返回原始URL
        }
    }
    
    // 视频下载函数
    function downloadVideo(videoUrl, fileName = '', fileType = 'mp4') {
        // 生成默认文件名
        if (!fileName) {
            const timestamp = new Date().getTime();
            fileName = `video_${timestamp}.${fileType}`;
        }
        
        // 添加文件扩展名
        if (!fileName.includes('.')) {
            fileName += `.${fileType}`;
        }
        
        try {
            // 使用GM_download下载
            GM_download({
                url: videoUrl,
                name: fileName,
                saveAs: true, // 显示保存对话框
                onload: function() {
                    console.log('视频下载完成:', fileName);
                },
                onerror: function(error) {
                    console.error('视频下载失败:', error);
                    // 如果GM_download失败,尝试使用原生下载
                    fallbackDownload(videoUrl, fileName);
                }
            });
        } catch (error) {
            console.error('GM_download失败:', error);
            // 回退到原生下载
            fallbackDownload(videoUrl, fileName);
        }
    }
    
    // 原生下载回退函数
    function fallbackDownload(videoUrl, fileName) {
        const link = document.createElement('a');
        link.href = videoUrl;
        link.download = fileName;
        link.style.display = 'none';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        
        // 对于blob URL,需要清理
        if (videoUrl.startsWith('blob:')) {
            setTimeout(() => {
                URL.revokeObjectURL(videoUrl);
            }, 1000);
        }
    }
    
    // 完整的视频下载流程
    async function processAndDownload(video, convertToMp4 = true) {
        let videoUrl = video.url;
        let fileType = video.type;
        
        // 如果需要转换为mp4且当前不是mp4格式
        if (convertToMp4 && video.type !== 'mp4') {
            console.log('正在转换视频格式...');
            const convertedUrl = await convertVideo(video.url, video.type, 'mp4');
            videoUrl = convertedUrl;
            fileType = 'mp4';
        }
        
        // 下载视频
        console.log('开始下载视频...');
        downloadVideo(videoUrl, '', fileType);
    }
    
    // 创建用户界面
    function createUI() {
        // 检查界面是否已存在
        if (document.getElementById('video-downloader-ui')) {
            return;
        }
        
        // 创建UI容器
        const container = document.createElement('div');
        container.id = 'video-downloader-ui';
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            width: 350px;
            max-height: 500px;
            background: rgba(255, 255, 255, 0.95);
            border: 1px solid #ccc;
            border-radius: 8px;
            padding: 15px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
            z-index: 9999;
            font-family: Arial, sans-serif;
            overflow-y: auto;
        `;
        
        // 创建标题
        const title = document.createElement('h3');
        title.textContent = '视频下载器';
        title.style.cssText = `
            margin: 0 0 15px 0;
            font-size: 18px;
            color: #333;
        `;
        container.appendChild(title);
        
        // 创建捕捉按钮
        const captureBtn = document.createElement('button');
        captureBtn.textContent = '捕捉视频';
        captureBtn.id = 'capture-videos-btn';
        captureBtn.style.cssText = `
            width: 100%;
            padding: 10px;
            margin-bottom: 15px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        `;
        container.appendChild(captureBtn);
        
        // 创建自动转换选项
        const convertOption = document.createElement('div');
        convertOption.style.cssText = `
            margin-bottom: 15px;
            display: flex;
            align-items: center;
        `;
        
        const convertCheckbox = document.createElement('input');
        convertCheckbox.type = 'checkbox';
        convertCheckbox.id = 'auto-convert-checkbox';
        convertCheckbox.checked = true;
        convertCheckbox.style.marginRight = '8px';
        
        const convertLabel = document.createElement('label');
        convertLabel.htmlFor = 'auto-convert-checkbox';
        convertLabel.textContent = '自动转换为MP4格式';
        convertLabel.style.fontSize = '14px';
        
        convertOption.appendChild(convertCheckbox);
        convertOption.appendChild(convertLabel);
        container.appendChild(convertOption);
        
        // 创建视频列表容器
        const videoList = document.createElement('div');
        videoList.id = 'video-list';
        videoList.style.cssText = `
            max-height: 300px;
            overflow-y: auto;
        `;
        container.appendChild(videoList);
        
        // 添加到页面
        document.body.appendChild(container);
        
        // 添加关闭按钮
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        closeBtn.style.cssText = `
            position: absolute;
            top: 5px;
            right: 10px;
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #666;
        `;
        
        closeBtn.addEventListener('click', () => {
            container.style.display = 'none';
        });
        container.appendChild(closeBtn);
        
        // 绑定捕捉按钮事件
        captureBtn.addEventListener('click', () => {
            const videos = captureVideos();
            displayVideos(videos);
        });
    }
    
    // 显示视频列表
    function displayVideos(videos) {
        const videoList = document.getElementById('video-list');
        videoList.innerHTML = '';
        
        if (videos.length === 0) {
            videoList.innerHTML = '<p style="text-align: center; color: #666; padding: 20px;">未找到视频</p>';
            return;
        }
        
        videos.forEach((video, index) => {
            const videoItem = document.createElement('div');
            videoItem.style.cssText = `
                background: #f9f9f9;
                padding: 10px;
                margin-bottom: 10px;
                border-radius: 4px;
                border: 1px solid #eee;
            `;
            
            // 视频信息
            const videoInfo = document.createElement('div');
            videoInfo.style.cssText = `
                font-size: 12px;
                margin-bottom: 8px;
                color: #666;
                word-break: break-all;
            `;
            
            const videoUrlShort = video.url.length > 50 ? video.url.substring(0, 50) + '...' : video.url;
            videoInfo.innerHTML = `
                <p>视频 ${index + 1}</p>
                <p>格式: ${video.type}</p>
                <p>URL: ${videoUrlShort}</p>
            `;
            
            // 下载按钮容器
            const buttonContainer = document.createElement('div');
            buttonContainer.style.cssText = `
                display: flex;
                gap: 5px;
            `;
            
            // 原始下载按钮
            const downloadBtn = document.createElement('button');
            downloadBtn.textContent = '下载';
            downloadBtn.style.cssText = `
                flex: 1;
                padding: 6px;
                background: #2196F3;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            `;
            
            downloadBtn.addEventListener('click', () => {
                processAndDownload(video, false);
            });
            
            // 转换下载按钮
            const convertDownloadBtn = document.createElement('button');
            convertDownloadBtn.textContent = '转换下载';
            convertDownloadBtn.style.cssText = `
                flex: 1;
                padding: 6px;
                background: #FF9800;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            `;
            
            convertDownloadBtn.addEventListener('click', () => {
                processAndDownload(video, true);
            });
            
            // 添加按钮
            buttonContainer.appendChild(downloadBtn);
            buttonContainer.appendChild(convertDownloadBtn);
            
            // 添加到视频项
            videoItem.appendChild(videoInfo);
            videoItem.appendChild(buttonContainer);
            
            // 添加到列表
            videoList.appendChild(videoItem);
        });
    }
    
    // 脚本加载完成后执行
    window.addEventListener('load', () => {
        // 延迟创建UI,确保页面已完全加载
        setTimeout(createUI, 1000);
    });

})();