Drednot Video Player

Only works with dropbox and wikimedia links (any site that allows anonymous crossorigin). Kind of shitty as of right now.

// ==UserScript==
// @name         Drednot Video Player
// @namespace    http://tampermonkey.net/
// @version      0.31
// @description  Only works with dropbox and wikimedia links (any site that allows anonymous crossorigin). Kind of shitty as of right now.
// @author       You
// @match        https://test.drednot.io/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drednot.io
// @grant        none
// ==/UserScript==

(function() {
var vidurl = "https://dl.dropbox.com/s/5k69wjzvblaqs44/ezgif.com-gif-maker%20%282%29.mp4"
let framerate = 20
let dithering1 = false
let monospace1 = true
let bottombar = document.getElementById("motd-content");
bottombar.innerHTML += `<textarea id="text" style="z-index:-1" readonly ></textarea>
	<div id="options">
		<div ><a href="https://github.com/505e06b2/Image-to-Braille" target="_blank" hidden>Open Repo</a></div>
		<div ><input id="file" type="file" hidden accept="image/*"></div>

		<div title="Toggle dark theme" ><input type="checkbox" id="darktheme" > Dark Theme</div>
		<div title="Invert black with white" ><input type="checkbox" id="inverted"> Invert</div>
		<div title="Monochrome dithering" ><input type="checkbox" id="dithering"> Dithering</div>
		<div title="Disable placeholder spacing" ><input type="checkbox" id="monospace"> Monospace</div>
		<div title="Greyscale Mode" >
			<select id="greyscale_mode">
				<option value="luminance">Luminance</option>
				<option value="lightness">Lightness</option>
				<option value="average">Average</option>
				<option value="value">Value</option>
			</select>
		</div>
		<div >
			<input type="number" min="2" value="50" step="2" max="500" id="width">
			Width (characters)
		</div>
		<div >
			<button id="clipboard">Copy to clipboard</button>
		</div>
		<div >
			Character Count: <span id="charcount">0</span>
		</div>
	</div>
  <div id="theater">
  <video id="video" src=${vidurl} controls="false" crossOrigin="" width=25 height=25></video>
  <canvas id="canvas1" hidden></canvas>
  <button onclick="video.pause();video.currentTime = 0;video.load();video.play();" class="btn-yellow btn-small last-left"><i class="heeheeheehaw"></i> Restart</button>
<button onclick="video.pause();let a = prompt('Raw video link:');video.src = a;video.currentTime = 0;video.load();video.play();" class ="btn-blue btn-small last-left"><i class="heeheeheehaw"></i> Change video</button>
  <label>
    <br />Dithering code from 505e06b2.github.io. Put together by ibuildcomputers</label>
  <br />
</div>`;

function createImageCanvas(src) {
	return new Promise((resolve, reject) => {
		const canvas = document.createElement("CANVAS");
		const image = new Image();

		image.onload = () => {
			let width = image.width;
			let height = image.height;
			if(image.width != (settings.width * 2)) {
				width = settings.width * 2;
				height = width * image.height / image.width;
			}

			//nearest multiple
			canvas.width = width - (width % 2);
			canvas.height = height - (height % 4);

			ctx = canvas.getContext("2d");
			ctx.fillStyle = "#FFFFFF"; //get rid of alpha
			ctx.fillRect(0,0, canvas.width,canvas.height);

			ctx.mozImageSmoothingEnabled = false;
			ctx.webkitImageSmoothingEnabled = false;
			ctx.msImageSmoothingEnabled = false;
			ctx.imageSmoothingEnabled = false;

			ctx.drawImage(image, 0,0, canvas.width,canvas.height);
			resolve(canvas);
		};

		image.src = src;
	});
}

function pixelsToCharacter(pixels_lo_hi) { //expects an array of 8 bools
	//Codepoint reference - https://www.ssec.wisc.edu/~tomw/java/unicode.html#x2800
	const shift_values = [0, 1, 2, 6, 3, 4, 5, 7]; //correspond to dots in braille chars compared to the given array
	let codepoint_offset = 0;
	for(const i in pixels_lo_hi) {
		codepoint_offset += (+pixels_lo_hi[i]) << shift_values[i];
	}

	if(codepoint_offset === 0 && settings.monospace === false) { //pixels were all blank
		codepoint_offset = 4; //0x2800 is a blank braille char, 0x2804 is a single dot
	}
    return String.fromCharCode(0x2800 + codepoint_offset);
}

function toGreyscale(r, g, b) {
	switch(settings.greyscale_mode) {
		case "luminance":
			return (0.22 * r) + (0.72 * g) + (0.06 * b);

		case "lightness":
			return (Math.max(r,g,b) + Math.min(r,g,b)) / 2;

		case "average":
			return (r + g + b) / 3;

		case "value":
			return Math.max(r,g,b);

		default:
			console.error("Greyscale mode is not valid");
			return 0;
	}
}

function canvasToText(canvas) {
	const ctx = canvas.getContext("2d");
	const width = canvas.width;
	const height = canvas.height;

	let image_data = [];
	if(settings.dithering) {
		if(settings.last_dithering === null || settings.last_dithering.canvas !== canvas) {
			settings.last_dithering = new Dithering(canvas);
		}
		image_data = settings.last_dithering.image_data;
	} else {
		image_data = new Uint8Array(ctx.getImageData(0,0,width,height).data.buffer);
	}

	let output = "";

	for(let imgy = 0; imgy < height; imgy += 4) {
		for(let imgx = 0; imgx < width; imgx += 2) {
			const braille_info = [0,0,0,0,0,0,0,0];
			let dot_index = 0;
			for(let x = 0; x < 2; x++) {
				for(let y = 0; y < 4; y++) {
					const index = (imgx+x + width * (imgy+y)) * 4;
					const pixel_data = image_data.slice(index, index+4); //ctx.getImageData(imgx+x,imgy+y,1,1).data
					if(pixel_data[3] >= 128) { //account for alpha
						const grey = toGreyscale(pixel_data[0], pixel_data[1], pixel_data[2]);
						if(settings.inverted) {
							if(grey >= 128) braille_info[dot_index] = 1;
						} else {
							if(grey <= 128) braille_info[dot_index] = 1;
						}
					}
					dot_index++;
				}
			}
			output += pixelsToCharacter(braille_info);
		}
		output += "\n";
	}

	return output;
}
// Credit to https://gist.github.com/PhearTheCeal/6443667 for the algorithm
// adding change framerate?
//trying to however it cannot access the variable stated in js as this is basically just something running in the console
function Dithering(canvas) {
	this.canvas = canvas;
	this.image_data = new Uint8Array(canvas.getContext("2d").getImageData(0,0, canvas.width, canvas.height).data); //clone

	let oldpixel;
	let newpixel;
	let quant_error;
	let err_red, err_green, err_blue;

	const _getPixel = (x, y) => {
		const index = (x + y * canvas.width) * 4;
		return [ this.image_data[index+0], this.image_data[index+1], this.image_data[index+2] ];
	};

	const _setPixel = (x, y, colour) => {
		const index = (x + y * canvas.width) * 4;
		this.image_data[index+0] = Math.floor(colour[0]+0.5);
		this.image_data[index+1] = Math.floor(colour[1]+0.5);
		this.image_data[index+2] = Math.floor(colour[2]+0.5);
		this.image_data[index+3] = 255;
	}

	const _closestPalleteColour = (pixel) => {
		return (0.2126*pixel[0] + 0.7152*pixel[1] + 0.0722*pixel[2]) > 128 ? [255,255,255] : [0,0,0];
	};

	const _colourDifference = (one, two) => {
		return [(one[0] - two[0]), (one[1] - two[1]), (one[2] - two[2])];
	};

	const _colourAddError = (x, y, err_red, err_green, err_blue) => {
		const clip = (x) => (x < 0 ? 0 : (x > 255 ? 255 : x));
		const index = (x + y * canvas.width) * 4;
		this.image_data[index+0] = clip(this.image_data[index+0] + err_red);
		this.image_data[index+1] = clip(this.image_data[index+1] + err_green);
		this.image_data[index+2] = clip(this.image_data[index+2] + err_blue);
		this.image_data[index+3] = 255;
	};

	for(let y = 0; y < canvas.height; y++) {
		for(let x = 0; x < canvas.width; x++) {
			oldpixel = _getPixel(x, y);
			newpixel = _closestPalleteColour(oldpixel);
			_setPixel(x, y, newpixel);
			quant_error = _colourDifference(oldpixel, newpixel);

			err_red = quant_error[0];
			err_green = quant_error[1];
			err_blue = quant_error[2];

			if(x+1 < canvas.width)             _colourAddError(x+1, y,   (7/16) * err_red, (7/16) * err_green, (7/16) * err_blue);
			if(x-1 > 0 && y+1 < canvas.height) _colourAddError(x-1, y+1, (3/16) * err_red, (3/16) * err_green, (3/16) * err_blue);
			if(y+1 < canvas.height)            _colourAddError(x,   y+1, (5/16) * err_red, (5/16) * err_green, (5/16) * err_blue);
			if(x+1 < canvas.width)             _colourAddError(x+1, y+1, (1/16) * err_red, (1/16) * err_green, (1/16) * err_blue);
		}
	}
}
const settings = {
	last_canvas: null,
	last_dithering: null,
	last_source: "",

	width: 35,
	greyscale_mode: "luminance",
	inverted: true,
	dithering: dithering1,
	monospace: monospace1
};

function setUIElement(selector, value) {
	const elem = document.querySelector(selector);
	switch(elem.getAttribute("type")) { //should all be <input>
		case "checkbox":
			elem.checked = value;
			break;

		default:
			elem.value = value;
	}
	return elem;
}

function initUI() {
	document.body.ondragover = (e) => e.preventDefault();
	document.body.ondrop = (e) => {
		e.preventDefault();
		loadNewImage(URL.createObjectURL(e.dataTransfer.items[0].getAsFile()));
	};
		event.preventDefault();	loadNewImage(URL.createObjectURL(e.clipboardData.items[0].getAsFile()));
	};

	//buttons
	const r = () => parseCanvas(settings.last_canvas); //shorten for compactness

	document.querySelector('input[type="file"]').onchange = (e) => {
		 loadNewImage(URL.createObjectURL(e.target.files[0]));
	};

	setUIElement('#darktheme', settings.inverted).onchange = (e) => {
		const element = document.querySelector('#text');
		if(e.target.checked) element.classList.add("dark");
		else element.classList.remove("dark");
	};

	setUIElement('#inverted', settings.inverted).onchange = (e) => {settings.inverted = e.target.checked; r();};
	setUIElement('#dithering', settings.dithering).onchange = (e) => {settings.dithering = e.target.checked; r();};
	setUIElement('#monospace', settings.monospace).onchange = (e) => {settings.monospace = e.target.checked; r();};

	document.querySelector('#greyscale_mode').onchange = (e) => {
		settings.greyscale_mode = e.target.value;
		parseCanvas(settings.last_canvas);
	};

	setUIElement('#width', settings.width).onchange = (e) => {
		settings.width = e.target.value;
		loadNewImage(settings.last_source);
	};

	document.querySelector('#clipboard').onclick = (e) => {
		 document.querySelector('#text').select();
		 document.execCommand("copy");
	}


async function loadNewImage(src) {
	if(src === undefined) return;

	if(settings.last_source && settings.last_source !== src) URL.revokeObjectURL(settings.last_source);

	settings.last_source = src;
	const canvas = await createImageCanvas(src);
	settings.last_canvas = canvas;
	settings.last_dithering = null;
	await parseCanvas(canvas);
}

async function parseCanvas(canvas) {
	const text = canvasToText(canvas);
	document.querySelector('#text').value = text;
  let btn = document.getElementById("motd-edit-button");
  let box = document.getElementById("motd-edit-text");
  btn.click;
  box.value = text;
  saveMotd(true);
	document.querySelector('#charcount').innerText = text.length;
}

window.onload = () => {
	initUI();
	loadNewImage("select.png");
}

var canvas1 = document.getElementById('canvas1');
var ctx1 = canvas1.getContext('2d');
var video = document.getElementById('video');

// set canvas size = video size when known
video.addEventListener('loadedmetadata', function() {
  canvas1.width = video.videoWidth;
  canvas1.height = video.videoHeight;
  video.play();
  video.style.zIndex = 2147483647;
});

video.addEventListener('play', function() {
  var $this = this; //cache
  (function loop() {
    if (!$this.paused && !$this.ended) {
      ctx1.drawImage($this, 0, 0);
      loadNewImage(canvas1.toDataURL());
      setTimeout(loop, 1000 / framerate);
    }
  })();
}, 0);
})();

//hello