UNPKG

@devbab/plex-tags

Version:

Add Faces and Places tags to photos in Plex Database

181 lines (136 loc) 6.15 kB
"use strict"; const debug = require("debug")("Face"); const _ = require("lodash"); const plex = require("./plex.js"); const dayjs = require("dayjs"); const log = require("@devbab/logger").child({ label: "Plex-face" }); /** * return a list of unique names, quote escaped * @param {Object} tags array of {mids,faces:[name,name]} * @returns {array} array of names */ function uniqueNames(tags) { // got unique list of faces and escape potential quote let names = tags.map(elt => elt.faces); names = _.flatten(names); names = _.uniq(names); names = _.compact(names); // remove falsey return names.map(face => face.replace(/'/g, "''")).sort(); } /** * list all existing faces and add the new ones which do not exist yet * @param {Object} tags array of {mid,faces:[name,name]} */ async function addFacesinTags(names) { debug(`addFacesinTags`); // get list of existing whitin the potential new plex.init(); let existingFaces = plex.listFaces(names); existingFaces = existingFaces.map(elt => elt.tag); plex.end(); // console.log("Existing", existingFaces); // console.log("New Faces", names); let add = _.difference(names, existingFaces); log.debug("Faces to add", add); // creates all the new faces const now = new dayjs().format("YYYY-MM-DD HH:mm:ss"); const sqlIntro = "INSERT INTO tags (tag, created_at,updated_at,tag_type, extra_tag) VALUES "; await plex.runBig(add, sqlIntro, (elt) => { return `('${elt}','${now}','${now}',0,'FACE')`; }); } /** * add the tags to the corresponding mid in table taggings * mid = field metadata_item_id in table media_items @param {Object} tags array of {mid,faces:[name,name]} */ async function addSubFacesInTaggings(names, tags) { log.debug(`addFacesInTaggings`, tags); //build list of existing links = [mid,tag_id]. tag_id is id in tags plex.init(); let existingFaces = plex.listFaces(names); // list of {id,tag} where tag = name of the face // list of existing let existingLinks = plex.listTagging("FACE"); // array of {mid,tag_id,index} plex.end(); // keeps in existing links the ones related to the mid in tags : the images where there are changes const midsToChange = tags.map(tag => tag.mid); existingLinks = existingLinks.filter(elt => { return midsToChange.includes(elt.mid); }) //console.log(`midsToChange ${midsToChange}`); //console.log(`existingLinks ${existingLinks}`); // lets build list of links to add, we'll remove after those already existing // array of {mid,tag_id} let newLinks = []; // let's build the list to create tags.forEach((tag) => { //console.log("working on tag", tag); tag.faces.forEach(face => { const found = existingFaces.find(elt => elt.tag == face); if (!found) return console.error(`ERROR: addFacesInTaggings, cannot find entry for face ${face}`); //console.log(`${face} = ${found.id}`); newLinks.push({ mid: tag.mid, tag_id: found.id }); }); }); // here we have a list of [mid, tag_id] // console.log("new links ", newLinks); let add = _.differenceBy(newLinks, existingLinks, (elt) => { return `${elt.mid}-${elt.tag_id}}`; }); add = _.uniqBy(add, (elt) => { return `${elt.mid}-${elt.tag_id}}`; }); log.debug("Taggings links to Add", add); // what to remove = existing links not in new ones. let sub = _.differenceBy(existingLinks, newLinks, (elt) => { return `${elt.mid}-${elt.tag_id}}`; }); sub = _.uniqBy(sub, (elt) => { return `${elt.mid}-${elt.tag_id}}`; }); // remove duplicates but there should not be any log.debug("Taggings links to remove", sub); /* const trackMid = 13; console.log("TRACK existing Links", existingLinks.filter(elt => elt.mid == trackMid)); console.log("TRACK new Links", newLinks.filter(elt => elt.mid == trackMid)); console.log("TRACK Taggings links to Add", add.filter(elt => elt.mid == trackMid)); console.log("TRACK Taggings links to remove", sub); */ // creates all the new links if (add.length > 0) { const now = new dayjs().format("YYYY-MM-DD HH:mm:ss"); const sqlIntro = "INSERT INTO taggings (metadata_item_id, tag_id,'index', created_at, extra_tag) VALUES " await plex.runBig(add, sqlIntro, (elt) => { return `('${elt.mid}','${elt.tag_id}',0,'${now}','FACE')`; }); } if (sub.length > 0) { const ids = sub.map(elt => elt.id).join(","); // remove all the past links not needed anymore const sql = `DELETE FROM taggings WHERE id in (${ids})`; log.debug(`remove taggings SQL `, sql); await plex.run(sql); } } /** * Add faces in database = add whatever is new in table tags, and does the linking in table tagging * plus mark the images as updated * @param {Object} tags array of {mid,faces:[name,name]} */ async function addFaces(tags) { if (!tags?.length) return; // nothing to update log.debug(`addFaces ${tags.length} tags`); let names = uniqueNames(tags); await addFacesinTags(names); // substract previous refer await addSubFacesInTaggings(names, tags); // mark the image as updateds const mids = tags.map(elt => elt.mid); await plex.markImagesAsUpdated(mids, ["FACE"]); } /** * delete all Face * This means delete entries in tags where tag_type = 0 && extra_tag='FACE' AND delete entries in tagging where tag_id does not exists anymore * @returns : promise to delete SQL query */ function deleteAllFaces() { let sql; // find the entry of concern sql = `DELETE from tags where tag_type == 0 AND extra_tag = 'FACE' `; plex.run(sql); // wider clean of whatever tagging which is not found in tag sql = `DELETE from taggings where tag_id not in (select id from tags) `; plex.run(sql); } module.exports = { addFaces, deleteAllFaces }