subtitle parsing progress, v 0.99999

This commit is contained in:
ThaUnknown 2020-09-15 18:20:44 +02:00
parent 792021bcb4
commit 7ef4f14de5
6 changed files with 379 additions and 360 deletions

View file

@ -207,11 +207,13 @@ function viewAnime(media) {
document.querySelector(".view .banner img").src = ""
document.querySelector(".view .banner img").src = media.bannerImage
document.querySelector(".view .contain-img").src = media.coverImage.extraLarge
document.querySelector(".view .title").textContent = !!media.title.english ? media.title.english : media.title.romaji
document.querySelector(".view .desc").innerHTML = !!media.description ? media.description : ""
document.querySelector(".view .title").textContent = media.title.english || media.title.romaji
document.querySelector(".view .desc").innerHTML = media.description || ""
document.querySelector(".view .details").innerHTML = ""
document.querySelector(".view #play").onclick = function () { nyaaSearch(media, document.querySelector(".view #ep").value); halfmoon.toggleModal("view") }
detailsCreator(media)
document.querySelector(".view #ep").value = 1
document.querySelector(".view #ep").max = media.episodes || 999
document.querySelector(".view .details").appendChild(detailsfrag)
}

View file

@ -56,7 +56,7 @@
<div class="row">
<div class="col-md-3 mt-nc pb-20 d-flex flex-column justify-content-between">
<img class="contain-img rounded w-full">
<div class="input-group">
<div class="input-group pt-10">
<div class="input-group-prepend">
<button class="btn bg-primary pr-5 pl-10" type="button" id="play">Play
Episode</button>
@ -216,10 +216,8 @@
</form>
</div>
</div>
<div>
<div class="gallery">
<div class="gallery h-full overflow-y-scroll">
</div>
</div>
</section>
</div>
</div>

View file

