Improve handling of MP4 tags
* Preserve multiple data atoms * Assume JPEG as raw data type when making cover field (instead of UTF-8) * Split certain functions * Simplify code
This commit is contained in:
parent
047d739918
commit
84183aaf02
|
@ -418,8 +418,7 @@ Mp4TagMaker::Mp4TagMaker(Mp4Tag &tag, Diagnostics &diag)
|
||||||
for (auto &field : m_tag.fields()) {
|
for (auto &field : m_tag.fields()) {
|
||||||
if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
|
if (!field.second.value().isEmpty() && (!m_omitPreDefinedGenre || field.first != Mp4TagAtomIds::PreDefinedGenre)) {
|
||||||
try {
|
try {
|
||||||
m_maker.emplace_back(field.second.prepareMaking(diag));
|
m_ilstSize += m_maker.emplace_back(field.second.prepareMaking(diag)).requiredSize();
|
||||||
m_ilstSize += m_maker.back().requiredSize();
|
|
||||||
} catch (const Failure &) {
|
} catch (const Failure &) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,19 +90,27 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild, Diagnostics &diag)
|
||||||
diag.emplace_back(DiagLevel::Warning, "Truncated child atom \"data\" in tag atom (ilst child) found. (will be ignored)", context);
|
diag.emplace_back(DiagLevel::Warning, "Truncated child atom \"data\" in tag atom (ilst child) found. (will be ignored)", context);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
auto *val = &value();
|
||||||
|
auto *rawDataType = &m_parsedRawDataType;
|
||||||
|
auto *countryIndicator = &m_countryIndicator;
|
||||||
|
auto *languageIndicator = &m_langIndicator;
|
||||||
if (++dataAtomFound > 1) {
|
if (++dataAtomFound > 1) {
|
||||||
if (dataAtomFound == 2) {
|
if (dataAtomFound == 2) {
|
||||||
diag.emplace_back(
|
diag.emplace_back(
|
||||||
DiagLevel::Warning, "Multiple \"data\" child atom in tag atom (ilst child) found. (will be ignored)", context);
|
DiagLevel::Warning, "Multiple \"data\" child atom in tag atom (ilst child) found. (will be ignored)", context);
|
||||||
}
|
}
|
||||||
continue;
|
auto &additionalData = m_additionalData.emplace_back();
|
||||||
|
val = &additionalData.value;
|
||||||
|
rawDataType = &additionalData.rawDataType;
|
||||||
|
countryIndicator = &additionalData.countryIndicator;
|
||||||
|
languageIndicator = &additionalData.languageIndicator;
|
||||||
}
|
}
|
||||||
stream.seekg(static_cast<streamoff>(dataAtom->dataOffset()));
|
stream.seekg(static_cast<streamoff>(dataAtom->dataOffset()));
|
||||||
if (reader.readByte() != 0) {
|
if (reader.readByte() != 0) {
|
||||||
diag.emplace_back(DiagLevel::Warning,
|
diag.emplace_back(DiagLevel::Warning,
|
||||||
"The version indicator byte is not zero, the tag atom might be unsupported and hence not be parsed correctly.", context);
|
"The version indicator byte is not zero, the tag atom might be unsupported and hence not be parsed correctly.", context);
|
||||||
}
|
}
|
||||||
setTypeInfo(m_parsedRawDataType = reader.readUInt24BE());
|
setTypeInfo(*rawDataType = reader.readUInt24BE());
|
||||||
try { // try to show warning if parsed raw data type differs from expected raw data type for this atom id
|
try { // try to show warning if parsed raw data type differs from expected raw data type for this atom id
|
||||||
const vector<std::uint32_t> expectedRawDataTypes = this->expectedRawDataTypes();
|
const vector<std::uint32_t> expectedRawDataTypes = this->expectedRawDataTypes();
|
||||||
if (find(expectedRawDataTypes.cbegin(), expectedRawDataTypes.cend(), m_parsedRawDataType) == expectedRawDataTypes.cend()) {
|
if (find(expectedRawDataTypes.cbegin(), expectedRawDataTypes.cend(), m_parsedRawDataType) == expectedRawDataTypes.cend()) {
|
||||||
|
@ -111,13 +119,13 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild, Diagnostics &diag)
|
||||||
} catch (const Failure &) {
|
} catch (const Failure &) {
|
||||||
// tag id is unknown, it is not possible to validate parsed data type
|
// tag id is unknown, it is not possible to validate parsed data type
|
||||||
}
|
}
|
||||||
m_countryIndicator = reader.readUInt16BE(); // FIXME: use locale within the tag value
|
*countryIndicator = reader.readUInt16BE(); // FIXME: use locale within the tag value
|
||||||
m_langIndicator = reader.readUInt16BE(); // FIXME: use locale within the tag value
|
*languageIndicator = reader.readUInt16BE(); // FIXME: use locale within the tag value
|
||||||
switch (m_parsedRawDataType) {
|
switch (*rawDataType) {
|
||||||
case RawDataType::Utf8:
|
case RawDataType::Utf8:
|
||||||
case RawDataType::Utf16:
|
case RawDataType::Utf16:
|
||||||
stream.seekg(static_cast<streamoff>(dataAtom->dataOffset() + 8));
|
stream.seekg(static_cast<streamoff>(dataAtom->dataOffset() + 8));
|
||||||
value().assignText(reader.readString(dataAtom->dataSize() - 8),
|
val->assignText(reader.readString(dataAtom->dataSize() - 8),
|
||||||
(m_parsedRawDataType == RawDataType::Utf16) ? TagTextEncoding::Utf16BigEndian : TagTextEncoding::Utf8);
|
(m_parsedRawDataType == RawDataType::Utf16) ? TagTextEncoding::Utf16BigEndian : TagTextEncoding::Utf8);
|
||||||
break;
|
break;
|
||||||
case RawDataType::Gif:
|
case RawDataType::Gif:
|
||||||
|
@ -126,23 +134,23 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild, Diagnostics &diag)
|
||||||
case RawDataType::Bmp: {
|
case RawDataType::Bmp: {
|
||||||
switch (m_parsedRawDataType) {
|
switch (m_parsedRawDataType) {
|
||||||
case RawDataType::Gif:
|
case RawDataType::Gif:
|
||||||
value().setMimeType("image/gif");
|
val->setMimeType("image/gif");
|
||||||
break;
|
break;
|
||||||
case RawDataType::Jpeg:
|
case RawDataType::Jpeg:
|
||||||
value().setMimeType("image/jpeg");
|
val->setMimeType("image/jpeg");
|
||||||
break;
|
break;
|
||||||
case RawDataType::Png:
|
case RawDataType::Png:
|
||||||
value().setMimeType("image/png");
|
val->setMimeType("image/png");
|
||||||
break;
|
break;
|
||||||
case RawDataType::Bmp:
|
case RawDataType::Bmp:
|
||||||
value().setMimeType("image/bmp");
|
val->setMimeType("image/bmp");
|
||||||
break;
|
break;
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
const auto coverSize = static_cast<streamoff>(dataAtom->dataSize() - 8);
|
const auto coverSize = static_cast<streamoff>(dataAtom->dataSize() - 8);
|
||||||
auto coverData = make_unique<char[]>(static_cast<size_t>(coverSize));
|
auto coverData = make_unique<char[]>(static_cast<size_t>(coverSize));
|
||||||
stream.read(coverData.get(), coverSize);
|
stream.read(coverData.get(), coverSize);
|
||||||
value().assignData(move(coverData), static_cast<size_t>(coverSize), TagDataType::Picture);
|
val->assignData(move(coverData), static_cast<size_t>(coverSize), TagDataType::Picture);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RawDataType::BeSignedInt: {
|
case RawDataType::BeSignedInt: {
|
||||||
|
@ -159,10 +167,10 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild, Diagnostics &diag)
|
||||||
}
|
}
|
||||||
switch (ilstChild.id()) {
|
switch (ilstChild.id()) {
|
||||||
case PreDefinedGenre: // consider number as standard genre index
|
case PreDefinedGenre: // consider number as standard genre index
|
||||||
value().assignStandardGenreIndex(number);
|
val->assignStandardGenreIndex(number);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
value().assignInteger(number);
|
val->assignInteger(number);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -180,10 +188,10 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild, Diagnostics &diag)
|
||||||
}
|
}
|
||||||
switch (ilstChild.id()) {
|
switch (ilstChild.id()) {
|
||||||
case PreDefinedGenre: // consider number as standard genre index
|
case PreDefinedGenre: // consider number as standard genre index
|
||||||
value().assignStandardGenreIndex(number - 1);
|
val->assignStandardGenreIndex(number - 1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
value().assignInteger(number);
|
val->assignInteger(number);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -203,14 +211,14 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild, Diagnostics &diag)
|
||||||
if (dataAtom->dataSize() >= (8 + 6)) {
|
if (dataAtom->dataSize() >= (8 + 6)) {
|
||||||
total = reader.readUInt16BE();
|
total = reader.readUInt16BE();
|
||||||
}
|
}
|
||||||
value().assignPosition(PositionInSet(pos, total));
|
val->assignPosition(PositionInSet(pos, total));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PreDefinedGenre:
|
case PreDefinedGenre:
|
||||||
if (dataAtom->dataSize() < (8 + 2)) {
|
if (dataAtom->dataSize() < (8 + 2)) {
|
||||||
diag.emplace_back(DiagLevel::Warning, "Genre index is truncated.", context);
|
diag.emplace_back(DiagLevel::Warning, "Genre index is truncated.", context);
|
||||||
} else {
|
} else {
|
||||||
value().assignStandardGenreIndex(reader.readUInt16BE() - 1);
|
val->assignStandardGenreIndex(reader.readUInt16BE() - 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default: // no supported data type, read raw data
|
default: // no supported data type, read raw data
|
||||||
|
@ -218,9 +226,9 @@ void Mp4TagField::reparse(Mp4Atom &ilstChild, Diagnostics &diag)
|
||||||
auto data = make_unique<char[]>(static_cast<size_t>(dataSize));
|
auto data = make_unique<char[]>(static_cast<size_t>(dataSize));
|
||||||
stream.read(data.get(), dataSize);
|
stream.read(data.get(), dataSize);
|
||||||
if (ilstChild.id() == Mp4TagAtomIds::Cover) {
|
if (ilstChild.id() == Mp4TagAtomIds::Cover) {
|
||||||
value().assignData(move(data), static_cast<size_t>(dataSize), TagDataType::Picture);
|
val->assignData(move(data), static_cast<size_t>(dataSize), TagDataType::Picture);
|
||||||
} else {
|
} else {
|
||||||
value().assignData(move(data), static_cast<size_t>(dataSize), TagDataType::Undefined);
|
val->assignData(move(data), static_cast<size_t>(dataSize), TagDataType::Undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,14 +357,13 @@ std::vector<std::uint32_t> Mp4TagField::expectedRawDataTypes() const
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns an appropriate raw data type.
|
* \brief Returns an appropriate raw data type.
|
||||||
*
|
* \return
|
||||||
* Returns the type info if assigned; otherwise returns a raw data type considered as appropriate for
|
* Returns the type info if assigned; otherwise returns a raw data type considered as appropriate for the
|
||||||
* the ID of the field. The latter is supposed to work for all supported tag fields IDs (those where a
|
* ID of the field and its value.
|
||||||
* conversion to KnownField via Mp4Tag exists).
|
* \sa See Mp4TagField::appropriateRawDataTypeForValue() for the behavior if no type info is assigned.
|
||||||
*/
|
*/
|
||||||
std::uint32_t Mp4TagField::appropriateRawDataType() const
|
std::uint32_t Mp4TagField::appropriateRawDataType() const
|
||||||
{
|
{
|
||||||
using namespace Mp4TagAtomIds;
|
|
||||||
if (isTypeInfoAssigned()) {
|
if (isTypeInfoAssigned()) {
|
||||||
// obtain raw data type from tag field if present
|
// obtain raw data type from tag field if present
|
||||||
return typeInfo();
|
return typeInfo();
|
||||||
|
@ -365,6 +372,20 @@ std::uint32_t Mp4TagField::appropriateRawDataType() const
|
||||||
// there is no raw data type assigned (tag field was not present in original file and
|
// there is no raw data type assigned (tag field was not present in original file and
|
||||||
// has been inserted by the library's user without type)
|
// has been inserted by the library's user without type)
|
||||||
// -> try to derive appropriate raw data type from atom ID
|
// -> try to derive appropriate raw data type from atom ID
|
||||||
|
return appropriateRawDataTypeForValue(value());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns an appropriate raw data type.
|
||||||
|
* \returns
|
||||||
|
* Returns a raw data type considered as appropriate for the ID of the field and the specified \a value.
|
||||||
|
* \throws
|
||||||
|
* Throws TagParser::Failure if an appropriate raw data type can not be determined. It is possible to determine
|
||||||
|
* the raw data type for all supported tag field IDs (those where a conversion to KnownField via Mp4Tag exists).
|
||||||
|
*/
|
||||||
|
std::uint32_t Mp4TagField::appropriateRawDataTypeForValue(const TagValue &value) const
|
||||||
|
{
|
||||||
|
using namespace Mp4TagAtomIds;
|
||||||
switch (id()) {
|
switch (id()) {
|
||||||
case Album:
|
case Album:
|
||||||
case Artist:
|
case Artist:
|
||||||
|
@ -381,7 +402,7 @@ std::uint32_t Mp4TagField::appropriateRawDataType() const
|
||||||
case Performers:
|
case Performers:
|
||||||
case Lyricist:
|
case Lyricist:
|
||||||
case AlbumArtist:
|
case AlbumArtist:
|
||||||
switch (value().dataEncoding()) {
|
switch (value.dataEncoding()) {
|
||||||
case TagTextEncoding::Utf8:
|
case TagTextEncoding::Utf8:
|
||||||
return RawDataType::Utf8;
|
return RawDataType::Utf8;
|
||||||
case TagTextEncoding::Utf16BigEndian:
|
case TagTextEncoding::Utf16BigEndian:
|
||||||
|
@ -397,7 +418,7 @@ std::uint32_t Mp4TagField::appropriateRawDataType() const
|
||||||
case Rating:
|
case Rating:
|
||||||
return RawDataType::BeSignedInt;
|
return RawDataType::BeSignedInt;
|
||||||
case Cover: {
|
case Cover: {
|
||||||
const string &mimeType = value().mimeType();
|
const string &mimeType = value.mimeType();
|
||||||
if (mimeType == "image/jpg" || mimeType == "image/jpeg") { // "well-known" type
|
if (mimeType == "image/jpg" || mimeType == "image/jpeg") { // "well-known" type
|
||||||
return RawDataType::Jpeg;
|
return RawDataType::Jpeg;
|
||||||
} else if (mimeType == "image/png") {
|
} else if (mimeType == "image/png") {
|
||||||
|
@ -410,7 +431,7 @@ std::uint32_t Mp4TagField::appropriateRawDataType() const
|
||||||
if (mean() != Mp4TagExtendedMeanIds::iTunes) {
|
if (mean() != Mp4TagExtendedMeanIds::iTunes) {
|
||||||
throw Failure();
|
throw Failure();
|
||||||
}
|
}
|
||||||
switch (value().dataEncoding()) {
|
switch (value.dataEncoding()) {
|
||||||
case TagTextEncoding::Utf8:
|
case TagTextEncoding::Utf8:
|
||||||
return RawDataType::Utf8;
|
return RawDataType::Utf8;
|
||||||
case TagTextEncoding::Utf16BigEndian:
|
case TagTextEncoding::Utf16BigEndian:
|
||||||
|
@ -438,6 +459,13 @@ void Mp4TagField::reset()
|
||||||
m_langIndicator = 0;
|
m_langIndicator = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \cond
|
||||||
|
Mp4TagFieldMaker::Data::Data()
|
||||||
|
: convertedData(stringstream::in | stringstream::out | stringstream::binary)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
/// \endcond
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \class TagParser::Mp4TagFieldMaker
|
* \class TagParser::Mp4TagFieldMaker
|
||||||
* \brief The Mp4TagFieldMaker class helps making tag fields.
|
* \brief The Mp4TagFieldMaker class helps making tag fields.
|
||||||
|
@ -451,12 +479,11 @@ void Mp4TagField::reset()
|
||||||
*/
|
*/
|
||||||
Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag)
|
Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag)
|
||||||
: m_field(field)
|
: m_field(field)
|
||||||
, m_convertedData(stringstream::in | stringstream::out | stringstream::binary)
|
, m_writer(nullptr)
|
||||||
, m_writer(&m_convertedData)
|
, m_totalSize(0)
|
||||||
, m_rawDataType(0)
|
|
||||||
{
|
{
|
||||||
if (!m_field.id()) {
|
if (!m_field.id()) {
|
||||||
diag.emplace_back(DiagLevel::Warning, "Invalid tag atom id.", "making MP4 tag field");
|
diag.emplace_back(DiagLevel::Warning, "Invalid tag atom ID.", "making MP4 tag field");
|
||||||
throw InvalidDataException();
|
throw InvalidDataException();
|
||||||
}
|
}
|
||||||
const string context("making MP4 tag field " + Mp4TagField::fieldIdToString(m_field.id()));
|
const string context("making MP4 tag field " + Mp4TagField::fieldIdToString(m_field.id()));
|
||||||
|
@ -465,33 +492,63 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag)
|
||||||
throw InvalidDataException();
|
throw InvalidDataException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate size for name and mean
|
||||||
|
m_totalSize = 8 + (m_field.name().empty() ? 0 : (12 + m_field.name().size())) + (m_field.mean().empty() ? 0 : (12 + m_field.mean().size()));
|
||||||
|
|
||||||
|
// prepare making data atom and calculate the expected size
|
||||||
|
m_totalSize += prepareDataAtom(field.value(), field.countryIndicator(), field.languageIndicator(), context, diag);
|
||||||
|
for (const auto &additionalData : m_field.additionalData()) {
|
||||||
|
m_totalSize += prepareDataAtom(additionalData.value, additionalData.countryIndicator, additionalData.languageIndicator, context, diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_totalSize > numeric_limits<std::uint32_t>::max()) {
|
||||||
|
diag.emplace_back(DiagLevel::Critical, "Making a such big MP4 tag field is not possible.", context);
|
||||||
|
throw NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Prepares making a data atom for the specified \a value.
|
||||||
|
*/
|
||||||
|
std::uint64_t Mp4TagFieldMaker::prepareDataAtom(
|
||||||
|
const TagValue &value, std::uint16_t countryIndicator, std::uint16_t languageIndicator, const std::string &context, Diagnostics &diag)
|
||||||
|
{
|
||||||
|
// add new data entry
|
||||||
|
auto &data = m_data.emplace_back();
|
||||||
|
m_writer.setStream(&data.convertedData);
|
||||||
|
|
||||||
|
// assign local info
|
||||||
|
// FIXME: use locale within the tag value instead of just passing through current values
|
||||||
|
data.countryIndicator = countryIndicator;
|
||||||
|
data.languageIndicator = languageIndicator;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// try to use appropriate raw data type
|
// try to use appropriate raw data type
|
||||||
m_rawDataType = m_field.appropriateRawDataType();
|
data.rawType = m_field.isTypeInfoAssigned() ? m_field.typeInfo() : m_field.appropriateRawDataTypeForValue(value);
|
||||||
} catch (const Failure &) {
|
} catch (const Failure &) {
|
||||||
// unable to obtain appropriate raw data type
|
// unable to obtain appropriate raw data type
|
||||||
if (m_field.id() == Mp4TagAtomIds::Cover) {
|
if (m_field.id() == Mp4TagAtomIds::Cover) {
|
||||||
// assume JPEG image
|
// assume JPEG image
|
||||||
m_rawDataType = RawDataType::Utf8;
|
data.rawType = RawDataType::Jpeg;
|
||||||
diag.emplace_back(
|
diag.emplace_back(
|
||||||
DiagLevel::Warning, "It was not possible to find an appropriate raw data type id. JPEG image will be assumed.", context);
|
DiagLevel::Warning, "It was not possible to find an appropriate raw data type id. JPEG image will be assumed.", context);
|
||||||
} else {
|
} else {
|
||||||
// assume UTF-8 text
|
// assume UTF-8 text
|
||||||
m_rawDataType = RawDataType::Utf8;
|
data.rawType = RawDataType::Utf8;
|
||||||
diag.emplace_back(DiagLevel::Warning, "It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
|
diag.emplace_back(DiagLevel::Warning, "It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!m_field.value().isEmpty()) { // there might be only mean and name info, but no data
|
if (!value.isEmpty()) { // there might be only mean and name info, but no data
|
||||||
m_convertedData.exceptions(std::stringstream::failbit | std::stringstream::badbit);
|
data.convertedData.exceptions(std::stringstream::failbit | std::stringstream::badbit);
|
||||||
switch (m_rawDataType) {
|
switch (data.rawType) {
|
||||||
case RawDataType::Utf8:
|
case RawDataType::Utf8:
|
||||||
case RawDataType::Utf16:
|
case RawDataType::Utf16:
|
||||||
m_writer.writeString(m_field.value().toString());
|
m_writer.writeString(value.toString());
|
||||||
break;
|
break;
|
||||||
case RawDataType::BeSignedInt: {
|
case RawDataType::BeSignedInt: {
|
||||||
int number = m_field.value().toInteger();
|
int number = value.toInteger();
|
||||||
if (number <= numeric_limits<std::int16_t>::max() && number >= numeric_limits<std::int16_t>::min()) {
|
if (number <= numeric_limits<std::int16_t>::max() && number >= numeric_limits<std::int16_t>::min()) {
|
||||||
m_writer.writeInt16BE(static_cast<std::int16_t>(number));
|
m_writer.writeInt16BE(static_cast<std::int16_t>(number));
|
||||||
} else {
|
} else {
|
||||||
|
@ -500,7 +557,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RawDataType::BeUnsignedInt: {
|
case RawDataType::BeUnsignedInt: {
|
||||||
int number = m_field.value().toInteger();
|
int number = value.toInteger();
|
||||||
if (number <= numeric_limits<std::uint16_t>::max() && number >= numeric_limits<std::uint16_t>::min()) {
|
if (number <= numeric_limits<std::uint16_t>::max() && number >= numeric_limits<std::uint16_t>::min()) {
|
||||||
m_writer.writeUInt16BE(static_cast<std::uint16_t>(number));
|
m_writer.writeUInt16BE(static_cast<std::uint16_t>(number));
|
||||||
} else if (number > 0) {
|
} else if (number > 0) {
|
||||||
|
@ -522,7 +579,7 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag)
|
||||||
// raw data type 0 is used, information is stored as pair of unsigned integers
|
// raw data type 0 is used, information is stored as pair of unsigned integers
|
||||||
case Mp4TagAtomIds::TrackPosition:
|
case Mp4TagAtomIds::TrackPosition:
|
||||||
case Mp4TagAtomIds::DiskPosition: {
|
case Mp4TagAtomIds::DiskPosition: {
|
||||||
PositionInSet pos = m_field.value().toPositionInSet();
|
PositionInSet pos = value.toPositionInSet();
|
||||||
m_writer.writeInt32BE(pos.position());
|
m_writer.writeInt32BE(pos.position());
|
||||||
if (pos.total() <= numeric_limits<std::int16_t>::max()) {
|
if (pos.total() <= numeric_limits<std::int16_t>::max()) {
|
||||||
m_writer.writeInt16BE(static_cast<std::int16_t>(pos.total()));
|
m_writer.writeInt16BE(static_cast<std::int16_t>(pos.total()));
|
||||||
|
@ -535,32 +592,31 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Mp4TagAtomIds::PreDefinedGenre:
|
case Mp4TagAtomIds::PreDefinedGenre:
|
||||||
m_writer.writeUInt16BE(static_cast<std::uint16_t>(m_field.value().toStandardGenreIndex()));
|
m_writer.writeUInt16BE(static_cast<std::uint16_t>(value.toStandardGenreIndex()));
|
||||||
break;
|
break;
|
||||||
default:; // leave converted data empty to write original data later
|
default:; // leave converted data empty to write original data later
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const ConversionException &ex) {
|
} catch (const ConversionException &e) {
|
||||||
// it was not possible to perform required conversions
|
// it was not possible to perform required conversions
|
||||||
if (char_traits<char>::length(ex.what())) {
|
if (char_traits<char>::length(e.what())) {
|
||||||
diag.emplace_back(DiagLevel::Critical, ex.what(), context);
|
diag.emplace_back(DiagLevel::Critical, e.what(), context);
|
||||||
} else {
|
} else {
|
||||||
diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
|
diag.emplace_back(DiagLevel::Critical, "The assigned tag value can not be converted to be written appropriately.", context);
|
||||||
}
|
}
|
||||||
throw InvalidDataException();
|
throw InvalidDataException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate data size
|
// calculate data size; assign raw data
|
||||||
m_dataSize
|
if (value.isEmpty()) {
|
||||||
= m_field.value().isEmpty() ? 0 : (m_convertedData.tellp() ? static_cast<size_t>(m_convertedData.tellp()) : m_field.value().dataSize());
|
return data.size = 0;
|
||||||
m_totalSize = 8 // calculate entire size
|
} else if (data.convertedData.tellp()) {
|
||||||
+ (m_field.name().empty() ? 0 : (12 + m_field.name().length())) + (m_field.mean().empty() ? 0 : (12 + m_field.mean().length()))
|
data.size = static_cast<std::size_t>(data.convertedData.tellp());
|
||||||
+ (m_dataSize ? (16 + m_dataSize) : 0);
|
} else {
|
||||||
if (m_totalSize > numeric_limits<std::uint32_t>::max()) {
|
data.rawData = std::string_view(value.dataPointer(), data.size = value.dataSize());
|
||||||
diag.emplace_back(DiagLevel::Critical, "Making a such big MP4 tag field is not supported.", context);
|
|
||||||
throw NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
return data.size += 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -577,33 +633,37 @@ void Mp4TagFieldMaker::make(ostream &stream)
|
||||||
m_writer.writeUInt32BE(static_cast<std::uint32_t>(m_totalSize));
|
m_writer.writeUInt32BE(static_cast<std::uint32_t>(m_totalSize));
|
||||||
// id of tag atom
|
// id of tag atom
|
||||||
m_writer.writeUInt32BE(m_field.id());
|
m_writer.writeUInt32BE(m_field.id());
|
||||||
|
// write "mean" atom
|
||||||
if (!m_field.mean().empty()) {
|
if (!m_field.mean().empty()) {
|
||||||
// write "mean"
|
|
||||||
m_writer.writeUInt32BE(static_cast<std::uint32_t>(12 + m_field.mean().size()));
|
m_writer.writeUInt32BE(static_cast<std::uint32_t>(12 + m_field.mean().size()));
|
||||||
m_writer.writeUInt32BE(Mp4AtomIds::Mean);
|
m_writer.writeUInt32BE(Mp4AtomIds::Mean);
|
||||||
m_writer.writeUInt32BE(0);
|
m_writer.writeUInt32BE(0);
|
||||||
m_writer.writeString(m_field.mean());
|
m_writer.writeString(m_field.mean());
|
||||||
}
|
}
|
||||||
|
// write "name" atom
|
||||||
if (!m_field.name().empty()) {
|
if (!m_field.name().empty()) {
|
||||||
// write "name"
|
|
||||||
m_writer.writeUInt32BE(static_cast<std::uint32_t>(12 + m_field.name().length()));
|
m_writer.writeUInt32BE(static_cast<std::uint32_t>(12 + m_field.name().length()));
|
||||||
m_writer.writeUInt32BE(Mp4AtomIds::Name);
|
m_writer.writeUInt32BE(Mp4AtomIds::Name);
|
||||||
m_writer.writeUInt32BE(0);
|
m_writer.writeUInt32BE(0);
|
||||||
m_writer.writeString(m_field.name());
|
m_writer.writeString(m_field.name());
|
||||||
}
|
}
|
||||||
if (!m_field.value().isEmpty()) { // write data
|
// write "data" atoms
|
||||||
m_writer.writeUInt32BE(static_cast<std::uint32_t>(16 + m_dataSize)); // size of data atom
|
for (auto &data : m_data) {
|
||||||
|
if (!data.size) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_writer.writeUInt32BE(static_cast<std::uint32_t>(data.size)); // size of data atom
|
||||||
m_writer.writeUInt32BE(Mp4AtomIds::Data); // id of data atom
|
m_writer.writeUInt32BE(Mp4AtomIds::Data); // id of data atom
|
||||||
m_writer.writeByte(0); // version
|
m_writer.writeByte(0); // version
|
||||||
m_writer.writeUInt24BE(m_rawDataType);
|
m_writer.writeUInt24BE(data.rawType);
|
||||||
m_writer.writeUInt16BE(m_field.countryIndicator()); // FIXME: use locale within the tag value
|
m_writer.writeUInt16BE(data.countryIndicator);
|
||||||
m_writer.writeUInt16BE(m_field.languageIndicator()); // FIXME: use locale within the tag value
|
m_writer.writeUInt16BE(data.languageIndicator);
|
||||||
if (m_convertedData.tellp()) {
|
if (data.convertedData.tellp()) {
|
||||||
// write converted data
|
// write converted data
|
||||||
stream << m_convertedData.rdbuf();
|
stream << data.convertedData.rdbuf();
|
||||||
} else {
|
} else {
|
||||||
// no conversion was needed, write data directly from tag value
|
// no conversion was needed, write data directly from tag value
|
||||||
stream.write(m_field.value().dataPointer(), static_cast<streamoff>(m_field.value().dataSize()));
|
stream.write(data.rawData.data(), static_cast<std::streamoff>(data.rawData.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,18 +64,32 @@ class TAG_PARSER_EXPORT Mp4TagFieldMaker {
|
||||||
friend class Mp4TagField;
|
friend class Mp4TagField;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
Mp4TagFieldMaker(Mp4TagFieldMaker &&) = default;
|
||||||
void make(std::ostream &stream);
|
void make(std::ostream &stream);
|
||||||
const Mp4TagField &field() const;
|
const Mp4TagField &field() const;
|
||||||
std::uint64_t requiredSize() const;
|
std::uint64_t requiredSize() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// \cond
|
||||||
|
struct Data {
|
||||||
|
Data();
|
||||||
|
Data(Data &&) = default;
|
||||||
|
std::string_view rawData;
|
||||||
|
std::stringstream convertedData;
|
||||||
|
std::uint64_t size = 0;
|
||||||
|
std::uint32_t rawType = 0;
|
||||||
|
std::uint16_t countryIndicator = 0;
|
||||||
|
std::uint16_t languageIndicator = 0;
|
||||||
|
};
|
||||||
|
/// \endcond
|
||||||
|
|
||||||
Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag);
|
Mp4TagFieldMaker(Mp4TagField &field, Diagnostics &diag);
|
||||||
|
std::uint64_t prepareDataAtom(
|
||||||
|
const TagValue &value, std::uint16_t countryIndicator, std::uint16_t languageIndicator, const std::string &context, Diagnostics &diag);
|
||||||
|
|
||||||
Mp4TagField &m_field;
|
Mp4TagField &m_field;
|
||||||
std::stringstream m_convertedData;
|
|
||||||
CppUtilities::BinaryWriter m_writer;
|
CppUtilities::BinaryWriter m_writer;
|
||||||
std::uint32_t m_rawDataType;
|
std::vector<Data> m_data;
|
||||||
std::uint64_t m_dataSize;
|
|
||||||
std::uint64_t m_totalSize;
|
std::uint64_t m_totalSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,6 +113,13 @@ class TAG_PARSER_EXPORT Mp4TagField : public TagField<Mp4TagField> {
|
||||||
friend class TagField<Mp4TagField>;
|
friend class TagField<Mp4TagField>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
struct AdditionalData {
|
||||||
|
TagValue value;
|
||||||
|
std::uint32_t rawDataType = 0;
|
||||||
|
std::uint16_t countryIndicator = 0;
|
||||||
|
std::uint16_t languageIndicator = 0;
|
||||||
|
};
|
||||||
|
|
||||||
Mp4TagField();
|
Mp4TagField();
|
||||||
Mp4TagField(IdentifierType id, const TagValue &value);
|
Mp4TagField(IdentifierType id, const TagValue &value);
|
||||||
Mp4TagField(const std::string &mean, const std::string &name, const TagValue &value);
|
Mp4TagField(const std::string &mean, const std::string &name, const TagValue &value);
|
||||||
|
@ -107,6 +128,8 @@ public:
|
||||||
Mp4TagFieldMaker prepareMaking(Diagnostics &diag);
|
Mp4TagFieldMaker prepareMaking(Diagnostics &diag);
|
||||||
void make(std::ostream &stream, Diagnostics &diag);
|
void make(std::ostream &stream, Diagnostics &diag);
|
||||||
|
|
||||||
|
const std::vector<AdditionalData> &additionalData() const;
|
||||||
|
std::vector<AdditionalData> &additionalData();
|
||||||
bool isAdditionalTypeInfoUsed() const;
|
bool isAdditionalTypeInfoUsed() const;
|
||||||
const std::string &name() const;
|
const std::string &name() const;
|
||||||
void setName(const std::string &name);
|
void setName(const std::string &name);
|
||||||
|
@ -118,6 +141,7 @@ public:
|
||||||
bool supportsNestedFields() const;
|
bool supportsNestedFields() const;
|
||||||
std::vector<std::uint32_t> expectedRawDataTypes() const;
|
std::vector<std::uint32_t> expectedRawDataTypes() const;
|
||||||
std::uint32_t appropriateRawDataType() const;
|
std::uint32_t appropriateRawDataType() const;
|
||||||
|
std::uint32_t appropriateRawDataTypeForValue(const TagValue &value) const;
|
||||||
|
|
||||||
static IdentifierType fieldIdFromString(const char *idString, std::size_t idStringSize = std::string::npos);
|
static IdentifierType fieldIdFromString(const char *idString, std::size_t idStringSize = std::string::npos);
|
||||||
static std::string fieldIdToString(IdentifierType id);
|
static std::string fieldIdToString(IdentifierType id);
|
||||||
|
@ -126,11 +150,30 @@ private:
|
||||||
void reset();
|
void reset();
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
std::string m_mean;
|
std::string m_mean;
|
||||||
|
std::vector<AdditionalData> m_additionalData;
|
||||||
std::uint32_t m_parsedRawDataType;
|
std::uint32_t m_parsedRawDataType;
|
||||||
std::uint16_t m_countryIndicator;
|
std::uint16_t m_countryIndicator;
|
||||||
std::uint16_t m_langIndicator;
|
std::uint16_t m_langIndicator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns additional data (and the corresponding raw data type, country and language).
|
||||||
|
* \remarks Some files seen in the wild have multiple data atoms. This function allows to access the data from additional atoms.
|
||||||
|
*/
|
||||||
|
inline const std::vector<Mp4TagField::AdditionalData> &Mp4TagField::additionalData() const
|
||||||
|
{
|
||||||
|
return m_additionalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns additional data (and the corresponding raw data type, country and language).
|
||||||
|
* \remarks Some files seen in the wild have multiple data atoms. This function allows to access the data from additional atoms.
|
||||||
|
*/
|
||||||
|
inline std::vector<Mp4TagField::AdditionalData> &Mp4TagField::additionalData()
|
||||||
|
{
|
||||||
|
return m_additionalData;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns whether the additional type info is used.
|
* \brief Returns whether the additional type info is used.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue