Limit overlapping segments to just one through weighted randomness

This commit is contained in:
Martijn van der Ven
2020-05-23 21:17:47 +02:00
parent d8203dce77
commit c2510d302a

View File

@@ -12,24 +12,64 @@ const getIP = require('../utils/getIP.js');
* @property {string} UUID Unique identifier for the specific segment * @property {string} UUID Unique identifier for the specific segment
*/ */
/**
* @typedef {Object} Row
* @property {string} videoID
* @property {number} startTime
* @property {number} endTime
* @property {number} votes
* @property {string} UUID
* @property {string} category
* @property {number} shadowHidden
*/
/**
* Input an array of database records and get only one back, weighed on votes.
* The logic is taken from getWeightedRandomChoice, just simplified input and output to not work on indices only.
*
* @param {Row[]} rows
* @returns {?Row}
*/
function pickWeightedRandomRow(rows) {
if (rows.length === 0) {
return null;
} else if (rows.length === 1) {
return rows[0];
}
const sqrtWeightsList = [];
let totalSqrtWeights = 0;
for (const row of rows) {
let sqrtVote = Math.sqrt((row.votes + 3) * 10);
sqrtWeightsList.push(sqrtVote);
totalSqrtWeights += sqrtVote;
}
const randomNumber = Math.random();
let currentVoteNumber = 0;
for (let i = 0; i < sqrtWeightsList.length; i++) {
if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[i]) / totalSqrtWeights) {
return rows[i];
}
currentVoteNumber += sqrtWeightsList[i];
}
}
/** /**
* @param {string} prefix Lowercased hexadecimal hash prefix * @param {string} prefix Lowercased hexadecimal hash prefix
* @param {string} hashedIP Custom hash of the visitors IP address * @param {string} hashedIP Custom hash of the visitors IP address
* @returns {Segment[]} * @returns {Object.<string, Segment[]>}
*/ */
function getSkipSegmentsByHash(prefix, hashedIP) { function getSkipSegmentsByHash(prefix, hashedIP) {
/** /** @type Row[] */
* @constant const rows = db.prepare('SELECT videoID, startTime, endTime, votes, UUID, category, shadowHidden FROM sponsorTimes WHERE votes >= -1 AND hashedVideoID LIKE ? ORDER BY videoID, startTime')
* @type {Segment[]}
* @default
*/
const segments = [];
const rows = db.prepare('SELECT videoID, startTime, endTime, UUID, category, shadowHidden FROM sponsorTimes WHERE votes >= -1 AND hashedVideoID LIKE ? ORDER BY startTime')
.all(prefix + '%'); .all(prefix + '%');
/** @type {string[]} */
const onlyForCurrentUser = privateDB.prepare('SELECT videoID FROM sponsorTimes WHERE hashedIP = ?').all(hashedIP).map(row => row.videoID); const onlyForCurrentUser = privateDB.prepare('SELECT videoID FROM sponsorTimes WHERE hashedIP = ?').all(hashedIP).map(row => row.videoID);
/** @type {Object.<string, Segment[][]>} */
const rowGroupsPerVideo = {};
let previousVideoID = null;
let previousEndTime = null;
for (const row of rows) { for (const row of rows) {
/** @TODO check if this logic does what is expected. */ /** @TODO check if this logic does what is expected. */
if (row.shadowHidden === 1 && onlyForCurrentUser.indexOf(row.videoID) === -1) { if (row.shadowHidden === 1 && onlyForCurrentUser.indexOf(row.videoID) === -1) {
@@ -37,16 +77,30 @@ function getSkipSegmentsByHash(prefix, hashedIP) {
// Do not send shadowHidden segments to them. // Do not send shadowHidden segments to them.
continue; continue;
} }
// Split up the rows per video and group overlapping segments together.
segments.push({ if (!(row.videoID in rowGroupsPerVideo)) {
videoID: row.videoID, rowGroupsPerVideo[row.videoID] = [];
segment: [row.startTime, row.endTime], }
category: row.category, if (previousVideoID === row.videoID && row.startTime <= previousEndTime) {
UUID: row.UUID rowGroupsPerVideo[row.videoID][rowGroupsPerVideo[row.videoID].length - 1].push(row);
}); previousEndTime = Math.max(previousEndTime, row.endTime);
} else {
rowGroupsPerVideo[row.videoID].push([row]);
previousVideoID = row.videoID;
previousEndTime = row.endTime;
}
} }
return segments; /** @type {Object.<string, Segment[]>} */
const output = {};
for (const videoID in rowGroupsPerVideo) {
const pickedVideosForVideoID = [];
for (const group of rowGroupsPerVideo[videoID]) {
pickedVideosForVideoID.push(pickWeightedRandomRow(group));
}
output[videoID] = pickedVideosForVideoID.map(row => ({ videoID: row.videoID, segment: [row.startTime, row.endTime], category: row.category, UUID: row.UUID }));
}
return output;
} }
const minimumPrefix = config.minimumPrefix || '3'; const minimumPrefix = config.minimumPrefix || '3';