tagparser/vorbis/vorbiscomment.cpp

270 lines
8.9 KiB
C++
Raw Normal View History

2015-09-06 19:57:33 +02:00
#include "./vorbiscomment.h"
#include "./vorbiscommentids.h"
2015-04-22 19:22:01 +02:00
2015-09-06 19:57:33 +02:00
#include "../ogg/oggiterator.h"
2015-04-22 19:22:01 +02:00
#include "../diagnostics.h"
2018-03-07 01:17:50 +01:00
#include "../exceptions.h"
2015-04-22 19:22:01 +02:00
#include <c++utilities/io/binaryreader.h>
#include <c++utilities/io/binarywriter.h>
#include <c++utilities/io/copy.h>
#include <map>
2017-02-05 21:02:40 +01:00
#include <memory>
2015-04-22 19:22:01 +02:00
using namespace std;
using namespace IoUtilities;
using namespace ConversionUtilities;
namespace TagParser {
2015-04-22 19:22:01 +02:00
/*!
* \class TagParser::VorbisComment
* \brief Implementation of TagParser::Tag for Vorbis comments.
2015-04-22 19:22:01 +02:00
*/
const TagValue &VorbisComment::value(KnownField field) const
{
2018-03-07 01:17:50 +01:00
switch (field) {
case KnownField::Vendor:
return vendor();
default:
return FieldMapBasedTag<VorbisComment>::value(field);
}
}
bool VorbisComment::setValue(KnownField field, const TagValue &value)
{
2018-03-07 01:17:50 +01:00
switch (field) {
case KnownField::Vendor:
setVendor(value);
return true;
default:
return FieldMapBasedTag<VorbisComment>::setValue(field, value);
}
}
VorbisComment::IdentifierType VorbisComment::internallyGetFieldId(KnownField field) const
2015-04-22 19:22:01 +02:00
{
using namespace VorbisCommentIds;
2018-03-07 01:17:50 +01:00
switch (field) {
case KnownField::Album:
return album();
case KnownField::Artist:
return artist();
case KnownField::Comment:
return comment();
case KnownField::Cover:
return cover();
case KnownField::Year:
return date();
case KnownField::Title:
return title();
case KnownField::Genre:
return genre();
case KnownField::TrackPosition:
return trackNumber();
case KnownField::DiskPosition:
return diskNumber();
case KnownField::PartNumber:
return partNumber();
case KnownField::Composer:
return composer();
case KnownField::Encoder:
return encoder();
case KnownField::EncoderSettings:
return encoderSettings();
case KnownField::Description:
return description();
case KnownField::Grouping:
return grouping();
2018-03-07 01:17:50 +01:00
case KnownField::RecordLabel:
return label();
case KnownField::Performers:
return performer();
case KnownField::Language:
return language();
case KnownField::Lyricist:
return lyricist();
case KnownField::AlbumArtist:
return albumArtist();
2018-03-07 01:17:50 +01:00
default:
return string();
2015-04-22 19:22:01 +02:00
}
}
KnownField VorbisComment::internallyGetKnownField(const IdentifierType &id) const
2015-04-22 19:22:01 +02:00
{
using namespace VorbisCommentIds;
// clang-format off
static const map<string, KnownField, CaseInsensitiveStringComparer> fieldMap({
{ album(), KnownField::Album },
{ artist(), KnownField::Artist },
{ comment(), KnownField::Comment },
{ cover(), KnownField::Cover },
{ date(), KnownField::Year },
{ title(), KnownField::Title },
{ genre(), KnownField::Genre },
{ trackNumber(), KnownField::TrackPosition },
{ diskNumber(), KnownField::DiskPosition },
{ partNumber(), KnownField::PartNumber },
{ composer(), KnownField::Composer },
{ encoder(), KnownField::Encoder },
{ encoderSettings(), KnownField::EncoderSettings },
{ description(), KnownField::Description },
{ grouping(), KnownField::Grouping },
{ label(), KnownField::RecordLabel },
{ performer(), KnownField::Performers },
{ lyricist(), KnownField::Lyricist },
{ albumArtist(), KnownField::AlbumArtist },
});
// clang-format on
const auto knownField(fieldMap.find(id));
return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
2015-04-22 19:22:01 +02:00
}
/*!
2016-05-16 20:56:53 +02:00
* \brief Internal implementation for parsing.
2015-04-22 19:22:01 +02:00
*/
2018-03-07 01:17:50 +01:00
template <class StreamType> void VorbisComment::internalParse(StreamType &stream, uint64 maxSize, VorbisCommentFlags flags, Diagnostics &diag)
2015-04-22 19:22:01 +02:00
{
// prepare parsing
static const string context("parsing Vorbis comment");
2016-05-16 20:56:53 +02:00
uint64 startOffset = static_cast<uint64>(stream.tellg());
2015-04-22 19:22:01 +02:00
try {
// read signature: 0x3 + "vorbis"
char sig[8];
2016-05-14 00:24:01 +02:00
bool skipSignature = flags & VorbisCommentFlags::NoSignature;
2018-03-07 01:17:50 +01:00
if (!skipSignature) {
2016-05-16 20:56:53 +02:00
CHECK_MAX_SIZE(7);
stream.read(sig, 7);
2016-01-17 19:32:58 +01:00
skipSignature = (ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
}
2018-03-07 01:17:50 +01:00
if (skipSignature) {
2015-04-22 19:22:01 +02:00
// read vendor (length prefixed string)
{
2016-05-16 20:56:53 +02:00
CHECK_MAX_SIZE(4);
stream.read(sig, 4);
2016-05-14 00:24:01 +02:00
const auto vendorSize = LE::toUInt32(sig);
2018-03-07 01:17:50 +01:00
if (vendorSize <= maxSize) {
auto buff = make_unique<char[]>(vendorSize);
2016-05-16 20:56:53 +02:00
stream.read(buff.get(), vendorSize);
2016-05-14 00:24:01 +02:00
m_vendor.assignData(move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
// TODO: Is the vendor string actually UTF-8 (like the field values)?
2016-01-17 19:32:58 +01:00
} else {
diag.emplace_back(DiagLevel::Critical, "Vendor information is truncated.", context);
2016-01-17 19:32:58 +01:00
throw TruncatedDataException();
}
2016-05-16 20:56:53 +02:00
maxSize -= vendorSize;
2015-04-22 19:22:01 +02:00
}
// read field count
2016-05-16 20:56:53 +02:00
CHECK_MAX_SIZE(4);
stream.read(sig, 4);
2015-04-22 19:22:01 +02:00
uint32 fieldCount = LE::toUInt32(sig);
2018-03-07 01:17:50 +01:00
for (uint32 i = 0; i < fieldCount; ++i) {
2015-04-22 19:22:01 +02:00
// read fields
VorbisCommentField field;
2015-04-22 19:22:01 +02:00
try {
field.parse(stream, maxSize, diag);
fields().emplace(field.id(), move(field));
2018-03-07 01:17:50 +01:00
} catch (const TruncatedDataException &) {
2015-04-22 19:22:01 +02:00
throw;
2018-03-07 01:17:50 +01:00
} catch (const Failure &) {
2015-04-22 19:22:01 +02:00
// nothing to do here since notifications will be added anyways
}
}
2018-03-07 01:17:50 +01:00
if (!(flags & VorbisCommentFlags::NoFramingByte)) {
2016-05-16 20:56:53 +02:00
stream.ignore(); // skip framing byte
2016-05-14 00:24:01 +02:00
}
2016-05-16 20:56:53 +02:00
m_size = static_cast<uint32>(static_cast<uint64>(stream.tellg()) - startOffset);
2015-04-22 19:22:01 +02:00
} else {
diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
2015-04-22 19:22:01 +02:00
throw InvalidDataException();
}
2018-03-07 01:17:50 +01:00
} catch (const TruncatedDataException &) {
2016-05-16 20:56:53 +02:00
m_size = static_cast<uint32>(static_cast<uint64>(stream.tellg()) - startOffset);
diag.emplace_back(DiagLevel::Critical, "Vorbis comment is truncated.", context);
2015-04-22 19:22:01 +02:00
throw;
}
}
2016-05-16 20:56:53 +02:00
/*!
* \brief Parses tag information using the specified OGG \a iterator.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing
2016-05-16 20:56:53 +02:00
* error occurs.
*/
void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag)
2016-05-16 20:56:53 +02:00
{
internalParse(iterator, iterator.streamSize(), flags, diag);
2016-05-16 20:56:53 +02:00
}
/*!
* \brief Parses tag information using the specified OGG \a iterator.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing
2016-05-16 20:56:53 +02:00
* error occurs.
*/
void VorbisComment::parse(istream &stream, uint64 maxSize, VorbisCommentFlags flags, Diagnostics &diag)
2016-05-16 20:56:53 +02:00
{
internalParse(stream, maxSize, flags, diag);
2016-05-16 20:56:53 +02:00
}
2015-04-22 19:22:01 +02:00
/*!
* \brief Writes tag information to the specified \a stream.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a making
2015-04-22 19:22:01 +02:00
* error occurs.
*/
void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
2015-04-22 19:22:01 +02:00
{
// prepare making
static const string context("making Vorbis comment");
string vendor;
try {
m_vendor.toString(vendor);
2018-03-07 01:17:50 +01:00
} catch (const ConversionException &) {
diag.emplace_back(DiagLevel::Warning, "Can not convert the assigned vendor to string.", context);
}
2015-04-22 19:22:01 +02:00
BinaryWriter writer(&stream);
2018-03-07 01:17:50 +01:00
if (!(flags & VorbisCommentFlags::NoSignature)) {
2016-01-17 19:32:58 +01:00
// write signature
2018-03-07 01:17:50 +01:00
static const char sig[7] = { 0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 };
2016-01-17 19:32:58 +01:00
stream.write(sig, sizeof(sig));
}
2015-04-22 19:22:01 +02:00
// write vendor
writer.writeUInt32LE(vendor.size());
writer.writeString(vendor);
2016-05-16 20:56:53 +02:00
// write field count later
const auto fieldCountOffset = stream.tellp();
writer.writeUInt32LE(0);
2015-04-22 19:22:01 +02:00
// write fields
2016-05-16 20:56:53 +02:00
uint32 fieldsWritten = 0;
2018-03-07 01:17:50 +01:00
for (auto i : fields()) {
2015-04-22 19:22:01 +02:00
VorbisCommentField &field = i.second;
2018-03-07 01:17:50 +01:00
if (!field.value().isEmpty()) {
2015-08-16 23:39:42 +02:00
try {
2018-03-07 01:17:50 +01:00
if (field.make(writer, flags, diag)) {
2016-05-16 20:56:53 +02:00
++fieldsWritten;
}
2018-03-07 01:17:50 +01:00
} catch (const Failure &) {
2015-08-16 23:39:42 +02:00
}
2015-04-22 19:22:01 +02:00
}
}
2016-05-16 20:56:53 +02:00
// write field count
const auto framingByteOffset = stream.tellp();
stream.seekp(fieldCountOffset);
writer.writeUInt32LE(fieldsWritten);
stream.seekp(framingByteOffset);
2015-04-22 19:22:01 +02:00
// write framing byte
2018-03-07 01:17:50 +01:00
if (!(flags & VorbisCommentFlags::NoFramingByte)) {
2016-05-14 00:24:01 +02:00
stream.put(0x01);
}
2015-04-22 19:22:01 +02:00
}
2018-03-07 01:17:50 +01:00
} // namespace TagParser