Subversion Repositories spk

Rev

Rev 325 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
270 cycrow 1
/*
2
 Voice file Creation V1.00 Created by Cycrow (Matthew Gravestock)
3
*/
4
 
5
// Main Spk File Library Include
6
#ifdef _WIN32
7
#include <spk.h>
8
#include <StringList.h>
9
#else
10
#include "../spk/spk.h"
11
#endif
12
#include <time.h>
13
 
14
#ifdef _WIN32
15
#include <windows.h>
16
#include <direct.h>
17
#include <shlobj.h>
18
#endif
19
 
325 cycrow 20
#include <set>
21
 
270 cycrow 22
#include "sndfile.hh"
304 cycrow 23
#include "Utils/CommandLine.h"
270 cycrow 24
 
25
 
325 cycrow 26
void PrintSyntax(const Utils::WString& cmd)
27
{
28
	wprintf(L"Syntax: %s <command> [options] [arguments]\n", cmd.c_str());
29
	wprintf(L"Commands:\n");
30
	wprintf(L"\toffset [options] <offset> <file> [--out:<file>]\n");
31
	wprintf(L"\t\tEdits the stream xml file to offset the times for a voice page\n");
32
	wprintf(L"\tmerge [--out:<file>][--outxml:<file>] <file.xml> <file.wav> <mergefile.xml> <mergefile.wav>\n");
33
	wprintf(L"\t\tMerges a voice page with xml into another\n");
34
	wprintf(L"\textract [--stream:<#>] <page> <file.xml> <file.wav> <output>\n");
35
	wprintf(L"\t\tExtracts a voice page from a large file using the XML for timings\n");
36
	wprintf(L"\tcompile [--stream:<#>] <file.xml> <file.wav> <dir>\n");
37
	wprintf(L"\t\tCompiles a collection of voice files into a larger one and generates the xml file\n");
270 cycrow 38
}
39
 
40
void Pause()
41
{
42
#ifdef _DEBUG
43
	char pause;
44
	scanf ( "%s", &pause );
45
#endif
46
}
47
 
48
#define BUFFER_LEN 4096
287 cycrow 49
 
304 cycrow 50
void DoOffset(const Utils::CommandLine &cmd)
287 cycrow 51
{
304 cycrow 52
	if (cmd.argCount() < 3)
287 cycrow 53
	{
304 cycrow 54
		wprintf(L"Syntax: %s offset [options] <offset> <file> [--out:<file>]\n", cmd.cmdName().c_str());
287 cycrow 55
		wprintf(L"	<offset>\t\tOffset time to adjust by\n");
56
		wprintf(L"	<file>\t\tThe file to adjust\n");
57
		wprintf(L"\nOptions:\n");
58
		wprintf(L"  --out:<file>\t\tThe output filename, otherwise, will write to the input\n");
59
		wprintf(L"  --page:<id>\t\tThe page id to read (otherwise will do all of them)\n");
60
	}
61
	else
62
	{
304 cycrow 63
		Utils::WString offsetStr = cmd.arg(1);
64
		Utils::WString fileStr = cmd.arg(2);
65
		Utils::WString outFileStr = cmd.hasSwitch(L"out") ? cmd.switchData(L"out") : fileStr;
66
		unsigned int pageID = cmd.hasSwitch(L"page") ? cmd.switchData(L"page").toInt() : 0;
287 cycrow 67
		long offset = offsetStr.toLong();
304 cycrow 68
		bool doIgnore = cmd.hasSwitch(L"ignore");
287 cycrow 69
 
70
		CFileIO f;
71
		if (f.open(fileStr))
72
		{
73
			bool inPage = false;
289 cycrow 74
			bool ignore = false;
287 cycrow 75
			Utils::WStringList list;
76
			Utils::WStringList outList;
77
			if (f.readLines(list))
78
			{
79
				for (auto itr = list.begin(); itr != list.end(); itr++)
80
				{
81
					Utils::WString line = (*itr)->str;
82
					if (line.contains(L"<page id=\""))
83
					{
84
						long id = line.between(L"<page id=\"", L"\"").toLong();
85
						if (pageID == 0 || pageID == id)
86
							inPage = true;
289 cycrow 87
						else
88
							ignore = true;
287 cycrow 89
					}
90
					else if (line.contains(L"</page>"))
91
						inPage = false;
92
					else if (inPage)
93
					{
94
						long pos = line.findPos(L"s=\"");
95
						if (pos != -1)
96
						{
97
							Utils::WString s = line.substr(pos + 3);
98
							s = s.token(L"\"", 1);
289 cycrow 99
							long long time = s.toLong64();
100
							long long toTime = time + offset;
287 cycrow 101
							line = line.findReplace(time, toTime);
102
						}
103
					}
289 cycrow 104
 
105
					if(!ignore || !doIgnore)
106
						outList.pushBack(line);
107
 
108
					if (line.contains(L"</page>"))
109
						ignore = false;
287 cycrow 110
				}
111
				CFileIO outFile(outFileStr);
112
				outFile.writeFile(&outList);
113
				outFile.close();
114
			}
115
		}
116
		else
117
			wprintf(L"Error: Unable to open file, %s\n", fileStr.c_str());
118
	}
119
 
120
}
121
 
325 cycrow 122
void ExtractPage(int page, int stream, const Utils::WStringList& lines, Utils::WStringList& outLines)
270 cycrow 123
{
325 cycrow 124
	int inPage = 0;
125
	size_t lowestPos = 0;
126
	for (auto itr = lines.begin(); itr != lines.end(); itr++)
127
	{
128
		Utils::WString line = (*itr)->str;
129
		if (inPage)
130
		{
131
			if (line.contains(L"</page>"))
132
			{
133
				inPage = 0;// close the page
134
				return;
135
			}
136
			else if (line.contains(L"<t id="))
137
				outLines.pushBack(line);
138
		}
139
		else if (line.contains(L"<page id=\""))
140
		{
141
			long strm = line.between(L" stream=\"", L"\"").toLong();
142
			if (!stream || strm == stream)
143
			{
144
				long id = line.between(L"<page id=\"", L"\"").toLong();
145
				if (page == id)
146
					inPage = id;
147
			}
148
		}
149
	}
150
}
151
bool ExtractPageTimes(int page, int stream, std::map<int, size_t>& pageIDs, const Utils::WStringList& lines)
152
{
153
	int inPage = 0;
154
	size_t lowestPos = 0;
155
	for (auto itr = lines.begin(); itr != lines.end(); itr++)
156
	{
157
		Utils::WString line = (*itr)->str;
158
		if (inPage)
159
		{
160
			if (line.contains(L"</page>"))
161
			{
162
				if (lowestPos)
163
					pageIDs[inPage] = lowestPos;
164
				inPage = 0;// close the page
165
			}
166
			else if (line.contains(L"<t id="))
167
			{
168
				size_t s = static_cast<size_t>(line.between(L" s=\"", L"\"").toLong64());
169
				if (!lowestPos || s < lowestPos)
170
					lowestPos = s;
171
			}
172
		}
173
		else if (line.contains(L"<page id=\""))
174
		{
175
			long strm = line.between(L" stream=\"", L"\"").toLong();
176
			if (!stream || strm == stream)
177
			{
178
				long id = line.between(L"<page id=\"", L"\"").toLong();
179
				if (page == 0 || page == id)
180
					inPage = id;
181
			}
182
		}
183
	}
270 cycrow 184
 
325 cycrow 185
	return true;
186
}
270 cycrow 187
 
325 cycrow 188
void MergeXML(int id, int stream, long long offset, const Utils::WStringList &in, Utils::WStringList &out)
189
{
190
	Utils::WStringList lines;
191
	ExtractPage(id, 0, in, lines);
192
	for (auto itr2 = lines.begin(); itr2 != lines.end(); itr2++)
193
	{
194
		Utils::WString line2 = (*itr2)->str;
195
		long long i = line2.between(L" id=\"", L"\"").toLong64();
196
		long long s = line2.between(L" s=\"", L"\"").toLong64() + offset;
197
		long long l = line2.between(L" l=\"", L"\"").toLong64();
198
		out.pushBack(Utils::WString(L"\t<t id=\"") + Utils::WString(i) + L"\" s=\"" + Utils::WString(s) + L"\" l=\"" + Utils::WString(l) + "\"/>");
199
	}
200
}
201
 
