feat: custom default font

This commit is contained in:
ThaUnknown 2022-12-31 23:32:48 +01:00
parent bd6e302705
commit a19920729f
6 changed files with 188 additions and 64 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "Miru", "name": "Miru",
"version": "3.5.0", "version": "3.5.1",
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>", "author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"description": "Stream anime torrents, real-time with no waiting for downloads.", "description": "Stream anime torrents, real-time with no waiting for downloads.",
"main": "src/index.js", "main": "src/index.js",

View file

@ -0,0 +1,81 @@
<script context='module'>
import { writable } from 'simple-store-svelte'
const fonts = writable({})
let availableFonts = null
async function loadFonts () {
const _fonts = {}
if (availableFonts) return
const styleSheet = new CSSStyleSheet()
try {
availableFonts = await queryLocalFonts()
for (const metadata of availableFonts) {
if (!_fonts[metadata.family]) {
_fonts[metadata.family] = []
}
_fonts[metadata.family].push(metadata)
}
} catch (err) {
console.warn(err.name, err.message)
}
for (const fontFamily of Object.keys(_fonts)) {
_fonts[fontFamily] = _fonts[fontFamily]
.map(font => {
// Replace font variation name "Arial" with "Arial Regular".
const variationName = font.fullName
.replace(fontFamily, '')
.replaceAll('-', '')
.trim()
font.variationName = variationName || 'Regular'
return font
})
.sort((a, b) => {
// "Regular" always comes first, else use alphabetic order.
if (a.variationName === 'Regular') {
return -1
} else if (b.variationName === 'Regular') {
return 1
} else if (a.variationName < b.variationName) {
return -1
} else if (a.variationName > b.variationName) {
return 1
}
return 0
})
for (const font of _fonts[fontFamily]) {
styleSheet.insertRule(`@font-face { font-family: '${font.postscriptName}'; src: local('${font.postscriptName}'), local('${font.fullName}'); }`)
}
}
document.adoptedStyleSheets = [
styleSheet,
...document.adoptedStyleSheets
]
fonts.value = _fonts
}
</script>
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function fontChange ({ target }) {
const value = target.value
const font = availableFonts.find(({ postscriptName }) => postscriptName === value)
dispatch('change', font)
}
export let value
loadFonts()
</script>
<!-- eslint-disable-next-line svelte/valid-compile -->
<select on:click|once={loadFonts} on:change={fontChange} {...$$restProps} style='font-family: {value}' bind:value>
{#each Object.entries($fonts) as [fontName, fonts]}
<optgroup label={fontName} style='font-family: {fontName}'>
{#each fonts as fontData}
<option style='font-family: {fontData.postscriptName}' value={fontData.postscriptName}>{fontData.fullName}</option>
{/each}
</optgroup>
{/each}
</select>

View file

@ -1,54 +1,54 @@
<script> <script>
import { getContext } from 'svelte' import { getContext } from 'svelte'
import { traceAnime } from '@/modules/anime.js' import { traceAnime } from '@/modules/anime.js'
export let search export let search
export let current export let current
export let media = null export let media = null
export let loadCurrent export let loadCurrent
let searchTimeout = null let searchTimeout = null
let searchTextInput let searchTextInput
const view = getContext('view') const view = getContext('view')
$: !$view && searchTextInput?.focus() $: !$view && searchTextInput?.focus()
function searchClear () { function searchClear () {
search = { search = {
format: '', format: '',
genre: '', genre: '',
season: '', season: '',
sort: '', sort: '',
status: '' status: ''
} }
current = null current = null
searchTextInput?.focus() searchTextInput?.focus()
} }
function input () { function input () {
if (!searchTimeout) { if (!searchTimeout) {
if (Object.values(search).filter(v => v).length) media = [new Promise(() => {})] if (Object.values(search).filter(v => v).length) media = [new Promise(() => {})]
} else { } else {
clearTimeout(searchTimeout) clearTimeout(searchTimeout)
} }
searchTimeout = setTimeout(() => { searchTimeout = setTimeout(() => {
if (current === null) { if (current === null) {
if (Object.values(search).filter(v => v).length) current = 'search' if (Object.values(search).filter(v => v).length) current = 'search'
} else { } else {
if (Object.values(search).filter(v => v).length) { if (Object.values(search).filter(v => v).length) {
loadCurrent(false) loadCurrent(false)
} else { } else {
current = null current = null
} }
}
searchTimeout = null
}, 500)
}
function handleFile ({ target }) {
const { files } = target
if (files?.[0]) {
traceAnime(files[0], 'file')
target.value = null
} }
searchTimeout = null
}, 500)
}
function handleFile ({ target }) {
const { files } = target
if (files?.[0]) {
traceAnime(files[0], 'file')
target.value = null
} }
}
</script> </script>
<div class='container-fluid row p-20' on:input={input}> <div class='container-fluid row p-20' on:input={input}>
@ -61,7 +61,6 @@ function handleFile ({ target }) {
<div class='input-group-prepend'> <div class='input-group-prepend'>
<span class='input-group-text d-flex material-icons bg-dark pr-0 font-size-18'>search</span> <span class='input-group-text d-flex material-icons bg-dark pr-0 font-size-18'>search</span>
</div> </div>
<!-- svelte-ignore missing-declaration -->
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y-autofocus -->
<input <input
on:input={({ target }) => { on:input={({ target }) => {
@ -170,7 +169,7 @@ function handleFile ({ target }) {
<option value='UPDATED_TIME_DESC' disabled hidden>Updated Date</option> <option value='UPDATED_TIME_DESC' disabled hidden>Updated Date</option>
</select> </select>
</div> </div>
<input type='file' class='d-none' id='search-image' accept='image/*' on:input={handleFile}> <input type='file' class='d-none' id='search-image' accept='image/*' on:input={handleFile} />
<div class='col-auto p-10 d-flex'> <div class='col-auto p-10 d-flex'>
<button class='btn bg-dark material-icons font-size-18 px-5 align-self-end shadow-lg border-0' type='button'> <button class='btn bg-dark material-icons font-size-18 px-5 align-self-end shadow-lg border-0' type='button'>
<label for='search-image' class='pointer'> <label for='search-image' class='pointer'>

View file

@ -72,6 +72,7 @@
<script> <script>
import { Tabs, TabLabel, Tab } from './Tabination.js' import { Tabs, TabLabel, Tab } from './Tabination.js'
import FontSelect from './FontSelect.svelte'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
onDestroy(() => { onDestroy(() => {
@ -110,6 +111,25 @@
window.IPC.on('path', data => { window.IPC.on('path', data => {
settings.torrentPath = data settings.torrentPath = data
}) })
async function changeFont ({ detail }) {
try {
const blob = await detail.blob()
const data = await blob.arrayBuffer()
settings.font = {
name: detail.fullName,
value: detail.postscriptName,
data: [...new Uint8Array(data)]
}
} catch (error) {
console.warn(error)
addToast({
text: /* html */`${error.message}<br>Try using a different font.`,
title: 'File Error',
type: 'secondary',
duration: 8000
})
}
}
</script> </script>
<Tabs> <Tabs>
@ -161,6 +181,19 @@
<div class='h-full p-20 m-20'> <div class='h-full p-20 m-20'>
<Tab> <Tab>
<div class='root'> <div class='root'>
<div class='col p-10 d-flex flex-column justify-content-end'>
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
<div class='material-icons mr-10 font-size-30'>font_download</div>
Default Subtitle Font
</div>
<FontSelect class='form-control bg-dark shadow-lg w-300' on:change={changeFont} value={settings.font?.value} />
</div>
<div class='col p-10 d-flex flex-column justify-content-end'>
<div class='font-size-24 font-weight-semi-bold d-flex'>
<div class='material-icons mr-10 font-size-30'>play_arrow</div>
Playback Settings
</div>
</div>
<div <div
class='custom-switch mb-10 pl-10 font-size-16 w-300' class='custom-switch mb-10 pl-10 font-size-16 w-300'
data-toggle='tooltip' data-toggle='tooltip'
@ -177,6 +210,12 @@
<input type='checkbox' id='player-pause' bind:checked={settings.playerPause} /> <input type='checkbox' id='player-pause' bind:checked={settings.playerPause} />
<label for='player-pause'>Pause When Tabbing Out</label> <label for='player-pause'>Pause When Tabbing Out</label>
</div> </div>
<div class='col p-10 d-flex flex-column justify-content-end'>
<div class='font-size-24 font-weight-semi-bold d-flex'>
<div class='material-icons mr-10 font-size-30'>list</div>
Anilist Settings
</div>
</div>
<div <div
class='custom-switch mb-10 pl-10 font-size-16 w-300' class='custom-switch mb-10 pl-10 font-size-16 w-300'
data-toggle='tooltip' data-toggle='tooltip'

View file

@ -92,15 +92,14 @@ const handleRequest = limiter.wrap(async opts => {
return json return json
}) })
export const alID = !!alToken && alRequest({ method: 'Viewer', token: alToken }) export let alID = !!alToken
if (alID) { alID = alRequest({ method: 'Viewer', token: alToken }).then(result => {
alID.then(result => { const lists = result?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
const lists = result?.data?.Viewer?.mediaListOptions?.animeList?.customLists || [] if (!lists.includes('Watched using Miru')) {
if (!lists.includes('Watched using Miru')) { alRequest({ method: 'CustomList', lists })
alRequest({ method: 'CustomList', lists }) }
} return result
}) })
}
function printError (error) { function printError (error) {
console.warn(error) console.warn(error)
@ -186,6 +185,7 @@ export async function alRequest (opts) {
Accept: 'application/json' Accept: 'application/json'
} }
} }
const userId = (await alID)?.data?.Viewer.id
const queryObjects = /* js */` const queryObjects = /* js */`
id, id,
title { title {
@ -349,7 +349,7 @@ query{
}` }`
break break
} case 'UserLists': { } case 'UserLists': {
variables.id = (await alID)?.data?.Viewer?.id variables.id = userId
query = /* js */` query = /* js */`
query($page: Int, $perPage: Int, $id: Int, $status_in: [MediaListStatus]){ query($page: Int, $perPage: Int, $id: Int, $status_in: [MediaListStatus]){
Page(page: $page, perPage: $perPage){ Page(page: $page, perPage: $perPage){
@ -365,7 +365,7 @@ query($page: Int, $perPage: Int, $id: Int, $status_in: [MediaListStatus]){
}` }`
break break
} case 'NewSeasons': { } case 'NewSeasons': {
variables.id = (await alID)?.data?.Viewer?.id variables.id = userId
query = /* js */` query = /* js */`
query($id: Int){ query($id: Int){
MediaListCollection(userId: $id, status_in: [REPEATING, COMPLETED], type: ANIME, forceSingleCompletedList: true){ MediaListCollection(userId: $id, status_in: [REPEATING, COMPLETED], type: ANIME, forceSingleCompletedList: true){
@ -387,7 +387,7 @@ query($id: Int){
}` }`
break break
} case 'SearchIDStatus': { } case 'SearchIDStatus': {
variables.id = (await alID)?.data?.Viewer?.id variables.id = userId
variables.mediaId = opts.id variables.mediaId = opts.id
query = /* js */` query = /* js */`
query($id: Int, $mediaId: Int){ query($id: Int, $mediaId: Int){

View file

@ -2,12 +2,13 @@ import JASSUB from 'jassub'
import workerUrl from 'jassub/dist/jassub-worker.js?url' import workerUrl from 'jassub/dist/jassub-worker.js?url'
import 'jassub/dist/jassub-worker.wasm?url' import 'jassub/dist/jassub-worker.wasm?url'
import { toTS, videoRx, subRx } from './util.js' import { toTS, videoRx, subRx } from './util.js'
import { set } from '@/lib/Settings.svelte'
import { client } from '@/modules/torrent.js' import { client } from '@/modules/torrent.js'
const defaultHeader = `[V4+ Styles] const defaultHeader = `[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default, Roboto Medium,26,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,1.3,0,2,20,20,23,1 Style: Default, ${set.font?.name || 'Roboto Medium'},26,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,1.3,0,2,20,20,23,1
[Events] [Events]
` `
@ -130,16 +131,20 @@ export default class Subtitles {
initSubtitleRenderer () { initSubtitleRenderer () {
if (!this.renderer) { if (!this.renderer) {
this.renderer = new JASSUB({ const options = {
video: this.video, video: this.video,
subContent: this.headers[this.current].header.slice(0, -1), subContent: this.headers[this.current].header.slice(0, -1),
fonts: this.fonts, fonts: this.fonts,
fallbackFont: 'roboto medium', fallbackFont: set.font?.name || 'roboto medium',
availableFonts: { availableFonts: {
'roboto medium': './Roboto.ttf' 'roboto medium': './Roboto.ttf'
}, },
workerUrl workerUrl
}) }
if (set.font) {
options.availableFonts[set.font.name.toLowerCase()] = new Uint8Array(set.font.data)
}
this.renderer = new JASSUB(options)
this.selectCaptions(this.current) this.selectCaptions(this.current)
} }
} }