Tag Parser  8.2.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
vorbiscomment.cpp
Go to the documentation of this file.
1 #include "./vorbiscomment.h"
2 #include "./vorbiscommentids.h"
3 
4 #include "../ogg/oggiterator.h"
5 
6 #include "../diagnostics.h"
7 #include "../exceptions.h"
8 
9 #include <c++utilities/io/binaryreader.h>
10 #include <c++utilities/io/binarywriter.h>
11 #include <c++utilities/io/copy.h>
12 
13 #include <map>
14 #include <memory>
15 
16 using namespace std;
17 using namespace IoUtilities;
18 using namespace ConversionUtilities;
19 
20 namespace TagParser {
21 
27 const TagValue &VorbisComment::value(KnownField field) const
28 {
29  switch (field) {
30  case KnownField::Vendor:
31  return vendor();
32  default:
34  }
35 }
36 
37 bool VorbisComment::setValue(KnownField field, const TagValue &value)
38 {
39  switch (field) {
40  case KnownField::Vendor:
41  setVendor(value);
42  return true;
43  default:
44  return FieldMapBasedTag<VorbisComment>::setValue(field, value);
45  }
46 }
47 
48 VorbisComment::IdentifierType VorbisComment::internallyGetFieldId(KnownField field) const
49 {
50  using namespace VorbisCommentIds;
51  switch (field) {
52  case KnownField::Album:
53  return album();
54  case KnownField::Artist:
55  return artist();
57  return comment();
58  case KnownField::Cover:
59  return cover();
60  case KnownField::Year:
61  return date();
62  case KnownField::Title:
63  return title();
64  case KnownField::Genre:
65  return genre();
67  return trackNumber();
69  return diskNumber();
70  case KnownField::PartNumber:
71  return partNumber();
73  return composer();
75  return encoder();
76  case KnownField::EncoderSettings:
77  return encoderSettings();
79  return description();
81  return grouping();
83  return label();
85  return performer();
86  case KnownField::Language:
87  return language();
89  return lyricist();
91  return albumArtist();
92  default:
93  return string();
94  }
95 }
96 
97 KnownField VorbisComment::internallyGetKnownField(const IdentifierType &id) const
98 {
99  using namespace VorbisCommentIds;
100  // clang-format off
101  static const map<string, KnownField, CaseInsensitiveStringComparer> fieldMap({
102  { album(), KnownField::Album },
103  { artist(), KnownField::Artist },
105  { cover(), KnownField::Cover },
106  { date(), KnownField::Year },
107  { title(), KnownField::Title },
108  { genre(), KnownField::Genre },
111  { partNumber(), KnownField::PartNumber },
114  { encoderSettings(), KnownField::EncoderSettings },
121  });
122  // clang-format on
123  const auto knownField(fieldMap.find(id));
124  return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
125 }
126 
130 template <class StreamType> void VorbisComment::internalParse(StreamType &stream, uint64 maxSize, VorbisCommentFlags flags, Diagnostics &diag)
131 {
132  // prepare parsing
133  static const string context("parsing Vorbis comment");
134  uint64 startOffset = static_cast<uint64>(stream.tellg());
135  try {
136  // read signature: 0x3 + "vorbis"
137  char sig[8];
138  bool skipSignature = flags & VorbisCommentFlags::NoSignature;
139  if (!skipSignature) {
140  CHECK_MAX_SIZE(7);
141  stream.read(sig, 7);
142  skipSignature = (ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
143  }
144  if (skipSignature) {
145  // read vendor (length prefixed string)
146  {
147  CHECK_MAX_SIZE(4);
148  stream.read(sig, 4);
149  const auto vendorSize = LE::toUInt32(sig);
150  if (vendorSize <= maxSize) {
151  auto buff = make_unique<char[]>(vendorSize);
152  stream.read(buff.get(), vendorSize);
153  m_vendor.assignData(move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
154  // TODO: Is the vendor string actually UTF-8 (like the field values)?
155  } else {
156  diag.emplace_back(DiagLevel::Critical, "Vendor information is truncated.", context);
157  throw TruncatedDataException();
158  }
159  maxSize -= vendorSize;
160  }
161  // read field count
162  CHECK_MAX_SIZE(4);
163  stream.read(sig, 4);
164  uint32 fieldCount = LE::toUInt32(sig);
165  for (uint32 i = 0; i < fieldCount; ++i) {
166  // read fields
167  VorbisCommentField field;
168  try {
169  field.parse(stream, maxSize, diag);
170  fields().emplace(field.id(), move(field));
171  } catch (const TruncatedDataException &) {
172  throw;
173  } catch (const Failure &) {
174  // nothing to do here since notifications will be added anyways
175  }
176  }
177  if (!(flags & VorbisCommentFlags::NoFramingByte)) {
178  stream.ignore(); // skip framing byte
179  }
180  m_size = static_cast<uint32>(static_cast<uint64>(stream.tellg()) - startOffset);
181  } else {
182  diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
183  throw InvalidDataException();
184  }
185  } catch (const TruncatedDataException &) {
186  m_size = static_cast<uint32>(static_cast<uint64>(stream.tellg()) - startOffset);
187  diag.emplace_back(DiagLevel::Critical, "Vorbis comment is truncated.", context);
188  throw;
189  }
190 }
191 
199 void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag)
200 {
201  internalParse(iterator, iterator.streamSize(), flags, diag);
202 }
203 
211 void VorbisComment::parse(istream &stream, uint64 maxSize, VorbisCommentFlags flags, Diagnostics &diag)
212 {
213  internalParse(stream, maxSize, flags, diag);
214 }
215 
223 void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
224 {
225  // prepare making
226  static const string context("making Vorbis comment");
227  string vendor;
228  try {
229  m_vendor.toString(vendor);
230  } catch (const ConversionException &) {
231  diag.emplace_back(DiagLevel::Warning, "Can not convert the assigned vendor to string.", context);
232  }
233  BinaryWriter writer(&stream);
234  if (!(flags & VorbisCommentFlags::NoSignature)) {
235  // write signature
236  static const char sig[7] = { 0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 };
237  stream.write(sig, sizeof(sig));
238  }
239  // write vendor
240  writer.writeUInt32LE(vendor.size());
241  writer.writeString(vendor);
242  // write field count later
243  const auto fieldCountOffset = stream.tellp();
244  writer.writeUInt32LE(0);
245  // write fields
246  uint32 fieldsWritten = 0;
247  for (auto i : fields()) {
248  VorbisCommentField &field = i.second;
249  if (!field.value().isEmpty()) {
250  try {
251  if (field.make(writer, flags, diag)) {
252  ++fieldsWritten;
253  }
254  } catch (const Failure &) {
255  }
256  }
257  }
258  // write field count
259  const auto framingByteOffset = stream.tellp();
260  stream.seekp(fieldCountOffset);
261  writer.writeUInt32LE(fieldsWritten);
262  stream.seekp(framingByteOffset);
263  // write framing byte
264  if (!(flags & VorbisCommentFlags::NoFramingByte)) {
265  stream.put(0x01);
266  }
267 }
268 
269 } // namespace TagParser
constexpr TAG_PARSER_EXPORT const char * encoder()
The FieldMapBasedTag provides a generic implementation of Tag which stores the tag fields using std::...
Definition: fieldbasedtag.h:31
uint64 streamSize() const
Returns the stream size (which has been specified when constructing the iterator).
Definition: oggiterator.h:114
VorbisCommentFlags
The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.
constexpr TAG_PARSER_EXPORT const char * performer()
constexpr TAG_PARSER_EXPORT const char * date()
typename FieldMapBasedTagTraits< VorbisComment >::FieldType::IdentifierType IdentifierType
Definition: fieldbasedtag.h:36
The OggIterator class helps iterating through all segments of an OGG bitstream.
Definition: oggiterator.h:11
constexpr TAG_PARSER_EXPORT const char * lyricist()
constexpr TAG_PARSER_EXPORT const char * grouping()
constexpr TAG_PARSER_EXPORT const char * trackNumber()
bool make(IoUtilities::BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
Writes the field to a stream using the specified writer.
KnownField
Specifies the field.
Definition: tag.h:42
constexpr TAG_PARSER_EXPORT const char * language()
constexpr TAG_PARSER_EXPORT const char * encoderSettings()
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:389
constexpr TAG_PARSER_EXPORT const char * cover()
constexpr TAG_PARSER_EXPORT const char * comment()
Contains utility classes helping to read and write streams.
constexpr TAG_PARSER_EXPORT const char * partNumber()
Definition: matroskatagid.h:30
constexpr TAG_PARSER_EXPORT const char * album()
Definition: matroskatagid.h:81
constexpr TAG_PARSER_EXPORT const char * title()
Definition: matroskatagid.h:39
constexpr TAG_PARSER_EXPORT const char * genre()
constexpr TAG_PARSER_EXPORT const char * diskNumber()
constexpr TAG_PARSER_EXPORT const char * albumArtist()
constexpr TAG_PARSER_EXPORT const char * artist()
Definition: matroskatagid.h:77
constexpr TAG_PARSER_EXPORT const char * composer()
Definition: matroskatagid.h:93
The VorbisCommentField class is used by VorbisComment to store the fields.
The TagValue class wraps values of different types.
Definition: tagvalue.h:65
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
constexpr TAG_PARSER_EXPORT const char * description()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagValue & value()
Returns the value of the current TagField.
#define CHECK_MAX_SIZE(sizeDenotation)
Throws TruncatedDataException() if the specified sizeDenotation exceeds maxSize; otherwise maxSize is...
Definition: exceptions.h:70