202
void Merge(const Utils::CommandLine& cmd)
203
{
204
	if (cmd.argCount() < 5)
205
	{
206
		wprintf(L"Syntax: %s merge [--out:<file>][--outxml:<file>] <file.xml> <file.wav> <mergefile.xml> <mergefile.wav>\n", cmd.cmdName().c_str());
207
		wprintf(L"	<file.wav>\t\tThe main wav file to merge into\n");
208
		wprintf(L"	<file.xml>\t\tThe main xml file to merge into\n");
209
		wprintf(L"	<mergefile.wav>\t\tThe wav file to merge\n");
210
		wprintf(L"	<mergefile.xml>\t\tThe xml file to merge\n");
211
		wprintf(L"\nOptions:\n");
212
		wprintf(L"	--out:<file>\t\tOptional output wav file, otherwise will write over the input\n");
213
		wprintf(L"	--outxml:<file>\t\tOptional output xml file, otherwise will write over the input\n");
214
		wprintf(L"	--stream:<#>\t\tThe stream id to write too\n");
215
		wprintf(L"	--page:<#>\t\tThe page id to merge\n");
216
	}
287 cycrow 217
	else
218
	{
325 cycrow 219
		Utils::WString xmlfile = cmd.fullFilename(cmd.arg(1));
220
		Utils::WString wavfile = cmd.fullFilename(cmd.arg(2));
221
		Utils::WString mergexmlfile = cmd.fullFilename(cmd.arg(3));
222
		Utils::WString mergewavfile = cmd.fullFilename(cmd.arg(4));
223
		Utils::WString outwavefile = cmd.hasSwitch(L"out") ? cmd.fullFilename(cmd.switchData(L"out")) : wavfile;
224
		int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 3;
225
		int page = cmd.hasSwitch(L"page") ? cmd.switchData(L"page").toInt() : 0;
270 cycrow 226
 
325 cycrow 227
		if (!CFileIO(wavfile).exists())
228
		{
229
			wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());
230
			return;
231
		}
232
		if (!CFileIO(mergewavfile).exists())
233
		{
234
			wprintf(L"Error: Unable to open wav file, %s\n", mergewavfile.c_str());
235
			return;
236
		}
237
		CFileIO xml(xmlfile);
238
		if (!xml.exists())
239
		{
240
			wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());
241
			return;
242
		}
270 cycrow 243
 
325 cycrow 244
		Utils::WStringList xmlLines;
245
		if (!xml.readLines(xmlLines))
270 cycrow 246
		{
325 cycrow 247
			wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());
248
			return;
249
		}
270 cycrow 250
 
325 cycrow 251
		CFileIO mergexml(mergexmlfile);
252
		if (!mergexml.exists())
253
		{
254
			wprintf(L"Error: Unable to open xml file, %s\n", mergexmlfile.c_str());
255
			return;
256
		}
270 cycrow 257
 
325 cycrow 258
		Utils::WStringList mergeXmlLines;
259
		if (!mergexml.readLines(mergeXmlLines))
260
		{
261
			wprintf(L"Error: Unable to read xml file, %s\n", mergexmlfile.c_str());
262
			return;
263
		}
270 cycrow 264
 
325 cycrow 265
		bool overwrite = (wavfile.Compare(outwavefile));
266
 
267
		int sampleRate = 44100;
268
		{
269
			SF_INFO inInfo;
270
			SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);
271
			if (inputFile)
270 cycrow 272
			{
325 cycrow 273
				sampleRate = inInfo.samplerate;
274
				sf_close(inputFile);
275
			}
276
		}
287 cycrow 277
 
325 cycrow 278
		SF_INFO outInfo;
279
		outInfo.channels = 1;
280
		outInfo.samplerate = sampleRate;
281
		outInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
282
		SNDFILE* outputFile = sf_open(outwavefile.toStdString().c_str(), overwrite ? SFM_RDWR : SFM_WRITE, &outInfo);
283
		if (outputFile)
284
		{
285
			double buffer[BUFFER_LEN];
286
			size_t currentPos = 0;
287 cycrow 287
 
325 cycrow 288
			// seek to end of file
289
			int check = sf_format_check(&outInfo);
290
			sf_count_t seek = sf_seek(outputFile, 0, SEEK_END);
287 cycrow 291
 
325 cycrow 292
			// if output file is different, then we need to copy the data
293
			if (!overwrite)
294
			{
295
				SF_INFO inInfo;
296
				SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);
297
				if (inputFile)
270 cycrow 298
				{
325 cycrow 299
					double duration = static_cast<double>(inInfo.frames) / static_cast<double>(inInfo.samplerate);
300
					size_t len = static_cast<size_t>((duration * 1000.0) + 0.5);
301
					sf_count_t readcount;
302
					while ((readcount = sf_read_double(inputFile, buffer, BUFFER_LEN)) > 0)
303
						sf_write_double(outputFile, buffer, readcount);
304
					sf_close(inputFile);
305
					currentPos += len;
306
				}
307
			}
