Subversion Repositories spk

Rev

Rev 304 | Go to most recent revision | 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 _DEBUG
        char pause;
        scanf ( "%s", &pause );
#endif
}

#define BUFFER_LEN 4096

void 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;
                                                else
                                                        ignore = 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();
                        }
                }
                else
                        wprintf(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 page
                                return;
                        }
                        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 file
                        int 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 data
                        if (!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;
                                }
                        }
                        else
                                currentPos += 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 file
                        Utils::WString outxmlfile = cmd.hasSwitch(L"outxml") ? cmd.fullFilename(cmd.switchData(L"outxml")) : xmlfile;
                        Utils::WStringList outXmlLines;

                        // extract the pages we need to merge
                        std::map<int, size_t> pageIDs;
                        ExtractPageTimes(page, 0, pageIDs, mergeXmlLines);

                        // write the new xml file
                        for (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 line
                                                        outXmlLines.pushBack(line);

                                                        // add all the new lines
                                                        MergeXML(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 lines
                                        for (auto itr = pageIDs.begin(); itr != pageIDs.end(); itr++)
                                        {
                                                // add the page line
                                                Utils::WString line = Utils::WString(L"<page id=\"") + (long)itr->first + L"\" stream=\"" + (long)stream + L"\">";
                                                outXmlLines.pushBack(line);
                                                // add all the new lines
                                                MergeXML(itr->first, stream, static_cast<long long>(startPos) - static_cast<long long>(itr->second), mergeXmlLines, outXmlLines);
                                                outXmlLines.pushBack(L"</page>");
                                        }
                                }

                                // add the line
                                outXmlLines.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());
                                else
                                        wprintf(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 page
        Utils::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)
{

}


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 position
                double 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 file
                int check = sf_format_check(&info);
                sf_count_t seek = sf_seek(mainFile, 0, SEEK_END);

                double buffer[BUFFER_LEN];

                // get all files from directory
                CDirIO 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 file
                        Utils::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 lines
                                        for (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 prompt
        if(!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);
        else
                PrintSyntax(cmd.cmdName());

        return 0;
}