8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/io/binaryreader.h>
10#include <c++utilities/io/binarywriter.h>
30 : m_parsedRawDataType(RawDataType::Reserved)
31 , m_countryIndicator(0)
41 , m_parsedRawDataType(RawDataType::Reserved)
42 , m_countryIndicator(0)
76 using namespace Mp4AtomIds;
77 using namespace Mp4TagAtomIds;
78 string context(
"parsing MP4 tag field");
79 ilstChild.
parse(diag);
81 context =
"parsing MP4 tag field " + ilstChild.
idToString();
82 iostream &stream = ilstChild.
stream();
83 BinaryReader &reader = ilstChild.
container().reader();
84 int dataAtomFound = 0, meanAtomFound = 0, nameAtomFound = 0;
87 dataAtom->parse(diag);
89 if (dataAtom->dataSize() < 8) {
91 "Truncated child atom \"data\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
96 auto *rawDataType = &m_parsedRawDataType;
99 if (++dataAtomFound > 1) {
100 if (dataAtomFound == 2) {
102 "Multiple \"data\" child atom in tag atom (ilst child) found. It will be ignored but preserved when applying changes.",
111 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset()));
112 if (reader.readByte() != 0) {
114 "The version indicator byte is not zero, the tag atom might be unsupported and hence not be parsed correctly.", context);
120 diag.emplace_back(
DiagLevel::Warning,
"Unexpected data type indicator found.", context);
127 switch (*rawDataType) {
130 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 8));
131 val->assignText(reader.readString(dataAtom->dataSize() - 8),
138 switch (m_parsedRawDataType) {
140 val->setMimeType(
"image/gif");
143 val->setMimeType(
"image/jpeg");
146 val->setMimeType(
"image/png");
149 val->setMimeType(
"image/bmp");
153 const auto coverSize =
static_cast<streamoff
>(dataAtom->dataSize() - 8);
154 auto coverData = make_unique<char[]>(
static_cast<size_t>(coverSize));
155 stream.read(coverData.get(), coverSize);
161 if (dataAtom->dataSize() > (8 + 4)) {
162 diag.emplace_back(
DiagLevel::Warning,
"Data atom stores integer of invalid size. Trying to read data anyways.", context);
164 if (dataAtom->dataSize() >= (8 + 4)) {
165 number = reader.readInt32BE();
166 }
else if (dataAtom->dataSize() == (8 + 2)) {
167 number = reader.readInt16BE();
168 }
else if (dataAtom->dataSize() == (8 + 1)) {
169 number = reader.readChar();
171 switch (ilstChild.
id()) {
172 case PreDefinedGenre:
173 val->assignStandardGenreIndex(number);
176 val->assignInteger(number);
182 if (dataAtom->dataSize() > (8 + 4)) {
183 diag.emplace_back(
DiagLevel::Warning,
"Data atom stores integer of invalid size. Trying to read data anyways.", context);
185 if (dataAtom->dataSize() >= (8 + 4)) {
186 number =
static_cast<int>(reader.readUInt32BE());
187 }
else if (dataAtom->dataSize() == (8 + 2)) {
188 number =
static_cast<int>(reader.readUInt16BE());
189 }
else if (dataAtom->dataSize() == (8 + 1)) {
190 number =
static_cast<int>(reader.readByte());
192 switch (ilstChild.
id()) {
193 case PreDefinedGenre:
194 val->assignStandardGenreIndex(number - 1);
197 val->assignInteger(number);
202 switch (ilstChild.
id()) {
206 if (dataAtom->dataSize() < (8 + 6)) {
207 diag.emplace_back(
DiagLevel::Warning,
"Track/disk position is truncated. Trying to read data anyways.", context);
209 std::uint16_t pos = 0, total = 0;
210 if (dataAtom->dataSize() >= (8 + 4)) {
211 stream.seekg(2, ios_base::cur);
212 pos = reader.readUInt16BE();
214 if (dataAtom->dataSize() >= (8 + 6)) {
215 total = reader.readUInt16BE();
220 case PreDefinedGenre:
221 if (dataAtom->dataSize() < (8 + 2)) {
224 val->assignStandardGenreIndex(reader.readUInt16BE() - 1);
228 const auto dataSize =
static_cast<streamsize
>(dataAtom->dataSize() - 8);
229 auto data = make_unique<char[]>(
static_cast<size_t>(dataSize));
230 stream.read(data.get(), dataSize);
239 if (dataAtom->dataSize() < 8) {
241 "Truncated child atom \"mean\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
245 if (++meanAtomFound > 1) {
246 if (meanAtomFound == 2) {
248 "Tag atom contains more than one mean atom. The additional mean atoms will be ignored and discarded when applying "
254 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 4));
255 m_mean = reader.readString(dataAtom->dataSize() - 4);
257 if (dataAtom->dataSize() < 4) {
259 "Truncated child atom \"name\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
263 if (++nameAtomFound > 1) {
264 if (nameAtomFound == 2) {
266 "Tag atom contains more than one name atom. The addiational name atoms will be ignored and discarded when applying "
272 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 4));
273 m_name = reader.readString(dataAtom->dataSize() - 4);
276 "Unknown child atom \"" % dataAtom->idToString()
277 +
"\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
282 "Unable to parse all children atom in tag atom (ilst child) found. Invalid children will be ignored and discarded when applying "
287 if (
value().isEmpty()) {
324 using namespace Mp4TagAtomIds;
325 std::vector<std::uint32_t> res;
344 case PreDefinedGenre:
404 using namespace Mp4TagAtomIds;
432 case PreDefinedGenre:
438 if (mimeType ==
"image/jpg" || mimeType ==
"image/jpeg") {
440 }
else if (mimeType ==
"image/png") {
442 }
else if (mimeType ==
"image/bmp") {
469void Mp4TagField::internallyClearValue()
472 m_additionalData.clear();
473 m_countryIndicator = 0;
480void Mp4TagField::internallyClearFurtherData()
488Mp4TagFieldMaker::Data::Data()
489 : convertedData(stringstream::in | stringstream::out | stringstream::binary)
511 diag.emplace_back(DiagLevel::Warning,
"Invalid tag atom ID.",
"making MP4 tag field");
512 throw InvalidDataException();
515 if (m_field.value().isEmpty() && (!m_field.mean().empty() || !m_field.name().empty())) {
516 diag.emplace_back(DiagLevel::Critical,
"No tag value assigned.", context);
517 throw InvalidDataException();
521 m_totalSize = 8 + (m_field.name().empty() ? 0 : (12 + m_field.name().size())) + (m_field.mean().empty() ? 0 : (12 + m_field.mean().size()));
524 m_totalSize += prepareDataAtom(field.value(), field.countryIndicator(), field.languageIndicator(), context, diag);
526 m_totalSize += prepareDataAtom(additionalData.value, additionalData.countryIndicator, additionalData.languageIndicator, context, diag);
529 if (m_totalSize > numeric_limits<std::uint32_t>::max()) {
530 diag.emplace_back(
DiagLevel::Critical,
"Making a such big MP4 tag field is not possible.", context);
531 throw NotImplementedException();
538std::uint64_t Mp4TagFieldMaker::prepareDataAtom(
539 const TagValue &value, std::uint16_t countryIndicator, std::uint16_t languageIndicator,
const std::string &context, Diagnostics &diag)
542 auto &data = m_data.emplace_back();
543 m_writer.setStream(&data.convertedData);
547 data.countryIndicator = countryIndicator;
548 data.languageIndicator = languageIndicator;
553 }
catch (
const Failure &) {
559 DiagLevel::Warning,
"It was not possible to find an appropriate raw data type id. JPEG image will be assumed.", context);
563 diag.emplace_back(
DiagLevel::Warning,
"It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
568 if (!value.isEmpty()) {
569 data.convertedData.exceptions(std::stringstream::failbit | std::stringstream::badbit);
570 switch (data.rawType) {
582 int number = value.toInteger();
583 if (number <= numeric_limits<std::int16_t>::max() && number >= numeric_limits<std::int16_t>::min()) {
584 m_writer.writeInt16BE(
static_cast<std::int16_t
>(number));
586 m_writer.writeInt32BE(number);
591 int number = value.toInteger();
592 if (number <= numeric_limits<std::uint16_t>::max() && number >= numeric_limits<std::uint16_t>::min()) {
593 m_writer.writeUInt16BE(
static_cast<std::uint16_t
>(number));
594 }
else if (number > 0) {
595 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(number));
597 throw ConversionException(
598 "Negative integer can not be assigned to the field with the ID \"" % interpretIntegerAsString<std::uint32_t>(m_field.
id())
608 switch (m_field.
id()) {
614 m_writer.writeInt32BE(pos.position());
615 if (pos.total() <= numeric_limits<std::int16_t>::max()) {
616 m_writer.writeInt16BE(
static_cast<std::int16_t
>(pos.total()));
618 throw ConversionException(
619 "Integer can not be assigned to the field with the id \"" % interpretIntegerAsString<std::uint32_t>(m_field.
id())
620 +
"\" because it is to big.");
622 m_writer.writeUInt16BE(0);
626 m_writer.writeUInt16BE(
static_cast<std::uint16_t
>(value.toStandardGenreIndex()));
632 }
catch (
const ConversionException &e) {
634 if (char_traits<char>::length(e.what())) {
637 diag.emplace_back(
DiagLevel::Critical,
"The assigned tag value can not be converted to be written appropriately.", context);
639 throw InvalidDataException();
643 if (value.isEmpty()) {
644 return data.size = 0;
645 }
else if (data.convertedData.tellp()) {
646 data.size =
static_cast<std::size_t
>(data.convertedData.tellp());
648 data.rawData = std::string_view(value.dataPointer(), data.size = value.dataSize());
650 return data.size += 16;
662 m_writer.setStream(&stream);
664 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(m_totalSize));
666 m_writer.writeUInt32BE(m_field.
id());
668 if (!m_field.
mean().empty()) {
669 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(12 + m_field.
mean().size()));
671 m_writer.writeUInt32BE(0);
672 m_writer.writeString(m_field.
mean());
675 if (!m_field.
name().empty()) {
676 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(12 + m_field.
name().length()));
678 m_writer.writeUInt32BE(0);
679 m_writer.writeString(m_field.
name());
682 for (
auto &data : m_data) {
686 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(data.size));
688 m_writer.writeByte(0);
689 m_writer.writeUInt24BE(data.rawType);
690 m_writer.writeUInt16BE(data.countryIndicator);
691 m_writer.writeUInt16BE(data.languageIndicator);
692 if (data.convertedData.tellp()) {
694 stream << data.convertedData.rdbuf();
697 stream.write(data.rawData.data(),
static_cast<std::streamoff
>(data.rawData.size()));
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...
const IdentifierType & id() const
Returns the element ID.
std::iostream & stream()
Returns the related stream.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * firstChild()
Returns the first child of the element.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
ContainerType & container()
Returns the related container.
The Mp4Atom class helps to parse MP4 files.
std::string idToString() const
Converts the specified atom ID to a printable string.
The Mp4TagFieldMaker class helps making tag fields.
void make(std::ostream &stream)
Saves the field (specified when constructing the object) to the specified stream.
Mp4TagFieldMaker(Mp4TagFieldMaker &&)=default
The Mp4TagField class is used by Mp4Tag to store the fields.
Mp4TagFieldMaker prepareMaking(Diagnostics &diag)
Prepares making.
const std::vector< AdditionalData > & additionalData() const
Returns additional data (and the corresponding raw data type, country and language).
void reparse(Mp4Atom &ilstChild, Diagnostics &diag)
Parses field information from the specified Mp4Atom.
const std::string & name() const
Returns the "name" for "extended" fields.
Mp4TagField()
Constructs a new Mp4TagField.
std::uint32_t appropriateRawDataTypeForValue(const TagValue &value) const
Returns an appropriate raw data type.
const std::string & mean() const
Returns the "mean" for "extended" fields.
std::uint32_t appropriateRawDataType() const
Returns an appropriate raw data type.
std::uint16_t languageIndicator() const
Returns the language indicator.
std::vector< std::uint32_t > expectedRawDataTypes() const
Returns the expected raw data types for the ID of the field.
std::uint16_t countryIndicator() const
Returns the country indicator.
void make(std::ostream &stream, Diagnostics &diag)
Saves the field to the specified stream.
static std::string fieldIdToString(IdentifierType id)
Returns the string representation for the specified id.
The TagField class is used by FieldMapBasedTag to store the fields.
void setTypeInfo(const TypeInfoType &typeInfo)
const TypeInfoType & typeInfo() const
IdentifierType & id()
Returns the id of the current TagField.
bool isTypeInfoAssigned() const
void setId(const IdentifierType &id)
The TagValue class wraps values of different types.
const std::string & mimeType() const
Returns the MIME type.
TagTextEncoding dataEncoding() const
Returns the data encoding.
void clearDataAndMetadata()
Wipes assigned data including meta data.
Contains all classes and functions of the TagInfo library.