Tag Parser 11.3.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"
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
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 default:
109 return std::string();
110 }
111}
112
114{
115 using namespace VorbisCommentIds;
116 // clang-format off
117 static const std::map<std::string_view, KnownField, CaseInsensitiveStringComparer> fieldMap({
144 { isrc(), KnownField::ISRC },
146 });
147 // clang-format on
148 const auto knownField(fieldMap.find(id));
149 return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
150}
151
155template <class StreamType> void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
156{
157 // prepare parsing
158 static const string context("parsing Vorbis comment");
159 const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
160 try {
161 // read signature: 0x3 + "vorbis"
162 char sig[8];
163 bool skipSignature = flags & VorbisCommentFlags::NoSignature;
164 if (!skipSignature) {
166 stream.read(sig, 7);
167 skipSignature = (BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
168 }
169 if (skipSignature) {
170 // read vendor (length prefixed string)
171 {
173 stream.read(sig, 4);
174 const auto vendorSize = LE::toUInt32(sig);
175 if (vendorSize <= maxSize) {
176 auto buff = make_unique<char[]>(vendorSize);
177 stream.read(buff.get(), vendorSize);
178 m_vendor.assignData(move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
179 // TODO: Is the vendor string actually UTF-8 (like the field values)?
180 } else {
181 diag.emplace_back(DiagLevel::Critical, "Vendor information is truncated.", context);
182 throw TruncatedDataException();
183 }
184 maxSize -= vendorSize;
185 }
186 // read field count
188 stream.read(sig, 4);
189 std::uint32_t fieldCount = LE::toUInt32(sig);
190 for (std::uint32_t i = 0; i < fieldCount; ++i) {
191 // read fields
192 VorbisCommentField field;
193 try {
194 field.parse(stream, maxSize, diag);
195 fields().emplace(field.id(), move(field));
196 } catch (const TruncatedDataException &) {
197 throw;
198 } catch (const Failure &) {
199 // nothing to do here since notifications will be added anyways
200 }
201 }
202 if (!(flags & VorbisCommentFlags::NoFramingByte)) {
203 stream.ignore(); // skip framing byte
204 }
205 m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
206 // turn "YEAR" into "DATE" (unless "DATE" exists)
207 // note: "DATE" is an official field and "YEAR" only an unofficial one but present in some files. In consistency with
208 // MediaInfo and VLC player it is treated like "DATE" here.
209 static const auto dateFieldId = std::string(VorbisCommentIds::date()), yearFieldId = std::string(VorbisCommentIds::year());
210 if (fields().find(dateFieldId) == fields().end()) {
211 const auto [first, end] = fields().equal_range(yearFieldId);
212 for (auto i = first; i != end; ++i) {
213 fields().emplace(dateFieldId, std::move(i->second));
214 }
215 fields().erase(first, end);
216 }
217 } else {
218 diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
219 throw InvalidDataException();
220 }
221 } catch (const TruncatedDataException &) {
222 m_size = static_cast<std::uint64_t>(stream.tellg()) - startOffset;
223 diag.emplace_back(DiagLevel::Critical, "Vorbis comment is truncated.", context);
224 throw;
225 }
226
227 // warn if there are bytes left in the last segment of the Ogg packet containing the comment
228 if constexpr (std::is_same_v<std::decay_t<StreamType>, OggIterator>) {
229 auto bytesRemaining = std::uint64_t();
230 if (stream) {
231 bytesRemaining = stream.remainingBytesInCurrentSegment();
232 if (stream.currentPage().isLastSegmentUnconcluded()) {
233 stream.nextSegment();
234 if (stream) {
235 bytesRemaining += stream.remainingBytesInCurrentSegment();
236 }
237 }
238 }
239 if (bytesRemaining) {
240 diag.emplace_back(DiagLevel::Warning, argsToString(bytesRemaining, " bytes left in last segment."), context);
241 }
242 }
243}
244
253{
254 internalParse(iterator, iterator.streamSize(), flags, diag);
255}
256
264void VorbisComment::parse(istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
265{
266 internalParse(stream, maxSize, flags, diag);
267}
268
276void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag)
277{
278 // prepare making
279 static const string context("making Vorbis comment");
280 string vendor;
281 try {
282 m_vendor.toString(vendor);
283 } catch (const ConversionException &) {
284 diag.emplace_back(DiagLevel::Warning, "Can not convert the assigned vendor to string.", context);
285 }
286 BinaryWriter writer(&stream);
287 if (!(flags & VorbisCommentFlags::NoSignature)) {
288 // write signature
289 static const char sig[7] = { 0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 };
290 stream.write(sig, sizeof(sig));
291 }
292 // write vendor
293 writer.writeUInt32LE(static_cast<std::uint32_t>(vendor.size()));
294 writer.writeString(vendor);
295 // write field count later
296 const auto fieldCountOffset = stream.tellp();
297 writer.writeUInt32LE(0);
298 // write fields
299 std::uint32_t fieldsWritten = 0;
300 for (auto &i : fields()) {
301 VorbisCommentField &field = i.second;
302 if (!field.value().isEmpty()) {
303 try {
304 if (field.make(writer, flags, diag)) {
305 ++fieldsWritten;
306 }
307 } catch (const Failure &) {
308 }
309 }
310 }
311 // write field count
312 const auto framingByteOffset = stream.tellp();
313 stream.seekp(fieldCountOffset);
314 writer.writeUInt32LE(fieldsWritten);
315 stream.seekp(framingByteOffset);
316 // write framing byte
317 if (!(flags & VorbisCommentFlags::NoFramingByte)) {
318 stream.put(0x01);
319 }
320}
321
322} // 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
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
Definition: fieldbasedtag.h:36
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
Returns the fields of the tag by providing direct access to the field map of the tag.
KnownField knownField(const IdentifierType &id) const
Returns the field for the specified ID.
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:130
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:544
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:584
std::uint64_t m_size
Definition: tag.h:201
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.
Definition: vorbiscomment.h:76
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.
Definition: vorbiscomment.h:85
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 rating()
constexpr TAG_PARSER_EXPORT std::string_view album()
Definition: matroskatagid.h:90
constexpr TAG_PARSER_EXPORT std::string_view encoderSettings()
constexpr TAG_PARSER_EXPORT std::string_view encodedBy()
constexpr TAG_PARSER_EXPORT std::string_view description()
constexpr TAG_PARSER_EXPORT std::string_view isrc()
constexpr TAG_PARSER_EXPORT std::string_view title()
Definition: matroskatagid.h:44
constexpr TAG_PARSER_EXPORT std::string_view comment()
constexpr TAG_PARSER_EXPORT std::string_view language()
constexpr TAG_PARSER_EXPORT std::string_view composer()
constexpr TAG_PARSER_EXPORT std::string_view partNumber()
Definition: matroskatagid.h:33
constexpr TAG_PARSER_EXPORT std::string_view conductor()
constexpr TAG_PARSER_EXPORT std::string_view director()
constexpr TAG_PARSER_EXPORT std::string_view genre()
constexpr TAG_PARSER_EXPORT std::string_view artist()
Definition: matroskatagid.h:86
constexpr TAG_PARSER_EXPORT std::string_view copyright()
constexpr TAG_PARSER_EXPORT std::string_view lyrics()
constexpr TAG_PARSER_EXPORT std::string_view encoder()
constexpr TAG_PARSER_EXPORT std::string_view license()
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:28
VorbisCommentFlags
The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.