Tag Parser  10.1.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/conversion/stringbuilder.h>
10 #include <c++utilities/io/binaryreader.h>
11 #include <c++utilities/io/binarywriter.h>
12 #include <c++utilities/io/copy.h>
13 
14 #include <map>
15 #include <memory>
16 
17 using namespace std;
18 using namespace CppUtilities;
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 std::string(album());
54  case KnownField::Artist:
55  return std::string(artist());
57  return std::string(comment());
58  case KnownField::Cover:
59  return std::string(cover());
60  case KnownField::RecordDate:
61  return std::string(date());
62  case KnownField::Title:
63  return std::string(title());
64  case KnownField::Genre:
65  return std::string(genre());
67  return std::string(trackNumber());
69  return std::string(diskNumber());
70  case KnownField::PartNumber:
71  return std::string(partNumber());
73  return std::string(composer());
75  return std::string(encoder());
76  case KnownField::EncoderSettings:
77  return std::string(encoderSettings());
79  return std::string(description());
81  return std::string(grouping());
83  return std::string(label());
85  return std::string(performer());
86  case KnownField::Language:
87  return std::string(language());
89  return std::string(lyricist());
90  case KnownField::Lyrics:
91  return std::string(lyrics());
93  return std::string(albumArtist());
94  default:
95  return std::string();
96  }
97 }
98 
99 KnownField VorbisComment::internallyGetKnownField(const IdentifierType &id) const
100 {
101  using namespace VorbisCommentIds;
102  // clang-format off
103  static const std::map<std::string_view, KnownField, CaseInsensitiveStringComparer> fieldMap({
104  { album(), KnownField::Album },
105  { artist(), KnownField::Artist },
107  { cover(), KnownField::Cover },
108  { date(), KnownField::RecordDate },
109  { year(), KnownField::RecordDate },
110  { title(), KnownField::Title },
111  { genre(), KnownField::Genre },
114  { partNumber(), KnownField::PartNumber },
117  { encoderSettings(), KnownField::EncoderSettings },
123  { lyrics(), KnownField::Lyrics },
125  });
126  // clang-format on
127  const auto knownField(fieldMap.find(id));
128  return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
129 }
130 
134 template <class StreamType> void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
135 {
136  // prepare parsing
137  static const string context("parsing Vorbis comment");
138  const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
139  try {
140  // read signature: 0x3 + "vorbis"
141  char sig[8];
142  bool skipSignature = flags & VorbisCommentFlags::NoSignature;
143  if (!skipSignature) {
144  CHECK_MAX_SIZE(7)
145  stream.read(sig, 7);
146  skipSignature = (BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
147  }
148  if (skipSignature) {
149  // read vendor (length prefixed string)
150  {
151  CHECK_MAX_SIZE(4)
152  stream.read(sig, 4);
153  const auto vendorSize = LE::toUInt32(sig);
154  if (vendorSize <= maxSize) {
155  auto buff = make_unique<char[]>(vendorSize);
156  stream.read(buff.get(), vendorSize);
157  m_vendor.assignData(move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
158  // TODO: Is the vendor string actually UTF-8 (like the field values)?
159  } else {
160  diag.emplace_back(DiagLevel::Critical, "Vendor information is truncated.", context);
161  throw TruncatedDataException();
162  }
163  maxSize -= vendorSize;
164  }
165  // read field count
166  CHECK_MAX_SIZE(4)
167  stream.read(sig, 4);
168  std::uint32_t fieldCount = LE::toUInt32(sig);
169  for (std::uint32_t i = 0; i < fieldCount; ++i) {
170  // read fields
171  VorbisCommentField field;
172  try {
173  field.parse(stream, maxSize, diag);
174  fields().emplace(field.id(), move(field));
175  } catch (const TruncatedDataException &) {
176  throw;
177  } catch (const Failure &) {
178  // nothing to do here since notifications will be added anyways
179  }
180  }
181  if (!(flags & VorbisCommentFlags::NoFramingByte)) {
182  stream.ignore(); // skip framing byte
183  }
184  m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
185  // turn "YEAR" into "DATE" (unless "DATE" exists)
186  // note: "DATE" is an official field and "YEAR" only an unofficial one but present in some files. In consistency with
187  // MediaInfo and VLC player it is treated like "DATE" here.
188  static const auto dateFieldId = std::string(VorbisCommentIds::date()), yearFieldId = std::string(VorbisCommentIds::year());
189  if (fields().find(dateFieldId) == fields().end()) {
190  const auto [first, end] = fields().equal_range(yearFieldId);
191  for (auto i = first; i != end; ++i) {
192  fields().emplace(dateFieldId, std::move(i->second));
193  }
194  fields().erase(first, end);
195  }
196  } else {
197  diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
198  throw InvalidDataException();
199  }
200  } catch (const TruncatedDataException &) {
201  m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
202  diag.emplace_back(DiagLevel::Critical, "Vorbis comment is truncated.", context);
203  throw;
204  }
205 
206  // warn if there are bytes left in the last segment of the Ogg packet containing the comment
207  if constexpr (std::is_same_v<std::decay_t<StreamType>, OggIterator>) {
208  auto bytesRemaining = std::uint64_t();
209  if (stream) {
210  bytesRemaining = stream.remainingBytesInCurrentSegment();
211  if (stream.currentPage().isLastSegmentUnconcluded()) {
212  stream.nextSegment();
213  if (stream) {
214  bytesRemaining += stream.remainingBytesInCurrentSegment();
215  }
216  }
217  }
218  if (bytesRemaining) {
219  diag.emplace_back(DiagLevel::Warning, argsToString(bytesRemaining, " bytes left in last segment."), context);
220  }
221  }
222 }
223 
231 void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag)
232 {
233  internalParse(iterator, iterator.streamSize(), flags, diag);
234 }
235 
243 void VorbisComment::parse(istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
244 {
245  internalParse(stream, maxSize, flags, diag);
246 }
247 
255 void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
256 {
257  // prepare making
258  static const string context("making Vorbis comment");
259  string vendor;
260  try {
261  m_vendor.toString(vendor);
262  } catch (const ConversionException &) {
263  diag.emplace_back(DiagLevel::Warning, "Can not convert the assigned vendor to string.", context);
264  }
265  BinaryWriter writer(&stream);
266  if (!(flags & VorbisCommentFlags::NoSignature)) {
267  // write signature
268  static const char sig[7] = { 0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 };
269  stream.write(sig, sizeof(sig));
270  }
271  // write vendor
272  writer.writeUInt32LE(static_cast<std::uint32_t>(vendor.size()));
273  writer.writeString(vendor);
274  // write field count later
275  const auto fieldCountOffset = stream.tellp();
276  writer.writeUInt32LE(0);
277  // write fields
278  std::uint32_t fieldsWritten = 0;
279  for (auto i : fields()) {
280  VorbisCommentField &field = i.second;
281  if (!field.value().isEmpty()) {
282  try {
283  if (field.make(writer, flags, diag)) {
284  ++fieldsWritten;
285  }
286  } catch (const Failure &) {
287  }
288  }
289  }
290  // write field count
291  const auto framingByteOffset = stream.tellp();
292  stream.seekp(fieldCountOffset);
293  writer.writeUInt32LE(fieldsWritten);
294  stream.seekp(framingByteOffset);
295  // write framing byte
296  if (!(flags & VorbisCommentFlags::NoFramingByte)) {
297  stream.put(0x01);
298  }
299 }
300 
301 } // 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:117
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.