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.
*/
User Script Managers
A userscript manager is the browser extension that injects scripts into websites
to change their behavior to your liking.
Recommendation
All scripts on this site have been developed and tested with Tampermonkey on firefox.
Try other browsers and other script managers at your own risk.
No script should use firefox or chrome specific features,
which means they should also work in other modern browsers.
If you prefer, you can use greasemonkey.
Get Tampermonkey,
Get Greasemonkey (Firefox only)
Script Installation
Once you have obtained a user script manager,
clicking on the install button will pop up an installation prompt.
To allow script manager detection,
you can install this helper script.
It's not necessary but simplifies your future visits to this site.
Script Installation
We detected, that you have a script manager installed and active.
Click the "Install Script" button to obtain the script.