Tag Parser 12.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
vorbiscommentfield.cpp
Go to the documentation of this file.
3
4#include "../flac/flacmetadata.h"
5
6#include "../ogg/oggiterator.h"
7
8#include "../diagnostics.h"
9#include "../exceptions.h"
10
11#include <c++utilities/conversion/binaryconversion.h>
12#include <c++utilities/conversion/stringbuilder.h>
13#include <c++utilities/conversion/stringconversion.h>
14#include <c++utilities/io/binaryreader.h>
15#include <c++utilities/io/binarywriter.h>
16
17#include <iostream>
18#include <memory>
19
20using namespace std;
21using namespace CppUtilities;
22
23namespace TagParser {
24
36
40VorbisCommentField::VorbisCommentField(const IdentifierType &id, const TagValue &value)
41 : TagField<VorbisCommentField>(id, value)
42{
43}
44
48template <class StreamType> void VorbisCommentField::internalParse(StreamType &stream, std::uint64_t &maxSize, Diagnostics &diag)
49{
50 static const string context("parsing Vorbis comment field");
51 char buff[4];
52 if (maxSize < 4) {
53 diag.emplace_back(DiagLevel::Critical, argsToString("Field expected at ", static_cast<std::streamoff>(stream.tellg()), '.'), context);
55 } else {
56 maxSize -= 4;
57 }
58 stream.read(buff, 4);
59 if (const auto size = LE::toUInt32(buff)) { // read size
60 if (size <= maxSize) {
61 maxSize -= size;
62 // read data
63 auto data = make_unique<char[]>(size);
64 stream.read(data.get(), size);
65 std::uint32_t idSize = 0;
66 for (const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize)
67 ;
68 // extract id
69 setId(string(data.get(), idSize));
70 if (!idSize) {
71 // empty field ID
72 diag.emplace_back(
73 DiagLevel::Critical, argsToString("The field ID at ", static_cast<std::streamoff>(stream.tellg()), " is empty."), context);
74 throw InvalidDataException();
75 } else if (id() == VorbisCommentIds::cover()) {
76 // extract cover value
77 try {
78 auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
79 stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
80 bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
81#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
82 bufferStream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
83#else
84 bufferStream.write(reinterpret_cast<const char *>(decoded.first.get()), decoded.second);
85#endif
86 FlacMetaDataBlockPicture pictureBlock(value());
87 pictureBlock.parse(bufferStream, decoded.second);
88 setTypeInfo(pictureBlock.pictureType());
89 } catch (const TruncatedDataException &) {
90 diag.emplace_back(DiagLevel::Critical, "METADATA_BLOCK_PICTURE is truncated.", context);
91 throw;
92 } catch (const ConversionException &) {
93 diag.emplace_back(DiagLevel::Critical, "Base64 coding of METADATA_BLOCK_PICTURE is invalid.", context);
94 throw InvalidDataException();
95 } catch (const std::ios_base::failure &failure) {
96 diag.emplace_back(DiagLevel::Critical,
97 argsToString("An IO error occurred when reading the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
98 throw Failure();
99 }
100 } else if (id().size() + 1 < size) {
101 const auto str = std::string_view(data.get() + idSize + 1, size - idSize - 1);
102 if (id() == VorbisCommentIds::rating()) {
103 try {
104 // set rating as Popularity to preserve the scale information
105 value().assignPopularity(Popularity{ .rating = stringToNumber<double>(str), .scale = TagType::VorbisComment });
106 } catch (const ConversionException &) {
107 // fallback to text
109 diag.emplace_back(DiagLevel::Warning, argsToString("The rating is not a number."), context);
110 }
111 } else {
112 // extract other values (as string)
114 }
115 }
116 } else {
117 diag.emplace_back(DiagLevel::Critical, argsToString("Field at ", static_cast<std::streamoff>(stream.tellg()), " is truncated."), context);
118 throw TruncatedDataException();
119 }
120 }
121}
122
134{
135 std::uint64_t maxSize = iterator.streamSize() - iterator.currentCharacterOffset();
136 internalParse(iterator, maxSize, diag);
137}
138
149void VorbisCommentField::parse(OggIterator &iterator, std::uint64_t &maxSize, Diagnostics &diag)
150{
151 internalParse(iterator, maxSize, diag);
152}
153
164void VorbisCommentField::parse(istream &stream, std::uint64_t &maxSize, Diagnostics &diag)
165{
166 internalParse(stream, maxSize, diag);
167}
168
178bool VorbisCommentField::make(BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
179{
180 static const string context("making Vorbis comment field");
181 if (id().empty()) {
182 diag.emplace_back(DiagLevel::Critical, "The field ID is empty.", context);
183 }
184 try {
185 // try to convert value to string
186 string valueString;
187 if (id() == VorbisCommentIds::cover()) {
188 if (flags & VorbisCommentFlags::NoCovers) {
189 return false;
190 }
191 // make cover
192 if (value().type() != TagDataType::Picture) {
193 diag.emplace_back(DiagLevel::Critical, "Assigned value of cover field is not picture data.", context);
194 throw InvalidDataException();
195 }
196 try {
197 FlacMetaDataBlockPicture pictureBlock(value());
198 pictureBlock.setPictureType(typeInfo());
199
200 const auto requiredSize = pictureBlock.requiredSize();
201 auto buffer = make_unique<char[]>(requiredSize);
202 stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
203 bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
204#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
205 bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize);
206#endif
207 pictureBlock.make(bufferStream);
208#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
209 bufferStream.read(buffer.get(), static_cast<std::streamsize>(requiredSize));
210#endif
211 valueString = encodeBase64(reinterpret_cast<std::uint8_t *>(buffer.get()), requiredSize);
212
213 } catch (const Failure &) {
214 diag.emplace_back(DiagLevel::Critical, "Unable to make METADATA_BLOCK_PICTURE struct from the assigned value.", context);
215 throw;
216 } catch (const std::ios_base::failure &failure) {
217 diag.emplace_back(DiagLevel::Critical,
218 argsToString("An IO error occurred when writing the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
219 throw Failure();
220 }
221 } else if (value().type() == TagDataType::Popularity) {
223 } else {
224 // make normal string value
225 valueString = value().toString(TagTextEncoding::Utf8);
226 }
227 const auto size(valueString.size() + id().size() + 1);
228 if (size > numeric_limits<std::uint32_t>::max()) {
229 diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds the maximum size.", context);
230 throw InvalidDataException();
231 }
232 writer.writeUInt32LE(static_cast<std::uint32_t>(size));
233 writer.writeString(id());
234 writer.writeChar('=');
235 writer.writeString(valueString);
236 } catch (const ConversionException &) {
237 diag.emplace_back(DiagLevel::Critical, "Assigned value can not be converted appropriately.", context);
238 throw InvalidDataException();
239 }
240 return true;
241}
242
243} // namespace TagParser
The Diagnostics class is a container for DiagMessage.
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.
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.
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...
std::uint64_t streamSize() const
Returns the stream size (which has been specified when constructing the iterator).
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:147
void assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding=TagTextEncoding::Latin1, TagTextEncoding convertTo=TagTextEncoding::Unspecified)
Assigns a copy of the given text.
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
Popularity toScaledPopularity(TagType scale=TagType::Unspecified) const
Converts the value of the current TagValue object to its equivalent Popularity representation using t...
Definition tagvalue.cpp:901
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
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.
std::string toString() const
Returns the popularity as string in the format "rating" if only a rating is present or in the format ...