// ==UserScript==
// @name 在线视频外挂字幕
// @namespace https://truework.top
// @version 0.30
// @description 目前支持bilibili,优酷,爱奇艺和西瓜视频(腾讯视频的有bug暂不支持),按Q键+100ms,按W键-100ms,按E键显示/隐藏字幕,console可作为transcript使用
// @author cyj98
// @match https://www.bilibili.com/bangumi/*
// @match https://www.iqiyi.com/*
// @match https://www.ixigua.com/cinema/album/*
// @match https://v.youku.com/*
// @grant none
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @require https://greasyfork.org/scripts/373379-subtitle-utils-module/code/subtitle%20utils%20module.js?version=637875
// ==/UserScript==
(function () {
'use strict';
$("body").prepend('<button id="open-file">字幕</button>')
$("#open-file").click(() => {
let hostname = $(location).attr('hostname')
let subtitleSel, curTimeSel
if (hostname === 'www.bilibili.com') {
// '.bilibili-player-video-subtitle'
subtitleSel = '.subtitle-position'
curTimeSel = 'bilibili-player-video-time-now'
} else if (hostname === 'www.iqiyi.com') {
subtitleSel = '.iqp-subtitle'
curTimeSel = 'iqp-time-cur'
$(".iqp-subtitle").css({ "display": "block", "height": "50px" });
} else if (hostname === 'www.ixigua.com') {
subtitleSel = '.teleplay__playerContainer'
curTimeSel = 'xgplayer-time'
} else if (hostname === 'v.youku.com') {
subtitleSel = '.subtitle-container'
curTimeSel = 'control-time-current'
}
$('head').append('<style>' + `
#snackbar {
font-size: 16px;
visibility: hidden;
position: absolute;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
#snackbar.show {
visibility: visible;
}
` + '</style>');
$(subtitleSel).prepend('<div id="custom-subtitle" style="text-align:center; font-size: 32px; font-weight: bold; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;"> </div>')
$(subtitleSel).prepend('<div id="snackbar"></div>')
if (hostname === 'www.ixigua.com') {
$('#snackbar').css({ "position": "absolute", "z-index": '1', "color": "white", "bottom": "56px" })
$("#custom-subtitle").css({ "position": "absolute", "z-index": '1', "color": "white", "bottom": "0", "left": "50%", "-webkit-transform": "translateX(-50%)", "transform": "translateX(-50%)" });
}
let fileInput = document.createElement("input")
fileInput.type = 'file'
fileInput.style.display = 'none'
fileInput.onchange = (e) => {
let file = e.target.files[0];
if (!file) {
return;
}
let isShowSubtitle = false
let isShowDelay = false
let reader = new FileReader();
reader.onload = (e) => {
let delayTime = 0
$(window).on('keypress', (e) => {
let key = e.key.toUpperCase()
if (key === 'Q' || key === 'W') {
if (key === 'Q') delayTime += 100
if (key === 'W') delayTime -= 100
// console.log("delayTime: ", delayTime)
var delaySnackbar = document.getElementById("snackbar");
delaySnackbar.innerHTML = 'delaytime: ' + delayTime
if (isShowDelay) {
return
}
delaySnackbar.className = "show";
isShowDelay = true
setTimeout(() => {
delaySnackbar.className = delaySnackbar.className.replace("show", "");
isShowDelay = false
}, 2000);
} else if (key === 'E') {
if (!isShowSubtitle) {
$('#custom-subtitle').css({ "display": "none" })
console.log("hide subtitle")
} else {
$('#custom-subtitle').css({ "display": "block" })
console.log("show subtitle")
}
isShowSubtitle = !isShowSubtitle
}
});
let contents = e.target.result;
document.body.removeChild(fileInput)
// console.log(contents)
let targetNode = document.getElementsByClassName(curTimeSel)[0]
console.log(targetNode)
let config = {
attributes: true,
childList: true,
subtree: true
};
let prevPos = -2
let binarySearch = (target, arr) => {
let start = 0;
let end = arr.length - 1;
while (start <= end) {
let mid = parseInt(start + (end - start) / 2);
if (target >= arr[mid].start && target <= arr[mid].end) {
return mid;
} else if (target > arr[mid].end) {
start = mid + 1;
} else {
end = mid - 1;
}
}
return -1;
}
let callback = (_, observer) => {
// console.log("observer.subtitles", observer.subtitles)
let strTime
if (hostname === 'www.ixigua.com') {
strTime = targetNode.firstElementChild.firstElementChild.innerHTML;
} else {
strTime = targetNode.innerHTML
}
if (strTime.length <= 5) {
strTime = "00:" + strTime + ",000"
} else {
strTime = strTime + ",000"
}
let time = window.Subtitle.toMS(strTime);
let subtitles = observer.subtitles;
let pos = binarySearch(time + delayTime, subtitles)
if (pos === -1) {
$('#custom-subtitle').css({ "display": "none" })
return;
}
if (pos === prevPos) {
prevPos = pos
return
}
console.log(subtitles[pos].text);
if (isShowSubtitle === false) {
$('#custom-subtitle').css({ "display": "block" })
}
$('#custom-subtitle').html(subtitles[pos].text)
window.subtitleCount += 1
prevPos = pos
};
let observer = new MutationObserver(callback);
// 将字幕转换为对象数组
try {
observer.subtitles = window.Subtitle.parse(contents)
} catch (e) {
alert("字幕解析出现问题");
}
observer.observe(targetNode, config);
}
reader.readAsText(file)
}
document.body.appendChild(fileInput)
let eventMouse = document.createEvent("MouseEvents")
eventMouse.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
fileInput.dispatchEvent(eventMouse)
})
})();