From 64cf7b54a08de679029aab703a7114c0c2449071 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Wed, 30 Nov 2022 19:41:07 -0500 Subject: [PATCH] Sponsorblock progressbar (#18) * Generate sponsorblock bar * Sponsorblock chapter label * Add comment * Fix typo * Changelog --- CHANGELOG.md | 1 + .../VideoPlayer/SponsorBlockTask.bs | 9 ++- src/components/VideoPlayer/Video.bs | 6 ++ src/components/VideoPlayer/VideoPlayer.bs | 78 +++++++++++++++++++ src/components/VideoPlayer/VideoPlayer.xml | 16 ++++ src/source/services/SponsorBlock.bs | 67 ++++++++++++++++ src/source/utils/TimeUtils.bs | 16 ++++ 7 files changed, 192 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c0625f..3f646eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Version check in settings page - Support for web request body parsing - Error dialog for video load fail +- SponsorBlock sections and category info ## [0.4.0] - 2022-11-24 ### Added diff --git a/src/components/VideoPlayer/SponsorBlockTask.bs b/src/components/VideoPlayer/SponsorBlockTask.bs index 8264a4d2..d941563d 100644 --- a/src/components/VideoPlayer/SponsorBlockTask.bs +++ b/src/components/VideoPlayer/SponsorBlockTask.bs @@ -9,10 +9,17 @@ function TaskMain() videoId = input.videoId skipSegments = SponsorBlock.GetSkipSegmentsForVideo(videoId) + barPath = invalid + + if skipSegments <> invalid + barPath = `tmp:/sponsorblock_bar_${videoId}.png` + SponsorBlock.GenerateProgressBarBackground(skipSegments, barPath) + end if m.top.setField("output", { videoId: videoId, - skipSegments: skipSegments + skipSegments: skipSegments, + barPath: barPath }) end function diff --git a/src/components/VideoPlayer/Video.bs b/src/components/VideoPlayer/Video.bs index 075675b4..c385e0eb 100644 --- a/src/components/VideoPlayer/Video.bs +++ b/src/components/VideoPlayer/Video.bs @@ -136,9 +136,15 @@ function StartSponsorBlockTask(videoId as string) } }).then(function(task as object) skipSegments = task.output.skipSegments + barPath = task.output.barPath if skipSegments <> invalid m.videoPlayer.addFields({ skipSegments: skipSegments }) m.videoPlayer.seekMode = "accurate" + if barPath <> invalid + m.videoPlayer.trickPlayBar.trackImageUri = barPath + m.videoPlayer.trickPlayBar.filledBarBlendColor = "0xFF000080" + end if + m.videoPlayer.ObserveField("position", "OnPositionChangeSkipSponsorBlockSections") end if end function) diff --git a/src/components/VideoPlayer/VideoPlayer.bs b/src/components/VideoPlayer/VideoPlayer.bs index 13cf35f2..de923ce6 100644 --- a/src/components/VideoPlayer/VideoPlayer.bs +++ b/src/components/VideoPlayer/VideoPlayer.bs @@ -1,4 +1,82 @@ +import "pkg:/source/utils/TimeUtils.bs" +import "pkg:/source/services/SponsorBlock.bs" + function Init() + SetChapterLabel() + m.timeLabel = FindTimeLabel() + m.top.trickPlayBar.observeField("visible", "OnTrickPlayBarVisible") + m.chapterLabelTimer = m.top.findNode("chapterLabelTimer") + m.chapterLabelTimer.observeField("fire", "OnChapterLabelTimer") +end function + +' This is a hack to access the position of the trickplay +function FindTimeLabel() as object + children = m.top.trickPlayBar.getChildren(m.top.trickPlayBar.getChildCount(), 0) + label = invalid + for each child in children + ' It's very unfortunate to have to find a label with 0:00 text, + ' and that's on the left side of the screen. This might need completely custom trickPlayBar + if child.isSubtype("Label") and child.text = "0:00" + if label = invalid + label = child + else + if child.translation[0] < label.translation[0] + label = child + end if + end if + end if + end for + return label +end function + +function OnTrickPlayBarVisible() + if m.top.trickPlayBar.visible + m.top.chapter = "" + m.chapterLabelTimer.control = "start" + else + m.chapterLabelTimer.control = "stop" + end if +end function + +function OnChapterLabelTimer() as void + if m.timeLabel = invalid or m.top.skipSegments = invalid + return + end if + + if m.sponsorBlockLastTime = m.timeLabel.text + return + end if + m.sponsorBlockLastTime = m.timeLabel.text + + time = TimeUtils.ParseTime(m.timeLabel.text) + UpdateSponsorBlockChapter(time) +end function + +function UpdateSponsorBlockChapter(time as integer) as void + segments = m.top.skipSegments + for each segment in segments + segmentRange = segment["segment"] + segmentStart = segmentRange[0] + segmentEnd = segmentRange[1] + + if (segmentStart <= time) and (segmentEnd >= time) + m.top.chapter = SponsorBlock.SegmentTitle(segment["category"]) + return + end if + end for + m.top.chapter = "" +end function + +function SetChapterLabel() + m.chapterLabel = m.top.findNode("chapterLabel") + m.chapterLabel.reparent(m.top.trickPlayBar, false) + trickPlayBarWidth = m.top.trickPlayBar.boundingRect().width + #if DASH_THUMBNAILS + yPos = 25 + #else + yPos = 55 + #end if + m.chapterLabel.translation = [trickPlayBarWidth / 2 - m.chapterLabel.width / 2, yPos] end function function OnkeyEvent(key as string, press as boolean) as boolean diff --git a/src/components/VideoPlayer/VideoPlayer.xml b/src/components/VideoPlayer/VideoPlayer.xml index 16065e3c..e78c4c43 100644 --- a/src/components/VideoPlayer/VideoPlayer.xml +++ b/src/components/VideoPlayer/VideoPlayer.xml @@ -3,5 +3,21 @@ + + + \ No newline at end of file diff --git a/src/source/services/SponsorBlock.bs b/src/source/services/SponsorBlock.bs index c271ce99..b82a5fc5 100644 --- a/src/source/services/SponsorBlock.bs +++ b/src/source/services/SponsorBlock.bs @@ -6,6 +6,52 @@ namespace SponsorBlock const API_URL = "https://sponsor.ajay.app" const SKIP_SEGMENT_ENDPOINT = "/api/skipSegments" + function SegmentColor(category as string) as integer + map = m.SponsorBlockColors + if map = invalid + map = { + sponsor: &h00D400FF, + selfpromo: &hFFFF00FF, + exclusive_access: &h008A5CFF, + interaction: &hCC00FFFF, + poi_highlight: &hFF1684FF, + intro: &h00FFFFFF, + outro: &h0202EDFF, + preview: &h008FD6FF, + filler: &h7300FFFF, + music_offtopic: &hFF9900FF + } + m.SponsorBlockColors = map + end if + if map.doesexist(category) + return map[category] + end if + return 0 + end function + + function SegmentTitle(category as string) as string + map = m.SponsorBlockTitles + if map = invalid + map = { + sponsor: "Sponsor", + selfpromo: "Unpaid/Self Promotion", + exclusive_access: "Exclusive Access", + interaction: "Interaction Reminder (Subscribe)", + poi_highlight: "Highlight", + intro: "Intermission/Intro Animation", + outro: "Endcards/Credits", + preview: "Preview/Recap", + filler: "Filler Tangent/Jokes", + music_offtopic: "Non-Music Section" + } + m.SponsorBlockTitles = map + end if + if map.doesexist(category) + return map[category] + end if + return "" + end function + function GetSkipSegmentsForVideo(videoId as string) as object categories = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "poi_highlight", "chapter", "filler", "exclusive_access"] actionTypes = ["skip", "mute", "chapter", "full", "poi"] @@ -29,4 +75,25 @@ namespace SponsorBlock return invalid end function + function GenerateProgressBarBackground(segments as object, path as string) + bar = CreateObject("roBitmap", { width: 1000, height: 20, AlphaEnable: true }) + bar.Clear(&hFFFFFF80) + width = bar.GetWidth() + height = bar.GetHeight() + for each segment in segments + pixelStart = (segment.segment[0] / segment.videoDuration) * width + pixelEnd = (segment.segment[1] / segment.videoDuration) * width + color = SponsorBlock.SegmentColor(segment.category) + ' highlight's duration is zero, so it is not visble on the bar. + ' Add a couple of pixels to see it + if pixelStart = pixelEnd + pixelEnd += 2 + end if + bar.DrawRect(pixelStart, 0, pixelEnd - pixelStart, height, color) + end for + bar.Finish() + buffer = bar.GetPng(0, 0, width, height) + buffer.WriteFile(path) + end function + end namespace diff --git a/src/source/utils/TimeUtils.bs b/src/source/utils/TimeUtils.bs index 7d52617f..76f9a439 100644 --- a/src/source/utils/TimeUtils.bs +++ b/src/source/utils/TimeUtils.bs @@ -45,4 +45,20 @@ namespace TimeUtils return validstr(ma[mNum - 1]) end function + function ParseTime(time as string) as integer + hours = 0 + minutes = 0 + seconds = 0 + tokens = time.Tokenize(":") + if tokens.Count() = 3 + hours = tokens[0].ToInt() + minutes = tokens[1].ToInt() + seconds = tokens[2].ToInt() + else if tokens.Count() = 2 + minutes = tokens[0].ToInt() + seconds = tokens[1].ToInt() + end if + return hours * 3600 + minutes * 60 + seconds + end function + end namespace