308
			else
309
				currentPos += static_cast<size_t>((static_cast<double>(outInfo.frames) / static_cast<double>(outInfo.samplerate) * 1000.0) + 0.5);
310
 
311
			size_t startPos = currentPos;
312
 
313
			// now we need to merge the wav file
314
			{
315
				SF_INFO inInfo;
316
				SNDFILE* inputFile = sf_open(mergewavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);
317
				if (inputFile)
318
				{
319
					double duration = static_cast<double>(inInfo.frames) / static_cast<double>(inInfo.samplerate);
320
					size_t len = static_cast<size_t>((duration * 1000.0) + 0.5);
321
					sf_count_t readcount;
322
					while ((readcount = sf_read_double(inputFile, buffer, BUFFER_LEN)) > 0)
323
						sf_write_double(outputFile, buffer, readcount);
324
					sf_close(inputFile);
325
					currentPos += len;
326
				}
327
			}
328
			sf_close(outputFile);
329
 
330
			// finally, we need to generate the new xml file
331
			Utils::WString outxmlfile = cmd.hasSwitch(L"outxml") ? cmd.fullFilename(cmd.switchData(L"outxml")) : xmlfile;
332
			Utils::WStringList outXmlLines;
333
 
334
			// extract the pages we need to merge
335
			std::map<int, size_t> pageIDs;
336
			ExtractPageTimes(page, 0, pageIDs, mergeXmlLines);
337
 
338
			// write the new xml file
339
			for (auto itr = xmlLines.begin(); itr != xmlLines.end(); itr++)
340
			{
341
				Utils::WString line = (*itr)->str;
342
				if (line.contains(L"<page id=\""))
343
				{
344
					long id = line.between(L"<page id=\"", L"\"").toLong();
345
					auto findItr = pageIDs.find(id);
346
					if (findItr != pageIDs.end())
270 cycrow 347
					{
325 cycrow 348
						long strm = line.between(L"stream=\"", L"\"").toLong();
349
						if (strm == stream)
287 cycrow 350
						{
325 cycrow 351
							// add the page line
352
							outXmlLines.pushBack(line);
270 cycrow 353
 
325 cycrow 354
							// add all the new lines
355
							MergeXML(id, stream, static_cast<long long>(startPos) - static_cast<long long>(findItr->second), mergeXmlLines, outXmlLines);
356
							pageIDs.erase(findItr);
357
							continue;
358
						}
359
					}
360
				}
361
				else if (line.contains(L"</language>"))
362
				{
363
					// now we need to add the new lines
364
					for (auto itr = pageIDs.begin(); itr != pageIDs.end(); itr++)
365
					{
366
						// add the page line
367
						Utils::WString line = Utils::WString(L"<page id=\"") + (long)itr->first + L"\" stream=\"" + (long)stream + L"\">";
368
						outXmlLines.pushBack(line);
369
						// add all the new lines
370
						MergeXML(itr->first, stream, static_cast<long long>(startPos) - static_cast<long long>(itr->second), mergeXmlLines, outXmlLines);
371
						outXmlLines.pushBack(L"</page>");
372
					}
373
				}
270 cycrow 374
 
325 cycrow 375
				// add the line
376
				outXmlLines.pushBack(line);
377
			}
378
 
379
			CFileIO outFile(outxmlfile);
380
			bool write = outFile.writeFile(&outXmlLines);
381
 
382
			if (!cmd.hasSwitch(L"quiet"))
383
			{
384
				if(write)
385
					wprintf(L"Output XML file, %s, created\n", outxmlfile.c_str());
386
				else
387
					wprintf(L"Error: Unable to write XML file, %s\n", outxmlfile.c_str());
388
			}
389
			if (write && cmd.hasSwitch(L"showresult"))
390
				wprintf(L"Merged %s into %s at %lldms for page %d\n", CFileIO(mergewavfile).filename().c_str(), CFileIO(outwavefile).filename().c_str(), startPos, page);
391
		}
392
	}
393
}
394
 
