Пример #1
0
void Tool::error(const char *format, ...) {
	char buf[4096];
	va_list va;

	va_start(va, format);
	vsnprintf(buf, 4096, format, va);
	va_end(va);

	throw ToolException(buf);
}
Пример #2
0
void Tool::parseOutputArguments() {
	if (_arguments.empty())
		return;
	if (_arguments.front() == "-o" || _arguments.front() == "--output") {
		// It's an -o argument

		_arguments.pop_front();
		if (_arguments.empty())
			throw ToolException("Could not parse arguments: Expected path after '-o' or '--output'.");

		_outputPath = _arguments.front();
		_arguments.pop_front();
	}
}
Пример #3
0
void CompressTouche::execute() {
	Common::Filename inpath(_inputPaths[0].path);
	Common::Filename &outpath = _outputPath;

	if (outpath.getFullName().empty()) {
		switch(_format) {
		case AUDIO_MP3:
			outpath.setFullName(OUTPUT_MP3);
			break;
		case AUDIO_VORBIS:
			outpath.setFullName(OUTPUT_OGG);
			break;
		case AUDIO_FLAC:
			outpath.setFullName(OUTPUT_FLA);
			break;
		default:
			throw ToolException("Unknown audio format");
			break;
		}
	}

	compress_sound_data(&inpath, &outpath);
}
Пример #4
0
void CompressSword2::execute() {
	int j;
	uint32 indexSize;
	uint32 totalSize;
	uint32 length;

	Common::Filename inpath(_inputPaths[0].path);
	Common::Filename &outpath = _outputPath;

	if (outpath.empty())
		// Extensions change between the in/out files, so we can use the same directory
		outpath = inpath;

	switch (_format) {
	case AUDIO_MP3:
		_audioOutputFilename = TEMP_MP3;
		outpath.setExtension(".cl3");
		break;
	case AUDIO_VORBIS:
		_audioOutputFilename = TEMP_OGG;
		outpath.setExtension(".clg");
		break;
	case AUDIO_FLAC:
		_audioOutputFilename = TEMP_FLAC;
		outpath.setExtension(".clf");
		break;
	default:
		throw ToolException("Unknown audio format");
		break;
	}

	_input.open(inpath, "rb");

	indexSize = _input.readUint32LE();
	totalSize = 12 * (indexSize + 1);

	if (_input.readUint32BE() != 0xfff0fff0) {
		error("This doesn't look like a cluster file");
	}

	_output_idx.open(TEMP_IDX, "wb");
	_output_snd.open(TEMP_DAT, "wb");

	_output_idx.writeUint32LE(indexSize);
	_output_idx.writeUint32BE(0xfff0fff0);
	_output_idx.writeUint32BE(0xfff0fff0);

	for (int i = 0; i < (int)indexSize; i++) {
		// Update progress, this loop is where most of the time is spent
		updateProgress(i, indexSize);

		uint32 pos;
		uint32 enc_length;

		_input.seek(8 * (i + 1), SEEK_SET);

		pos = _input.readUint32LE();
		length = _input.readUint32LE();

		if (pos != 0 && length != 0) {
			uint16 prev;

			Common::File f(TEMP_WAV, "wb");

			/*
			 * The number of decodeable 16-bit samples is one less
			 * than the length of the resource.
			 */

			length--;

			/*
			 * Back when this tool was written, encodeAudio() had
			 * no way of encoding 16-bit data, so it was simpler to
			 * output a WAV file.
			 */

			f.writeUint32BE(0x52494646);	/* "RIFF" */
			f.writeUint32LE(2 * length + 36);
			f.writeUint32BE(0x57415645);	/* "WAVE" */
			f.writeUint32BE(0x666d7420);	/* "fmt " */
			f.writeUint32LE(16);
			f.writeUint16LE(1);				/* PCM */
			f.writeUint16LE(1);				/* mono */
			f.writeUint32LE(22050);			/* sample rate */
			f.writeUint32LE(44100);			/* bytes per second */
			f.writeUint16LE(2);				/* basic block size */
			f.writeUint16LE(16);			/* sample width */
			f.writeUint32BE(0x64617461);	/* "data" */
			f.writeUint32LE(2 * length);

			_input.seek(pos, SEEK_SET);

			/*
			 * The first sample is stored uncompressed. Subsequent
			 * samples are stored as some sort of 8-bit delta.
			 */

			prev = _input.readUint16LE();

			f.writeUint16LE(prev);

			for (j = 1; j < (int)length; j++) {
				byte data;
				uint16 out;

				data = _input.readByte();
				if (GetCompressedSign(data))
					out = prev - (GetCompressedAmplitude(data) << GetCompressedShift(data));
				else
					out = prev + (GetCompressedAmplitude(data) << GetCompressedShift(data));

				f.writeUint16LE(out);
				prev = out;
			}

			f.close();

			encodeAudio(TEMP_WAV, false, -1, tempEncoded, _format);
			enc_length = append_to_file(_output_snd, tempEncoded);

			_output_idx.writeUint32LE(totalSize);
			_output_idx.writeUint32LE(length);
			_output_idx.writeUint32LE(enc_length);
			totalSize = totalSize + enc_length;
		} else {
			_output_idx.writeUint32LE(0);
			_output_idx.writeUint32LE(0);
			_output_idx.writeUint32LE(0);
		}
	}

	_output_snd.close();
	_output_idx.close();

	Common::File output(outpath, "wb");

	append_to_file(output, TEMP_IDX);
	append_to_file(output, TEMP_DAT);

	output.close();

	Common::removeFile(TEMP_DAT);
	Common::removeFile(TEMP_IDX);
	Common::removeFile(TEMP_MP3);
	Common::removeFile(TEMP_OGG);
	Common::removeFile(TEMP_FLAC);
	Common::removeFile(TEMP_WAV);
}
Пример #5
0
void CompressionTool::encodeRaw(const char *rawData, int length, int samplerate, const char *outname, AudioFormat compmode) {

	print(" - len=%ld, ch=%d, rate=%d, %dbits", length, (rawAudioType.isStereo ? 2 : 1), samplerate, rawAudioType.bitsPerSample);

#ifdef USE_VORBIS
	if (compmode == AUDIO_VORBIS) {
		char outputString[256] = "";
		int numChannels = (rawAudioType.isStereo ? 2 : 1);
		int totalSamples = length / ((rawAudioType.bitsPerSample / 8) * numChannels);
		int samplesLeft = totalSamples;
		int eos = 0;
		int totalBytes = 0;

		vorbis_info vi;
		vorbis_comment vc;
		vorbis_dsp_state vd;
		vorbis_block vb;

		ogg_stream_state os;
		ogg_page og;
		ogg_packet op;

		ogg_packet header;
		ogg_packet header_comm;
		ogg_packet header_code;

		Common::File outputOgg(outname, "wb");

		vorbis_info_init(&vi);

		if (oggparms.nominalBitr > 0) {
			int result = 0;

			/* Input is in kbps, function takes bps */
			result = vorbis_encode_setup_managed(&vi, numChannels, samplerate, (oggparms.maxBitr > 0 ? 1000 * oggparms.maxBitr : -1), (1000 * oggparms.nominalBitr), (oggparms.minBitr > 0 ? 1000 * oggparms.minBitr : -1));

			if (result == OV_EFAULT) {
				vorbis_info_clear(&vi);
				error("Error: Internal Logic Fault");
			} else if ((result == OV_EINVAL) || (result == OV_EIMPL)) {
				vorbis_info_clear(&vi);
				error("Error: Invalid bitrate parameters");
			}

			if (!oggparms.silent) {
				sprintf(outputString, "Encoding to\n         \"%s\"\nat average bitrate %i kbps (", outname, oggparms.nominalBitr);

				if (oggparms.minBitr > 0) {
					sprintf(outputString + strlen(outputString), "min %i kbps, ", oggparms.minBitr);
				} else {
					sprintf(outputString + strlen(outputString), "no min, ");
				}

				if (oggparms.maxBitr > 0) {
					sprintf(outputString + strlen(outputString), "max %i kbps),\nusing full bitrate management engine\nSet optional hard quality restrictions\n", oggparms.maxBitr);
				} else {
					sprintf(outputString + strlen(outputString), "no max),\nusing full bitrate management engine\nSet optional hard quality restrictions\n");
				}
			}
		} else {
			int result = 0;

			/* Quality input is -1 - 10, function takes -0.1 through 1.0 */
			result = vorbis_encode_setup_vbr(&vi, numChannels, samplerate, oggparms.quality * 0.1f);

			if (result == OV_EFAULT) {
				vorbis_info_clear(&vi);
				error("Internal Logic Fault");
			} else if ((result == OV_EINVAL) || (result == OV_EIMPL)) {
				vorbis_info_clear(&vi);
				error("Invalid bitrate parameters");
			}

			if (!oggparms.silent) {
				sprintf(outputString, "Encoding to\n         \"%s\"\nat quality %2.2f", outname, oggparms.quality);
			}

			if ((oggparms.minBitr > 0) || (oggparms.maxBitr > 0)) {
				struct ovectl_ratemanage_arg extraParam;
				vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE_GET, &extraParam);

				extraParam.bitrate_hard_min = (oggparms.minBitr > 0 ? (1000 * oggparms.minBitr) : -1);
				extraParam.bitrate_hard_max = (oggparms.maxBitr > 0 ? (1000 * oggparms.maxBitr) : -1);
				extraParam.management_active = 1;

				vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE_SET, &extraParam);

				if (!oggparms.silent) {
					sprintf(outputString + strlen(outputString), " using constrained VBR (");

					if (oggparms.minBitr != -1) {
						sprintf(outputString + strlen(outputString), "min %i kbps, ", oggparms.minBitr);
					} else {
						sprintf(outputString + strlen(outputString), "no min, ");
					}

					if (oggparms.maxBitr != -1) {
						sprintf(outputString + strlen(outputString), "max %i kbps)\nSet optional hard quality restrictions\n", oggparms.maxBitr);
					} else {
						sprintf(outputString + strlen(outputString), "no max)\nSet optional hard quality restrictions\n");
					}
				}
			} else {
				sprintf(outputString + strlen(outputString), "\n");
			}
		}

		puts(outputString);

		vorbis_encode_setup_init(&vi);
		vorbis_comment_init(&vc);
		vorbis_analysis_init(&vd, &vi);
		vorbis_block_init(&vd, &vb);
		ogg_stream_init(&os, 0);
		vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code);

		ogg_stream_packetin(&os, &header);
		ogg_stream_packetin(&os, &header_comm);
		ogg_stream_packetin(&os, &header_code);

		while (!eos) {
			int result = ogg_stream_flush(&os,&og);

			if (result == 0) {
				break;
			}

			outputOgg.write(og.header, og.header_len);
			outputOgg.write(og.body, og.body_len);
		}

		while (!eos) {
			int numSamples = ((samplesLeft < 2048) ? samplesLeft : 2048);
			float **buffer = vorbis_analysis_buffer(&vd, numSamples);

			/* We must tell the encoder that we have reached the end of the stream */
			if (numSamples == 0) {
				vorbis_analysis_wrote(&vd, 0);
			} else {
				/* Adapted from oggenc 1.1.1 */
				if (rawAudioType.bitsPerSample == 8) {
					const byte *rawDataUnsigned = (const byte *)rawData;
					for (int i = 0; i < numSamples; i++) {
						for (int j = 0; j < numChannels; j++) {
							buffer[j][i] = ((int)(rawDataUnsigned[i * numChannels + j]) - 128) / 128.0f;
						}
					}
				} else if (rawAudioType.bitsPerSample == 16) {
					if (rawAudioType.isLittleEndian) {
						for (int i = 0; i < numSamples; i++) {
							for (int j = 0; j < numChannels; j++) {
								buffer[j][i] = ((rawData[(i * 2 * numChannels) + (2 * j) + 1] << 8) | (rawData[(i * 2 * numChannels) + (2 * j)] & 0xff)) / 32768.0f;
							}
						}
					} else {
						for (int i = 0; i < numSamples; i++) {
							for (int j = 0; j < numChannels; j++) {
								buffer[j][i] = ((rawData[(i * 2 * numChannels) + (2 * j)] << 8) | (rawData[(i * 2 * numChannels) + (2 * j) + 1] & 0xff)) / 32768.0f;
							}
						}
					}
				}

				vorbis_analysis_wrote(&vd, numSamples);
			}

			while (vorbis_analysis_blockout(&vd, &vb) == 1) {
				vorbis_analysis(&vb, NULL);
				vorbis_bitrate_addblock(&vb);

				while (vorbis_bitrate_flushpacket(&vd, &op)) {
					ogg_stream_packetin(&os, &op);

					while (!eos) {
						int result = ogg_stream_pageout(&os, &og);

						if (result == 0) {
							break;
						}

						totalBytes += outputOgg.write(og.header, og.header_len);
						totalBytes += outputOgg.write(og.body, og.body_len);

						if (ogg_page_eos(&og)) {
							eos = 1;
						}
					}
				}
			}

			rawData += 2048 * (rawAudioType.bitsPerSample / 8) * numChannels;
			samplesLeft -= 2048;
		}

		ogg_stream_clear(&os);
		vorbis_block_clear(&vb);
		vorbis_dsp_clear(&vd);
		vorbis_info_clear(&vi);

		if (!oggparms.silent) {
			print("\nDone encoding file \"%s\"", outname);
			print("\n\tFile length:  %dm %ds", (int)(totalSamples / samplerate / 60), (totalSamples / samplerate % 60));
			print("\tAverage bitrate: %.1f kb/s\n", (8.0 * (double)totalBytes / 1000.0) / ((double)totalSamples / (double)samplerate));
		}
	}
