Merge pull request #440 from ajayyy/full-video-labels

Full video labeling
This commit is contained in:
Ajay Ramachandran
2022-01-06 15:55:11 -05:00
committed by GitHub
14 changed files with 438 additions and 125 deletions

View File

@@ -18,6 +18,7 @@ describe("getSkipSegmentsByHash", () => {
const requiredSegmentHashVidHash = "17bf8d9090e050257772f8bff277293c29c7ce3b25eb969a8fae111a2434504d";
const differentCategoryVidHash = "7fac44d1ee3257ec7f18953e2b5f991828de6854ad57193d1027c530981a89c0";
const nonMusicOverlapVidHash = "306151f778f9bfd19872b3ccfc83cbab37c4f370717436bfd85e0a624cd8ba3c";
const fullCategoryVidHash = "278fa987eebfe07ae3a4a60cf0663989ad874dd0c1f0430831d63c2001567e6f";
before(async () => {
const query = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", views, category, "actionType", "service", "hidden", "shadowHidden", "hashedVideoID", "description") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
await db.prepare("run", query, ["getSegmentsByHash-0", 1, 10, 2, 0, "getSegmentsByHash-01", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, getSegmentsByHash0Hash, ""]);
@@ -50,6 +51,8 @@ describe("getSkipSegmentsByHash", () => {
await db.prepare("run", query, ["differentCategoryVid", 60, 70, 2, 1, "differentCategoryVid-2", "testman", 0, 50, "intro", "skip", "YouTube", 0, 0, differentCategoryVidHash, ""]);
await db.prepare("run", query, ["nonMusicOverlapVid", 60, 70, 2, 0, "nonMusicOverlapVid-1", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, nonMusicOverlapVidHash, ""]);
await db.prepare("run", query, ["nonMusicOverlapVid", 60, 70, 2, 1, "nonMusicOverlapVid-2", "testman", 0, 50, "music_offtopic", "skip", "YouTube", 0, 0, nonMusicOverlapVidHash, ""]);
await db.prepare("run", query, ["fullCategoryVid", 60, 70, 2, 0, "fullCategoryVid-1", "testman", 0, 50, "sponsor", "full", "YouTube", 0, 0, fullCategoryVidHash, ""]);
await db.prepare("run", query, ["fullCategoryVid", 60, 70, 2, 1, "fullCategoryVid-2", "testman", 0, 50, "selfpromo", "full", "YouTube", 0, 0, fullCategoryVidHash, ""]);
});
it("Should be able to get a 200", (done) => {
@@ -528,6 +531,19 @@ describe("getSkipSegmentsByHash", () => {
.catch(err => done(err));
});
it("Should only return one segment when fetching full video segments", (done) => {
client.get(`${endpoint}/278f`, { params: { category: ["sponsor", "selfpromo"], actionType: "full" } })
.then(res => {
assert.strictEqual(res.status, 200);
const data = res.data;
assert.strictEqual(data.length, 1);
assert.strictEqual(data[0].segments.length, 1);
assert.strictEqual(data[0].segments[0].category, "selfpromo");
done();
})
.catch(err => done(err));
});
it("Should be able to get specific segments with partial requiredSegments", (done) => {
const requiredSegment1 = "fbf0af454059733c8822f6a4ac8ec568e0787f8c0a5ee915dd5b05e0d7a9a388";
const requiredSegment2 = "7e1ebc5194551d2d0a606d64f675e5a14952e4576b2959f8c9d51e316c14f8da";

View File

@@ -1,12 +1,12 @@
import { getSubmissionUUID } from "../../src/utils/getSubmissionUUID";
import assert from "assert";
import { ActionType, VideoID, Service } from "../../src/types/segments.model";
import { ActionType, VideoID, Service, Category } from "../../src/types/segments.model";
import { UserID } from "../../src/types/user.model";
describe("getSubmissionUUID", () => {
it("Should return the hashed value", () => {
assert.strictEqual(
getSubmissionUUID("video001" as VideoID, "skip" as ActionType, "testuser001" as UserID, 13.33337, 42.000001, Service.YouTube),
"529611b4cdd7319e705a32ae9557a02e59c8dbc1306097b2d2d5807c6405e9b1a");
getSubmissionUUID("video001" as VideoID, "sponsor" as Category, "skip" as ActionType, "testuser001" as UserID, 13.33337, 42.000001, Service.YouTube),
"2a473bca993dd84d8c2f6a4785989b20948dfe0c12c00f6f143bbda9ed561dca6");
});
});

View File

@@ -3,8 +3,9 @@ import { db } from "../../src/databases/databases";
import assert from "assert";
import { LockCategory } from "../../src/types/segments.model";
import { client } from "../utils/httpClient";
import { partialDeepEquals } from "../utils/partialDeepEquals";
const stringDeepEquals = (a: string[] ,b: string[]): boolean => {
const stringDeepEquals = (a: string[], b: string[]): boolean => {
let result = true;
b.forEach((e) => {
if (!a.includes(e)) result = false;
@@ -23,18 +24,27 @@ describe("lockCategoriesRecords", () => {
const insertVipUserQuery = 'INSERT INTO "vipUsers" ("userID") VALUES (?)';
await db.prepare("run", insertVipUserQuery, [lockVIPUserHash]);
const insertLockCategoryQuery = 'INSERT INTO "lockCategories" ("userID", "videoID", "category", "reason", "service") VALUES (?, ?, ?, ?, ?)';
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id", "sponsor", "reason-1", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id", "intro", "reason-1", "YouTube"]);
const insertLockCategoryQuery = 'INSERT INTO "lockCategories" ("userID", "videoID", "actionType", "category", "reason", "service") VALUES (?, ?, ?, ?, ?, ?)';
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id", "skip", "sponsor", "reason-1", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id", "mute", "sponsor", "reason-1", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id", "skip", "intro", "reason-1", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id", "mute", "intro", "reason-1", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id-1", "sponsor", "reason-2", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id-1", "intro", "reason-2", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "lockCategoryVideo", "sponsor", "reason-3", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id-1", "skip", "sponsor", "reason-2", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id-1", "mute", "sponsor", "reason-2", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id-1", "skip", "intro", "reason-2", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "no-segments-video-id-1", "mute", "intro", "reason-2", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "lockCategoryVideo", "skip", "sponsor", "reason-3", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "lockCategoryVideo", "mute", "sponsor", "reason-3", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record", "sponsor", "reason-4", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "lockCategoryVideo-2", "skip", "sponsor", "reason-4", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "sponsor", "reason-5", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "intro", "reason-5", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record", "skip", "sponsor", "reason-4", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record", "mute", "sponsor", "reason-4", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "skip", "sponsor", "reason-5", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "mute", "sponsor", "reason-5", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "skip", "intro", "reason-5", "YouTube"]);
await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "mute", "intro", "reason-5", "YouTube"]);
});
it("Should update the database version when starting the application", async () => {
@@ -60,6 +70,32 @@ describe("lockCategoriesRecords", () => {
"outro",
"shilling",
],
submittedValues: [
{
actionType: "skip",
category: "outro"
},
{
actionType: "mute",
category: "outro"
},
{
actionType: "skip",
category: "shilling"
},
{
actionType: "mute",
category: "shilling"
},
{
actionType: "skip",
category: "intro"
},
{
actionType: "mute",
category: "intro"
}
]
};
client.post(endpoint, json)
.then(res => {
@@ -88,15 +124,15 @@ describe("lockCategoriesRecords", () => {
.then(async res => {
assert.strictEqual(res.status, 200);
const result = await checkLockCategories(videoID);
assert.strictEqual(result.length, 4);
assert.strictEqual(result.length, 8);
const oldRecordNotChangeReason = result.filter(item =>
item.reason === "reason-2" && ["sponsor", "intro"].includes(item.category)
);
const newRecordWithEmptyReason = result.filter(item =>
item.reason === "" && ["outro", "shilling"].includes(item.category)
);
assert.strictEqual(newRecordWithEmptyReason.length, 2);
assert.strictEqual(oldRecordNotChangeReason.length, 2);
assert.strictEqual(newRecordWithEmptyReason.length, 4);
assert.strictEqual(oldRecordNotChangeReason.length, 4);
done();
})
.catch(err => done(err));
@@ -160,7 +196,7 @@ describe("lockCategoriesRecords", () => {
.then(async res => {
assert.strictEqual(res.status, 200);
const result = await checkLockCategories(videoID);
assert.strictEqual(result.length, 4);
assert.strictEqual(result.length, 8);
const newRecordWithNewReason = result.filter(item =>
expectedWithNewReason.includes(item.category) && item.reason === "new reason"
);
@@ -168,8 +204,8 @@ describe("lockCategoriesRecords", () => {
item.reason === "reason-2"
);
assert.strictEqual(newRecordWithNewReason.length, 3);
assert.strictEqual(oldRecordNotChangeReason.length, 1);
assert.strictEqual(newRecordWithNewReason.length, 6);
assert.strictEqual(oldRecordNotChangeReason.length, 2);
done();
})
.catch(err => done(err));
@@ -187,7 +223,7 @@ describe("lockCategoriesRecords", () => {
.then(async res => {
assert.strictEqual(res.status, 200);
const result = await checkLockCategories("underscore");
assert.strictEqual(result.length, 1);
assert.strictEqual(result.length, 2);
done();
})
.catch(err => done(err));
@@ -205,7 +241,7 @@ describe("lockCategoriesRecords", () => {
.then(async res => {
assert.strictEqual(res.status, 200);
const result = await checkLockCategories("bothCases");
assert.strictEqual(result.length, 1);
assert.strictEqual(result.length, 2);
done();
})
.catch(err => done(err));
@@ -231,6 +267,41 @@ describe("lockCategoriesRecords", () => {
.catch(err => done(err));
});
it("Should be able to submit specific action type not in video (sql check)", (done) => {
const videoID = "lockCategoryVideo-2";
const json = {
videoID,
userID: lockVIPUser,
categories: [
"sponsor",
],
actionTypes: [
"mute"
],
reason: "custom-reason",
};
client.post(endpoint, json)
.then(async res => {
assert.strictEqual(res.status, 200);
const result = await checkLockCategories(videoID);
assert.strictEqual(result.length, 2);
assert.ok(partialDeepEquals(result, [
{
category: "sponsor",
actionType: "skip",
reason: "reason-4",
},
{
category: "sponsor",
actionType: "mute",
reason: "custom-reason",
}
]));
done();
})
.catch(err => done(err));
});
it("Should return 400 for missing params", (done) => {
client.post(endpoint, {})
.then(res => {
@@ -365,7 +436,7 @@ describe("lockCategoriesRecords", () => {
.then(async res => {
assert.strictEqual(res.status, 200);
const result = await checkLockCategories(videoID);
assert.strictEqual(result.length, 1);
assert.strictEqual(result.length, 2);
done();
})
.catch(err => done(err));

View File

@@ -16,6 +16,7 @@ describe("postSkipSegments", () => {
// Constant and helpers
const submitUserOne = `PostSkipUser1${".".repeat(18)}`;
const submitUserTwo = `PostSkipUser2${".".repeat(18)}`;
const submitUserTwoHash = getHash(submitUserTwo);
const submitUserThree = `PostSkipUser3${".".repeat(18)}`;
const warnUser01 = "warn-user01-qwertyuiopasdfghjklzxcvbnm";
@@ -34,8 +35,9 @@ describe("postSkipSegments", () => {
const warnVideoID = "postSkip2";
const badInputVideoID = "dQw4w9WgXcQ";
const shadowBanVideoID = "postSkipBan";
const shadowBanVideoID2 = "postSkipBan2";
const queryDatabase = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
const queryDatabase = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "votes", "userID", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
const queryDatabaseActionType = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
const queryDatabaseChapter = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "actionType", "description" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
const queryDatabaseDuration = (videoID: string) => db.prepare("get", `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
@@ -54,10 +56,15 @@ describe("postSkipSegments", () => {
});
before(() => {
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "shadowHidden", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
db.prepare("run", insertSponsorTimeQuery, ["80percent_video", 0, 1000, 0, "80percent-uuid-0", submitUserOneHash, 0, 0, "interaction", 0, "80percent_video"]);
db.prepare("run", insertSponsorTimeQuery, ["80percent_video", 1001, 1005, 0, "80percent-uuid-1", submitUserOneHash, 0, 0, "interaction", 0, "80percent_video"]);
db.prepare("run", insertSponsorTimeQuery, ["80percent_video", 0, 5000, -2, "80percent-uuid-2", submitUserOneHash, 0, 0, "interaction", 0, "80percent_video"]);
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "videoDuration", "shadowHidden", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
db.prepare("run", insertSponsorTimeQuery, ["80percent_video", 0, 1000, 0, "80percent-uuid-0", submitUserOneHash, 0, 0, "interaction", "skip", 0, 0, "80percent_video"]);
db.prepare("run", insertSponsorTimeQuery, ["80percent_video", 1001, 1005, 0, "80percent-uuid-1", submitUserOneHash, 0, 0, "interaction", "skip", 0, 0, "80percent_video"]);
db.prepare("run", insertSponsorTimeQuery, ["80percent_video", 0, 5000, -2, "80percent-uuid-2", submitUserOneHash, 0, 0, "interaction", "skip", 0, 0, "80percent_video"]);
db.prepare("run", insertSponsorTimeQuery, ["full_video_segment", 0, 0, 0, "full-video-uuid-0", submitUserTwoHash, 0, 0, "sponsor", "full", 0, 0, "full_video_segment"]);
db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 0, 0, 0, "full-video-duration-uuid-0", submitUserTwoHash, 0, 0, "sponsor", "full", 123, 0, "full_video_duration_segment"]);
db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 25, 30, 0, "full-video-duration-uuid-1", submitUserTwoHash, 0, 0, "sponsor", "skip", 123, 0, "full_video_duration_segment"]);
const now = Date.now();
const warnVip01Hash = getHash("warn-vip01-qwertyuiopasdfghjklzxcvbnm");
@@ -405,6 +412,39 @@ describe("postSkipSegments", () => {
.catch(err => done(err));
});
it("Should be able to submit with a new duration, and not hide full video segments", async () => {
const videoID = "full_video_duration_segment";
const res = await postSkipSegmentJSON({
userID: submitUserOne,
videoID,
videoDuration: 100,
segments: [{
segment: [20, 30],
category: "sponsor",
}],
});
assert.strictEqual(res.status, 200);
const videoRows = await db.prepare("all", `SELECT "startTime", "endTime", "locked", "category", "actionType", "videoDuration"
FROM "sponsorTimes" WHERE "videoID" = ? AND hidden = 0`, [videoID]);
const hiddenVideoRows = await db.prepare("all", `SELECT "startTime", "endTime", "locked", "category", "videoDuration"
FROM "sponsorTimes" WHERE "videoID" = ? AND hidden = 1`, [videoID]);
assert.strictEqual(videoRows.length, 2);
const expected = {
startTime: 20,
endTime: 30,
locked: 0,
category: "sponsor",
videoDuration: 100
};
assert.ok(partialDeepEquals(videoRows[1], expected));
const fullExpected = {
category: "sponsor",
actionType: "full"
};
assert.ok(partialDeepEquals(videoRows[0], fullExpected));
assert.strictEqual(hiddenVideoRows.length, 1);
});
it("Should be able to submit a single time under a different service (JSON method)", (done) => {
const videoID = "postSkip7";
postSkipSegmentJSON({
@@ -901,6 +941,26 @@ describe("postSkipSegments", () => {
.catch(err => done(err));
});
it("Should return not be 403 when submitting with locked category but unlocked actionType", (done) => {
const videoID = "lockedVideo";
db.prepare("run", `INSERT INTO "lockCategories" ("userID", "videoID", "category", "reason")
VALUES(?, ?, ?, ?)`, [getHash("VIPUser-lockCategories"), videoID, "sponsor", "Custom Reason"])
.then(() => postSkipSegmentJSON({
userID: submitUserOne,
videoID,
segments: [{
segment: [1, 10],
category: "sponsor",
actionType: "mute"
}],
}))
.then(res => {
assert.strictEqual(res.status, 200);
done();
})
.catch(err => done(err));
});
it("Should return 403 for submiting in lockedCategory", (done) => {
const videoID = "lockedVideo1";
db.prepare("run", `INSERT INTO "lockCategories" ("userID", "videoID", "category", "reason")
@@ -1044,6 +1104,77 @@ describe("postSkipSegments", () => {
.catch(err => done(err));
});
it("Should allow submitting full video sponsor", (done) => {
const videoID = "qqwerth";
postSkipSegmentParam({
videoID,
startTime: 0,
endTime: 0,
category: "sponsor",
actionType: "full",
userID: submitUserTwo
})
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await queryDatabase(videoID);
const expected = {
startTime: 0,
endTime: 0,
votes: 0,
userID: submitUserTwoHash,
category: "sponsor",
actionType: "full"
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
});
it("Submitting duplicate full video sponsor should count as an upvote", (done) => {
const videoID = "full_video_segment";
postSkipSegmentParam({
videoID,
startTime: 0,
endTime: 0,
category: "sponsor",
actionType: "full",
userID: submitUserOne
})
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await queryDatabase(videoID);
const expected = {
startTime: 0,
endTime: 0,
votes: 1,
userID: submitUserTwoHash,
category: "sponsor",
actionType: "full"
};
assert.ok(partialDeepEquals(row, expected));
done();
})
.catch(err => done(err));
});
it("Should not allow submitting full video sponsor not at zero seconds", (done) => {
const videoID = "qqwerth";
postSkipSegmentParam({
videoID,
startTime: 0,
endTime: 1,
category: "sponsor",
actionType: "full",
userID: submitUserTwo
})
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
it("Should not be able to submit with colons in timestamps", (done) => {
const videoID = "colon-1";
postSkipSegmentJSON({
@@ -1085,6 +1216,25 @@ describe("postSkipSegments", () => {
.catch(err => done(err));
});
it("Should not add full segments to database if user if shadowbanned", (done) => {
const videoID = shadowBanVideoID2;
postSkipSegmentParam({
videoID,
startTime: 0,
endTime: 0,
category: "sponsor",
actionType: "full",
userID: banUser01
})
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await db.prepare("get", `SELECT "startTime", "endTime", "shadowHidden", "userID" FROM "sponsorTimes" WHERE "videoID" = ?`, [videoID]);
assert.strictEqual(row, undefined);
done();
})
.catch(err => done(err));
});
it("Should return 400 if videoID is empty", (done) => {
const videoID = null as string;
postSkipSegmentParam({

View File

@@ -13,7 +13,7 @@ export class YouTubeApiMock {
};
}
if (obj.id === "noDuration") {
if (obj.id === "noDuration" || obj.id === "full_video_duration_segment") {
return {
err: null,
data: {