PNG Player

Plays audio files embedded in images. Use [SHIFT]+Mouseclick on an image to trigger the download or press [F7] while hovering over an image

Click here to install Browse More Scripts
// ==UserScript==
// @name         PNG Player
// @namespace    11b24ea6d9f6e9e27c2afb07b7b1f4220ebcbda1
// @version      0.9
// @description  Plays audio files embedded in images. Use [SHIFT]+Mouseclick on an image to trigger the download or press [F7] while hovering over an image
// @author       /u/AyrA_ch
// @include      https://*
// @include      http://*
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

/*
Important note!
The first time you run this script it will ask for permissions.
You should allow this script for all domains to prevent further interruptions.
 */

//Changelog
//0.9 - Add player controls to DOM only on first usage
//0.8 - Allow F7
//0.7 - Fix close link
//0.6 - Data URLs don't work for download links?
//0.5 - Fix variable error
//0.4 - Switch to data URL because content policy
//0.3 - Setting content-type in Blob URL
//0.2 - Allow URL redirector tags
//0.1 - Initial Version with Audio playback

(function ($, $$) {
	'use strict';

	//https://gist.github.com/jonleighton/958841
	var base64ArrayBuffer = function (arrayBuffer) {
		var base64 = "";
		var encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

		var bytes = new Uint8Array(arrayBuffer);
		var byteLength = bytes.byteLength;
		var byteRemainder = byteLength % 3;
		var mainLength = byteLength - byteRemainder;

		var a,
		b,
		c,
		d;
		var chunk;

		// Main loop deals with bytes in chunks of 3
		for (var i = 0; i < mainLength; i += 3) {
			// Combine the three bytes into a single integer
			chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];

			// Use bitmasks to extract 6-bit segments from the triplet
			a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
			b = (chunk & 258048) >> 12; // 258048   = (2^6 - 1) << 12
			c = (chunk & 4032) >> 6; // 4032     = (2^6 - 1) << 6
			d = chunk & 63; // 63       = 2^6 - 1

			// Convert the raw binary segments to the appropriate ASCII encoding
			base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
		}

		// Deal with the remaining bytes and padding
		if (byteRemainder == 1) {
			chunk = bytes[mainLength];

			a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2

			// Set the 4 least significant bits to zero
			b = (chunk & 3) << 4; // 3 = 2^2 - 1

			base64 += encodings[a] + encodings[b] + '==';
		} else if (byteRemainder == 2) {
			chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];

			a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
			b = (chunk & 1008) >> 4; // 1008  = (2^6 - 1) << 4

			// Set the 2 least significant bits to zero
			c = (chunk & 15) << 2; // 15    = 2^4 - 1

			base64 += encodings[a] + encodings[b] + encodings[c] + '=';
		}

		return base64;
	};

	var RND = Math.random() * 100000 | 0;

	//Element that displays the audio controls
	var pngelement = document.createElement("div");
	pngelement.setAttribute("style", "z-index:" + Number.MAX_SAFE_INTEGER + ";display:none;position:fixed;padding:10px;right:10px;bottom:10px;background-color:#FFF;color:#000;" +
		"border:2px solid #F00;border-radius:5px;font-family:Arial;font-size:12pt;");
	var pngloader = null;

	//Creates the loader element and empties the parent element.
	var createLoader = function () {
		pngloader = document.createElement("span");
		pngelement.innerHTML = "";
		pngelement.appendChild(pngloader);
	};

	createLoader();

	//Logs content to console and a textbox
	var log = function () {
		var segments = Array.prototype.slice.call(arguments, 0);
		console.log(segments);
	};

	//Turns sizes into readable units
	var doSize = function (x, y) {
		var factor = 1024; //1000 would be correct for the SI units below but 1024 is the common way to do it for now
		var sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
		var count = 0;
		x = +x;
		y = y | 0;
		if (y < 0 || y > 3) {
			y = 0;
		}
		while (x >= factor && ++count < sizes.length - 1) {
			x /= factor;
		}

		return Math.round(x * Math.pow(10, y)) / Math.pow(10, y) + " " + sizes[count];
	};

	//Reads bytes to int32
	var btoi = function (data, index, littleEndian) {
		var buf = (data instanceof Array) ? data : new Uint8Array(data);
		if (!littleEndian) {
			return (buf[index + 3] * 1) +
			(buf[index + 2] * 256) +
			(buf[index + 1] * 256 * 256) +
			(buf[index + 0] * 256 * 256 * 256);
		}
		return (buf[index + 0] * 1) +
		(buf[index + 1] * 256) +
		(buf[index + 2] * 256 * 256) +
		(buf[index + 3] * 256 * 256 * 256);
	};

	//Reads all headers from a stream
	var getHeaders = function (x) {
		var buf = new Uint8Array(x);
		var headers = [];
		var pos = 8;
		while (pos < buf.length) {
			var header = {
				name: "____",
				data: []
			};
			//Data Length
			var len = btoi(x, pos);
			//Header Name
			pos += 4;
			header.name = Array.prototype.map.call(buf.slice(pos, pos + 4), toChars).join("");
			//Data
			pos += 4;
			header.data = Array.prototype.slice.call(buf.slice(pos, pos + len), 0);
			//Discard Checksum
			pos += len + 4;
			headers.push(header);
			log("Header Name:", header.name, "Length:", header.data.length);
		}
		return headers;
	};

	//(Very) Primitive check for PNG file
	var isPNG = function (x) {
		var PNG = [137, 80, 78, 71, 13, 10, 26, 10];
		var buf = (new Uint8Array(x)).slice(0, PNG.length);
		for (var i = 0; i < PNG.length; i++) {
			if (buf[i] != PNG[i]) {
				return false;
			}
		}
		return true;
	};

	//Sets the progress bar
	var setProgress = function (l, h) {
		if (arguments.length === 2) {
			pngloader.textContent = "Loading " + doSize(l, 1) + "/" + doSize(h, 1) + "... (" + (Math.round(l * 100 / h)) + "%)";
		}
	};

	//Gets an arrayBuffer from an Ajax request
	var getBuffer = function (URL, callback) {
		log("Loading", URL);
		pngelement.style.display = "inline-block";
		createLoader();
		GM_xmlhttpRequest({
			url: URL,
			method: "GET",
			responseType: "arraybuffer",
			onload: function (e) {
				pngloader.textContent = "Processing image headers...";
				log("Got Answer for", URL);
				window.setTimeout(function () {
					callback(e.response);
				}, 0);
			},
			onprogress: function (e) {
				if (e.loaded > 0 && e.total > 0) {
					setProgress(e.loaded, e.total);
				}
			}
		});
	};

	//Bytes to string simplifyer
	var toChars = function (v) {
		return String.fromCharCode(v);
	};

	//Checks if a PNG header is BMPENC encoded
	var isDataHeader = function (header) {
		return header.data.length >= 14 && header.data.slice(0, 6).map(toChars).join("") === "BMPENC";
	};

	//Gets the name of a BMPENC encoded PNG Header
	var getFileName = function (header) {
		var names = [btoi(header.data, 6), btoi(header.data, 6, true)];
		var namelen = names[names[0] < names[1] ? 0 : 1];
		log("File name length", namelen);
		return header.data.slice(10, 10 + namelen).map(toChars).join("");
	};

	//Gets the data of a BMPENC encoded PNG Header
	var getData = function (header) {
		var namelen = getFileName(header).length;
		var datalen = btoi(header.data, 10 + namelen);
		log("Data length", datalen);
		return header.data.slice(14 + namelen, 14 + namelen + datalen);
	};

	//Converts a JS array to an arrayBuffer
	var arrayToBuffer = function (data) {
		return Uint8Array.from(data).buffer;
	};

	var getTypeFromName = function (name) {
		var ext = name.toLowerCase().split('.').pop();
		switch (ext) {
		case "mp3":
			return "audio/mp3";
		case "ogg":
			return "audio/ogg";
		default:
			return null;
		}
	};

	//Button click
	var playImage = function (link, isRedir) {
		if (link) {
			getBuffer(link, function (x) {
				if (x) {
					var png = isPNG(x);
					log(png ? "File is a PNG" : "File is not a PNG");
					if (png) {
						var headers = getHeaders(x);
						for (var i = 0; i < headers.length; i++) {
							if (isDataHeader(headers[i])) {
								var filename = getFileName(headers[i]);
								if (filename.length > 255) {
									log("Filename seems very long with " + filename.length + ">255 bytes. Aborting");
									continue;
								}
								var data = getData(headers[i]);
								log("Name: ", filename, "Size:", data.length);
								if (data.length > 0) {
									//Download URLs
									if (filename.match(/\.url$/i) && !isRedir) {
										playImage(data.map(toChars).join(""), true);
										return;
									} else {
										var fileType = getTypeFromName(filename);
										pngelement.innerHTML = "";

										//Play extracted file
										var b = fileType ? new Blob([arrayToBuffer(data)], {
												type: fileType
											}) : new Blob([arrayToBuffer(data)]);
										var d = URL.createObjectURL(b);
										var audio = document.createElement("audio");
										if (!fileType) {
											fileType = "application/octet-stream";
										}
										var u = "data:" + fileType + ";base64," + base64ArrayBuffer(arrayToBuffer(data));
										audio.autoplay = true;
										audio.controls = true;
										audio.loop = true;
										audio.volume = 0.1;
										audio.src = u;
										pngelement.appendChild(audio);

										//Download Extracted file
										pngelement.appendChild(document.createElement("br"));
										var a = document.createElement("a");
										a.href = d;
										a.download = filename;
										a.textContent = "Download " + filename;
										pngelement.appendChild(a);

										//Close link
										pngelement.appendChild(document.createElement("br"));
										a = document.createElement("a");
										a.href = "#";
										a.textContent = "[X] Close";
										a.onclick = function (e) {
											pngelement.style.display = "none";
											audio.pause();
											pngelement.innerHTML = "";
											e.preventDefault();
											e.stopPropagation();
										};
										pngelement.appendChild(a);
									}
								}
								//Stop header processing after the first file.
								return;
							}
						}
						pngelement.innerHTML = "No Audio found.<br />";
						var link = document.createElement("a");
						link.href = "#";
						link.textContent = "[X] Close";
						link.onclick = function (e) {
							pngelement.style.display = "none";
							pngelement.innerHTML = "";
							e.preventDefault();
							e.stopPropagation();
						};
						pngelement.appendChild(link);
					} else {
						pngelement.innerHTML = "No Audio found.<br />";
						var closelink = document.createElement("a");
						closelink.href = "#";
						closelink.textContent = "[X] Close";
						closelink.onclick = function (e) {
							pngelement.style.display = "none";
							pngelement.innerHTML = "";
							e.preventDefault();
							e.stopPropagation();
						};
						pngelement.appendChild(closelink);
					}
					addPlayerElement();
				}
			});
		}
	};

	var mouseHandler = function (e) {
		if (e.shiftKey && e.button === 0) {
			var ele = e.target;
			if (ele.tagName.toUpperCase() !== "IMG") {
				ele = ele.querySelector("img");
				if (!ele) {
					console.log("no image in link");
					return;
				}
			}
			if (ele.src) {
				console.log(ele.src);
				playImage(ele.src);
			}
			e.preventDefault();
			e.stopPropagation();
		}
	};

	var registerEvents = function () {
		var images = $$("img");
		var i = 0;
		for (i = 0; i < images.length; i++) {
			images[i].removeEventListener("click", mouseHandler);
			images[i].addEventListener("click", mouseHandler);
		}
		images = $$("a");
		for (i = 0; i < images.length; i++) {
			images[i].removeEventListener("click", mouseHandler);
			if (images[i].querySelector("img")) {
				images[i].addEventListener("click", mouseHandler);
			}
		}
	};

	var addPlayerElement = function () {
		document.body.appendChild(pngelement);
	}

	var currentElement = null;
	document.body.addEventListener("mouseover", function (e) {
		currentElement = e.target;
	});

	document.body.addEventListener("keydown", function (e) {
		if (currentElement && e.keyCode === 118 /*F7*/) {
			if (currentElement.tagName.toLowerCase() === "img") {
				console.log("Current Element:", currentElement);
				e.stopPropagation();
				e.preventDefault();
				if (currentElement.src) {
					console.log(currentElement.src);
					playImage(currentElement.src);
				}
			}
		}
	});

	var mo = new MutationObserver(function (evt) {
			registerEvents();
		});
	mo.observe($("body"), {
		childList: true
	});
	registerEvents();
})(document.querySelector.bind(document), document.querySelectorAll.bind(document));

/*
LICENSE:
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
The full license text can be found here: http://creativecommons.org/licenses/by-nc-sa/4.0/
The link has an easy to understand version of the license and the full license text.

DISCLAIMER:
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/

Copyright © 2018 by Kevin Gut 📧 | More services | Generated for 18.217.207.112