mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-20 19:22:06 +00:00
feat: subtitle dialogue style overrides, increase al cache duration, optimise fonts
This commit is contained in:
parent
393276c9e2
commit
6dfc5f70e9
21 changed files with 275 additions and 22 deletions
|
|
@ -11,6 +11,15 @@ export default tseslint.config(
|
|||
tsconfigRootDir: import.meta.dirname,
|
||||
svelteConfig
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"svelte/no-useless-mustaches": [
|
||||
"error",
|
||||
{
|
||||
"ignoreIncludesComment": false,
|
||||
"ignoreStringEscape": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "6.3.29",
|
||||
"version": "6.3.30",
|
||||
"license": "BUSL-1.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.14.4",
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
"eslint-config-standard-universal": "^1.0.6",
|
||||
"gql.tada": "^1.8.10",
|
||||
"hayase-extensions": "github:hayase-app/extensions",
|
||||
"jassub": "^1.7.20",
|
||||
"jassub": "^1.8.2",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-check": "^4.2.1",
|
||||
"svelte-radix": "^1.1.1",
|
||||
|
|
|
|||
|
|
@ -157,8 +157,8 @@ importers:
|
|||
specifier: github:hayase-app/extensions
|
||||
version: https://codeload.github.com/hayase-app/extensions/tar.gz/a9415c297a899459be34a135a9adbcd72115e019
|
||||
jassub:
|
||||
specifier: ^1.7.20
|
||||
version: 1.7.20
|
||||
specifier: ^1.8.2
|
||||
version: 1.8.2
|
||||
svelte:
|
||||
specifier: ^4.2.19
|
||||
version: 4.2.19
|
||||
|
|
@ -1659,8 +1659,8 @@ packages:
|
|||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
jassub@1.7.20:
|
||||
resolution: {integrity: sha512-0ADY2BdpPc6qQ6DFetg3S0TAnt530dgvxo/Gx0jyCygjyIhK6ndI47wlzwFOZo7IN+aPRaGqnhxTATf5J0j3OQ==}
|
||||
jassub@1.8.2:
|
||||
resolution: {integrity: sha512-uCcuw2bxOkkoHbr+0lu3lZL0o7pNDfXiofz+CndfLes+pVP9qNQ2eIvAqj98BABr2FnCOScbmWGlc2HpdiDRNg==}
|
||||
|
||||
jiti@1.21.6:
|
||||
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
|
||||
|
|
@ -4224,7 +4224,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jassub@1.7.20:
|
||||
jassub@1.8.2:
|
||||
dependencies:
|
||||
rvfc-polyfill: 1.0.7
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
export { className as class }
|
||||
</script>
|
||||
|
||||
<div class={cn('flex flex-col md:flex-row md:items-center justify-between bg-neutral-950 rounded-md px-6 py-4 space-y-3 md:space-y-0 md:space-x-3', className)}>
|
||||
<div class={cn('flex flex-col md:flex-row md:items-center justify-between bg-neutral-950 rounded-md px-6 py-4 gap-3', className)}>
|
||||
<Label for={id} class='space-1 block leading-[unset] grow'>
|
||||
<div class='font-bold'>{title}</div>
|
||||
<div class='text-muted-foreground text-xs whitespace-pre-wrap block'>{description}</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import JASSUB, { type ASS_Event as ASSEvent } from 'jassub'
|
||||
import JASSUB, { type ASS_Style as ASSStyle, type ASS_Event as ASSEvent } from 'jassub'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
|
|
@ -6,15 +6,15 @@ import type { ResolvedFile } from './resolver'
|
|||
import type { TorrentFile } from '../../../../app'
|
||||
|
||||
import native from '$lib/modules/native'
|
||||
import { settings, SUPPORTS } from '$lib/modules/settings'
|
||||
import { type defaults, settings, SUPPORTS } from '$lib/modules/settings'
|
||||
import { fontRx, HashMap, subRx, subtitleExtensions, toTS } from '$lib/utils'
|
||||
|
||||
const defaultHeader = `[Script Info]
|
||||
Title: English (US)
|
||||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
PlayResX: 1280
|
||||
PlayResY: 720
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
ScaledBorderAndShadow: yes
|
||||
|
||||
[V4+ Styles]
|
||||
|
|
@ -24,6 +24,34 @@ Style: Default, Roboto Medium,52,&H00FFFFFF,&H00FFFFFF,&H00000000,&H00000000,0,0
|
|||
|
||||
`
|
||||
|
||||
const STYLE_OVERRIDES: Record<typeof defaults.subtitleStyle, Pick<ASSStyle, 'FontName' |'Spacing' | 'ScaleX'>> = {
|
||||
none: {
|
||||
FontName: 'Roboto Medium',
|
||||
Spacing: 0,
|
||||
ScaleX: 1
|
||||
},
|
||||
gandhisans: {
|
||||
FontName: 'Gandhi Sans',
|
||||
Spacing: 0.2,
|
||||
ScaleX: 0.98
|
||||
},
|
||||
notosans: {
|
||||
FontName: 'Noto Sans',
|
||||
Spacing: 0,
|
||||
ScaleX: 0.99
|
||||
},
|
||||
roboto: {
|
||||
FontName: 'Roboto Medium',
|
||||
Spacing: 0,
|
||||
ScaleX: 1
|
||||
}
|
||||
}
|
||||
|
||||
const OVERRIDE_FONTS: Partial<Record<typeof defaults.subtitleStyle, string>> = {
|
||||
gandhisans: '/GandhiSans-Bold.woff2',
|
||||
notosans: '/NotoSans-Bold.woff2'
|
||||
}
|
||||
|
||||
const stylesRx = /^Style:[^,]*/gm
|
||||
export default class Subtitles {
|
||||
video: HTMLVideoElement
|
||||
|
|
@ -40,12 +68,19 @@ export default class Subtitles {
|
|||
constructor (video: HTMLVideoElement, otherFiles: TorrentFile[], selected: ResolvedFile) {
|
||||
this.video = video
|
||||
this.selected = selected
|
||||
this.fonts = ['/Roboto.ttf', ...otherFiles.filter(file => fontRx.test(file.name)).map(file => file.url)]
|
||||
this.fonts = ['/Roboto.woff2', ...otherFiles.filter(file => fontRx.test(file.name)).map(file => file.url)]
|
||||
|
||||
this.current.subscribe(value => {
|
||||
this.selectCaptions(value)
|
||||
})
|
||||
|
||||
settings.subscribe(set => {
|
||||
if (this.set.subtitleStyle !== set.subtitleStyle) {
|
||||
this.set = set
|
||||
this._applyStyleOverride(set.subtitleStyle)
|
||||
}
|
||||
})
|
||||
|
||||
const subFiles = otherFiles.filter(({ name }) => subRx.test(name))
|
||||
|
||||
const fetchAndLoad = async (file: TorrentFile) => {
|
||||
|
|
@ -126,8 +161,10 @@ export default class Subtitles {
|
|||
native.attachments(this.selected.hash, this.selected.id).then(attachments => {
|
||||
for (const attachment of attachments) {
|
||||
if (fontRx.test(attachment.filename) || attachment.mimetype.toLowerCase().includes('font')) {
|
||||
this.fonts.push(attachment.url)
|
||||
this.renderer?.addFont(attachment.url)
|
||||
if (!this.fonts.includes(attachment.url)) {
|
||||
this.fonts.push(attachment.url)
|
||||
this.renderer?.addFont(attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -203,20 +240,53 @@ export default class Subtitles {
|
|||
subContent: defaultHeader,
|
||||
fonts: this.fonts,
|
||||
offscreenRender: !SUPPORTS.isAndroid,
|
||||
libassMemoryLimit: 1024, // TODO: more? check how much MPV uses
|
||||
libassGlyphLimit: 80000,
|
||||
maxRenderHeight: parseInt(this.set.subtitleRenderHeight) || 0,
|
||||
fallbackFont: 'roboto medium',
|
||||
availableFonts: {
|
||||
'roboto medium': './Roboto.ttf'
|
||||
},
|
||||
workerUrl: new URL('jassub/dist/jassub-worker.js', import.meta.url).toString(),
|
||||
wasmUrl: new URL('jassub/dist/jassub-worker.wasm', import.meta.url).toString(),
|
||||
legacyWasmUrl: new URL('jassub/dist/jassub-worker.wasm.js', import.meta.url).toString(),
|
||||
modernWasmUrl: new URL('jassub/dist/jassub-worker-modern.wasm', import.meta.url).toString(),
|
||||
useLocalFonts: this.set.missingFont,
|
||||
dropAllBlur: this.set.disableSubtitleBlur
|
||||
})
|
||||
|
||||
this._applyStyleOverride(this.set.subtitleStyle)
|
||||
}
|
||||
|
||||
_applyStyleOverride (subtitleStyle: typeof defaults.subtitleStyle) {
|
||||
if (subtitleStyle !== 'none') {
|
||||
const font = OVERRIDE_FONTS[subtitleStyle]
|
||||
if (font && !this.fonts.includes(font)) {
|
||||
this.fonts.push(font)
|
||||
this.renderer?.addFont(font)
|
||||
}
|
||||
const overrideStyle: ASSStyle = {
|
||||
Name: 'DialogueStyleOverride',
|
||||
FontSize: 72,
|
||||
PrimaryColour: 0xFFFFFF00,
|
||||
SecondaryColour: 0xFF000000,
|
||||
OutlineColour: 0,
|
||||
BackColour: 0,
|
||||
Bold: 1,
|
||||
Italic: 0,
|
||||
Underline: 0,
|
||||
StrikeOut: 0,
|
||||
ScaleY: 1,
|
||||
Angle: 0,
|
||||
BorderStyle: 1,
|
||||
Outline: 4,
|
||||
Shadow: 0,
|
||||
Alignment: 2,
|
||||
MarginL: 135,
|
||||
MarginR: 135,
|
||||
MarginV: 50,
|
||||
Encoding: 1,
|
||||
treat_fontname_as_pattern: 0,
|
||||
Blur: 0,
|
||||
Justify: 0,
|
||||
...STYLE_OVERRIDES[subtitleStyle]
|
||||
}
|
||||
this.renderer?.styleOverride(overrideStyle)
|
||||
}
|
||||
}
|
||||
|
||||
track (trackNumber: number | string) {
|
||||
|
|
|
|||
25
src/lib/components/ui/toggle-group/index.ts
Normal file
25
src/lib/components/ui/toggle-group/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { getContext, setContext } from 'svelte'
|
||||
|
||||
import Item from './toggle-group-item.svelte'
|
||||
import Root from './toggle-group.svelte'
|
||||
|
||||
import type { toggleVariants } from '$lib/components/ui/toggle/index.js'
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
|
||||
export type ToggleVariants = VariantProps<typeof toggleVariants>
|
||||
|
||||
export function setToggleGroupCtx (props: ToggleVariants) {
|
||||
setContext('toggleGroup', props)
|
||||
}
|
||||
|
||||
export function getToggleGroupCtx () {
|
||||
return getContext<ToggleVariants>('toggleGroup')
|
||||
}
|
||||
|
||||
export {
|
||||
Root,
|
||||
Item,
|
||||
//
|
||||
Root as ToggleGroup,
|
||||
Item as ToggleGroupItem
|
||||
}
|
||||
33
src/lib/components/ui/toggle-group/toggle-group-item.svelte
Normal file
33
src/lib/components/ui/toggle-group/toggle-group-item.svelte
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<script lang='ts'>
|
||||
import { ToggleGroup as ToggleGroupPrimitive } from 'bits-ui'
|
||||
|
||||
import { type ToggleVariants, getToggleGroupCtx } from './index.js'
|
||||
|
||||
import { toggleVariants } from '$lib/components/ui/toggle/index.js'
|
||||
import { cn } from '$lib/utils.js'
|
||||
|
||||
type $$Props = ToggleGroupPrimitive.ItemProps & ToggleVariants
|
||||
|
||||
let className: string | undefined | null = undefined
|
||||
|
||||
export { className as class }
|
||||
export let variant: $$Props['variant'] = 'default'
|
||||
export let size: $$Props['size'] = 'default'
|
||||
export let value: $$Props['value']
|
||||
|
||||
const ctx = getToggleGroupCtx()
|
||||
</script>
|
||||
|
||||
<ToggleGroupPrimitive.Item
|
||||
class={cn(
|
||||
toggleVariants({
|
||||
variant: ctx.variant ?? variant,
|
||||
size: ctx.size ?? size
|
||||
}),
|
||||
className
|
||||
)}
|
||||
{value}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</ToggleGroupPrimitive.Item>
|
||||
34
src/lib/components/ui/toggle-group/toggle-group.svelte
Normal file
34
src/lib/components/ui/toggle-group/toggle-group.svelte
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<script lang='ts'>
|
||||
|
||||
import { ToggleGroup as ToggleGroupPrimitive } from 'bits-ui'
|
||||
|
||||
import { setToggleGroupCtx } from './index.js'
|
||||
|
||||
import type { toggleVariants } from '$lib/components/ui/toggle/index.js'
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
|
||||
import { cn } from '$lib/utils.js'
|
||||
|
||||
type T = $$Generic<'single' | 'multiple'>
|
||||
type $$Props = ToggleGroupPrimitive.Props<T> & VariantProps<typeof toggleVariants>
|
||||
|
||||
let className: string | undefined | null = undefined
|
||||
export { className as class }
|
||||
export let variant: $$Props['variant'] = 'default'
|
||||
export let size: $$Props['size'] = 'default'
|
||||
export let value: $$Props['value'] = undefined
|
||||
|
||||
setToggleGroupCtx({
|
||||
variant,
|
||||
size
|
||||
})
|
||||
</script>
|
||||
|
||||
<ToggleGroupPrimitive.Root
|
||||
class={cn('flex items-center justify-center gap-1', className)}
|
||||
bind:value
|
||||
{...$$restProps}
|
||||
let:builder
|
||||
>
|
||||
<slot {builder} />
|
||||
</ToggleGroupPrimitive.Root>
|
||||
31
src/lib/components/ui/toggle/index.ts
Normal file
31
src/lib/components/ui/toggle/index.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { type VariantProps, tv } from 'tailwind-variants'
|
||||
|
||||
import Root from './toggle.svelte'
|
||||
|
||||
export const toggleVariants = tv({
|
||||
base: 'hover:bg-muted hover:text-muted-foreground focus-visible:ring-ring data-[state=on]:bg-accent data-[state=on]:text-accent-foreground inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50',
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
outline: 'border-input hover:bg-accent hover:text-accent-foreground border bg-transparent shadow-sm'
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-3',
|
||||
sm: 'h-8 px-2',
|
||||
lg: 'h-10 px-3'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
})
|
||||
|
||||
export type Variant = VariantProps<typeof toggleVariants>['variant']
|
||||
export type Size = VariantProps<typeof toggleVariants>['size']
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Toggle
|
||||
}
|
||||
29
src/lib/components/ui/toggle/toggle.svelte
Normal file
29
src/lib/components/ui/toggle/toggle.svelte
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script lang='ts'>
|
||||
import { Toggle as TogglePrimitive } from 'bits-ui'
|
||||
|
||||
import { type Size, type Variant, toggleVariants } from './index.js'
|
||||
|
||||
import { cn } from '$lib/utils.js'
|
||||
|
||||
type $$Props = TogglePrimitive.Props & {
|
||||
variant?: Variant
|
||||
size?: Size
|
||||
}
|
||||
type $$Events = TogglePrimitive.Events
|
||||
|
||||
let className: $$Props['class'] = undefined
|
||||
export let variant: $$Props['variant'] = 'default'
|
||||
export let size: $$Props['size'] = 'default'
|
||||
export let pressed: $$Props['pressed'] = undefined
|
||||
export { className as class }
|
||||
</script>
|
||||
|
||||
<TogglePrimitive.Root
|
||||
bind:pressed
|
||||
class={cn(toggleVariants({ variant, size, className }))}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
</TogglePrimitive.Root>
|
||||
|
|
@ -52,7 +52,7 @@ class AnilistClient {
|
|||
storage = makeDefaultStorage({
|
||||
idbName: 'graphcache-v3',
|
||||
onCacheHydrated: () => this.storagePromise.resolve(),
|
||||
maxAge: 14 // The maximum age of the persisted data in days
|
||||
maxAge: 31 // The maximum age of the persisted data in days
|
||||
})
|
||||
|
||||
client = new Client({
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export default {
|
|||
playerPause: true,
|
||||
playerAutocomplete: true,
|
||||
playerDeband: false,
|
||||
subtitleStyle: 'none' as 'none' | 'gandhisans' | 'notosans' | 'roboto',
|
||||
searchQuality: '1080' as keyof typeof videoResolutions,
|
||||
rssFeedsNew: SUPPORTS.isAndroid ? [['New Releases', 'SubsPlease']] : [],
|
||||
searchAutoSelect: true,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { SingleCombo } from '$lib/components/ui/combobox'
|
||||
import { Input } from '$lib/components/ui/input'
|
||||
import { Switch } from '$lib/components/ui/switch'
|
||||
import * as ToggleGroup from '$lib/components/ui/toggle-group'
|
||||
import native from '$lib/modules/native'
|
||||
import { settings, languageCodes, subtitleResolutions } from '$lib/modules/settings'
|
||||
|
||||
|
|
@ -27,6 +28,26 @@
|
|||
<SingleCombo bind:value={$settings.subtitleRenderHeight} items={subtitleResolutions} class='w-32 shrink-0 border-input border' />
|
||||
</SettingCard>
|
||||
|
||||
<SettingCard class='md:flex-col md:items-start' title='Subtitle Dialogue Style Overrides' description={'Selectively override the default dialogue style for subtitles. This will not change the style of typesetting.\n\nWarning: the heuristic used for deciding when to override the style is rather rough, and enabling this option can lead to incorrectly rendered subtitles.'}>
|
||||
<ToggleGroup.Root type='single' class='grid sm:grid-cols-2 gap-3' bind:value={$settings.subtitleStyle}>
|
||||
<ToggleGroup.Item value='none' class='h-auto aspect-video text-4xl px-0 relative'>
|
||||
<div class='absolute top-4 text-xl font-bold'>None</div>🚫
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value='gandhisans' class='h-auto px-0 relative'>
|
||||
<div class='absolute top-4 text-xl font-bold'>Gandhi Sans Bold</div>
|
||||
<img src='/gandhisans.png' class='w-full' />
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value='notosans' class='h-auto px-0 relative'>
|
||||
<div class='absolute top-4 text-xl font-bold'>Noto Sans Bold</div>
|
||||
<img src='/notosans.png' class='w-full' />
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value='roboto' class='h-auto px-0 relative'>
|
||||
<div class='absolute top-4 text-xl font-bold'>Roboto Bold</div>
|
||||
<img src='/roboto.png' class='w-full' />
|
||||
</ToggleGroup.Item>
|
||||
</ToggleGroup.Root>
|
||||
</SettingCard>
|
||||
|
||||
<div class='font-weight-bold text-xl font-bold'>Language Settings</div>
|
||||
<SettingCard title='Preferred Subtitle Language' description="What subtitle language to automatically select when a video is loaded if it exists. This won't find torrents with this language automatically. If not found defaults to English.">
|
||||
<SingleCombo bind:value={$settings.subtitleLanguage} items={languageCodes} class='w-36 shrink-0 border-input border' />
|
||||
|
|
|
|||
BIN
static/GandhiSans-Bold.woff2
Normal file
BIN
static/GandhiSans-Bold.woff2
Normal file
Binary file not shown.
BIN
static/NotoSans-Bold.woff2
Normal file
BIN
static/NotoSans-Bold.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
static/Roboto.woff2
Normal file
BIN
static/Roboto.woff2
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 402 KiB |
BIN
static/gandhisans.png
Normal file
BIN
static/gandhisans.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
BIN
static/notosans.png
Normal file
BIN
static/notosans.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
static/roboto.png
Normal file
BIN
static/roboto.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Loading…
Reference in a new issue