@ -13069,245 +13069,251 @@ if (typeof Object.create === 'function') {
},{}],14:[function(require,module,exports){
(function (Buffer){
const Transform = require('readable-stream').Transform
const ebml = require('ebml')
const ebmlBlock = require('ebml-block')
const readElement = require('./lib/read-element')
// track elements we care about
const TRACK_ELEMENTS = ['TrackNumber', 'TrackType', 'Language', 'CodecID', 'CodecPrivate']
const SUBTITLE_TYPES = ['S_TEXT/UTF8', 'S_TEXT/SSA', 'S_TEXT/ASS']
const ASS_KEYS = ['readOrder', 'layer', 'style', 'name', 'marginL', 'marginR', 'marginV', 'effect', 'text']
const CUES_ID = Buffer.from('1C53BB6B', 'hex')
class MatroskaSubtitles extends Transform {
constructor ({ prevInstance, offset } = {}) {
super()
let currentTrack = null
let currentSubtitleBlock = null
let currentClusterTimecode = null
let currentSeekID = null
let waitForNext = false
this.decoder = new ebml.Decoder()
if (prevInstance instanceof MatroskaSubtitles) {
if (offset == null) throw new Error('no offset')
prevInstance.once('drain', () => {
// prevInstance.end()
console.log('prevInstance drained')
})
if (offset === 0) {
// just begin normal parsing
this.subtitleTracks = prevInstance.subtitleTracks || new Map()
this.timecodeScale = prevInstance.timecodeScale || 1
this.cues = prevInstance.cues
this.decoder.on('data', _onMetaData.bind(this))
return
}
// copy previous metadata
this.subtitleTracks = prevInstance.subtitleTracks
this.timecodeScale = prevInstance.timecodeScale
const ebml = require('ebml')
const ebmlBlock = require('ebml-block')
const readElement = require('./lib/read-element')
// track elements we care about
const TRACK_ELEMENTS = ['TrackNumber', 'TrackType', 'Language', 'CodecID', 'CodecPrivate']
const SUBTITLE_TYPES = ['S_TEXT/UTF8', 'S_TEXT/SSA', 'S_TEXT/ASS']
const ASS_KEYS = ['readOrder', 'layer', 'style', 'name', 'marginL', 'marginR', 'marginV', 'effect', 'text']
const CUES_ID = Buffer.from('1C53BB6B', 'hex')
class MatroskaSubtitles extends Transform {
constructor ({ prevInstance, offset } = {}) {
super()
let currentTrack = null
let currentSubtitleBlock = null
let currentClusterTimecode = null
let currentSeekID = null
let waitForNext = false
this.decoder = new ebml.Decoder()
if (prevInstance instanceof MatroskaSubtitles) {
if (offset == null) throw new Error('no offset')
prevInstance.once('drain', () => {
// prevInstance.end()
console.log('prevInstance drained')
})
if (offset === 0) {
// just begin normal parsing
this.subtitleTracks = prevInstance.subtitleTracks || new Map()
this.timecodeScale = prevInstance.timecodeScale || 1
this.cues = prevInstance.cues
if (!this.cues) {
this.decoder = null
return console.warn('No cues was parsed. Subtitle parsing disabled.')
}
// find a cue that's close to the file offset
// const cueArray = Uint32Array.from(this.cues.positions)
// cueArray.sort()
const cueArray = Array.from(this.cues.positions)
cueArray.sort((a, b) => a - b)
const closestCue = cueArray.find(i => i >= offset)
if (closestCue != null) {
// prepare to skip file stream until we hit a cue position
this.skip = closestCue - offset
// set internal decoder position to output consistent file offsets
this.decoder.total = closestCue
// console.log('using cue:', closestCue)
this.decoder.on('data', _onMetaData.bind(this))
} else {
this.decoder = null
console.warn(`No cues for offset ${offset}. Subtitle parsing disabled.`)
}
} else {
if (offset) {
this.decoder = null
console.error(`Offset is ${offset}, and must be 0 for initial instance. Subtitle parsing disabled.`)
return
}
this.subtitleTracks = new Map()
this.timecodeScale = 1
this.decoder.on('data', _onMetaData.bind(this))
return
}
function _onMetaData (chunk) {
if (waitForNext) {
waitForNext = false
// Keep cues if this is the same segment
if (!this.cues || this.cues.start !== chunk[1].start) {
this.cues = { start: chunk[1].start, positions: new Set() }
}
}
if (chunk[0] === 'start' && chunk[1].name === 'Segment') {
// TODO: only record first segment?
// TODO: find a simpler way to do this
waitForNext = true
}
if (chunk[1].name === 'SeekID') {
// TODO: .value is undefined for some reason?
currentSeekID = chunk[1].data
}
if (currentSeekID && chunk[1].name === 'SeekPosition') {
if (CUES_ID.equals(currentSeekID)) {
// hack: this is not a cue position, but the position to the cue data itself,
// in case it's not located at the beginning of the file.
this.cues.positions.add(this.cues.start + chunk[1].value)
}
}
if (chunk[1].name === 'CueClusterPosition') {
this.cues.positions.add(this.cues.start + chunk[1].value)
}
if (chunk[0] === 'end' && chunk[1].name === 'Cues') {
this.emit('cues')
}
// Segment Information
if (chunk[1].name === 'TimecodeScale') {
this.timecodeScale = readElement(chunk[1]) / 1000000
}
// Tracks
if (chunk[0] === 'start' && chunk[1].name === 'TrackEntry') {
currentTrack = {}
}
if (currentTrack && chunk[0] === 'tag') {
// save info about track currently being scanned
if (TRACK_ELEMENTS.includes(chunk[1].name)) {
currentTrack[chunk[1].name] = readElement(chunk[1])
}
}
if (chunk[0] === 'end' && chunk[1].name === 'TrackEntry') {
if (currentTrack.TrackType === 0x11) { // Subtitle Track
if (SUBTITLE_TYPES.includes(currentTrack.CodecID)) {
const track = {
number: currentTrack.TrackNumber,
language: currentTrack.Language,
type: currentTrack.CodecID.substring(7).toLowerCase()
}
if (currentTrack.CodecPrivate) {
// only SSA/ASS
track.header = currentTrack.CodecPrivate.toString('utf8')
}
this.subtitleTracks.set(currentTrack.TrackNumber, track)
}
}
currentTrack = null
}
if (chunk[0] === 'end' && chunk[1].name === 'Tracks') {
// this.decoder.removeListener('data', _onMetaData)
// if (this.subtitleTracks.size <= 0) return this.end()
// this.decoder.on('data', _onClusterData)
this.emit('tracks', Array.from(this.subtitleTracks.values()))
}
// }
// function _onClusterData (chunk) {
// TODO: assuming this is a Cluster `Timecode`
if (chunk[1].name === 'Timecode') {
currentClusterTimecode = readElement(chunk[1])
}
if (chunk[1].name === 'Block') {
const block = ebmlBlock(chunk[1].data)
if (this.subtitleTracks.has(block.trackNumber)) {
const type = this.subtitleTracks.get(block.trackNumber).type
const subtitle = {
text: block.frames[0].toString('utf8'),
time: (block.timecode + currentClusterTimecode) * this.timecodeScale
}
if (type === 'ass' || type === 'ssa') {
// extract SSA/ASS keys
const values = subtitle.text.split(',')
// ignore read-order, and skip layer if ssa
let i = type === 'ssa' ? 2 : 1
for (; i < 9; i++) {
subtitle[ASS_KEYS[i]] = values[i]
}
// re-append extra text that might have been split
for (i = 9; i < values.length; i++) {
subtitle.text += ',' + values[i]
}
}
currentSubtitleBlock = [subtitle, block.trackNumber]
}
}
// TODO: assuming `BlockDuration` exists and always comes after `Block`
if (currentSubtitleBlock && chunk[1].name === 'BlockDuration') {
currentSubtitleBlock[0].duration = readElement(chunk[1]) * this.timecodeScale
this.emit('subtitle', ...currentSubtitleBlock)
currentSubtitleBlock = null
}
// copy previous metadata
this.subtitleTracks = prevInstance.subtitleTracks
this.timecodeScale = prevInstance.timecodeScale
this.cues = prevInstance.cues
if (!this.cues) {
this.decoder = null
return console.warn('No cues was parsed. Subtitle parsing disabled.')
}
}
_transform (chunk, _, callback) {
if (!this.decoder) return callback(null, chunk)
if (this.skip) {
// skip bytes to reach cue position
if (this.skip < chunk.length) {
// slice chunk
const sc = chunk.slice(this.skip)
this.skip = 0
this.decoder.write(sc)
} else {
// skip entire chunk
this.skip -= chunk.length
}
// find a cue that's close to the file offset
// const cueArray = Uint32Array.from(this.cues.positions)
// cueArray.sort()
const cueArray = Array.from(this.cues.positions)
cueArray.sort((a, b) => a - b)
const closestCue = cueArray.find(i => i >= offset)
if (closestCue != null) {
// prepare to skip file stream until we hit a cue position
this.skip = closestCue - offset
// set internal decoder position to output consistent file offsets
this.decoder.total = closestCue
// console.log('using cue:', closestCue)
this.decoder.on('data', _onMetaData.bind(this))
} else {
this.decoder.write(chunk)
this.decoder = null
console.warn(`No cues for offset ${offset}. Subtitle parsing disabled.`)
}
} else {
if (offset) {
this.decoder = null
console.error(`Offset is ${offset}, and must be 0 for initial instance. Subtitle parsing disabled.`)
return
}
this.subtitleTracks = new Map()
this.timecodeScale = 1
this.decoder.on('data', _onMetaData.bind(this))
}
function _onMetaData (chunk) {
if (waitForNext) {
waitForNext = false
// Keep cues if this is the same segment
if (!this.cues) {
this.cues = { start: chunk[1].start, positions: new Set() }
} else if (this.cues.start !== chunk[1].start) {
this.cues = { start: chunk[1].start, positions: new Set() }
console.warn('New segment found - resetting cues! Not sure we can handle this!?')
} else {
console.info('Saw first segment again. Keeping cues.')
}
}
if (chunk[0] === 'start' && chunk[1].name === 'Segment') {
// TODO: only record first segment?
// TODO: find a simpler way to do this
waitForNext = true
}
if (chunk[1].name === 'SeekID') {
// TODO: .value is undefined for some reason?
currentSeekID = chunk[1].data
}
if (currentSeekID && chunk[1].name === 'SeekPosition') {
//if (CUES_ID.equals(currentSeekID)) {
// hack: this is not a cue position, but the position to the cue data itself,
// in case it's not located at the beginning of the file.
// actually, just add all seek positions.
this.cues.positions.add(this.cues.start + chunk[1].value)
//}
}
if (chunk[1].name === 'CueClusterPosition') {
this.cues.positions.add(this.cues.start + chunk[1].value)
}
if (chunk[0] === 'end' && chunk[1].name === 'Cues') {
this.emit('cues')
}
// Segment Information
if (chunk[1].name === 'TimecodeScale') {
this.timecodeScale = readElement(chunk[1]) / 1000000
}
// Tracks
if (chunk[0] === 'start' && chunk[1].name === 'TrackEntry') {
currentTrack = {}
}
if (currentTrack && chunk[0] === 'tag') {
// save info about track currently being scanned
if (TRACK_ELEMENTS.includes(chunk[1].name)) {
currentTrack[chunk[1].name] = readElement(chunk[1])
}
}
if (chunk[0] === 'end' && chunk[1].name === 'TrackEntry') {
if (currentTrack.TrackType === 0x11) { // Subtitle Track
if (SUBTITLE_TYPES.includes(currentTrack.CodecID)) {
const track = {
number: currentTrack.TrackNumber,
language: currentTrack.Language,
type: currentTrack.CodecID.substring(7).toLowerCase()
}
if (currentTrack.CodecPrivate) {
// only SSA/ASS
track.header = currentTrack.CodecPrivate.toString('utf8')
}
this.subtitleTracks.set(currentTrack.TrackNumber, track)
}
}
currentTrack = null
}
if (chunk[0] === 'end' && chunk[1].name === 'Tracks') {
// this.decoder.removeListener('data', _onMetaData)
// if (this.subtitleTracks.size <= 0) return this.end()
// this.decoder.on('data', _onClusterData)
this.emit('tracks', Array.from(this.subtitleTracks.values()))
}
// }
// function _onClusterData (chunk) {
// TODO: assuming this is a Cluster `Timecode`
if (chunk[1].name === 'Timecode') {
currentClusterTimecode = readElement(chunk[1])
}
if (chunk[1].name === 'Block') {
const block = ebmlBlock(chunk[1].data)
if (this.subtitleTracks.has(block.trackNumber)) {
const type = this.subtitleTracks.get(block.trackNumber).type
const subtitle = {
text: block.frames[0].toString('utf8'),
time: (block.timecode + currentClusterTimecode) * this.timecodeScale
}
if (type === 'ass' || type === 'ssa') {
// extract SSA/ASS keys
const values = subtitle.text.split(',')
// ignore read-order, and skip layer if ssa
let i = type === 'ssa' ? 2 : 1
for (; i < 9; i++) {
subtitle[ASS_KEYS[i]] = values[i]
}
// re-append extra text that might have been split
for (i = 9; i < values.length; i++) {
subtitle.text += ',' + values[i]
}
}
currentSubtitleBlock = [subtitle, block.trackNumber]
}
}
// TODO: assuming `BlockDuration` exists and always comes after `Block`
if (currentSubtitleBlock && chunk[1].name === 'BlockDuration') {
currentSubtitleBlock[0].duration = readElement(chunk[1]) * this.timecodeScale
this.emit('subtitle', ...currentSubtitleBlock)
currentSubtitleBlock = null
}
callback(null, chunk)
}
}
module.exports = MatroskaSubtitles
_transform (chunk, _, callback) {
if (!this.decoder) return callback(null, chunk)
if (this.skip) {
// skip bytes to reach cue position
if (this.skip < chunk.length) {
// slice chunk
const sc = chunk.slice(this.skip)
this.skip = 0
this.decoder.write(sc)
} else {
// skip entire chunk
this.skip -= chunk.length
}
} else {
this.decoder.write(chunk)
}
callback(null, chunk)
}
}
module.exports = MatroskaSubtitles
}).call(this,require("buffer").Buffer)
},{"./lib/read-element":15,"buffer":3,"ebml":12,"ebml-block":9,"readable-stream":31}],15:[function(require,module,exports){

View file

@ -283,4 +283,5 @@ section {
section:target {
display: block;
height: 100%
}

View file

@ -1,114 +1,147 @@
let tracks = []
let tracks = [],
parser
let re_newline = /\\N/g, // replace \N with newline
function parseSubs(range, stream) {
if (video.src.endsWith(".mkv")) {
console.log('set parser', range)
parser = new MatroskaSubtitles({ prevInstance: parser, offset: range.start })
parser.once('tracks', function (pTracks) {
console.log(pTracks)
tracks = []
pTracks.forEach(track => {
tracks[track.number] = video.addTextTrack('captions', track.type, track.language || track.number)
})
if (video.textTracks[0]) {
video.textTracks[0].mode = "showing"
}
})
parser.once('cues', function () {
console.log('seeking ready')
})
parser.on('subtitle', function (subtitle, trackNumber) {
subConvt(subtitle, trackNumber)
})
stream.pipe(parser)
}
}
const re_newline = /\\N/g, // replace \N with newline
re_softbreak = /\\n/g, // There's no equivalent function in WebVTT.
re_hardspace = /\\h/g, // Replace with &nbsp;
re_style = /\{([^}]+)\}/; // replace style
function subConvt(result, trackNumber) {
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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";
cue.text = "&nbsp;\r\n"
} 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";
cue.text = "&nbsp;\r\n"
} 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;
if (tracks[trackNumber].label == "ass") {
// Support for special characters in WebVTT.
// For obvious reasons, the ampersand one *must* be first.
text = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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";
cue.text = "&nbsp;\r\n"
} 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";
cue.text = "&nbsp;\r\n"
} 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 {
// 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() + '>';
}
}
} 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);
}
}
// 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_style, replaceString); // Replace override tag.
text = text.replace(re_newline, "\r\n").replace(re_softbreak, " ").replace(
re_hardspace, "&nbsp;");
let content = "<v " + result.style + ">" + text
while (tagsToClose.length > 0) {
content += '</' + tagsToClose.pop() + '>';
}
cue.text += `${content}\r\n&nbsp;`
} else {
cue.text = `${text}\r\n&nbsp;`
}
text = text.replace(re_newline, "\r\n").replace(re_softbreak, " ").replace(
re_hardspace, "&nbsp;");
let content = "<v " + result.style + ">" + text
while (tagsToClose.length > 0) {
content += '</' + tagsToClose.pop() + '>';
}
cue.text += `${content}\r\n&nbsp;`
if (!Object.values(tracks[trackNumber].cues).some(c => c.text == cue.text)) {
tracks[trackNumber].addCue(cue)
}

