Tag Parser  10.0.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 CppUtilities;
18 
19 namespace TagParser {
20 
26 const TagValue &VorbisComment::value(KnownField field) const
27 {
28  switch (field) {
29  case KnownField::Vendor:
30  return vendor();
31  default:
33  }
34 }
35 
36 bool VorbisComment::setValue(KnownField field, const TagValue &value)
37 {
38  switch (field) {
39  case KnownField::Vendor:
40  setVendor(value);
41  return true;
42  default:
43  return FieldMapBasedTag<VorbisComment>::setValue(field, value);
44  }
45 }
46 
47 VorbisComment::IdentifierType VorbisComment::internallyGetFieldId(KnownField field) const
48 {
49  using namespace VorbisCommentIds;
50  switch (field) {
51  case KnownField::Album:
52  return std::string(album());
53  case KnownField::Artist:
54  return std::string(artist());
56  return std::string(comment());
57  case KnownField::Cover:
58  return std::string(cover());
59  case KnownField::RecordDate:
60  return std::string(date());
61  case KnownField::Title:
62  return std::string(title());
63  case KnownField::Genre:
64  return std::string(genre());
66  return std::string(trackNumber());
68  return std::string(diskNumber());
69  case KnownField::PartNumber:
70  return std::string(partNumber());
72  return std::string(composer());
74  return std::string(encoder());
75  case KnownField::EncoderSettings:
76  return std::string(encoderSettings());
78  return std::string(description());
80  return std::string(grouping());
82  return std::string(label());
84  return std::string(performer());
85  case KnownField::Language:
86  return std::string(language());
88  return std::string(lyricist());
89  case KnownField::Lyrics:
90  return std::string(lyrics());
92  return std::string(albumArtist());
93  default:
94  return std::string();
95  }
96 }
97 
98 KnownField VorbisComment::internallyGetKnownField(const IdentifierType &id) const
99 {
100  using namespace VorbisCommentIds;
101  // clang-format off
102  static const std::map<std::string_view, KnownField, CaseInsensitiveStringComparer> fieldMap({
103  { album(), KnownField::Album },
104  { artist(), KnownField::Artist },
106  { cover(), KnownField::Cover },
107  { date(), KnownField::RecordDate },
108  { year(), KnownField::RecordDate },
109  { title(), KnownField::Title },
110  { genre(), KnownField::Genre },
113  { partNumber(), KnownField::PartNumber },
116  { encoderSettings(), KnownField::EncoderSettings },
122  { lyrics(), KnownField::Lyrics },
124  });
125  // clang-format on
126  const auto knownField(fieldMap.find(id));
127  return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
128 }
129 
133 template <class StreamType> void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
134 {
135  // prepare parsing
136  static const string context("parsing Vorbis comment");
137  std::uint64_t startOffset = static_cast<std::uint64_t>(stream.tellg());
138  try {
139  // read signature: 0x3 + "vorbis"
140  char sig[8];
141  bool skipSignature = flags & VorbisCommentFlags::NoSignature;
142  if (!skipSignature) {
143  CHECK_MAX_SIZE(7)
144  stream.read(sig, 7);
145  skipSignature = (BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
146  }
147  if (skipSignature) {
148  // read vendor (length prefixed string)
149  {
150  CHECK_MAX_SIZE(4)
151  stream.read(sig, 4);
152  const auto vendorSize = LE::toUInt32(sig);
153  if (vendorSize <= maxSize) {
154  auto buff = make_unique<char[]>(vendorSize);
155  stream.read(buff.get(), vendorSize);
156  m_vendor.assignData(move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
157  // TODO: Is the vendor string actually UTF-8 (like the field values)?
158  } else {
159  diag.emplace_back(DiagLevel::Critical, "Vendor information is truncated.", context);
160  throw TruncatedDataException();
161  }
162  maxSize -= vendorSize;
163  }
164  // read field count
165  CHECK_MAX_SIZE(4)
166  stream.read(sig, 4);
167  std::uint32_t fieldCount = LE::toUInt32(sig);
168  for (std::uint32_t i = 0; i < fieldCount; ++i) {
169  // read fields
170  VorbisCommentField field;
171  try {
172  field.parse(stream, maxSize, diag);
173  fields().emplace(field.id(), move(field));
174  } catch (const TruncatedDataException &) {
175  throw;
176  } catch (const Failure &) {
177  // nothing to do here since notifications will be added anyways
178  }
179  }
180  if (!(flags & VorbisCommentFlags::NoFramingByte)) {
181  stream.ignore(); // skip framing byte
182  }
183  m_size = static_cast<std::uint32_t>(static_cast<std::uint64_t>(stream.tellg()) - startOffset);
184  // turn "YEAR" into "DATE" (unless "DATE" exists)
185  // note: "DATE" is an official field and "YEAR" only an inofficial one but present in some files. In consistency with
186  // MediaInfo and VLC player it is treated like "DATE" here.
187  static const auto dateFieldId = std::string(VorbisCommentIds::date()), yearFieldId = std::string(VorbisCommentIds::year());
188  if (fields().find(dateFieldId) == fields().end()) {
189  const auto [first, end] = fields().equal_range(yearFieldId);
190  for (auto i = first; i != end; ++i) {
191  fields().emplace(dateFieldId, std::move(i->second));
192  }
193  fields().erase(first, end);
194  }
195  } else {
196  diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
197  throw InvalidDataException();
198  }
199  } catch (const TruncatedDataException &) {
200  m_size = static_cast<std::uint32_t>(static_cast<std::uint64_t>(stream.tellg()) - startOffset);
201  diag.emplace_back(DiagLevel::Critical, "Vorbis comment is truncated.", context);
202  throw;
203  }
204 }
205 
213 void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag)
214 {
215  internalParse(iterator, iterator.streamSize(), flags, diag);
216 }
217 
225 void VorbisComment::parse(istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
226 {
227  internalParse(stream, maxSize, flags, diag);
228 }
229 
237 void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
238 {
239  // prepare making
240  static const string context("making Vorbis comment");
241  string vendor;
242  try {
243  m_vendor.toString(vendor);
244  } catch (const ConversionException &) {
245  diag.emplace_back(DiagLevel::Warning, "Can not convert the assigned vendor to string.", context);
246  }
247  BinaryWriter writer(&stream);
248  if (!(flags & VorbisCommentFlags::NoSignature)) {
249  // write signature
250  static const char sig[7] = { 0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 };
251  stream.write(sig, sizeof(sig));
252  }
253  // write vendor
254  writer.writeUInt32LE(static_cast<std::uint32_t>(vendor.size()));
255  writer.writeString(vendor);
256  // write field count later
257  const auto fieldCountOffset = stream.tellp();
258  writer.writeUInt32LE(0);
259  // write fields
260  std::uint32_t fieldsWritten = 0;
261  for (auto i : fields()) {
262  VorbisCommentField &field = i.second;
263  if (!field.value().isEmpty()) {
264  try {
265  if (field.make(writer, flags, diag)) {
266  ++fieldsWritten;
267  }
268  } catch (const Failure &) {
269  }
270  }
271  }
272  // write field count
273  const auto framingByteOffset = stream.tellp();
274  stream.seekp(fieldCountOffset);
275  writer.writeUInt32LE(fieldsWritten);
276  stream.seekp(framingByteOffset);
277  // write framing byte
278  if (!(flags & VorbisCommentFlags::NoFramingByte)) {
279  stream.put(0x01);
280  }
281 }
282 
283 } // 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 FieldMapBasedTag provides a generic implementation of Tag which stores the tag fields using std::...
Definition: fieldbasedtag.h:31
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
std::uint64_t streamSize() const
Returns the stream size (which has been specified when constructing the iterator).
Definition: oggiterator.h:114
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:527
The VorbisCommentField class is used by VorbisComment to store the fields.
bool make(CppUtilities::BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
Writes the field to a stream using the specified writer.
#define CHECK_MAX_SIZE(sizeDenotation)
Throws TruncatedDataException() if the specified sizeDenotation exceeds maxSize; otherwise maxSize is...
Definition: exceptions.h:70
constexpr TAG_PARSER_EXPORT std::string_view album()
Definition: matroskatagid.h:80
constexpr TAG_PARSER_EXPORT std::string_view encoderSettings()
constexpr TAG_PARSER_EXPORT std::string_view description()
constexpr TAG_PARSER_EXPORT std::string_view title()
Definition: matroskatagid.h:38
constexpr TAG_PARSER_EXPORT std::string_view comment()
constexpr TAG_PARSER_EXPORT std::string_view language()
constexpr TAG_PARSER_EXPORT std::string_view composer()
Definition: matroskatagid.h:92
constexpr TAG_PARSER_EXPORT std::string_view partNumber()
Definition: matroskatagid.h:29
constexpr TAG_PARSER_EXPORT std::string_view genre()
constexpr TAG_PARSER_EXPORT std::string_view artist()
Definition: matroskatagid.h:76
constexpr TAG_PARSER_EXPORT std::string_view lyrics()
constexpr TAG_PARSER_EXPORT std::string_view encoder()
constexpr TAG_PARSER_EXPORT std::string_view lyricist()
constexpr TAG_PARSER_EXPORT std::string_view trackNumber()
constexpr TAG_PARSER_EXPORT std::string_view performer()
constexpr TAG_PARSER_EXPORT std::string_view year()
constexpr TAG_PARSER_EXPORT std::string_view albumArtist()
constexpr TAG_PARSER_EXPORT std::string_view diskNumber()
constexpr TAG_PARSER_EXPORT std::string_view cover()
constexpr TAG_PARSER_EXPORT std::string_view grouping()
constexpr TAG_PARSER_EXPORT std::string_view date()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
KnownField
Specifies the field.
Definition: tag.h:42
VorbisCommentFlags
The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.