Tag Parser 12.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
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());
114 default:
115 return std::string();
116 }
117}
118
120{
121 using namespace VorbisCommentIds;
122 // clang-format off
123 static const std::map<std::string_view, KnownField, CaseInsensitiveStringComparer> fieldMap({
124 { album(), KnownField::Album },
125 { artist(), KnownField::Artist },
126 { comment(), KnownField::Comment },
127 { cover(), KnownField::Cover },
128 { date(), KnownField::RecordDate },
129 { year(), KnownField::RecordDate },
130 { title(), KnownField::Title },
131 { genre(), KnownField::Genre },
132 { trackNumber(), KnownField::TrackPosition },
133 { diskNumber(), KnownField::DiskPosition },
134 { partNumber(), KnownField::PartNumber },
135 { composer(), KnownField::Composer },
136 { encoder(), KnownField::Encoder },
137 { encodedBy(), KnownField::EncodedBy },
138 { encoderSettings(), KnownField::EncoderSettings },
139 { description(), KnownField::Description },
140 { grouping(), KnownField::Grouping },
141 { label(), KnownField::RecordLabel },
142 { performer(), KnownField::Performers },
143 { lyricist(), KnownField::Lyricist },
144 { lyrics(), KnownField::Lyrics },
145 { albumArtist(), KnownField::AlbumArtist },
146 { conductor(), KnownField::Conductor },
147 { copyright(), KnownField::Copyright },
148 { license(), KnownField::License },
149 { director(), KnownField::Director },
150 { isrc(), KnownField::ISRC },
151 { rating(), KnownField::Rating },
152 { bpm(), KnownField::Bpm },
153 { publisher(), KnownField::Publisher },
154 { publisherWebpage(), KnownField::PublisherWebpage },
155 });
156 // clang-format on
157 const auto knownField(fieldMap.find(id));
158 return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
159}
160
162void VorbisComment::extendPositionInSetField(std::string_view field, std::string_view totalField, const std::string &diagContext, Diagnostics &diag)
163{
164 auto totalValues = std::vector<std::int32_t>();
165 auto fieldsIter = fields().equal_range(std::string(totalField));
166 auto fieldsDist = std::distance(fieldsIter.first, fieldsIter.second);
167 if (!fieldsDist) {
168 return;
169 }
170 totalValues.reserve(static_cast<std::size_t>(fieldsDist));
171 for (; fieldsIter.first != fieldsIter.second;) {
172 try {
173 totalValues.emplace_back(fieldsIter.first->second.value().toInteger());
174 fields().erase(fieldsIter.first++);
175 } catch (const ConversionException &e) {
176 diag.emplace_back(DiagLevel::Warning, argsToString("Unable to parse \"", totalField, "\" as integer: ", e.what()), diagContext);
177 totalValues.emplace_back(0);
178 ++fieldsIter.first;
179 }
180 }
181
182 auto totalIter = totalValues.begin(), totalEnd = totalValues.end();
183 for (fieldsIter = fields().equal_range(std::string(field)); fieldsIter.first != fieldsIter.second && totalIter != totalEnd;
184 ++fieldsIter.first, ++totalIter) {
185 auto &v = fieldsIter.first->second.value();
186 try {
187 auto p = v.toPositionInSet();
188 if (p.total() && p.total() != *totalIter) {
189 diag.emplace_back(DiagLevel::Warning,
190 argsToString("The \"", totalField, "\" field value (", *totalIter, ") does not match \"", field, "\" field value (", p.total(),
191 "). Discarding the former in favor of the latter."),
192 diagContext);
193 } else {
194 p.setTotal(*totalIter);
195 v.assignPosition(p);
196 }
197 } catch (const ConversionException &e) {
198 diag.emplace_back(DiagLevel::Warning,
199 argsToString("Unable to parse \"", field, "\" as position in set for incorporating \"", totalField, "\": ", e.what()), diagContext);
200 }
201 }
202 if (totalIter != totalEnd) {
203 diag.emplace_back(
204 DiagLevel::Warning, argsToString("Vorbis Comment contains more \"", totalField, "\" fields than \"", field, "\" fields."), diagContext);
205 }
206 for (; totalIter != totalEnd; ++totalIter) {
207 fields().insert(std::make_pair(field, VorbisCommentField(std::string(field), TagValue(PositionInSet(0, *totalIter)))));
208 }
209}
211
215void VorbisComment::convertTotalFields(const std::string &diagContext, Diagnostics &diag)
216{
217 extendPositionInSetField(VorbisCommentIds::trackNumber(), VorbisCommentIds::trackTotal(), diagContext, diag);
218 extendPositionInSetField(VorbisCommentIds::diskNumber(), VorbisCommentIds::diskTotal(), diagContext, diag);
219 extendPositionInSetField(VorbisCommentIds::partNumber(), VorbisCommentIds::partTotal(), diagContext, diag);
220}
221
225template <class StreamType>
226void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag)
227{
228 // prepare parsing
229 static const string context("parsing Vorbis comment");
230 const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
231 try {
232 // read signature: 0x3 + "vorbis"
233 char sig[8];
234 bool skipSignature = flags & VorbisCommentFlags::NoSignature;
235 if (!skipSignature) {
237 stream.read(sig, 7);
238 skipSignature = (BE::toInt<std::uint64_t>(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
239 }
240 if (skipSignature) {
241 // read vendor (length prefixed string)
242 {
244 stream.read(sig, 4);
245 const auto vendorSize = LE::toUInt32(sig);
246 if (vendorSize <= maxSize) {
247 auto buff = make_unique<char[]>(vendorSize);
248 stream.read(buff.get(), vendorSize);
249 m_vendor.assignData(std::move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
250 // TODO: Is the vendor string actually UTF-8 (like the field values)?
251 } else {
252 diag.emplace_back(DiagLevel::Critical, "Vendor information is truncated.", context);
253 throw TruncatedDataException();
254 }
255 maxSize -= vendorSize;
256 }
257 // read field count
259 stream.read(sig, 4);
260 std::uint32_t fieldCount = LE::toUInt32(sig);
261 for (std::uint32_t i = 0; i < fieldCount; ++i) {
262 // read fields
263 VorbisCommentField field;
264 try {
265 field.parse(stream, maxSize, diag);
266 fields().emplace(field.id(), std::move(field));
267 } catch (const TruncatedDataException &) {
268 throw;
269 } catch (const Failure &) {
270 // nothing to do here since notifications will be added anyways
271 }
272 }
273 if (!(flags & VorbisCommentFlags::NoFramingByte)) {
274 stream.ignore(); // skip framing byte
275 }
276 m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
277 // turn "YEAR" into "DATE" (unless "DATE" exists)
278 // note: "DATE" is an official field and "YEAR" only an unofficial one but present in some files. In consistency with
279 // MediaInfo and VLC player it is treated like "DATE" here.
280 static const auto dateFieldId = std::string(VorbisCommentIds::date()), yearFieldId = std::string(VorbisCommentIds::year());
281 if (fields().find(dateFieldId) == fields().end()) {
282 const auto [first, end] = fields().equal_range(yearFieldId);
283 for (auto i = first; i != end; ++i) {
284 fields().emplace(dateFieldId, std::move(i->second));
285 }
286 fields().erase(first, end);
287 }
288 } else {
289 diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
290 throw InvalidDataException();
291 }
292 } catch (const TruncatedDataException &) {
293 m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
294 diag.emplace_back(DiagLevel::Critical, "Vorbis comment is truncated.", context);
295 throw;
296 }
297
298 // warn if there are bytes left in the last segment of the Ogg packet containing the comment
299 if constexpr (std::is_same_v<std::decay_t<StreamType>, OggIterator>) {
300 auto bytesRemaining = std::uint64_t();
301 if (stream) {
302 bytesRemaining = stream.remainingBytesInCurrentSegment();
303 if (stream.currentPage().isLastSegmentUnconcluded()) {
304 stream.nextSegment();
305 if (stream) {
306 bytesRemaining += stream.remainingBytesInCurrentSegment();
307 }
308 }
309 }
310 if (bytesRemaining) {
311 diag.emplace_back(DiagLevel::Information, argsToString(bytesRemaining, " bytes left in last segment."), context);
312 padding += bytesRemaining;
313 }
314 }
315
317 convertTotalFields(context, diag);
318 }
319}
320
329{
330 auto padding = std::uint64_t();
331 internalParse(iterator, iterator.streamSize(), flags, padding, diag);
332}
333
341void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag)
342{
343 internalParse(iterator, iterator.streamSize(), flags, padding, diag);
344}
345
353void VorbisComment::parse(istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
354{
355 auto padding = std::uint64_t();
356 internalParse(stream, maxSize, flags, padding, diag);
357}
358
360static std::uint32_t makeField(VorbisCommentField &field, BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
361{
362 if (field.value().isEmpty()) {
363 return 0;
364 }
365 try {
366 if (field.make(writer, flags, diag)) {
367 return 1;
368 }
369 } catch (const Failure &) {
370 }
371 return 0;
372}
374
382void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
383{
384 // prepare making
385 static const string context("making Vorbis comment");
386 string vendor;
387 try {
388 m_vendor.toString(vendor);
389 } catch (const ConversionException &) {
390 diag.emplace_back(DiagLevel::Warning, "Can not convert the assigned vendor to string.", context);
391 }
392 BinaryWriter writer(&stream);
393 if (!(flags & VorbisCommentFlags::NoSignature)) {
394 // write signature
395 static const char sig[7] = { 0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 };
396 stream.write(sig, sizeof(sig));
397 }
398 // write vendor
399 writer.writeUInt32LE(static_cast<std::uint32_t>(vendor.size()));
400 writer.writeString(vendor);
401 // write field count later
402 const auto fieldCountOffset = stream.tellp();
403 writer.writeUInt32LE(0);
404 // write fields
405 std::uint32_t fieldsWritten = 0;
406 static const auto coverId = std::string(VorbisCommentIds::cover());
407 for (auto &i : fields()) {
408 if (i.first != coverId) { // write cover at the end
409 fieldsWritten += makeField(i.second, writer, flags, diag);
410 }
411 }
412 if (const auto cover = fields().find(coverId); cover != fields().end()) {
413 fieldsWritten += makeField(cover->second, writer, flags, diag);
414 }
415 // write field count
416 const auto framingByteOffset = stream.tellp();
417 stream.seekp(fieldCountOffset);
418 writer.writeUInt32LE(fieldsWritten);
419 stream.seekp(framingByteOffset);
420 // write framing byte
421 if (!(flags & VorbisCommentFlags::NoFramingByte)) {
422 stream.put(0x01);
423 }
424}
425
426} // 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:206
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:29
VorbisCommentFlags
The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.