NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Free your hand - Pornhub // @namespace // @version 1.1.2 // @license MPL-2.0 // @description easily fast forward video to the high time. // @author c4r, foolool // @match https://*.pornhub.com/view_video.php?viewkey=* // @match https://*.pornhubpremium.com/view_video.php?viewkey=* // @match www.pornhubselect.com/* // @require https://code.jquery.com/jquery-latest.js // @grant none // ==/UserScript== (function () { 'use strict'; /** * Custom : the shortcut * you can specific your code via : https://keycode.info/ * default : * - next : n(78), >(190) * - previous : b(66), 188(<) * - antic clockwise rotate : h(72), [(219) * - clockwise rotate : j(74) , ](219) */ let array_next_key = [78, 190] let array_pre_key = [66, 188] let array_anticlock = [72, 219] let array_clock = [74, 221] /*--- 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; } // Returns rotation in degrees when obtaining transform-styles using javascript // author : adamcbrewer // https://gist.github.com/adamcbrewer/4202226 function getRotationDegrees(obj) { var matrix = obj.css("-webkit-transform") || obj.css("-moz-transform") || obj.css("-ms-transform") || obj.css("-o-transform") || obj.css("transform"); if (matrix !== 'none') { var values = matrix.split('(')[1].split(')')[0].split(','); var a = values[0]; var b = values[1]; var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI)); } else { var angle = 0; } return angle; } /** * merge two sorted array * @param {*} left * @param {*} right */ 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); } /** * merge sort method * @param {*} array */ 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); // split into two sub-array return merge(mergeSort(left), mergeSort(right)); // recurrence } /** * easiest Mean Average method * @param {array} array_y y value with spread with equal interval * @returns array with same length of array_y */ 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; } /** * find the maximum peak in array_y * @param {*} array_y */ function find_peak(array_y) { let array_sort = array_y; mergeSort(array_sort); let average = array_sort[Math.floor(array_sort.length * 0.7)]; 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); } // remove excess let peek_del = new Array(); for (let i = 0; i < peek.length; i++) { let toSave = true for (let j = 0; j < peek.length; j++) { // The shortest red dot spacing is 40 equal parts for the video duration, the highest in the 40 sec. if (toSave && i != j && Math.abs(peek[j] - peek[i]) < array_y.length / 40 && array_y[peek[i]] <= array_y[peek[j]]) { toSave = false } } if (toSave) { peek_del.push(peek[i]) } } return peek_del; } /** * attach the marker to the progress bar in the page * @param {array} array_y * @param {float} duration */ 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 isMarked() { return $('[data-tag="HighTime"]').length > 0 } function loadedPolygon() { return $("polygon").length > 0 && $("polygon").attr("points").split(" ").length > 0 } /** * if video is found in page, this function will be called. * this functions contains : * - get all the view data * - analyse the progress bar * - get the highpoint * - add marker to page */ function actionVideo() { if (isMarked()) { return } if (!loadedPolygon()) { return } /**<============Get view data============> * the raw view data will be stored in `array_point` as a two dimensional matrix * array_point : [[x1,y2],[x2,y2],..] * x : 0 to 1000 * y : 0 - 100 */ let str_point = $("polygon").attr("points"); let str_array_point = str_point.split(" "); let len_point = parseFloat(str_array_point[str_array_point.length - 2].split(",")[0]); //console.log("video :" + len_point); 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]); } /**<============interpolation============> * interpolate the raw data at every second, and store in * array_x : second. range : 0 to the duration (closest even integer) * array_y : interpolated data from array_point. range : 0-100 */ let nodevideo = $("video").get(0); let len_point_sec = Math.floor(nodevideo.duration); if (len_point_sec % 2 == 0) { len_point_sec = len_point_sec + 1; } let dis = len_point / (len_point_sec - 1); let array_y = new Array(); let array_x = new Array(); for (i = 0; i < len_point_sec; 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); } // <============smooth y data============> let array_smooth_y = filter_av(array_y); // <============Get the peak corresponding index============> let array_peek_index = find_peak(array_smooth_y); // <============get the corresponding time============> for (let i = 0; i < array_peek_index.length; i++) { array_peek_index[i] = array_peek_index[i] * dis / len_point * nodevideo.duration; } // <============add markers on the process bar============> mark(array_peek_index, nodevideo.duration); console.log('your hands are free now !!!') // console.log('peek index : ', array_peek_index) // <============listen keyboard============> $(document).keydown(function (event) { if (array_next_key.includes(event.keyCode)) { // next point (N) for (let i = 0; i < array_peek_index.length; i++) { if (array_peek_index[i] > nodevideo.currentTime) { nodevideo.currentTime = array_peek_index[i]; break; } } event.stopImmediatePropagation(); } else if (array_pre_key.includes(event.keyCode)) { // previous point (B) let setDuration let currentTime = nodevideo.currentTime for (let i = array_peek_index.length - 1; i > 0; i--) { // console.log('i : ', i ,array_peek_index[i] , currentTime ) if (array_peek_index[i] < currentTime) { if (i == 0) { if ((currentTime - array_peek_index[i]) < (array_peek_index[i + 1] - array_peek_index[i]) / 3.) { setDuration = 0; break; } else { setDuration = array_peek_index[i]; break; } } else if (i == array_peek_index.length - 1) { if ((currentTime - array_peek_index[i]) < (nodevideo.duration - array_peek_index[i]) / 3.) { setDuration = array_peek_index[i - 1]; break; } else { setDuration = array_peek_index[i]; break; } } else { if ((currentTime - array_peek_index[i]) < (array_peek_index[i + 1] - array_peek_index[i]) / 3.) { setDuration = array_peek_index[i - 1]; break; } else { setDuration = array_peek_index[i]; break; } } } } // console.log('set duration : ', setDuration) nodevideo.currentTime = setDuration event.stopImmediatePropagation(); } else if (event.keyCode >= 48 && event.keyCode <= 57) { // number key // console.log("press ", (event.keyCode - 48)) nodevideo.currentTime = (event.keyCode - 48) * nodevideo.duration / 10. event.stopImmediatePropagation(); } else if (event.keyCode >= 96 && event.keyCode <= 105) { // numpad number key // console.log("press ", (event.keyCode - 96)) nodevideo.currentTime = (event.keyCode - 96) * nodevideo.duration / 10. event.stopImmediatePropagation(); } else if (array_anticlock.includes(event.keyCode)) { // Rotate anticlockwise (H) // console.log("press H") var angle = getRotationDegrees($(nodevideo)) - 90; console.log(angle); if (Math.abs(angle) === 90 || angle === 270) { $(nodevideo).css("transform", "rotate(" + angle + "deg)" + " scale(calc(16/9))") } else { $(nodevideo).css("transform", "rotate(" + angle + "deg)" + " scale(1)") } event.stopImmediatePropagation(); } else if (array_clock.includes(event.keyCode)) { // Rotate clockwise (J) // console.log("press J") var angle = getRotationDegrees($(nodevideo)) + 90; console.log(angle); if (Math.abs(angle) === 90 || angle === 270) { $(nodevideo).css("transform", "rotate(" + angle + "deg)" + " scale(calc(16/9))") } else { $(nodevideo).css("transform", "rotate(" + angle + "deg)" + " scale(1)") } event.stopImmediatePropagation(); } }); } // <============Start Here============> $(document).ready(function () { console.log("loading your hand assistant..."); // waiting video appeared 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) }); })();