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