diff options
author | Ajay Ramachandran <[email protected]> | 2021-11-07 15:26:00 -0500 |
---|---|---|
committer | Ajay Ramachandran <[email protected]> | 2021-11-07 15:26:00 -0500 |
commit | 7dfee8118834baeec5103a6c607d218d04f2f4ed (patch) | |
tree | 6deefc4a39e5964b76d4e3a9dba6dea369b42837 | |
parent | 3a2d9c0e0e63c0302200304a9da5884a61c4ebe3 (diff) | |
download | SponsorBlock-7dfee8118834baeec5103a6c607d218d04f2f4ed.tar.gz SponsorBlock-7dfee8118834baeec5103a6c607d218d04f2f4ed.zip |
Add chapter sorting method to show small chapters in the middle of large ones
-rw-r--r-- | src/config.ts | 2 | ||||
-rw-r--r-- | src/js-components/previewBar.ts | 132 | ||||
-rw-r--r-- | src/types.ts | 5 | ||||
-rw-r--r-- | test/previewBar.test.ts | 575 |
4 files changed, 677 insertions, 37 deletions
diff --git a/src/config.ts b/src/config.ts index 51c1f06e..f76f634b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -427,6 +427,8 @@ function migrateOldFormats(config: SBConfig) { } async function setupConfig() { + if (typeof(chrome) === "undefined") return; + await fetchConfig(); addDefaults(); convertJSON(); diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 390b806a..37bc7098 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -6,9 +6,9 @@ https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd 'use strict'; import Config from "../config"; -import { ActionType, ActionTypes, Category, CategoryActionType, SponsorTime } from "../types"; +import { ActionType, Category, CategoryActionType, SegmentContainer, SponsorTime } from "../types"; import Utils from "../utils"; -import { getCategoryActionType, getSkippingText } from "../utils/categoryUtils"; +import { getCategoryActionType } from "../utils/categoryUtils"; const utils = new Utils(); const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible'; @@ -22,6 +22,10 @@ export interface PreviewBarSegment { description: string; } +interface ChapterGroup extends SegmentContainer { + originalDuration: number +} + class PreviewBar { container: HTMLUListElement; categoryTooltip?: HTMLDivElement; @@ -39,7 +43,8 @@ class PreviewBar { customChaptersBar: HTMLElement; chaptersBarSegments: PreviewBarSegment[]; - constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) { + constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, test=false) { + if (test) return; this.container = document.createElement('ul'); this.container.id = 'previewbar'; @@ -228,18 +233,10 @@ class PreviewBar { this.customChaptersBar?.remove(); // Merge overlapping chapters - const mergedSegments = segments?.filter((segment) => this.chapterFilter(segment)) - .reduce((acc, curr) => { - if (acc.length === 0 || curr.segment[0] > acc[acc.length - 1].segment[1]) { - acc.push(curr); - } else { - acc[acc.length - 1].segment[1] = Math.max(acc[acc.length - 1].segment[1], curr.segment[1]); - } + const filteredSegments = segments?.filter((segment) => this.chapterFilter(segment)); + const chaptersToRender = this.createChapterRenderGroups(filteredSegments).filter((segment) => this.chapterGroupFilter(segment)); - return acc; - }, [] as PreviewBarSegment[]); - - if (mergedSegments?.length <= 0) { + if (chaptersToRender?.length <= 0) { chapterBar.style.removeProperty("display"); return; } @@ -254,31 +251,13 @@ class PreviewBar { this.chaptersBarSegments = segments; // Modify it to have sections for each segment - for (let i = 0; i < mergedSegments.length; i++) { - const segment = mergedSegments[i]; - if (i === 0 && segment.segment[0] > 0) { - const newBlankSection = originalSection.cloneNode(true) as HTMLElement; - const blankDuration = segment.segment[0]; - - this.setupChapterSection(newBlankSection, blankDuration); - newChapterBar.appendChild(newBlankSection); - } - - const duration = segment.segment[1] - segment.segment[0]; + for (let i = 0; i < chaptersToRender.length; i++) { + const chapter = chaptersToRender[i].segment; + const duration = chapter[1] - chapter[0]; const newSection = originalSection.cloneNode(true) as HTMLElement; this.setupChapterSection(newSection, duration); newChapterBar.appendChild(newSection); - - const nextSegment = mergedSegments[i + 1]; - const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; - if (this.timeToDecimal(nextTime - segment.segment[1]) > MIN_CHAPTER_SIZE) { - const newBlankSection = originalSection.cloneNode(true) as HTMLElement; - const blankDuration = nextTime - segment.segment[1]; - - this.setupChapterSection(newBlankSection, blankDuration); - newChapterBar.appendChild(newBlankSection); - } } // Hide old bar @@ -294,6 +273,83 @@ class PreviewBar { this.updateChapterAllMutation(chapterBar, progressBar, true); } + createChapterRenderGroups(segments: PreviewBarSegment[]): ChapterGroup[] { + const result: ChapterGroup[] = []; + + segments?.forEach((segment, index) => { + const latestChapter = result[result.length - 1]; + if (latestChapter && latestChapter.segment[1] > segment.segment[0]) { + const segmentDuration = segment.segment[1] - segment.segment[0]; + if (segmentDuration < latestChapter.originalDuration) { + // Remove latest if it starts too late + let latestValidChapter = latestChapter; + const chaptersToAddBack: ChapterGroup[] = [] + while (latestValidChapter?.segment[0] >= segment.segment[0]) { + const invalidChapter = result.pop(); + if (invalidChapter.segment[1] > segment.segment[1]) { + if (invalidChapter.segment[0] === segment.segment[0]) { + invalidChapter.segment[0] = segment.segment[1]; + } + chaptersToAddBack.push(invalidChapter); + } + latestValidChapter = result[result.length - 1]; + } + + // Split the latest chapter if smaller + result.push({ + segment: [segment.segment[0], segment.segment[1]], + originalDuration: segmentDuration, + }); + if (latestValidChapter?.segment[1] > segment.segment[1]) { + result.push({ + segment: [segment.segment[1], latestValidChapter.segment[1]], + originalDuration: latestValidChapter.originalDuration + }); + } + + result.push(...chaptersToAddBack); + if (latestValidChapter) latestValidChapter.segment[1] = segment.segment[0]; + } else { + // Start at end of old one if bigger + result.push({ + segment: [latestChapter.segment[1], segment.segment[1]], + originalDuration: segmentDuration + }); + } + } else { + // Add empty buffer before segment if needed + const lastTime = latestChapter?.segment[1] || 0; + if (segment.segment[0] > lastTime) { + result.push({ + segment: [lastTime, segment.segment[0]], + originalDuration: 0 + }); + } + + // Normal case + result.push({ + segment: [segment.segment[0], segment.segment[1]], + originalDuration: segment.segment[1] - segment.segment[0] + }); + } + + // Add empty buffer after segment if needed + if (index === segments.length - 1) { + const nextSegment = segments[index + 1]; + const nextTime = nextSegment ? nextSegment.segment[0] : this.videoDuration; + const lastTime = result[result.length - 1]?.segment[1] || segment.segment[1]; + if (this.timeToDecimal(nextTime - lastTime) > MIN_CHAPTER_SIZE) { + result.push({ + segment: [lastTime, nextTime], + originalDuration: 0 + }); + } + } + }); + + return result; + } + private setupChapterSection(section: HTMLElement, duration: number): void { section.style.marginRight = "2px"; section.style.width = `calc(${this.timeToPercentage(duration)} - 2px)`; @@ -447,7 +503,11 @@ class PreviewBar { private chapterFilter(segment: PreviewBarSegment): boolean { return getCategoryActionType(segment.category) !== CategoryActionType.POI - && segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > MIN_CHAPTER_SIZE; + && this.chapterGroupFilter(segment); + } + + private chapterGroupFilter(segment: SegmentContainer): boolean { + return segment.segment.length === 2 && this.timeToDecimal(segment.segment[1] - segment.segment[0]) > MIN_CHAPTER_SIZE; } timeToPercentage(time: number): string { diff --git a/src/types.ts b/src/types.ts index 9d877d67..0c1a7d87 100644 --- a/src/types.ts +++ b/src/types.ts @@ -74,8 +74,11 @@ export enum SponsorSourceType { Local = 1 } -export interface SponsorTime { +export interface SegmentContainer { segment: [number] | [number, number]; +} + +export interface SponsorTime extends SegmentContainer { UUID: SegmentUUID; locked?: number; diff --git a/test/previewBar.test.ts b/test/previewBar.test.ts new file mode 100644 index 00000000..755d79a6 --- /dev/null +++ b/test/previewBar.test.ts @@ -0,0 +1,575 @@ +import PreviewBar, { PreviewBarSegment } from "../src/js-components/previewBar"; + +describe("createChapterRenderGroups", () => { + let previewBar: PreviewBar; + beforeEach(() => { + previewBar = new PreviewBar(null, null, null, true); + }) + + it("Two unrelated times", () => { + previewBar.videoDuration = 315; + const groups = previewBar.createChapterRenderGroups([{ + segment: [2, 30], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }, { + segment: [50, 80], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }] as PreviewBarSegment[]); + + expect(groups).toStrictEqual([{ + segment: [0, 2], + originalDuration: 0 + }, { + segment: [2, 30], + originalDuration: 30 - 2 + }, { + segment: [30, 50], + originalDuration: 0 + }, { + segment: [50, 80], + originalDuration: 80 - 50 + }, { + segment: [80, 315], + originalDuration: 0 + }]); + }); + + it("Small time in bigger time", () => { + previewBar.videoDuration = 315; + const groups = previewBar.createChapterRenderGroups([{ + segment: [2.52, 30], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }, { + segment: [20, 25], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }] as PreviewBarSegment[]); + + expect(groups).toStrictEqual([{ + segment: [0, 2.52], + originalDuration: 0 + }, { + segment: [2.52, 20], + originalDuration: 30 - 2.52 + }, { + segment: [20, 25], + originalDuration: 25 - 20 + }, { + segment: [25, 30], + originalDuration: 30 - 2.52 + }, { + segment: [30, 315], + originalDuration: 0 + }]); + }); + + it("Same start time", () => { + previewBar.videoDuration = 315; + const groups = previewBar.createChapterRenderGroups([{ + segment: [2.52, 30], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }, { + segment: [2.52, 40], + category: "sponsor", + unsubmitted: false, + showLarger: false, + description: "" + }] as PreviewBarSegment[]); + + expect(groups).toStrictEqual([{ + segment: [0, 2.52], + originalDuration: 0 + }, { + segment: [2.52, 30], + originalDuration: 30 - 2.52 + }, { + segment: [30, 40], + originalDuration: 40 - 2.52 + }, { + segment: [40, 315], + originalDuration: 0 + }]); + }); + + it("Lots of overlapping segments", () => { + previewBar.videoDuration = 315.061; + const groups = previewBar.createChapterRenderGroups([ + { + "category": "chapter", + "segment": [ + 0, + 49.977 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "b1919787a85cd422af07136a913830eda1364d32e8a9e12104cf5e3bad8f6f45", + "description": "Start of video" + }, + { + "category": "sponsor", + "segment": [ + 2.926, + 5 + ], + "locked": 1, + "votes": 2, + "videoDuration": 316, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "chapter", + "segment": [ + 14.487, + 37.133 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "b1919787a85cd422af07136a913830eda1364d32e8a9e12104cf5e3bad8f6f45", + "description": "Subset of start" + }, + { + "category": "sponsor", + "segment": [ + 23.450537, + 34.486084 + ], + "locked": 0, + "votes": -1, + "videoDuration": 315.061, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "interaction", + "segment": [ + 50.015343, + 56.775314 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "b2a85e8cdfbf21dd504babbcaca7f751b55a5a2df8179c1a83a121d0f5d56c0e", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 62.51888, + 74.33331 + ], + "locked": 0, + "votes": -1, + "videoDuration": 316, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 88.71328, + 96.05933 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "6c08c092db2b7a31210717cc1f2652e7e97d032e03c82b029a27c81cead1f90c", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 101.50703, + 115.088326 + ], + "votes": 0, + "videoDuration": 315.061, + "userID": "2db207ad4b7a535a548fab293f4567bf97373997e67aadb47df8f91b673f6e53", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 122.211845, + 137.42178 + ], + "locked": 0, + "votes": 1, + "videoDuration": 0, + "userID": "0312cbfa514d9d2dfb737816dc45f52aba7c23f0a3f0911273a6993b2cb57fcc", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 144.08913, + 160.14084 + ], + "locked": 0, + "votes": -1, + "videoDuration": 316, + "userID": "938444fccfdb7272fd871b7f98c27ea9e5e806681db421bb69452e6a42730c20", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 164.22084, + 170.98082 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "845c4377060d5801f5324f8e1be1e8373bfd9addcf6c68fc5a3c038111b506a3", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 180.56674, + 189.16516 + ], + "locked": 0, + "votes": -1, + "videoDuration": 315.061, + "userID": "7c6b015687db7800c05756a0fd226fd8d101f5a1e1bfb1e5d97c440331fd6cb7", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 204.10468, + 211.87865 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "3472e8ee00b5da957377ae32d59ddd3095c2b634c2c0c970dfabfb81d143699f", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 214.92064, + 222.0186 + ], + "locked": 0, + "votes": 0, + "videoDuration": 0, + "userID": "62a00dffb344d27de7adf8ea32801c2fc0580087dc8d282837923e4bda6a1745", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 233.0754, + 244.56734 + ], + "locked": 0, + "votes": -1, + "videoDuration": 315, + "userID": "dcf7fb0a6c071d5a93273ebcbecaee566e0ff98181057a36ed855e9b92bf25ea", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 260.64053, + 269.35938 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "e0238059ae4e711567af5b08a3afecfe45601c995b0ea2f37ca43f15fca4e298", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 288.686, + 301.96 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "e0238059ae4e711567af5b08a3afecfe45601c995b0ea2f37ca43f15fca4e298", + "description": "" + }, + { + "category": "sponsor", + "segment": [ + 288.686, + 295 + ], + "locked": 0, + "votes": 0, + "videoDuration": 315.061, + "userID": "e0238059ae4e711567af5b08a3afecfe45601c995b0ea2f37ca43f15fca4e298", + "description": "" + }] as unknown as PreviewBarSegment[]); + + expect(groups).toStrictEqual([ + { + "segment": [ + 0, + 2.926 + ], + "originalDuration": 49.977 + }, + { + "segment": [ + 2.926, + 5 + ], + "originalDuration": 2.074 + }, + { + "segment": [ + 5, + 14.487 + ], + "originalDuration": 49.977 + }, + { + "segment": [ + 14.487, + 23.450537 + ], + "originalDuration": 22.646 + }, + { + "segment": [ + 23.450537, + 34.486084 + ], + "originalDuration": 11.035546999999998 + }, + { + "segment": [ + 34.486084, + 37.133 + ], + "originalDuration": 22.646 + }, + { + "segment": [ + 37.133, + 49.977 + ], + "originalDuration": 49.977 + }, + { + "segment": [ + 49.977, + 50.015343 + ], + "originalDuration": 0 + }, + { + "segment": [ + 50.015343, + 56.775314 + ], + "originalDuration": 6.759971 + }, + { + "segment": [ + 56.775314, + 62.51888 + ], + "originalDuration": 0 + }, + { + "segment": [ + 62.51888, + 74.33331 + ], + "originalDuration": 11.814429999999994 + }, + { + "segment": [ + 74.33331, + 88.71328 + ], + "originalDuration": 0 + }, + { + "segment": [ + 88.71328, + 96.05933 + ], + "originalDuration": 7.346050000000005 + }, + { + "segment": [ + 96.05933, + 101.50703 + ], + "originalDuration": 0 + }, + { + "segment": [ + 101.50703, + 115.088326 + ], + "originalDuration": 13.581295999999995 + }, + { + "segment": [ + 115.088326, + 122.211845 + ], + "originalDuration": 0 + }, + { + "segment": [ + 122.211845, + 137.42178 + ], + "originalDuration": 15.209935000000016 + }, + { + "segment": [ + 137.42178, + 144.08913 + ], + "originalDuration": 0 + }, + { + "segment": [ + 144.08913, + 160.14084 + ], + "originalDuration": 16.051709999999986 + }, + { + "segment": [ + 160.14084, + 164.22084 + ], + "originalDuration": 0 + }, + { + "segment": [ + 164.22084, + 170.98082 + ], + "originalDuration": 6.759979999999985 + }, + { + "segment": [ + 170.98082, + 180.56674 + ], + "originalDuration": 0 + }, + { + "segment": [ + 180.56674, + 189.16516 + ], + "originalDuration": 8.598419999999976 + }, + { + "segment": [ + 189.16516, + 204.10468 + ], + "originalDuration": 0 + }, + { + "segment": [ + 204.10468, + 211.87865 + ], + "originalDuration": 7.773969999999991 + }, + { + "segment": [ + 211.87865, + 214.92064 + ], + "originalDuration": 0 + }, + { + "segment": [ + 214.92064, + 222.0186 + ], + "originalDuration": 7.0979600000000005 + }, + { + "segment": [ + 222.0186, + 233.0754 + ], + "originalDuration": 0 + }, + { + "segment": [ + 233.0754, + 244.56734 + ], + "originalDuration": 11.49194 + }, + { + "segment": [ + 244.56734, + 260.64053 + ], + "originalDuration": 0 + }, + { + "segment": [ + 260.64053, + 269.35938 + ], + "originalDuration": 8.718849999999975 + }, + { + "segment": [ + 269.35938, + 288.686 + ], + "originalDuration": 0 + }, + { + "segment": [ + 288.686, + 295 + ], + "originalDuration": 6.314000000000021 + }, + { + "segment": [ + 295, + 301.96 + ], + "originalDuration": 13.274000000000001 + }, + { + "segment": [ + 301.96, + 315.061 + ], + "originalDuration": 0 + } + ]); + }) +})
\ No newline at end of file |