395
void Extract(const Utils::CommandLine& cmd)
396
{
397
	if (cmd.argCount() < 5)
398
	{
399
		wprintf(L"Syntax: %s extract [--stream:<#>] <page> <file.xml> <file.wav> <output>\n", cmd.cmdName().c_str());
400
		wprintf(L"	<page>\t\tThe page id to extract\n");
401
		wprintf(L"	<file.wav>\t\tThe main wav file to extract from\n");
402
		wprintf(L"	<file.xml>\t\tThe main xml file to read timings from\n");
403
		wprintf(L"	<output>\t\tThe output wav file to create\n");
404
		wprintf(L"\nOptions:\n");
405
		wprintf(L"	--stream:<#>\t\tThe stream id to write too\n");
406
		return;
407
	}
408
 
409
	bool quiet = cmd.hasSwitch(L"quiet");
410
	bool verbose = cmd.hasSwitch(L"verbose");
411
 
412
	Utils::WString xmlfile = cmd.fullFilename(cmd.arg(2));
413
	Utils::WString wavfile = cmd.fullFilename(cmd.arg(3));
414
	Utils::WString outputfile = cmd.fullFilename(cmd.arg(4));
415
	int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 0;
416
	int page = cmd.arg(1).toInt();
417
 
418
	if (page <= 0)
419
	{
420
		wprintf(L"Error: Invalid page id, %s\n", cmd.arg(1).c_str());
421
		return;
422
	}
423
	if (!CFileIO(wavfile).exists())
424
	{
425
		wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());
426
		return;
427
	}
428
	CFileIO xml(xmlfile);
429
	if (!xml.exists())
430
	{
431
		wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());
432
		return;
433
	}
434
 
435
	Utils::WStringList xmlLines;
436
	if (!xml.readLines(xmlLines))
437
	{
438
		wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());
439
		return;
440
	}
441
 
442
	// find the timings for the page
443
	Utils::WStringList outLines;
444
	ExtractPage(page, stream, xmlLines, outLines);
445
 
446
	if (outLines.size() == 0)
447
	{
448
		wprintf(L"Error: Unable to find page id, %d\n", page);
449
		return;
450
	}
451
 
452
	long long startPos = outLines.front().between(L" s=\"", L"\"").toLong64();
453
	long long endPos = outLines.back().between(L" s=\"", L"\"").toLong64() + outLines.back().between(L" l=\"", L"\"").toLong64();
454
	long long length = endPos - startPos;
455
 
456
	SF_INFO inInfo;
457
	SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);
458
	if (!inputFile)
459
	{
460
		wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());
461
		return;
462
	}
463
	int sampleRate = inInfo.samplerate;
464
 
465
	if (verbose)
466
	{
467
		wprintf(L"Extracting page %d from %s to %s\n", page, CFileIO(wavfile).filename().c_str(), CFileIO(outputfile).filename().c_str());
468
		wprintf(L"\tStart Time: \t%lld\n", startPos);
469
		wprintf(L"\tEnd Time: \t%lld\n", endPos);
470
		wprintf(L"\tLength: \t%lld\n", length);
471
	}
472
 
473
	long long startFrame = static_cast<long long>((static_cast<double>(startPos) / 1000.0) * sampleRate);
474
	sf_count_t seek = sf_seek(inputFile, startFrame, SEEK_SET);
475
 
476
	SF_INFO outInfo;
477
	outInfo.channels = 1;
478
	outInfo.samplerate = sampleRate;
479
	outInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
480
	SNDFILE* outputFile = sf_open(outputfile.toFileUTF8().c_str(), SFM_WRITE, &outInfo);
481
	if (outputFile)
482
	{
483
		double buffer[BUFFER_LEN];
484
		long long endFrame = static_cast<long long>((static_cast<double>(endPos) / 1000.0) * sampleRate);
485
 
486
		long long readRemaining = endFrame - startFrame;
487
		while (readRemaining > 0)
488
		{
489
			long long readAmount = readRemaining > BUFFER_LEN ? BUFFER_LEN : readRemaining;
490
			sf_count_t readcount = sf_read_double(inputFile, buffer, readAmount);
491
			sf_write_double(outputFile, buffer, readcount);
492
 
493
			readRemaining -= readcount;
494
		}
495
 
496
		sf_close(outputFile);
497
	}
498
 
499
	sf_close(inputFile);
500
 
501
	if (!quiet || cmd.hasSwitch(L"showresult"))
502
		wprintf(L"Extracted %lldms from page %d to %s\n", length, page, CFileIO(outputfile).filename().c_str());
503
}
504
 
