feat: subtitle dialogue style overrides, increase al cache duration, optimise fonts

This commit is contained in:
ThaUnknown 2025-05-29 02:01:13 +02:00
parent 393276c9e2
commit 6dfc5f70e9
No known key found for this signature in database
21 changed files with 275 additions and 22 deletions

View file

@ -11,6 +11,15 @@ export default tseslint.config(
tsconfigRootDir: import.meta.dirname,
svelteConfig
}
},
rules: {
"svelte/no-useless-mustaches": [
"error",
{
"ignoreIncludesComment": false,
"ignoreStringEscape": true
}
]
}
}
)

View file

@ -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",

View file

@ -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

View file

@ -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>

View file

@ -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) {

View 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
}

View 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>

View 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>

View 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
}

View 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>

View file

@ -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({

View file

@ -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,

View file

@ -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' />

Binary file not shown.

BIN
static/NotoSans-Bold.woff2 Normal file

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
static/notosans.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
static/roboto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB