fix: S2 text being removed from titles, player failing on no media, improve resolveFileMedia speed

This commit is contained in:
ThaUnknown 2022-05-19 22:29:26 +02:00
parent 26a1741aae
commit 8969d293e4
6 changed files with 133 additions and 135 deletions

View file

@ -1,6 +1,6 @@
{
"name": "Miru",
"version": "2.2.3",
"version": "2.2.4",
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"main": "src/index.js",
"homepage": "https://github.com/ThaUnknown/miru#readme",

View file

@ -11,7 +11,7 @@
<link href='/lib/halfmoon.css' rel='stylesheet'>
<link href="/lib/Material-Icons.css" rel="stylesheet">
<script defer type="module" src="/src/main.js"></script>
<script defer type="module" src="/src/main.js" async></script>
</head>
<body class="dark-mode with-custom-webkit-scrollbars with-custom-css-scrollbars">

View file

@ -97,7 +97,6 @@
exclusions.push('AC3', 'AC-3')
}
video.remove()
console.log(exclusions)
async function getRSSEntries ({ media, episode, mode }) {
// mode cuts down on the amt of queries made

View file

@ -7,7 +7,7 @@
export let media = null
let fileMedia = null
let hadImage = false
export function updateMedia(fileMed) {
export function updateMedia (fileMed) {
if (!fileMedia) {
setDiscordRPC(fileMed)
}
@ -20,34 +20,34 @@
const metadata =
fileMedia.episodeThumbnail || fileMedia.mediaCover
? new MediaMetadata({
title: name,
artwork: [
{
src: fileMedia.episodeThumbnail || fileMedia.mediaCover,
sizes: '256x256',
type: 'image/jpg'
}
]
})
title: name,
artwork: [
{
src: fileMedia.episodeThumbnail || fileMedia.mediaCover,
sizes: '256x256',
type: 'image/jpg'
}
]
})
: new MediaMetadata({ title: name })
if (fileMedia.parseObject?.release_group) metadata.artist = fileMedia.parseObject.release_group
navigator.mediaSession.metadata = metadata
}
function setDiscordRPC(fileMedia) {
function setDiscordRPC (fileMedia) {
if (fileMedia) {
window.IPC.emit('discord', {
activity: {
details: fileMedia.media.title.userPreferred,
state: 'Watching Episode' + (!fileMedia.media.episodes ? ` ${fileMedia.episodeNumber}` : ''),
details: fileMedia.media?.title.userPreferred || fileMedia.parseObject.anime_title,
state: 'Watching Episode ' + ((!fileMedia.media?.episodes && fileMedia.episodeNumber) || ''),
timestamps: {
start: Date.now()
},
party: {
size: (fileMedia.episodeNumber && fileMedia.media.episodes && [fileMedia.episodeNumber, fileMedia.media.episodes]) || undefined
size: (fileMedia.episodeNumber && fileMedia.media?.episodes && [fileMedia.episodeNumber, fileMedia.media.episodes]) || undefined
},
assets: {
large_text: fileMedia.media.title.userPreferred,
large_image: fileMedia.media.coverImage.extraLarge,
large_text: fileMedia.media?.title.userPreferred,
large_image: fileMedia.media?.coverImage.extraLarge,
small_image: 'logo',
small_text: 'https://github.com/ThaUnknown/miru'
},
@ -74,11 +74,11 @@
paused = detail.paused
})
function updatew2g() {
function updatew2g () {
w2gEmitter.emit('player', { time: Math.floor(currentTime), paused })
}
async function mediaChange(current, image) {
async function mediaChange (current, image) {
if (current && 'mediaSession' in navigator) {
if (!media || (!hadImage && image)) {
// filename is already mapped so this *should* be fine
@ -92,8 +92,8 @@
export let miniplayer = false
export let page
$: updateFiles(files)
export let files = []
$: updateFiles(files)
let src = null
let video = null
let container = null
@ -111,16 +111,16 @@
let bufferTimeout = null
let subHeaders = null
let pip = false
let presentationRequest = null
const presentationRequest = null
let presentationConnection = null
let canCast = false
const canCast = false
let isFullscreen = false
let ended = false
let volume = localStorage.getItem('volume') || 1
let playbackRate = 1
$: localStorage.setItem('volume', volume || 0)
function checkAudio() {
function checkAudio () {
if ('audioTracks' in HTMLVideoElement.prototype && !video.audioTracks.length) {
addToast({
text: "This torrent's audio codec is not supported, try a different release by disabling Autoplay Torrents in RSS settings.",
@ -129,12 +129,12 @@
})
}
}
function getFPS() {
function getFPS () {
video.fps = new Promise(resolve => {
let lastmeta = null
let count = 0
function handleFrames(now, metadata) {
function handleFrames (now, metadata) {
if (count) {
// resolve on 2nd frame, 1st frame might be a cut-off
if (lastmeta) {
@ -169,7 +169,7 @@
}
// plays one frame
function playFrame() {
function playFrame () {
let wasPaused = false
video.requestVideoFrameCallback(() => {
if (wasPaused) paused = true
@ -193,16 +193,16 @@
// })
// }
//document.fullscreenElement isn't reactive
// document.fullscreenElement isn't reactive
document.addEventListener('fullscreenchange', () => {
isFullscreen = !!document.fullscreenElement
})
function handleHeaders() {
function handleHeaders () {
subHeaders = subs?.headers
}
function updateFiles(files) {
function updateFiles (files) {
if (files && files.length) {
videos = files.filter(file => videoRx.test(file.name))
if (videos?.length) {
@ -222,7 +222,7 @@
}
}
async function handleCurrent(file) {
async function handleCurrent (file) {
if (file) {
if (thumbnailData.video?.src) URL.revokeObjectURL(video?.src)
Object.assign(thumbnailData, {
@ -243,7 +243,7 @@
let hasNext = false
let hasLast = false
function checkAvail(current) {
function checkAvail (current) {
if ((media?.nextAiringEpisode?.episode - 1 || media?.episodes) > fileMedia?.episodeNumber) {
hasNext = true
} else if (videos.indexOf(current) !== videos.length - 1) {
@ -260,11 +260,11 @@
}
}
function initSubs() {
function initSubs () {
if (subs) subs.destroy()
subs = new Subtitles(video, files, current, handleHeaders)
}
function cycleSubtitles() {
function cycleSubtitles () {
if (current && subs?.headers) {
const tracks = subs.headers.filter(header => header)
const index = tracks.indexOf(subs.headers[subs.current]) + 1
@ -274,33 +274,33 @@
let subDelay = 0
$: updateDelay(subDelay)
function updateDelay(delay) {
function updateDelay (delay) {
if (subs?.renderer) subs.renderer.timeOffset = delay
}
let currentTime = 0
$: progress = currentTime / duration
$: targetTime = (!paused && currentTime) || targetTime
function handleMouseDown({ target }) {
function handleMouseDown ({ target }) {
wasPaused = paused
paused = true
targetTime = target.value * duration
}
function handleMouseUp() {
function handleMouseUp () {
paused = wasPaused
currentTime = targetTime
}
function handleProgress({ target }) {
function handleProgress ({ target }) {
targetTime = target.value * duration
}
function autoPlay() {
function autoPlay () {
if (!miniplayer) video.play()
}
function playPause() {
function playPause () {
paused = !paused
}
function toggleMute() {
function toggleMute () {
muted = !muted
}
let visibilityPaused = true
@ -314,10 +314,10 @@
}
}
})
function tryPlayNext() {
function tryPlayNext () {
if (set.playerAutoplay) playNext()
}
function playNext() {
function playNext () {
if (hasNext) {
const index = videos.indexOf(current)
if (index + 2 < videos.length) {
@ -327,7 +327,7 @@
}
}
}
function playLast() {
function playLast () {
if (hasLast) {
const index = videos.indexOf(current)
if (index > 1) {
@ -337,10 +337,10 @@
}
}
}
function toggleFullscreen() {
function toggleFullscreen () {
document.fullscreenElement ? document.exitFullscreen() : container.requestFullscreen()
}
function seek(time) {
function seek (time) {
if (time === 85 && currentTime < 10) {
targetTime = currentTime = 90
} else if (time === 85 && duration - currentTime < 90) {
@ -349,13 +349,13 @@
targetTime = currentTime += time
}
}
function forward() {
function forward () {
seek(2)
}
function rewind() {
function rewind () {
seek(-2)
}
function selectAudio(id) {
function selectAudio (id) {
if (id !== undefined) {
for (const track of video.audioTracks) {
track.enabled = track.id === id
@ -363,7 +363,7 @@
seek(-0.5) // stupid fix because video freezes up when chaging tracks
}
}
function toggleCast() {
function toggleCast () {
if (video.readyState) {
if (presentationConnection) {
presentationConnection?.terminate()
@ -372,7 +372,7 @@
}
}
}
function togglePopout() {
function togglePopout () {
if (video.readyState) {
if (!subs?.renderer) {
if (video !== document.pictureInPictureElement) {
@ -414,7 +414,7 @@
}
}
let showKeybinds = false
async function handleKeydown({ key }) {
async function handleKeydown ({ key }) {
if (!miniplayer) {
switch (key) {
case 'r':
@ -481,7 +481,7 @@
}
}
function getBurnIn(noSubs) {
function getBurnIn (noSubs) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
let loop = null
@ -502,7 +502,7 @@
return { stream: canvas.captureStream(), destroy }
}
function initCast(event) {
function initCast (event) {
// these quality settings are likely to make cast overheat, oh noes!
let peer = new Peer({
polite: true,
@ -559,12 +559,12 @@
}
}
function immersePlayer() {
function immersePlayer () {
immersed = true
immerseTimeout = undefined
}
function resetImmerse() {
function resetImmerse () {
if (immerseTimeout) {
clearTimeout(immerseTimeout)
} else {
@ -573,7 +573,7 @@
immerseTimeout = setTimeout(immersePlayer, 8 * 1000)
}
function hideBuffering() {
function hideBuffering () {
if (bufferTimeout) {
clearTimeout(bufferTimeout)
bufferTimeout = null
@ -581,7 +581,7 @@
}
}
function showBuffering() {
function showBuffering () {
bufferTimeout = setTimeout(() => {
buffering = true
resetImmerse()
@ -604,7 +604,7 @@
}
let stats = null
let requestCallback = null
function toggleStats() {
function toggleStats () {
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
if (requestCallback) {
stats = null
@ -618,7 +618,7 @@
}
}
}
async function handleStats(now, metadata) {
async function handleStats (now, metadata) {
if (stats) {
stats = {
fps: await video.fps,
@ -633,8 +633,8 @@
setTimeout(() => video.requestVideoFrameCallback(handleStats), 200)
}
}
function getBufferHealth(time) {
for (let index = video.buffered.length; index--; ) {
function getBufferHealth (time) {
for (let index = video.buffered.length; index--;) {
if (time < video.buffered.end(index) && time > video.buffered.start(index)) {
return parseInt(video.buffered.end(index) - time)
}
@ -652,13 +652,13 @@
let hover = null
let hoverTime = 0
let hoverOffset = 0
function handleHover({ offsetX, target }) {
function handleHover ({ offsetX, target }) {
hoverOffset = offsetX / target.clientWidth
hoverTime = duration * hoverOffset
hover.style.setProperty('left', hoverOffset * 100 + '%')
thumbnail = thumbnailData.thumbnails[Math.floor(hoverTime / thumbnailData.interval)] || ' '
}
function createThumbnail(vid = video) {
function createThumbnail (vid = video) {
if (vid?.readyState >= 2) {
const index = Math.floor(vid.currentTime / thumbnailData.interval)
if (!thumbnailData.thumbnails[index]) {
@ -669,7 +669,7 @@
}
}
}
function initThumbnails() {
function initThumbnails () {
const height = 200 / (videoWidth / videoHeight)
if (!isNaN(height)) {
thumbnailData.interval = duration / 300 < 5 ? 5 : duration / 300
@ -677,7 +677,7 @@
}
}
function finishThumbnails() {
function finishThumbnails () {
const t0 = performance.now()
const video = document.createElement('video')
let index = 0
@ -715,7 +715,7 @@
let innerWidth, innerHeight, videoWidth, videoHeight
let menubarOffset = 0
// $: calcMenubarOffset(innerWidth, innerHeight, videoWidth, videoHeight)
function calcMenubarOffset(innerWidth, innerHeight, videoWidth, videoHeight) {
function calcMenubarOffset (innerWidth, innerHeight, videoWidth, videoHeight) {
// outerheight resize and innerheight resize is mutual, additionally update on metadata and app state change
if (videoWidth && videoHeight) {
// so windows is very dumb, and calculates windowed mode as if it was window XP, with the old bars, but not when maximised
@ -734,13 +734,13 @@
}
}
function toggleDropdown({ target }) {
function toggleDropdown ({ target }) {
target.classList.toggle('active')
target.closest('.dropdown').classList.toggle('show')
}
let completed = false
function checkCompletion() {
function checkCompletion () {
if (!completed && duration && video?.readyState && duration - 180 < currentTime) {
if (fileMedia?.media?.episodes || fileMedia?.media?.nextAiringEpisode?.episode) {
if (fileMedia.media.episodes || fileMedia.media.nextAiringEpisode?.episode > fileMedia.episodeNumber) {
@ -752,7 +752,7 @@
}
const torrent = {}
client.on('stats', updateStats)
function updateStats({ detail }) {
function updateStats ({ detail }) {
torrent.peers = detail.numPeers || 0
torrent.up = detail.uploadSpeed || 0
torrent.down = detail.downloadSpeed || 0
@ -763,13 +763,13 @@
ctx = bufferCanvas.getContext('2d')
clearCanvas()
})
function clearCanvas() {
function clearCanvas () {
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)'
ctx.fillRect(0, 0, bufferCanvas.width, 1)
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'
}
let imageData = null
function handlePieces({ detail }) {
function handlePieces ({ detail }) {
if (detail.constructor === Array) {
if (imageData) {
for (let i = 0; i < detail.length; ++i) {

View file

@ -370,7 +370,7 @@ query ($page: Int, $perPage: Int, $sort: [MediaSort], $type: MediaType, $search:
}
options.body = JSON.stringify({
query: query.replace(/\s/g, ''),
variables: variables
variables
})
return await handleRequest(options)

View file

@ -91,47 +91,51 @@ const postfix = {
}
async function resolveTitle (name) {
if (!(name in relations)) {
const method = { name, method: 'SearchName', perPage: 1, status: ['RELEASING', 'FINISHED'], sort: 'SEARCH_MATCH' }
const method = { name, method: 'SearchName', perPage: 1, status: ['RELEASING', 'FINISHED'], sort: 'SEARCH_MATCH' }
// inefficient but readable
// inefficient but readable
let media = null
// change S2 into Season 2 or 2nd Season
const match = method.name.match(/ S(\d)$/)
const oldname = method.name
try {
let media = null
// change S2 into Season 2 or 2nd Season
const match = method.name.match(/ S(\d)$/)
const oldname = method.name
try {
if (match) {
method.name = method.name.replace(/ S(\d)$/, ` ${match[1]}${postfix[match[1]] || 'th'} Season`)
media = (await alRequest(method)).data.Page.media[0]
if (!media) {
method.name = oldname.replace(/ S(\d)$/, ` Season ${match[1]}`)
media = (await alRequest(method)).data.Page.media[0]
}
} else {
media = (await alRequest(method)).data.Page.media[0]
}
// remove (TV)
if (!media) {
const match = method.name.match(/\(TV\)/)
if (match) {
method.name = method.name.replace(/ S(\d)$/, ` ${match[1]}${postfix[match[1]] || 'th'} Season`)
media = (await alRequest(method)).data.Page.media[0]
if (!media) {
method.name = oldname.replace(/ S(\d)$/, ` Season ${match[1]}`)
media = (await alRequest(method)).data.Page.media[0]
}
} else {
method.name = name.replace('(TV)', '').replace('-', '')
media = (await alRequest(method)).data.Page.media[0]
}
// remove (TV)
if (!media) {
const match = method.name.match(/\(TV\)/)
if (match) {
method.name = name.replace('(TV)', '').replace('-', '')
media = (await alRequest(method)).data.Page.media[0]
}
}
// remove 2020
if (!media) {
const match = method.name.match(/ (19[5-9]\d|20\d{2})/)
if (match) {
method.name = method.name.replace(/ (19[5-9]\d|20\d{2})/, '')
media = (await alRequest(method)).data.Page.media[0]
}
// remove 2020
if (!media) {
const match = method.name.match(/ (19[5-9]\d|20\d{2})/)
if (match) {
method.name = method.name.replace(/ (19[5-9]\d|20\d{2})/, '')
media = (await alRequest(method)).data.Page.media[0]
}
}
} catch (e) { }
}
} catch (e) { }
if (media?.id) relations[name] = media.id
}
if (media) relations[name] = media
}
function getParseObjTitle (obj) {
let title = obj.anime_title
if (obj.anime_season) title += ' S' + obj.anime_season
return title
}
export async function resolveFileMedia (opts) {
@ -140,28 +144,23 @@ export async function resolveFileMedia (opts) {
? opts.fileName.map(name => anitomyscript(name))
: [anitomyscript(opts.fileName)]
const parseObjs = await Promise.all(parsePromises)
await PromiseBatch(resolveTitle, [...new Set(parseObjs.map(obj => obj.anime_title).filter(title => !(title in relations)))], 10)
const assoc = {}
for (let ids = [...new Set(parseObjs.map(obj => relations[obj.anime_title]))]; ids.length; ids = ids.slice(50)) {
for await (const media of (await alRequest({ method: 'SearchIDS', id: ids.slice(0, 50), perPage: 50 }))?.data?.Page.media || []) {
assoc[media.id] = media
}
}
// batches promises in 10 at a time, because of CF burst protection, which still sometimes gets triggered :/
await PromiseBatch(resolveTitle, [...new Set(parseObjs.map(obj => getParseObjTitle(obj)))].filter(title => !(title in relations)), 10)
const fileMedias = []
for (const praseObj of parseObjs) {
for (const parseObj of parseObjs) {
let failed = false
let episode
let media = assoc[relations[praseObj.anime_title]]
let media = relations[getParseObjTitle(parseObj)]
// resolve episode, if movie, dont.
const maxep = media?.nextAiringEpisode?.episode || media?.episodes
if ((media?.format !== 'MOVIE' || maxep) && praseObj.episode_number) {
if (praseObj.episode_number.constructor === Array) {
if ((media?.format !== 'MOVIE' || maxep) && parseObj.episode_number) {
if (parseObj.episode_number.constructor === Array) {
// is an episode range
if (parseInt(praseObj.episode_number[0]) === 1) {
if (parseInt(parseObj.episode_number[0]) === 1) {
// if it starts with #1 and overflows then it includes more than 1 season in a batch, cant fix this cleanly, name is parsed per file basis so this shouldnt be an issue
episode = `${praseObj.episode_number[0]} ~ ${praseObj.episode_number[1]}`
episode = `${parseObj.episode_number[0]} ~ ${parseObj.episode_number[1]}`
} else {
if (maxep && parseInt(praseObj.episode_number[1]) > maxep) {
if (maxep && parseInt(parseObj.episode_number[1]) > maxep) {
// get root media to start at S1, instead of S2 or some OVA due to parsing errors
// this is most likely safe, if it was relative episodes then it would likely use an accurate title for the season
// if they didnt use an accurate title then its likely an absolute numbering scheme
@ -170,43 +169,43 @@ export async function resolveFileMedia (opts) {
const root = prequel && (await resolveSeason({ media: (await alRequest({ method: 'SearchIDSingle', id: prequel.id })).data.Media, force: true })).media
// if highest value is bigger than episode count or latest streamed episode +1 for safety, parseint to math.floor a number like 12.5 - specials - in 1 go
const result = await resolveSeason({ media: root || media, episode: praseObj.episode_number[1] })
const result = await resolveSeason({ media: root || media, episode: parseObj.episode_number[1] })
media = result.rootMedia
const diff = praseObj.episode_number[1] - result.episode
episode = `${praseObj.episode_number[0] - diff} ~ ${result.episode}`
const diff = parseObj.episode_number[1] - result.episode
episode = `${parseObj.episode_number[0] - diff} ~ ${result.episode}`
failed = result.failed
} else {
// cant find ep count or range seems fine
episode = `${Number(praseObj.episode_number[0])} ~ ${Number(praseObj.episode_number[1])}`
episode = `${Number(parseObj.episode_number[0])} ~ ${Number(parseObj.episode_number[1])}`
}
}
} else {
if (maxep && parseInt(praseObj.episode_number) > maxep) {
if (maxep && parseInt(parseObj.episode_number) > maxep) {
// see big comment above
const prequel = findEdge(media, 'PREQUEL')?.node || ((media.format === 'OVA' || media.format === 'ONA') && findEdge(media, 'PARENT')?.node)
const root = prequel && (await resolveSeason({ media: (await alRequest({ method: 'SearchIDSingle', id: prequel.id })).data.Media, force: true })).media
// value bigger than episode count
const result = await resolveSeason({ media: root || media, episode: parseInt(praseObj.episode_number) })
const result = await resolveSeason({ media: root || media, episode: parseInt(parseObj.episode_number) })
media = result.rootMedia
episode = result.episode
failed = result.failed
} else {
// cant find ep count or episode seems fine
episode = Number(praseObj.episode_number)
episode = Number(parseObj.episode_number)
}
}
}
const streamingEpisode = media?.streamingEpisodes.filter(episode => episodeRx.exec(episode.title) && Number(episodeRx.exec(episode.title)[1]) === Number(praseObj.episode_number))[0]
const streamingEpisode = media?.streamingEpisodes.filter(episode => episodeRx.exec(episode.title) && Number(episodeRx.exec(episode.title)[1]) === Number(parseObj.episode_number))[0]
fileMedias.push({
mediaTitle: media?.title.userPreferred,
episodeNumber: episode,
mediaTitle: media?.title.userPreferred || parseObj.anime_title,
episodeNumber: episode || parseObj.episode_number,
episodeTitle: streamingEpisode && episodeRx.exec(streamingEpisode.title)[2],
episodeThumbnail: streamingEpisode?.thumbnail,
mediaCover: media?.coverImage.medium,
name: 'Miru',
parseObject: praseObj,
media: media,
parseObject: parseObj,
media,
failed
})
}
@ -268,4 +267,4 @@ export async function resolveSeason (opts) {
return resolveSeason({ media, episode, increment, offset, rootMedia, force })
}
export const relations = {}
const relations = {}