Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
const path = require('path') | |
const smi = require('node-nvidia-smi') | |
window.batch_state = { | |
lines: [], | |
fastModeActuallyFinishedTasks: 0, | |
fastModeOutputPromises: [], | |
lastModel: undefined, | |
lastVocoder: undefined, | |
lineIndex: 0, | |
state: false, | |
outPathsChecked: [], | |
skippedExisting: 0, | |
paginationIndex: 0, | |
taskBarPercent: 0, | |
startTime: undefined, | |
linesDoneSinceStart: 0 | |
} | |
// https://stackoverflow.com/questions/1293147/example-javascript-code-to-parse-csv-data | |
function CSVToArray( strData, strDelimiter ){ | |
// Check to see if the delimiter is defined. If not, | |
// then default to comma. | |
strDelimiter = (strDelimiter || ","); | |
// Create a regular expression to parse the CSV values. | |
var objPattern = new RegExp( | |
( | |
// Delimiters. | |
"(\\" + strDelimiter + "|\\r?\\n|\\r|^)" + | |
// Quoted fields. | |
"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + | |
// Standard fields. | |
"([^\"\\" + strDelimiter + "\\r\\n]*))" | |
), | |
"gi" | |
); | |
// Create an array to hold our data. Give the array | |
// a default empty first row. | |
var arrData = [[]]; | |
// Create an array to hold our individual pattern | |
// matching groups. | |
var arrMatches = null; | |
// Keep looping over the regular expression matches | |
// until we can no longer find a match. | |
while (arrMatches = objPattern.exec( strData )){ | |
// Get the delimiter that was found. | |
var strMatchedDelimiter = arrMatches[ 1 ]; | |
// Check to see if the given delimiter has a length | |
// (is not the start of string) and if it matches | |
// field delimiter. If id does not, then we know | |
// that this delimiter is a row delimiter. | |
if ( | |
strMatchedDelimiter.length && | |
strMatchedDelimiter !== strDelimiter | |
){ | |
// Since we have reached a new row of data, | |
// add an empty row to our data array. | |
arrData.push( [] ); | |
} | |
var strMatchedValue; | |
// Now that we have our delimiter out of the way, | |
// let's check to see which kind of value we | |
// captured (quoted or unquoted). | |
if (arrMatches[ 2 ]){ | |
// We found a quoted value. When we capture | |
// this value, unescape any double quotes. | |
strMatchedValue = arrMatches[ 2 ].replace( | |
new RegExp( "\"\"", "g" ), | |
"\"" | |
); | |
} else { | |
// We found a non-quoted value. | |
strMatchedValue = arrMatches[ 3 ]; | |
} | |
// Now that we have our value string, let's add | |
// it to the data array. | |
arrData[ arrData.length - 1 ].push( strMatchedValue ); | |
} | |
// Return the parsed data. | |
return( arrData ); | |
} | |
window.CSVToArray = CSVToArray | |
let smiInterval = setInterval(() => { | |
try { | |
if (window.userSettings.useGPU) { | |
smi((err, data) => { | |
if (err) { | |
console.log("smi error: ", err) | |
} | |
if (data && data.nvidia_smi_log.cuda_version) { | |
let total | |
let used | |
if (data.nvidia_smi_log.gpu.length) { | |
total = parseInt(data.nvidia_smi_log.gpu[0].fb_memory_usage.total.split(" ")[0]) | |
used = parseInt(data.nvidia_smi_log.gpu[0].fb_memory_usage.used.split(" ")[0]) | |
} else { | |
total = parseInt(data.nvidia_smi_log.gpu.fb_memory_usage.total.split(" ")[0]) | |
used = parseInt(data.nvidia_smi_log.gpu.fb_memory_usage.used.split(" ")[0]) | |
} | |
const percent = used/total*100 | |
vramUsage.innerHTML = `${(used/1000).toFixed(1)}/${(total/1000).toFixed(1)} GB (${percent.toFixed(2)}%)` | |
} | |
}) | |
} else { | |
vramUsage.innerHTML = window.i18n.NOT_USING_GPU | |
} | |
} catch (e) { | |
console.log(e) | |
window.appLogger.log(e.stack) | |
clearInterval(smiInterval) | |
} | |
}, 1000) | |
batch_instructions_btn.addEventListener("click", () => { | |
window.createModal("error", `${window.i18n.BATCH_INSTR1} <a href="https://www.youtube.com/watch?v=PK-m54f84q4" target="_blank">${window.i18n.BATCH_INSTR2}</a> <span>${window.i18n.BATCH_INSTR3}</span>`) | |
}) | |
batch_generateSample.addEventListener("click", () => { | |
// const lines = [] | |
const csv = ["game_id,voice_id,text,vocoder,out_path,pacing"] // TODO: ffmpeg options | |
const games = Object.keys(window.games) | |
if (games.length==0) { | |
window.errorModal(window.i18n.BATCH_ERR_NO_VOICES) | |
return | |
} | |
const sampleText = [ | |
"Include as many lines of text you wish with one line of data per line of voice to be read out.", | |
"Make sure that the required columns (game_id, voice_id, and text) are filled out", | |
"The others can be left blank, and the app will figure out some sensible defaults", | |
"The valid options for vocoder are one of: quickanddirty, waveglow, waveglowBIG, hifi", | |
"If your specified model does not have a bespoke hifi model, it will use the waveglow model, also the default if you leave this blank.", | |
"For all the other options, you can leave them blank.", | |
"If no output path is specified for a specific voice, the default batch output directory will be used", | |
] | |
sampleText.forEach(line => { | |
const game = games[parseInt(Math.random()*games.length)] | |
const gameModels = window.games[game].models | |
const model = gameModels[parseInt(Math.random()*gameModels.length)].variants[0] | |
console.log("game", game, "model", model) | |
const record = { | |
game_id: game, | |
voice_id: model.voiceId, | |
text: line, | |
vocoder: ["quickanddirty","waveglow","waveglowBIG","hifi"][parseInt(Math.random()*4)], | |
out_path: "", | |
pacing: 1 | |
} | |
// lines.push(record) | |
csv.push(Object.values(record).map(v => typeof v =="string" ? `"${v}"` : v).join(",")) | |
}) | |
const out_directory = `${__dirname.replace("\\javascript", "").replace(/\\/g,"/")}/batch`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app") | |
if (!fs.existsSync(out_directory)){ | |
fs.mkdirSync(out_directory) | |
} | |
fs.writeFileSync(`${out_directory}/sample.csv`, csv.join("\n")) | |
// shell.showItemInFolder(`${out_directory}/sample.csv`) | |
er.shell.showItemInFolder(`${out_directory}/sample.csv`) | |
spawn(`explorer`, [out_directory], {stdio: "ignore"}) | |
}) | |
window.readFileTxt = (file) => { | |
return new Promise((resolve, reject) => { | |
const dataLines = [] | |
const reader = new FileReader() | |
reader.readAsText(file) | |
reader.onloadend = () => { | |
const lines = reader.result.replace(/\r\n/g, "\n").split("\n") | |
lines.forEach(line => { | |
if (line.trim().length) { | |
const record = {} | |
record.game_id = window.currentModel.games[0].gameId | |
record.voice_id = window.currentModel.games[0].voiceId | |
record.text = line | |
if (window.currentModel.hifi) { | |
record.vocoder = "hifi" | |
} | |
dataLines.push(record) | |
} | |
}) | |
resolve(dataLines) | |
} | |
}) | |
} | |
window.readFile = (file) => { | |
return new Promise((resolve, reject) => { | |
const dataLines = [] | |
const reader = new FileReader() | |
reader.readAsText(file) | |
reader.onloadend = () => { | |
const lines = reader.result.replace(/\r\n/g, "\n").split("\n") | |
const headerLine = lines.shift() | |
const doRest = () => { | |
const header = headerLine.split(window.userSettings.batch_delimiter).map(head => head.replace(/\r/, "")) | |
lines.forEach(line => { | |
const record = {} | |
if (line.trim().length) { | |
const parts = CSVToArray(line, window.userSettings.batch_delimiter)[0] | |
parts.forEach((val, vi) => { | |
try { | |
let header_val = header[vi].replace(/^"/, "").replace(/"$/, "") | |
record[header_val.replace(/\s/g,"")] = (val||"").replace(/\\/g, "/") | |
} catch (e) { | |
window.errorModal(`Error parsing line: ${val}`) | |
console.log(e) | |
window.appLogger.log(e) | |
} | |
}) | |
dataLines.push(record) | |
} | |
}) | |
resolve(dataLines) | |
} | |
const headerLine_clean = headerLine.replaceAll("\"", "") | |
if (headerLine_clean.includes(window.userSettings.batch_delimiter)) { | |
doRest() | |
} else { | |
const potentialDelimiter = headerLine_clean.split("voice_id")[0].split("game_id")[1] | |
window.confirmModal(window.i18n.BATCH_CHANGE_DELIMITER.replace("_1", window.userSettings.batch_delimiter).replace("_2", potentialDelimiter)).then(response => { | |
if (response) { | |
window.userSettings.batch_delimiter = potentialDelimiter | |
setting_batch_delimiter.value = potentialDelimiter | |
window.saveUserSettings() | |
doRest() | |
} | |
}) | |
} | |
} | |
}) | |
} | |
let handleMetadataCSVdrop_response = undefined | |
const sleep = ms => { | |
return new Promise(resolve => { | |
setTimeout(() => { | |
resolve() | |
}, ms) | |
}) | |
} | |
const handleMetadataCSVdrop = (file) => { | |
return new Promise(async resolve => { | |
i18n_batch_metadata_open_btn.click() | |
handleMetadataCSVdrop_response = undefined | |
batch_metadata_input_gameID.value = window.currentGame.gameId | |
if (window.currentModel) { | |
batch_metadata_input_voiceID.value = window.currentModel.voiceId | |
} | |
await sleep(1000) | |
while (handleMetadataCSVdrop_response === undefined) { | |
if (batchMetadataCSVContainer.style.opacity.length==0 || parseFloat(batchMetadataCSVContainer.style.opacity)==1) { | |
await sleep(1000) | |
} else { | |
handleMetadataCSVdrop_response = false | |
} | |
} | |
if (handleMetadataCSVdrop_response) { | |
const dataLines = [] | |
const reader = new FileReader() | |
reader.readAsText(file) | |
reader.onloadend = () => { | |
const lines = reader.result.replace(/\r\n/g, "\n").split("\n") | |
lines.forEach(line => { | |
if (line.trim().length) { | |
const text = line.split("|")[1] | |
const record = {} | |
record.game_id = batch_metadata_input_gameID.value | |
record.voice_id = batch_metadata_input_voiceID.value | |
record.text = text | |
if (!window.currentModel || window.currentModel.hifi) { | |
record.vocoder = "hifi" | |
} | |
dataLines.push(record) | |
} | |
}) | |
resolve(dataLines) | |
} | |
} else { | |
resolve(false) | |
} | |
}) | |
} | |
i18n_batch_metadata_confirm_btn.addEventListener("click", () => { | |
handleMetadataCSVdrop_response = true | |
batchMetadataCSVContainer.click() | |
batchIcon.click() | |
}) | |
window.uploadBatchCSVs = async (eType, event) => { | |
if (["dragenter", "dragover"].includes(eType)) { | |
batch_main.style.background = "#5b5b5b" | |
batch_main.style.color = "white" | |
} | |
if (["dragleave", "drop"].includes(eType)) { | |
batch_main.style.background = "#4b4b4b" | |
batch_main.style.color = "gray" | |
} | |
event.preventDefault() | |
event.stopPropagation() | |
const dataLines = [] | |
if (eType=="drop") { | |
batchDropZoneNote.innerHTML = window.i18n.PROCESSING_DATA | |
window.batch_state.skippedExisting = 0 | |
const dataTransfer = event.dataTransfer | |
const files = Array.from(dataTransfer.files) | |
for (let fi=0; fi<files.length; fi++) { | |
const file = files[fi] | |
if (!file.name.toLowerCase().endsWith(".csv") || file.name.toLowerCase()=="metadata.csv") { | |
if ( file.name.toLowerCase().endsWith(".txt") || file.name.toLowerCase()=="metadata.csv" ) { | |
if (window.currentModel || file.name.toLowerCase()=="metadata.csv") { | |
window.appLogger.log(`Reading file: ${file.name}`) | |
let records | |
if (file.name.toLowerCase()=="metadata.csv") { | |
records = await handleMetadataCSVdrop(file) | |
if (records===false) { | |
continue | |
} | |
} else { | |
if (window.currentModel) { | |
records = await window.readFileTxt(file) | |
} | |
} | |
if (window.currentModel || file.name.toLowerCase()=="metadata.csv") { | |
if (window.userSettings.batch_skipExisting) { | |
window.appLogger.log("Checking existing files before adding to queue") | |
} else { | |
window.appLogger.log("Adding files to queue") | |
} | |
records.forEach(item => { | |
let outPath | |
if (item.out_path && item.out_path.split("/").reverse()[0].includes(".")) { | |
outPath = item.out_path | |
} else { | |
if (item.out_path) { | |
outPath = item.out_path | |
} else { | |
outPath = window.userSettings.batchOutFolder | |
} | |
if (item.vc_content) { | |
let vc_content_fname = item.vc_content.split("/").reverse()[0] | |
outPath = `${outPath}/${vc_content_fname.slice(0,vc_content_fname.length-4).slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}` | |
} else { | |
outPath = `${outPath}/${item.voice_id}_${item.vocoder}_${item.text.replace(/[\/\\:\*?<>"|]*/g, "").slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}` | |
} | |
} | |
outPath = outPath.startsWith("./") ? window.userSettings.batchOutFolder + outPath.slice(1,100000) : outPath | |
item.out_path = outPath | |
if (window.userSettings.batch_skipExisting && fs.existsSync(outPath)) { | |
window.batch_state.skippedExisting++ | |
} else { | |
dataLines.push(item) | |
} | |
}) | |
} | |
} | |
continue | |
} else { | |
continue | |
} | |
} | |
window.appLogger.log(`Reading file: ${file.name}`) | |
const records = await window.readFile(file) | |
if (window.userSettings.batch_skipExisting) { | |
window.appLogger.log("Checking existing files before adding to queue") | |
} else { | |
window.appLogger.log("Adding files to queue") | |
} | |
records.forEach((item, ii) => { | |
let outPath | |
if (item.out_path && item.out_path.split("/").reverse()[0].includes(".")) { | |
outPath = item.out_path | |
} else { | |
if (item.out_path) { | |
outPath = item.out_path | |
} else { | |
outPath = window.userSettings.batchOutFolder | |
} | |
if (item.vc_content) { | |
let vc_content_fname = item.vc_content.split("/").reverse()[0] | |
outPath = `${outPath}/${vc_content_fname.slice(0,vc_content_fname.length-4).slice(0,item.vc_content.length-4).slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}` | |
} else { | |
outPath = `${outPath}/${item.voice_id}_${item.vocoder}_${item.text.replace(/[\/\\:\*?<>"|]*/g, "").slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}` | |
} | |
} | |
outPath = outPath.startsWith("./") ? window.userSettings.batchOutFolder + outPath.slice(1,100000) : outPath | |
item.out_path = outPath | |
if (window.userSettings.batch_skipExisting && fs.existsSync(outPath)) { | |
window.batch_state.skippedExisting++ | |
} else { | |
dataLines.push(item) | |
} | |
}) | |
} | |
if (dataLines.length==0 && window.batch_state.skippedExisting) { | |
batchDropZoneNote.innerHTML = window.i18n.BATCH_DROPZONE | |
return window.errorModal(window.i18n.BATCH_ERR_SKIPPEDALL.replace("_1", window.batch_state.skippedExisting)) | |
} | |
window.batch_state.paginationIndex = 0 | |
batch_pageNum.value = 1 | |
window.appLogger.log("Preprocessing data...") | |
const cleanedData = window.preProcessCSVData(dataLines) | |
if (cleanedData.length) { | |
window.populateBatchRecordsList(cleanedData) | |
window.appLogger.log("Grouping up lines...") | |
const finalOrder = window.groupBatchLines() | |
window.refreshBatchRecordsList(finalOrder) | |
window.batch_state.lines = finalOrder | |
} else { | |
// batch_clearBtn.click() | |
} | |
window.appLogger.log("batch import done") | |
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize) | |
batch_total_pages.innerHTML = `of ${numPages}` | |
batchDropZoneNote.innerHTML = window.i18n.BATCH_DROPZONE | |
} | |
} | |
window.preProcessCSVData = data => { | |
batch_main.style.display = "block" | |
batchDropZoneNote.style.display = "none" | |
batchRecordsHeader.style.display = "flex" | |
batch_clearBtn.style.display = "inline-block" | |
Array.from(batchRecordsHeader.children).forEach(item => item.style.backgroundColor = `#${window.currentGame.themeColourPrimary}`) | |
const availableGames = Object.keys(window.games) | |
for (let di=0; di<data.length; di++) { | |
try { | |
const record = data[di] | |
// Validate the records first | |
// ================== | |
if (!record.game_id) { | |
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.MISSING} game_id`) | |
return [] | |
} | |
if (!record.voice_id) { | |
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.MISSING} voice_id`) | |
return [] | |
} | |
if ((!record.text || record.text.length==0) && (!record.vc_content)) { | |
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.MISSING} text/vc_content`) | |
return [] | |
} | |
// Check that the game_id exists | |
if (!availableGames.includes(record.game_id)) { | |
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: game_id "${record.game_id}" ${window.i18n.BATCH_ERR_GAMEID} <br><br>(${availableGames.join(', ')})`) | |
return [] | |
} | |
// Check that the voice_id exists | |
const gameVoices = [] | |
window.games[record.game_id].models.forEach(model => { | |
model.variants.forEach(variant => gameVoices.push(variant.voiceId)) | |
}) | |
if (!gameVoices.includes(record.voice_id)) { | |
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: voice_id "${record.voice_id}" ${window.i18n.BATCH_ERR_VOICEID}: ${record.game_id}`) | |
return [] | |
} | |
// Check that the vocoder exists | |
if (!["quickanddirty", "waveglow", "waveglowBIG", "hifi", "", "-", undefined].includes(record.vocoder)) { | |
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.BATCHH_VOCODER} "${record.vocoder}" ${window.i18n.BATCH_ERR_VOCODER1}: quickanddirty, waveglow, waveglowBIG, hifi ${window.i18n.BATCH_ERR_VOCODER2}`) | |
return [] | |
} | |
data[di].modelType = undefined | |
let hasHifi = false | |
window.games[data[di].game_id].models.forEach(model => { | |
model.variants.forEach(variant => { | |
if (variant.voiceId==data[di].voice_id) { | |
data[di].modelType = variant.modelType || model.modelType | |
record.voiceName = model.voiceName // For easy access later on | |
if (variant.hifi) { | |
hasHifi = variant.hifi | |
} | |
if (variant.lang && !record.lang) { | |
record.lang = "en" | |
} | |
// TODO allow batch mode voice conversion/speech to speech by computing the embs of specified audio files instead of using the base voice embedding | |
// Also TODO, might need to allow for custom voice embeddings | |
if (variant.modelType=="xVAPitch") { | |
record.base_emb = variant.base_speaker_emb | |
record.vocoder = "-" | |
} | |
} | |
}) | |
}) | |
// Fill with defaults | |
// ================== | |
if (!record.out_path) { | |
record.out_path = window.userSettings.batchOutFolder | |
} | |
if (!record.pacing) { | |
record.pacing = 1 | |
} | |
record.pacing = parseFloat(record.pacing) | |
if (!record.pitch_amp) { | |
record.pitch_amp = 1 | |
} | |
record.pitch_amp = parseFloat(record.pitch_amp) | |
if (!record.out_path.includes(":/") && !record.out_path.startsWith("./")) { | |
record.out_path = `./${record.out_path}` | |
} | |
if (!record.vocoder || (record.vocoder=="hifi" && !hasHifi)) { | |
record.vocoder = "quickanddirty" | |
} | |
} catch (e) { | |
console.log(e) | |
window.appLogger.log(e) | |
console.log(data[di]) | |
console.log(window.games[data[di].game_id]) | |
} | |
} | |
return data | |
} | |
window.populateBatchRecordsList = records => { | |
batch_synthesizeBtn.style.display = "inline-block" | |
batchDropZoneNote.style.display = "none" | |
records.forEach((record, ri) => { | |
const row = createElem("div") | |
const rNumElem = createElem("div", batchRecordsContainer.children.length.toString()) | |
const rStatusElem = createElem("div", "Ready") | |
const rActionsElem = createElem("div") | |
const rVoiceElem = createElem("div", record.voice_id) | |
const rTextElem = createElem("div", (record.vc_content?record.vc_content:record.text).toString()) | |
rTextElem.title = rTextElem.innerText | |
if (record.vc_content) { | |
rTextElem.style.fontStyle = "italic" | |
} | |
const rGameElem = createElem("div", record.game_id) | |
const rOutPathElem = createElem("div", "‎"+record.out_path+"‎") | |
rOutPathElem.title = record.out_path | |
const rBaseLangElem = createElem("div", record.vc_content?"-":(record.lang||" ").toString()) | |
const rVCStyleElem = createElem("div", (record.vc_style||" ").toString()) | |
rVCStyleElem.title = rVCStyleElem.innerText | |
const rVocoderElem = createElem("div", record.vocoder) | |
const rPacingElem = createElem("div", record.vc_content?"-":(record.pacing||" ").toString()) | |
const rPitchAmpElem = createElem("div", record.vc_content?"-":(record.pitch_amp||" ").toString()) | |
row.appendChild(rNumElem) | |
row.appendChild(rStatusElem) | |
row.appendChild(rActionsElem) | |
row.appendChild(rVoiceElem) | |
row.appendChild(rTextElem) | |
row.appendChild(rGameElem) | |
row.appendChild(rOutPathElem) | |
row.appendChild(rBaseLangElem) | |
row.appendChild(rVCStyleElem) | |
row.appendChild(rVocoderElem) | |
row.appendChild(rPacingElem) | |
row.appendChild(rPitchAmpElem) | |
window.batch_state.lines.push([record, row, ri]) | |
}) | |
} | |
window.refreshBatchRecordsList = (finalOrder) => { | |
batchRecordsContainer.innerHTML = "" | |
finalOrder = finalOrder ? finalOrder : window.batch_state.lines | |
const startIndex = (window.batch_state.paginationIndex*window.userSettings.batch_paginationSize) | |
const endIndex = Math.min(startIndex+window.userSettings.batch_paginationSize, finalOrder.length) | |
for (let ri=startIndex; ri<endIndex; ri++) { | |
const recordAndElem = finalOrder[ri] | |
recordAndElem[1].children[0].innerHTML = (ri+1)//batchRecordsContainer.children.length.toString() | |
batchRecordsContainer.appendChild(recordAndElem[1]) | |
} | |
window.toggleNumericalRecordsDisplay() | |
} | |
// Sort the lines by voice_id, and then by vocoder used | |
window.groupBatchLines = () => { | |
if (window.userSettings.batch_doGrouping) { | |
const voices_order = [] | |
const lines = window.batch_state.lines.sort((a,b) => { | |
return a.voice_id - b.voice_id | |
}) | |
const voices_groups = {} | |
// Get the order of the voice_id, and group them up | |
window.batch_state.lines.forEach(record => { | |
if (!voices_order.includes(record[0].voice_id)) { | |
voices_order.push(record[0].voice_id) | |
voices_groups[record[0].voice_id] = [] | |
} | |
voices_groups[record[0].voice_id].push(record) | |
}) | |
// Go through the voice groups and sort them by vocoder | |
if (window.userSettings.batch_doVocoderGrouping) { | |
voices_order.forEach(voice_id => { | |
voices_groups[voice_id] = voices_groups[voice_id].sort((a,b) => a[0].vocoder<b[0].vocoder?1:-1) | |
}) | |
} | |
// Collate everything back into the final order | |
const finalOrder = [] | |
voices_order.forEach(voice_id => { | |
voices_groups[voice_id].forEach(record => finalOrder.push(record)) | |
}) | |
return finalOrder | |
} else { | |
return window.batch_state.lines | |
} | |
} | |
batch_clearBtn.addEventListener("click", () => { | |
window.batch_state.lines = [] | |
batch_main.style.display = "flex" | |
batchDropZoneNote.style.display = "block" | |
batchRecordsHeader.style.display = "none" | |
batch_clearBtn.style.display = "none" | |
batch_outputFolderInput.style.display = "inline-block" | |
batch_clearDirOpts.style.display = "flex" | |
batch_skipExistingOpts.style.display = "flex" | |
batch_useSR.style.display = "flex" | |
batch_useCleanup.style.display = "flex" | |
batch_outputNumericallyOpts.style.display = "flex" | |
batch_progressItems.style.display = "none" | |
batch_progressBar.style.display = "none" | |
batch_pauseBtn.style.display = "none" | |
batch_stopBtn.style.display = "none" | |
batch_synthesizeBtn.style.display = "none" | |
batchRecordsContainer.innerHTML = "" | |
}) | |
window.startBatch = () => { | |
// Output directory | |
if (!fs.existsSync(window.userSettings.batchOutFolder)) { | |
window.userSettings.batchOutFolder.split("/") | |
.reduce((prevPath, folder) => { | |
const currentPath = path.join(prevPath, folder, path.sep); | |
if (!fs.existsSync(currentPath)){ | |
try { | |
fs.mkdirSync(currentPath); | |
} catch (e) { | |
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}:<br><br>`+e.message) | |
throw "" | |
} | |
} | |
return currentPath; | |
}, ''); | |
} | |
if (batch_clearDirFirstCkbx.checked) { | |
const filesAndFolders = fs.readdirSync(window.userSettings.batchOutFolder) | |
filesAndFolders.forEach(faf => { | |
// Ignore .csv and .txt files | |
if (faf.toLowerCase().endsWith(".csv") || faf.toLowerCase().endsWith(".txt")) { | |
return | |
} | |
if (fs.lstatSync(`${window.userSettings.batchOutFolder}/${faf}`).isDirectory()) { | |
window.deleteFolderRecursive(`${window.userSettings.batchOutFolder}/${faf}`, false) | |
} else { | |
fs.unlinkSync(`${window.userSettings.batchOutFolder}/${faf}`) | |
} | |
console.log(`${window.userSettings.batchOutFolder}/${faf}`, ) | |
}) | |
} | |
batch_synthesizeBtn.style.display = "none" | |
batch_clearBtn.style.display = "none" | |
batch_outputFolderInput.style.display = "none" | |
batch_clearDirOpts.style.display = "none" | |
batch_skipExistingOpts.style.display = "none" | |
batch_useSR.style.display = "none" | |
batch_useCleanup.style.display = "none" | |
batch_outputNumericallyOpts.style.display = "none" | |
batch_progressItems.style.display = "flex" | |
batch_progressBar.style.display = "flex" | |
batch_pauseBtn.style.display = "inline-block" | |
batch_stopBtn.style.display = "inline-block" | |
batch_openDirBtn.style.display = "none" | |
window.batch_state.lines.forEach(record => { | |
record[1].children[1].innerHTML = window.i18n.READY | |
record[1].children[1].style.background = "none" | |
}) | |
window.batch_state.fastModeOutputPromises = [] | |
window.batch_state.fastModeActuallyFinishedTasks = 0 | |
window.batch_state.lineIndex = 0 | |
window.batch_state.state = true | |
window.batch_state.outPathsChecked = [] | |
window.batch_state.startTime = new Date() | |
window.batch_state.linesDoneSinceStart = 0 | |
window.performSynthesis() | |
} | |
window.batchChangeVoice = (game, voice, modelType) => { | |
return new Promise((resolve) => { | |
if (!window.batch_state.state) { | |
return resolve() | |
} | |
// Update the main app with any changes, if a voice has already been selected | |
if (window.currentModel) { | |
generateVoiceButton.innerHTML = window.i18n.LOAD_MODEL | |
keepSampleButton.style.display = "none" | |
wavesurferContainer.innerHTML = "" | |
const modelGameFolder = window.currentModel.audioPreviewPath.split("/")[0] | |
const modelFileName = window.currentModel.audioPreviewPath.split("/")[1].split(".wav")[0] | |
generateVoiceButton.dataset.modelQuery = JSON.stringify({ | |
outputs: parseInt(window.currentModel.outputs), | |
model: `${window.path}/models/${modelGameFolder}/${modelFileName}`, | |
model_speakers: window.currentModel.emb_size, | |
cmudict: window.currentModel.cmudict | |
}) | |
} | |
if (window.batch_state.state) { | |
batch_progressNotes.innerHTML = `${window.i18n.BATCH_CHANGING_MODEL_TO}: ${voice}` | |
} | |
let model | |
window.games[game].models.forEach(gameModel => { | |
gameModel.variants.forEach(variant => { | |
if (variant.voiceId==voice) { | |
model = variant | |
} | |
}) | |
}) | |
doFetch(`http://localhost:8008/loadModel`, { | |
method: "Post", | |
body: JSON.stringify({ | |
"modelType": modelType, | |
"outputs": null, | |
"model": `${window.userSettings[`modelspath_${game}`]}/${voice}`, | |
"model_speakers": model.num_speakers, | |
"base_lang": model.lang, | |
"pluginsContext": JSON.stringify(window.pluginsContext) | |
}) | |
}).then(r=>r.text()).then(res => { | |
resolve() | |
}).catch(async e => { | |
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") { | |
await window.batchChangeVoice(game, voice, modelType) | |
resolve() | |
} else { | |
console.log(e) | |
window.appLogger.log(e) | |
batch_pauseBtn.click() | |
if (document.getElementById("activeModal")) { | |
activeModal.remove() | |
} | |
if (e.code=="ENOENT") { | |
window.errorModal(window.i18n.ERR_SERVER) | |
} else { | |
window.errorModal(e.message) | |
} | |
resolve() | |
} | |
}) | |
}) | |
} | |
window.batchChangeVocoder = (vocoder, game, voice) => { | |
return new Promise((resolve) => { | |
if (!window.batch_state.state) { | |
return resolve() | |
} | |
console.log("Changing vocoder: ", vocoder) | |
if (window.batch_state.state) { | |
batch_progressNotes.innerHTML = `${window.i18n.BATCH_CHANGING_VOCODER_TO}: ${vocoder}` | |
} | |
const vocoderMappings = [["waveglow", "256_waveglow"], ["waveglowBIG", "big_waveglow"], ["quickanddirty", "qnd"], ["hifi", `${game}/${voice}.hg.pt`]] | |
const vocoderId = vocoderMappings.find(record => record[0]==vocoder)[1] | |
doFetch(`http://localhost:8008/setVocoder`, { | |
method: "Post", | |
body: JSON.stringify({ | |
vocoder: vocoderId, | |
modelPath: vocoderId=="256_waveglow" ? window.userSettings.waveglow_path : window.userSettings.bigwaveglow_path, | |
}) | |
}).then(r=>r.text()).then((res) => { | |
if (res=="ENOENT") { | |
closeModal(undefined, batchGenerationContainer).then(() => { | |
setTimeout(() => { | |
vocoder_select.value = window.userSettings.vocoder | |
window.errorModal(`${window.i18n.BATCH_MODEL_NOT_FOUND}.${vocoderId.includes("waveglow")?" "+window.i18n.BATCH_DOWNLOAD_WAVEGLOW:""}`) | |
batch_pauseBtn.click() | |
resolve() | |
}, 300) | |
}) | |
} else { | |
window.batch_state.lastVocoder = vocoder | |
resolve() | |
} | |
}).catch(async e => { | |
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") { | |
await window.batchChangeVocoder(vocoder, game, voice) | |
resolve() | |
} else { | |
console.log(e) | |
window.appLogger.log(e) | |
batch_pauseBtn.click() | |
if (document.getElementById("activeModal")) { | |
activeModal.remove() | |
} | |
if (e.code=="ENOENT") { | |
window.errorModal(window.i18n.ERR_SERVER) | |
} else { | |
window.errorModal(e.message) | |
} | |
resolve() | |
} | |
}) | |
}) | |
} | |
window.prepareLinesBatchForSynth = () => { | |
const linesBatch = [] | |
const records = [] | |
let firstItemVoiceId = undefined | |
let firstItemVocoder = undefined | |
let speaker_i = 0 | |
for (let i=0; i<Math.min(window.userSettings.batch_batchSize, window.batch_state.lines.length-window.batch_state.lineIndex); i++) { | |
const record = window.batch_state.lines[window.batch_state.lineIndex+i] | |
const vocoderMappings = [["waveglow", "256_waveglow"], ["waveglowBIG", "big_waveglow"], ["quickanddirty", "qnd"], ["hifi", `${record[0].game_id}/${record[0].voice_id}.hg.pt`]] | |
const vocoder = record[0].vocoder=="-"?"-":vocoderMappings.find(voc => voc[0]==record[0].vocoder)[1] | |
if (firstItemVoiceId==undefined) firstItemVoiceId = record[0].voice_id | |
if (firstItemVocoder==undefined) firstItemVocoder = vocoder | |
if (record[0].voice_id!=firstItemVoiceId || vocoder!=firstItemVocoder) { | |
break | |
} | |
let model | |
window.games[record[0].game_id].models.forEach(gamesModel => { | |
gamesModel.variants.forEach(variant => { | |
if (variant.voiceId==record[0].voice_id) { | |
model = variant | |
} | |
}) | |
}) | |
const sequence = record[0].text | |
const pitch = undefined // maybe later | |
const duration = undefined // maybe later | |
speaker_i = model.emb_i || 0 | |
let pace = record[0].pacing | |
pace = Number.isNaN(pace) ? 1.0 : pace | |
let pitch_amp = record[0].pitch_amp | |
pitch_amp = Number.isNaN(pitch_amp) ? 1.0 : pitch_amp | |
const tempFileNum = `${Math.random().toString().split(".")[1]}` | |
const tempFileLocation = `${window.path}/output/temp-${tempFileNum}.wav` | |
let outPath | |
let outFolder | |
outPath = record[0].out_path | |
outFolder = String(record[0].out_path).split("/").reverse().slice(1,10000).reverse().join("/") | |
outFolder = outFolder.length ? outFolder : window.userSettings.batchOutFolder | |
if (batch_outputNumerically.checked) { | |
outPath = `${window.userSettings.batchOutFolder}/${String(record[2]).padStart(10, '0')}.${outPath.split(".").reverse()[0]}` | |
} else { | |
outPath = outPath.startsWith("./") ? window.userSettings.batchOutFolder + outPath.slice(1,100000) : outPath | |
} | |
linesBatch.push([sequence, pitch, duration, pace, tempFileLocation, outPath, outFolder, pitch_amp, record[0].lang, record[0].base_emb, record[0].vc_content, record[0].vc_style]) | |
records.push(record) | |
} | |
return [speaker_i, firstItemVoiceId, firstItemVocoder, linesBatch, records] | |
} | |
window.addActionButtons = (records, ri) => { | |
let audioPreview | |
const playButton = createElem("button.smallButton", window.i18n.PLAY) | |
playButton.style.background = `#${window.currentGame.themeColourPrimary}` | |
playButton.addEventListener("click", () => { | |
let audioPreviewPath = records[ri][0].fileOutputPath | |
if (audioPreviewPath.startsWith("./")) { | |
audioPreviewPath = window.userSettings.batchOutFolder + audioPreviewPath.replace("./", "/") | |
} | |
if (audioPreview==undefined) { | |
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", { | |
src: audioPreviewPath | |
})) | |
audioPreview.addEventListener("play", () => { | |
if (window.ctrlKeyIsPressed) { | |
audioPreview.setSinkId(window.userSettings.alt_speaker) | |
} else { | |
audioPreview.setSinkId(window.userSettings.base_speaker) | |
} | |
}) | |
audioPreview.setSinkId(window.userSettings.base_speaker) | |
} | |
}) | |
records[ri][1].children[2].appendChild(playButton) | |
// If not a Voice Conversion line | |
if (!records[ri][0].vc_content) { | |
const editButton = createElem("button.smallButton", window.i18n.EDIT) | |
editButton.style.background = `#${window.currentGame.themeColourPrimary}` | |
editButton.addEventListener("click", () => { | |
audioPreview = undefined | |
if (window.batch_state.state) { | |
window.errorModal(window.i18n.BATCH_ERR_EDIT) | |
return | |
} | |
// Change app theme to the voice's game | |
if (window.currentGame.gameId!=records[ri][0].game_id) { | |
window.changeGame(window.gameAssets[records[ri][0].game_id]) | |
} | |
dialogueInput.value = records[ri][0].text | |
// Simulate voice loading through the UI | |
if (!window.currentModel || window.currentModel.voiceId != records[ri][0].voice_id) { | |
const voiceName = records[ri][0].voiceName | |
const voiceButton = Array.from(voiceTypeContainer.children).find(button => button.innerHTML==voiceName) | |
voiceButton.click() | |
vocoder_select.value = records[ri][0].vocoder=="hifi" ? `${records[ri][0].game_id}/${records[ri][0].voice_id}.hg.pt` : records[ri][0].vocoder | |
generateVoiceButton.click() | |
} | |
window.closeModal(batchGenerationContainer) | |
setTimeout(() => { | |
let audioPreviewPath = records[ri][0].fileOutputPath | |
if (audioPreviewPath.startsWith("./")) { | |
audioPreviewPath = window.userSettings.batchOutFolder + audioPreviewPath.replace("./", "/") | |
} | |
keepSampleButton.dataset.newFileLocation = "BATCH_EDIT"+audioPreviewPath | |
generateVoiceButton.click() | |
}, 500) | |
}) | |
records[ri][1].children[2].appendChild(editButton) | |
} | |
} | |
window.batchKickOffMPffmpegOutput = (records, tempPaths, outPaths, options, extraInfo) => { | |
let hasShownError = false | |
return new Promise((resolve, reject) => { | |
doFetch(`http://localhost:8008/batchOutputAudio`, { | |
method: "Post", | |
body: JSON.stringify({ | |
input_paths: tempPaths, | |
output_paths: outPaths, | |
isBatchMode: true, | |
pluginsContext: JSON.stringify(window.pluginsContext), | |
processes: window.userSettings.batch_MPCount, | |
extraInfo: extraInfo, | |
options: JSON.stringify(options) | |
}) | |
}).then(r=>r.text()).then(res => { | |
res = res.split("\n") | |
res.forEach((resItem, ri) => { | |
if (resItem.length && resItem!="-") { | |
window.appLogger.log(`Batch error, item ${ri} - ${resItem}`) | |
if (window.batch_state.state) { | |
batch_pauseBtn.click() | |
} | |
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}:<br><br>`+resItem) | |
hasShownError = true | |
records[ri][1].children[1].innerHTML = window.i18n.FAILED | |
records[ri][1].children[1].style.background = "red" | |
} else { | |
records[ri][1].children[1].innerHTML = window.i18n.DONE | |
records[ri][1].children[1].style.background = "green" | |
fs.unlinkSync(tempPaths[ri]) | |
window.addActionButtons(records, ri) | |
} | |
// if (!window.userSettings.batch_fastMode) { // No more fast modde. TODO, remove completely | |
window.batch_state.lineIndex += 1 | |
// } | |
window.batch_state.fastModeActuallyFinishedTasks += 1 | |
}) | |
const percentDone = (window.batch_state.fastModeActuallyFinishedTasks) / window.batch_state.lines.length * 100 | |
batch_progressBar.style.background = `linear-gradient(90deg, green ${parseInt(percentDone)}%, rgba(255,255,255,0) ${parseInt(percentDone)}%)` | |
batch_progressBar.innerHTML = `${parseInt(percentDone* 100)/100}%` | |
window.batch_state.taskBarPercent = percentDone/100 | |
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent) | |
window.adjustETA() | |
resolve() | |
}).catch(async e => { | |
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") { | |
await window.batchKickOffMPffmpegOutput(records, tempPaths, outPaths, options, extraInfo) | |
resolve() | |
} else { | |
console.log(e) | |
window.appLogger.log(e.stack) | |
if (document.getElementById("activeModal")) { | |
activeModal.remove() | |
} | |
if (!hasShownError) { | |
window.errorModal(e.message) | |
} | |
resolve() | |
} | |
}) | |
}) | |
} | |
window.batchKickOffFfmpegOutput = (ri, linesBatch, records, tempFileLocation, body) => { | |
return new Promise((resolve, reject) => { | |
doFetch(`http://localhost:8008/outputAudio`, { | |
method: "Post", | |
body | |
}).then(r=>r.text()).then(res => { | |
if (res.length && res!="-") { | |
window.appLogger.log("res", res) | |
if (window.batch_state.state) { | |
batch_pauseBtn.click() | |
} | |
for (let ri2=ri; ri2<linesBatch.length; ri2++) { | |
records[ri][1].children[1].innerHTML = window.i18n.FAILED | |
records[ri][1].children[1].style.background = "red" | |
} | |
reject(res) | |
} else { | |
records[ri][1].children[1].innerHTML = window.i18n.DONE | |
records[ri][1].children[1].style.background = "green" | |
fs.unlinkSync(tempFileLocation) | |
window.addActionButtons(records, ri) | |
window.batch_state.fastModeActuallyFinishedTasks += 1 | |
const percentDone = (window.batch_state.fastModeActuallyFinishedTasks) / window.batch_state.lines.length * 100 | |
batch_progressBar.style.background = `linear-gradient(90deg, green ${parseInt(percentDone)}%, rgba(255,255,255,0) ${parseInt(percentDone)}%)` | |
batch_progressBar.innerHTML = `${parseInt(percentDone* 100)/100}%` | |
window.batch_state.taskBarPercent = percentDone/100 | |
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent) | |
window.adjustETA() | |
resolve() | |
} | |
}).catch(async e => { | |
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") { | |
await window.batchKickOffFfmpegOutput(ri, linesBatch, records, tempFileLocation, body) | |
resolve() | |
} else { | |
console.log(e) | |
window.appLogger.log(e) | |
batch_pauseBtn.click() | |
if (document.getElementById("activeModal")) { | |
activeModal.remove() | |
} | |
window.errorModal(e.message) | |
resolve() | |
} | |
}) | |
}) | |
} | |
window.batchKickOffGeneration = () => { | |
return new Promise((resolve) => { | |
if (!window.batch_state.state) { | |
return resolve() | |
} | |
const [speaker_i, voice_id, vocoder, linesBatch, records] = window.prepareLinesBatchForSynth() | |
records.forEach((record, ri) => { | |
record[1].children[1].innerHTML = window.i18n.RUNNING | |
record[1].children[1].style.background = "goldenrod" | |
record[0].fileOutputPath = linesBatch[ri][5] | |
}) | |
const record = window.batch_state.lines[window.batch_state.lineIndex] | |
if (window.batch_state.state) { | |
if (linesBatch.length==1) { | |
batch_progressNotes.innerHTML = `${window.i18n.SYNTHESIZING}: <i>${record[0].text}</i>` | |
} else { | |
batch_progressNotes.innerHTML = `${window.i18n.SYNTHESIZING} ${linesBatch.length} ${window.i18n.LINES}` | |
} | |
} | |
const batchPostData = { | |
modelType: records[0][0].modelType, | |
batchSize: window.userSettings.batch_batchSize, | |
defaultOutFolder: window.userSettings.batchOutFolder, | |
pluginsContext: JSON.stringify(window.pluginsContext), | |
outputJSON: window.userSettings.batch_json, | |
useSR: batch_useSRCkbx.checked, | |
useCleanup: batch_useCleanupCkbx.checked, | |
speaker_i, vocoder, linesBatch | |
} | |
doFetch(`http://localhost:8008/synthesize_batch`, { | |
method: "Post", | |
body: JSON.stringify(batchPostData) | |
}).then(r=>r.text()).then(async (res) => { | |
if (res && res!="-") { | |
if (res=="CUDA OOM") { | |
window.errorModal(window.i18n.BATCH_ERR_CUDA_OOM) | |
} else { | |
window.errorModal(res.replace(/\n/g, "<br>")) | |
} | |
if (window.batch_state.state) { | |
batch_pauseBtn.click() | |
} | |
return | |
} | |
// Create the output directory if it does not exist | |
linesBatch.forEach(record => { | |
let outFolder = record[6].startsWith("./") ? window.userSettings.batchOutFolder + record[6].slice(1,100000) : record[6] | |
if (!window.batch_state.outPathsChecked.includes(outFolder)) { | |
window.batch_state.outPathsChecked.push(outFolder) | |
if (!fs.existsSync(outFolder)) { | |
window.createFolderRecursive(outFolder) | |
} | |
} | |
}) | |
if (window.userSettings.audio.ffmpeg) { | |
const options = { | |
hz: window.userSettings.audio.hz, | |
padStart: window.userSettings.audio.padStart, | |
padEnd: window.userSettings.audio.padEnd, | |
bit_depth: window.userSettings.audio.bitdepth, | |
amplitude: window.userSettings.audio.amplitude, | |
pitchMult: window.userSettings.audio.pitchMult, | |
tempo: window.userSettings.audio.tempo, | |
deessing: window.userSettings.audio.deessing, | |
nr: window.userSettings.audio.nr, | |
nf: window.userSettings.audio.nf, | |
useNR: window.userSettings.audio.useNR, | |
useSR: batch_useSRCkbx.checked, | |
useCleanup: batch_useCleanupCkbx.checked, | |
} | |
if (window.batch_state.state) { | |
batch_progressNotes.innerHTML = window.i18n.BATCH_OUTPUTTING_FFMPEG | |
} | |
const tempPaths = linesBatch.map(line => line[4]) | |
const outPaths = linesBatch.map((line, li) => { | |
let outPath = linesBatch[li][5].includes(":") || linesBatch[li][5].includes("./") ? linesBatch[li][5] : `${linesBatch[li][6]}/${linesBatch[li][5]}` | |
if (outPath.startsWith("./")) { | |
outPath = window.userSettings.batchOutFolder + outPath.slice(1,100000) | |
} | |
return outPath | |
}) | |
if (window.userSettings.batch_useMP) { | |
const extraInfo = { | |
game: records.map(rec => rec[0].game_id), | |
voiceId: records.map(rec => rec[0].voice_id), | |
voiceName: records.map(rec => rec[0].voiceName), | |
inputSequence: records.map(rec => rec[0].text) | |
} | |
if (window.userSettings.batch_fastMode && false) { // No more fast mode. TODO, remove completely | |
window.batch_state.fastModeOutputPromises.push(window.batchKickOffMPffmpegOutput(records, tempPaths, outPaths, options, JSON.stringify(extraInfo))) | |
window.batch_state.lineIndex += records.length | |
} else { | |
await window.batchKickOffMPffmpegOutput(records, tempPaths, outPaths, options, JSON.stringify(extraInfo)) | |
} | |
} else { | |
for (let ri=0; ri<linesBatch.length; ri++) { | |
let tempFileLocation = tempPaths[ri] | |
let outPath = outPaths[ri] | |
try { | |
if (window.batch_state.state) { | |
records[ri][1].children[1].innerHTML = window.i18n.OUTPUTTING | |
const extraInfo = { | |
game: records[ri][0].game_id, | |
voiceId: records[ri][0].voiceId, | |
voiceName: records[ri][0].voiceName, | |
letters: records[ri][0].text | |
} | |
if (window.userSettings.batch_fastMode && false) { // No more fast modde. TODO, remove completely | |
window.batch_state.fastModeOutputPromises.push(window.batchKickOffFfmpegOutput(ri, linesBatch, records, tempFileLocation, JSON.stringify({ | |
input_path: tempFileLocation, | |
output_path: outPath, | |
isBatchMode: true, | |
pluginsContext: JSON.stringify(window.pluginsContext), | |
extraInfo: JSON.stringify(extraInfo), | |
options: JSON.stringify(options) | |
}))) | |
} else { | |
await window.batchKickOffFfmpegOutput(ri, linesBatch, records, tempFileLocation, JSON.stringify({ | |
input_path: tempFileLocation, | |
output_path: outPath, | |
isBatchMode: true, | |
pluginsContext: JSON.stringify(window.pluginsContext), | |
extraInfo: JSON.stringify(extraInfo), | |
options: JSON.stringify(options) | |
})) | |
} | |
window.batch_state.lineIndex += 1 | |
} | |
} catch (e) { | |
console.log(e) | |
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}:<br><br>`+e) | |
resolve() | |
} | |
} | |
} | |
window.batch_state.linesDoneSinceStart += linesBatch.length | |
resolve() | |
} else { | |
linesBatch.forEach((lineRecord, li) => { | |
let tempFileLocation = lineRecord[4] | |
let outPath = lineRecord[5] | |
try { | |
fs.copyFileSync(tempFileLocation, outPath) | |
records[li][1].children[1].innerHTML = window.i18n.DONE | |
records[li][1].children[1].style.background = "green" | |
window.batch_state.lineIndex += 1 | |
window.addActionButtons(records, li) | |
} catch (err) { | |
console.log(err) | |
window.appLogger.log(err) | |
window.errorModal(err.message) | |
batch_pauseBtn.click() | |
} | |
window.batch_state.linesDoneSinceStart += linesBatch.length | |
resolve() | |
}) | |
} | |
}).catch(async e => { | |
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") { | |
await window.batchKickOffGeneration() | |
resolve() | |
} else { | |
console.log(e) | |
window.appLogger.log(e) | |
batch_pauseBtn.click() | |
if (document.getElementById("activeModal")) { | |
activeModal.remove() | |
} | |
console.log(e.message) | |
window.errorModal(e.message).then(() => resolve()) | |
} | |
}) | |
}) | |
} | |
window.performSynthesis = async () => { | |
if (batch_state.lineIndex-batch_state.fastModeActuallyFinishedTasks > window.userSettings.batch_fastModeMaxParallelizations) { | |
console.log(`Ahead by ${batch_state.lineIndex-batch_state.fastModeActuallyFinishedTasks} tasks. Waiting...`) | |
setTimeout(() => {window.performSynthesis()}, 1000) | |
return | |
} | |
if (!window.batch_state.state) { | |
return | |
} | |
if (window.batch_state.lineIndex==0) { | |
const percentDone = (window.batch_state.lineIndex) / window.batch_state.lines.length * 100 | |
batch_progressBar.style.background = `linear-gradient(90deg, green ${parseInt(percentDone)}%, rgba(255,255,255,0) ${parseInt(percentDone)}%)` | |
batch_progressBar.innerHTML = `${parseInt(percentDone* 100)/100}%` | |
window.batch_state.taskBarPercent = percentDone/100 | |
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent) | |
} | |
const record = window.batch_state.lines[window.batch_state.lineIndex] | |
// Change the voice model if the next line uses a different one | |
if (window.batch_state.lastModel!=record[0].voice_id) { | |
await window.batchChangeVoice(record[0].game_id, record[0].voice_id, record[0].modelType) | |
window.batch_state.lastModel = record[0].voice_id | |
} | |
// Change the vocoder if the next line uses a different one | |
if (window.batch_state.lastVocoder!=record[0].vocoder && record[0].vocoder!="-") { | |
await window.batchChangeVocoder(record[0].vocoder, record[0].game_id, record[0].voice_id) | |
} | |
await window.batchKickOffGeneration() | |
if (window.batch_state.lineIndex==window.batch_state.lines.length) { | |
// The end | |
if (window.userSettings.batch_fastMode && false) { // No more fast modde. TODO, remove completely | |
Promise.all(window.batch_state.fastModeOutputPromises).then(() => { | |
window.stopBatch() | |
batch_openDirBtn.style.display = "inline-block" | |
}) | |
} else { | |
window.stopBatch() | |
batch_openDirBtn.style.display = "inline-block" | |
} | |
} else { | |
window.performSynthesis() | |
} | |
} | |
window.pauseResumeBatch = () => { | |
batch_progressNotes.innerHTML = window.i18n.PAUSED | |
const isRunning = window.batch_state.state | |
batch_pauseBtn.innerHTML = isRunning ? window.i18n.RESUME : window.i18n.PAUSE | |
window.batch_state.state = !isRunning | |
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent?window.batch_state.taskBarPercent:1, {mode: isRunning ? "paused" : "normal"}) | |
if (window.batch_state.state) { | |
window.batch_state.startTime = new Date() | |
window.batch_state.linesDoneSinceStart = 0 | |
} | |
if (!isRunning) { | |
window.performSynthesis() | |
} | |
} | |
window.stopBatch = (stoppedByUser) => { | |
window.electronBrowserWindow.setProgressBar(0) | |
window.batch_state.state = false | |
window.batch_state.lineIndex = 0 | |
batch_ETA_container.style.opacity = 0 | |
batch_synthesizeBtn.style.display = "inline-block" | |
batch_clearBtn.style.display = "inline-block" | |
batch_outputFolderInput.style.display = "inline-block" | |
batch_clearDirOpts.style.display = "flex" | |
batch_skipExistingOpts.style.display = "flex" | |
batch_useSR.style.display = "flex" | |
batch_useCleanup.style.display = "flex" | |
batch_outputNumericallyOpts.style.display = "flex" | |
batch_progressItems.style.display = "none" | |
batch_progressBar.style.display = "none" | |
batch_pauseBtn.style.display = "none" | |
batch_stopBtn.style.display = "none" | |
window.batch_state.lines.forEach(record => { | |
if (record[1].children[1].innerHTML==window.i18n.READY || record[1].children[1].innerHTML==window.i18n.RUNNING) { | |
record[1].children[1].innerHTML = window.i18n.STOPPED | |
record[1].children[1].style.background = "none" | |
} | |
}) | |
const pluginData = { | |
stoppedByUser: stoppedByUser | |
} | |
window.pluginsManager.runPlugins(window.pluginsManager.pluginsModules["batch-stop"]["post"], event="post batch-stop", pluginData) | |
} | |
window.adjustETA = () => { | |
if (window.batch_state.state && window.batch_state.fastModeActuallyFinishedTasks>=2) { | |
batch_ETA_container.style.opacity = 1 | |
// Lines per second | |
const timeNow = new Date() | |
const timeSinceStart = timeNow - window.batch_state.startTime | |
const avgMSTimePerLine = timeSinceStart / window.batch_state.fastModeActuallyFinishedTasks | |
batch_eta_lps.innerHTML = parseInt((1000/avgMSTimePerLine)*100)/100 | |
const remainingLines = window.batch_state.lines.length - window.batch_state.fastModeActuallyFinishedTasks | |
let estTimeRemaining = avgMSTimePerLine*remainingLines | |
// Estimated finish time | |
const finishTime = new Date(timeNow.getTime() + estTimeRemaining) | |
let etaFinishTime = `${finishTime.getHours()}:${String(finishTime.getMinutes()).padStart(2, "0")}:${String(finishTime.getSeconds()).padStart(2, "0")}` | |
const days = [window.i18n.SUNDAY, window.i18n.MONDAY, window.i18n.TUESDAY, window.i18n.WEDNESDAY, window.i18n.THURSDAY, window.i18n.FRIDAY, window.i18n.SATURDAY] | |
etaFinishTime = `${days[finishTime.getDay()]} ${etaFinishTime}` | |
batch_eta_eta.innerHTML = etaFinishTime | |
// Time remaining | |
let etaTimeDisplay = [] | |
if (estTimeRemaining > (1000*60*60)) { // hours | |
const hours = parseInt(estTimeRemaining/(1000*60*60)) | |
etaTimeDisplay.push(hours+"h") | |
estTimeRemaining -= hours*(1000*60*60) | |
} | |
if (estTimeRemaining > (1000*60)) { // minutes | |
const minutes = parseInt(estTimeRemaining/(1000*60)) | |
etaTimeDisplay.push(String(minutes).padStart(2, "0")+"m") | |
estTimeRemaining -= minutes*(1000*60) | |
} | |
if (estTimeRemaining > (1000)) { // seconds | |
const seconds = parseInt(estTimeRemaining/(1000)) | |
etaTimeDisplay.push(String(seconds).padStart(2, "0")+"s") | |
estTimeRemaining -= seconds*(1000) | |
} | |
batch_eta_time.innerHTML = etaTimeDisplay.join(" ") | |
} else { | |
batch_ETA_container.style.opacity = 0 | |
} | |
} | |
const openOutput = () => { | |
er.shell.showItemInFolder(window.userSettings.batchOutFolder+"/dummy.txt") | |
spawn(`explorer`, [window.userSettings.batchOutFolder.replace(/\//g, "\\")], {stdio: "ignore"}) | |
} | |
batch_paginationPrev.addEventListener("click", () => { | |
batch_pageNum.value = Math.max(1, parseInt(batch_pageNum.value)-1) | |
window.batch_state.paginationIndex = batch_pageNum.value-1 | |
window.refreshBatchRecordsList() | |
}) | |
batch_paginationNext.addEventListener("click", () => { | |
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize) | |
batch_pageNum.value = Math.min(parseInt(batch_pageNum.value)+1, numPages) | |
window.batch_state.paginationIndex = batch_pageNum.value-1 | |
window.refreshBatchRecordsList() | |
}) | |
batch_pageNum.addEventListener("change", () => { | |
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize) | |
batch_pageNum.value = Math.max(1, Math.min(parseInt(batch_pageNum.value), numPages)) | |
window.batch_state.paginationIndex = batch_pageNum.value-1 | |
window.refreshBatchRecordsList() | |
}) | |
setting_batch_paginationSize.addEventListener("change", () => { | |
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize) | |
batch_pageNum.value = Math.max(1, Math.min(parseInt(batch_pageNum.value), numPages)) | |
window.batch_state.paginationIndex = batch_pageNum.value-1 | |
batch_total_pages.innerHTML = window.i18n.PAGINATION_TOTAL_OF.replace("_1", numPages) | |
window.refreshBatchRecordsList() | |
}) | |
window.toggleNumericalRecordsDisplay = () => { | |
window.batch_state.lines.forEach(record => { | |
record[1].children[6].innerHTML = batch_outputNumerically.checked ? `${window.userSettings.batchOutFolder}/${String(record[2]).padStart(10, '0')}` : record[0].out_path | |
}) | |
} | |
batch_outputNumerically.addEventListener("click", () => { | |
window.toggleNumericalRecordsDisplay() | |
}) | |
batch_saveToCSV.addEventListener("click", () => { | |
try { | |
const csv_file = [`game_id|voice_id|text`] | |
window.batch_state.lines.forEach(line => { | |
csv_file.push(`${line[0].game_id}|${line[0].voice_id}|${line[0].text}`) | |
}) | |
const outFileName = JSON.stringify(new Date()).replace("\"","").replaceAll(":","_").split(".")[0]+"_batch.csv" | |
fs.writeFileSync(`${window.userSettings.batchOutFolder}/${outFileName}`, csv_file.join("\n"), "utf8") | |
window.createModal("error", `${window.i18n.BATCH_TOCSV_DONE}<br><br>${window.userSettings.batchOutFolder}/${outFileName}`) | |
} catch(e) { | |
console.log(e) | |
window.appLogger.log(e.stack) | |
window.errorModal(e.stack) | |
} | |
}) | |
batch_main.addEventListener("dragenter", event => window.uploadBatchCSVs("dragenter", event), false) | |
batch_main.addEventListener("dragleave", event => window.uploadBatchCSVs("dragleave", event), false) | |
batch_main.addEventListener("dragover", event => window.uploadBatchCSVs("dragover", event), false) | |
batch_main.addEventListener("drop", event => window.uploadBatchCSVs("drop", event), false) | |
batch_synthesizeBtn.addEventListener("click", window.startBatch) | |
batch_pauseBtn.addEventListener("click", window.pauseResumeBatch) | |
batch_stopBtn.addEventListener("click", () => window.stopBatch(true)) | |
batch_openDirBtn.addEventListener("click", openOutput) |