From 7c1abd9747572d8424f2ac00f27ca2f5c150b5ab Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Sat, 4 May 2024 21:30:09 +0200 Subject: [PATCH] Make returned video duration in getBranding.ts consistent Instead of picking the first segment returned by the db (i.e. possibly random), sort segments by submission time and use the oldest visible segment with a non-zero video duration. --- src/routes/getBranding.ts | 16 ++++++++++------ test/cases/getBranding.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/routes/getBranding.ts b/src/routes/getBranding.ts index ac897d0..1631d6c 100644 --- a/src/routes/getBranding.ts +++ b/src/routes/getBranding.ts @@ -45,7 +45,8 @@ export async function getVideoBranding(res: Response, videoID: VideoID, service: const getSegments = () => db.prepare( "all", `SELECT "startTime", "endTime", "category", "videoDuration" FROM "sponsorTimes" - WHERE "votes" > -2 AND "shadowHidden" = 0 AND "hidden" = 0 AND "actionType" = 'skip' AND "videoID" = ? AND "service" = ?`, + WHERE "votes" > -2 AND "shadowHidden" = 0 AND "hidden" = 0 AND "actionType" = 'skip' AND "videoID" = ? AND "service" = ? + ORDER BY "timeSubmitted" ASC`, [videoID, service], { useReplica: true } ) as Promise; @@ -110,7 +111,8 @@ export async function getVideoBrandingByHash(videoHashPrefix: VideoIDHash, servi const getSegments = () => db.prepare( "all", `SELECT "videoID", "startTime", "endTime", "category", "videoDuration" FROM "sponsorTimes" - WHERE "votes" > -2 AND "shadowHidden" = 0 AND "hidden" = 0 AND "actionType" = 'skip' AND "hashedVideoID" LIKE ? AND "service" = ?`, + WHERE "votes" > -2 AND "shadowHidden" = 0 AND "hidden" = 0 AND "actionType" = 'skip' AND "hashedVideoID" LIKE ? AND "service" = ? + ORDER BY "timeSubmitted" ASC`, [`${videoHashPrefix}%`, service], { useReplica: true } ) as Promise; @@ -200,11 +202,13 @@ async function filterAndSortBranding(videoID: VideoID, returnUserID: boolean, fe })) .filter((a) => fetchAll || a.votes >= 0 || a.locked) as ThumbnailResult[]; + const videoDuration = dbSegments.filter(s => s.videoDuration !== 0)[0]?.videoDuration ?? null; + return { titles, thumbnails, - randomTime: findRandomTime(videoID, dbSegments), - videoDuration: dbSegments[0]?.videoDuration ?? null + randomTime: findRandomTime(videoID, dbSegments, videoDuration), + videoDuration: videoDuration, }; } @@ -233,7 +237,7 @@ async function shouldKeepSubmission(submissions: BrandingDBSubmission[], type: B return (_, index) => shouldKeep[index]; } -export function findRandomTime(videoID: VideoID, segments: BrandingSegmentDBResult[]): number { +export function findRandomTime(videoID: VideoID, segments: BrandingSegmentDBResult[], videoDuration: number): number { let randomTime = SeedRandom.alea(videoID)(); // Don't allow random times past 90% of the video if no endcard @@ -243,7 +247,7 @@ export function findRandomTime(videoID: VideoID, segments: BrandingSegmentDBResu if (segments.length === 0) return randomTime; - const videoDuration = segments[0].videoDuration || Math.max(...segments.map((s) => s.endTime)); + videoDuration ||= Math.max(...segments.map((s) => s.endTime)); // use highest end time as a fallback here // There are segments, treat this as a relative time in the chopped up video const sorted = segments.sort((a, b) => a.startTime - b.startTime); diff --git a/test/cases/getBranding.ts b/test/cases/getBranding.ts index 4a2f499..c4dd0a0 100644 --- a/test/cases/getBranding.ts +++ b/test/cases/getBranding.ts @@ -13,6 +13,7 @@ describe("getBranding", () => { const videoIDEmpty = "videoID4"; const videoIDRandomTime = "videoID5"; const videoIDUnverified = "videoID6"; + const videoIDvidDuration = "videoID7"; const videoID1Hash = getHash(videoID1, 1).slice(0, 4); const videoID2LockedHash = getHash(videoID2Locked, 1).slice(0, 4); @@ -20,6 +21,7 @@ describe("getBranding", () => { const videoIDEmptyHash = "aaaa"; const videoIDRandomTimeHash = getHash(videoIDRandomTime, 1).slice(0, 4); const videoIDUnverifiedHash = getHash(videoIDUnverified, 1).slice(0, 4); + const videoIDvidDurationHash = getHash(videoIDUnverified, 1).slice(0, 4); const endpoint = "/api/branding"; const getBranding = (params: Record) => client({ @@ -40,6 +42,7 @@ describe("getBranding", () => { const thumbnailQuery = `INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?)`; const thumbnailTimestampsQuery = `INSERT INTO "thumbnailTimestamps" ("UUID", "timestamp") VALUES (?, ?)`; const thumbnailVotesQuery = `INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden", "downvotes", "removed") VALUES (?, ?, ?, ?, ?, ?)`; + const segmentQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "hidden", "shadowHidden", "description", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; await Promise.all([ db.prepare("run", titleQuery, [videoID1, "title1", 0, "userID1", Service.YouTube, videoID1Hash, 1, "UUID1"]), @@ -107,9 +110,8 @@ describe("getBranding", () => { db.prepare("run", thumbnailVotesQuery, ["UUID32T", 1, 0, 1, 0, 0]) ]); - const query = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "hidden", "shadowHidden", "description", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - await db.prepare("run", query, [videoIDRandomTime, 1, 11, 1, 0, "uuidbranding1", "testman", 0, 50, "sponsor", "skip", "YouTube", 100, 0, 0, "", videoIDRandomTimeHash]); - await db.prepare("run", query, [videoIDRandomTime, 20, 33, 2, 0, "uuidbranding2", "testman", 0, 50, "intro", "skip", "YouTube", 100, 0, 0, "", videoIDRandomTimeHash]); + await db.prepare("run", segmentQuery, [videoIDRandomTime, 1, 11, 1, 0, "uuidbranding1", "testman", 0, 50, "sponsor", "skip", "YouTube", 100, 0, 0, "", videoIDRandomTimeHash]); + await db.prepare("run", segmentQuery, [videoIDRandomTime, 20, 33, 2, 0, "uuidbranding2", "testman", 0, 50, "intro", "skip", "YouTube", 100, 0, 0, "", videoIDRandomTimeHash]); await Promise.all([ db.prepare("run", titleQuery, [videoIDUnverified, "title1", 0, "userID1", Service.YouTube, videoIDUnverifiedHash, 1, "UUID-uv-1"]), @@ -130,6 +132,17 @@ describe("getBranding", () => { db.prepare("run", thumbnailVotesQuery, ["UUID-uv-2T", 2, 0, 0, 0, 0]), db.prepare("run", thumbnailVotesQuery, ["UUID-uv-3T", 1, 0, 0, 0, 0]) ]); + + // Video duration test segments + await Promise.all([ + db.prepare("run", segmentQuery, [videoIDvidDuration, 0, 1, 0, 0, "uuidvd1", "testman", 10, 0, "sponsor", "skip", "YouTube", 0, 0, 0, "", videoIDvidDurationHash]), // visible, no vid duration + db.prepare("run", segmentQuery, [videoIDvidDuration, 0, 2, -2, 0, "uuidvd2", "testman", 11, 0, "sponsor", "skip", "YouTube", 10, 0, 0, "", videoIDvidDurationHash]), // downvoted + db.prepare("run", segmentQuery, [videoIDvidDuration, 0, 3, 0, 0, "uuidvd3", "testman", 12, 0, "sponsor", "skip", "YouTube", 10.1, 1, 0, "", videoIDvidDurationHash]), // hidden + db.prepare("run", segmentQuery, [videoIDvidDuration, 0, 4, 0, 0, "uuidvd4", "testman", 13, 0, "sponsor", "skip", "YouTube", 20.1, 0, 1, "", videoIDvidDurationHash]), // shadowhidden + db.prepare("run", segmentQuery, [videoIDvidDuration, 0, 5, 0, 0, "uuidvd5", "testman", 14, 0, "sponsor", "skip", "YouTube", 21.3, 0, 0, "", videoIDvidDurationHash]), // oldest visible w/ duration, should be picked + db.prepare("run", segmentQuery, [videoIDvidDuration, 0, 6, 0, 0, "uuidvd6", "testman", 15, 0, "sponsor", "skip", "YouTube", 21.37, 0, 0, "", videoIDvidDurationHash]), // not the oldest visible + db.prepare("run", segmentQuery, [videoIDvidDuration, 0, 7, -2, 0, "uuidvd7", "testman", 16, 0, "sponsor", "skip", "YouTube", 21.38, 0, 0, "", videoIDvidDurationHash]), // downvoted, not the oldest + ]); }); it("should get top titles and thumbnails", async () => { @@ -312,6 +325,16 @@ describe("getBranding", () => { }); }); + it("should get the correct video duration", async () => { + const correctDuration = 21.3; + + const result1 = await getBranding({ videoID: videoIDvidDuration, fetchAll: true }); + const result2 = await getBrandingByHash(videoIDvidDurationHash, { fetchAll: true }); + + assert.strictEqual(result1.data.videoDuration, correctDuration); + assert.strictEqual(result2.data[videoIDvidDuration].videoDuration, correctDuration); + }); + async function checkVideo(videoID: string, videoIDHash: string, fetchAll: boolean, expected: { titles: TitleResult[], thumbnails: ThumbnailResult[]