-
Notifications
You must be signed in to change notification settings - Fork 34
/
youtube-subtitle.user.js
145 lines (134 loc) · 4.21 KB
/
youtube-subtitle.user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// ==UserScript==
// @name youtube-subtitle
// @namespace youtube-subtitle.xinggsf
// @description Set subtitle of youtube
// @description-zh 设置油管的字幕,从而显示双字幕
// @version 0.0.1
// @homepage http://bbs.kafan.cn/thread-2093014-1-1.html
// @include https://www.youtube.com/watch?v=*
// @grant unsafeWindow
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @updateURL https://raw.githubusercontent.com/xinggsf/gm/master/youtube-subtitle.user.js
// ==/UserScript==
'use strict';
if (!NodeList.prototype[Symbol.iterator]) NodeList.prototype[Symbol.iterator] = HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
class Subtitle{
constructor() {
this.lang = GM_getValue('language', '') || navigator.language || navigator.browserLanguage;
let a = this.lang.split('-');
if (a[1]) {//IE: browserLanguage == 'zh-cn'
a[1] = a[1].toUpperCase();
this.lang = a.join('-');
}
}
run() {
const t = setInterval(() => {
if (this.checkVar()) {
clearInterval(t);
if (this.getSubtitleUrl()) {
this.fetchSubtitle();
GM_addStyle(
`<track kind="subtitles" label="${i}" src="${url}" srclang="${lang}" default>`
`::cue {
color: #ACF;
background: transparent;
text-shadow: black 0 0 0.2em;
font-size: 1.1em;
}`
)
}
}
}, 300);
}
// 检查YT变量
checkVar() {
let p = unsafeWindow.ytplayer;
if (p && p.config && p.config.args) {
p = p.config.args;
this.vid = p.vid;
this.title = p.title;
p = JSON.parse(p.player_response);
this.tracks = p.captions && p.captions.playerCaptionsTracklistRenderer.captionTracks;
}
}
getSubtitleUrl() {
// firstLang = 'zh-cn', secondLang = 'en'
if (!this.tracks) return !1;//无字幕
let lang = language_code.split('-', 1)[0];
let a = this.tracks.filter(k => k.languageCode === this.lang);
if (a.length == 1) return a[0].baseUrl;
if (a.length > 1) return a.find(k => 'asr' !== k.kind).baseUrl;
if (language_code !== lang) {
let res = this.tracks.find(k => k.languageCode.startsWith(lang));
if (res) return res.baseUrl;
}
let a = this.tracks.filter(k => k.isTranslatable);
if (!a.length) return;//无可翻译字幕
if (lang == 'zh') lang = language_code === 'zh-cn' ? 'zh-Hans': 'zh-Hant';
else lang = language_code;
if (a.length > 1) {
a = a.filter(k => 'asr' !== k.kind);// 排除自动生成的字幕
return a[0].baseURL + "&tlang=" + lang;
}
for (let caption of this.tracks) {
if (caption.isTranslatable && caption.languageCode === language_code) {
return caption.baseUrl + "&tlang=zh-Hans"; // 转成简中字幕。 zh-Hant 繁体中文
}
}
return !1;
}
downloadSubtitle() {
fetch(this.getSubtitleUrl())
.then(r => r.text())
.then(r => {
const xml = new DOMParser().parseFromString(r, 'text/xml');
let a = ['WEBVTT'],
start_time, start, end_time, end, content;
// 保存结果的字符串
for (let p of xml.getElementsByTagName('p')) {
content = p.textContent;
start = p.getAttribute('t');
start_time = this.processTime(start);
end = ~~start + ~~p.getAttribute('d');
if (!end) end = start + 3000;
end_time = this.processTime(end);
// ==== 开始处理数据 ====
// 标准srt时间轴: 00:00:01,850 --> 00:00:02,720
a.push(`\n\n${start_time} --> ${end_time}\n`);
// 加字幕内容
a.push(content);
}
let url = URL.createObjectURL(new Blob(a), {'type': 'text/vtt'});
let title = get_file_name(language_name_1c7);
downloadFile(title, result);
// 下载
}).catch(()=>{
alert("Error: No response from server.");
});
}
// 处理时间. 比如 start="671.33" start="37.64" start="12" start="23.029"
// 处理成 srt 时间, 比如 00:00:00,090 00:00:08,460 00:10:29,350
processTime(t) {
//let [s, ms] = t.toFixed(3).split('.');
let ms, s, m = 0, h = 0;
if ('string' != typeof t) {
ms = t % 1000;
s = t / 1000 | 0;
} else {
ms = t.slice(-3);
s = t.slice(0, -3) | 0;
}
if (s >= 60) {
m = s / 60 | 0;
s %= 60;
h = m / 60 | 0;
m %= 60;
}
if (h < 10) h = '0' + h;
if (m < 10) m = '0' + m;
if (s < 10) s = '0' + s;
return `${h}:${m}:${s}.${ms}`;
}
}