#endif

#ifdef USE_FLAC
	if (compmode == AUDIO_FLAC) {
		int i;
		int numChannels = (rawAudioType.isStereo ? 2 : 1);
		int samplesPerChannel = length / ((rawAudioType.bitsPerSample / 8) * numChannels);
		FLAC__StreamEncoder *encoder;
		FLAC__StreamEncoderInitStatus initStatus;
		FLAC__int32 *flacData;

		flacData = (FLAC__int32 *)malloc(samplesPerChannel * numChannels * sizeof(FLAC__int32));

		if (rawAudioType.bitsPerSample == 8) {
			for (i = 0; i < samplesPerChannel * numChannels; i++) {
				FLAC__uint8 *rawDataUnsigned;
				rawDataUnsigned = (FLAC__uint8 *)rawData;
				flacData[i] = (FLAC__int32)rawDataUnsigned[i] - 0x80;
			}
		} else if (rawAudioType.bitsPerSample == 16) {
			/* The rawData pointer is an 8-bit char so we must create a new pointer to access 16-bit samples */
			FLAC__int16 *rawData16;
			rawData16 = (FLAC__int16 *)rawData;
			for (i = 0; i < samplesPerChannel * numChannels; i++) {
				flacData[i] = (FLAC__int32)rawData16[i];
			}
		}

		if (!flacparms.silent) {
			print("Encoding to\n         \"%s\"\nat compression level %d using blocksize %d\n", outname, flacparms.compressionLevel, flacparms.blocksize);
		}

		encoder = FLAC__stream_encoder_new();

		FLAC__stream_encoder_set_bits_per_sample(encoder, rawAudioType.bitsPerSample);
		FLAC__stream_encoder_set_blocksize(encoder, flacparms.blocksize);
		FLAC__stream_encoder_set_channels(encoder, numChannels);
		FLAC__stream_encoder_set_compression_level(encoder, flacparms.compressionLevel);
		FLAC__stream_encoder_set_sample_rate(encoder, samplerate);
		FLAC__stream_encoder_set_streamable_subset(encoder, false);
		FLAC__stream_encoder_set_total_samples_estimate(encoder, samplesPerChannel);
		FLAC__stream_encoder_set_verify(encoder, flacparms.verify);

		initStatus = FLAC__stream_encoder_init_file(encoder, outname, NULL, NULL);

		if (initStatus != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
			char buf[2048];
			sprintf(buf, "Error in FLAC encoder. (check the parameters)\nExact error was:%s", FLAC__StreamEncoderInitStatusString[initStatus]);
			free(flacData);
			throw ToolException(buf);
		} else {
			FLAC__stream_encoder_process_interleaved(encoder, flacData, samplesPerChannel);
		}

		FLAC__stream_encoder_finish(encoder);
		FLAC__stream_encoder_delete(encoder);

		free(flacData);

		if (!flacparms.silent) {
			print("\nDone encoding file \"%s\"", outname);
			print("\n\tFile length:  %dm %ds\n", (int)(samplesPerChannel / samplerate / 60), (samplesPerChannel / samplerate % 60));
		}
	}
