mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-19 15:42:05 +00:00
feat: sub, dub, partial dub audio labels
This commit is contained in:
parent
0d53446a05
commit
9367679319
9 changed files with 158 additions and 5 deletions
|
|
@ -2,6 +2,7 @@
|
|||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
export let mediaList
|
||||
|
||||
let current = mediaList[0]
|
||||
|
|
@ -69,6 +70,9 @@
|
|||
{current.duration + ' Minutes'}
|
||||
</span>
|
||||
{/if}
|
||||
<span class='text-nowrap d-flex align-items-center'>
|
||||
<AudioLabel media={current} banner={true}/>
|
||||
</span>
|
||||
{#if current.season || current.seasonYear}
|
||||
<span class='text-nowrap d-flex align-items-center'>
|
||||
{[current.season?.toLowerCase(), current.seasonYear].filter(s => s).join(' ')}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { click } from '@/modules/click.js'
|
||||
import { countdown } from '@/modules/util.js'
|
||||
import { page } from '@/App.svelte'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
|
||||
|
|
@ -17,8 +18,9 @@
|
|||
<div class='card m-0 p-0 overflow-hidden pointer content-visibility-auto'
|
||||
style:--color={media.coverImage.color || '#1890ff'}>
|
||||
<div class='row h-full'>
|
||||
<div class='col-4'>
|
||||
<div class='col-4 d-inline-block position-relative'>
|
||||
<img loading='lazy' src={media.coverImage.extraLarge || ''} alt='cover' class='cover-img w-full h-full' />
|
||||
<AudioLabel {media} />
|
||||
</div>
|
||||
<div class='col-8 h-full card-grid'>
|
||||
<div class='px-15 py-10 bg-very-dark'>
|
||||
|
|
@ -59,6 +61,9 @@
|
|||
{:else if media.duration}
|
||||
<span class='text-nowrap'>{media.duration + ' Minutes'}</span>
|
||||
{/if}
|
||||
<span class='text-nowrap'>
|
||||
<AudioLabel {media} banner={true}/>
|
||||
</span>
|
||||
{#if media.status}
|
||||
<span class='text-nowrap'>{media.status?.toLowerCase().replace(/_/g, ' ')}</span>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
|
||||
|
|
@ -119,6 +120,9 @@
|
|||
{media.duration + ' Minutes'}
|
||||
</span>
|
||||
{/if}
|
||||
<span class='text-nowrap d-flex align-items-center'>
|
||||
<AudioLabel {media} banner={true}/>
|
||||
</span>
|
||||
{#if media.season || media.seasonYear}
|
||||
<span class='text-nowrap d-flex align-items-center'>
|
||||
{[media.season?.toLowerCase(), media.seasonYear].filter(s => s).join(' ')}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { formatMap, statusColorMap } from '@/modules/anime.js'
|
||||
import { hoverClick } from '@/modules/click.js'
|
||||
import { countdown } from '@/modules/util.js'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
|
||||
import { page } from '@/App.svelte'
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
|
|
@ -36,8 +37,10 @@
|
|||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<img loading='lazy' src={media.coverImage.extraLarge || ''} alt='cover' class='cover-img w-full rounded' style:--color={media.coverImage.color || '#1890ff'} />
|
||||
|
||||
<div class="d-inline-block position-relative">
|
||||
<img loading='lazy' src={media.coverImage.extraLarge || ''} alt='cover' class='cover-img w-full rounded' style:--color={media.coverImage.color || '#1890ff'} />
|
||||
<AudioLabel {media} />
|
||||
</div>
|
||||
<div class='text-white font-weight-very-bold font-size-16 pt-15 title overflow-hidden'>
|
||||
{#if media.mediaListEntry?.status}
|
||||
<div style:--statusColor={statusColorMap[media.mediaListEntry.status]} class='list-status-circle d-inline-flex overflow-hidden mr-5' title={media.mediaListEntry.status} />
|
||||
|
|
|
|||
1
common/modules/al.d.ts
vendored
1
common/modules/al.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
|||
export type Media = {
|
||||
id: number
|
||||
idMal: number
|
||||
title: {
|
||||
romaji?: string
|
||||
english?: string
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { alToken } from '@/modules/settings.js'
|
|||
import { toast } from 'svelte-sonner'
|
||||
import { sleep } from './util.js'
|
||||
|
||||
const codes = {
|
||||
export const codes = {
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
|
|
@ -465,7 +465,7 @@ class AnilistClient {
|
|||
async getUserLists (variables) {
|
||||
const userId = this.userID?.viewer?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
if (variables.sort) { variables.sort = variables.sort.replace('USER_SCORE_DESC', 'SCORE_DESC')}
|
||||
variables.sort = variables.sort?.replace('USER_SCORE_DESC', 'SCORE_DESC');
|
||||
const query = /* js */`
|
||||
query($id: Int $sort: [MediaListSort]){
|
||||
MediaListCollection(userId: $id, type: ANIME, sort: $sort, forceSingleCompletedList: true) {
|
||||
|
|
|
|||
55
common/modules/audiolabel.js
Normal file
55
common/modules/audiolabel.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { toast } from 'svelte-sonner'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { codes } from '@/modules/anilist.js'
|
||||
|
||||
export const dubInfo = writable()
|
||||
|
||||
const initialized = writable(false)
|
||||
|
||||
|
||||
export async function cacheDubs() {
|
||||
await initialized.subscribe(async value => {
|
||||
if (!value) {
|
||||
dubInfo.value = await getDubInfo()
|
||||
initialized.set(true)
|
||||
// update dubInfo every hour
|
||||
setInterval(() => dubInfo.value = getDubInfo(), 1000 * 60 * 60)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function getDubInfo() {
|
||||
let res = {}
|
||||
try {
|
||||
res = await fetch('https://raw.githubusercontent.com/MAL-Dubs/MAL-Dubs/main/data/dubInfo.json')
|
||||
} catch (e) {
|
||||
if (!res || res.status !== 404) throw e
|
||||
}
|
||||
if (!res.ok && (res.status === 429 || res.status === 500)) {
|
||||
throw res
|
||||
}
|
||||
let json = null
|
||||
try {
|
||||
json = await res.json()
|
||||
} catch (error) {
|
||||
if (res.ok) printError(error)
|
||||
}
|
||||
if (!res.ok) {
|
||||
if (json) {
|
||||
for (const error of json?.errors || []) {
|
||||
printError(error)
|
||||
}
|
||||
} else {
|
||||
printError(res)
|
||||
}
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
function printError(error) {
|
||||
console.warn(error)
|
||||
toast.error('Dub Caching Failed', {
|
||||
description: `Failed to load dub information!\nTry again in a minute.\n${error.status || 429} - ${error.message || codes[error.status || 429]}`,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
77
common/views/ViewAnime/AudioLabel.svelte
Normal file
77
common/views/ViewAnime/AudioLabel.svelte
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { settings } from '@/modules/settings.js'
|
||||
import { cacheDubs } from '@/modules/audiolabel.js'
|
||||
import { dubInfo } from '@/modules/audiolabel.js'
|
||||
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media = null
|
||||
|
||||
export let banner = false
|
||||
export let viewAnime = false
|
||||
export let example = false
|
||||
|
||||
let isDubbed = false
|
||||
let isPartial = false
|
||||
|
||||
/**
|
||||
* @param {number} id
|
||||
*/
|
||||
async function setLabel(id) {
|
||||
const info = await dubInfo
|
||||
if (info && info.value) {
|
||||
isDubbed = info.value.dubbed.includes(id)
|
||||
isPartial = info.value.incomplete.includes(id)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await cacheDubs()
|
||||
if (media != null) {
|
||||
await setLabel(media.idMal)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if !banner && !viewAnime && !example && settings.value.cardAudio}
|
||||
<span class='material-symbols-outlined font-size-24 label position-absolute {isDubbed ? 'dubbed' : isPartial ? 'incomplete' : 'subbed'}'>
|
||||
{isDubbed ? 'mic' : isPartial ? 'mic_off' : 'closed_caption'}
|
||||
</span>
|
||||
{:else if !viewAnime && !example}
|
||||
{isDubbed ? 'Dub' : isPartial ? 'Partial Dub' : 'Sub'}
|
||||
{:else if !example}
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'>
|
||||
{isDubbed ? 'mic' : isPartial ? 'mic_off' : 'closed_caption'}
|
||||
</span>
|
||||
<span class='mr-20'>
|
||||
{isDubbed ? 'Dub' : isPartial ? 'Partial Dub' : 'Sub'}
|
||||
</span>
|
||||
{:else}
|
||||
<div>
|
||||
<span class='material-symbols-outlined font-size-24 label ml-20 position-relative subbed'>closed_caption</span>
|
||||
<span class='position-relative ml-2'>Sub Only</span>
|
||||
<span class='material-symbols-outlined font-size-24 label ml-20 position-relative incomplete'>mic_off</span>
|
||||
<span class='position-relative ml-2'>Partial Dub</span>
|
||||
<span class='material-symbols-outlined font-size-24 label ml-20 position-relative dubbed'>mic</span>
|
||||
<span class='position-relative ml-2'>Dub</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.label {
|
||||
padding: 0.4rem 0.2rem;
|
||||
border-radius: 0.8rem;
|
||||
top: 0.625rem;
|
||||
right: 0.625rem;
|
||||
z-index: 1;
|
||||
}
|
||||
.dubbed {
|
||||
background-color: rgba(121, 97, 9, 0.7);
|
||||
}
|
||||
.subbed {
|
||||
background-color: rgba(76, 41, 124, 0.7);
|
||||
}
|
||||
.incomplete {
|
||||
background-color: rgba(221, 100, 25, 0.7);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
import Details from './Details.svelte'
|
||||
import EpisodeList from './EpisodeList.svelte'
|
||||
import ToggleList from './ToggleList.svelte'
|
||||
import AudioLabel from './AudioLabel.svelte'
|
||||
import Following from './Following.svelte'
|
||||
import smoothScroll from '@/modules/scroll.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
|
|
@ -114,6 +115,9 @@
|
|||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
<div class='d-flex flex-row mt-10'>
|
||||
<AudioLabel {media} viewAnime={true}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class='d-flex flex-row flex-wrap'>
|
||||
<button class='btn btn-lg btn-secondary w-250 text-dark font-weight-bold shadow-none border-0 d-flex align-items-center justify-content-center mr-10 mt-20'
|
||||
|
|
|
|||
Loading…
Reference in a new issue