6 #include "../exceptions.h"
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/io/binaryreader.h>
10 #include <c++utilities/io/binarywriter.h>
29 Mp4TagField::Mp4TagField()
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;
85 for (
Mp4Atom *dataAtom = ilstChild.
firstChild(); dataAtom; dataAtom = dataAtom->nextSibling()) {
87 dataAtom->parse(diag);
89 if (dataAtom->dataSize() < 8) {
90 diag.emplace_back(DiagLevel::Warning,
"Truncated child atom \"data\" in tag atom (ilst child) found. (will be ignored)", context);
93 if (++dataAtomFound > 1) {
94 if (dataAtomFound == 2) {
96 DiagLevel::Warning,
"Multiple \"data\" child atom in tag atom (ilst child) found. (will be ignored)", context);
100 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset()));
101 if (reader.readByte() != 0) {
102 diag.emplace_back(DiagLevel::Warning,
103 "The version indicator byte is not zero, the tag atom might be unsupported and hence not be parsed correctly.", context);
105 setTypeInfo(m_parsedRawDataType = reader.readUInt24BE());
109 diag.emplace_back(DiagLevel::Warning,
"Unexpected data type indicator found.", context);
114 m_countryIndicator = reader.readUInt16BE();
115 m_langIndicator = reader.readUInt16BE();
116 switch (m_parsedRawDataType) {
119 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 8));
127 switch (m_parsedRawDataType) {
142 const auto coverSize =
static_cast<streamoff
>(dataAtom->dataSize() - 8);
143 auto coverData = make_unique<char[]>(
static_cast<size_t>(coverSize));
144 stream.read(coverData.get(), coverSize);
145 value().
assignData(move(coverData),
static_cast<size_t>(coverSize), TagDataType::Picture);
150 if (dataAtom->dataSize() > (8 + 4)) {
151 diag.emplace_back(DiagLevel::Warning,
"Data atom stores integer of invalid size. Trying to read data anyways.", context);
153 if (dataAtom->dataSize() >= (8 + 4)) {
154 number = reader.readInt32BE();
155 }
else if (dataAtom->dataSize() == (8 + 2)) {
156 number = reader.readInt16BE();
157 }
else if (dataAtom->dataSize() == (8 + 1)) {
158 number = reader.readChar();
160 switch (ilstChild.
id()) {
171 if (dataAtom->dataSize() > (8 + 4)) {
172 diag.emplace_back(DiagLevel::Warning,
"Data atom stores integer of invalid size. Trying to read data anyways.", context);
174 if (dataAtom->dataSize() >= (8 + 4)) {
175 number =
static_cast<int>(reader.readUInt32BE());
176 }
else if (dataAtom->dataSize() == (8 + 2)) {
177 number =
static_cast<int>(reader.readUInt16BE());
178 }
else if (dataAtom->dataSize() == (8 + 1)) {
179 number =
static_cast<int>(reader.readByte());
181 switch (ilstChild.
id()) {
191 switch (ilstChild.
id()) {
195 if (dataAtom->dataSize() < (8 + 6)) {
196 diag.emplace_back(DiagLevel::Warning,
"Track/disk position is truncated. Trying to read data anyways.", context);
198 std::uint16_t pos = 0, total = 0;
199 if (dataAtom->dataSize() >= (8 + 4)) {
200 stream.seekg(2, ios_base::cur);
201 pos = reader.readUInt16BE();
203 if (dataAtom->dataSize() >= (8 + 6)) {
204 total = reader.readUInt16BE();
210 if (dataAtom->dataSize() < (8 + 2)) {
211 diag.emplace_back(DiagLevel::Warning,
"Genre index is truncated.", context);
217 const auto dataSize =
static_cast<streamsize
>(dataAtom->dataSize() - 8);
218 auto data = make_unique<char[]>(
static_cast<size_t>(dataSize));
219 stream.read(data.get(), dataSize);
221 value().
assignData(move(data),
static_cast<size_t>(dataSize), TagDataType::Picture);
228 if (dataAtom->dataSize() < 8) {
229 diag.emplace_back(DiagLevel::Warning,
"Truncated child atom \"mean\" in tag atom (ilst child) found. (will be ignored)", context);
232 if (++meanAtomFound > 1) {
233 if (meanAtomFound == 2) {
235 DiagLevel::Warning,
"Tag atom contains more than one mean atom. The addiational mean atoms will be ignored.", context);
239 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 4));
240 m_mean = reader.readString(dataAtom->dataSize() - 4);
242 if (dataAtom->dataSize() < 4) {
243 diag.emplace_back(DiagLevel::Warning,
"Truncated child atom \"name\" in tag atom (ilst child) found. (will be ignored)", context);
246 if (++nameAtomFound > 1) {
247 if (nameAtomFound == 2) {
249 DiagLevel::Warning,
"Tag atom contains more than one name atom. The addiational name atoms will be ignored.", context);
253 stream.seekg(
static_cast<streamoff
>(dataAtom->dataOffset() + 4));
254 m_name = reader.readString(dataAtom->dataSize() - 4);
256 diag.emplace_back(DiagLevel::Warning,
257 "Unkown child atom \"" % dataAtom->idToString() +
"\" in tag atom (ilst child) found. (will be ignored)", context);
260 diag.emplace_back(DiagLevel::Warning,
"Unable to parse all children atom in tag atom (ilst child) found. (will be ignored)", context);
263 if (
value().isEmpty()) {
264 diag.emplace_back(DiagLevel::Warning,
"The field value is empty.", context);
300 using namespace Mp4TagAtomIds;
301 std::vector<std::uint32_t> res;
359 using namespace Mp4TagAtomIds;
384 switch (
value().dataEncoding()) {
387 case TagTextEncoding::Utf16BigEndian:
401 if (mimeType ==
"image/jpg" || mimeType ==
"image/jpeg") {
403 }
else if (mimeType ==
"image/png") {
405 }
else if (mimeType ==
"image/bmp") {
413 switch (
value().dataEncoding()) {
416 case TagTextEncoding::Utf16BigEndian:
432 void Mp4TagField::reset()
437 m_countryIndicator = 0;
452 Mp4TagFieldMaker::Mp4TagFieldMaker(
Mp4TagField &field, Diagnostics &diag)
454 , m_convertedData(stringstream::in | stringstream::out | stringstream::binary)
455 , m_writer(&m_convertedData)
459 diag.emplace_back(DiagLevel::Warning,
"Invalid tag atom id.",
"making MP4 tag field");
460 throw InvalidDataException();
463 if (m_field.value().isEmpty() && (!m_field.mean().empty() || !m_field.name().empty())) {
464 diag.emplace_back(DiagLevel::Critical,
"No tag value assigned.", context);
465 throw InvalidDataException();
470 m_rawDataType = m_field.appropriateRawDataType();
471 }
catch (
const Failure &) {
477 DiagLevel::Warning,
"It was not possible to find an appropriate raw data type id. JPEG image will be assumed.", context);
481 diag.emplace_back(DiagLevel::Warning,
"It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
486 if (!m_field.value().isEmpty()) {
487 m_convertedData.exceptions(std::stringstream::failbit | std::stringstream::badbit);
488 switch (m_rawDataType) {
491 m_writer.writeString(m_field.value().toString());
494 int number = m_field.value().toInteger();
495 if (number <= numeric_limits<std::int16_t>::max() && number >= numeric_limits<std::int16_t>::min()) {
496 m_writer.writeInt16BE(
static_cast<std::int16_t
>(number));
498 m_writer.writeInt32BE(number);
503 int number = m_field.value().toInteger();
504 if (number <= numeric_limits<std::uint16_t>::max() && number >= numeric_limits<std::uint16_t>::min()) {
505 m_writer.writeUInt16BE(
static_cast<std::uint16_t
>(number));
506 }
else if (number > 0) {
507 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(number));
509 throw ConversionException(
510 "Negative integer can not be assigned to the field with the ID \"" % interpretIntegerAsString<std::uint32_t>(m_field.id())
520 switch (m_field.id()) {
525 PositionInSet pos = m_field.value().toPositionInSet();
526 m_writer.writeInt32BE(pos.position());
527 if (pos.total() <= numeric_limits<std::int16_t>::max()) {
528 m_writer.writeInt16BE(
static_cast<std::int16_t
>(pos.total()));
530 throw ConversionException(
531 "Integer can not be assigned to the field with the id \"" % interpretIntegerAsString<std::uint32_t>(m_field.id())
532 +
"\" because it is to big.");
534 m_writer.writeUInt16BE(0);
538 m_writer.writeUInt16BE(
static_cast<std::uint16_t
>(m_field.value().toStandardGenreIndex()));
544 }
catch (ConversionException &ex) {
546 if (char_traits<char>::length(ex.what())) {
547 diag.emplace_back(DiagLevel::Critical, ex.what(), context);
549 diag.emplace_back(DiagLevel::Critical,
"The assigned tag value can not be converted to be written appropriately.", context);
551 throw InvalidDataException();
556 = m_field.value().isEmpty() ? 0 : (m_convertedData.tellp() ?
static_cast<size_t>(m_convertedData.tellp()) : m_field.value().dataSize());
558 + (m_field.name().empty() ? 0 : (12 + m_field.name().length())) + (m_field.mean().empty() ? 0 : (12 + m_field.mean().length()))
559 + (m_dataSize ? (16 + m_dataSize) : 0);
560 if (m_totalSize > numeric_limits<std::uint32_t>::max()) {
561 diag.emplace_back(DiagLevel::Critical,
"Making a such big MP4 tag field is not supported.", context);
562 throw NotImplementedException();
575 m_writer.setStream(&stream);
577 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(m_totalSize));
579 m_writer.writeUInt32BE(m_field.
id());
580 if (!m_field.
mean().empty()) {
582 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(12 + m_field.
mean().size()));
584 m_writer.writeUInt32BE(0);
585 m_writer.writeString(m_field.
mean());
587 if (!m_field.
name().empty()) {
589 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(12 + m_field.
name().length()));
591 m_writer.writeUInt32BE(0);
592 m_writer.writeString(m_field.
name());
595 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(16 + m_dataSize));
597 m_writer.writeByte(0);
598 m_writer.writeUInt24BE(m_rawDataType);
601 if (m_convertedData.tellp()) {
603 stream << m_convertedData.rdbuf();