Add hasStartSegment to video label

This commit is contained in:
Ajay
2025-01-17 23:30:32 -05:00
parent bba06511ce
commit e2a9976cd0
4 changed files with 64 additions and 31 deletions

View File

@@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { db } from "../databases/databases"; import { db } from "../databases/databases";
import { videoLabelsHashKey, videoLabelsKey } from "../utils/redisKeys"; import { videoLabelsHashKey, videoLabelsKey } from "../utils/redisKeys";
import { SBRecord } from "../types/lib.model"; import { SBRecord } from "../types/lib.model";
import { Category, DBSegment, Segment, Service, VideoData, VideoID, VideoIDHash } from "../types/segments.model"; import { ActionType, Category, DBSegment, Segment, Service, VideoData, VideoID, VideoIDHash } from "../types/segments.model";
import { Logger } from "../utils/logger"; import { Logger } from "../utils/logger";
import { QueryCacher } from "../utils/queryCacher"; import { QueryCacher } from "../utils/queryCacher";
import { getService } from "../utils/getService"; import { getService } from "../utils/getService";
@@ -13,6 +13,7 @@ interface FullVideoSegment {
interface FullVideoSegmentVideoData { interface FullVideoSegmentVideoData {
segments: FullVideoSegment[]; segments: FullVideoSegment[];
hasStartSegment: boolean;
} }
function transformDBSegments(segments: DBSegment[]): FullVideoSegment[] { function transformDBSegments(segments: DBSegment[]): FullVideoSegment[] {
@@ -21,7 +22,7 @@ function transformDBSegments(segments: DBSegment[]): FullVideoSegment[] {
})); }));
} }
async function getLabelsByVideoID(videoID: VideoID, service: Service): Promise<FullVideoSegment[]> { async function getLabelsByVideoID(videoID: VideoID, service: Service): Promise<FullVideoSegmentVideoData> {
try { try {
const segments: DBSegment[] = await getSegmentsFromDBByVideoID(videoID, service); const segments: DBSegment[] = await getSegmentsFromDBByVideoID(videoID, service);
return chooseSegment(segments); return chooseSegment(segments);
@@ -53,11 +54,13 @@ async function getLabelsByHash(hashedVideoIDPrefix: VideoIDHash, service: Servic
}, {}); }, {});
for (const [videoID, videoData] of Object.entries(segmentPerVideoID)) { for (const [videoID, videoData] of Object.entries(segmentPerVideoID)) {
const result = chooseSegment(videoData.segments);
const data: FullVideoSegmentVideoData = { const data: FullVideoSegmentVideoData = {
segments: chooseSegment(videoData.segments), segments: result.segments,
hasStartSegment: result.hasStartSegment
}; };
if (data.segments.length > 0) { if (data.segments.length > 0 || data.hasStartSegment) {
segments[videoID] = data; segments[videoID] = data;
} }
} }
@@ -74,7 +77,7 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service
.prepare( .prepare(
"all", "all",
`SELECT "startTime", "endTime", "videoID", "votes", "locked", "UUID", "userID", "category", "actionType", "hashedVideoID", "description" FROM "sponsorTimes" `SELECT "startTime", "endTime", "videoID", "votes", "locked", "UUID", "userID", "category", "actionType", "hashedVideoID", "description" FROM "sponsorTimes"
WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "actionType" = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`, WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "hidden" = 0 AND "shadowHidden" = 0`,
[`${hashedVideoIDPrefix}%`, service] [`${hashedVideoIDPrefix}%`, service]
) as Promise<DBSegment[]>; ) as Promise<DBSegment[]>;
@@ -90,22 +93,34 @@ async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): P
.prepare( .prepare(
"all", "all",
`SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "description" FROM "sponsorTimes" `SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "description" FROM "sponsorTimes"
WHERE "videoID" = ? AND "service" = ? AND "actionType" = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`, WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 AND "shadowHidden" = 0`,
[videoID, service] [videoID, service]
) as Promise<DBSegment[]>; ) as Promise<DBSegment[]>;
return await QueryCacher.get(fetchFromDB, videoLabelsKey(videoID, service)); return await QueryCacher.get(fetchFromDB, videoLabelsKey(videoID, service));
} }
function chooseSegment<T extends DBSegment>(choices: T[]): FullVideoSegment[] { function chooseSegment<T extends DBSegment>(choices: T[]): FullVideoSegmentVideoData {
// filter out -2 segments // filter out -2 segments
choices = choices.filter((segment) => segment.votes > -2); choices = choices.filter((segment) => segment.votes > -2);
const hasStartSegment = !!choices.some((segment) => segment.startTime < 5
&& (segment.actionType === ActionType.Skip || segment.actionType === ActionType.Mute));
choices = choices.filter((segment) => segment.actionType === ActionType.Full);
const results = []; const results = [];
// trivial decisions // trivial decisions
if (choices.length === 0) { if (choices.length === 0) {
return []; return {
segments: [],
hasStartSegment
};
} else if (choices.length === 1) { } else if (choices.length === 1) {
return transformDBSegments(choices); return {
segments: transformDBSegments(choices),
hasStartSegment
}
} }
// if locked, only choose from locked // if locked, only choose from locked
const locked = choices.filter((segment) => segment.locked); const locked = choices.filter((segment) => segment.locked);
@@ -114,7 +129,10 @@ function chooseSegment<T extends DBSegment>(choices: T[]): FullVideoSegment[] {
} }
//no need to filter, just one label //no need to filter, just one label
if (choices.length === 1) { if (choices.length === 1) {
return transformDBSegments(choices); return {
segments: transformDBSegments(choices),
hasStartSegment
};
} }
// sponsor > exclusive > selfpromo // sponsor > exclusive > selfpromo
const findCategory = (category: string) => choices.find((segment) => segment.category === category); const findCategory = (category: string) => choices.find((segment) => segment.category === category);
@@ -122,25 +140,36 @@ function chooseSegment<T extends DBSegment>(choices: T[]): FullVideoSegment[] {
const categoryResult = findCategory("sponsor") ?? findCategory("exclusive_access") ?? findCategory("selfpromo"); const categoryResult = findCategory("sponsor") ?? findCategory("exclusive_access") ?? findCategory("selfpromo");
if (categoryResult) results.push(categoryResult); if (categoryResult) results.push(categoryResult);
return transformDBSegments(results); return {
segments: transformDBSegments(results),
hasStartSegment
};
} }
async function handleGetLabel(req: Request, res: Response): Promise<FullVideoSegment[] | false> { async function handleGetLabel(req: Request, res: Response): Promise<FullVideoSegmentVideoData | FullVideoSegment[] | false> {
const videoID = req.query.videoID as VideoID; const videoID = req.query.videoID as VideoID;
if (!videoID) { if (!videoID) {
res.status(400).send("videoID not specified"); res.status(400).send("videoID not specified");
return false; return false;
} }
const hasStartSegment = !!req.query.hasStartSegment;
const service = getService(req.query.service, req.body.service); const service = getService(req.query.service, req.body.service);
const segments = await getLabelsByVideoID(videoID, service); const segmentData = await getLabelsByVideoID(videoID, service);
const segments = segmentData.segments;
if (!segments || segments.length === 0) { if (!segments || segments.length === 0) {
res.sendStatus(404); res.sendStatus(404);
return false; return false;
} }
return segments; if (hasStartSegment) {
return segmentData;
} else {
return segments;
}
} }
async function endpoint(req: Request, res: Response): Promise<Response> { async function endpoint(req: Request, res: Response): Promise<Response> {

View File

@@ -21,6 +21,7 @@ export async function getVideoLabelsByHash(req: Request, res: Response): Promise
const output = Object.entries(segments).map(([videoID, data]) => ({ const output = Object.entries(segments).map(([videoID, data]) => ({
videoID, videoID,
segments: data.segments, segments: data.segments,
hasStartSegment: data.hasStartSegment
})); }));
return res.status(output.length === 0 ? 404 : 200).json(output); return res.status(output.length === 0 ? 404 : 200).json(output);
} }

