Tag Parser 12.4.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
vorbiscomment.cpp
Go to the documentation of this file.
1#include "./vorbiscomment.h"
3
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
17using namespace std;
18using namespace CppUtilities;
19
20namespace TagParser {
21
28{
29 switch (field) {
31 return vendor();
32 default:
34 }
35}
36
38{
39 switch (field) {
42 return true;
43 default:
45 }
46}
47
49{
50 using namespace VorbisCommentIds;
51 switch (field) {
53 return std::string(album());
55 return std::string(artist());
57 return std::string(comment());
59 return std::string(cover());
61 return std::string(date());
63 return std::string(title());
65 return std::string(genre());
67 return std::string(trackNumber());
69 return std::string(diskNumber());
71 return std::string(partNumber());
73 return std::string(composer());
75 return std::string(encoder());
77 return std::string(encodedBy());
79 return std::string(encoderSettings());
81 return std::string(description());
83 return std::string(grouping());
85 return std::string(label());
87 return std::string(performer());
89 return std::string(language());
91 return std::string(lyricist());
93 return std::string(lyrics());
95 return std::string(albumArtist());
97 return std::string(conductor());
99 return std::string(copyright());
101 return std::string(license());
103 return std::string(director());
104 case KnownField::ISRC:
105 return std::string(isrc());
107 return std::string(rating());
108 case KnownField::Bpm:
109 return std::string(bpm());
111 return std::string(publisher());
113 return std::string(publisherWebpage());
115 return std::string(website());
117 return std::string(arranger());
118 default:
119 return std::string();
120 }
121}
122
124{
125 using namespace VorbisCommentIds;
126 // clang-format off
127 static const std::map<std::string_view, KnownField, CaseInsensitiveStringComparer> fieldMap({
128 { album(), KnownField::Album },
129 { artist(), KnownField::Artist },
130 { comment(), KnownField::Comment },
131 { cover(), KnownField::Cover },
132 { date(), KnownField::RecordDate },
133 { year(), KnownField::RecordDate },
134 { title(), KnownField::Title },
135 { genre(), KnownField::Genre },
136 { trackNumber(), KnownField::TrackPosition },
137 { diskNumber(), KnownField::DiskPosition },
138 { partNumber(), KnownField::PartNumber },
139 { composer(), KnownField::Composer },
140 { encoder(), KnownField::Encoder },
141 { encodedBy(), KnownField::EncodedBy },
142 { encoderSettings(), KnownField::EncoderSettings },
143 { description(), KnownField::Description },
144 { grouping(), KnownField::Grouping },
145 { label(), KnownField::RecordLabel },
146 { performer(), KnownField::Performers },
147 { lyricist(), KnownField::Lyricist },
148 { lyrics(), KnownField::Lyrics },
149 { albumArtist(), KnownField::AlbumArtist },
150 { conductor(), KnownField::Conductor },
151 { copyright(), KnownField::Copyright },
152 { license(), KnownField::License },
153 { director(), KnownField::Director },
154 { isrc(), KnownField::ISRC },
155 { rating(), KnownField::Rating },
156 { bpm(), KnownField::Bpm },
157 { publisher(), KnownField::Publisher },
158 { publisherWebpage(), KnownField::PublisherWebpage },
159 { website(), KnownField::PerformerWebpage },
160 { arranger(), KnownField::Arranger },
161 });
162 // clang-format on
163 const auto knownField(fieldMap.find(id));
164 return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
165}
166
168void VorbisComment::extendPositionInSetField(std::string_view field, std::string_view totalField, const std::string &diagContext, Diagnostics &diag)
169{
170 auto totalValues = std::vector<std::int32_t>();
171 auto fieldsIter = fields().equal_range(std::string(totalField));
172 auto fieldsDist = std::distance(fieldsIter.first, fieldsIter.second);
173 if (!fieldsDist) {
174 return;
175 }
176 totalValues.reserve(static_cast<std::size_t>(fieldsDist));
177 for (; fieldsIter.first != fieldsIter.second;) {
178 try {
179 totalValues.emplace_back(fieldsIter.first->second.value().toInteger());
180 fields().erase(fieldsIter.first++);
181 } catch (const ConversionException &e) {
182 diag.emplace_back(DiagLevel::Warning, argsToString("Unable to parse \"", totalField, "\" as integer: ", e.what()), diagContext);
183 totalValues.emplace_back(0);
184 ++fieldsIter.first;
185 }
186 }
187
188 auto totalIter = totalValues.begin(), totalEnd = totalValues.end();
189 for (fieldsIter = fields().equal_range(std::string(field)); fieldsIter.first != fieldsIter.second && totalIter != totalEnd;
190 ++fieldsIter.first, ++totalIter) {
191 auto &v = fieldsIter.first->second.value();
192 try {
193 auto p = v.toPositionInSet();
194 if (p.total() && p.total() != *totalIter) {
195 diag.emplace_back(DiagLevel::Warning,
196 argsToString("The \"", totalField, "\" field value (", *totalIter, ") does not match \"", field, "\" field value (", p.total(),
197 "). Discarding the former in favor of the latter."),
198 diagContext);
199 } else {
200 p.setTotal(*totalIter);
201 v.assignPosition(p);
202 }
203 } catch (const ConversionException &e) {
204 diag.emplace_back(DiagLevel::Warning,
205 argsToString("Unable to parse \"", field, "\" as position in set for incorporating \"", totalField, "\": ", e.what()), diagContext);
206 }
207 }
208 if (totalIter != totalEnd) {
209 diag.emplace_back(
210 DiagLevel::Warning, argsToString("Vorbis Comment contains more \"", totalField, "\" fields than \"", field, "\" fields."), diagContext);
211 }
212 for (; totalIter != totalEnd; ++totalIter) {
213 fields().insert(std::make_pair(field, VorbisCommentField(std::string(field), TagValue(PositionInSet(0, *totalIter)))));
214 }
215}
217
221void VorbisComment::convertTotalFields(const std::string &diagContext, Diagnostics &diag)
222{
223 extendPositionInSetField(VorbisCommentIds::trackNumber(), VorbisCommentIds::trackTotal(), diagContext, diag);
224 extendPositionInSetField(VorbisCommentIds::diskNumber(), VorbisCommentIds::diskTotal(), diagContext, diag);
225 extendPositionInSetField(VorbisCommentIds::partNumber(), VorbisCommentIds::partTotal(), diagContext, diag);
226}
227
231template <class StreamType>
232void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag)
233{
234 // prepare parsing
235 static const string context("parsing Vorbis comment");
236 const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
237 try {
238 // read signature: 0x3 + "vorbis"
239 char sig[8];
240 bool skipSignature = flags & VorbisCommentFlags::NoSignature;
241 if (!skipSignature) {
243 stream.read(sig, 7);
244 skipSignature = (BE::toInt<std::uint64_t>(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
245 }
246 if (skipSignature) {
247 // read vendor (length prefixed string)
248 {
250 stream.read(sig, 4);
251 const auto vendorSize = LE::toUInt32(sig);
252 if (vendorSize <= maxSize) {
253 auto buff = make_unique<char[]>(vendorSize);
254 stream.read(buff.get(), vendorSize);
255 m_vendor.assignData(std::move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
256 // TODO: Is the vendor string actually UTF-8 (like the field values)?
257 } else {
258 diag.emplace_back(DiagLevel::Critical, "Vendor information is truncated.", context);
259 throw TruncatedDataException();
260 }
261 maxSize -= vendorSize;
262 }
263 // read field count
265 stream.read(sig, 4);
266 std::uint32_t fieldCount = LE::toUInt32(sig);
267 for (std::uint32_t i = 0; i < fieldCount; ++i) {
268 // read fields
269 VorbisCommentField field;
270 try {
271 field.parse(stream, maxSize, diag);
272 fields().emplace(field.id(), std::move(field));
273 } catch (const TruncatedDataException &) {
274 throw;
275 } catch (const Failure &) {
276 // nothing to do here since notifications will be added anyways
277 }
278 }
279 if (!(flags & VorbisCommentFlags::NoFramingByte)) {
280 stream.ignore(); // skip framing byte
281 }
282 m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
283 // turn "YEAR" into "DATE" (unless "DATE" exists)
284 // note: "DATE" is an official field and "YEAR" only an unofficial one but present in some files. In consistency with
285 // MediaInfo and VLC player it is treated like "DATE" here.
286 static const auto dateFieldId = std::string(VorbisCommentIds::date()), yearFieldId = std::string(VorbisCommentIds::year());
287 if (fields().find(dateFieldId) == fields().end()) {
288 const auto [first, end] = fields().equal_range(yearFieldId);
289 for (auto i = first; i != end; ++i) {
290 fields().emplace(dateFieldId, std::move(i->second));
291 }
292 fields().erase(first, end);
293 }
294 } else {
295 diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
296 throw InvalidDataException();
297 }
298 } catch (const TruncatedDataException &) {
299 m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
300 diag.emplace_back(DiagLevel::Critical, "Vorbis comment is truncated.", context);
301 throw;
302 }
303
304 // warn if there are bytes left in the last segment of the Ogg packet containing the comment
305 if constexpr (std::is_same_v<std::decay_t<StreamType>, OggIterator>) {
306 auto bytesRemaining = std::uint64_t();
307 if (stream) {
308 bytesRemaining = stream.remainingBytesInCurrentSegment();
309 if (stream.currentPage().isLastSegmentUnconcluded()) {
310 stream.nextSegment();
311 if (stream) {
312 bytesRemaining += stream.remainingBytesInCurrentSegment();
313 }
314 }
315 }
316 if (bytesRemaining) {
317 diag.emplace_back(DiagLevel::Information, argsToString(bytesRemaining, " bytes left in last segment."), context);
318 padding += bytesRemaining;
319 }
320 }
321
323 convertTotalFields(context, diag);
324 }
325}
326
335{
336 auto padding = std::uint64_t();
337 internalParse(iterator, iterator.streamSize(), flags, padding, diag);
338}
339
347void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag)
348{
349 internalParse(iterator, iterator.streamSize(), flags, padding, diag);
350}
351
359void VorbisComment::parse(istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
360{
361 auto padding = std::uint64_t();
362 internalParse(stream, maxSize, flags, padding, diag);
363}
364
366static std::uint32_t makeField(VorbisCommentField &field, BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
367{
368 if (field.value().isEmpty()) {
369 return 0;
370 }
371 try {
372 if (field.make(writer, flags, diag)) {
373 return 1;
374 }
375 } catch (const Failure &) {
376 }
377 return 0;
378}
380
388void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
389{
390 // prepare making
391 static const string context("making Vorbis comment");
392 string vendor;
393 try {
394 m_vendor.toString(vendor);
395 } catch (const ConversionException &) {
396 diag.emplace_back(DiagLevel::Warning, "Can not convert the assigned vendor to string.", context);
397 }
398 BinaryWriter writer(&stream);
399 if (!(flags & VorbisCommentFlags::NoSignature)) {
400 // write signature
401 static const char sig[7] = { 0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 };
402 stream.write(sig, sizeof(sig));
403 }
404 // write vendor
405 writer.writeUInt32LE(static_cast<std::uint32_t>(vendor.size()));
406 writer.writeString(vendor);
407 // write field count later
408 const auto fieldCountOffset = stream.tellp();
409 writer.writeUInt32LE(0);
410 // write fields
411 std::uint32_t fieldsWritten = 0;
412 static const auto coverId = std::string(VorbisCommentIds::cover());
413 for (auto &i : fields()) {
414 if (i.first != coverId) { // write cover at the end
415 fieldsWritten += makeField(i.second, writer, flags, diag);
416 }
417 }
418 if (const auto cover = fields().find(coverId); cover != fields().end()) {
419 fieldsWritten += makeField(cover->second, writer, flags, diag);
420 }
421 // write field count
422 const auto framingByteOffset = stream.tellp();
423 stream.seekp(fieldCountOffset);
424 writer.writeUInt32LE(fieldsWritten);
425 stream.seekp(framingByteOffset);
426 // write framing byte
427 if (!(flags & VorbisCommentFlags::NoFramingByte)) {
428 stream.put(0x01);
429 }
430}
431
432} // namespace TagParser
The Diagnostics class is a container for DiagMessage.
bool setValue(const IdentifierType &id, const TagValue &value)
Assigns the given value to the field with the specified id.
typename FieldMapBasedTagTraits< VorbisComment >::FieldType::IdentifierType IdentifierType
const TagValue & value(const IdentifierType &id) const
Returns the value of the field with the specified id.
const std::multimap< IdentifierType, FieldType, Compare > & fields() const
KnownField knownField(const IdentifierType &id) const
The OggIterator class helps iterating through all segments of an Ogg bitstream.
std::uint64_t streamSize() const
Returns the stream size (which has been specified when constructing the iterator).
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
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:450
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition tagvalue.h:490
std::uint64_t m_size
Definition tag.h:217
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.
void make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
Writes tag information to the specified stream.
const TagValue & vendor() const
Returns the vendor.
void parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag)
Parses tag information using the specified Ogg iterator.
IdentifierType internallyGetFieldId(KnownField field) const
void setVendor(const TagValue &vendor)
Sets the vendor.
const TagValue & value(KnownField field) const override
Returns the value of the specified field.
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
KnownField internallyGetKnownField(const IdentifierType &id) const
#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 trackNumber()
constexpr TAG_PARSER_EXPORT std::string_view diskTotal()
constexpr TAG_PARSER_EXPORT std::string_view year()
constexpr TAG_PARSER_EXPORT std::string_view partTotal()
constexpr TAG_PARSER_EXPORT std::string_view partNumber()
constexpr TAG_PARSER_EXPORT std::string_view trackTotal()
constexpr TAG_PARSER_EXPORT std::string_view diskNumber()
constexpr TAG_PARSER_EXPORT std::string_view cover()
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:37
VorbisCommentFlags
The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.