/* eslint-disable no-fallthrough */ /* eslint-disable no-undef */ import assert from 'assert' import path from 'path' import { fileURLToPath } from 'url' 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, copyDir, readdirSync, exists, mkdir } from './libs/file.js' import AssetsProcessor from './libs/assets_processor.js' import init from './libs/initializer.js' import directory from './libs/directory.js' import Background from './libs/background.js' import CharwordTable from './libs/charword_table.js'; import Music from './libs/music.js'; import OfficalInfo from './libs/offical_info.js'; import CFPages from './libs/cf_pages.js'; async function main() { global.__projectRoot = path.dirname(fileURLToPath(import.meta.url)) const officalInfo = new OfficalInfo() global.__config = getConfig(officalInfo) global.__error = [] const OPERATOR_SOURCE_FOLDER = path.join(__projectRoot, __config.folder.operator) const OPERATOR_SOURCE_DATA_FOLDER = path.join(__projectRoot, __config.folder.operator_data) const OPERATOR_SHARE_FOLDER = path.join(OPERATOR_SOURCE_DATA_FOLDER, __config.folder.share) const op = process.argv[2] let OPERATOR_NAMES = process.argv.slice(3); const charwordTable = new CharwordTable() const musicTable = new Music() /** * Skip all, no need for OPERATOR_NAME * build-all: build all assets * directory: build directory */ switch (op) { case 'operator:build-all': for (const [key,] of Object.entries(__config.operators)) { OPERATOR_NAMES.push(key) } break case 'operator:preview': assert(OPERATOR_NAMES.length !== 0, 'Please set the operator name.') fork(path.join(__projectRoot, 'vite.config.js'), [op, OPERATOR_NAMES]) return case 'charwords:update': await charwordTable.process() process.exit(0) case 'music': await musicTable.process() process.exit(0) case 'offical_update': await officalInfo.update() process.exit(0) case 'cf:upload': await (new CFPages()).upload() process.exit(0) case 'cf:download': await (new CFPages()).download() process.exit(0) default: break } assert(OPERATOR_NAMES.length !== 0, 'Please set the operator name.') const background = new Background(OPERATOR_SHARE_FOLDER, OPERATOR_SOURCE_FOLDER) await background.process() const backgrounds = ['operator_bg.png', ...background.files] const { musicToCopy, musicMapping } = musicTable.copy(OPERATOR_SHARE_FOLDER) for (const e of musicToCopy) { const musicPath = path.join(e.source, e.filename) if (!exists(musicPath)) { __error.push(`Music file ${e.filename} is not found in music folder.`) } } for (const e of Object.keys(musicMapping)) { if (!backgrounds.includes(e)) { __error.push(`Background file ${e} is not found in background folder.`) } } for (const OPERATOR_NAME of OPERATOR_NAMES) { const OPERATOR_RELEASE_FOLDER = path.join(__projectRoot, __config.folder.release, OPERATOR_NAME) const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(OPERATOR_RELEASE_FOLDER, "assets") const EXTRACTED_FOLDER = path.join(OPERATOR_SOURCE_DATA_FOLDER, OPERATOR_NAME, 'extracted') const VOICE_FOLDERS = __config.folder.voice.sub.map((sub) => path.join(OPERATOR_SOURCE_DATA_FOLDER, OPERATOR_NAME, __config.folder.voice.main, sub.name)) /** * Skip assets generation part * init: init folder and config for an operator * readme: append a new line to README.md */ switch (op) { case 'init': init(OPERATOR_NAME, [EXTRACTED_FOLDER, ...VOICE_FOLDERS], officalInfo) process.exit(0) default: break } rmdir(OPERATOR_RELEASE_FOLDER) mkdir(path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME)) const charwordTableLookup = charwordTable.lookup(OPERATOR_NAME) const voiceJson = {} voiceJson.config = { default_region: charwordTableLookup.config.default_region.replace("_", "-"), regions: charwordTableLookup.config.regions.map((item) => item.replace("_", "-")), } voiceJson.voiceLangs = {} voiceJson.subtitleLangs = {} const subtitleInfo = Object.keys(charwordTableLookup.operator.info) subtitleInfo.forEach((item) => { if (Object.keys(charwordTableLookup.operator.info[item]).length > 0) { const key = item.replace("_", "-") voiceJson.subtitleLangs[key] = {} for (const [id, subtitles] of Object.entries(charwordTableLookup.operator.voice[item])) { const match = id.replace(/(.+?)([A-Z]\w+)/, '$2') if (match === id) { voiceJson.subtitleLangs[key].default = subtitles } else { voiceJson.subtitleLangs[key][match] = subtitles } } voiceJson.voiceLangs[key] = {} Object.values(charwordTableLookup.operator.info[item]).forEach((item) => { voiceJson.voiceLangs[key] = { ...voiceJson.voiceLangs[key], ...item } }) } }) let voiceLangs = [], subtitleLangs = []; try { voiceLangs = Object.keys(voiceJson.voiceLangs["zh-CN"]) subtitleLangs = Object.keys(voiceJson.subtitleLangs) writeSync(JSON.stringify(voiceJson), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'charword_table.json')) } catch (e) { console.log(`charword_table is not available`) } // check whether voice files has been added const customVoiceName = voiceLangs.filter(i => !__config.folder.voice.sub.map(e => e.lang).includes(i))[0] const voiceLangMapping = __config.folder.voice.sub.filter(e => { return voiceLangs.includes(e.lang) || (e.lang === "CUSTOM" && typeof customVoiceName !== 'undefined') }).map(e => { return { name: e.name, lang: e.lang === "CUSTOM" ? customVoiceName : e.lang } }) for (const voiceSubFolderMapping of voiceLangMapping) { const voiceSubFolder = path.join(OPERATOR_SOURCE_DATA_FOLDER, OPERATOR_NAME, __config.folder.voice.main, voiceSubFolderMapping.name) if (readdirSync(voiceSubFolder).length === 0) { __error.push(`Voice folder ${voiceSubFolderMapping.name} for ${OPERATOR_NAME} is empty.`) } } const envPath = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, '.env') writeSync((new EnvGenerator()).generate([ { key: "link", value: __config.operators[OPERATOR_NAME].link }, { key: "title", value: __config.operators[OPERATOR_NAME].title }, { key: "filename", value: __config.operators[OPERATOR_NAME].filename.replace('#', '%23') }, { key: "logo_filename", value: __config.operators[OPERATOR_NAME].logo }, { key: "fallback_filename", value: __config.operators[OPERATOR_NAME].fallback_name.replace('#', '%23') }, { key: "viewport_left", value: __config.operators[OPERATOR_NAME].viewport_left }, { key: "viewport_right", value: __config.operators[OPERATOR_NAME].viewport_right }, { key: "viewport_top", value: __config.operators[OPERATOR_NAME].viewport_top }, { key: "viewport_bottom", value: __config.operators[OPERATOR_NAME].viewport_bottom }, { key: "invert_filter", value: __config.operators[OPERATOR_NAME].invert_filter }, { key: "image_width", value: 2048 }, { key: "image_height", value: 2048 }, { key: "background_files", value: JSON.stringify(backgrounds) }, { key: "background_folder", value: __config.folder.background }, { key: "voice_folders", value: JSON.stringify(__config.folder.voice) }, { key: "music_folder", value: __config.folder.music }, { key: "music_mapping", value: JSON.stringify(musicMapping) }, { key: "use_json", value: __config.operators[OPERATOR_NAME].use_json } ]), envPath) const projectJson = new ProjectJson(OPERATOR_NAME, OPERATOR_SHARE_FOLDER, { backgrounds, voiceLangs, subtitleLangs, music: Object.keys(musicMapping) }) projectJson.load().then((content) => { write(JSON.stringify(content, null, 2), path.join(OPERATOR_RELEASE_FOLDER, 'project.json')) }) const assetsProcessor = new AssetsProcessor(OPERATOR_NAME, OPERATOR_SHARE_FOLDER) const assetContent = await assetsProcessor.process(EXTRACTED_FOLDER) write(JSON.stringify(assetContent.assetsJson, null), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, `assets.json`)) // copy remaining files const filesToCopy = [ ...background.getFilesToCopy(SHOWCASE_PUBLIC_ASSSETS_FOLDER), ...musicToCopy.map(entry => { return { ...entry, target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER, __config.folder.music) } }), { filename: 'preview.jpg', source: path.join(OPERATOR_SOURCE_DATA_FOLDER, OPERATOR_NAME), target: path.join(OPERATOR_RELEASE_FOLDER) }, { filename: 'operator_bg.png', source: path.join(OPERATOR_SHARE_FOLDER, __config.folder.background), target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER, __config.folder.background) }, { filename: `${__config.operators[OPERATOR_NAME].logo}.png`, source: path.join(OPERATOR_SHARE_FOLDER, 'logo'), target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER) }, { filename: `${__config.operators[OPERATOR_NAME].fallback_name}.png`, source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME), target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER) }, { filename: `${__config.operators[OPERATOR_NAME].fallback_name}_portrait.png`, source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME), target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER) } ] filesToCopy.forEach((file) => { copy(path.join(file.source, file.filename), path.join(file.target, file.filename)) }) const foldersToCopy = [ { source: path.join(OPERATOR_SOURCE_DATA_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) }) } switch (op) { case op.startsWith('directory'): directory(OPERATOR_SOURCE_DATA_FOLDER, { backgrounds, musicMapping }) default: break } if (__error.length > 0) { const str = `${__error.length} error${__error.length > 1 ? 's were' : ' was'} found:\n${__error.join('\n')}` throw new Error(str) } else { for (const OPERATOR_NAME of OPERATOR_NAMES) { fork(path.join(__projectRoot, 'vite.config.js'), [op, OPERATOR_NAME]) } } } main();