Sleazy Fork is available in English.

Free your hand - Pornhub

easily fast forward video to the high time.

As of 2018-10-14. See the latest version.

// ==UserScript==
// @name         Free your hand - Pornhub
// @namespace    
// @version      0.4.2
// @license      MPL-2.0
// @description  easily fast forward video to the high time.
// @author       c4r
// @match        https://www.pornhub.com/view_video.php?viewkey=*
// @match        https://www.pornhubpremium.com/view_video.php?viewkey=*
// @match        www.pornhubselect.com/*
// @require      https://code.jquery.com/jquery-latest.js
// @grant        none
// ==/UserScript==

(function () {
    'use strict';



    /*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
        that detects and handles AJAXed content.
        auther : BrockA
        homepage : https://gist.github.com/BrockA/2625891#file-waitforkeyelements-js
        Usage example:

            waitForKeyElements (
                "div.comments"
                , commentCallbackFunction
            );

            //--- Page-specific function to do what we want when the node is found.
            function commentCallbackFunction (jNode) {
                jNode.text ("This comment changed by waitForKeyElements().");
            }

        IMPORTANT: This function requires your script to have loaded jQuery.
    */
    function waitForKeyElements(
        selectorTxt,    /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */
        actionFunction, /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */
        bWaitOnce,      /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */
        iframeSelector  /* Optional: If set, identifies the iframe to
                        search.
                    */
    ) {
        var targetNodes, btargetsFound;

        if (typeof iframeSelector == "undefined")
            targetNodes = $(selectorTxt);
        else
            targetNodes = $(iframeSelector).contents()
                .find(selectorTxt);

        if (targetNodes && targetNodes.length > 0) {
            btargetsFound = true;
            /*--- Found target node(s).  Go through each and act if they
                are new.
            */
            targetNodes.each(function () {
                var jThis = $(this);
                var alreadyFound = jThis.data('alreadyFound') || false;

                if (!alreadyFound) {
                    //--- Call the payload function.
                    var cancelFound = actionFunction(jThis);
                    if (cancelFound)
                        btargetsFound = false;
                    else
                        jThis.data('alreadyFound', true);
                }
            });
        }
        else {
            btargetsFound = false;
        }

        //--- Get the timer-control variable for this selector.
        var controlObj = waitForKeyElements.controlObj || {};
        var controlKey = selectorTxt.replace(/[^\w]/g, "_");
        var timeControl = controlObj[controlKey];

        //--- Now set or clear the timer as appropriate.
        if (btargetsFound && bWaitOnce && timeControl) {
            //--- The only condition where we need to clear the timer.
            clearInterval(timeControl);
            delete controlObj[controlKey]
        }
        else {
            //--- Set a timer, if needed.
            if (!timeControl) {
                timeControl = setInterval(function () {
                    waitForKeyElements(selectorTxt,
                        actionFunction,
                        bWaitOnce,
                        iframeSelector
                    );
                },
                    300
                );
                controlObj[controlKey] = timeControl;
            }
        }
        waitForKeyElements.controlObj = controlObj;
    }



    function merge(left, right) { //合并两个子数组
        var result = [];
        while (left.length && right.length) {
            var item = left[0] >= right[0] ? left.shift() : right.shift();//注意:判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
            result.push(item);
        }
        return result.concat(left.length ? left : right);
    }

    function mergeSort(array) {  //采用自上而下的递归方法
        var length = array.length;
        if (length < 2) {
            return array;
        }
        var m = (length >> 1),
            left = array.slice(0, m),
            right = array.slice(m); //拆分为两个子数组
        return merge(mergeSort(left), mergeSort(right));//子数组继续递归拆分,然后再合并
    }
    function filter_av(array_y) {
        let av_n = Math.floor(array_y.length / 100.);
        if (av_n < 5) {
            av_n = 5;
        }
        if (av_n % 2 == 0) {
            av_n = av_n + 1;
        }
        let array_r = new Array(array_y.length);
        for (let i = 0; i < array_y.length; i++) {
            if (i < (av_n - 1) / 2) {
                array_r[i] = array_y[i];
            } else if (array_y.length - i <= (av_n - 1) / 2) {
                array_r[i] = array_y[i];
            } else {
                array_r[i] = 0;
                for (let j = 0; j < av_n; j++) {
                    array_r[i] = array_r[i] + array_y[i + j - (av_n - 1) / 2];
                }
                array_r[i] = array_r[i] / av_n;
            }
        }
        return array_r;
    }

    function find_peak(array_y) {

        let array_sort = array_y;
        mergeSort(array_sort);
        let average = array_sort[Math.floor(array_sort.length * 0.7)];
        // average = 0;
        console.log("av : " + average);

        let peek = new Array();
        if (array_y[1] < array_y[0] && array_y[0] > average) {
            peek.push(0);
        }

        for (let i = 1; i < array_y.length - 1; i++) {
            if (array_y[i - 1] < array_y[i] && array_y[i + 1] <= array_y[i] && array_y[i] > average) {
                // console.log(peek.length, i,peek[peek.length-1], array_y[i]);
                // if(peek.length == 0 || (i - peek[peek.length-1] > array_y.length/40) || (array_y[i] > array_y[peek[peek.length-1]]) ){
                peek.push(i);
                // }

            }
        }

        if (array_y[array_y.length - 2] < array_y[array_y.length - 1] && array_y[array_y.length - 1] > average) {
            peek.push(array_y.length - 1);
        }

        // console.log(peek)
        // console.log("============")
        // console.log(array_y.length/40)
        // console.log("============")
        // 去除多余
        let peek_del = new Array();
        for (let i = 0; i < peek.length; i++) {
            let toSave = true
            for (let j = 0; j < peek.length; j++) {
                // 红点间距最短为视频时长40等分, 在前后40等分中取最高的
                if (toSave && i != j && Math.abs(peek[j] - peek[i]) < array_y.length / 40 && array_y[peek[i]] <= array_y[peek[j]]) {


                    toSave = false
                    // console.log('del-----')
                    // console.log(i,peek[i],array_y[peek[i]])
                    // console.log(j,peek[j],array_y[peek[j]])
                }
            }
            if (toSave) {
                peek_del.push(peek[i])
                // console.log('save-----')
                // console.log(i,peek[i],array_y[peek[i]])
            }
        }

        return peek_del;
    }

    function mark(array_y, duration) {

        let objBar = $("div.mhp1138_progressOverflow");
        // console.log(objBar);
        let markP1 = "<div data-tag=\"HighTime\" class=\"mhp1138_actionTag\" style=\"left: ";
        let markP3 = "%; width: 0.178995%;\"></div>";

        for (let i = 0; i < array_y.length; i++) {
            // console.log(i);
            $(objBar).append(markP1 + (array_y[i] / duration * 100.).toString() + markP3);
        }

        $(objBar).find("div.mhp1138_actionTag").each((index, element) => {
            if ($(element).attr("data-tag") == "HighTime") {

                $(element).css("background-color", "red");

            }
        });
    }

    function actionVideo() {
        let str_point = $("polygon").attr("points");
        let str_array_point = str_point.split(" ");

        // console.log(str_array_point.length);
        // console.log(str_array_point[str_array_point.length-1]);

        // 得到数组
        let len_video = parseFloat(str_array_point[str_array_point.length - 2].split(",")[0]);
        console.log("video :" + len_video);
        let array_point = new Array();
        for (i = 0; i < str_array_point.length - 1; i++) {
            let point = str_array_point[i].split(",");
            let x = parseFloat(point[0]);
            let y = -parseFloat(point[1]) + 100.;
            // console.log(x,y);
            array_point.push([x, y]);
        }


        let nodevideo = $("video").get(0);
        let len_array = Math.floor(nodevideo.duration);

        console.log("debug : len_array : ", len_array)
        if (len_array % 2 == 0) {
            len_array = len_array + 1;
        }
        let array_eq_point = new Array(len_array);
        let dis = len_video / (len_array - 1);

        let array_y = new Array();
        let array_x = new Array();
        for (i = 0; i < len_array; i++) {
            let x = dis * (i);
            let y = 0.;
            let xInRange = false;
            for (let j = 0; j < array_point.length; j++) {
                if ((array_point[j])[0] > x) {
                    y = ((array_point[j])[1] - (array_point[j - 1])[1]) / ((array_point[j])[0] - (array_point[j - 1])[0]) * (x - (array_point[j - 1])[0]) + (array_point[j - 1])[1];
                    break;
                }
            }
            array_y.push(y);
            array_x.push(x);
            // console.log(i, x,y, (array_point[i]) )
        }

        let array_filter_y = filter_av(array_y);

        // console.log("====");
        // console.log(array_filter_y);
        // console.log("====");

        // <============得到峰值对应的index============>
        let array_peek_index = find_peak(array_filter_y);

        console.log("时长 : " + nodevideo.duration);

        // 得到对应的时间
        for (let i = 0; i < array_peek_index.length; i++) {
            array_peek_index[i] = array_peek_index[i] * dis / len_video * nodevideo.duration;
        }

        // console.log("peak : " + array_peek_index.length);

        // 做标记
        mark(array_peek_index, nodevideo.duration);

        // console.log(array_peek_index);
        // 当前播放进度
        // console.log(nodevideo.currentTime);


        $(document).keydown(function (event) {

            if (event.keyCode == 190) { // 前进

                for (let i = 0; i < array_peek_index.length; i++) {

                    if (array_peek_index[i] > nodevideo.currentTime) {
                        nodevideo.currentTime = array_peek_index[i];
                        break;
                    }
                }

            } else if (event.keyCode == 188) { // 后退

                for (let i = array_peek_index.length - 1; i > 0; i--) {

                    if (array_peek_index[i] < nodevideo.currentTime) {

                        if (i == 0) {

                            if ((nodevideo.currentTime - array_peek_index[i]) < (array_peek_index[i + 1] - array_peek_index[i]) / 3.) {
                                nodevideo.currentTime = 0;
                                break;
                            } else {
                                nodevideo.currentTime = array_peek_index[i];
                                break;
                            }

                        } else if (i == array_peek_index.length - 1) {
                            if ((nodevideo.currentTime - array_peek_index[i]) < (nodevideo.duration - array_peek_index[i]) / 3.) {
                                nodevideo.currentTime = array_peek_index[i - 1];
                                break;
                            } else {
                                nodevideo.currentTime = array_peek_index[i];
                                break;
                            }
                        } else {
                            if ((nodevideo.currentTime - array_peek_index[i]) < (array_peek_index[i + 1] - array_peek_index[i]) / 3.) {
                                nodevideo.currentTime = array_peek_index[i - 1];
                                break;
                            } else {
                                nodevideo.currentTime = array_peek_index[i];
                                break;
                            }
                        }
                    }
                }

            } else if (event.keyCode >= 48 && event.keyCode <= 57) { // 数字键

                nodevideo.currentTime = (event.keyCode - 48) * nodevideo.duration / 10.

            }
        });
    }

    $(document).ready(function () {
        console.log("ready!");


        // 准备等分数组
        // if(array_point.length %2 == 0){
        //     let len_array = array_point.length+1;
        // }else{
        //     let len_array = array_point.length;
        // }


        // console.log("check video : " , $("video").get(0))

        waitForKeyElements("video", function () {
            if (isNaN($("video").get(0).duration)) {
                console.log("wait load")
                $("video").on('loadedmetadata', function () {
                    actionVideo()
                })
            } else {
                console.log("load directly")
                actionVideo()
            }
        }, false)


    });

})();