diff --git a/src/renderer/components/ft-list-video/ft-list-video.js b/src/renderer/components/ft-list-video/ft-list-video.js index b7bc0afd1ab0a..13d4792692085 100644 --- a/src/renderer/components/ft-list-video/ft-list-video.js +++ b/src/renderer/components/ft-list-video/ft-list-video.js @@ -10,6 +10,7 @@ import { toLocalePublicationString, toDistractionFreeTitle } from '../../helpers/utils' +import { deArrowData } from '../../helpers/sponsorblock' export default defineComponent({ name: 'FtListVideo', @@ -320,6 +321,14 @@ export default defineComponent({ currentLocale: function () { return this.$i18n.locale.replace('_', '-') }, + + useDeArrowTitles: function () { + return this.$store.getters.getUseDeArrowTitles + }, + + deArrowCache: function () { + return this.$store.getters.getDeArrowCache(this.id) + } }, watch: { historyIndex() { @@ -331,6 +340,25 @@ export default defineComponent({ this.checkIfWatched() }, methods: { + getDeArrowDataEntry: async function() { + // Read from local cache or remote + // Write to cache if read from remote + if (!this.useDeArrowTitles) { return null } + + if (this.deArrowCache) { return this.deArrowCache } + + const videoId = this.id + const data = await deArrowData(this.id) + const cacheData = { videoId, title: null } + if (Array.isArray(data?.titles) && data.titles.length > 0 && (data.titles[0].locked || data.titles[0].votes > 0)) { + cacheData.title = data.titles[0].title + } + + // Save data to cache whether data available or not to prevent duplicate requests + this.$store.commit('addVideoToDeArrowCache', cacheData) + return cacheData + }, + handleExternalPlayer: function () { this.$emit('pause-player') @@ -401,9 +429,9 @@ export default defineComponent({ } }, - parseVideoData: function () { + parseVideoData: async function () { this.id = this.data.videoId - this.title = this.data.title + this.title = (await this.getDeArrowDataEntry())?.title ?? this.data.title // this.thumbnail = this.data.videoThumbnails[4].url this.channelName = this.data.author ?? null diff --git a/src/renderer/components/sponsor-block-settings/sponsor-block-settings.js b/src/renderer/components/sponsor-block-settings/sponsor-block-settings.js index af1fd5abf8ea5..4455e84208be6 100644 --- a/src/renderer/components/sponsor-block-settings/sponsor-block-settings.js +++ b/src/renderer/components/sponsor-block-settings/sponsor-block-settings.js @@ -38,6 +38,10 @@ export default defineComponent({ }, sponsorBlockShowSkippedToast: function () { return this.$store.getters.getSponsorBlockShowSkippedToast + }, + + useDeArrowTitles: function () { + return this.$store.getters.getUseDeArrowTitles } }, methods: { @@ -45,6 +49,10 @@ export default defineComponent({ this.updateUseSponsorBlock(value) }, + handleUpdateUseDeArrowTitles: function (value) { + this.updateUseDeArrowTitles(value) + }, + handleUpdateSponsorBlockUrl: function (value) { const sponsorBlockUrlWithoutTrailingSlash = value.replace(/\/$/, '') const sponsorBlockUrlWithoutApiSuffix = sponsorBlockUrlWithoutTrailingSlash.replace(/\/api$/, '') @@ -58,7 +66,8 @@ export default defineComponent({ ...mapActions([ 'updateUseSponsorBlock', 'updateSponsorBlockUrl', - 'updateSponsorBlockShowSkippedToast' + 'updateSponsorBlockShowSkippedToast', + 'updateUseDeArrowTitles' ]) } }) diff --git a/src/renderer/components/sponsor-block-settings/sponsor-block-settings.vue b/src/renderer/components/sponsor-block-settings/sponsor-block-settings.vue index abda76eebd13e..44de745c5baaf 100644 --- a/src/renderer/components/sponsor-block-settings/sponsor-block-settings.vue +++ b/src/renderer/components/sponsor-block-settings/sponsor-block-settings.vue @@ -8,11 +8,20 @@ :default-value="useSponsorBlock" @change="handleUpdateSponsorBlock" /> +
- + - + byte.toString(16).padStart(2, '0')) .slice(0, 4) .join('') - +} +export async function sponsorBlockSkipSegments(videoId, categories) { + const videoIdHashPrefix = await getVideoHash(videoId) const requestUrl = `${store.getters.getSponsorBlockUrl}/api/skipSegments/${videoIdHashPrefix}?categories=${JSON.stringify(categories)}` try { @@ -30,3 +31,23 @@ export async function sponsorBlockSkipSegments(videoId, categories) { throw error } } + +export async function deArrowData(videoId) { + const videoIdHashPrefix = (await getVideoHash(videoId)).substring(0, 4) + const requestUrl = `${store.getters.getSponsorBlockUrl}/api/branding/${videoIdHashPrefix}` + + try { + const response = await fetch(requestUrl) + + // 404 means that there are no segments registered for the video + if (response.status === 404) { + return undefined + } + + const json = await response.json() + return json[videoId] ?? undefined + } catch (error) { + console.error('failed to fetch DeArrow data', requestUrl, error) + throw error + } +} diff --git a/src/renderer/store/modules/settings.js b/src/renderer/store/modules/settings.js index c156eaddd6ac9..ba47b6f0eac72 100644 --- a/src/renderer/store/modules/settings.js +++ b/src/renderer/store/modules/settings.js @@ -286,6 +286,7 @@ const state = { settingsPassword: '', allowDashAv1Formats: false, commentAutoLoadEnabled: false, + useDeArrowTitles: false, } const stateWithSideEffects = { diff --git a/src/renderer/store/modules/utils.js b/src/renderer/store/modules/utils.js index 129a042a4b264..b3b9ccb8bd103 100644 --- a/src/renderer/store/modules/utils.js +++ b/src/renderer/store/modules/utils.js @@ -27,6 +27,7 @@ const state = { movies: null }, cachedPlaylist: null, + deArrowCache: {}, showProgressBar: false, progressBarPercentage: 0, regionNames: [], @@ -57,6 +58,10 @@ const getters = { return state.sessionSearchHistory }, + getDeArrowCache: (state) => (videoId) => { + return state.deArrowCache[videoId] + }, + getPopularCache () { return state.popularCache }, @@ -611,6 +616,18 @@ const mutations = { state.sessionSearchHistory = history }, + setDeArrowCache (state, cache) { + state.deArrowCache = cache + }, + + addVideoToDeArrowCache (state, payload) { + const sameVideo = state.deArrowCache[payload.videoId] + + if (!sameVideo) { + state.deArrowCache[payload.videoId] = payload + } + }, + addToSessionSearchHistory (state, payload) { const sameSearch = state.sessionSearchHistory.findIndex((search) => { return search.query === payload.query && searchFiltersMatch(payload.searchSettings, search.searchSettings) diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml index 305cb8e3e88ae..33f7bc84d2db9 100644 --- a/static/locales/en-US.yaml +++ b/static/locales/en-US.yaml @@ -410,6 +410,7 @@ Settings: Enable SponsorBlock: Enable SponsorBlock 'SponsorBlock API Url (Default is https://sponsor.ajay.app)': SponsorBlock API Url (Default is https://sponsor.ajay.app) Notify when sponsor segment is skipped: Notify when sponsor segment is skipped + UseDeArrowTitles: Use DeArrow Video Titles Skip Options: Skip Option: Skip Option Auto Skip: Auto Skip @@ -841,6 +842,8 @@ Tooltips: when the watch page is closed. Experimental Settings: Replace HTTP Cache: Disables Electron's disk based HTTP cache and enables a custom in-memory image cache. Will lead to increased RAM usage. + SponsorBlock Settings: + UseDeArrowTitles: Replace video titles with user-submitted titles from DeArrow. # Toast Messages Local API Error (Click to copy): Local API Error (Click to copy)