feat(showcase): add operator voice
This commit is contained in:
14
config.yaml
14
config.yaml
@@ -2,9 +2,21 @@ folder:
|
||||
operator: ./operator/
|
||||
release: ./release/
|
||||
background: background
|
||||
voice: voice
|
||||
directory: _assets
|
||||
share: _share
|
||||
voice:
|
||||
main: voice
|
||||
sub:
|
||||
- name: jp
|
||||
lang: JP
|
||||
- name: cn
|
||||
lang: CN_MANDARIN
|
||||
- name: en
|
||||
lang: EN
|
||||
- name: kr
|
||||
lang: KR
|
||||
- name: custom
|
||||
lang: CUSTOM
|
||||
operators:
|
||||
chen: !include config/chen.yaml
|
||||
dusk: !include config/dusk.yaml
|
||||
|
||||
@@ -23,6 +23,7 @@ export default class EnvGenerator {
|
||||
`VITE_IMAGE_HEIGHT=2048`,
|
||||
`VITE_BACKGROUND_FILES=${JSON.stringify(this.#assets.backgrounds)}`,
|
||||
`VITE_BACKGROUND_FOLDER=${__config.folder.background}`,
|
||||
`VITE_VOICE_FOLDERS=${JSON.stringify(__config.folder.voice)}`,
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,15 @@ export async function copy(sourcePath, targetPath) {
|
||||
return await fsP.copyFile(sourcePath, targetPath)
|
||||
}
|
||||
|
||||
export async function copyDir(sourcePath, targetPath) {
|
||||
if (!exists(sourcePath)) {
|
||||
console.warn(`Source file ${sourcePath} does not exist.`)
|
||||
return
|
||||
}
|
||||
mkdir(targetPath)
|
||||
return await fsP.cp(sourcePath, targetPath, { recursive: true })
|
||||
}
|
||||
|
||||
export function appendSync(content, filePath) {
|
||||
return fs.appendFileSync(filePath, content, 'utf8');
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import { mkdir, writeSync } from './file.js'
|
||||
import { appendMainConfig } from './append.js'
|
||||
|
||||
export default function init(operatorName, extractedDir) {
|
||||
mkdir(extractedDir)
|
||||
extractedDir.forEach((dir) => {
|
||||
mkdir(dir)
|
||||
})
|
||||
const date = new Date()
|
||||
const template = readYAML(path.join(__dirname, 'config', '_template.yaml'))
|
||||
template.link = operatorName
|
||||
|
||||
15
runner.js
15
runner.js
@@ -5,7 +5,7 @@ import { fork } from 'child_process';
|
||||
import getConfig from './libs/config.js'
|
||||
import ProjectJson from './libs/project_json.js'
|
||||
import EnvGenerator from './libs/env_generator.js'
|
||||
import { write, rmdir, copy, writeSync } from './libs/file.js'
|
||||
import { write, rmdir, copy, writeSync, copyDir } from './libs/file.js'
|
||||
import AssetsProcessor from './libs/assets_processor.js'
|
||||
import init from './libs/initializer.js'
|
||||
import directory from './libs/directory.js'
|
||||
@@ -54,6 +54,7 @@ async function main() {
|
||||
const OPERATOR_RELEASE_FOLDER = path.join(__dirname, __config.folder.release, OPERATOR_NAME)
|
||||
const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(OPERATOR_RELEASE_FOLDER, "assets")
|
||||
const EXTRACTED_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'extracted')
|
||||
const VOICE_FOLDERS = __config.folder.voice.sub.map((sub) => path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, __config.folder.voice.main, sub.name))
|
||||
const OPERATOR_SHARE_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, __config.folder.share)
|
||||
|
||||
/**
|
||||
@@ -63,7 +64,7 @@ async function main() {
|
||||
*/
|
||||
switch (op) {
|
||||
case 'init':
|
||||
init(OPERATOR_NAME, EXTRACTED_FOLDER)
|
||||
init(OPERATOR_NAME, [EXTRACTED_FOLDER, ...VOICE_FOLDERS])
|
||||
process.exit(0)
|
||||
case 'readme':
|
||||
appendReadme(OPERATOR_NAME)
|
||||
@@ -115,6 +116,16 @@ async function main() {
|
||||
copy(path.join(file.source, file.filename), path.join(file.target, file.filename))
|
||||
})
|
||||
|
||||
const foldersToCopy = [
|
||||
{
|
||||
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, __config.folder.voice.main),
|
||||
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER, __config.folder.voice.main)
|
||||
}
|
||||
]
|
||||
foldersToCopy.forEach((folder) => {
|
||||
copyDir(folder.source, folder.target)
|
||||
})
|
||||
|
||||
const envPath = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, '.env')
|
||||
writeSync((new EnvGenerator(OPERATOR_NAME, {
|
||||
backgrounds
|
||||
|
||||
@@ -55,6 +55,7 @@ export default function spinePlayer(el) {
|
||||
entry.mixDuration = 0.3;
|
||||
widget.animationState.addAnimation(0, "Idle", true, 0);
|
||||
}
|
||||
window.voice.success()
|
||||
window.settings.success()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -38,6 +38,8 @@ export default class Settings {
|
||||
#logoX = this.#defaultLogoX
|
||||
#logoY = this.#defaultLogoY
|
||||
#isInsightsInited = false
|
||||
#doNotTrack = false
|
||||
#lastFunctionInsights = null
|
||||
|
||||
constructor(el, logoEl) {
|
||||
this.#el = el
|
||||
@@ -65,22 +67,31 @@ export default class Settings {
|
||||
|
||||
success() {
|
||||
this.loadViewport()
|
||||
this.insights(false)
|
||||
this.insights(false, false)
|
||||
}
|
||||
|
||||
insights(flag) {
|
||||
if (this.#isInsightsInited) return
|
||||
window.umami.trackView(`/${import.meta.env.VITE_LINK}${flag ? "?steam" : ""}`);
|
||||
insights(isWallpaperEngine, doNotTrack) {
|
||||
this.#isInsightsInited = true
|
||||
if (this.#isInsightsInited || doNotTrack || import.meta.env.MODE === 'development') return
|
||||
this.#doNotTrack = doNotTrack
|
||||
window.umami?.trackView(`/${import.meta.env.VITE_LINK}${isWallpaperEngine ? "?steam" : ""}`);
|
||||
}
|
||||
|
||||
functionInsights(functionName) {
|
||||
if (!this.#isInsightsInited || this.#doNotTrack || import.meta.env.MODE === 'development' || functionName === this.#lastFunctionInsights) return
|
||||
this.#lastFunctionInsights = functionName
|
||||
window.umami?.trackEvent(`${functionName}`);
|
||||
}
|
||||
|
||||
setFPS(value) {
|
||||
this.#fps = value
|
||||
this.spinePlayer.setFps(value)
|
||||
this.functionInsights("setFPS")
|
||||
}
|
||||
|
||||
setLogoDisplay(flag) {
|
||||
this.#logoEl.hidden = flag;
|
||||
this.functionInsights("setLogoDisplay")
|
||||
}
|
||||
|
||||
#resize(_this, value) {
|
||||
@@ -101,6 +112,7 @@ export default class Settings {
|
||||
this.#logoEl.src = src
|
||||
this.#resize()
|
||||
this.#setLogoInvertFilter(invert_filter)
|
||||
this.functionInsights("setLogo")
|
||||
}
|
||||
|
||||
#readFile(e, onload, callback) {
|
||||
@@ -131,23 +143,27 @@ export default class Settings {
|
||||
setLogoRatio(value) {
|
||||
this.#ratio = value
|
||||
this.#resize(this, value)
|
||||
this.functionInsights("setLogoRatio")
|
||||
}
|
||||
|
||||
setLogoOpacity(value) {
|
||||
this.#logoEl.style.opacity = value / 100
|
||||
this.#opacity = value
|
||||
this.functionInsights("setLogoOpacity")
|
||||
}
|
||||
|
||||
setBackgoundImage(v) {
|
||||
setBackgoundImage(v, skipInsights = false) {
|
||||
document.body.style.backgroundImage = v
|
||||
if (!skipInsights) this.functionInsights("setBackgoundImage");
|
||||
}
|
||||
|
||||
setDefaultBackground(e) {
|
||||
const backgroundURL = `url("${import.meta.env.BASE_URL}assets/${import.meta.env.VITE_BACKGROUND_FOLDER}/${e}")`
|
||||
if (document.getElementById("custom_background_clear").disabled && !document.body.style.backgroundImage.startsWith("url(\"file:")) {
|
||||
this.setBackgoundImage(backgroundURL)
|
||||
this.setBackgoundImage(backgroundURL, true)
|
||||
}
|
||||
this.#defaultBackgroundImage = backgroundURL
|
||||
this.functionInsights("setDefaultBackground")
|
||||
}
|
||||
|
||||
setBackground(e) {
|
||||
@@ -198,6 +214,7 @@ export default class Settings {
|
||||
break;
|
||||
}
|
||||
this.loadViewport()
|
||||
this.functionInsights("positionPadding")
|
||||
}
|
||||
|
||||
positionReset() {
|
||||
@@ -242,6 +259,7 @@ export default class Settings {
|
||||
break;
|
||||
}
|
||||
this.elementPosition(this.#logoEl, this.#logoX, this.#logoY)
|
||||
this.functionInsights("logoPadding")
|
||||
}
|
||||
|
||||
logoReset() {
|
||||
|
||||
@@ -3,11 +3,15 @@ import '@/components/voice.css'
|
||||
|
||||
export default class Voice {
|
||||
#el
|
||||
#widgetEl
|
||||
#audioEl = new Audio()
|
||||
#audioElId = 'voice-audio'
|
||||
#defaultVoiceLang = "JP"
|
||||
#defaultRegion = charword_table.config.default_region
|
||||
#defaultIdleDuration = 10 * 60 * 1000
|
||||
#defaultNextDuration = 3 * 60 * 1000
|
||||
#voiceLang = this.#defaultVoiceLang
|
||||
#voiceLanguages = Object.keys(this.#getCVInfo(this.#defaultRegion))
|
||||
#subtitleLang = this.#defaultRegion
|
||||
#useSubtitle = false
|
||||
#useVoice = false
|
||||
@@ -19,19 +23,22 @@ export default class Voice {
|
||||
#nextListener = -1
|
||||
#nextDuration = this.#defaultNextDuration
|
||||
#lastClickToNext = false
|
||||
#voiceFolderObject = this.#getVoiceFolderObject()
|
||||
|
||||
constructor(el) {
|
||||
constructor(el, widgetEl) {
|
||||
this.#el = el
|
||||
this.#widgetEl = widgetEl
|
||||
}
|
||||
|
||||
init() {
|
||||
this.#playTitleVoice()
|
||||
this.#insertHTML()
|
||||
this.#audioEl = document.getElementById(this.#audioElId)
|
||||
}
|
||||
|
||||
success() {
|
||||
this.#playEntryVoice()
|
||||
this.#initNextVoiceTimer()
|
||||
document.addEventListener('click', e => {
|
||||
this.#widgetEl.addEventListener('click', e => {
|
||||
this.#lastClickToNext = true
|
||||
this.#nextVoice()
|
||||
})
|
||||
@@ -86,7 +93,7 @@ export default class Voice {
|
||||
* @param {string} lang
|
||||
*/
|
||||
set language(lang) {
|
||||
if (this.#getVoiceLanguages().includes(lang)) {
|
||||
if (this.#voiceLanguages.includes(lang)) {
|
||||
this.#voiceLang = lang
|
||||
} else {
|
||||
this.#voiceLang = this.#defaultVoiceLang
|
||||
@@ -102,7 +109,7 @@ export default class Voice {
|
||||
}
|
||||
|
||||
get languages() {
|
||||
return this.#getVoiceLanguages()
|
||||
return this.#voiceLanguages
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,10 +142,6 @@ export default class Voice {
|
||||
return this.#nextDuration / 60 / 1000
|
||||
}
|
||||
|
||||
#playTitleVoice() {
|
||||
this.#playSpecialVoice("标题")
|
||||
}
|
||||
|
||||
#initIdleVoiceTimer() {
|
||||
this.#idleListener = setInterval(() => {
|
||||
this.#playSpecialVoice("闲置")
|
||||
@@ -169,16 +172,26 @@ export default class Voice {
|
||||
|
||||
#playVoice(id) {
|
||||
this.#currentVoiceId = id
|
||||
const audio = new Audio()
|
||||
// audio.src = `https://cdn.jsdelivr.net/gh/Arondight/Adachi-BOT@master/src/assets/voice/${id}.mp3`
|
||||
audio.play()
|
||||
audio.addEventListener('ended', () => {
|
||||
this.#audioEl.src = `./assets/${this.#getVoiceFoler()
|
||||
}/${id}.wav`
|
||||
let startPlayPromise = this.#audioEl.play()
|
||||
if (startPlayPromise !== undefined) {
|
||||
startPlayPromise
|
||||
.then(() => {
|
||||
const audioEndedFunc = () => {
|
||||
this.#lastVoiceId = this.#currentVoiceId
|
||||
this.#currentVoiceId = null
|
||||
this.#setCurrentSubtitle(null)
|
||||
this.#lastClickToNext = false
|
||||
})
|
||||
this.#audioEl.removeEventListener('ended', audioEndedFunc)
|
||||
}
|
||||
this.#audioEl.addEventListener('ended', audioEndedFunc)
|
||||
this.#setCurrentSubtitle(id)
|
||||
})
|
||||
.catch(() => {
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#playSpecialVoice(matcher) {
|
||||
@@ -186,6 +199,11 @@ export default class Voice {
|
||||
this.#playVoice(voiceId)
|
||||
}
|
||||
|
||||
#getVoiceFoler() {
|
||||
const folderObject = this.#voiceFolderObject
|
||||
return `${folderObject.main}/${folderObject.sub.find(e => e.lang === this.#voiceLang).name}`
|
||||
}
|
||||
|
||||
#getSpecialVoiceId(matcher) {
|
||||
const voices = this.#getVoices()
|
||||
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
|
||||
@@ -200,8 +218,17 @@ export default class Voice {
|
||||
return charword_table.operator.voice[this.#subtitleLang][this.#getWordKeyByVoiceLang()[this.#voiceLang]][id]
|
||||
}
|
||||
|
||||
#getVoiceLanguages() {
|
||||
return Object.keys(this.#getCVInfo(this.#defaultRegion))
|
||||
#getVoiceFolderObject() {
|
||||
const folderObject = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS)
|
||||
const languagesCopy = this.#voiceLanguages.slice()
|
||||
const customVoiceName = languagesCopy.filter(i => !folderObject.sub.map(e => e.lang).includes(i))[0]
|
||||
folderObject.sub = folderObject.sub.map(e => {
|
||||
return {
|
||||
name: e.name,
|
||||
lang: e.lang === "CUSTOM" ? customVoiceName : e.lang
|
||||
}
|
||||
})
|
||||
return folderObject
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,4 +278,12 @@ export default class Voice {
|
||||
return Object.keys(this.#getCVInfoByVoiceLang()[this.#voiceLang])
|
||||
}
|
||||
|
||||
#insertHTML() {
|
||||
this.#el.innerHTML = `
|
||||
<audio id="${this.#audioElId}" autoplay>
|
||||
<source type="audio/wav">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,16 @@ import Voice from '@/components/voice'
|
||||
document.querySelector('#app').innerHTML = `
|
||||
<img src="./assets/${import.meta.env.VITE_LOGO_FILENAME}.png" class="logo invert-filter" id="logo" alt="operator logo" />
|
||||
<div id="settings"></div>
|
||||
<div id="voice" hidden></div>
|
||||
<div id="widget-wrapper">
|
||||
<div id="fallback"
|
||||
style="background-image: url(./assets/${import.meta.env.VITE_FALLBACK_FILENAME}.png)"
|
||||
hidden
|
||||
></div>
|
||||
<div id="player" hidden></div>
|
||||
<div id="voice" hidden></div>
|
||||
</div>
|
||||
`
|
||||
window.voice = new Voice(document.querySelector('#voice'))
|
||||
window.voice = new Voice(document.querySelector('#voice'), document.querySelector('#widget-wrapper'))
|
||||
window.voice.init()
|
||||
window.settings = new Settings(document.querySelector('#settings'), document.querySelector('#logo'))
|
||||
document.title = import.meta.env.VITE_TITLE
|
||||
|
||||
@@ -5,7 +5,7 @@ window.wallpaperPropertyListener = {
|
||||
}
|
||||
},
|
||||
applyUserProperties: function (properties) {
|
||||
window.settings.insights(true)
|
||||
window.settings.insights(true, false)
|
||||
if (properties.logo) {
|
||||
window.settings.setLogoDisplay(!properties.logo.value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user