Skip to content

Instantly share code, notes, and snippets.

@rubyu
Last active October 4, 2018 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rubyu/9e8ad8cb86f0de29fa04cd927c25bab3 to your computer and use it in GitHub Desktop.
Save rubyu/9e8ad8cb86f0de29fa04cd927c25bab3 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Crunchyroll Screenshot
// @version 1.4
// @description Tested on Chrome 64 and Firefox 61 with Tampermonkey 4.7 and Crunchyroll HTML5 0.12.0.
// @include /^https?://www.crunchyroll\.com/.*$/
// @grant GM_notification
// ==/UserScript==
(function() {
'use strict';
// The trigger key to capture screenshot.
const invocationKey = "F9";
// Modifier keys required when the trigger key to be activated. (acceptable values: "alt", "ctrl", "shift", "meta")
const invocationKeyModifiers = [];
// Mime type of output image file. (acceptable values: "image/png", "image/jpeg", "image/webp")
const outputMimeType = "image/png";
// Quality argument indicates the quality of output image file. (range: 0.0 to 1.0; 1.0 is best and 0.0 is worst)
// Note: png encoder won't be effected by this parameter because it is a lossless format.
const outputQuality = 0.9;
// Add episode title to the filename if true.
const outputEpisodeTitle = true;
// Set to true if you need to pause the video to capture it precisely.
const pausePlaybackBeforeCapturing = false;
// Set to true if you need to focus the player element to control by keys assigned to it.
const focusPlayerOnLoad = true;
// Set to true if you need to focus the player element after capturing.
const focusPlayerAfterCapturing = true;
// Settings for notification feature.
const notificationEnabled = true;
const notificationTimeout = 2000;
const appName = "Crunchyroll Screenshot";
function saveCavnasAsImageFile(canvas, filename) {
canvas.toBlob(function (blob) {
const link = document.createElement("a");
link.download = filename;
link.href = window.URL.createObjectURL(blob);
dispatchClickEvent(link);
}, outputMimeType, outputQuality);
}
function dispatchClickEvent(target) {
const evnt = document.createEvent("MouseEvents");
evnt.initMouseEvent("click", {
view: window,
bubbles: true,
cancelable: true
});
target.dispatchEvent(evnt);
}
function parseSeconds(seconds) {
const sec = Math.floor(seconds);
const msec = Math.floor((seconds - sec) * 1000);
const minute = Math.floor(sec / 60);
const hour = Math.floor(minute / 60);
return {
msec: msec,
sec: sec % 60,
minute: minute % 60,
hour: hour
};
}
function lzerofill(obj, minLength) {
const str = obj.toString();
return ("0".repeat(minLength) + str).slice(-Math.max(minLength, str.length));
}
function secondsToCurrentTimeString(seconds) {
const ct = parseSeconds(seconds);
return lzerofill(ct.hour, 2) + "_" + lzerofill(ct.minute, 2) + "_" + lzerofill(ct.sec, 2) + "." + lzerofill(ct.msec, 3);
}
function getPrefferedExtension() {
if (outputMimeType == "image/png") return "png";
else if (outputMimeType == "image/jpeg") return "jpg";
else if (outputMimeType == "image/webp") return "webp";
else return outputMimeType.split("/")[1];
}
function removeQuotes(str) {
return str.replace("“", "").replace("”", "");
}
function getPrefferedFileName(currentTime) {
const media = document.getElementById("showmedia_about_media");
const arr = Array.from(media.getElementsByTagName("h4")).map(function(v, i) {
const text = v.textContent.trim();
if (i == 0) return text;
return "[" + text.split(",").map(function(v) {
return v.trim();
}).join("] [") + "]";
});
if (arr.length < 2) {
arr.push("[]");
}
if (outputEpisodeTitle) {
const name = document.getElementById("showmedia_about_name");
if (name) {
arr.push(removeQuotes(name.textContent));
}
}
const ct = secondsToCurrentTimeString(currentTime);
arr.push("[" + ct + "]");
const ext = getPrefferedExtension();
return arr.map(function(v) {
return v.replace(/[\s\r\n]+/g, " ").trim();
}).join(" ") + "." + ext;
}
if (focusPlayerOnLoad) {
window.addEventListener("load", function(evnt) {
const player = document.getElementsByClassName("html5-video-player")[0];
player.focus();
});
}
document.addEventListener("keydown", function(evnt) {
if (evnt.key != invocationKey ||
invocationKeyModifiers.indexOf("alt") >= 0 && !evnt.altKey ||
invocationKeyModifiers.indexOf("ctrl") >= 0 && !evnt.ctrlKey ||
invocationKeyModifiers.indexOf("shift") >= 0 && !evnt.shiftKey ||
invocationKeyModifiers.indexOf("meta") >= 0 && !evnt.metaKey) {
return;
}
try {
console.log(appName + " was invoked");
const player = document.getElementsByClassName("html5-video-player")[0];
const container = player.getElementsByClassName("html5-video-container")[0];
const action = player.getElementsByClassName("html5-video-action")[0];
const vs = container.getElementsByClassName("video-stream")[0];
const sub = container.getElementsByClassName("html5-subtitle-container")[0].getElementsByTagName("canvas")[0];
if (pausePlaybackBeforeCapturing) {
dispatchClickEvent(action);
}
const w = vs.clientWidth;
const h = vs.clientHeight;
console.log("width: " + w);
console.log("height: " + h);
const canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
ctx.drawImage(vs, 0, 0, w, h);
ctx.drawImage(sub, 0, 0, w, h);
const filename = getPrefferedFileName(vs.currentTime);
saveCavnasAsImageFile(canvas, filename);
const successMessage = "Screenshot was saved to \"" + filename + "\"";
console.log(successMessage);
if (notificationEnabled) {
GM_notification({
image: "http://static.ak.crunchyroll.com/i/beta/ios_icon/cr_ios.png",
text: successMessage,
timeout: notificationTimeout
});
}
if (focusPlayerAfterCapturing) {
player.focus();
}
} catch (ex) {
const errorMessage = "An error has occurred: " + ex.toString();
console.log(errorMessage);
if (notificationEnabled) {
GM_notification({
image: "http://static.ak.crunchyroll.com/i/beta/ios_icon/cr_ios.png",
title: "Oops!",
text: errorMessage,
timeout: 10000
});
}
}
// Cancel the key down event.
return false;
});
document.addEventListener("keyup", function(evnt) {
if (evnt.key != invocationKey ||
invocationKeyModifiers.indexOf("alt") >= 0 && !evnt.altKey ||
invocationKeyModifiers.indexOf("ctrl") >= 0 && !evnt.ctrlKey ||
invocationKeyModifiers.indexOf("shift") >= 0 && !evnt.shiftKey ||
invocationKeyModifiers.indexOf("meta") >= 0 && !evnt.metaKey) {
return;
}
// Cancel the key up event.
return false;
});
console.log(appName + " was loaded");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment