diff --git a/css/videoskip.css b/css/videoskip.css new file mode 100644 index 0000000..cc08b29 --- /dev/null +++ b/css/videoskip.css @@ -0,0 +1,88 @@ +/* General */ +html { + scroll-padding-top: 70px; /* height of sticky header */ +} + +body { + font: 20px Montserrat, sans-serif; + line-height: 1.8; + color: #f5f6f7; +} + +p { + font-size: 16px; +} +.info { + background-color: palegreen; +} +.error { + background-color: red; + color: white; +} + +/* Navbar Elements */ +.navbar { + padding-top: 15px; + padding-bottom: 15px; + border: 0; + border-radius: 0; + margin-bottom: 0; + letter-spacing: 2px; +} +.navbar-nav li a:hover { + color: #1abc9c !important; +} + +/* First jumbotron */ +.jumbotron { + margin-top: 50px !important; + color: #000; + padding: 100px 25px; + font-family: Montserrat, sans-serif; + font-size: x-large; +} + +/* Skip container Area */ +.skip-container { + margin-top: 15px; +} +#skipMsg { + margin-bottom: 5px !important; +} +#boxMsg { + color: blue; + z-index: 2; +} +/*#subFile { + display: none; +} +#skipFile { + display: none; +} +#screenShot { + z-index: 1; +} +.cssbutton { + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + font-family: Arial; + font-size: 18px; + padding: 12px; + text-decoration: none; + border: 0px; + color: #555555; + background: #e6e6e6; + z-index: 2; +} +.cssbutton:hover { + text-decoration: none; + cursor: pointer; + background: #cfcfcf; +} +.imptButton { + background: #e8eef3; +} +.imptButton:hover { + background: #c1dfff; +}*/ \ No newline at end of file diff --git a/img/favicon.png b/img/favicon.png new file mode 100644 index 0000000..6c5e559 Binary files /dev/null and b/img/favicon.png differ diff --git a/js/videoskip.js b/js/videoskip.js new file mode 100644 index 0000000..10f55bf --- /dev/null +++ b/js/videoskip.js @@ -0,0 +1,664 @@ +/* +* Load video from a local file. Implemented by Dimitar Bonev as answered on StackOverflow. +* +* @link https://jsfiddle.net/dsbonev/cCCZ2/ +*/ +(function localFileVideoPlayer() { + var URL = window.URL || window.webkitURL; + var playSelectedFile = function(event) { + var file = this.files[0] + var type = file.type + var videoNode = document.querySelector('video') + var canPlay = videoNode.canPlayType(type) + if (canPlay === '') canPlay = 'no' + var message = 'Can play type "' + type + '": ' + canPlay + var isError = canPlay === 'no' + displayMessage(message, isError) + if (isError) { + return + } + var fileURL = URL.createObjectURL(file) + videoNode.src = fileURL; + setTimeout(function() { cuts = PF_SRT.parse(skipBox.value); + setActions(); + makeTimeLabels() }, 1000) + } + var inputNode = document.querySelector('input') + inputNode.addEventListener('change', playSelectedFile, false) +})(); + +/* +* Display a message in the #videoMsg element from HTML. +* +* @param {String} message - Message to be displayed in the HTML object. +* @param {String} isError - String representing whether is an error message. +*/ +var displayMessage = function(message, isError) { + var element = document.querySelector('#videoMsg') + element.innerHTML = message + element.className = isError ? 'error' : 'info' +} + +var name = ''; //global variable with name of skip file, minus extension +var cuts = []; //global variable containing the cuts, each array element is an object with this format {startTime,endTime,text,action} +var ua = navigator.userAgent.toLowerCase(); //to add a fix for Safari and choose fastest filter method, per https://jsben.ch/5qRcU +if (ua.indexOf('safari') != -1) { + if (ua.indexOf('chrome') == -1) { + var isSafari = true + } else { + var isChrome = true + } +} else if (typeof InstallTrigger !== 'undefined') { + var isFirefox = true +} else if (document.documentMode || /Edge/.test(navigator.userAgent)) { + var isEdge = true +} +if (isSafari) videoFile.accept = 'video/mp4,video/x-m4v,video/*'; //file loading fix + + +/* +* Loads the skips file. +*/ +function loadFileAsURL() { + var fileToLoad = skipFile.files[0], + fileReader = new FileReader(); + fileReader.onload = function(fileLoadedEvent) { + var URLFromFileLoaded = fileLoadedEvent.target.result; + var extension = fileToLoad.name.slice(-4); + if (extension == ".skp") { + var data = URLFromFileLoaded.split('data:image/jpeg;base64,'); //separate skips from screenshot + name = fileToLoad.name.slice(0, -4); + skipBox.value = data[0].trim(); + if (data[1]) screenShot.src = 'data:image/jpeg;base64,' + data[1]; + } else { + boxMsg.textContent = "wrong file type" + } + }; + fileReader.readAsText(fileToLoad); + boxMsg.textContent = 'This is the content of file: ' + fileToLoad.name; + setTimeout(function() { cuts = PF_SRT.parse(skipBox.value); + setActions(); + makeTimeLabels() }, 1000) //give it a whole second to load before data is extracted to memory +} + +/* +* Load subtitles file. +*/ +function loadSubs() { + var fileToLoad = subFile.files[0], + fileReader = new FileReader(); + fileReader.onload = function(fileLoadedEvent) { + var URLFromFileLoaded = fileLoadedEvent.target.result; + var extension = fileToLoad.name.slice(-4); + if (extension == ".vtt" || extension == ".srt") { //allow only .vtt and .srt formats + track = document.createElement("track"); + track.kind = "captions"; + track.label = "Loaded"; + track.srclang = "en"; + if (extension == ".vtt") { + track.src = URL.createObjectURL(fileToLoad) + } else { + var subs = URLFromFileLoaded; //get subs in text format, to be edited + subs = 'WEBVTT\n\n' + subs.replace(/(\d),(\d)/g, '$1.$2'); //convert decimal commas to periods and add header + var subBlob = new Blob([subs], { "type": 'text/plain' }); + track.src = URL.createObjectURL(subBlob); + } + track.addEventListener("load", function() { + this.mode = "showing"; + myVideo.textTracks[0].mode = "showing"; // thanks Firefox + }); + myVideo.appendChild(track); + displayMessage("Subtitles loaded. Enable them in the video with lower right icon", false) + } else { + displayMessage("Only .vtt and .srt subs are supported", true) + } + }; + fileReader.readAsText(fileToLoad) +} + +/* +* Download data to a file. +* +* @link StackOverflow +* +* @param {Object} data - Data to be extracted. +* @param {String} filename - Path of the file. +* @param {String} type - Type of the data to download. +*/ +function download(data, filename, type) { + var a = document.createElement("a"); + var file = new Blob([data], { "type": type }), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function() { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0) +} + +/* +* Parse the content of the skip box in something close to .srt format. +* +* @link StackOverflow +*/ +var PF_SRT = function() { + //SRT format + var pattern = /([\d:,.]+)\s*-+\>\s*([\d:,.]+)\n([\s\S]*?(?=\n{2}|$))?/gm; //no item number, can use decimal dot instead of comma, malformed arrows + var _regExp; + var init = function() { + _regExp = new RegExp(pattern); + }; + var parse = function(f) { + if (typeof(f) != "string") + throw "Sorry, the parser accepts only strings"; + var result = []; + if (f == null) + return _subtitles; + f = f.replace(/\r\n|\r|\n/g, '\n') + while ((matches = pattern.exec(f)) != null) { + result.push(toLineObj(matches)); + } + return result; + } + var toLineObj = function(group) { + return { + startTime: fromHMS(group[1]), + endTime: fromHMS(group[2]), + text: group[3], + action: '' //no action by default, to be filled later + }; + } + init(); + return { + parse: parse + } +}(); + +/* +* Convert seconds into hour:minute:second. +* +* @param {Number} seconds - Total seconds to re-format. +* +* @return {String} Seconds with format hour:minute:second. +*/ +function toHMS(seconds) { + var hours = Math.floor(seconds / 3600); + seconds -= hours * 3600; + var minutes = Math.floor(seconds / 60); + minutes = (minutes >= 10) ? minutes : "0" + minutes; + seconds = Math.floor((seconds % 60) * 100) / 100; //precision is 0.01 s + seconds = (seconds >= 10) ? seconds : "0" + seconds; + return hours + ":" + minutes + ":" + seconds; +} + +/* +* Convert hour:minute:second string to seconds. +* +* @param {String} timeString - Time in format hour:minute:second +* +* @return {Number} Float representing total seconds. +*/ +function fromHMS(timeString) { + timeString = timeString.replace(/,/, "."); //in .srt format decimal seconds use a comma + var time = timeString.split(":"); + if (time.length == 3) { //has hours + return parseInt(time[0]) * 3600 + parseInt(time[1]) * 60 + parseFloat(time[2]) + } else if (time.length == 2) { //minutes and seconds + return parseInt(time[0]) * 60 + parseFloat(time[1]) + } else { //only seconds + return parseFloat(time[0]) + } +} + +/* +* Shifts all times by a number of seconds entered in prompt. +*/ +function shiftTimes() { + var reply = prompt('Enter seconds to delay skips (negative for advance), or leave empty to sort the timings in ascending order'); + if (reply == null) { boxMsg.textContent = 'Time shift canceled'; return }; + + var initialData = skipBox.value.trim().split('\n').slice(0, 2); //first two lines + var shotTime = fromHMS(initialData[0]); + + if (reply) { //number entered, so apply the given shift + var seconds = parseFloat(reply); + for (var i = 0; i < cuts.length; i++) { + cuts[i].startTime += seconds; + cuts[i].endTime += seconds + } + } else { //nothing entered, so sort the times in ascending order + cuts.sort(function(a, b) { return a.startTime - b.startTime; }) + } + times2box(); //put shifted times in the box + + if (shotTime) { //reconstruct initial data, if present + if (seconds) initialData[0] = toHMS(shotTime + seconds); + skipBox.value = initialData.join('\n') + '\n\n' + skipBox.value + } + if (!seconds) { + boxMsg.textContent = "Skips sorted by start time" + } else if (seconds >= 0) { + boxMsg.textContent = "Skips delayed by " + Math.floor(seconds * 100) / 100 + " seconds" + } else { + boxMsg.textContent = "Skips advanced by " + Math.floor(-seconds * 100) / 100 + " seconds" + } + setTimeout(function() { makeTimeLabels(); if (isSuper) moveShot() }, 100) +} + +/* +* Shift all times so the screenshot has correct timing in the video. +* ShiftTimes also has this functionality, with empty input. +*/ +function syncTimes() { + var initialData = skipBox.value.trim().split('\n').slice(0, 2), //first two lines + shotTime = fromHMS(initialData[0]), + seconds = shotTime ? myVideo.currentTime - shotTime : 0; + for (var i = 0; i < cuts.length; i++) { + cuts[i].startTime += seconds; + cuts[i].endTime += seconds + } + times2box(); //put shifted times in the box + + if (shotTime) { //reconstruct initial data, if present + initialData[0] = toHMS(shotTime + seconds); + skipBox.value = initialData.join('\n') + '\n\n' + skipBox.value + } + if (seconds >= 0) { + boxMsg.textContent = "Skips delayed by " + Math.floor(seconds * 100) / 100 + " seconds" + } else { + boxMsg.textContent = "Skips advanced by " + Math.floor(-seconds * 100) / 100 + " seconds" + } + setTimeout(function() { makeTimeLabels(); if (isSuper) moveShot() }, 100) +} + +/* +* Put data from the cuts array into skipBox. +*/ +function times2box() { + var text = ''; + for (var i = 0; i < cuts.length; i++) { + text += toHMS(cuts[i].startTime) + ' --> ' + toHMS(cuts[i].endTime) + '\n' + cuts[i].text + '\n\n' + } + skipBox.value = text.trim(); + setTimeout(function() { makeTimeLabels() }, 100) +} + +/* +* Insert string in box, at cursor or replacing selection. +* +* @param {String} string - Text to insert. +* @param {Boolean} isScrub - Boolean. +*/ +function writeIn(string, isScrub) { + var start = skipBox.selectionStart, + end = skipBox.selectionEnd, + newEnd = start + string.length; + skipBox.value = skipBox.value.slice(0, start) + string + skipBox.value.slice(end, skipBox.length); + if (isScrub) { + skipBox.setSelectionRange(start, newEnd) + } else { + skipBox.setSelectionRange(newEnd, newEnd); + } + setTimeout(function() { cuts = PF_SRT.parse(skipBox.value); + setActions(); + makeTimeLabels() }, 100); + skipBox.focus() +} + +/* +* Gets index of a particular HMS time in the box +* +* @param {String} string - Text to obtain the index from. +*/ +function getTimeIndex(string) { + var stringStart = skipBox.value.indexOf(string); + for (var i = 0; i < timeLabels[0].length; i++) { + if (timeLabels[0][i].match(string)) return i + } +} + +/* +* Forwar Skip part of the video. +* +* Called by forward button +*/ +function fwdSkip() { + if (myVideo.paused) { + var selection = skipBox.value.slice(skipBox.selectionStart, skipBox.selectionEnd).trim(); + if (selection != '' && !selection.match(/[^\d:.]/)) { //valid time selected, so scrub to next time + var index = getTimeIndex(selection); + if (index != null) { + if (shiftMode.checked) { + myVideo.currentTime = fromHMS(timeLabels[0][index]); //first go there + myVideo.currentTime += fineMode.checked ? 0.0417 : 0.417; //now scrub by a small amount + writeIn(toHMS(myVideo.currentTime), true); //and write it in + skipBox.focus() + } else { + var nextIndex = index + 1; + if (nextIndex >= timeLabels[0].length) nextIndex = 0; + myVideo.currentTime = fromHMS(timeLabels[0][nextIndex]); + skipBox.selectionStart = timeLabels[1][nextIndex]; + skipBox.selectionEnd = timeLabels[2][nextIndex]; + skipBox.focus() + } + } + } else { //scrub by a small amount + myVideo.currentTime += fineMode.checked ? 0.0417 : 0.417 + } + } else { + myVideo.pause(); + speedMode = 0 + } +} + +/* +* Backwards Skip part of the video. +* +* Called by back button. +*/ +function backSkip() { + if (myVideo.paused) { + var selection = skipBox.value.slice(skipBox.selectionStart, skipBox.selectionEnd).trim(); + if (selection != '' && !selection.match(/[^\d:.]/)) { //valid time selected, so scrub to next time + var index = getTimeIndex(selection); + if (index != null) { + if (shiftMode.checked) { + myVideo.currentTime = fromHMS(timeLabels[0][index]); //first go there + myVideo.currentTime -= fineMode.checked ? 0.0417 : 0.417; //now scrub by a small amount + writeIn(toHMS(myVideo.currentTime), true); //and write it in + skipBox.focus() + } else { + var nextIndex = index - 1; + if (nextIndex < 0) nextIndex = timeLabels[0].length - 1; + myVideo.currentTime = fromHMS(timeLabels[0][nextIndex]); + skipBox.selectionStart = timeLabels[1][nextIndex]; + skipBox.selectionEnd = timeLabels[2][nextIndex]; + skipBox.focus() + } + } + } else { //scrub by a small amount + myVideo.currentTime -= fineMode.checked ? 0.0417 : 0.417 + } + } else { + myVideo.pause(); + speedMode = 0 + } +} + +var speedMode = 1; +/* +* Toggles normal, max and zerp speeds +*/ +function toggleFF() { + if (myVideo.paused) { //if paused, restart, no speed change + speedMode = 1; + myVideo.muted = false; + myVideo.playbackRate = 1; + myVideo.play() + } else { //if playing, set speed + if (speedMode == 1) { + speedMode = 2; + myVideo.muted = true; + myVideo.playbackRate = 16 + } else { + speedMode = 0; + myVideo.muted = false; + myVideo.playbackRate = 1; + myVideo.pause() + } + } +} +//for screenshots +var canvas = document.createElement('canvas'); +canvas.width = 320; //for full scale: myVideo.videoWidth - 100 +canvas.height = 240; +var ctx = canvas.getContext('2d') + +/* +* Take a screen shot. +*/ +function makeShot() { + myVideo.pause(); + skipBox.value = toHMS(myVideo.currentTime) + '\n' + skipBox.value; //put time at start + //draw image to canvas. scale to target dimensions + canvas.width = myVideo.videoWidth / myVideo.videoHeight * canvas.height; + ctx.drawImage(myVideo, 0, 0, canvas.width, canvas.height); + //convert to desired file format + var dataURI = canvas.toDataURL('image/jpeg'); // can also use 'image/png' but the file is 10x bigger + screenShot.src = dataURI +} + +/* +* Scrub to first time in the box, unless a time is selected +*/ +function scrub2shot() { + myVideo.pause(); + var selection = skipBox.value.slice(skipBox.selectionStart, skipBox.selectionEnd).trim(); + if (selection != '' && !selection.match(/[^\d:.]/)) { //valid time selected, so scrub to it + var index = getTimeIndex(selection); + if (index != null) { + myVideo.currentTime = fromHMS(timeLabels[0][index]); + skipBox.focus() + } + } else { //scrub to 1st time + myVideo.currentTime = fromHMS(timeLabels[0][0]) + } +} + +var isSuper = false; +/* +* Put the screenshot on top of the video so a perfect match can be found, and back. +*/ +function moveShot() { + if (isSuper) { + isSuper = false; + screenShot.height = 240; + screenShot.width = myVideo.videoWidth / myVideo.videoHeight * 240; + screenShot.style.position = ''; + screenShot.style.top = ''; + screenShot.style.left = ''; + screenShot.style.opacity = '' + } else { + isSuper = true; + screenShot.height = myVideo.videoHeight; //rescales the picture + screenShot.width = myVideo.videoWidth; + screenShot.style.position = 'absolute'; + screenShot.style.top = myVideo.offsetTop + 'px'; + screenShot.style.left = myVideo.offsetLeft + 'px'; + screenShot.style.opacity = "50%" + } +} + +var timeLabels = []; +/* +* Remakes array timeLabels containing HMS times, +* plus their positions in the box [HMS time, start, end]. +*/ +function makeTimeLabels() { + timeLabels = [ + [], + [], + [] + ]; //string, startPosition, endPosition + var text = skipBox.value, + string, start, end = 0; + var matches = text.match(/([\d:.]+)/g); + if (matches) { + for (var i = 0; i < matches.length; i++) { + string = matches[i]; + timeLabels[0][i] = string; + start = text.indexOf(string, end) + timeLabels[1][i] = start; + end = start + string.length; + timeLabels[2][i] = end + } + } +} + +/* +* Toggle instructions on and off. +*/ +/*function toggleHelp() { + if (instructions.style.display == 'none') { + instructions.style.display = 'block' + } else { + instructions.style.display = 'none' + } +}*/ + +//to move and resize superimposed shot +document.onkeydown = checkKey; +function checkKey(e) { + if (isSuper) { //this only works when a screenshot is superimposed on the video + e = e || window.event; + if (e.altKey) { //alt combinations resize, regular moves, hold shift for fine movement + if (e.keyCode == '38') { + screenShot.height -= e.shiftKey ? 1 : 10 + } else if (e.keyCode == '40') { + screenShot.height += e.shiftKey ? 1 : 10 + } else if (e.keyCode == '37') { + screenShot.width -= e.shiftKey ? 1 : 10 + } else if (e.keyCode == '39') { + screenShot.width += e.shiftKey ? 1 : 10 + } + } else { //move shot + if (e.keyCode == '38') { + screenShot.style.top = parseInt(screenShot.style.top.slice(0, -2)) - (e.shiftKey ? 1 : 10) + 'px' + } else if (e.keyCode == '40') { + screenShot.style.top = parseInt(screenShot.style.top.slice(0, -2)) + (e.shiftKey ? 1 : 10) + 'px' + } else if (e.keyCode == '37') { + screenShot.style.left = parseInt(screenShot.style.left.slice(0, -2)) - (e.shiftKey ? 1 : 10) + 'px' + } else if (e.keyCode == '39') { + screenShot.style.left = parseInt(screenShot.style.left.slice(0, -2)) + (e.shiftKey ? 1 : 10) + 'px' + } + } + } +} + +checkBoxes.addEventListener('change', setActions); +skipFile.addEventListener('change', loadFileAsURL); +/*exchangeBtn.addEventListener('click', function(){window.open('https://prgomez.com/videoskip/exchange')});*/ +subFile.addEventListener('change', loadSubs); +shiftBtn.addEventListener('click', shiftTimes); +timeBtn.addEventListener('click', function() { writeIn(toHMS(myVideo.currentTime)) }); +arrowBtn.addEventListener('click', function() { writeIn(' --> ') }); +/*instructions.style.display = 'none';*/ +/*help.addEventListener('click', toggleHelp);*/ +saveFile.addEventListener('click', function() { + if (!name) name = prompt('Enter the file name. Extension .skp wil be added'); + download(skipBox.value + '\n' + screenShot.src, name + '.skp', "text/plain"); + boxMsg.textContent = 'File saved with name ' + name + '.skp' +}) +fwdBtn.addEventListener('click', fwdSkip); +fFwdBtn.addEventListener('click', toggleFF); +backBtn.addEventListener('click', backSkip); +shotTimeBtn.addEventListener('click', scrub2shot); +moveBtn.addEventListener('click', moveShot); +syncBtn.addEventListener('click', syncTimes); +shotBtn.addEventListener('click', makeShot); +skipBox.addEventListener('change', function() { + setTimeout(function() { cuts = PF_SRT.parse(skipBox.value); + setActions(); + makeTimeLabels() }, 100) +}) + +/* +* Faster way to check for content depending on browser. +* +* @param {String} containerStr. +* @param {String} stringArray. +* @param {String} regex - Regular expression to search for. +* +* @return {Boolean} Boolean indicating if regex and stringArray content should match. +*/ +function isContained(containerStr, regex) { + var result = false; + if (isFirefox) { + result = containerStr.search(regex) != -1 + } else if (isSafari || isEdge || isChrome) { + result = regex.test(containerStr) + } else { + result = !!containerStr.match(regex) + } + return result +} + +/* +* To decide whether a particular content is to be skipped, according to check boxes. +* Allows alternative and incomplete keywords. +* +* @param {String} label - Label to check if it is to be skipped. +* +ยท @return {Boolean} Boolean indicating whether it needs to be skipped. +*/ +function isSkipped(label) { + if (isContained(label, /sex|nud/) && sexMode.checked) { + return true + } else if (isContained(label, /vio|gor/) && violenceMode.checked) { + return true + } else if (isContained(label, /pro|cur|hat/) && curseMode.checked) { + return true + } else if (isContained(label, /alc|dru|smo/) && boozeMode.checked) { + return true + } else if (isContained(label, /fri|sca|int/) && scareMode.checked) { + return true + } else if (isContained(label, /oth|bor/) && otherMode.checked) { + return true + } else { + return false + } +} + +/* +* Fills the action field in object cuts, according to the position of the check boxes +* and the text at each time. +*/ +function setActions() { + for (var i = 0; i < cuts.length; i++) { + var label = cuts[i].text.toLowerCase().replace(/\(.*\)/g, ''), //ignore text in parentheses + isAudio = isContained(label, /aud|sou|spe|wor/), + isVideo = isContained(label, /vid|ima|img/); + if (!isAudio && !isVideo) { + cuts[i].action = isSkipped(label) ? 'skip' : '' + } else if (isAudio) { + cuts[i].action = isSkipped(label) ? 'mute' : '' + } else { + cuts[i].action = isSkipped(label) ? 'blank' : '' + } + } +} +var prevAction = ''; + +//to skip video during playback +myVideo.ontimeupdate = function() { + var action = '', + startTime, endTime; + for (var i = 0; i < cuts.length; i++) { //find out what action to take, according to timing and setting in cuts object + startTime = cuts[i].startTime; + endTime = cuts[i].endTime; + if (myVideo.currentTime > startTime && myVideo.currentTime < endTime) { + action = cuts[i].action; + break + } else { + action = '' + } + } + if (action == prevAction) { //apply action to the DOM if there's a change + return + } else if (action == 'skip') { + myVideo.currentTime = endTime + } else if (action == 'blank') { + myVideo.style.opacity = 0 + } else if (action == 'mute') { + myVideo.muted = true; + if (myVideo.textTracks.length > 0) myVideo.textTracks[0].mode = 'disabled' + } else { + myVideo.style.opacity = ''; + myVideo.muted = false; + if (myVideo.textTracks.length > 0) myVideo.textTracks[0].mode = 'showing' + } + prevAction = action +} \ No newline at end of file diff --git a/videoskip.html b/videoskip.html index c9d9ef2..49c09ee 100644 --- a/videoskip.html +++ b/videoskip.html @@ -1,826 +1,255 @@ - - - - -Video Skip player - - - - - - - - + + + + Video Skip Player + + + + + + + + + + + + + + - -

Video Skip Player

-

v 0.1.5  © F. Ruiz 2020

-
Load video file with left button, .vtt or .srt subtitles with right button
-
- - - - -      
-
- -
- Select the content to skip: -

-
-    -  Sex and Nudity   -  Violence and Gore   -  Profanity and Hate   -  Alcohol, Drugs and Smoking   -  Frightening and Intense   -  Other + + + + + +
+

Video Skip Player

+

v.0.1 © F. Ruiz 2020

+

The video Player that allows you to skip categorized unwanted scenes from any video.

+
+ + +
+
+ +
+ +
+ +
+
+

+

1. Load your video File.

+
+ + +
+
+
+

2. Optionally load your subtitles File and enable them in the video.

+
+ + +
+
+
+

3. Load the skip file, edit the skips and select the type of content to skip.

+
+ +
+
+
+ + +
+
Here you would be able to load a skip file, edit select the type of content to skip or easily edit your skip file.
+ +
+

Select Content to Skip:

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
-
-
Load here the .skp file containing the skips:
-
- - - -

- Edit skips   - - - - -    - -  Fine  -  Shift - - - - - - -
- -     -
-
-

Instructions

-

This little app allows you to filter out several types of objectionable content from downloaded video files. It uses the same categories as the "Parents Guide" section of IMDB.com. It can skip sections entirely, or simply mute the sound or blank the video, at your discretion. Skip files can be shared at the official VideoSkip Exchange, or any way you want. The exchange is at https://prgomez.com/videoskip/exchange There is also an exension version of this app, available from the Chrome and Firefox web stores, which works on streaming video from any source.

-

Step by step:

-
    -
  1. Load the video from a local file using the "Load video" button. Because the video must be playable in a browser, it can take .mp4, .ogg, and .webm formats. You can save videos from streaming sources in these formats, rip a disc using software, or convert video files into these formats from a different format (more information below).
  2. -
  3. Optionally, load a file containing subtitles using the "Load subtitles". Subtitles must be in .vtt or .srt format. They might show as "English" but never mind.
  4. -
  5. Toggle the category filters using the checkboxes. A check means that the filter is on and content labeled accordingly will be skipped or blanked out. Otherwise it will be shown.
  6. -
  7. If you have a .skp file containing the skips, you can load it now with the "Load skip file" button. Once loaded, its contents will appear in the box, plus a screenshot on its right. Each skip consists of beginning and end times relative to the start of the video, with an arrow between them, and on the line below a category plus maybe a handling label (such as "video" or "audio").
  8. -
  9. If you don't have a skip file for the film you're watching, chances are another user has made one, and has posted it in the VideoSkip Exchange. There's a button to load the Exchange on a separate tab. You don't have to register in order to browse and download skip files.
  10. -
  11. The time shown on the first line of a loaded skip file is used for syncing the skips for different versions of the video. Click the "Go to time" button; if the video shows the same image as the screenshot, everything is in sync and you can go ahead and watch it, otherwise you need to sync the skips as explained in the section below.
  12. -
  13. If you want to change something in the list of skips, go right ahead. There's a button to insert the current time on the video (the video itself has a scrub bar to get there), and a correctly formatted "arrow" if you feel lazy. There are also buttons for shifting the time of all skips by a given amount, and for saving your edited skip list.
  14. -
  15. Click the play button on the video, and maybe the fullscreen button, sit back, and enjoy. Skips will take place when their time arrives.
  16. -
-

How to sync the skips:

-
    -
  1. If the video matches the screenshot when you click the "Go to Time" button, everything is in sync and you're done with this phase. Scrub the video back to the beginning and start watching.
  2. -
  3. But if the video is from a source different from that used to make the sync file, there may be a mismatch. Use the arrow buttons to scrub the video until the screen matches the screenshot or description (frame by frame, if "Fine" is checked). The fast forward button toggles the speed, or resumes playing if paused. The "Superimpose" button puts the screenshot right on top of the video, so you can see the precise moment when they match.You can move the screenshot around with the arrow keys, and resize it if you hold the Alt key as well. Hold Shift also for fine corrections.
  4. -
  5. Click the "Sync times" button when video and screenshot match. This will shift all the skips by the right amount, so they will happen at the correct times.
  6. -
  7. If the screenshot is still on top of the video, click "Superimpose" again in order to remove it.
  8. -
-

Getting the video files:

-
    -
  • If you are using a streaming service, these often allow you to pre-download the complete video in order to avoid stuttering on low-badwidth connections. YouTube and Vimeo videos are very easy to get this way (Google it), but other services may use copy-protection schemes that will need to be removed before you can use the file.
  • -
  • If the video is in a DVD or BlueRay disc that your own, there are excellent programs that will extract the data into whatever format you choose. Currently my favorite is Handbrake, available for Windows and for Mac.
  • -
  • Or you may have an old .avi, .mkv, or some other format. Handbrake can also convert the file from those formats into .mp4. A feature film may take an hour of processing. There are online converters as well, but they usually limit how much processing you can do in one day.
  • -
  • Another way to get them is by sharing with others, via BitTorrent or similar protocol. Be advised that this is not always legal.
  • -
  • Subtitles in .srt format are very easy to find online. This app will load them in this format, but if you want to convert them to .vtt there are easy online utilities like this one.
  • -
-

Making your own skips:

-
    -
  • A well-constructed skip file will have a first entry with the timing of an event near the start of the video where the image is changing quickly, plus a description of it on the line below it (do not use numbers in the description), and a screenshot. This is to allow viewers to use the skips in the file even if they are watching a video obtained from a different source, which might have a different timing. To take a screenshot, just scrub the video to the desired time and click "Take screenshot"; the time will be added at the beginning of the skip list.
  • -
  • Inserting skips is as easy as scrubbing to the point where the skip/mute/blank is supposed to begin, clicking the "Insert time" button on a new line, then the "Insert arrow" button, scrubbing to the point where the skip is to end, clicking the "Insert time" button once more, and then going to the next line and writing a content and (optionally) handling label. If you are initially unsure of where the skips should be inserted, there is a "Fast Forward" button that scrubs the video at high speed. Click it again to stop. The button to its left also stops the video.
  • -
  • Content labels are case-insensitive. They consist of any word (actually, the first three letters are enough) of the IMDB categories, which are displayed next to the checkboxes, such as "sex", "nudity", "gore", "drugs", etc. Handling labels are these words: "audio", "sound", "video", "image" (or the first three letters of each, including "img") also case-insensitive.
  • -
  • The "audio" or "sound" keywords will cause the sound to be muted during the given interval while the video is still shown, rather than skipping the section entirely, which might be useful for removing profanity. The "video" and "image" keywords cause the image to blank out while the sound still plays, which might be useful for instances of nudity, etc. No handling keyword means that the section will be completely skipped. You can add explanatory text in parentheses, which won't trigger the filters.
  • -
  • Everything is editable in the box. Use this to fine-tune timings. For instance, words to be muted typically take a lot less than a second. You can review each timing by selecting on the list and clicking "Go to time", or by clicking the forward and back buttons when a time is selected on the list. If the "Shift" box is checked, the selected timing will shift with the arrows, rather than moving to another timing.
  • -
  • The "Insert" buttons put things always at the current cursor position.
  • -
  • The "Shift skips" button opens a dialog where you can enter a number of seconds to delay all skips, or advance them if you enter a negative number, including the screenshot time at the top, if any. Clicking OK with nothing entered sorts the skips in ascending order of start times leaving the screenshot time at the top, if any, unchanged. In any case, skips will be made at the right times even though they may not be in sequence on the list.
  • -
  • The "Save" button will save your list, plus screenshot, in .skp format in your default Download folder. You will have to move it from there to the folder where you want it. This format is text-based, so you can view the file in any text editor.
  • -
  • The "VideoSkip Exchange" button will open a web page where you can share your skip file with other VideoSkip users.
  • -
-

Sample skip, which will cause the screen to blank out from 14 minutes, 8.27 seconds from the start of the movie until 14 minutes, 14 seconds, while the sound still plays, if "Sex and Nudity" is checked:

-

0:14:08.27 --> 0:14:14
- nude image

-

Here are some videos that show how to make and use skip files:

-

VideoSkip developer plays ukulele with a pick, Egad! (WARNING, this video might be offensive to sensitive ukulele players):
- https://www.youtube.com/watch?v=uDZfW9SLb_4

-

Obtaining the skip file for the uke video from the VideoSkip Exchange:
- https://www.youtube.com/watch?v=vyQKZVHa898

-

Using VideoSkip to edit the playback of the uke video:
- https://www.youtube.com/watch?v=QhYIXEbYfnY

-

Making a VideoSkip file for the uke video:
- https://www.youtube.com/watch?v=Fx04SVgUKO0

-

Posting a skip file to the VideoSkip Exchange:
- https://www.youtube.com/watch?v=Tv5wXTkXhcs

-

Legal Notice: Content copyright owners and distributors are hereby informed that users and developers of this software are exercising their right of free speech, guaranteed by law in many nations, by voluntarily refraining from seeing or hearing content without modifying said content in any way. Legal action that ignores this notice will be considered harassment and infringement of basic rights, and prosecuted according to the law.

-
Favicon made by Smashicons from www.flaticon.com
-
- + + +
\ No newline at end of file