View File

@@ -55,7 +55,7 @@ export const tempVIPKey = (userID: HashedUserID): string =>
`vip.temp.${userID}`; `vip.temp.${userID}`;
export const videoLabelsKey = (videoID: VideoID, service: Service): string => export const videoLabelsKey = (videoID: VideoID, service: Service): string =>
`labels.v1.${service}.videoID.${videoID}`; `labels.v2.${service}.videoID.${videoID}`;
export function videoLabelsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string { export function videoLabelsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string {
hashedVideoIDPrefix = hashedVideoIDPrefix.substring(0, 3) as VideoIDHash; hashedVideoIDPrefix = hashedVideoIDPrefix.substring(0, 3) as VideoIDHash;

View File

@@ -6,26 +6,27 @@ import { getHash } from "../../src/utils/getHash";
describe("getVideoLabelHash", () => { describe("getVideoLabelHash", () => {
const endpoint = "/api/videoLabels"; const endpoint = "/api/videoLabels";
before(async () => { before(async () => {
const query = 'INSERT INTO "sponsorTimes" ("videoID", "hashedVideoID", "votes", "locked", "UUID", "userID", "timeSubmitted", "category", "actionType", "hidden", "shadowHidden", "startTime", "endTime", "views") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0)'; const query = 'INSERT INTO "sponsorTimes" ("videoID", "hashedVideoID", "votes", "locked", "UUID", "userID", "timeSubmitted", "category", "actionType", "hidden", "shadowHidden", "startTime", "endTime", "views") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0)';
await db.prepare("run", query, ["getLabelHashSponsor" , getHash("getLabelHashSponsor", 1) , 2, 0, "labelhash01", "labeluser", 0, "sponsor", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashSponsor" , getHash("getLabelHashSponsor", 1) , 2, 0, "labelhash01", "labeluser", 0, "sponsor", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashEA" , getHash("getLabelHashEA", 1) , 2, 0, "labelhash02", "labeluser", 0, "exclusive_access", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashSponsor" , getHash("getLabelHashSponsor", 1) , 2, 0, "labelhash012", "labeluser", 0, "sponsor", "skip", 0, 0, 2]);
await db.prepare("run", query, ["getLabelHashSelfpromo" , getHash("getLabelHashSelfpromo", 1) , 2, 0, "labelhash03", "labeluser", 0, "selfpromo", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashEA" , getHash("getLabelHashEA", 1) , 2, 0, "labelhash02", "labeluser", 0, "exclusive_access", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashSelfpromo" , getHash("getLabelHashSelfpromo", 1) , 2, 0, "labelhash03", "labeluser", 0, "selfpromo", "full", 0, 0, 0]);
// priority override // priority override
await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash04", "labeluser", 0, "sponsor", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash04", "labeluser", 0, "sponsor", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash05", "labeluser", 0, "exclusive_access", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash05", "labeluser", 0, "exclusive_access", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash06", "labeluser", 0, "selfpromo", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash06", "labeluser", 0, "selfpromo", "full", 0, 0, 0]);
// locked only // locked only
await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash07", "labeluser", 0, "sponsor", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash07", "labeluser", 0, "sponsor", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash08", "labeluser", 0, "exclusive_access", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash08", "labeluser", 0, "exclusive_access", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 1, "labelhash09", "labeluser", 0, "selfpromo", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 1, "labelhash09", "labeluser", 0, "selfpromo", "full", 0, 0, 0]);
// hidden segments // hidden segments
await db.prepare("run", query, ["getLabelHashDownvote" , getHash("getLabelHashDownvote", 1) , -2, 0, "labelhash10", "labeluser", 0, "selfpromo", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashDownvote" , getHash("getLabelHashDownvote", 1) , -2, 0, "labelhash10", "labeluser", 0, "selfpromo", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashHidden" , getHash("getLabelHashHidden", 1) , 2, 0, "labelhash11", "labeluser", 0, "selfpromo", "full", 1, 0]); await db.prepare("run", query, ["getLabelHashHidden" , getHash("getLabelHashHidden", 1) , 2, 0, "labelhash11", "labeluser", 0, "selfpromo", "full", 1, 0, 0]);
await db.prepare("run", query, ["getLabelHashShHidden" , getHash("getLabelHashShHidden", 1) , 2, 0, "labelhash12", "labeluser", 0, "selfpromo", "full", 0, 1]); await db.prepare("run", query, ["getLabelHashShHidden" , getHash("getLabelHashShHidden", 1) , 2, 0, "labelhash12", "labeluser", 0, "selfpromo", "full", 0, 1, 0]);
// priority override2 // priority override2
await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , -2, 0, "labelhash13", "labeluser", 0, "sponsor", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , -2, 0, "labelhash13", "labeluser", 0, "sponsor", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash14", "labeluser", 0, "exclusive_access", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash14", "labeluser", 0, "exclusive_access", "full", 0, 0, 0]);
await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash15", "labeluser", 0, "selfpromo", "full", 0, 0]); await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash15", "labeluser", 0, "selfpromo", "full", 0, 0, 0]);
return; return;
}); });
@@ -46,6 +47,7 @@ describe("getVideoLabelHash", () => {
validateLabel(data, videoID); validateLabel(data, videoID);
const result = data[0].segments[0]; const result = data[0].segments[0];
assert.strictEqual(result.category, "sponsor"); assert.strictEqual(result.category, "sponsor");
assert.strictEqual(data[0].hasStartSegment, true);
done(); done();
}) })
.catch(err => done(err)); .catch(err => done(err));
@@ -60,6 +62,7 @@ describe("getVideoLabelHash", () => {
validateLabel(data, videoID); validateLabel(data, videoID);
const result = data[0].segments[0]; const result = data[0].segments[0];
assert.strictEqual(result.category, "exclusive_access"); assert.strictEqual(result.category, "exclusive_access");
assert.strictEqual(data[0].hasStartSegment, false);
done(); done();
}) })
.catch(err => done(err)); .catch(err => done(err));