Tag Parser  7.0.3
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 (...) {
195  catchIoFailure();
196  diag.emplace_back(DiagLevel::Critical, "An IO error occured when writing the METADATA_BLOCK_PICTURE struct.", context);
197  throw Failure();
198  }
199  } else {
200  // make normal string value
201  valueString = value().toString();
202  }
203  writer.writeUInt32LE(id().size() + 1 + valueString.size());
204  writer.writeString(id());
205  writer.writeChar('=');
206  writer.writeString(valueString);
207  } catch (const ConversionException &) {
208  diag.emplace_back(DiagLevel::Critical, "Assigned value can not be converted appropriately.", context);
209  throw InvalidDataException();
210  }
211  return true;
212 }
213 
214 } // namespace TagParser
void make(std::ostream &outputStream)
Makes the FLAC "METADATA_BLOCK_PICTURE".
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...
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.
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:343
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
TagValue & value()
Returns the value of the current TagField.