Skip to content

Commit e103543

Browse files
committed
add netflix
1 parent 84716d0 commit e103543

File tree

1 file changed

+98
-129
lines changed

1 file changed

+98
-129
lines changed

src/subtitle/netflix.js

Lines changed: 98 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import BaseVideo from "./baseVideo";
22
import $ from "jquery";
33

4-
5-
64
// https://github.com/mikesteele/dual-captions/blob/b0ab92e4670100a27b76b2796995ad1be89f1672/site_integrations/netflix/index.js
75
// https://stackoverflow.com/questions/42105028/netflix-video-player-in-chrome-how-to-seek
86

@@ -16,83 +14,76 @@ export default class Netflix extends BaseVideo {
1614
static captionContainerSelector = "";
1715
static captionWindowSelector = "";
1816
static captionBoxSelector = "";
19-
static sub={};
17+
static sub = {};
2018

2119
static #injectScriptConstructor = (() => {
22-
console.log("Netflix inject script constructor");
23-
2420
this.listenMessageFrameFromInject();
2521
})();
26-
static async listenPlayer() {
27-
28-
}
22+
static async listenPlayer() {}
2923
static async handleUrlChange(url = window.location.href) {
30-
console.log("Netflix handle url change");
3124
this.pausedByExtension = false;
3225
this.callMethodFromInject("activateCaption", url);
3326
}
3427
static async activateCaption(url) {
35-
console.log("activateCaption------------------00")
36-
console.log("toggle",this.setting["subtitleButtonToggle"])
37-
console.log("url",url)
38-
console.log("isVideoUrl",this.isVideoUrl(url))
3928
// skip if user caption off, is shorts skip
40-
if (
41-
// this.setting["subtitleButtonToggle"] == "false" ||
42-
!this.isVideoUrl(url)
43-
) {
29+
if (!this.isVideoUrl(url)) {
4430
return;
4531
}
46-
console.log("activateCaption------------------0")
4732
await this.waitPlayerReady(); //wait player load
4833
// get video lang
49-
var lang= await this.guessVideoLang();
50-
console.log("activateCaption------------------1")
34+
var lang = await this.guessVideoLang();
5135
this.killInterceptDebounce(); // end caption intercept
52-
console.log("activateCaption------------------2")
5336
await this.interceptCaption(); // start caption intercept
54-
console.log("activateCaption------------------3")
5537
await this.waitUntilForever(() => this.getVideoId());
56-
console.log("activateCaption------------------4")
57-
var videoId = this.getVideoId();
58-
this.requestTrack(lang,videoId); //turn on caption on specified lang
59-
38+
39+
if (this.checkPlayerCaptionOff()) {
40+
console.log("caption is off");
41+
} else {
42+
console.log(this.getPlayer().getTextTrack())
43+
var videoId = this.getVideoId();
44+
this.requestTrack(lang, videoId); //turn on caption on specified lang
45+
}
6046
}
61-
static async isVideoUrl(url) {
47+
static async isVideoUrl(url) {
6248
return url.includes(`${this.baseUrl}/watch`);
6349
}
6450
static async waitPlayerReady() {
6551
await this.waitUntilForever(() => this.getPlayer());
6652
var player = this.getPlayer();
6753
await this.waitUntilForever(() => player.getAudioTrack());
6854
}
69-
static setPlayerCaption(lang) {
55+
static setPlayerCaption(lang) {
7056
this.getPlayer().setTimedTextTrack(lang);
7157
}
72-
static setPlayerCaptionOff(){
73-
var offTrack = this.getPlayer().getTimedTextTrackList()[0];
58+
static setPlayerCaptionOff() {
59+
var offTrack = this.getPlayer()
60+
.getTimedTextTrackList()
61+
.find((track) => track.trackId.includes("NONE"));
7462
this.getPlayer().setTimedTextTrack(offTrack);
7563
}
64+
static checkPlayerCaptionOff() {
65+
const textTrackList = this.getPlayer().getTimedTextTrackList();
66+
var currentTrack = this.getPlayer().getTextTrack();
67+
return !currentTrack || currentTrack.trackId === textTrackList[0]?.trackId;
68+
}
7669

7770
static getPlayer() {
78-
var videoPlayer = window?.netflix?.appContext
79-
?.state
80-
?.playerApp
81-
?.getAPI()
82-
.videoPlayer
83-
var playerSessionId = videoPlayer?.getAllPlayerSessionIds()
84-
.find(s => s.includes("watch"));
85-
var player = videoPlayer?.getVideoPlayerBySessionId(playerSessionId)
86-
return player
71+
var videoPlayer =
72+
window?.netflix?.appContext?.state?.playerApp?.getAPI().videoPlayer;
73+
var playerSessionId = videoPlayer
74+
?.getAllPlayerSessionIds()
75+
.find((s) => s.includes("watch"));
76+
var player = videoPlayer?.getVideoPlayerBySessionId(playerSessionId);
77+
return player;
8778
}
8879
static getVideoId() {
8980
return String(this.getPlayer().getMovieId());
9081
}
9182
static async guessVideoLang() {
92-
return this.getPlayer()?.getAudioTrack()?.bcp47
83+
return this.getPlayer()?.getAudioTrack()?.bcp47;
9384
}
9485
static async guessSubtitleLang(url, subtitle) {
95-
return this.getPlayer()?.getTimedTextTrack()?.bcp47
86+
return this.getPlayer()?.getTimedTextTrack()?.bcp47;
9687
}
9788

9889
static async interceptCaption() {
@@ -108,134 +99,127 @@ export default class Netflix extends BaseVideo {
10899
var response = await this.requestSubtitle(request.url);
109100
var targetLang = this.setting["translateTarget"];
110101
var videoId = this.getVideoId();
111-
console.log("videoId",videoId);
112-
console.log("request.url",request.url);
113-
var xml= await response.text()
114-
var sub1 = this.parseSubtitle(xml,videoId);
102+
var xml = await response.text();
103+
var sub1 = this.parseSubtitle(xml, videoId);
115104
var responseSub = sub1;
116-
117-
118105

119-
if(sub1.lang != targetLang){
106+
if (sub1.lang != targetLang) {
120107
var sub2 = await this.requestSubtitleWithReset(targetLang);
121108
var mergedSub = this.mergeSubtitles(sub1, sub2);
122109
responseSub = mergedSub;
123-
console.log("merged@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
124-
console.log(mergedSub)
125-
}
126-
var xmlRes=this.encodeMergedSubtitles(responseSub);
127-
128-
129-
request.respondWith(
130-
new Response(xmlRes)
131-
);
110+
}
111+
var xmlRes = this.encodeMergedSubtitles(responseSub);
112+
request.respondWith(new Response(xmlRes));
132113
}
133114
} catch (error) {
134115
console.log(error);
135116
}
136117
});
137118
}
138-
139119

140120
static async requestSubtitle(subUrl, lang, tlang, videoId) {
141-
var res = await fetch(subUrl);
142-
return res;
121+
var res = await fetch(subUrl);
122+
return res;
143123
}
144-
static async requestSubtitleWithReset(lang,videoId) {
145-
var player= this.getPlayer()
146-
var prevSub=player.getTimedTextTrack()
147-
var sub=await this.requestTrack(lang,videoId)
148-
player.setTextTrack(prevSub)
149-
return sub
124+
static async requestSubtitleWithReset(lang, videoId) {
125+
var player = this.getPlayer();
126+
var prevSub = player.getTimedTextTrack();
127+
var sub = await this.requestTrack(lang, videoId);
128+
player.setTextTrack(prevSub);
129+
return sub;
150130
}
151-
static async requestTrack(lang,videoId) {
131+
static async requestTrack(lang, videoId) {
152132
var videoId = videoId || this.getVideoId();
153-
console.log("video43",videoId)
154-
155133
if (this.sub?.[videoId]?.[lang]) {
156-
console.log("requestTrackExist",lang);
157134
return this.sub?.[videoId]?.[lang];
158135
}
159-
160-
console.log("requestTracksssssssssssssssssssss",lang);
161-
console.log(player);
162-
var player= this.getPlayer()
163-
var subList=player.getTimedTextTrackList()
136+
var player = this.getPlayer();
137+
var subList = player.getTimedTextTrackList();
164138
const selectedTimedTextTrack = subList
165-
.sort((a, b) => (a.trackType === 'ASSISTIVE' ? -1 : 1))
139+
.sort((a, b) => (a.trackType === "ASSISTIVE" ? -1 : 1))
140+
.filter((textTrack) => textTrack.isImageBased=== false)
166141
.find((textTrack) => textTrack.bcp47 === lang);
167142

168143
if (!selectedTimedTextTrack) {
169144
return null;
170145
}
171146

172-
player.setTextTrack(selectedTimedTextTrack)
173-
console.log("reqq")
147+
player.setTextTrack(selectedTimedTextTrack);
174148
await this.waitUntilForever(() => {
175-
var a=this.sub?.[videoId]?.[lang]
176-
console.log("requestTrackEnd2",a);
177-
console.log(videoId)
178-
return a
149+
return this.sub?.[videoId]?.[lang];
179150
});
180-
console.log("requestTrackEnd",this.sub?.[videoId]?.[lang]);
181151
return this.sub?.[videoId]?.[lang];
182-
183152
}
184153
static extractVideoId(url) {
185154
const match = url.match(/\/watch\/(\d+)/);
186155
return match ? match[1] : null;
187-
}
156+
}
188157

189-
static parseSubtitle(sub,videoId) {
158+
static parseSubtitle(sub, videoId) {
190159
const concatSubtitles = [];
191160
const parser = new DOMParser();
192161
const xmlDoc = parser.parseFromString(sub, "text/xml");
193-
const subtitles = Array.from(xmlDoc.getElementsByTagName("p")).map(p => p.cloneNode(true));
162+
const subtitles = Array.from(xmlDoc.getElementsByTagName("p")).map((p) =>
163+
p.cloneNode(true)
164+
);
194165
var lang = xmlDoc.documentElement.getAttribute("xml:lang");
195166
// remove div tag
196167
const div = xmlDoc.querySelector("div");
197168
div?.parentNode?.removeChild(div);
198169

170+
// Extract all regions and update their IDs to start with the language code
171+
const layout = xmlDoc.getElementsByTagName("layout")[0];
172+
if (layout) {
173+
var regions = Array.from(layout.getElementsByTagName("region"));
174+
regions.forEach((region, index) => {
175+
const newId = `region${index}_${lang}`;
176+
region.setAttribute("xml:id", newId);
177+
});
178+
}
179+
199180
// parse subtitles
200181
for (let i = 0; i < subtitles.length; i++) {
201182
const subtitle = subtitles[i];
202183
const start = parseInt(subtitle.getAttribute("begin").replace("t", ""));
203184
const end = parseInt(subtitle.getAttribute("end").replace("t", ""));
204185
const text = subtitle.textContent;
205-
186+
const region = subtitle.getAttribute("region") + "_" + lang;
206187
var prev = concatSubtitles?.[concatSubtitles.length - 1];
207188
if (prev && prev.start === start && prev.end === end) {
208189
prev.text += " " + text;
209190
} else {
210-
concatSubtitles.push({ start, end, text });
191+
concatSubtitles.push({ start, end, text, region });
211192
}
212193
}
213-
214194

215-
216-
217-
var parsedSubMeta= {
195+
var parsedSubMeta = {
218196
xmlDoc,
219197
lang,
220198
subtitles: concatSubtitles,
221-
}
199+
regions,
200+
};
222201
if (!this.sub[videoId]) {
223202
this.sub[videoId] = {};
224203
}
225-
this.sub[videoId][lang] = parsedSubMeta
226-
console.log(this.sub)
227-
return parsedSubMeta
204+
this.sub[videoId][lang] = parsedSubMeta;
205+
return parsedSubMeta;
228206
}
229207

230208
static mergeSubtitles(sub1Meta, sub2Meta) {
231-
var mergedSub=[]
232-
var sub1=sub1Meta.subtitles;
233-
var sub2=sub2Meta.subtitles;
234-
var mergedSubMeta={...sub1Meta}
209+
var mergedSub = [];
210+
var sub1 = sub1Meta.subtitles;
211+
var sub2 = sub2Meta.subtitles;
212+
var mergedSubMeta = { ...sub1Meta };
213+
214+
const layout = mergedSubMeta?.xmlDoc?.getElementsByTagName("layout")?.[0];
215+
216+
sub2Meta?.regions?.forEach((region) => {
217+
layout?.appendChild(region);
218+
});
235219

236220
// fix mismatch length between sub1 sub2
237221
for (let [i, sub1Line] of sub1.entries()) {
238-
var line1 = sub1Line
222+
var line1 = sub1Line;
239223
var line2 = "";
240224
// get most overlapped sub
241225
sub2.forEach((line) => {
@@ -247,40 +231,38 @@ export default class Netflix extends BaseVideo {
247231
sub2.sort((a, b) => a.overlap - b.overlap);
248232

249233
// if sub2 has no overlap, use sub1
250-
mergedSub.push(line1)
234+
mergedSub.push(line1);
251235
if (sub2.length && 0 < sub2[0].overlap) {
252236
line2 = sub2[0];
253-
let line1Copy = { ...line1 }
254-
line1Copy.text=line2.text;
255-
mergedSub.push(line1Copy)
237+
let line1Copy = { ...line1 };
238+
line1Copy.text = line2.text;
239+
mergedSub.push(line1Copy);
256240
}
257241
}
258-
242+
259243
mergedSubMeta.subtitles = mergedSub;
260244
return mergedSubMeta;
261245
}
262246

263-
264247
static encodeMergedSubtitles(subMeta) {
265-
var xmlDoc= subMeta.xmlDoc
266-
var subtitles = subMeta.subtitles
248+
var xmlDoc = subMeta.xmlDoc;
249+
var subtitles = subMeta.subtitles;
267250
const body = xmlDoc.getElementsByTagName("body")[0];
268251
let div = body.getElementsByTagName("div")[0];
269252
if (!div) {
270253
div = xmlDoc.createElement("div");
271254
body.appendChild(div);
272255
}
273256

274-
275-
subtitles.forEach(sub => {
257+
subtitles.forEach((sub) => {
276258
const p = xmlDoc.createElement("p");
277259
p.setAttribute("xml:id", `subtitle${subtitles.indexOf(sub) + 1}`);
278260
p.setAttribute("begin", `${sub.start}t`);
279261
p.setAttribute("end", `${sub.end}t`);
280-
p.setAttribute("region", "region0");
262+
p.setAttribute("region", sub.region);
281263

282264
const span = xmlDoc.createElement("span");
283-
span.setAttribute("style", "style1");
265+
span.setAttribute("style", "style0");
284266
span.textContent = sub.text;
285267

286268
p.appendChild(span);
@@ -293,18 +275,5 @@ export default class Netflix extends BaseVideo {
293275

294276
static encodeSubtitle(subtitles) {
295277
return subtitles;
296-
}
297-
}
298-
// request track
299-
// parse
300-
// lang video id current
301-
// video audio and get lang
302-
// video lang to request audio
303-
//
304-
//
305-
// no tittle or only one title
306-
// current lang and off
307-
// target lang and off
308-
// current lang and source lang
309-
// current lang and target lang
310-
278+
}
279+
}

0 commit comments

Comments
 (0)