fixes, auto-detect and convert external subtitle files

This commit is contained in:
ThaUnknown 2021-04-15 00:47:52 +02:00
parent 24cdca08b8
commit f622218d31
2 changed files with 138 additions and 104 deletions

View file

@ -74,6 +74,7 @@ class TorrentPlayer extends WebTorrent {
this.player.addEventListener('fullscreenchange', () => this.updateFullscreen())
this.controls.ppToggle.addEventListener('dblclick', () => this.toggleFullscreen())
this.video.addEventListener('loadedmetadata', () => this.findSubtitleFiles(this.currentFile))
this.subtitleData = {
fonts: ['https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmEU9fBBc4.woff2'],
headers: [],
@ -170,7 +171,7 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
}
if ('setPositionState' in navigator.mediaSession) this.video.addEventListener('timeupdate', () => this.updatePositionState())
this.subtitleExtensions = ['.srt', '.ass', '.vtt']
this.subtitleExtensions = ['.srt', '.vtt', '.ass', '.ssa']
this.videoExtensions = ['.3g2', '.3gp', '.asf', '.avi', '.dv', '.flv', '.gxf', '.m2ts', '.m4a', '.m4b', '.m4p', '.m4r', '.m4v', '.mkv', '.mov', '.mp4', '.mpd', '.mpeg', '.mpg', '.mxf', '.nut', '.ogm', '.ogv', '.swf', '.ts', '.vob', '.webm', '.wmv', '.wtv']
this.videoFiles = undefined
@ -679,10 +680,10 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
const label = document.createElement('label')
input.name = `${type}-radio-set`
input.type = 'radio'
input.id = type === 'captions' ? `${type}-${track?.number || 'off'}-radio` : `${type}-${track.id}-radio`
input.value = type === 'captions' ? track?.number || null : track.id
input.id = type === 'captions' ? `${type}-${track ? track.number : 'off'}-radio` : `${type}-${track.id}-radio`
input.value = type === 'captions' ? track ? track.number : -1 : track.id
input.checked = type === 'captions' ? track?.number === this.subtitleData.current : track.enabled
label.htmlFor = type === 'captions' ? `${type}-${track?.number || 'off'}-radio` : `${type}-${track.id}-radio`
label.htmlFor = type === 'captions' ? `${type}-${track ? track.number : 'off'}-radio` : `${type}-${track.id}-radio`
label.textContent = track
? type === 'captions'
? (track.language || (!Object.values(this.subtitleData.headers).some(header => header.language === 'eng' || header.language === 'en') ? 'eng' : header.type)) + (track.name ? ' - ' + track.name : '')
@ -714,7 +715,7 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
this.subtitleData.timeout = setTimeout(() => {
this.subtitleData.timeout = undefined
if (this.subtitleData.renderer) {
this.subtitleData.renderer.setTrack(trackNumber ? this.subtitleData.headers[trackNumber].header.slice(0, -1) + Array.from(this.subtitleData.tracks[trackNumber]).join('\n') : this.subtitleData.headers[3].header.slice(0, -1))
this.subtitleData.renderer.setTrack(trackNumber !== -1 ? this.subtitleData.headers[trackNumber].header.slice(0, -1) + Array.from(this.subtitleData.tracks[trackNumber]).join('\n') : this.subtitleData.defaultHeader)
}
}, 1000)
}
@ -761,7 +762,6 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
this.subtitleData.parser.destroy()
this.selectCaptions(this.subtitleData.current)
parser = undefined
this.controls.captionsButton.removeAttribute('disabled')
if (!this.video.paused) {
this.video.pause()
this.playVideo()
@ -770,7 +770,6 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
})
console.log('Sub parsing started')
this.subtitleData.parser = file.createReadStream().pipe(parser)
// when this gets overwritten the parser stays so it might "leak" some RAM???
} else {
resolve()
}
@ -779,7 +778,6 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
handleSubtitleParser (parser, skipFile) {
parser.once('tracks', tracks => {
this.controls.captionsButton.removeAttribute('disabled')
tracks.forEach(track => {
if (!this.subtitleData.tracks[track.number]) {
// overwrite webvtt or other header with custom one
@ -828,58 +826,94 @@ Style: Default,${options.defaultSSAStyles || 'Roboto Medium,26,&H00FFFFFF,&H0000
}
}
if (!this.subtitleData.renderer) {
console.log()
this.subtitleData.renderer = new SubtitlesOctopus(options)
this.selectCaptions(this.subtitleData.current)
this.controls.captionsButton.removeAttribute('disabled')
}
}
}
convertFile (file) {
const regex = /^(?:\d+\n)?(\S{9,12})\s?-->\s?(\S{9,12})(.*)\n([\s\S]*)$/i
const subtitles = []
for (const split of fileContent.split('\n\n')) {
match = split.match(regex)
if (match) {
if (match[1].length === 9) {
match[1] = '0:' + match[1]
} else {
if (match[1][0] === '0') {
match[1].substring(1)
}
}
match[1].replace(',', '.')
if (match[2].length === 9) {
match[2] = '0:' + match[2]
} else {
if (match[2][0] === '0') {
match[2].substring(1)
}
}
match[2].replace(',', '.')
const matches = match[4].match(/<[^>]+>/g) // create array of all tags
if (matches) {
matches.forEach(matched => {
if (/<\//.test(matched)) { // check if its a closing tag
match[4] = match[4].replace(matched, matched.replace('</', '{\\').replace('>', '0}'))
convertSubFile (file, isAss, callback) {
const regex = /(?:\d+\n)?(\S{9,12})\s?-->\s?(\S{9,12})(.*)\n([\s\S]*)$/i
file.getBuffer((_err, buffer) => {
const subtitles = isAss ? buffer.toString() : []
if (isAss) {
callback(subtitles)
} else {
for (const split of buffer.toString().split('\n\n')) {
const match = split.match(regex)
if (match) {
match[1] = match[1].match(/.*[.,]\d{2}/)[0]
match[2] = match[2].match(/.*[.,]\d{2}/)[0]
if (match[1].length === 9) {
match[1] = '0:' + match[1]
} else {
match[4] = match[4].replace(matched, matched.replace('<', '{\\').replace('>', '1}'))
if (match[1][0] === '0') {
match[1] = match[1].substring(1)
}
}
})
match[1].replace(',', '.')
if (match[2].length === 9) {
match[2] = '0:' + match[2]
} else {
if (match[2][0] === '0') {
match[2] = match[2].substring(1)
}
}
match[2].replace(',', '.')
const matches = match[4].match(/<[^>]+>/g) // create array of all tags
if (matches) {
matches.forEach(matched => {
if (/<\//.test(matched)) { // check if its a closing tag
match[4] = match[4].replace(matched, matched.replace('</', '{\\').replace('>', '0}'))
} else {
match[4] = match[4].replace(matched, matched.replace('<', '{\\').replace('>', '1}'))
}
})
}
subtitles.push('Dialogue: 0,' + match[1].replace(',', '.') + ',' + match[2].replace(',', '.') + ',Default,,0,0,0,,' + match[4])
}
}
subtitles.push('Dialogue: 1,' + match[1] + ',' + match[2] + ',Default,,0,0,0,,' + match[4])
callback(subtitles)
}
}
return subtitles
})
}
findSubtitles (targetFile) {
findSubtitleFiles (targetFile) {
const path = targetFile.path.split(targetFile.name)[0]
const subtitleFiles = []
for (const file of targetFile.torrent_.files.filter(file => this.subtitleExtensions.some(ext => file.name.endsWith(ext)))) {
const split = file.split(path)
if (split.length === 2) subtitleFiles.push(file)
// array of subtitle files that match video name, or all subtitle files when only 1 vid file
const subtitleFiles = targetFile._torrent.files.filter(file => {
return this.subtitleExtensions.some(ext => file.name.endsWith(ext)) && (this.videoFiles.length === 1 ? true : file.path.split(path).length === 2)
})
if (subtitleFiles.length) {
this.createRadioElement(undefined, 'captions')
this.subtitleData.parsed = true
this.subtitleData.current = 0
for (const [index, file] of subtitleFiles.entries()) {
const isAss = file.name.endsWith('.ass') || file.name.endsWith('.ssa')
const extension = /\.(\w+)$/
const name = file.name.replace(targetFile.name, '') === file.name
? file.name.replace(targetFile.name.replace(extension, ''), '').slice(0, -4).replace(/[,._-]/g, ' ').trim()
: file.name.replace(targetFile.name, '').slice(0, -4).replace(/[,._-]/g, ' ').trim()
const header = {
header: this.subtitleData.defaultHeader,
language: name,
number: index,
type: file.name.match(extension)[1]
}
this.subtitleData.headers.push(header)
this.subtitleData.tracks[index] = []
this.createRadioElement(header, 'captions')
this.convertSubFile(file, isAss, subtitles => {
if (isAss) {
this.subtitleData.headers[index].header = subtitles
} else {
this.subtitleData.tracks[index] = subtitles
}
if (this.subtitleData.current === index) this.selectCaptions(this.subtitleData.current)
})
this.initSubtitleRenderer()
}
}
}
@ -1052,7 +1086,7 @@ const client = new TorrentPlayer({
})
},
onWatched: () => {
if (client.nowPlaying.media?.episodes || client.nowPlaying.media?.nextAiringEpisode?.episode) {
if (client.nowPlaying?.media?.episodes || client.nowPlaying?.media?.nextAiringEpisode?.episode) {
if (settings.other2 && (client.nowPlaying.media?.episodes || client.nowPlaying.media?.nextAiringEpisode?.episode > client.nowPlaying.episodeNumber)) {
alEntry()
} else {

View file

@ -2877,7 +2877,7 @@ if ('now' in self.performance === false) {
table[i] = new HuffmanCode(0, 0)
}
ReadHuffmanCode(num_htrees + max_run_length_prefix, table, 0, br)
for (i = 0; i < context_map_size; ) {
for (i = 0; i < context_map_size;) {
var code
br.readMoreInput()
code = ReadSymbol(table, 0, br)
@ -5051,17 +5051,17 @@ var Browser = {
typeof MozBlobBuilder !== 'undefined'
? MozBlobBuilder
: typeof WebKitBlobBuilder !== 'undefined'
? WebKitBlobBuilder
: !Browser.hasBlobConstructor
? console.log('warning: no BlobBuilder')
: null
? WebKitBlobBuilder
: !Browser.hasBlobConstructor
? console.log('warning: no BlobBuilder')
: null
Browser.URLObject =
typeof window !== 'undefined'
? window.URL
? window.URL
: window.webkitURL
? window.URL
: window.webkitURL
: undefined
if (!Module.noImageDecoding && typeof Browser.URLObject === 'undefined') {
if (!Module.noImageDecoding && !typeof Browser.URLObject === 'undefined') {
console.log(
'warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available.'
)
@ -8308,14 +8308,14 @@ var SYSCALLS = {
((tempDouble = stat.size),
+Math_abs(tempDouble) >= 1
? tempDouble > 0
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>>
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>>
0
: ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>>
: ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>>
0
: 0)
]),
(HEAP32[(buf + 40) >> 2] = tempI64[0]),
(HEAP32[(buf + 44) >> 2] = tempI64[1])
(HEAP32[(buf + 40) >> 2] = tempI64[0]),
(HEAP32[(buf + 44) >> 2] = tempI64[1])
HEAP32[(buf + 48) >> 2] = 4096
HEAP32[(buf + 52) >> 2] = stat.blocks
HEAP32[(buf + 56) >> 2] = (stat.atime.getTime() / 1e3) | 0
@ -8329,14 +8329,14 @@ var SYSCALLS = {
((tempDouble = stat.ino),
+Math_abs(tempDouble) >= 1
? tempDouble > 0
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>>
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>>
0
: ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>>
: ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>>
0
: 0)
]),
(HEAP32[(buf + 80) >> 2] = tempI64[0]),
(HEAP32[(buf + 84) >> 2] = tempI64[1])
(HEAP32[(buf + 80) >> 2] = tempI64[0]),
(HEAP32[(buf + 84) >> 2] = tempI64[1])
return 0
},
doMsync: function (addr, stream, len, flags, offset) {
@ -8558,41 +8558,41 @@ function ___sys_getdents64 (fd, dirp, count) {
type = FS.isChrdev(child.mode)
? 2
: FS.isDir(child.mode)
? 4
: FS.isLink(child.mode)
? 10
: 8
? 4
: FS.isLink(child.mode)
? 10
: 8
}
;(tempI64 = [
id >>> 0,
((tempDouble = id),
+Math_abs(tempDouble) >= 1
? tempDouble > 0
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) |
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) |
0) >>>
0
: ~~+Math_ceil(
(tempDouble - +(~~tempDouble >>> 0)) / 4294967296
) >>> 0
: ~~+Math_ceil(
(tempDouble - +(~~tempDouble >>> 0)) / 4294967296
) >>> 0
: 0)
]),
(HEAP32[(dirp + pos) >> 2] = tempI64[0]),
(HEAP32[(dirp + pos + 4) >> 2] = tempI64[1])
(HEAP32[(dirp + pos) >> 2] = tempI64[0]),
(HEAP32[(dirp + pos + 4) >> 2] = tempI64[1])
;(tempI64 = [
((idx + 1) * struct_size) >>> 0,
((tempDouble = (idx + 1) * struct_size),
+Math_abs(tempDouble) >= 1
? tempDouble > 0
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) |
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) |
0) >>>
0
: ~~+Math_ceil(
(tempDouble - +(~~tempDouble >>> 0)) / 4294967296
) >>> 0
: ~~+Math_ceil(
(tempDouble - +(~~tempDouble >>> 0)) / 4294967296
) >>> 0
: 0)
]),
(HEAP32[(dirp + pos + 8) >> 2] = tempI64[0]),
(HEAP32[(dirp + pos + 12) >> 2] = tempI64[1])
(HEAP32[(dirp + pos + 8) >> 2] = tempI64[0]),
(HEAP32[(dirp + pos + 12) >> 2] = tempI64[1])
HEAP16[(dirp + pos + 16) >> 1] = 280
HEAP8[(dirp + pos + 18) >> 0] = type
stringToUTF8(name, dirp + pos + 19, 256)
@ -8983,10 +8983,10 @@ function _fd_fdstat_get (fd, pbuf) {
const type = stream.tty
? 2
: FS.isDir(stream.mode)
? 3
: FS.isLink(stream.mode)
? 7
: 4
? 3
: FS.isLink(stream.mode)
? 7
: 4
HEAP8[pbuf >> 0] = type
return 0
} catch (e) {
@ -9022,14 +9022,14 @@ function _fd_seek (fd, offset_low, offset_high, whence, newOffset) {
((tempDouble = stream.position),
+Math_abs(tempDouble) >= 1
? tempDouble > 0
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>>
? (Math_min(+Math_floor(tempDouble / 4294967296), 4294967295) | 0) >>>
0
: ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>>
: ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>>
0
: 0)
]),
(HEAP32[newOffset >> 2] = tempI64[0]),
(HEAP32[(newOffset + 4) >> 2] = tempI64[1])
(HEAP32[newOffset >> 2] = tempI64[0]),
(HEAP32[(newOffset + 4) >> 2] = tempI64[1])
if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null
return 0
} catch (e) {
@ -12816,7 +12816,7 @@ self.setIsPaused = function (isPaused) {
self.offscreenRender = function (force) {
self.rafId = 0
self.renderPending = false
const startTime = performance.now()
// const startTime = performance.now()
const result = self.octObj.renderImage(
self.getCurrentTime() + self.delay,
self.changed
@ -12824,16 +12824,16 @@ self.offscreenRender = function (force) {
const changed = Module.getValue(self.changed, 'i32')
if ((Number(changed) !== 0 || force) && self.offscreenCanvas) {
const images = self.buildResultImage(result)
const newTime = performance.now()
const libassTime = newTime - startTime
// const newTime = performance.now()
// const libassTime = newTime - startTime
const promises = []
for (let i = 0; i < images.length; i++) {
promises[i] = createImageBitmap(images[i].image)
}
Promise.all(promises).then(function (bitmaps) {
const decodeTime = performance.now() - newTime
// const decodeTime = performance.now() - newTime
function renderFastFrames () {
const beforeDrawTime = performance.now()
// const beforeDrawTime = performance.now()
self.offscreenCanvasCtx.clearRect(
0,
0,
@ -12847,17 +12847,17 @@ self.offscreenRender = function (force) {
images[i].y
)
}
const drawTime = performance.now() - beforeDrawTime
console.log(
bitmaps.length +
' bitmaps, libass: ' +
libassTime +
'ms, decode: ' +
decodeTime +
'ms, draw: ' +
drawTime +
'ms'
)
// const drawTime = performance.now() - beforeDrawTime
// console.log(
// bitmaps.length +
// ' bitmaps, libass: ' +
// libassTime +
// 'ms, decode: ' +
// decodeTime +
// 'ms, draw: ' +
// drawTime +
// 'ms'
// )
}
self.requestAnimationFrame(renderFastFrames)
})