/*
* xSF - File structure
* By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com]
*
* Partially based on the vio*sf framework
*/
#include <algorithm>
#include <functional>
#include <stdexcept>
#include "zlib.h"
#include "XSFFile.h"
#include "XSFCommon.h"
#include "convert.h"
static inline void Set32BitsLE(uint32_t input, uint8_t *output)
{
output[0] = input & 0xFF;
output[1] = (input >> 8) & 0xFF;
output[2] = (input >> 16) & 0xFF;
output[3] = (input >> 24) & 0xFF;
}
// The whitespace trimming was modified from the following answer on Stack Overflow:
// http://stackoverflow.com/a/217605
struct IsWhitespace
{
using argument_type = char;;
bool operator()(const char &x) const
{
return x >= 0x01 && x <= 0x20;
}
};
static inline std::string LeftTrimWhitespace(const std::string &orig)
{
auto first_non_space = std::find_if(orig.begin(), orig.end(), std::not1(IsWhitespace()));
return std::string(first_non_space, orig.end());
}
static inline std::string RightTrimWhitespace(const std::string &orig)
{
auto last_non_space = std::find_if(orig.rbegin(), orig.rend(), std::not1(IsWhitespace())).base();
return std::string(orig.begin(), last_non_space);
}
static inline std::string TrimWhitespace(const std::string &orig)
{
return LeftTrimWhitespace(RightTrimWhitespace(orig));
}
XSFFile::XSFFile() : xSFType(0), hasFile(false), rawData(), reservedSection(), programSection(), tags(), fileName("")
{
}
XSFFile::XSFFile(const std::string &filename) : xSFType(0), hasFile(false), rawData(), reservedSection(), programSection(), tags(), fileName(filename)
{
this->ReadXSF(filename, 0, 0, true);
}
XSFFile::XSFFile(const std::string &filename, uint32_t programSizeOffset, uint32_t programHeaderSize) : xSFType(0), hasFile(false), rawData(), reservedSection(), programSection(), tags(), fileName(filename)
{
this->ReadXSF(filename, programSizeOffset, programHeaderSize);
}
#ifdef _WIN32
XSFFile::XSFFile(const std::wstring &filename) : xSFType(0), hasFile(false), rawData(), reservedSection(), programSection(), tags(), fileName(ConvertFuncs::WStringToString(filename))
{
this->ReadXSF(filename, 0, 0, true);
}
XSFFile::XSFFile(const std::wstring &filename, uint32_t programSizeOffset, uint32_t programHeaderSize) : xSFType(0), hasFile(false), rawData(), reservedSection(), programSection(), tags(), fileName(ConvertFuncs::WStringToString(filename))
{
this->ReadXSF(filename, programSizeOffset, programHeaderSize);
}
#endif
void XSFFile::ReadXSF(const std::string &filename, uint32_t programSizeOffset, uint32_t programHeaderSize, bool readTagsOnly)
{
if (!FileExists(filename))
throw std::logic_error("File " + filename + " does not exist.");
std::ifstream xSF;
xSF.exceptions(std::ifstream::failbit | std::ifstream::badbit);
xSF.open(filename.c_str(), std::ifstream::in | std::ifstream::binary);
this->ReadXSF(xSF, programSizeOffset, programHeaderSize, readTagsOnly);
xSF.close();
}
#ifdef _WIN32
void XSFFile::ReadXSF(const std::wstring &filename, uint32_t programSizeOffset, uint32_t programHeaderSize, bool readTagsOnly)
{
if (!FileExists(filename))
throw std::logic_error("File " + ConvertFuncs::WStringToString(filename) + " does not exist.");
#if defined(_WIN32) && !defined(_MSC_VER)
ifstream_wfopen xSF;
#else
std::ifstream xSF;
#endif
xSF.exceptions(std::ifstream::failbit | std::ifstream::badbit);
xSF.open(filename.c_str(), std::ifstream::in | std::ifstream::binary);
this->ReadXSF(xSF, programSizeOffset, programHeaderSize, readTagsOnly);
xSF.close();
}
#endif
void XSFFile::ReadXSF(std::ifstream &xSF, uint32_t programSizeOffset, uint32_t programHeaderSize, bool readTagsOnly)
{
xSF.seekg(0, std::ifstream::end);
auto filesize = xSF.tellg();
xSF.seekg(0, std::ifstream::beg);
if (filesize < 4)
throw std::runtime_error("File is too small.");
char PSFHeader[4];
xSF.read(PSFHeader, 4);
if (PSFHeader[0] != 'P' || PSFHeader[1] != 'S' || PSFHeader[2] != 'F')
throw std::runtime_error("Not a PSF file.");
this->xSFType = PSFHeader[3];
this->rawData.resize(4);
memcpy(&this->rawData[0], PSFHeader, 4);
if (filesize < 16)
throw std::runtime_error("File is too small.");
uint32_t reservedSize = Get32BitsLE(xSF), programCompressedSize = Get32BitsLE(xSF);
this->rawData.resize(reservedSize + programCompressedSize + 16);
Set32BitsLE(reservedSize, &this->rawData[4]);
Set32BitsLE(programCompressedSize, &this->rawData[8]);
xSF.read(reinterpret_cast<char *>(&this->rawData[12]), 4);
if (reservedSize)
{
if (filesize < reservedSize + 16)
throw std::runtime_error("File is too small.");
if (readTagsOnly)
xSF.read(reinterpret_cast<char *>(&this->rawData[16]), reservedSize);
else
{
this->reservedSection.resize(reservedSize);
xSF.read(reinterpret_cast<char *>(&this->reservedSection[0]), reservedSize);
memcpy(&this->rawData[16], &this->reservedSection[0], reservedSize);
}
}
if (programCompressedSize)
{
if (filesize < reservedSize + programCompressedSize + 16)
throw std::runtime_error("File is too small.");
if (readTagsOnly)
xSF.read(reinterpret_cast<char *>(&this->rawData[reservedSize + 16]), programCompressedSize);
else
{
auto programSectionCompressed = std::vector<uint8_t>(programCompressedSize);
xSF.read(reinterpret_cast<char *>(&programSectionCompressed[0]), programCompressedSize);
memcpy(&this->rawData[reservedSize + 16], &programSectionCompressed[0], programCompressedSize);
auto programSectionUncompressed = std::vector<uint8_t>(programHeaderSize);
unsigned long programUncompressedSize = programHeaderSize;
uncompress(&programSectionUncompressed[0], &programUncompressedSize, &programSectionCompressed[0], programCompressedSize);
programUncompressedSize = Get32BitsLE(&programSectionUncompressed[programSizeOffset]) + programHeaderSize;
this->programSection.resize(programUncompressedSize);
uncompress(&this->programSection[0], &programUncompressedSize, &programSectionCompressed[0], programCompressedSize);
}
}
if (xSF.tellg() != filesize && filesize >= reservedSize + programCompressedSize + 21)
{
char tagheader[6] = "";
xSF.read(tagheader, 5);
if (std::string(tagheader) == "[TAG]")
{
auto startOfTags = xSF.tellg();
unsigned lengthOfTags = static_cast<unsigned>(filesize - startOfTags);
if (lengthOfTags)
{
auto rawtags = std::vector<char>(lengthOfTags);
xSF.read(&rawtags[0], lengthOfTags);
std::string name, value;
bool onName = true;
for (unsigned x = 0; x < lengthOfTags; ++x)
{
char curr = rawtags[x];
if (curr == 0x0A)
{
if (!name.empty() && !value.empty())
{
name = TrimWhitespace(name);
value = TrimWhitespace(value);
if (this->tags.Exists(name))
this->tags[name] += "\n" + value;
else
this->tags[name] = value;
}
name = value = "";
onName = true;
continue;
}
if (curr == '=')
{
onName = false;
continue;
}
if (onName)
name += curr;
else
value += curr;
}
}
}
}
this->hasFile = true;
}
bool XSFFile::IsValidType(uint8_t type) const
{
return this->xSFType == type;
}
void XSFFile::Clear()
{
this->xSFType = 0;
this->hasFile = false;
this->reservedSection.clear();
this->programSection.clear();
this->tags.Clear();
}
bool XSFFile::HasFile() const
{
return this->hasFile;
}
std::vector<uint8_t> &XSFFile::GetReservedSection()
{
return this->reservedSection;
}
std::vector<uint8_t> XSFFile::GetReservedSection() const
{
return this->reservedSection;
}
std::vector<uint8_t> &XSFFile::GetProgramSection()
{
return this->programSection;
}
std::vector<uint8_t> XSFFile::GetProgramSection() const
{
return this->programSection;
}
const TagList &XSFFile::GetAllTags() const
{
return this->tags;
}
void XSFFile::SetAllTags(const TagList &newTags)
{
this->tags = newTags;
}
void XSFFile::SetTag(const std::string &name, const std::string &value)
{
this->tags[name] = value;
}
void XSFFile::SetTag(const std::string &name, const std::wstring &value)
{
this->tags[name] = ConvertFuncs::WStringToString(value);
}
bool XSFFile::GetTagExists(const std::string &name) const
{
return this->tags.Exists(name);
}
std::string XSFFile::GetTagValue(const std::string &name) const
{
return this->GetTagExists(name) ? this->tags[name] : "";
}
unsigned long XSFFile::GetLengthMS(unsigned long defaultLength) const
{
unsigned long length = 0;
std::string value = this->GetTagValue("length");
if (!value.empty())
length = ConvertFuncs::StringToMS(value);
if (!length)
length = defaultLength;
return length;
}
unsigned long XSFFile::GetFadeMS(unsigned long defaultFade) const
{
unsigned long fade = defaultFade;
std::string value = this->GetTagValue("fade");
if (!value.empty())
fade = ConvertFuncs::StringToMS(value);
return fade;
}
double XSFFile::GetVolume(VolumeType preferredVolumeType, PeakType preferredPeakType) const
{
if (preferredVolumeType == VOLUMETYPE_NONE)
return 1.0;
std::string replaygain_album_gain = this->GetTagValue("replaygain_album_gain"), replaygain_album_peak = this->GetTagValue("replaygain_album_peak");
std::string replaygain_track_gain = this->GetTagValue("replaygain_track_gain"), replaygain_track_peak = this->GetTagValue("replaygain_track_peak");
std::string volume = this->GetTagValue("volume");
double gain = 0.0;
bool hadReplayGain = false;
if (preferredVolumeType == VOLUMETYPE_REPLAYGAIN_ALBUM && !replaygain_album_gain.empty())
{
gain = convertTo<double>(replaygain_album_gain);
hadReplayGain = true;
}
if (!hadReplayGain && preferredVolumeType != VOLUMETYPE_VOLUME && !replaygain_track_gain.empty())
{
gain = convertTo<double>(replaygain_track_gain);
hadReplayGain = true;
}
if (hadReplayGain)
{
double vol = std::pow(10.0, gain / 20.0), peak = 1.0;
if (preferredPeakType == PEAKTYPE_REPLAYGAIN_ALBUM && !replaygain_album_peak.empty())
peak = convertTo<double>(replaygain_album_peak);
else if (preferredPeakType != PEAKTYPE_NONE && !replaygain_track_peak.empty())
peak = convertTo<double>(replaygain_track_peak);
return !fEqual(peak, 1.0) ? std::min(vol, 1.0 / peak) : vol;
}
return volume.empty() ? 1.0 : convertTo<double>(volume);
}
std::string XSFFile::FormattedTitleOptionalBlock(const std::string &block, bool &hadReplacement, unsigned level) const
{
std::string formattedBlock;
for (size_t x = 0, len = block.length(); x < len; ++x)
{
char c = block[x];
if (c == '%')
{
size_t origX = x;
for (++x; x < len; ++x)
if (block[x] == '%')
break;
if (x != len)
{
std::string tagname = block.substr(origX + 1, x - origX - 1);
std::string value = this->GetTagValue(tagname);
if (!value.empty())
{
formattedBlock += value;
hadReplacement = true;
}
}
continue;
}
if (c == '[' && level + 1 < 10)
{
size_t origX = x;
unsigned nests = 0;
for (++x; x < len; ++x)
{
if (block[x] == '[')
++nests;
else if (block[x] == ']')
{
if (!nests)
break;
--nests;
}
}
if (x != len)
{
std::string innerBlock = block.substr(origX + 1, x - origX - 1);
bool hadInnerReplacement = false;
std::string replacedBlock = this->FormattedTitleOptionalBlock(innerBlock, hadInnerReplacement, level + 1);
if (hadInnerReplacement)
formattedBlock += replacedBlock;
}
continue;
}
formattedBlock += c;
}
return formattedBlock;
}
std::string XSFFile::GetFormattedTitle(const std::string &format) const
{
std::string formattedTitle;
for (size_t x = 0, len = format.length(); x < len; ++x)
{
char c = format[x];
if (c == '%')
{
size_t origX = x;
for (++x; x < len; ++x)
if (format[x] == '%')
break;
if (x != len)
{
std::string tagname = format.substr(origX + 1, x - origX - 1);
std::string value = this->GetTagValue(tagname);
if (value.empty())
formattedTitle += "???";
else
formattedTitle += value;
}
}
else if (c == '[')
{
size_t origX = x;
unsigned nests = 0;
for (++x; x < len; ++x)
{
if (format[x] == '[')
++nests;
else if (format[x] == ']')
{
if (!nests)
break;
--nests;
}
}
if (x != len)
{
std::string block = format.substr(origX + 1, x - origX - 1);
bool hadReplacement = false;
std::string replacedBlock = this->FormattedTitleOptionalBlock(block, hadReplacement, 1);
if (hadReplacement)
formattedTitle += replacedBlock;
}
}
else
formattedTitle += c;
}
return formattedTitle;
}
std::string XSFFile::GetFilename() const
{
return this->fileName;
}
std::string XSFFile::GetFilenameWithoutPath() const
{
return ExtractFilenameFromPath(this->fileName);
}
void XSFFile::SaveFile() const
{
#if defined(_WIN32) && !defined(_MSC_VER)
ofstream_wfopen xSF;
#else
std::ofstream xSF;
#endif
xSF.exceptions(std::ofstream::failbit);
#ifdef _WIN32
xSF.open(ConvertFuncs::StringToWString(this->fileName).c_str(), std::ofstream::out | std::ofstream::binary);
#else
xSF.open(this->fileName.c_str(), std::ofstream::out | std::ofstream::binary);
#endif
xSF.write(reinterpret_cast<const char *>(&this->rawData[0]), this->rawData.size());
auto allTags = this->tags.GetTags();
if (!allTags.empty())
{
xSF.write("[TAG]", 5);
std::for_each(allTags.begin(), allTags.end(), [&](const std::string &tag)
{
xSF.write(tag.c_str(), tag.length());
xSF.write("\n", 1);
});
}
}