mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-08-20 05:05:48 +00:00
subtitle parsing and display finished, TODO: filetype and subtitle verification, multi-track support, multi-video support
This commit is contained in:
parent
0b323ff2cd
commit
e88bb172c3
4 changed files with 254 additions and 23 deletions
34
app/css2.css
34
app/css2.css
|
|
@ -88,6 +88,21 @@ video[src=""] {
|
|||
display: none;
|
||||
}
|
||||
|
||||
video::cue {
|
||||
font-family: Roboto, monospace;
|
||||
color: #fff;
|
||||
text-shadow: 2px 2px 0 #000,
|
||||
2px -2px 0 #000,
|
||||
-2px 2px 0 #000,
|
||||
-2px -2px 0 #000,
|
||||
2px 0px 0 #000,
|
||||
0px 2px 0 #000,
|
||||
-2px 0px 0 #000,
|
||||
0px -2px 0 #000,
|
||||
2px 2px 2px #000;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, .8), rgba(0, 0, 0, .4) 25%, rgba(0, 0, 0, .2) 50%, rgba(0, 0, 0, .1) 75%, transparent);
|
||||
}
|
||||
|
|
@ -183,11 +198,11 @@ video[src=""] {
|
|||
|
||||
|
||||
.controls input#prog[type=range]::after,
|
||||
.controls input#prog[type=range]::before{
|
||||
.controls input#prog[type=range]::before {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
transform: translate(-50%,-100%);
|
||||
transform: translate(-50%, -100%);
|
||||
font-family: Roboto, monospace;
|
||||
color: #ececec;
|
||||
white-space: nowrap;
|
||||
|
|
@ -197,21 +212,25 @@ video[src=""] {
|
|||
font-weight: 600;
|
||||
transition: .2s opacity ease;
|
||||
}
|
||||
.controls input#prog[type=range]::after{
|
||||
|
||||
.controls input#prog[type=range]::after {
|
||||
width: 150px;
|
||||
background: var(--background);
|
||||
content: "";
|
||||
height: var(--height);
|
||||
top: -2rem;
|
||||
}
|
||||
.controls input#prog[type=range]::before{
|
||||
|
||||
.controls input#prog[type=range]::before {
|
||||
top: 0rem;
|
||||
content: attr(data-ts);
|
||||
}
|
||||
|
||||
.controls input#prog[type=range]:active::after,
|
||||
.controls input#prog[type=range]:active::before{
|
||||
.controls input#prog[type=range]:active::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.controls input[type=range]:hover::-webkit-slider-thumb {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
|
|
@ -229,7 +248,7 @@ video[src=""] {
|
|||
transition: all .1s ease
|
||||
}
|
||||
|
||||
#buffering{
|
||||
#buffering {
|
||||
border: 4px solid #FFFFFF00;
|
||||
border-top: 4px solid #fff;
|
||||
border-radius: 50%;
|
||||
|
|
@ -239,7 +258,8 @@ video[src=""] {
|
|||
opacity: 1;
|
||||
transition: .5s opacity ease;
|
||||
}
|
||||
#buffering.hidden{
|
||||
|
||||
#buffering.hidden {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,125 @@
|
|||
function parseSubtitles(stream) {
|
||||
parser = new MatroskaSubtitles()
|
||||
parser.once('tracks', function (tracks) {
|
||||
console.log(tracks)
|
||||
})
|
||||
parser.on('subtitle', function (subtitle, trackNumber) {
|
||||
console.log('Track ' + trackNumber + ':', subtitle)
|
||||
})
|
||||
console.log(stream)
|
||||
stream.pipe(parser)
|
||||
var parser = new MatroskaSubtitles()
|
||||
let track
|
||||
|
||||
parser.once('tracks', function (tracks) {
|
||||
track = video.addTextTrack('captions', tracks[0].type, tracks[0].language)
|
||||
track.mode = "showing";
|
||||
})
|
||||
parser.on('subtitle', function (subtitle, trackNumber) {
|
||||
subConvt(subtitle)
|
||||
})
|
||||
|
||||
var re_newline = /\\N/g; // replace \N with newline
|
||||
var re_softbreak = /\\n/g; // There's no equivalent function in WebVTT.
|
||||
var re_hardspace = /\\h/g; // Replace with
|
||||
var re_style = /\{([^}]+)\}/; // replace style
|
||||
function subConvt(result) {
|
||||
let cue = new VTTCue(result.time / 1000, (result.time + result.duration) / 1000, ""),
|
||||
text = result.text;
|
||||
|
||||
// Support for special characters in WebVTT.
|
||||
// For obvious reasons, the ampersand one *must* be first.
|
||||
text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
let style, tagsToClose = []; // Places to stash style info.
|
||||
// Subtitles may contain any number of override tags, so we'll loop through
|
||||
// to find them all.
|
||||
while ((style = text.match(re_style))) {
|
||||
let tagsToOpen = [], replaceString = '';
|
||||
if (style[1] && style[1].split) { // Stop throwing errors on empty tags.
|
||||
style = style[1].split("\\"); // Get an array of override commands.
|
||||
for (let j = 1; j < style.length; j++) {
|
||||
// Extract the current tag name.
|
||||
let tagCommand = style[j].match(/[a-zA-Z]+/)[0];
|
||||
// Give special reckognition to one-letter tags.
|
||||
let oneLetter = (tagCommand.length == 1) ? tagCommand : "";
|
||||
|
||||
// "New" position commands. It is assumed that bottom center position is the default.
|
||||
if (tagCommand === "an") {
|
||||
let posNum = Number(style[j].substring(2, 3));
|
||||
if (Math.floor((posNum - 1) / 3) == 1) {
|
||||
cue.line = 0.5;
|
||||
} else if (Math.floor((posNum - 1) / 3) == 2) {
|
||||
cue.line = 0;
|
||||
}
|
||||
if (posNum % 3 == 1) {
|
||||
cue.align = "start";
|
||||
} else if (posNum % 3 == 0) {
|
||||
cue.align = "end";
|
||||
}
|
||||
// Legacy position commands.
|
||||
} else if (oneLetter === "a" && !Number.isNaN(Number(style[j].substring(1, 2)))) {
|
||||
let posNum = Number(style[j].substring(1, 2));
|
||||
if (posNum > 8) {
|
||||
cue.line = 0.5;
|
||||
} else if (posNum > 4) {
|
||||
cue.line = 0;
|
||||
}
|
||||
if ((posNum - 1) % 4 == 0) {
|
||||
cue.align = "start";
|
||||
} else if ((posNum - 1) % 4 == 2) {
|
||||
cue.align = "end";
|
||||
}
|
||||
// Map simple text decoration commands to equivalent WebVTT text tags.
|
||||
// NOTE: Strikethrough (the 's' tag) is not supported in WebVTT.
|
||||
} else if (['b', 'i', 'u', 's'].includes(oneLetter)) {
|
||||
if (Number(style[j].substring(1, 2)) === 0
|
||||
// The more elaborate 'b-tag', which we will treat as an on-off selector.
|
||||
|| (style[j].match(/b\d{3}/)
|
||||
&& Number(style[j].match(/b(\d{3})/)[1]) < 500)
|
||||
) {
|
||||
// Closing a tag.
|
||||
if (tagsToClose.includes(oneLetter)) {
|
||||
// Nothing needs to be done if this tag isn't already open.
|
||||
// HTML tags must be nested, so we must ensure that any tag nested inside
|
||||
// the tag being closed are also closed, and then opened again once the
|
||||
// current tag is closed.
|
||||
while (tagsToClose.length > 0) {
|
||||
let nowClosing = tagsToClose.pop();
|
||||
replaceString += '</' + nowClosing + '>';
|
||||
if (nowClosing !== oneLetter) {
|
||||
tagsToOpen.push(nowClosing);
|
||||
} else {
|
||||
// There's no need to close the tags that the current tag
|
||||
// is nested within.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Opening a tag.
|
||||
if (!tagsToClose.includes(oneLetter)) {
|
||||
// Nothing needs to be done if the tag is already open.
|
||||
// If no, place the tag on the bottom of the stack of tags being opened.
|
||||
tagsToOpen.splice(0, 0, oneLetter);
|
||||
}
|
||||
}
|
||||
} else if (oneLetter === 'r') {
|
||||
// Resetting override tags, by closing all open tags.
|
||||
// TODO: The 'r' tag can also be used to switch to a different named style,
|
||||
// however, named styles haven't been implemented.
|
||||
while (tagsToClose.length > 0) {
|
||||
replaceString += '</' + tagsToClose.pop() + '>';
|
||||
}
|
||||
}
|
||||
|
||||
// Insert open-tags for tags in the to-open list.
|
||||
while (tagsToOpen.length > 0) {
|
||||
let nowOpening = tagsToOpen.pop();
|
||||
replaceString += '<' + nowOpening + '>';
|
||||
tagsToClose.push(nowOpening);
|
||||
}
|
||||
}
|
||||
}
|
||||
text = text.replace(re_style, replaceString); // Replace override tag.
|
||||
}
|
||||
|
||||
text = text.replace(re_newline, "\r\n").replace(re_softbreak, " ").replace(
|
||||
re_hardspace, " ");
|
||||
let content = "<v " + result.style + ">" + text
|
||||
while (tagsToClose.length > 0) {
|
||||
content += '</' + tagsToClose.pop() + '>';
|
||||
}
|
||||
cue.text = ` \r\n${content}\r\n `
|
||||
track.addCue(cue)
|
||||
}
|
||||
94
app/subtitletest2.js
Normal file
94
app/subtitletest2.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
class ParsedSubtitle {
|
||||
constructor(subtitle, tracknumber) {
|
||||
this._trackN = tracknumber
|
||||
this._subtitle = subtitle
|
||||
this._time = subtitle.time
|
||||
this._duration = subtitle.duration
|
||||
this._text = subtitle.text
|
||||
this._cue = new VTTCue(subtitle.time / 1000, (subtitle.time + subtitle.duration) / 1000, subtitle.text)
|
||||
}
|
||||
|
||||
get trackNumber() {
|
||||
return this._trackN
|
||||
}
|
||||
|
||||
get raw() {
|
||||
return this._subtitle
|
||||
}
|
||||
|
||||
get time() {
|
||||
return this._time
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this._duration
|
||||
}
|
||||
|
||||
get end() {
|
||||
return this._time + this._duration
|
||||
}
|
||||
|
||||
get text() {
|
||||
return this._text
|
||||
}
|
||||
|
||||
get vttcue() {
|
||||
return this._cue
|
||||
}
|
||||
|
||||
get hashid() {
|
||||
if (this._hash !== undefined) {
|
||||
return this_hash
|
||||
}
|
||||
|
||||
let result = 17
|
||||
|
||||
result = 31 * Math.trunc(this._time)
|
||||
result = 31 * Math.trunc(this._duration)
|
||||
|
||||
return this._hash = result
|
||||
}
|
||||
}
|
||||
|
||||
class SubtitleHandler {
|
||||
constructor(parser) {
|
||||
this._parser = parser
|
||||
this._subs = []
|
||||
}
|
||||
|
||||
add(sub) {
|
||||
if (!this._subs.some(item => item.hashid === sub)) {
|
||||
this._subs.add(sub)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function setSubsTrack(track) {
|
||||
if (track !== undefined) {
|
||||
track.mode = "showing"
|
||||
window._texttrack = track
|
||||
}
|
||||
}
|
||||
|
||||
function getSubsTrack(callback) {
|
||||
if (window._texttrack !== undefined && typeof callback === "function") {
|
||||
callback(window._texttrack)
|
||||
}
|
||||
}
|
||||
|
||||
parser.once('tracks', function (tracks) {
|
||||
console.log(tracks)
|
||||
})
|
||||
|
||||
|
||||
parser.on('subtitle', function (subtitle, trackNumber) {
|
||||
console.log('Track ' + trackNumber + ':', subtitle)
|
||||
|
||||
const sub = new ParsedSubtitle(subtitle, trackNumber)
|
||||
|
||||
getSubsTrack(track => {
|
||||
track.addCue(sub.vttcue)
|
||||
})
|
||||
})
|
||||
|
|
@ -34,6 +34,9 @@ function t(a) {
|
|||
case 2:
|
||||
addTorrent("https://webtorrent.io/torrents/tears-of-steel.torrent")
|
||||
break;
|
||||
case 3:
|
||||
addTorrent("magnet:?xt=urn:btih:CE9156EB497762F8B7577B71C0647A4B0C3423E1&dn=Inception+%282010%29+720p+-+mkv+-+1.0GB+-+YIFY&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2920%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.pirateparty.gr%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.cyberia.is%3A6969%2Fannounce")
|
||||
break;
|
||||
}
|
||||
}
|
||||
WEBTORRENT_ANNOUNCE = announceList
|
||||
|
|
@ -87,10 +90,10 @@ function addTorrent(magnet) {
|
|||
})
|
||||
video.src = `${scope}webtorrent/${torrent.infoHash}/${encodeURI(videoFile.path)}`
|
||||
// createSubParser()
|
||||
// if (subStream){
|
||||
// subStream.destroy()
|
||||
// }
|
||||
// subStream = videoFile.createReadStream().pipe(parser)
|
||||
if (subStream){
|
||||
subStream.destroy()
|
||||
}
|
||||
subStream = videoFile.createReadStream().pipe(parser)
|
||||
document.location.href = "#player"
|
||||
nowPlaying(selected)
|
||||
halfmoon.toggleModal("tsearch")
|
||||
|
|
@ -128,8 +131,8 @@ function serveFile(file, req) {
|
|||
|
||||
res.body = req.method === 'HEAD' ? '' : 'stream'
|
||||
|
||||
parseSubtitles(file.createReadStream(range))
|
||||
|
||||
// file.createReadStream(range).pipe(parser)
|
||||
|
||||
return [res, req.method === 'GET' && file.createReadStream(range)]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue