|
@@ -1,98 +1,77 @@
|
|
-const Clarifai = require("clarifai");
|
|
|
|
-const ClarifaiTOKEN = require("../clarifai_keys.js");
|
|
|
|
const db = require("../db.js");
|
|
const db = require("../db.js");
|
|
const util = require("../util.js");
|
|
const util = require("../util.js");
|
|
const Jimp = require("jimp");
|
|
const Jimp = require("jimp");
|
|
const client = require("../client.js");
|
|
const client = require("../client.js");
|
|
-const schedule = require("node-schedule");
|
|
|
|
|
|
+const cv = require("opencv4nodejs");
|
|
|
|
+const path = require("path");
|
|
|
|
+const request = require("request-promise-native");
|
|
|
|
|
|
const EMOTE_GUILD = "505333548694241281";
|
|
const EMOTE_GUILD = "505333548694241281";
|
|
-const clarifaiApp = new Clarifai.App({
|
|
|
|
- apiKey: ClarifaiTOKEN
|
|
|
|
-});
|
|
|
|
-const MAX_REQUESTS_PER_MONTH = 5000;
|
|
|
|
|
|
|
|
-let quotaRecomputeJob = null;
|
|
|
|
-let dailyResetJob = null;
|
|
|
|
|
|
+const animeCascade = new cv.CascadeClassifier(path.resolve(__dirname, "./animu.xml"));
|
|
|
|
+const faceCascade = new cv.CascadeClassifier(cv.HAAR_FRONTALFACE_ALT2);
|
|
|
|
|
|
-function recomputeQuotas() {
|
|
|
|
- console.log(`Recomputing quotas on ${new Date().toISOString()}!`);
|
|
|
|
- db.set("faceEditInfo.quotaRecomputeDate", "").write();
|
|
|
|
|
|
|
|
- let channelPredictProbability = db.get("faceEditChannels").value();
|
|
|
|
- let channelStatistics = db.get("faceEditStatistics").value();
|
|
|
|
-
|
|
|
|
- let totalRandomPredicts = 0;
|
|
|
|
-
|
|
|
|
- for (const channel in channelStatistics) {
|
|
|
|
- if (channelStatistics.hasOwnProperty(channel) && channelPredictProbability.hasOwnProperty(channel)) {
|
|
|
|
- totalRandomPredicts += channelPredictProbability[channel] * channelStatistics[channel];
|
|
|
|
- channelStatistics[channel] = 0;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let dailyQuota = Math.floor((MAX_REQUESTS_PER_MONTH - totalRandomPredicts) / 30.0);
|
|
|
|
-
|
|
|
|
- db.set("faceEditInfo.customRequestsPerDay", dailyQuota).write();
|
|
|
|
- db.set("faceEditStatistics", channelStatistics).write();
|
|
|
|
-
|
|
|
|
- quotaRecomputeJob.cancel();
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-function resetDailyQuota() {
|
|
|
|
- console.log("Resetting today image request count!");
|
|
|
|
- db.set("faceEditInfo.todayRequests", 0).write();
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-function setupJobs() {
|
|
|
|
- let recomputeJobDate = db.get("faceEditInfo.quotaRecomputeDate").value();
|
|
|
|
- if (recomputeJobDate)
|
|
|
|
- quotaRecomputeJob = schedule.scheduleJob(new Date(recomputeJobDate), recomputeQuotas);
|
|
|
|
-
|
|
|
|
- let rule = new schedule.RecurrenceRule();
|
|
|
|
- rule.hour = 0;
|
|
|
|
- rule.minute = 0;
|
|
|
|
- dailyResetJob = schedule.scheduleJob(rule, resetDailyQuota);
|
|
|
|
-
|
|
|
|
- checkQuotaJobs();
|
|
|
|
|
|
+function intersects(r1, r2) {
|
|
|
|
+ return (r1.x <= r2.x + r2.width && r1.x + r1.width >= r2.x)
|
|
|
|
+ && (r1.y <= r2.y + r2.height && r1.y + r1.height >= r2.y);
|
|
}
|
|
}
|
|
-setupJobs();
|
|
|
|
|
|
|
|
-function checkQuotaJobs() {
|
|
|
|
- let jobDateText = db.get("faceEditInfo.quotaRecomputeDate").value();
|
|
|
|
- if (jobDateText)
|
|
|
|
- return;
|
|
|
|
|
|
+async function processFaceSwap(message, attachmentUrl, failMessage, successMessage) {
|
|
|
|
+ let data = await request(attachmentUrl, {encoding: null});
|
|
|
|
|
|
- let jobDate = new Date();
|
|
|
|
- jobDate.setMonth(jobDate.getMonth() + 1);
|
|
|
|
- db.set("faceEditInfo.quotaRecomputeDate", jobDate.toISOString()).write();
|
|
|
|
- quotaRecomputeJob.reschedule(jobDate, recomputeQuotas);
|
|
|
|
-}
|
|
|
|
|
|
+ let im = await cv.imdecodeAsync(data, cv.IMREAD_COLOR);
|
|
|
|
|
|
-async function processFaceSwap(message, attachmentUrl, failMessage, successMessage) {
|
|
|
|
- let result = await clarifaiApp.models.predict(Clarifai.FACE_DETECT_MODEL, attachmentUrl);
|
|
|
|
|
|
+ let gray = await im.cvtColorAsync(cv.COLOR_BGR2GRAY);
|
|
|
|
+ let normGray = await gray.equalizeHistAsync();
|
|
|
|
|
|
- checkQuotaJobs();
|
|
|
|
|
|
+ let animeFaces = await animeCascade.detectMultiScaleAsync(normGray, 1.1, 5, 0, new cv.Size(24, 24));
|
|
|
|
+ let normalFaces = await faceCascade.detectMultiScaleAsync(gray);
|
|
|
|
|
|
- if (result.outputs[0].data.regions === undefined || result.outputs[0].data.regions.length == 0) {
|
|
|
|
|
|
+ if(animeFaces.objects.length == 0 && normalFaces.objects.length == 0) {
|
|
if (failMessage)
|
|
if (failMessage)
|
|
message.channel.send(failMessage);
|
|
message.channel.send(failMessage);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ let faces = [...normalFaces.objects, ...animeFaces.objects];
|
|
|
|
+
|
|
|
|
+ let normalCount = normalFaces.objects.length;
|
|
|
|
+ let animeCount = animeFaces.objects.length;
|
|
|
|
+
|
|
|
|
+ for (let i = 0; i < normalCount; i++) {
|
|
|
|
+ const rNormal = faces[i];
|
|
|
|
+
|
|
|
|
+ if(animeCount == 0)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ for (let j = normalCount; j < faces.length; j++) {
|
|
|
|
+ const rAnime = faces[j];
|
|
|
|
+
|
|
|
|
+ if(intersects(rAnime, rNormal)) {
|
|
|
|
+ let animeA = rAnime.width * rAnime.height;
|
|
|
|
+ let faceA = rNormal.width * rNormal.height;
|
|
|
|
+
|
|
|
|
+ if(animeA > faceA){
|
|
|
|
+ faces.splice(i, 1);
|
|
|
|
+ normalCount--;
|
|
|
|
+ i--;
|
|
|
|
+ break;
|
|
|
|
+ } else {
|
|
|
|
+ faces.splice(j, 1);
|
|
|
|
+ animeCount--;
|
|
|
|
+ j--;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- let image = await Jimp.read(attachmentUrl);
|
|
|
|
- let w = image.getWidth();
|
|
|
|
- let h = image.getHeight();
|
|
|
|
-
|
|
|
|
|
|
+ let jimpImage = await Jimp.read(data);
|
|
let emojiKeys = [...client.guilds.get(EMOTE_GUILD).emojis.filter(e => !e.animated).keys()];
|
|
let emojiKeys = [...client.guilds.get(EMOTE_GUILD).emojis.filter(e => !e.animated).keys()];
|
|
|
|
|
|
- for (let region of result.outputs[0].data.regions) {
|
|
|
|
- let bb = region.region_info.bounding_box;
|
|
|
|
- let bw = (bb.right_col - bb.left_col) * w;
|
|
|
|
- let bh = (bb.bottom_row - bb.top_row) * h;
|
|
|
|
-
|
|
|
|
- let dx = (bb.right_col + bb.left_col) * w / 2;
|
|
|
|
- let dy = (bb.bottom_row + bb.top_row) * h / 2;
|
|
|
|
|
|
+ for (const rect of faces) {
|
|
|
|
+ let dx = rect.x + rect.width / 2;
|
|
|
|
+ let dy = rect.y + rect.height / 2;
|
|
|
|
|
|
let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
|
|
let emojiKey = emojiKeys[Math.floor(Math.random() * emojiKeys.length)];
|
|
let emoji = client.emojis.get(emojiKey);
|
|
let emoji = client.emojis.get(emojiKey);
|
|
@@ -101,17 +80,17 @@ async function processFaceSwap(message, attachmentUrl, failMessage, successMessa
|
|
let ew = emojiImage.getWidth();
|
|
let ew = emojiImage.getWidth();
|
|
let eh = emojiImage.getHeight();
|
|
let eh = emojiImage.getHeight();
|
|
|
|
|
|
- const CONSTANT_SCALE = 1.5;
|
|
|
|
- let scaleFactor = Math.max(bw, bh) / Math.min(ew, eh) * CONSTANT_SCALE;
|
|
|
|
|
|
+ const CONSTANT_SCALE = 1.1;
|
|
|
|
+ let scaleFactor = Math.max(rect.width, rect.height) / Math.min(ew, eh) * CONSTANT_SCALE;
|
|
ew *= scaleFactor;
|
|
ew *= scaleFactor;
|
|
eh *= scaleFactor;
|
|
eh *= scaleFactor;
|
|
|
|
|
|
emojiImage = emojiImage.scale(scaleFactor);
|
|
emojiImage = emojiImage.scale(scaleFactor);
|
|
- image = image.composite(emojiImage, dx - ew / 2, dy - eh / 2);
|
|
|
|
|
|
+ jimpImage = jimpImage.composite(emojiImage, dx - ew / 2, dy - eh / 2);
|
|
}
|
|
}
|
|
|
|
|
|
- image.quality(90);
|
|
|
|
- let buffer = await image.getBufferAsync(Jimp.MIME_JPEG);
|
|
|
|
|
|
+ jimpImage.quality(90);
|
|
|
|
+ let buffer = await jimpImage.getBufferAsync(Jimp.MIME_JPEG);
|
|
|
|
|
|
let messageContents = successMessage || `I noticed a face in the image. I think this looks better ${client.emojis.get("505076258753740810").toString()}`;
|
|
let messageContents = successMessage || `I noticed a face in the image. I think this looks better ${client.emojis.get("505076258753740810").toString()}`;
|
|
|
|
|