Rev 325 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*Voice file Creation V1.00 Created by Cycrow (Matthew Gravestock)*/// Main Spk File Library Include#ifdef _WIN32#include <spk.h>#include <StringList.h>#else#include "../spk/spk.h"#endif#include <time.h>#ifdef _WIN32#include <windows.h>#include <direct.h>#include <shlobj.h>#endif#include <set>#include "sndfile.hh"#include "Utils/CommandLine.h"void PrintSyntax(const Utils::WString& cmd){wprintf(L"Syntax: %s <command> [options] [arguments]\n", cmd.c_str());wprintf(L"Commands:\n");wprintf(L"\toffset [options] <offset> <file> [--out:<file>]\n");wprintf(L"\t\tEdits the stream xml file to offset the times for a voice page\n");wprintf(L"\tmerge [--out:<file>][--outxml:<file>] <file.xml> <file.wav> <mergefile.xml> <mergefile.wav>\n");wprintf(L"\t\tMerges a voice page with xml into another\n");wprintf(L"\textract [--stream:<#>] <page> <file.xml> <file.wav> <output>\n");wprintf(L"\t\tExtracts a voice page from a large file using the XML for timings\n");wprintf(L"\tcompile [--stream:<#>] <file.xml> <file.wav> <dir>\n");wprintf(L"\t\tCompiles a collection of voice files into a larger one and generates the xml file\n");}void Pause(){#ifdef _DEBUGchar pause;scanf ( "%s", &pause );#endif}#define BUFFER_LEN 4096void DoOffset(const Utils::CommandLine &cmd){if (cmd.argCount() < 3){wprintf(L"Syntax: %s offset [options] <offset> <file> [--out:<file>]\n", cmd.cmdName().c_str());wprintf(L" <offset>\t\tOffset time to adjust by\n");wprintf(L" <file>\t\tThe file to adjust\n");wprintf(L"\nOptions:\n");wprintf(L" --out:<file>\t\tThe output filename, otherwise, will write to the input\n");wprintf(L" --page:<id>\t\tThe page id to read (otherwise will do all of them)\n");}else{Utils::WString offsetStr = cmd.arg(1);Utils::WString fileStr = cmd.arg(2);Utils::WString outFileStr = cmd.hasSwitch(L"out") ? cmd.switchData(L"out") : fileStr;unsigned int pageID = cmd.hasSwitch(L"page") ? cmd.switchData(L"page").toInt() : 0;long offset = offsetStr.toLong();bool doIgnore = cmd.hasSwitch(L"ignore");CFileIO f;if (f.open(fileStr)){bool inPage = false;bool ignore = false;Utils::WStringList list;Utils::WStringList outList;if (f.readLines(list)){for (auto itr = list.begin(); itr != list.end(); itr++){Utils::WString line = (*itr)->str;if (line.contains(L"<page id=\"")){long id = line.between(L"<page id=\"", L"\"").toLong();if (pageID == 0 || pageID == id)inPage = true;elseignore = true;}else if (line.contains(L"</page>"))inPage = false;else if (inPage){long pos = line.findPos(L"s=\"");if (pos != -1){Utils::WString s = line.substr(pos + 3);s = s.token(L"\"", 1);long long time = s.toLong64();long long toTime = time + offset;line = line.findReplace(time, toTime);}}if(!ignore || !doIgnore)outList.pushBack(line);if (line.contains(L"</page>"))ignore = false;}CFileIO outFile(outFileStr);outFile.writeFile(&outList);outFile.close();}}elsewprintf(L"Error: Unable to open file, %s\n", fileStr.c_str());}}void ExtractPage(int page, int stream, const Utils::WStringList& lines, Utils::WStringList& outLines){int inPage = 0;size_t lowestPos = 0;for (auto itr = lines.begin(); itr != lines.end(); itr++){Utils::WString line = (*itr)->str;if (inPage){if (line.contains(L"</page>")){inPage = 0;// close the pagereturn;}else if (line.contains(L"<t id="))outLines.pushBack(line);}else if (line.contains(L"<page id=\"")){long strm = line.between(L" stream=\"", L"\"").toLong();if (!stream || strm == stream){long id = line.between(L"<page id=\"", L"\"").toLong();if (page == id)inPage = id;}}}}bool ExtractPageTimes(int page, int stream, std::map<int, size_t>& pageIDs, const Utils::WStringList& lines){int inPage = 0;size_t lowestPos = 0;for (auto itr = lines.begin(); itr != lines.end(); itr++){Utils::WString line = (*itr)->str;if (inPage){if (line.contains(L"</page>")){if (lowestPos)pageIDs[inPage] = lowestPos;inPage = 0;// close the page}else if (line.contains(L"<t id=")){size_t s = static_cast<size_t>(line.between(L" s=\"", L"\"").toLong64());if (!lowestPos || s < lowestPos)lowestPos = s;}}else if (line.contains(L"<page id=\"")){long strm = line.between(L" stream=\"", L"\"").toLong();if (!stream || strm == stream){long id = line.between(L"<page id=\"", L"\"").toLong();if (page == 0 || page == id)inPage = id;}}}return true;}void MergeXML(int id, int stream, long long offset, const Utils::WStringList &in, Utils::WStringList &out){Utils::WStringList lines;ExtractPage(id, 0, in, lines);for (auto itr2 = lines.begin(); itr2 != lines.end(); itr2++){Utils::WString line2 = (*itr2)->str;long long i = line2.between(L" id=\"", L"\"").toLong64();long long s = line2.between(L" s=\"", L"\"").toLong64() + offset;long long l = line2.between(L" l=\"", L"\"").toLong64();out.pushBack(Utils::WString(L"\t<t id=\"") + Utils::WString(i) + L"\" s=\"" + Utils::WString(s) + L"\" l=\"" + Utils::WString(l) + "\"/>");}}void Merge(const Utils::CommandLine& cmd){if (cmd.argCount() < 5){wprintf(L"Syntax: %s merge [--out:<file>][--outxml:<file>] <file.xml> <file.wav> <mergefile.xml> <mergefile.wav>\n", cmd.cmdName().c_str());wprintf(L" <file.wav>\t\tThe main wav file to merge into\n");wprintf(L" <file.xml>\t\tThe main xml file to merge into\n");wprintf(L" <mergefile.wav>\t\tThe wav file to merge\n");wprintf(L" <mergefile.xml>\t\tThe xml file to merge\n");wprintf(L"\nOptions:\n");wprintf(L" --out:<file>\t\tOptional output wav file, otherwise will write over the input\n");wprintf(L" --outxml:<file>\t\tOptional output xml file, otherwise will write over the input\n");wprintf(L" --stream:<#>\t\tThe stream id to write too\n");wprintf(L" --page:<#>\t\tThe page id to merge\n");}else{Utils::WString xmlfile = cmd.fullFilename(cmd.arg(1));Utils::WString wavfile = cmd.fullFilename(cmd.arg(2));Utils::WString mergexmlfile = cmd.fullFilename(cmd.arg(3));Utils::WString mergewavfile = cmd.fullFilename(cmd.arg(4));Utils::WString outwavefile = cmd.hasSwitch(L"out") ? cmd.fullFilename(cmd.switchData(L"out")) : wavfile;int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 3;int page = cmd.hasSwitch(L"page") ? cmd.switchData(L"page").toInt() : 0;if (!CFileIO(wavfile).exists()){wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());return;}if (!CFileIO(mergewavfile).exists()){wprintf(L"Error: Unable to open wav file, %s\n", mergewavfile.c_str());return;}CFileIO xml(xmlfile);if (!xml.exists()){wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());return;}Utils::WStringList xmlLines;if (!xml.readLines(xmlLines)){wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());return;}CFileIO mergexml(mergexmlfile);if (!mergexml.exists()){wprintf(L"Error: Unable to open xml file, %s\n", mergexmlfile.c_str());return;}Utils::WStringList mergeXmlLines;if (!mergexml.readLines(mergeXmlLines)){wprintf(L"Error: Unable to read xml file, %s\n", mergexmlfile.c_str());return;}bool overwrite = (wavfile.Compare(outwavefile));int sampleRate = 44100;{SF_INFO inInfo;SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);if (inputFile){sampleRate = inInfo.samplerate;sf_close(inputFile);}}SF_INFO outInfo;outInfo.channels = 1;outInfo.samplerate = sampleRate;outInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;SNDFILE* outputFile = sf_open(outwavefile.toStdString().c_str(), overwrite ? SFM_RDWR : SFM_WRITE, &outInfo);if (outputFile){double buffer[BUFFER_LEN];size_t currentPos = 0;// seek to end of fileint check = sf_format_check(&outInfo);sf_count_t seek = sf_seek(outputFile, 0, SEEK_END);// if output file is different, then we need to copy the dataif (!overwrite){SF_INFO inInfo;SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);if (inputFile){double duration = static_cast<double>(inInfo.frames) / static_cast<double>(inInfo.samplerate);size_t len = static_cast<size_t>((duration * 1000.0) + 0.5);sf_count_t readcount;while ((readcount = sf_read_double(inputFile, buffer, BUFFER_LEN)) > 0)sf_write_double(outputFile, buffer, readcount);sf_close(inputFile);currentPos += len;}}elsecurrentPos += static_cast<size_t>((static_cast<double>(outInfo.frames) / static_cast<double>(outInfo.samplerate) * 1000.0) + 0.5);size_t startPos = currentPos;// now we need to merge the wav file{SF_INFO inInfo;SNDFILE* inputFile = sf_open(mergewavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);if (inputFile){double duration = static_cast<double>(inInfo.frames) / static_cast<double>(inInfo.samplerate);size_t len = static_cast<size_t>((duration * 1000.0) + 0.5);sf_count_t readcount;while ((readcount = sf_read_double(inputFile, buffer, BUFFER_LEN)) > 0)sf_write_double(outputFile, buffer, readcount);sf_close(inputFile);currentPos += len;}}sf_close(outputFile);// finally, we need to generate the new xml fileUtils::WString outxmlfile = cmd.hasSwitch(L"outxml") ? cmd.fullFilename(cmd.switchData(L"outxml")) : xmlfile;Utils::WStringList outXmlLines;// extract the pages we need to mergestd::map<int, size_t> pageIDs;ExtractPageTimes(page, 0, pageIDs, mergeXmlLines);// write the new xml filefor (auto itr = xmlLines.begin(); itr != xmlLines.end(); itr++){Utils::WString line = (*itr)->str;if (line.contains(L"<page id=\"")){long id = line.between(L"<page id=\"", L"\"").toLong();auto findItr = pageIDs.find(id);if (findItr != pageIDs.end()){long strm = line.between(L"stream=\"", L"\"").toLong();if (strm == stream){// add the page lineoutXmlLines.pushBack(line);// add all the new linesMergeXML(id, stream, static_cast<long long>(startPos) - static_cast<long long>(findItr->second), mergeXmlLines, outXmlLines);pageIDs.erase(findItr);continue;}}}else if (line.contains(L"</language>")){// now we need to add the new linesfor (auto itr = pageIDs.begin(); itr != pageIDs.end(); itr++){// add the page lineUtils::WString line = Utils::WString(L"<page id=\"") + (long)itr->first + L"\" stream=\"" + (long)stream + L"\">";outXmlLines.pushBack(line);// add all the new linesMergeXML(itr->first, stream, static_cast<long long>(startPos) - static_cast<long long>(itr->second), mergeXmlLines, outXmlLines);outXmlLines.pushBack(L"</page>");}}// add the lineoutXmlLines.pushBack(line);}CFileIO outFile(outxmlfile);bool write = outFile.writeFile(&outXmlLines);if (!cmd.hasSwitch(L"quiet")){if(write)wprintf(L"Output XML file, %s, created\n", outxmlfile.c_str());elsewprintf(L"Error: Unable to write XML file, %s\n", outxmlfile.c_str());}if (write && cmd.hasSwitch(L"showresult"))wprintf(L"Merged %s into %s at %lldms for page %d\n", CFileIO(mergewavfile).filename().c_str(), CFileIO(outwavefile).filename().c_str(), startPos, page);}}}void Extract(const Utils::CommandLine& cmd){if (cmd.argCount() < 5){wprintf(L"Syntax: %s extract [--stream:<#>] <page> <file.xml> <file.wav> <output>\n", cmd.cmdName().c_str());wprintf(L" <page>\t\tThe page id to extract\n");wprintf(L" <file.wav>\t\tThe main wav file to extract from\n");wprintf(L" <file.xml>\t\tThe main xml file to read timings from\n");wprintf(L" <output>\t\tThe output wav file to create\n");wprintf(L"\nOptions:\n");wprintf(L" --stream:<#>\t\tThe stream id to write too\n");return;}bool quiet = cmd.hasSwitch(L"quiet");bool verbose = cmd.hasSwitch(L"verbose");Utils::WString xmlfile = cmd.fullFilename(cmd.arg(2));Utils::WString wavfile = cmd.fullFilename(cmd.arg(3));Utils::WString outputfile = cmd.fullFilename(cmd.arg(4));int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 0;int page = cmd.arg(1).toInt();if (page <= 0){wprintf(L"Error: Invalid page id, %s\n", cmd.arg(1).c_str());return;}if (!CFileIO(wavfile).exists()){wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());return;}CFileIO xml(xmlfile);if (!xml.exists()){wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());return;}Utils::WStringList xmlLines;if (!xml.readLines(xmlLines)){wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());return;}// find the timings for the pageUtils::WStringList outLines;ExtractPage(page, stream, xmlLines, outLines);if (outLines.size() == 0){wprintf(L"Error: Unable to find page id, %d\n", page);return;}long long startPos = outLines.front().between(L" s=\"", L"\"").toLong64();long long endPos = outLines.back().between(L" s=\"", L"\"").toLong64() + outLines.back().between(L" l=\"", L"\"").toLong64();long long length = endPos - startPos;SF_INFO inInfo;SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);if (!inputFile){wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());return;}int sampleRate = inInfo.samplerate;if (verbose){wprintf(L"Extracting page %d from %s to %s\n", page, CFileIO(wavfile).filename().c_str(), CFileIO(outputfile).filename().c_str());wprintf(L"\tStart Time: \t%lld\n", startPos);wprintf(L"\tEnd Time: \t%lld\n", endPos);wprintf(L"\tLength: \t%lld\n", length);}long long startFrame = static_cast<long long>((static_cast<double>(startPos) / 1000.0) * sampleRate);sf_count_t seek = sf_seek(inputFile, startFrame, SEEK_SET);SF_INFO outInfo;outInfo.channels = 1;outInfo.samplerate = sampleRate;outInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;SNDFILE* outputFile = sf_open(outputfile.toFileUTF8().c_str(), SFM_WRITE, &outInfo);if (outputFile){double buffer[BUFFER_LEN];long long endFrame = static_cast<long long>((static_cast<double>(endPos) / 1000.0) * sampleRate);long long readRemaining = endFrame - startFrame;while (readRemaining > 0){long long readAmount = readRemaining > BUFFER_LEN ? BUFFER_LEN : readRemaining;sf_count_t readcount = sf_read_double(inputFile, buffer, readAmount);sf_write_double(outputFile, buffer, readcount);readRemaining -= readcount;}sf_close(outputFile);}sf_close(inputFile);if (!quiet || cmd.hasSwitch(L"showresult"))wprintf(L"Extracted %lldms from page %d to %s\n", length, page, CFileIO(outputfile).filename().c_str());}void Split(const Utils::CommandLine& cmd){if (cmd.argCount() < 4){wprintf(L"Syntax: %s split [--stream:<#>] <file.xml> <file.wav> <dir>\n", cmd.cmdName().c_str());wprintf(L"\t<file.wav>\tThe main wav file to split\n");wprintf(L"\t<file.xml>\tThe main xml file to split\n");wprintf(L"\t<dir>\t\tThe directory to write the files to\n");return;}bool quiet = cmd.hasSwitch(L"quiet");bool verbose = cmd.hasSwitch(L"verbose");Utils::WString xmlfile = cmd.fullFilename(cmd.arg(1));Utils::WString wavfile = cmd.fullFilename(cmd.arg(2));Utils::WString dir = cmd.fullFilename(cmd.arg(3));int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 1;if (!CFileIO(wavfile).exists()){wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());return;}CFileIO xml(xmlfile);if (!xml.exists()){wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());return;}Utils::WStringList xmlLines;if (!xml.readLines(xmlLines)){wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());return;}int filesWritten = 0;SF_INFO inInfo;SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);if (inputFile){CDirIO D(dir);if (!D.exists()){if (!D.create()){wprintf(L"Error: Unable to create directory, %s\n", dir.c_str());return;}}double buffer[BUFFER_LEN];sf_count_t seek = sf_seek(inputFile, 0, SEEK_SET);long inPage = 0;for (auto itr = xmlLines.begin(); itr != xmlLines.end(); itr++){if ((*itr)->str.contains(L"</page>"))inPage = 0;else if ((*itr)->str.contains(L"<page id=\"")){long strm = (*itr)->str.between(L" stream=\"", L"\"").toLong();if (strm == stream)inPage = (*itr)->str.between(L"<page id=\"", L"\"").toLong();}else if (inPage > 0){if ((*itr)->str.contains(L"<t id=")){long i = (*itr)->str.between(L" id=\"", L"\"").toLong();long long s = (*itr)->str.between(L" s=\"", L"\"").toLong64();long long l = (*itr)->str.between(L" l=\"", L"\"").toLong64();long long startFrame = static_cast<long long>((static_cast<double>(s) / 1000.0) * inInfo.samplerate);sf_count_t seek = sf_seek(inputFile, startFrame, SEEK_SET);CDirIO writeDir(D.file(Utils::WString(inPage)));if (!writeDir.exists()){if (!writeDir.create()){wprintf(L"Error: Unable to create directory, %s\n", writeDir.dir().c_str());return;}}Utils::WString outFileName = writeDir.file(Utils::WString(i) + L".wav");// now we need to write the wav fileSF_INFO outInfo;outInfo.channels = 1;outInfo.samplerate = inInfo.samplerate;outInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;SNDFILE* outputFile = sf_open(outFileName.toFileUTF8().c_str(), SFM_WRITE, &outInfo);if (outputFile){long long endFrame = static_cast<long long>((static_cast<double>(s + l) / 1000.0) * inInfo.samplerate);long long readRemaining = endFrame - startFrame;while (readRemaining > 0){long long readAmount = readRemaining > BUFFER_LEN ? BUFFER_LEN : readRemaining;sf_count_t readcount = sf_read_double(inputFile, buffer, readAmount);sf_write_double(outputFile, buffer, readcount);readRemaining -= readcount;}sf_close(outputFile);if (verbose)wprintf(L"Wrote %lldms to %d\\%s\n", l, inPage, CFileIO(outFileName).filename().c_str());++filesWritten;}}}}sf_close(inputFile);}if(filesWritten > 0){if (!quiet || cmd.hasSwitch(L"showresult"))wprintf(L"Wrote %d files to %s\n", filesWritten, dir.c_str());}else{wprintf(L"Error: Unable to write any files to %s\n", dir.c_str());}}void Compile(const Utils::CommandLine& cmd){if (cmd.argCount() < 4){wprintf(L"Syntax: %s compile [--stream:<#>] <file.xml> <file.wav> <dir>\n", cmd.cmdName().c_str());wprintf(L"\t<file.wav>\tThe main wav file to merge into\n");wprintf(L"\t<file.xml>\tThe main xml file to merge into\n");wprintf(L"\t<dir>\t\tThe directory containing voice files\n");wprintf(L"\nOptions:\n");wprintf(L" --stream:<#>\tThe stream id to write too\n");return;}bool quiet = cmd.hasSwitch(L"quiet");bool verbose = cmd.hasSwitch(L"verbose");bool verboseresult = cmd.hasSwitch(L"verboseresult");Utils::WString xmlfile = cmd.fullFilename(cmd.arg(1));Utils::WString wavfile = cmd.fullFilename(cmd.arg(2));Utils::WString dir = cmd.fullFilename(cmd.arg(3));int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 3;if (!CFileIO(wavfile).exists()){wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());return;}CFileIO xml(xmlfile);if (!xml.exists()){wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());return;}Utils::WStringList xmlLines;if (!xml.readLines(xmlLines)){wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());return;}SF_INFO info;SNDFILE* mainFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_RDWR, &info);if (mainFile){Utils::WStringList addLines;// compute the current positiondouble duration = static_cast<double>(info.frames) / static_cast<double>(info.samplerate);size_t currentPos = static_cast<size_t>((duration * 1000.0) + 0.5);// seek to end of fileint check = sf_format_check(&info);sf_count_t seek = sf_seek(mainFile, 0, SEEK_END);double buffer[BUFFER_LEN];// get all files from directoryCDirIO D(dir);Utils::WStringList list;if (D.dirList(list)){for (auto itr = list.begin(); itr != list.end(); itr++){if (D.isDir((*itr)->str)){CDirIO curDir(D.dir((*itr)->str));long pageID = (*itr)->str.toInt();addLines.pushBack(Utils::WString(L"<page id=\"") + pageID + L"\" stream=\"" + (long)stream + L"\">");Utils::WStringList idList;if (curDir.dirList(idList)){for (auto itr2 = idList.begin(); itr2 != idList.end(); itr2++){if (curDir.isFile((*itr2)->str)){CFileIO F(curDir.file((*itr2)->str));long id = F.baseName().toInt();size_t len = 0;SF_INFO inInfo;SNDFILE* inputFile = sf_open(F.fullFilename().toFileUTF8().c_str(), SFM_READ, &inInfo);if (inputFile){if (inInfo.samplerate != info.samplerate)wprintf(L"Error: Sample rate mismatch, %d:%s (%d) != (%d)\n", pageID, F.filename().c_str(), info.samplerate, inInfo.samplerate);else{double duration = static_cast<double>(inInfo.frames) / static_cast<double>(inInfo.samplerate);size_t len = static_cast<size_t>((duration * 1000.0) + 0.5);sf_count_t readcount;while ((readcount = sf_read_double(inputFile, buffer, BUFFER_LEN)) > 0)sf_write_double(mainFile, buffer, readcount);//int* data = new int[inInfo.frames * inInfo.channels];//sf_count_t readCount = sf_readf_int(inputFile, data, inInfo.frames);//sf_count_t writeCount = sf_writef_int(outputFile, data, inInfo.frames);addLines.pushBack(Utils::WString(L"\t<t id=\"") + id + L"\" s=\"" + (long)currentPos + L"\" l=\"" + (long)len + "\"/>");if(verbose || verboseresult)wprintf(L"Writing %d:%s to %s at %lldms\n", pageID, F.filename().c_str(), CFileIO(wavfile).filename().c_str(), currentPos);currentPos += len;}sf_close(inputFile);}}}}addLines.pushBack(L"</page>");}}// merge the xml fileUtils::WStringList outLines;for (auto itr = xmlLines.begin(); itr != xmlLines.end(); itr++){Utils::WString line = (*itr)->str;if (line.contains(L"</language>")){// now we need to add the new linesfor (auto itr2 = addLines.begin(); itr2 != addLines.end(); itr2++)outLines.pushBack((*itr2)->str);}outLines.pushBack(line);}CFileIO xml(xmlfile);xml.writeFile(&outLines);}sf_close(mainFile);if (!quiet || cmd.hasSwitch(L"showresult")){wprintf(L"\nCompiled %s into %s\n", CFileIO(dir).filename().c_str(), CFileIO(wavfile).filename().c_str());if (verbose){wprintf(L"\tLength: \t%lldms\n", currentPos);wprintf(L"\tStream file: \t%s\n", CFileIO(xmlfile).filename().c_str());}}}}/*Main entry point to program*/int main ( int argc, char **argv ){Utils::CommandLine cmd(argc, argv);Utils::WString command = cmd.argCount() >= 1 ? cmd.arg(0) : L"";command.toLower();// display program header to command promptif(!cmd.hasSwitch(L"quiet"))printf("\nX3 Voice Create V1.10 (SPK Library Version %.2f) 18/05/2025 Created by Cycrow\n\n", GetLibraryVersion());if (command == L"offset")DoOffset(cmd);else if (command == L"help")PrintSyntax(cmd.cmdName());else if (command == L"merge")Merge(cmd);else if (command == L"extract")Extract(cmd);else if (command == L"split")Split(cmd);else if (command == L"compile")Compile(cmd);elsePrintSyntax(cmd.cmdName());return 0;}