mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-21 08:31:58 +00:00
feat: custom default font
This commit is contained in:
parent
bd6e302705
commit
a19920729f
6 changed files with 188 additions and 64 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
81
src/renderer/src/lib/FontSelect.svelte
Normal file
81
src/renderer/src/lib/FontSelect.svelte
Normal 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>
|
||||||
|
|
@ -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'>
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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){
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue