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)
64static bool assignSpecialInteger(
const Mp4Atom &ilstChild,
int source,
TagValue &dest)
66 switch (ilstChild.
id()) {
71 using Limits = std::numeric_limits<Mp4TagMediaTypeId>;
72 if (source >=
static_cast<int>(Limits::min()) && source <=
static_cast<int>(Limits::max())) {
80 using Limits = std::numeric_limits<Mp4TagContentRatingId>;
81 if (source >=
static_cast<int>(Limits::min()) && source <=
static_cast<int>(Limits::max())) {
107 using namespace Mp4AtomIds;
108 using namespace Mp4TagAtomIds;
109 string context(
"parsing MP4 tag field");
110 ilstChild.
parse(diag);
112 context =
"parsing MP4 tag field " + ilstChild.
idToString();
113 iostream &stream = ilstChild.
stream();
114 BinaryReader &reader = ilstChild.
container().reader();
115 int dataAtomFound = 0, meanAtomFound = 0, nameAtomFound = 0;
118 dataAtom->parse(diag);
120 if (dataAtom->dataSize() < 8) {
122 "Truncated child atom \"data\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
126 auto *val = &
value();
127 auto *rawDataType = &m_parsedRawDataType;
130 if (++dataAtomFound > 1) {
131 if (dataAtomFound == 2) {
133 "Multiple \"data\" child atom in tag atom (ilst child) found. It will be ignored but preserved when applying changes.",
142 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset()));
143 if (reader.readByte() != 0) {
145 "The version indicator byte is not zero, the tag atom might be unsupported and hence not be parsed correctly.", context);
151 diag.emplace_back(
DiagLevel::Warning,
"Unexpected data type indicator found.", context);
158 switch (*rawDataType) {
161 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 8));
162 val->assignText(reader.readString(dataAtom->dataSize() - 8),
169 switch (m_parsedRawDataType) {
171 val->setMimeType(
"image/gif");
174 val->setMimeType(
"image/jpeg");
177 val->setMimeType(
"image/png");
180 val->setMimeType(
"image/bmp");
184 const auto coverSize =
static_cast<streamoff
>(dataAtom->dataSize() - 8);
185 auto coverData = make_unique<char[]>(
static_cast<size_t>(coverSize));
186 stream.read(coverData.get(), coverSize);
192 if (dataAtom->dataSize() > (8 + 4)) {
193 diag.emplace_back(
DiagLevel::Warning,
"Data atom stores integer of invalid size. Trying to read data anyway.", context);
195 if (dataAtom->dataSize() >= (8 + 4)) {
196 number = reader.readInt32BE();
197 }
else if (dataAtom->dataSize() == (8 + 2)) {
198 number = reader.readInt16BE();
199 }
else if (dataAtom->dataSize() == (8 + 1)) {
200 number = reader.readChar();
202 if (!assignSpecialInteger(ilstChild, number, *val)) {
203 val->assignInteger(number);
208 auto number = std::uint64_t();
209 if (dataAtom->dataSize() > (8 + 8)) {
210 diag.emplace_back(
DiagLevel::Warning,
"Data atom stores integer of invalid size. Trying to read data anyway.", context);
212 if (dataAtom->dataSize() >= (8 + 8)) {
213 number = reader.readUInt64BE();
214 }
else if (dataAtom->dataSize() >= (8 + 4)) {
215 number = reader.readUInt32BE();
216 }
else if (dataAtom->dataSize() == (8 + 2)) {
217 number = reader.readUInt16BE();
218 }
else if (dataAtom->dataSize() == (8 + 1)) {
219 number = reader.readByte();
221 if (number <=
static_cast<std::uint64_t
>(std::numeric_limits<int>::max())
222 && !assignSpecialInteger(ilstChild,
static_cast<int>(number), *val)) {
223 val->assignUnsignedInteger(number);
228 switch (ilstChild.
id()) {
232 if (dataAtom->dataSize() < (8 + 6)) {
233 diag.emplace_back(
DiagLevel::Warning,
"Track/disk position is truncated. Trying to read data anyways.", context);
235 std::uint16_t pos = 0, total = 0;
236 if (dataAtom->dataSize() >= (8 + 4)) {
237 stream.seekg(2, ios_base::cur);
238 pos = reader.readUInt16BE();
240 if (dataAtom->dataSize() >= (8 + 6)) {
241 total = reader.readUInt16BE();
246 case PreDefinedGenre:
247 if (dataAtom->dataSize() < (8 + 2)) {
250 val->assignStandardGenreIndex(reader.readUInt16BE() - 1);
254 const auto dataSize =
static_cast<streamsize
>(dataAtom->dataSize() - 8);
255 auto data = make_unique<char[]>(
static_cast<size_t>(dataSize));
256 stream.read(data.get(), dataSize);
265 if (dataAtom->dataSize() < 8) {
267 "Truncated child atom \"mean\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
271 if (++meanAtomFound > 1) {
272 if (meanAtomFound == 2) {
274 "Tag atom contains more than one mean atom. The additional mean atoms will be ignored and discarded when applying "
280 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 4));
281 m_mean = reader.readString(dataAtom->dataSize() - 4);
283 if (dataAtom->dataSize() < 4) {
285 "Truncated child atom \"name\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
289 if (++nameAtomFound > 1) {
290 if (nameAtomFound == 2) {
292 "Tag atom contains more than one name atom. The addiational name atoms will be ignored and discarded when applying "
298 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 4));
299 m_name = reader.readString(dataAtom->dataSize() - 4);
302 "Unknown child atom \"" % dataAtom->idToString()
303 +
"\" in tag atom (ilst child) found. It will be ignored and discarded when applying changes.",
308 "Unable to parse all children atom in tag atom (ilst child) found. Invalid children will be ignored and discarded when applying "
313 if (
value().isEmpty()) {
350 using namespace Mp4TagAtomIds;
351 std::vector<std::uint32_t> res;
370 case PreDefinedGenre:
431 using namespace Mp4TagAtomIds;
459 case PreDefinedGenre:
466 if (mimeType ==
"image/jpg" || mimeType ==
"image/jpeg") {
468 }
else if (mimeType ==
"image/png") {
470 }
else if (mimeType ==
"image/bmp") {
497void Mp4TagField::internallyClearValue()
500 m_additionalData.clear();
501 m_countryIndicator = 0;
508void Mp4TagField::internallyClearFurtherData()
516Mp4TagFieldMaker::Data::Data()
517 : convertedData(stringstream::in | stringstream::out | stringstream::binary)
539 diag.emplace_back(DiagLevel::Warning,
"Invalid tag atom ID.",
"making MP4 tag field");
540 throw InvalidDataException();
543 if (m_field.value().isEmpty() && (!m_field.mean().empty() || !m_field.name().empty())) {
544 diag.emplace_back(DiagLevel::Critical,
"No tag value assigned.", context);
545 throw InvalidDataException();
549 m_totalSize = 8 + (m_field.name().empty() ? 0 : (12 + m_field.name().size())) + (m_field.mean().empty() ? 0 : (12 + m_field.mean().size()));
552 m_totalSize += prepareDataAtom(field.value(), field.countryIndicator(), field.languageIndicator(), context, diag);
554 m_totalSize += prepareDataAtom(additionalData.value, additionalData.countryIndicator, additionalData.languageIndicator, context, diag);
557 if (m_totalSize > numeric_limits<std::uint32_t>::max()) {
558 diag.emplace_back(
DiagLevel::Critical,
"Making a such big MP4 tag field is not possible.", context);
559 throw NotImplementedException();
564static bool writeSpecialInteger(std::underlying_type_t<Mp4TagAtomIds::KnownValue> fieldId,
const TagValue &value, CppUtilities::BinaryWriter &writer)
592std::uint64_t Mp4TagFieldMaker::prepareDataAtom(
593 const TagValue &value, std::uint16_t countryIndicator, std::uint16_t languageIndicator,
const std::string &context, Diagnostics &diag)
596 auto &data = m_data.emplace_back();
597 m_writer.setStream(&data.convertedData);
601 data.countryIndicator = countryIndicator;
602 data.languageIndicator = languageIndicator;
607 }
catch (
const Failure &) {
613 DiagLevel::Warning,
"It was not possible to find an appropriate raw data type id. JPEG image will be assumed.", context);
617 diag.emplace_back(
DiagLevel::Warning,
"It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
622 if (!value.isEmpty()) {
623 data.convertedData.exceptions(std::stringstream::failbit | std::stringstream::badbit);
624 switch (data.rawType) {
636 if (writeSpecialInteger(m_field.
id(), value, m_writer)) {
639 const auto number = value.toInteger();
640 if (number <= numeric_limits<std::int16_t>::max() && number >= numeric_limits<std::int16_t>::min()) {
641 m_writer.writeInt16BE(
static_cast<std::int16_t
>(number));
643 m_writer.writeInt32BE(number);
648 if (writeSpecialInteger(m_field.
id(), value, m_writer)) {
651 const auto number = value.toUnsignedInteger();
652 if (number <= numeric_limits<std::uint16_t>::max()) {
653 m_writer.writeUInt16BE(
static_cast<std::uint16_t
>(number));
654 }
else if (number <= numeric_limits<std::uint32_t>::max()) {
655 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(number));
657 m_writer.writeUInt64BE(number);
666 switch (m_field.
id()) {
672 m_writer.writeInt32BE(pos.position());
673 if (pos.total() <= numeric_limits<std::int16_t>::max()) {
674 m_writer.writeInt16BE(
static_cast<std::int16_t
>(pos.total()));
676 throw ConversionException(
677 "Integer can not be assigned to the field with the id \"" % interpretIntegerAsString<std::uint32_t>(m_field.
id())
678 +
"\" because it is to big.");
680 m_writer.writeUInt16BE(0);
684 m_writer.writeUInt16BE(
static_cast<std::uint16_t
>(value.toStandardGenreIndex()));
687 if (writeSpecialInteger(m_field.
id(), value, m_writer)) {
694 }
catch (
const ConversionException &e) {
696 if (
const auto what = std::string_view(e.what()); !what.empty()) {
699 diag.emplace_back(
DiagLevel::Critical,
"The assigned tag value can not be converted to be written appropriately.", context);
701 throw InvalidDataException();
705 if (value.isEmpty()) {
706 return data.size = 0;
707 }
else if (data.convertedData.tellp()) {
708 data.size =
static_cast<std::size_t
>(data.convertedData.tellp());
710 data.rawData = std::string_view(value.dataPointer(), data.size = value.dataSize());
712 return data.size += 16;
724 m_writer.setStream(&stream);
726 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(m_totalSize));
728 m_writer.writeUInt32BE(m_field.
id());
730 if (!m_field.
mean().empty()) {
731 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(12 + m_field.
mean().size()));
733 m_writer.writeUInt32BE(0);
734 m_writer.writeString(m_field.
mean());
737 if (!m_field.
name().empty()) {
738 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(12 + m_field.
name().length()));
740 m_writer.writeUInt32BE(0);
741 m_writer.writeString(m_field.
name());
744 for (
auto &data : m_data) {
748 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(data.size));
750 m_writer.writeByte(0);
751 m_writer.writeUInt24BE(data.rawType);
752 m_writer.writeUInt16BE(data.countryIndicator);
753 m_writer.writeUInt16BE(data.languageIndicator);
754 if (data.convertedData.tellp()) {
756 stream << data.convertedData.rdbuf();
759 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.
void assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding=TagTextEncoding::Latin1, TagTextEncoding convertTo=TagTextEncoding::Unspecified)
Assigns a copy of the given text.
const std::string & mimeType() const
Returns the MIME type.
TagTextEncoding dataEncoding() const
Returns the data encoding.
void clearDataAndMetadata()
Wipes assigned data including meta data.
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Contains all classes and functions of the TagInfo library.
TAG_PARSER_EXPORT std::string_view mp4TagMediaTypeName(Mp4TagMediaTypeId tagMediaTypeId)
TAG_PARSER_EXPORT std::string_view mp4TagContentRatingName(Mp4TagContentRatingId tagContentRatingId)
std::uint8_t Mp4TagContentRatingId
std::uint8_t Mp4TagMediaTypeId
TAG_PARSER_EXPORT std::string_view mediaTypeName(MediaType mediaType)
Returns the string representation for the specified mediaType.
TAG_PARSER_EXPORT std::optional< Mp4TagContentRating > mp4TagContentRatingId(std::string_view tagContentRatingName)
TAG_PARSER_EXPORT std::optional< Mp4TagMediaType > mp4TagMediaTypeId(std::string_view tagMediaTypeName)