Subversion Repositories spk

Rev

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