505
void Split(const Utils::CommandLine& cmd)
506
{
326 cycrow 507
	if (cmd.argCount() < 4)
508
	{
509
		wprintf(L"Syntax: %s split [--stream:<#>] <file.xml> <file.wav> <dir>\n", cmd.cmdName().c_str());
510
		wprintf(L"\t<file.wav>\tThe main wav file to split\n");
511
		wprintf(L"\t<file.xml>\tThe main xml file to split\n");
512
		wprintf(L"\t<dir>\t\tThe directory to write the files to\n");
513
		return;
514
	}
515
	bool quiet = cmd.hasSwitch(L"quiet");
516
	bool verbose = cmd.hasSwitch(L"verbose");
517
	Utils::WString xmlfile = cmd.fullFilename(cmd.arg(1));
518
	Utils::WString wavfile = cmd.fullFilename(cmd.arg(2));
519
	Utils::WString dir = cmd.fullFilename(cmd.arg(3));
520
	int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 1;
521
	if (!CFileIO(wavfile).exists())
522
	{
523
		wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());
524
		return;
525
	}
526
	CFileIO xml(xmlfile);
527
	if (!xml.exists())
528
	{
529
		wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());
530
		return;
531
	}
532
	Utils::WStringList xmlLines;
533
	if (!xml.readLines(xmlLines))
534
	{
535
		wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());
536
		return;
537
	}
538
	int filesWritten = 0;
325 cycrow 539
 
326 cycrow 540
	SF_INFO inInfo;
541
	SNDFILE* inputFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_READ, &inInfo);
542
	if (inputFile)
543
	{
544
		CDirIO D(dir);
545
		if (!D.exists())
546
		{
547
			if (!D.create())
548
			{
549
				wprintf(L"Error: Unable to create directory, %s\n", dir.c_str());
550
				return;
551
			}
552
		}
553
 
554
		double buffer[BUFFER_LEN];
555
		sf_count_t seek = sf_seek(inputFile, 0, SEEK_SET);
556
		long inPage = 0;
557
		for (auto itr = xmlLines.begin(); itr != xmlLines.end(); itr++)
558
		{
559
			if ((*itr)->str.contains(L"</page>"))
560
				inPage = 0;
561
			else if ((*itr)->str.contains(L"<page id=\""))
562
			{
563
				long strm = (*itr)->str.between(L" stream=\"", L"\"").toLong();
564
				if (strm == stream)
565
					inPage = (*itr)->str.between(L"<page id=\"", L"\"").toLong();
566
			}
567
			else if (inPage > 0)
568
			{
569
				if ((*itr)->str.contains(L"<t id="))
570
				{
571
					long i = (*itr)->str.between(L" id=\"", L"\"").toLong();
572
					long long s = (*itr)->str.between(L" s=\"", L"\"").toLong64();
573
					long long l = (*itr)->str.between(L" l=\"", L"\"").toLong64();
574
 
575
					long long startFrame = static_cast<long long>((static_cast<double>(s) / 1000.0) * inInfo.samplerate);
576
					sf_count_t seek = sf_seek(inputFile, startFrame, SEEK_SET);
577
 
578
					CDirIO writeDir(D.file(Utils::WString(inPage)));
579
					if (!writeDir.exists())
580
					{
581
						if (!writeDir.create())
582
						{
583
							wprintf(L"Error: Unable to create directory, %s\n", writeDir.dir().c_str());
584
							return;
585
						}
586
					}
587
 
588
					Utils::WString outFileName = writeDir.file(Utils::WString(i) + L".wav");
589
					// now we need to write the wav file
590
					SF_INFO outInfo;
591
					outInfo.channels = 1;
592
					outInfo.samplerate = inInfo.samplerate;
593
					outInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
594
 
595
					SNDFILE* outputFile = sf_open(outFileName.toFileUTF8().c_str(), SFM_WRITE, &outInfo);
596
					if (outputFile)
597
					{
598
						long long endFrame = static_cast<long long>((static_cast<double>(s + l) / 1000.0) * inInfo.samplerate);
599
						long long readRemaining = endFrame - startFrame;
600
						while (readRemaining > 0)
601
						{
602
							long long readAmount = readRemaining > BUFFER_LEN ? BUFFER_LEN : readRemaining;
603
							sf_count_t readcount = sf_read_double(inputFile, buffer, readAmount);
604
							sf_write_double(outputFile, buffer, readcount);
605
 
606
							readRemaining -= readcount;
607
						}
608
						sf_close(outputFile);
609
						if (verbose)
610
							wprintf(L"Wrote %lldms to %d\\%s\n", l, inPage, CFileIO(outFileName).filename().c_str());
611
						++filesWritten;
612
					}
613
				}
614
			}
615
		}
616
 
617
		sf_close(inputFile);
618
	}