View file

@ -24,7 +24,6 @@ const client = new WebTorrent(),
],
scope = '/app/',
sw = navigator.serviceWorker.register('sw.js', { scope })
let parser
//for debugging
function t(a) {
switch (a) {
@ -47,7 +46,7 @@ WEBTORRENT_ANNOUNCE = announceList
return url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0
})
var nowPlaying,
maxTorrents = 3,
maxTorrents = 1,
subStream
function addTorrent(magnet) {
if (client.torrents.length >= maxTorrents) {
@ -56,6 +55,7 @@ function addTorrent(magnet) {
halfmoon.toggleModal("tsearch")
document.location.href = "#player"
client.add(magnet, async function (torrent) {
video.src = ""
await sw
function onProgress() {
peers.textContent = torrent.numPeers
@ -90,6 +90,7 @@ function addTorrent(magnet) {
videoFile = file
}
})
parser = undefined
video.src = `${scope}webtorrent/${torrent.infoHash}/${encodeURI(videoFile.path)}`
if (subStream) {
subStream.destroy()
@ -128,36 +129,14 @@ function serveFile(file, req) {
}
res.headers['Cache-Control'] = 'no-store'
res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0'
res.headers['Expires'] = '0'
res.body = req.method === 'HEAD' ? '' : 'stream'
console.log('set parser', range)
parser = new MatroskaSubtitles({ prevInstance: parser, offset: range.start })
parser.once('tracks', function (pTracks) {
tracks = []
pTracks.forEach(track => {
if (track.type == "ass") {
tracks[track.number] = video.addTextTrack('captions', track.number, !!track.language ? track.language : track.number)
}
})
if (video.textTracks[0]) {
video.textTracks[0].mode = "showing"
}
})
parser.once('cues', function () {
console.log('seeking ready')
})
parser.on('subtitle', function (subtitle, trackNumber) {
subConvt(subtitle, trackNumber)
})
// parser is really a passthrough mkv stream now
file.createReadStream(range).pipe(parser)
let stream = file.createReadStream(range)
parseSubs(range, stream)
return [res, req.method === 'GET' && parser]
return [res, req.method === 'GET' && parser || stream]
}
// kind of a fetch event from service worker but for the main thread.