Tag Parser  10.0.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
vorbiscommentfield.cpp
Go to the documentation of this file.
1 #include "./vorbiscommentfield.h"
2 #include "./vorbiscommentids.h"
3 
4 #include "../flac/flacmetadata.h"
5 
6 #include "../ogg/oggiterator.h"
7 
8 #include "../id3/id3v2frame.h"
9 
10 #include "../diagnostics.h"
11 #include "../exceptions.h"
12 
13 #include <c++utilities/conversion/binaryconversion.h>
14 #include <c++utilities/conversion/stringbuilder.h>
15 #include <c++utilities/conversion/stringconversion.h>
16 #include <c++utilities/io/binaryreader.h>
17 #include <c++utilities/io/binarywriter.h>
18 
19 #include <iostream>
20 #include <memory>
21 
22 using namespace std;
23 using namespace CppUtilities;
24 
25 namespace TagParser {
26 
35 VorbisCommentField::VorbisCommentField()
36 {
37 }
38 
42 VorbisCommentField::VorbisCommentField(const IdentifierType &id, const TagValue &value)
43  : TagField<VorbisCommentField>(id, value)
44 {
45 }
46 
50 template <class StreamType> void VorbisCommentField::internalParse(StreamType &stream, std::uint64_t &maxSize, Diagnostics &diag)
51 {
52  static const string context("parsing Vorbis comment field");
53  char buff[4];
54  if (maxSize < 4) {
55  diag.emplace_back(DiagLevel::Critical, "Field expected.", context);
56  throw TruncatedDataException();
57  } else {
58  maxSize -= 4;
59  }
60  stream.read(buff, 4);
61  if (const auto size = LE::toUInt32(buff)) { // read size
62  if (size <= maxSize) {
63  maxSize -= size;
64  // read data
65  auto data = make_unique<char[]>(size);
66  stream.read(data.get(), size);
67  std::uint32_t idSize = 0;
68  for (const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize)
69  ;
70  // extract id
71  setId(string(data.get(), idSize));
72  if (!idSize) {
73  // empty field ID
74  diag.emplace_back(DiagLevel::Critical, "The field ID is empty.", context);
75  throw InvalidDataException();
76  } else if (id() == VorbisCommentIds::cover()) {
77  // extract cover value
78  try {
79  auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
80  stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
81  bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
82  bufferStream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
83  FlacMetaDataBlockPicture pictureBlock(value());
84  pictureBlock.parse(bufferStream, decoded.second);
85  setTypeInfo(pictureBlock.pictureType());
86  } catch (const TruncatedDataException &) {
87  diag.emplace_back(DiagLevel::Critical, "METADATA_BLOCK_PICTURE is truncated.", context);
88  throw;
89  } catch (const ConversionException &) {
90  diag.emplace_back(DiagLevel::Critical, "Base64 coding of METADATA_BLOCK_PICTURE is invalid.", context);
91  throw InvalidDataException();
92  } catch (const std::ios_base::failure &failure) {
93  diag.emplace_back(DiagLevel::Critical,
94  argsToString("An IO error occurred when reading the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
95  throw Failure();
96  }
97  } else if (id().size() + 1 < size) {
98  // extract other values (as string)
99  setValue(TagValue(string(data.get() + idSize + 1, size - idSize - 1), TagTextEncoding::Utf8));
100  }
101  } else {
102  diag.emplace_back(DiagLevel::Critical, "Field is truncated.", context);
103  throw TruncatedDataException();
104  }
105  }
106 }
107 
119 {
120  std::uint64_t maxSize = iterator.streamSize() - iterator.currentCharacterOffset();
121  internalParse(iterator, maxSize, diag);
122 }
123 
134 void VorbisCommentField::parse(OggIterator &iterator, std::uint64_t &maxSize, Diagnostics &diag)
135 {
136  internalParse(iterator, maxSize, diag);
137 }
138 
149 void VorbisCommentField::parse(istream &stream, std::uint64_t &maxSize, Diagnostics &diag)
150 {
151  internalParse(stream, maxSize, diag);
152 }
153 
163 bool VorbisCommentField::make(BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
164 {
165  static const string context("making Vorbis comment field");
166  if (id().empty()) {
167  diag.emplace_back(DiagLevel::Critical, "The field ID is empty.", context);
168  }
169  try {
170  // try to convert value to string
171  string valueString;
172  if (id() == VorbisCommentIds::cover()) {
173  if (flags & VorbisCommentFlags::NoCovers) {
174  return false;
175  }
176  // make cover
177  if (value().type() != TagDataType::Picture) {
178  diag.emplace_back(DiagLevel::Critical, "Assigned value of cover field is not picture data.", context);
179  throw InvalidDataException();
180  }
181  try {
182  FlacMetaDataBlockPicture pictureBlock(value());
183  pictureBlock.setPictureType(typeInfo());
184 
185  const auto requiredSize = pictureBlock.requiredSize();
186  auto buffer = make_unique<char[]>(requiredSize);
187  stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
188  bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
189  bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize);
190 
191  pictureBlock.make(bufferStream);
192  valueString = encodeBase64(reinterpret_cast<std::uint8_t *>(buffer.get()), requiredSize);
193  } catch (const Failure &) {
194  diag.emplace_back(DiagLevel::Critical, "Unable to make METADATA_BLOCK_PICTURE struct from the assigned value.", context);
195  throw;
196  } catch (const std::ios_base::failure &failure) {
197  diag.emplace_back(DiagLevel::Critical,
198  argsToString("An IO error occurred when writing the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
199  throw Failure();
200  }
201  } else {
202  // make normal string value
203  valueString = value().toString();
204  }
205  const auto size(valueString.size() + id().size() + 1);
206  if (size > numeric_limits<std::uint32_t>::max()) {
207  diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds the maximum size.", context);
208  throw InvalidDataException();
209  }
210  writer.writeUInt32LE(static_cast<std::uint32_t>(size));
211  writer.writeString(id());
212  writer.writeChar('=');
213  writer.writeString(valueString);
214  } catch (const ConversionException &) {
215  diag.emplace_back(DiagLevel::Critical, "Assigned value can not be converted appropriately.", context);
216  throw InvalidDataException();
217  }
218  return true;
219 }
220 
221 } // namespace TagParser
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The FlacMetaDataBlockPicture class is a FLAC "METADATA_BLOCK_PICTURE" parser and maker.
Definition: flacmetadata.h:248
std::uint32_t requiredSize() const
Returns the number of bytes make() will write.
void make(std::ostream &outputStream)
Makes the FLAC "METADATA_BLOCK_PICTURE".
void setPictureType(std::uint32_t pictureType)
Sets the picture type according to the ID3v2 APIC frame.
Definition: flacmetadata.h:289
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
The OggIterator class helps iterating through all segments of an OGG bitstream.
Definition: oggiterator.h:11
std::uint64_t currentCharacterOffset() const
Returns the offset of the current character in the input stream if the iterator is valid; otherwise a...
Definition: oggiterator.h:211
std::uint64_t streamSize() const
Returns the stream size (which has been specified when constructing the iterator).
Definition: oggiterator.h:114
The TagField class is used by FieldMapBasedTag to store the fields.
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
const TypeInfoType & typeInfo() const
Returns the type info of the current TagField.
void setValue(const TagValue &value)
Sets the value of the current TagField.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition: tagvalue.h:485
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
The VorbisCommentField class is used by VorbisComment to store the fields.
void parse(OggIterator &iterator, Diagnostics &diag)
Parses a field using the specified iterator.
bool make(CppUtilities::BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
Writes the field to a stream using the specified writer.
constexpr TAG_PARSER_EXPORT std::string_view cover()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
VorbisCommentFlags
The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.