Skip to content

Commit

Permalink
Support subtitle autodetection on NRK TV
Browse files Browse the repository at this point in the history
  • Loading branch information
killergerbah committed Jul 26, 2024
1 parent e5f9dc8 commit 96a74cb
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 2 deletions.
1 change: 1 addition & 0 deletions extension/src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"pages/emby-page.js",
"pages/osnplus-page.js",
"pages/bilibili-page.js",
"pages/nrk-tv-page.js",
"anki-ui.js",
"mp3-encoder-worker.js",
"pgs-parser-worker.js",
Expand Down
8 changes: 8 additions & 0 deletions extension/src/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@
"autoSync": {
"enabled": true
}
},
{
"host": "tv.nrk.no",
"script": "nrk-tv-page.js",
"path": ".*",
"autoSync": {
"enabled": true
}
}
]
}
4 changes: 2 additions & 2 deletions extension/src/pages/bandai-channel-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ inferTracks({
setBasename(value.bch.episode_title);
}
},
onRequest: (addTrack, setBasename) => {
const succeeded = poll(() => {
onRequest: async (addTrack, setBasename) => {
const succeeded = await poll(() => {
const basename = basenameFromDOM();

if (basename) {
Expand Down
85 changes: 85 additions & 0 deletions extension/src/pages/nrk-tv-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { inferTracks } from './util';

const originalFetch = window.fetch;
let lastMetadataUrl: string | undefined;

window.fetch = (...args) => {
let metadataUrl = undefined;

for (const arg of args) {
if (typeof arg === 'string' && arg.includes('metadata')) {
metadataUrl = arg;
}
if (arg instanceof Request && arg.url.includes('metadata')) {
metadataUrl = arg.url;
}
}

if (metadataUrl !== undefined) {
lastMetadataUrl = metadataUrl;
}

return originalFetch(...args);
};

const requestTracks = async (url: string) => {
const tracks = [];

try {
const value = await (await fetch(url)).json();

if (typeof value?.playable?.subtitles === 'object' && Array.isArray(value.playable.subtitles)) {
for (const track of value.playable.subtitles) {
if (
typeof track.label === 'string' &&
typeof track.language === 'string' &&
typeof track.webVtt === 'string'
) {
tracks.push({
label: track.label as string,
language: track.language as string,
url: track.webVtt as string,
extension: 'vtt',
});
}
}
}
} catch (e) {
console.error(e);
}

return tracks;
};

inferTracks({
onRequest: async (addTrack, setBasename) => {
if (lastMetadataUrl === undefined) {
return;
}

const manifestUrl = new URL(lastMetadataUrl);
const value = await (await fetch(lastMetadataUrl)).json();

if (typeof value?.preplay?.titles?.title === 'string') {
if (typeof value?.preplay?.titles?.subtitle === 'string') {
setBasename(`${value.preplay.titles.title} ${value.preplay.titles.subtitle}`);
} else {
setBasename(value.preplay.titles.title);
}
}

if (typeof value?._links?.manifests === 'object' && Array.isArray(value._links.manifests)) {
for (const manifest of value._links.manifests) {
if (typeof manifest.href === 'string') {
const tracks = await requestTracks(`${manifestUrl.origin}${manifest.href}`);

for (const track of tracks) {
addTrack(track);
}
}
}
}
},

waitForBasename: true,
});
20 changes: 20 additions & 0 deletions extension/src/services/pages.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
import pagesConfig from '../pages.json';

interface PageConfig {
// Regex for URLs where script should be loaded
host: string;

// Script to load
script?: string;

// URL relative path regex where subtitle track data syncing is allowed
path?: string;

// URL hash segment regex where subtitle track data syncing is allowed
hash?: string;

// Whether shadow roots should be searched for video elements on this page
searchShadowRoots?: boolean;

// Whether video elements with blank src should be bindable on this page
allowBlankSrc?: boolean;

autoSync?: {
// Whether to attempt to load detected subtitles automatically
enabled: boolean;

// Video src string regex for video elemennts that should be considered for auto-syync
videoSrc?: string;

// Video element ID regex for video elements that should be considered for auto-sync
elementId?: string;
};

ignore?: {
// CSS classes that should cause video elements to be ignored for binding
class?: string;
// Styles that should cause video elements to be ignored for binding
style?: { [key: string]: string };
};
}
Expand Down

0 comments on commit 96a74cb

Please sign in to comment.