taiko-web/public/src/js/importsongs.js

659 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class ImportSongs{
constructor(...args){
this.init(...args)
}
init(limited, otherFiles, noPlugins, pluginAmount){
this.limited = limited
this.tjaFiles = []
this.osuFiles = []
this.assetFiles = {}
this.pluginFiles = []
this.otherFiles = otherFiles || {}
this.noPlugins = noPlugins
this.pluginAmount = pluginAmount
this.songs = []
this.stylesheet = []
this.plugins = []
this.songTitle = this.otherFiles.songTitle || {}
this.uraRegex = /\s*[\(]裏[\)]$/
this.courseTypes = {
"easy": 0,
"normal": 1,
"hard": 2,
"oni": 3,
"ura": 4
}
this.categoryAliases = {}
assets.categories.forEach(cat => {
this.categoryAliases[cat.title.toLowerCase()] = cat.id
if(cat.aliases){
cat.aliases.forEach(alias => {
this.categoryAliases[alias.toLowerCase()] = cat.id
})
}
if(cat.title_lang){
for(var i in cat.title_lang){
this.categoryAliases[cat.title_lang[i].toLowerCase()] = cat.id
}
}
})
this.assetSelectors = {}
for(var selector in assets.cssBackground){
var filename = assets.cssBackground[selector]
var index = filename.lastIndexOf(".")
if(index !== -1){
filename = filename.slice(0, index)
}
this.assetSelectors[filename] = selector
}
this.comboVoices = ["v_combo_50"].concat(Array.from(Array(50), (d, i) => "v_combo_" + ((i + 1) * 100)))
}
load(files){
var extensionRegex = /\.[^\/]+$/
files.sort((a, b) => {
var path1 = a.path.replace(extensionRegex, "")
var path2 = b.path.replace(extensionRegex, "")
return path1 > path2 ? 1 : -1
})
var metaFiles = []
for(var i = 0; i < files.length; i++){
var file = files[i]
var name = file.name.toLowerCase()
var path = file.path.toLowerCase()
if(name.endsWith(".tja") || name.endsWith(".tjf")){
this.tjaFiles.push({
file: file,
index: i
})
}else if(name.endsWith(".osu")){
this.osuFiles.push({
file: file,
index: i
})
}else if(!this.limited && (name === "genre.ini" || name === "box.def") || name === "songtitle.txt"){
var level = (file.path.match(/\//g) || []).length
metaFiles.push({
file: file,
level: (level * 2) + (name === "genre.ini" ? 1 : 0)
})
}else if(!this.limited && (path.indexOf("/taiko-web assets/") !== -1 || path.indexOf("taiko-web assets/") === 0)){
if(!(name in this.assetFiles)){
this.assetFiles[name] = file
}
}else if(name.endsWith(".taikoweb.js")){
this.pluginFiles.push({
file: file,
index: i
})
}else{
this.otherFiles[path] = file
}
}
if(!this.noPlugins && this.pluginFiles.length){
var pluginPromises = []
this.pluginFiles.forEach(fileObj => {
pluginPromises.push(this.addPlugin(fileObj).catch(e => console.warn(e)))
})
return Promise.all(pluginPromises).then(() => {
var startPromises = []
var pluginAmount = 0
if(this.plugins.length && confirm(strings.plugins.warning.replace("%s",
strings.plugins.plugin[strings.plural.select(this.plugins.length)].replace("%s",
this.plugins.length.toString()
)
))){
this.plugins.forEach(obj => {
var plugin = plugins.add(obj.data, {
name: obj.name,
raw: true
})
if(plugin){
pluginAmount++
plugin.imported = true
startPromises.push(plugin.start())
}
})
}
return Promise.all(startPromises).then(() => {
var importSongs = new ImportSongs(this.limited, this.otherFiles, true, pluginAmount)
return importSongs.load(files)
})
})
}
var metaPromises = []
metaFiles.forEach(fileObj => {
metaPromises.push(this.addMeta(fileObj))
})
return Promise.all(metaPromises).then(() => {
var songPromises = []
this.tjaFiles.forEach(fileObj => {
songPromises.push(this.addTja(fileObj).catch(e => console.warn(e)))
})
this.osuFiles.forEach(fileObj => {
songPromises.push(this.addOsu(fileObj).catch(e => console.warn(e)))
})
songPromises.push(this.addAssets())
return Promise.all(songPromises)
}).then(this.loaded.bind(this))
}
addMeta(fileObj){
var file = fileObj.file
var level = fileObj.level
var name = file.name.toLowerCase()
return file.read(name === "songtitle.txt" ? undefined : "utf-8").then(data => {
var data = data.replace(/\0/g, "").split("\n")
var category
if(name === "genre.ini"){
var key
for(var i = 0; i < data.length; i++){
var line = data[i].trim().toLowerCase()
if(line.startsWith("[") && line.endsWith("]")){
key = line.slice(1, -1)
}else if(key === "genre"){
var equalsPos = line.indexOf("=")
if(equalsPos !== -1 && line.slice(0, equalsPos).trim() === "genrename"){
var value = line.slice(equalsPos + 1).trim()
if(value.toLowerCase() in this.categoryAliases){
category = value
}else{
category = data[i].trim().slice(equalsPos + 1).trim()
}
break
}
}
}
}else if(name === "box.def"){
for(var i = 0; i < data.length; i++){
var line = data[i].trim().toLowerCase()
if(line.startsWith("#title:")){
var value = line.slice(7).trim()
if(value.toLowerCase() in this.categoryAliases){
category = value
}
}else if(line.startsWith("#genre:")){
var value = line.slice(7).trim()
if(value.toLowerCase() in this.categoryAliases){
category = value
}else{
category = data[i].trim().slice(7).trim()
}
break
}
}
}else if(name === "songtitle.txt"){
var lastTitle
for(var i = 0; i < data.length; i++){
var line = data[i].trim()
if(line){
var lang = line.slice(0, 2)
if(line.charAt(2) !== " " || !(lang in allStrings)){
this.songTitle[line] = {}
lastTitle = line
}else if(lastTitle){
this.songTitle[lastTitle][lang] = line.slice(3).trim()
}
}
}
}
if(category){
var metaPath = file.path.toLowerCase().slice(0, file.name.length * -1)
var filesLoop = fileObj => {
var tjaPath = fileObj.file.path.toLowerCase().slice(0, fileObj.file.name.length * -1)
if(tjaPath.startsWith(metaPath) && (!("categoryLevel" in fileObj) || fileObj.categoryLevel < level)){
if(category.toLowerCase() in this.categoryAliases){
fileObj.category_id = this.categoryAliases[category.toLowerCase()]
}else{
fileObj.category = category
}
fileObj.categoryLevel = level
}
}
this.tjaFiles.forEach(filesLoop)
this.osuFiles.forEach(filesLoop)
}
}).catch(e => {
console.warn(e)
})
}
addTja(fileObj){
var file = fileObj.file
var index = fileObj.index
var category = fileObj.category
var category_id = fileObj.category_id
if(!this.limited){
var filePromise = file.read("sjis") // utf-8にしたい
}else{
var filePromise = Promise.resolve()
}
return filePromise.then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var tja = new ParseTja(data, "oni", 0, 0, true)
var songObj = {
id: index + 1,
order: index + 1,
title: file.name.slice(0, file.name.lastIndexOf(".")),
type: "tja",
chart: file,
courses: {},
music: "muted",
custom: true
}
if(this.limited){
songObj.unloaded = true
}
var coursesAdded = false
var titleLang = {}
var titleLangAdded = false
var subtitleLangAdded = false
var subtitleLang = {}
var dir = file.path.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
for(var diff in tja.metadata){
var meta = tja.metadata[diff]
songObj.title = meta.title || file.name.slice(0, file.name.lastIndexOf("."))
var subtitle = meta.subtitle || ""
if(subtitle.startsWith("--") || subtitle.startsWith("++")){
subtitle = subtitle.slice(2).trim()
}
songObj.subtitle = subtitle
songObj.preview = meta.demostart || 0
songObj.courses[diff] = {
stars: meta.level || 0,
branch: !!meta.branch
}
coursesAdded = true
if(meta.wave){
songObj.music = this.otherFiles[dir + meta.wave.toLowerCase()] || songObj.music
}
if(meta.genre){
if(meta.genre.toLowerCase() in this.categoryAliases){
songObj.category_id = this.categoryAliases[meta.genre.toLowerCase()]
}else{
songObj.category = meta.genre
}
}
if(meta.taikowebskin){
songObj.song_skin = this.getSkin(dir, meta.taikowebskin)
}
if(meta.maker){
var maker = meta.maker
var url = null
var gt = maker.lastIndexOf(">")
if(gt === maker.length - 1){
var lt = maker.lastIndexOf("<")
if(lt !== -1 && lt !== gt - 2){
url = maker.slice(lt + 1, gt).trim()
if(url.startsWith("http://") || url.startsWith("https://")){
maker = maker.slice(0, lt)
}else{
url = null
}
}
}
songObj.maker = {
name: maker,
url: url,
id: 1
}
}
if(meta.lyrics){
var lyricsFile = this.normPath(this.joinPath(dir, meta.lyrics))
if(lyricsFile in this.otherFiles){
songObj.lyrics = true
songObj.lyricsFile = this.otherFiles[lyricsFile]
}
}else if(meta.inlineLyrics){
songObj.lyrics = true
}
for(var id in allStrings){
var songTitle = songObj.title
var ura = ""
if(songTitle){
var uraPos = songTitle.search(this.uraRegex)
if(uraPos !== -1){
ura = songTitle.slice(uraPos)
songTitle = songTitle.slice(0, uraPos)
}
}
if(meta["title" + id]){
titleLang[id] = meta["title" + id]
titleLangAdded = true
}else if(songTitle in this.songTitle && this.songTitle[songTitle][id]){
titleLang[id] = this.songTitle[songTitle][id] + ura
titleLangAdded = true
}
if(meta["subtitle" + id]){
subtitleLang[id] = meta["subtitle" + id]
subtitleLangAdded = true
}
}
}
if(titleLangAdded){
songObj.title_lang = titleLang
}
if(subtitleLangAdded){
songObj.subtitle_lang = subtitleLang
}
if(!songObj.category_id && !songObj.category){
if(!category && category_id === undefined){
songObj.category_id = this.getCategory(file, [songTitle || songObj.title, file.name.slice(0, file.name.lastIndexOf("."))])
}else if(category){
songObj.category = category
songObj.orginalCategory = category
}else{
songObj.category_id = category_id
}
}
if(coursesAdded || songObj.unloaded){
this.songs[index] = songObj
}
if(!this.limited){
var hash = md5.base64(dataRaw).slice(0, -2)
songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true)
if(score){
score.title = songObj.title
}
}
})
}
addOsu(fileObj){
var file = fileObj.file
var index = fileObj.index
var category = fileObj.category
var category_id = fileObj.category_id
if(!this.limited){
var filePromise = file.read()
}else{
var filePromise = Promise.resolve()
}
return filePromise.then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var osu = new ParseOsu(data, "oni", 0, 0, true)
var dir = file.path.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
var songObj = {
id: index + 1,
order: index + 1,
type: "osu",
chart: file,
subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist,
subtitle_lang: {
en: osu.metadata.Artist || osu.metadata.ArtistUnicode
},
preview: osu.generalInfo.PreviewTime ? osu.generalInfo.PreviewTime / 1000 : 0,
courses: {},
music: (osu.generalInfo.AudioFilename ? this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] : "") || "muted"
}
if(!this.limited){
songObj.courses.oni = {
stars: parseInt(osu.difficulty.overallDifficulty) || 0,
branch: false
}
}else{
songObj.unloaded = true
}
var filename = file.name.slice(0, file.name.lastIndexOf("."))
var title = osu.metadata.TitleUnicode || osu.metadata.Title || file.name.slice(0, file.name.lastIndexOf("."))
if(title){
var suffix = ""
var matches = filename.match(/\[.+?\]$/)
if(matches){
suffix = " " + matches[0]
}
songObj.title = title + suffix
songObj.title_lang = {
en: (osu.metadata.Title || osu.metadata.TitleUnicode || title) + suffix
}
}else{
songObj.title = filename
}
this.songs[index] = songObj
if(!category && category_id === undefined){
songObj.category_id = this.getCategory(file, [osu.metadata.TitleUnicode, osu.metadata.Title, file.name.slice(0, file.name.lastIndexOf("."))])
}else if(category){
songObj.category = category
songObj.orginalCategory = category
}else{
songObj.category_id = category_id
}
if(!this.limited){
var hash = md5.base64(dataRaw).slice(0, -2)
songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true)
if(score){
score.title = songObj.title
}
}
})
}
addAssets(){
var promises = []
for(let name in this.assetFiles){
let id = this.getFilename(name)
var file = this.assetFiles[name]
var index = name.lastIndexOf(".")
if(name === "vectors.json"){
promises.push(file.read().then(response => {
vectors = JSON.parse(response)
}))
}
if(name.endsWith(".png") || name.endsWith(".gif")){
let image = document.createElement("img")
promises.push(pageEvents.load(image).then(() => {
if(id in this.assetSelectors){
var selector = this.assetSelectors[id]
var gradient = ""
if(selector === "#song-search"){
gradient = loader.songSearchGradient
}
this.stylesheet.push(loader.cssRuleset({
[selector]: {
"background-image": gradient + "url(\"" + image.src + "\")"
}
}))
}
}))
image.id = name
promises.push(file.blob().then(blob => {
image.src = URL.createObjectURL(blob)
}))
loader.assetsDiv.appendChild(image)
var oldImage = assets.image[id]
if(oldImage && oldImage.parentNode){
URL.revokeObjectURL(oldImage.src)
oldImage.parentNode.removeChild(oldImage)
}
assets.image[id] = image
}
if(assets.audioSfx.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.sfxGain))
}
if(assets.audioMusic.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.musicGain))
}
if(assets.audioSfxLR.indexOf(name) !== -1){
assets.sounds[id + "_p1"].clean()
assets.sounds[id + "_p2"].clean()
promises.push(this.loadSound(file, name, snd.sfxGain).then(sound => {
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
}))
}
if(assets.audioSfxLoud.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.sfxLoudGain))
}
if(this.comboVoices.indexOf(id) !== -1){
promises.push(snd.sfxGain.load(file).then(sound => {
assets.sounds[id] = sound
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
}))
}
}
return Promise.all(promises)
}
loadSound(file, name, gain){
var id = this.getFilename(name)
return gain.load(file).then(sound => {
assets.sounds[id] = sound
})
}
getFilename(name){
return name.slice(0, name.lastIndexOf("."))
}
addPlugin(fileObj){
var file = fileObj.file
var filePromise = file.read()
return filePromise.then(dataRaw => {
var name = file.name.slice(0, file.name.lastIndexOf(".taikoweb.js"))
this.plugins.push({
name: name,
data: dataRaw
})
})
}
getCategory(file, exclude){
var path = file.path.toLowerCase().split("/")
for(var i = path.length - 2; i >= 0; i--){
var hasTitle = false
for(var j in exclude){
if(exclude[j] && path[i].indexOf(exclude[j].toLowerCase()) !== -1){
hasTitle = true
break
}
}
if(!hasTitle){
for(var cat in this.categoryAliases){
if(path[i].indexOf(cat) !== -1){
return this.categoryAliases[cat]
}
}
}
}
}
getSkin(dir, config){
var configArray = config.toLowerCase().split(",")
var configObj = {}
for(var i in configArray){
var string = configArray[i].trim()
var space = string.indexOf(" ")
if(space !== -1){
configObj[string.slice(0, space).trim()] = string.slice(space + 1).trim()
}
}
if(!configObj.dir){
configObj.dir = ""
}
configObj.prefix = "custom "
var skinnable = ["song", "stage", "don"]
for(var i in skinnable){
var skinName = skinnable[i]
var skinValue = configObj[skinName]
if(skinValue && skinValue !== "none"){
var fileName = "bg_" + skinName + "_" + configObj.name
var skinPath = this.joinPath(dir, configObj.dir, fileName)
for(var j = 0; j < 2; j++){
if(skinValue !== "static"){
var suffix = (j === 0 ? "_a" : "_b") + ".png"
}else{
var suffix = ".png"
}
var skinFull = this.normPath(skinPath + suffix)
if(skinFull in this.otherFiles){
configObj[fileName + suffix] = this.otherFiles[skinFull]
}else{
configObj[skinName] = null
}
if(skinValue === "static"){
break
}
}
}
}
return configObj
}
loaded(){
this.songs = this.songs.filter(song => typeof song !== "undefined")
if(this.stylesheet.length){
var style = document.createElement("style")
style.appendChild(document.createTextNode(this.stylesheet.join("\n")))
document.head.appendChild(style)
}
if(this.songs.length){
if(this.limited){
assets.otherFiles = this.otherFiles
assets.otherFiles.songTitle = this.songTitle
}
return Promise.resolve(this.songs)
}else{
if(this.noPlugins && this.pluginAmount || Object.keys(this.assetFiles).length){
return Promise.resolve()
}else{
return Promise.reject("nosongs")
}
}
this.clean()
}
joinPath(){
var resultPath = arguments[0]
for(var i = 1; i < arguments.length; i++){
var pPath = arguments[i]
if(pPath && (pPath[0] === "/" || pPath[0] === "\\")){
resultPath = pPath
}else{
var lastChar = resultPath.slice(-1)
if(resultPath && (lastChar !== "/" || lastChar !== "\\")){
resultPath = resultPath + "/"
}
resultPath = resultPath + pPath
}
}
return resultPath
}
normPath(path){
path = path.replace(/\\/g, "/").toLowerCase()
while(path[0] === "/"){
path = path.slice(1)
}
var comps = path.split("/")
for(var i = 0; i < comps.length; i++){
if(comps[i] === "." || comps[i] === ""){
comps.splice(i, 1)
i--
}else if(i !== 0 && comps[i] === ".." && comps[i - 1] !== ".."){
comps.splice(i - 1, 2)
i -= 2
}
}
return comps.join("/")
}
clean(){
delete this.songs
delete this.tjaFiles
delete this.osuFiles
delete this.assetFiles
delete this.otherFiles
}
}