Greasy Fork

Greasy Fork is available in English.

HTML5视频截图器

基于HTML5的原生javascript视频截图器

当前为 2018-09-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         HTML5视频截图器
// @namespace    indefined
// @supportURL   https://github.com/indefined/UserScripts/issues
// @version      0.3.2
// @description  基于HTML5的原生javascript视频截图器
// @author       indefined
// @include      *://*
// @run-at       document-idle
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==

function init(){
    'use strict';
    if (document.querySelector('#HTML5VideoCapture')) return;
    const childs = "undefined"==typeof(unsafeWindow)?window.frames:unsafeWindow.frames;
    let videos,video,selectId;
    function videoShot(down){
        if (!video) return postMsg('shot',down);
        const canvas = document.createElement("canvas");
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        canvas.getContext('2d')
            .drawImage(video, 0, 0, canvas.width, canvas.height);
        try{
            if (!down) throw `i don't want to do it.`;
            const a = document.createElement('a');
            a.href = canvas.toDataURL('image/jpeg', 0.95);
            a.download = `${document.title}_${Math.floor(video.currentTime/60)}'${(video.currentTime%60).toFixed(3)}''.jpg`;
            document.head.appendChild(a);
            a.click();
            a.remove();
        }catch(e){
            const imgWin = open("",'_blank');
            canvas.style = "max-width:100%";
            imgWin.document.body.appendChild(canvas);
        }
    }

    function videoPlay(id){
        if (!video) return postMsg('play');
        video.paused?video.play():video.pause();
        videoStatusUpdate();
    }

    function videoStep(action){
        if (!video) return postMsg('step',action);
        if (!video.paused) videoPlay();
        if (action=='pre') video.currentTime -= 1/30;
        else video.currentTime += 1/30;
    }

    function videoDetech(){
        if (window==top){
            while(selector.firstChild) selector.removeChild(selector.firstChild);
        }
        videos = document.querySelectorAll('video');
        if (window!=top){
            top.postMessage({
                action:'captureReport',
                about:'videoNums',
                length:videos.length,
                id:window.captureId
            },'*');
        }else{
            appendSelector(videos);
            setTimeout(()=>{
                if (selector.childNodes.length) return videoSelect(selector.value);
                video = undefined;
                const toast = document.createElement('div');
                toast.style = `position: fixed;top: 50%;left: 50%;z-index: 999999;padding: 10px;background: darkcyan;transform: translate(-50%);color: #fff;border-radius: 6px;`
                toast.innerText = '当前页面没有检测到HTML5视频';
                document.body.appendChild(toast);
                setTimeout(()=>toast.remove(),2000);
            },100);
        }
        if (childs.length){
            [].forEach.call(childs,(w,i)=>w.postMessage({
                action:'captureDetech',
                id:window.captureId==undefined?i:window.captureId+'-'+i
            },'*'));
        }
        console.log(videos);
    }
    function videoSelect(id){
        selectId = id;
        if (videos[id]){
            video = videos[id];
            video.scrollIntoView();
            videoStatusUpdate();
        }
        else {
            postMsg('select');
        }
    }
    function videoStatusUpdate(){
        if (window==top) play.value = video.paused?"播放":"暂停";
        else{
            top.postMessage({
                action:'captureReport',
                about:'videoStatus',
                value:video.paused,
                id:window.captureId
            },'*');
        }
    }
    window.addEventListener('message', function(ev) {
        //console.info(ev.data);
        if (!ev.data.action) return;
        switch (ev.data.action){
            case 'captureDetech':
                if (ev.source!=window.parent) return;
                window.captureId = ev.data.id;
                videoDetech();
                break;
            case 'captureControl':
                if (ev.source!=window.parent||ev.data.target!=window.captureId) return;
                switch (ev.data.todo){
                    case 'play':
                        videoPlay(ev.data.value);
                        break;
                    case 'shot':
                        videoShot(ev.data.value);
                        break;
                    case 'step':
                        videoStep(ev.data.value);
                        break;
                    case 'select':
                        videoSelect(ev.data.id);
                        break;
                    default:
                        break;
                }
                break;
            case 'captureReport':
                if (ev.data.about=='videoNums') appendSelector(ev.data);
                else if (ev.data.about=='videoStatus'&&selector.value.startsWith(ev.data.id)){
                    play.value = ev.data.value?"播放":"暂停";;
                }
                break;
        }
    });
    function postMsg(type,data){
        if (selectId==undefined||selectId=='') return;
        const ids = selectId.split('-');
        if (ids.length>1){
            const target = ids.shift();
            if (!childs[target]) return;
            childs[target].postMessage({
                action:'captureControl',
                target:window.captureId==undefined?target:window.captureId+'-'+target,
                todo:type,
                id:ids.join('-'),
                value:data
            },'*');
        }
    }
    if (window!=top) return;
    function appendSelector(v){
        if (v&&v.length){
            for (let i=0;i<v.length;i++){
                const item = document.createElement('option');
                item.value = v.id!=undefined?v.id+'-'+i:i;
                item.innerText = v.id!=undefined?v.id+'-'+i:i;
                selector.appendChild(item);
            }
        }
    }
    function dialogMove(ev){
        if (ev.type=='mousedown'){
            panel.t = ev.pageY-panel.offsetTop;
            panel.l = ev.pageX-panel.offsetLeft;
            panel.addEventListener('mousemove',dialogMove);
        }
        else if (ev.type=='mouseup'){
            panel.removeEventListener('mousemove',dialogMove);
        }
        else{
            panel.style.top = ev.pageY-panel.t+'px';
            panel.style.left = ev.pageX-panel.l+'px';
        }
    }
    const panel = document.createElement('div');
    panel.id = "HTML5VideoCapture";
    panel.style = `position: fixed;top: 50px;left: 20px;z-index: 999999;padding: 10px;background: darkcyan;`
    panel.innerHTML = `<div style="margin-bottom: 5px;cursor:move;user-select:none;line-height:1;color:#fff">HTML5视频截图器</div>\
<input type="button" value="检测" style="margin-right: 10px;"><select title="选择视频" style="font-family: initial;"></select>\
<input type="button" value="播放" style="margin-left: 10px;"><input type="button" value="上一帧" style="margin-left: 10px;">\
<input type="button" value="截图" style="margin-left: 10px;"><input type="button" value="↓" style="font-family: initial;">\
<input type="button" value="下一帧" style="margin-left: 10px;"><input type="button" value="关闭" style="margin-left: 10px;">`
    const {0:title,1:detech,2:selector,3:play,4:preFrame,5:capture,6:captureDown,7:nextFrame,8:close} = panel.childNodes;
    title.onmousedown = dialogMove;
    title.onmouseup = dialogMove;
    selector.onchange = ()=>videoSelect(selector.value);
    detech.onclick = videoDetech;
    play.onclick = videoPlay;
    preFrame.onclick = ()=>videoStep('pre');
    nextFrame.onclick = ()=>videoStep('next');
    capture.onclick = ()=>videoShot();
    captureDown.onclick = ()=>videoShot(true);
    close.onclick = ()=>panel.remove();
    document.body.appendChild(panel);
    videoDetech();
}
if ('function'==typeof(GM_registerMenuCommand) && window==top){
    GM_registerMenuCommand('启用HTML5视频截图器',init);
}else init();