#endif
}
Пример #6
0
void CompressionTool::encodeAudio(const char *inname, bool rawInput, int rawSamplerate, const char *outname, AudioFormat compmode) {
	bool err = false;
	char fbuf[2048];
	char *tmp = fbuf;

	if (compmode == AUDIO_MP3) {
		tmp += sprintf(tmp, "%s -t ", lameparms.lamePath.c_str());
		if (rawInput) {
			tmp += sprintf(tmp, "-r ");
			tmp += sprintf(tmp, "--bitwidth %d ", rawAudioType.bitsPerSample);

			if (rawAudioType.isLittleEndian) {
				tmp += sprintf(tmp, "--little-endian ");
			} else {
				tmp += sprintf(tmp, "--big-endian ");
			}

			tmp += sprintf(tmp, (rawAudioType.isStereo ? "-m j " : "-m m "));
			tmp += sprintf(tmp, "-s %d ", rawSamplerate);
		}

		if (lameparms.type == CBR)
			tmp += sprintf(tmp, "--cbr -b %d ", lameparms.targetBitr);
		else {
			if (lameparms.type == ABR)
				tmp += sprintf(tmp, "--abr %d ", lameparms.targetBitr);
			else
				tmp += sprintf(tmp, "--vbr-new -V %d ", lameparms.vbrqual);

			if (lameparms.minBitr != -1)
				tmp += sprintf(tmp, "-b %d ", lameparms.minBitr);
			if (lameparms.maxBitr != -1)
				tmp += sprintf(tmp, "-B %d ", lameparms.maxBitr);
		}

		/* Explicitly specify a target sample rate, to work around a bug (?)
		* in newer lame versions (>= 3.95) which causes it to malfunction
		* for odd sample rates when in VBR mode. See also bug #934026.
		* We essentially duplicate the old behaviour of lame (found in e.g.
		* version 3.93.1): we round the input sample rate up to the next
		* higher valid MP3 sample rate, with a margin of 3%.
		*/
		if (rawSamplerate != -1) {
			tmp += sprintf(tmp, "--resample %d ", map2MP3Frequency(97 * rawSamplerate / 100));
		}

		if (lameparms.silent) {
			tmp += sprintf(tmp, " --silent ");
		}

		tmp += sprintf(tmp, "-q %d ", lameparms.algqual);

		tmp += sprintf(tmp, "\"%s\" \"%s\" ", inname, outname);

		err = spawnSubprocess(fbuf) != 0;

		if (err) {
			char buf[2048];
			sprintf(buf, "Error in MP3 encoder.(check parameters) \nMP3 Encoder Commandline:%s\n", fbuf);
			throw ToolException(buf, err);
		} else {
			return;
		}
	}

#ifndef USE_VORBIS
	if (compmode == AUDIO_VORBIS) {
		tmp += sprintf(tmp, "oggenc ");
		if (rawInput) {
			tmp += sprintf(tmp, "--raw ");
			tmp += sprintf(tmp, "--raw-chan=%d ", (rawAudioType.isStereo ? 2 : 1));
			tmp += sprintf(tmp, "--raw-bits=%d ", rawAudioType.bitsPerSample);
			tmp += sprintf(tmp, "--raw-rate=%d ", rawSamplerate);
			tmp += sprintf(tmp, "--raw-endianness=%d ", (rawAudioType.isLittleEndian ? 0 : 1));
		}

		if (oggparms.nominalBitr != -1) {
			tmp += sprintf(tmp, "--bitrate=%d ", oggparms.nominalBitr);
		} else {
			tmp += sprintf(tmp, "--quality=%f ", oggparms.quality);
		}

		if (oggparms.minBitr != -1) {
			tmp += sprintf(tmp, "--min-bitrate=%d ", oggparms.minBitr);
		}

		if (oggparms.maxBitr != -1) {
			tmp += sprintf(tmp, "--max-bitrate=%d ", oggparms.maxBitr);
		}

		if (oggparms.silent) {
			tmp += sprintf(tmp, "--quiet ");
		}

		tmp += sprintf(tmp, "--output=\"%s\" ", outname);
		tmp += sprintf(tmp, "\"%s\" ", inname);

		err = spawnSubprocess(fbuf) != 0;

		if (err) {
			char buf[2048];
			sprintf(buf, "Error in Vorbis encoder. (check parameters)\nVorbis Encoder Commandline:%s\n", fbuf);
			throw ToolException(buf, err);
		} else {
			return;
		}
	}
#endif

#ifndef USE_FLAC
	if (compmode == AUDIO_FLAC) {
		/* --lax is needed to allow 11kHz, we dont need place for meta-tags, and no seektable */
		/* -f is reqired to force override of unremoved temp file. See bug #1294648 */
		tmp += sprintf(tmp, "flac -f --lax --no-padding --no-seektable --no-ogg ");

		if (rawInput) {
			tmp += sprintf(tmp, "--force-raw-format ");
			tmp += sprintf(tmp, "--sign=%s ", ((rawAudioType.bitsPerSample == 8) ? "unsigned" : "signed"));
			tmp += sprintf(tmp, "--channels=%d ", (rawAudioType.isStereo ? 2 : 1));
			tmp += sprintf(tmp, "--bps=%d ", rawAudioType.bitsPerSample);
			tmp += sprintf(tmp, "--sample-rate=%d ", rawSamplerate);
			tmp += sprintf(tmp, "--endian=%s ", (rawAudioType.isLittleEndian ? "little" : "big"));
		}

		if (flacparms.silent) {
			tmp += sprintf(tmp, "--silent ");
		}

		if (flacparms.verify) {
			tmp += sprintf(tmp, "--verify ");
		}

		tmp += sprintf(tmp, "--compression-level-%d ", flacparms.compressionLevel);
		tmp += sprintf(tmp, "-b %d ", flacparms.blocksize);
		tmp += sprintf(tmp, "-o \"%s\" ", outname);
		tmp += sprintf(tmp, "\"%s\" ", inname);

		err = spawnSubprocess(fbuf) != 0;

		if (err) {
			char buf[2048];
			sprintf(buf, "Error in FLAC encoder. (check parameters)\nFLAC Encoder Commandline:%s\n", fbuf);
			throw ToolException(buf, err);
		} else {
			return;
		}
	}
#endif
	if (rawInput) {
		long length;
		char *rawData;

		Common::File inputRaw(inname, "rb");
		length = inputRaw.size();
		rawData = (char *)malloc(length);
		inputRaw.read_throwsOnError(rawData, length);

		encodeRaw(rawData, length, rawSamplerate, outname, compmode);

		free(rawData);
	} else {
		int fmtHeaderSize, length, numChannels, sampleRate, bitsPerSample;
		char *wavData;

		Common::File inputWav(inname, "rb");

		/* Standard PCM fmt header is 16 bits, but at least Simon 1 and 2 use 18 bits */
		inputWav.seek(16, SEEK_SET);
		fmtHeaderSize = inputWav.readUint32LE();

		inputWav.seek(22, SEEK_SET);
		numChannels = inputWav.readUint16LE();
		sampleRate = inputWav.readUint32LE();

		inputWav.seek(34, SEEK_SET);
		bitsPerSample = inputWav.readUint16LE();

		/* The size of the raw audio is after the RIFF chunk (12 bytes), fmt chunk (8 + fmtHeaderSize bytes), and data chunk id (4 bytes) */
		inputWav.seek(24 + fmtHeaderSize, SEEK_SET);
		length = inputWav.readUint32LE();

		wavData = (char *)malloc(length);
		inputWav.read_throwsOnError(wavData, length);

		setRawAudioType(true, numChannels == 2, (uint8)bitsPerSample);
		encodeRaw(wavData, length, sampleRate, outname, compmode);

		free(wavData);
	}
}
Пример #7
0
void CompressionExample::execute() {
	// By now, all arguments have been parsed, all members setup and all input paths setup
	// If this was a compression tool _format would contain the selected audio format
	// Note that we almost need no error handling, since exceptions will be thrown if we do
	// something bad, and all parameters have already been setup

	// This 'tool' doesn't takes two input files
	// It writes the of each file 100 times into X files, where X is the input file size.

	// _inputPaths[0].path is always valid and contains the correct path, same for 1
	Common::Filename inpath1(_inputPaths[0].path);
	Common::Filename inpath2(_inputPaths[1].path);

	// We always need to setup default output path, since there is no obligation to specify it
	// If you don't do this, the OS will usually default to the working directory, if we output a file
	// it will fail most likely
	if (_outputPath.empty())
		_outputPath = "output/";

	// The File class is very similar to the FILE struct, look in util.h for details
	File in1(inpath1, "r");
	File in2(inpath2, "r");

	// Read the complete contents of the files (if they don't contain NUL ofcourse)
	std::string text1 = in1.readString();
	std::string text2 = in2.readString();

	// Start the 'extraction'
	size_t total_files = in1.size() + in2.size();

	// There has to be some roof on this
	if (total_files > 1000)
		throw ToolException("Input files are too large!");

	for (size_t i = 0; i < total_files; ++i) {
		// This updates any progress bar, if there is any
		// if you don't support progress bars, you should use notifyProgress instead
		// to make sure progress can be aborted (print calls notifyProgress internally)
		updateProgress(i, total_files);

		// Convert i to string
		std::ostringstream outname;
		outname << i;

		// Open a file for writing
		if (_outputFiles) {
			File out(_outputPath.getPath() + outname.str() + ".exo", "w");

			// What we actually do, output some text alot
			for (size_t j = 0; j < 100; ++j) {
				if (i < in1.size())
					out.write(text1.c_str(), 1, text1.size());
				else
					out.write(text2.c_str(), 1, text2.size());
			}
		} else {
			// Do nothing for awhile
			for (int i = 0; i < 1000000; ++i) {
				int *n = new int;
				delete n;
			}
		}

		// Print can also throw an AbortException
		// Do NOT use printf or anything else that outputs to standard output, as it will not display in the GUI.
		print("Outputted file %d of %d", i, total_files);
	}

	// We indicate success by not throwing any exceptions during the execution
	print("Extraction finished without errors!");
}