Tag Parser 11.3.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
vorbiscommentfield.cpp
Go to the documentation of this file.
3
4#include "../flac/flacmetadata.h"
5
6#include "../ogg/oggiterator.h"
7
8#include "../id3/id3v2frame.h"
9
10#include "../diagnostics.h"
11#include "../exceptions.h"
12
13#include <c++utilities/conversion/binaryconversion.h>
14#include <c++utilities/conversion/stringbuilder.h>
15#include <c++utilities/conversion/stringconversion.h>
16#include <c++utilities/io/binaryreader.h>
17#include <c++utilities/io/binarywriter.h>
18
19#include <iostream>
20#include <memory>
21
22using namespace std;
23using namespace CppUtilities;
24
25namespace TagParser {
26
36{
37}
38
42VorbisCommentField::VorbisCommentField(const IdentifierType &id, const TagValue &value)
43 : TagField<VorbisCommentField>(id, value)
44{
45}
46
50template <class StreamType> void VorbisCommentField::internalParse(StreamType &stream, std::uint64_t &maxSize, Diagnostics &diag)
51{
52 static const string context("parsing Vorbis comment field");
53 char buff[4];
54 if (maxSize < 4) {
55 diag.emplace_back(DiagLevel::Critical, argsToString("Field expected at ", static_cast<std::streamoff>(stream.tellg()), '.'), context);
57 } else {
58 maxSize -= 4;
59 }
60 stream.read(buff, 4);
61 if (const auto size = LE::toUInt32(buff)) { // read size
62 if (size <= maxSize) {
63 maxSize -= size;
64 // read data
65 auto data = make_unique<char[]>(size);
66 stream.read(data.get(), size);
67 std::uint32_t idSize = 0;
68 for (const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize)
69 ;
70 // extract id
71 setId(string(data.get(), idSize));
72 if (!idSize) {
73 // empty field ID
74 diag.emplace_back(
75 DiagLevel::Critical, argsToString("The field ID at ", static_cast<std::streamoff>(stream.tellg()), " is empty."), context);
76 throw InvalidDataException();
77 } else if (id() == VorbisCommentIds::cover()) {
78 // extract cover value
79 try {
80 auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
81 stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
82 bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
83 bufferStream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
84 FlacMetaDataBlockPicture pictureBlock(value());
85 pictureBlock.parse(bufferStream, decoded.second);
86 setTypeInfo(pictureBlock.pictureType());
87 } catch (const TruncatedDataException &) {
88 diag.emplace_back(DiagLevel::Critical, "METADATA_BLOCK_PICTURE is truncated.", context);
89 throw;
90 } catch (const ConversionException &) {
91 diag.emplace_back(DiagLevel::Critical, "Base64 coding of METADATA_BLOCK_PICTURE is invalid.", context);
92 throw InvalidDataException();
93 } catch (const std::ios_base::failure &failure) {
94 diag.emplace_back(DiagLevel::Critical,
95 argsToString("An IO error occurred when reading the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
96 throw Failure();
97 }
98 } else if (id().size() + 1 < size) {
99 const auto str = std::string_view(data.get() + idSize + 1, size - idSize - 1);
100 if (id() == VorbisCommentIds::rating()) {
101 try {
102 // set rating as Popularity to preserve the scale information
103 value().assignPopularity(Popularity{ .rating = stringToNumber<double>(str), .scale = TagType::VorbisComment });
104 } catch (const ConversionException &) {
105 // fallback to text
107 }
108 } else {
109 // extract other values (as string)
111 }
112 }
113 } else {
114 diag.emplace_back(DiagLevel::Critical, argsToString("Field at ", static_cast<std::streamoff>(stream.tellg()), " is truncated."), context);
115 throw TruncatedDataException();
116 }
117 }
118}
119
131{
132 std::uint64_t maxSize = iterator.streamSize() - iterator.currentCharacterOffset();
133 internalParse(iterator, maxSize, diag);
134}
135
146void VorbisCommentField::parse(OggIterator &iterator, std::uint64_t &maxSize, Diagnostics &diag)
147{
148 internalParse(iterator, maxSize, diag);
149}
150
161void VorbisCommentField::parse(istream &stream, std::uint64_t &maxSize, Diagnostics &diag)
162{
163 internalParse(stream, maxSize, diag);
164}
165
175bool VorbisCommentField::make(BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
176{
177 static const string context("making Vorbis comment field");
178 if (id().empty()) {
179 diag.emplace_back(DiagLevel::Critical, "The field ID is empty.", context);
180 }
181 try {
182 // try to convert value to string
183 string valueString;
184 if (id() == VorbisCommentIds::cover()) {
185 if (flags & VorbisCommentFlags::NoCovers) {
186 return false;
187 }
188 // make cover
189 if (value().type() != TagDataType::Picture) {
190 diag.emplace_back(DiagLevel::Critical, "Assigned value of cover field is not picture data.", context);
191 throw InvalidDataException();
192 }
193 try {
194 FlacMetaDataBlockPicture pictureBlock(value());
195 pictureBlock.setPictureType(typeInfo());
196
197 const auto requiredSize = pictureBlock.requiredSize();
198 auto buffer = make_unique<char[]>(requiredSize);
199 stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
200 bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
201 bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize);
202
203 pictureBlock.make(bufferStream);
204 valueString = encodeBase64(reinterpret_cast<std::uint8_t *>(buffer.get()), requiredSize);
205 } catch (const Failure &) {
206 diag.emplace_back(DiagLevel::Critical, "Unable to make METADATA_BLOCK_PICTURE struct from the assigned value.", context);
207 throw;
208 } catch (const std::ios_base::failure &failure) {
209 diag.emplace_back(DiagLevel::Critical,
210 argsToString("An IO error occurred when writing the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
211 throw Failure();
212 }
213 } else {
214 // make normal string value
215 valueString = value().toString();
216 }
217 const auto size(valueString.size() + id().size() + 1);
218 if (size > numeric_limits<std::uint32_t>::max()) {
219 diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds the maximum size.", context);
220 throw InvalidDataException();
221 }
222 writer.writeUInt32LE(static_cast<std::uint32_t>(size));
223 writer.writeString(id());
224 writer.writeChar('=');
225 writer.writeString(valueString);
226 } catch (const ConversionException &) {
227 diag.emplace_back(DiagLevel::Critical, "Assigned value can not be converted appropriately.", context);
228 throw InvalidDataException();
229 }
230 return true;
231}
232
233} // 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 FlacMetaDataBlockPicture class is a FLAC "METADATA_BLOCK_PICTURE" parser and maker.
Definition: flacmetadata.h:248
std::uint32_t requiredSize() const
Returns the number of bytes make() will write.
void make(std::ostream &outputStream)
Makes the FLAC "METADATA_BLOCK_PICTURE".
void setPictureType(std::uint32_t pictureType)
Sets the picture type according to the ID3v2 APIC frame.
Definition: flacmetadata.h:289
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
The OggIterator class helps iterating through all segments of an OGG bitstream.
Definition: oggiterator.h:11
std::uint64_t currentCharacterOffset() const
Returns the offset of the current character in the input stream if the iterator is valid; otherwise a...
Definition: oggiterator.h:222
std::uint64_t streamSize() const
Returns the stream size (which has been specified when constructing the iterator).
Definition: oggiterator.h:117
The TagField class is used by FieldMapBasedTag to store the fields.
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
const TypeInfoType & typeInfo() const
Returns the type info of the current TagField.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
Definition: tagvalue.h:130
void assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding=TagTextEncoding::Latin1, TagTextEncoding convertTo=TagTextEncoding::Unspecified)
Assigns a copy of the given text.
Definition: tagvalue.cpp:955
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
Definition: tagvalue.cpp:1077
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
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
The VorbisCommentField class is used by VorbisComment to store the fields.
void parse(OggIterator &iterator, Diagnostics &diag)
Parses a field using the specified iterator.
VorbisCommentField()
Constructs a new Vorbis comment field.
bool make(CppUtilities::BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
Writes the field to a stream using the specified writer.
constexpr TAG_PARSER_EXPORT std::string_view rating()
constexpr TAG_PARSER_EXPORT std::string_view cover()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
VorbisCommentFlags
The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.