619
 
620
	if(filesWritten > 0)
621
	{
622
		if (!quiet || cmd.hasSwitch(L"showresult"))
623
			wprintf(L"Wrote %d files to %s\n", filesWritten, dir.c_str());
624
	}
625
	else
626
	{
627
		wprintf(L"Error: Unable to write any files to %s\n", dir.c_str());
628
	}
325 cycrow 629
}
630
 
631
 
632
void Compile(const Utils::CommandLine& cmd)
633
{
634
	if (cmd.argCount() < 4)
635
	{
636
		wprintf(L"Syntax: %s compile [--stream:<#>] <file.xml> <file.wav> <dir>\n", cmd.cmdName().c_str());
637
		wprintf(L"\t<file.wav>\tThe main wav file to merge into\n");
638
		wprintf(L"\t<file.xml>\tThe main xml file to merge into\n");
639
		wprintf(L"\t<dir>\t\tThe directory containing voice files\n");
640
		wprintf(L"\nOptions:\n");
641
		wprintf(L"	--stream:<#>\tThe stream id to write too\n");
642
		return;
643
	}
644
 
645
	bool quiet = cmd.hasSwitch(L"quiet");
646
	bool verbose = cmd.hasSwitch(L"verbose");
647
	bool verboseresult = cmd.hasSwitch(L"verboseresult");
648
 
649
	Utils::WString xmlfile = cmd.fullFilename(cmd.arg(1));
650
	Utils::WString wavfile = cmd.fullFilename(cmd.arg(2));
651
	Utils::WString dir = cmd.fullFilename(cmd.arg(3));
652
	int stream = cmd.hasSwitch(L"stream") ? cmd.switchData(L"stream").toInt() : 3;
653
 
654
	if (!CFileIO(wavfile).exists())
655
	{
656
		wprintf(L"Error: Unable to open wav file, %s\n", wavfile.c_str());
657
		return;
658
	}
659
	CFileIO xml(xmlfile);
660
	if (!xml.exists())
661
	{
662
		wprintf(L"Error: Unable to open xml file, %s\n", xmlfile.c_str());
663
		return;
664
	}
665
 
666
	Utils::WStringList xmlLines;
667
	if (!xml.readLines(xmlLines))
668
	{
669
		wprintf(L"Error: Unable to read xml file, %s\n", xmlfile.c_str());
670
		return;
671
	}
672
 
673
 
674
	SF_INFO info;
675
	SNDFILE* mainFile = sf_open(wavfile.toFileUTF8().c_str(), SFM_RDWR, &info);
676
	if (mainFile)
677
	{
678
		Utils::WStringList addLines;
679
 
680
		// compute the current position
681
		double duration = static_cast<double>(info.frames) / static_cast<double>(info.samplerate);
682
		size_t currentPos = static_cast<size_t>((duration * 1000.0) + 0.5);
683
 
684
		// seek to end of file
685
		int check = sf_format_check(&info);
686
		sf_count_t seek = sf_seek(mainFile, 0, SEEK_END);
687
 
688
		double buffer[BUFFER_LEN];
689
 
690
		// get all files from directory
691
		CDirIO D(dir);
692
		Utils::WStringList list;
693
		if (D.dirList(list))
694
		{
695
			for (auto itr = list.begin(); itr != list.end(); itr++)
696
			{
697
				if (D.isDir((*itr)->str))
698
				{
699
					CDirIO curDir(D.dir((*itr)->str));
700
					long pageID = (*itr)->str.toInt();
701
 
702
					addLines.pushBack(Utils::WString(L"<page id=\"") + pageID + L"\" stream=\"" + (long)stream + L"\">");
703
 
704
					Utils::WStringList idList;
705
					if (curDir.dirList(idList))
706
					{
707
						for (auto itr2 = idList.begin(); itr2 != idList.end(); itr2++)
708
						{
709
							if (curDir.isFile((*itr2)->str))
270 cycrow 710
							{
325 cycrow 711
								CFileIO F(curDir.file((*itr2)->str));
712
								long id = F.baseName().toInt();
713
								size_t len = 0;
714
 
715
								SF_INFO inInfo;
716
								SNDFILE* inputFile = sf_open(F.fullFilename().toFileUTF8().c_str(), SFM_READ, &inInfo);
717
								if (inputFile)
270 cycrow 718
								{
325 cycrow 719
									if (inInfo.samplerate != info.samplerate)
720
										wprintf(L"Error: Sample rate mismatch, %d:%s (%d) != (%d)\n", pageID, F.filename().c_str(), info.samplerate, inInfo.samplerate);
721
									else
270 cycrow 722
									{
325 cycrow 723
										double duration = static_cast<double>(inInfo.frames) / static_cast<double>(inInfo.samplerate);
724
										size_t len = static_cast<size_t>((duration * 1000.0) + 0.5);
270 cycrow 725
 
325 cycrow 726
										sf_count_t readcount;
727
										while ((readcount = sf_read_double(inputFile, buffer, BUFFER_LEN)) > 0)
728
											sf_write_double(mainFile, buffer, readcount);
729
										//int* data = new int[inInfo.frames * inInfo.channels];
730
										//sf_count_t readCount = sf_readf_int(inputFile, data, inInfo.frames);
731
										//sf_count_t writeCount = sf_writef_int(outputFile, data, inInfo.frames);
270 cycrow 732
 
325 cycrow 733
										addLines.pushBack(Utils::WString(L"\t<t id=\"") + id + L"\" s=\"" + (long)currentPos + L"\" l=\"" + (long)len + "\"/>");
270 cycrow 734
 
325 cycrow 735
										if(verbose || verboseresult)
736
											wprintf(L"Writing %d:%s to %s at %lldms\n", pageID, F.filename().c_str(), CFileIO(wavfile).filename().c_str(), currentPos);
287 cycrow 737
 
325 cycrow 738
										currentPos += len;
270 cycrow 739
									}
325 cycrow 740
									sf_close(inputFile);
270 cycrow 741
								}
742
							}
743
						}
744
					}
325 cycrow 745
 
746
					addLines.pushBack(L"</page>");
270 cycrow 747
				}
325 cycrow 748
			}
270 cycrow 749
 
325 cycrow 750
			// merge the xml file
751
			Utils::WStringList outLines;
752
			for (auto itr = xmlLines.begin(); itr != xmlLines.end(); itr++)
753
			{
754
				Utils::WString line = (*itr)->str;
755
				if (line.contains(L"</language>"))
756
				{
757
					// now we need to add the new lines
758
					for (auto itr2 = addLines.begin(); itr2 != addLines.end(); itr2++)
759
						outLines.pushBack((*itr2)->str);
760
				}
270 cycrow 761
 
325 cycrow 762
				outLines.pushBack(line);
287 cycrow 763
			}
764
 
325 cycrow 765
			CFileIO xml(xmlfile);
766
			xml.writeFile(&outLines);
270 cycrow 767
		}
325 cycrow 768
 
769
		sf_close(mainFile);
770
 
771
		if (!quiet || cmd.hasSwitch(L"showresult"))
772
		{
773
			wprintf(L"\nCompiled %s into %s\n", CFileIO(dir).filename().c_str(), CFileIO(wavfile).filename().c_str());
774
			if (verbose)
775
			{
776
				wprintf(L"\tLength: \t%lldms\n", currentPos);
777
				wprintf(L"\tStream file: \t%s\n", CFileIO(xmlfile).filename().c_str());
778
			}
779
		}
270 cycrow 780
	}
781
 
325 cycrow 782
}
783
 
784
 
785
/*
786
	Main entry point to program
787
*/
788
int main ( int argc, char **argv )
789
{
790
 
791
	Utils::CommandLine cmd(argc, argv);
792
	Utils::WString command = cmd.argCount() >= 1 ? cmd.arg(0) : L"";
793
	command.toLower();
794
 
795
	// display program header to command prompt
796
	if(!cmd.hasSwitch(L"quiet"))
797
		printf("\nX3 Voice Create V1.10 (SPK Library Version %.2f) 18/05/2025 Created by Cycrow\n\n", GetLibraryVersion());
798
 
799
	if (command == L"offset")
800
		DoOffset(cmd);
801
	else if (command == L"help")
802
		PrintSyntax(cmd.cmdName());
803
	else if (command == L"merge")
804
		Merge(cmd);
805
	else if (command == L"extract")
806
		Extract(cmd);
807
	else if (command == L"split")
808
		Split(cmd);
809
	else if (command == L"compile")
810
		Compile(cmd);
811
	else
812
		PrintSyntax(cmd.cmdName());
813
 
270 cycrow 814
	return 0;
815
}