2015-09-06 19:57:33 +02:00
|
|
|
#include "./mp4atom.h"
|
|
|
|
#include "./mp4container.h"
|
2018-03-07 01:17:50 +01:00
|
|
|
#include "./mp4ids.h"
|
|
|
|
#include "./mp4tag.h"
|
|
|
|
#include "./mp4track.h"
|
2015-09-06 15:42:18 +02:00
|
|
|
|
2015-09-06 19:57:33 +02:00
|
|
|
#include "../exceptions.h"
|
2018-03-07 01:17:50 +01:00
|
|
|
#include "../mediafileinfo.h"
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2017-01-27 18:59:22 +01:00
|
|
|
#include <c++utilities/conversion/stringbuilder.h>
|
2015-04-22 19:22:01 +02:00
|
|
|
#include <c++utilities/io/binaryreader.h>
|
|
|
|
#include <c++utilities/io/binarywriter.h>
|
|
|
|
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
using namespace std;
|
2019-06-10 22:49:11 +02:00
|
|
|
using namespace CppUtilities;
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2018-03-06 23:09:15 +01:00
|
|
|
namespace TagParser {
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::Mp4Atom
|
2015-04-22 19:22:01 +02:00
|
|
|
* \brief The Mp4Atom class helps to parse MP4 files.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new top level atom with the specified \a container at the specified \a startOffset.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
Mp4Atom::Mp4Atom(GenericFileElement::ContainerType &container, std::uint64_t startOffset)
|
2018-03-07 01:17:50 +01:00
|
|
|
: GenericFileElement<Mp4Atom>(container, startOffset)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2015-06-07 00:18:28 +02:00
|
|
|
/*!
|
|
|
|
* \brief Constructs a new top level atom with the specified \a container at the specified \a startOffset.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
Mp4Atom::Mp4Atom(GenericFileElement::ContainerType &container, std::uint64_t startOffset, std::uint64_t maxSize)
|
2018-03-07 01:17:50 +01:00
|
|
|
: GenericFileElement<Mp4Atom>(container, startOffset, maxSize)
|
|
|
|
{
|
|
|
|
}
|
2015-06-07 00:18:28 +02:00
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Constructs a new sub level atom with the specified \a parent at the specified \a startOffset.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
Mp4Atom::Mp4Atom(Mp4Atom &parent, std::uint64_t startOffset)
|
2018-03-07 01:17:50 +01:00
|
|
|
: GenericFileElement<Mp4Atom>(parent, startOffset)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the parsing context.
|
|
|
|
*/
|
|
|
|
string Mp4Atom::parsingContext() const
|
|
|
|
{
|
2017-01-30 00:42:35 +01:00
|
|
|
return "parsing " % idToString() % " atom at " + startOffset();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Parses the MP4 atom.
|
|
|
|
*/
|
2018-03-05 17:49:29 +01:00
|
|
|
void Mp4Atom::internalParse(Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
static const string context("parsing MP4 atom");
|
2018-03-07 01:17:50 +01:00
|
|
|
if (maxTotalSize() < minimumElementSize()) {
|
|
|
|
diag.emplace_back(DiagLevel::Critical,
|
|
|
|
argsToString("Atom is smaller than 8 byte and hence invalid. The remaining size within the parent atom is ", maxTotalSize(), '.'),
|
|
|
|
context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2018-06-02 22:56:08 +02:00
|
|
|
stream().seekg(static_cast<streamoff>(startOffset()));
|
2015-04-22 19:22:01 +02:00
|
|
|
m_dataSize = reader().readUInt32BE();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_dataSize == 0) {
|
2015-04-22 19:22:01 +02:00
|
|
|
// atom size extends to rest of the file/enclosing container
|
|
|
|
m_dataSize = maxTotalSize();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!m_dataSize) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "No data found (only null bytes).", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw NoDataFoundException();
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_dataSize < 8 && m_dataSize != 1) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Atom is smaller than 8 byte and hence invalid.", context);
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
|
|
|
m_id = reader().readUInt32BE();
|
|
|
|
m_idLength = 4;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_dataSize == 1) { // atom denotes 64-bit size
|
2015-04-22 19:22:01 +02:00
|
|
|
m_dataSize = reader().readUInt64BE();
|
|
|
|
m_sizeLength = 12; // 4 bytes indicate long size denotation + 8 bytes for actual size denotation
|
2018-03-07 01:17:50 +01:00
|
|
|
if (dataSize() < 16 && m_dataSize != 1) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Atom denoting 64-bit size is smaller than 16 byte and hence invalid.", parsingContext());
|
2015-04-22 19:22:01 +02:00
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_sizeLength = 4;
|
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (maxTotalSize() < m_dataSize) { // currently m_dataSize holds data size plus header size!
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Warning, "The atom seems to be truncated; unable to parse siblings of that ", parsingContext());
|
2015-04-22 19:22:01 +02:00
|
|
|
m_dataSize = maxTotalSize(); // using max size instead
|
|
|
|
}
|
|
|
|
// currently m_dataSize holds data size plus header size!
|
|
|
|
m_dataSize -= headerSize();
|
|
|
|
Mp4Atom *child = nullptr;
|
2019-03-13 19:06:42 +01:00
|
|
|
if (std::uint64_t firstChildOffset = this->firstChildOffset()) {
|
2018-03-07 01:17:50 +01:00
|
|
|
if (firstChildOffset + minimumElementSize() <= totalSize()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
child = new Mp4Atom(static_cast<Mp4Atom &>(*this), startOffset() + firstChildOffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_firstChild.reset(child);
|
|
|
|
Mp4Atom *sibling = nullptr;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (totalSize() < maxTotalSize()) {
|
|
|
|
if (parent()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
sibling = new Mp4Atom(*(parent()), startOffset() + totalSize());
|
|
|
|
} else {
|
2015-06-07 00:18:28 +02:00
|
|
|
sibling = new Mp4Atom(container(), startOffset() + totalSize(), maxTotalSize() - totalSize());
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
m_nextSibling.reset(sibling);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief This function helps to write the atom size after writing an atom to a stream.
|
|
|
|
* \param stream Specifies the stream.
|
|
|
|
* \param startOffset Specifies the start offset of the atom.
|
2018-06-02 22:56:08 +02:00
|
|
|
* \remarks The caller must ensure that no seek before \a startOffset happended.
|
|
|
|
* \throw The caller has to be sure, that the number of written bytes does not exceed
|
|
|
|
* maximum of an 32-bit unsigned integer. Otherwise the function will throw
|
|
|
|
* Failure and Mp4Atom::seekBackAndWriteAtomSize64 should be used instead.
|
2015-04-22 19:22:01 +02:00
|
|
|
*
|
|
|
|
* This function seeks back to the start offset and writes the difference between the
|
|
|
|
* previous offset and the start offset as 32-bit unsigned integer to the \a stream.
|
|
|
|
* Then it seeks back to the previous offset.
|
|
|
|
*/
|
2019-06-01 15:07:48 +02:00
|
|
|
void Mp4Atom::seekBackAndWriteAtomSize(std::ostream &stream, const std::ostream::pos_type &startOffset, Diagnostics &diag)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
ostream::pos_type currentOffset = stream.tellp();
|
2018-06-02 22:56:08 +02:00
|
|
|
const auto atomSize(currentOffset - startOffset);
|
2019-03-13 19:06:42 +01:00
|
|
|
if (atomSize > numeric_limits<std::uint32_t>::max()) {
|
2018-07-10 17:07:34 +02:00
|
|
|
diag.emplace_back(DiagLevel::Fatal, argsToString(atomSize, " exceeds maximum."), "write 32-bit atom size");
|
2018-06-02 22:56:08 +02:00
|
|
|
throw Failure();
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.seekp(startOffset);
|
|
|
|
BinaryWriter writer(&stream);
|
2019-03-13 19:06:42 +01:00
|
|
|
writer.writeUInt32BE(static_cast<std::uint32_t>(atomSize));
|
2015-11-07 15:23:36 +01:00
|
|
|
stream.seekp(currentOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief This function helps to write the atom size after writing an atom to a stream.
|
|
|
|
* \param stream Specifies the stream.
|
|
|
|
* \param startOffset Specifies the start offset of the atom.
|
2018-06-02 22:56:08 +02:00
|
|
|
* \remarks The caller must ensure that no seek before \a startOffset happended.
|
2015-11-07 15:23:36 +01:00
|
|
|
*
|
|
|
|
* This function seeks back to the start offset and writes the difference between the
|
|
|
|
* previous offset and the start offset as 64-bit unsigned integer to the \a stream.
|
|
|
|
* Then it seeks back to the previous offset.
|
|
|
|
*/
|
|
|
|
void Mp4Atom::seekBackAndWriteAtomSize64(std::ostream &stream, const ostream::pos_type &startOffset)
|
|
|
|
{
|
|
|
|
ostream::pos_type currentOffset = stream.tellp();
|
|
|
|
stream.seekp(startOffset);
|
|
|
|
BinaryWriter writer(&stream);
|
|
|
|
writer.writeUInt32BE(1);
|
|
|
|
stream.seekp(4, ios_base::cur);
|
2019-03-13 19:06:42 +01:00
|
|
|
writer.writeUInt64BE(static_cast<std::uint64_t>(currentOffset - startOffset));
|
2015-04-22 19:22:01 +02:00
|
|
|
stream.seekp(currentOffset);
|
|
|
|
}
|
|
|
|
|
2015-12-21 00:04:56 +01:00
|
|
|
/*!
|
|
|
|
* \brief Writes an MP4 atom header to the specified \a stream.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Mp4Atom::makeHeader(std::uint64_t size, std::uint32_t id, BinaryWriter &writer)
|
2015-12-21 00:04:56 +01:00
|
|
|
{
|
2019-03-13 19:06:42 +01:00
|
|
|
if (size < numeric_limits<std::uint32_t>::max()) {
|
|
|
|
writer.writeUInt32BE(static_cast<std::uint32_t>(size));
|
2015-12-21 00:04:56 +01:00
|
|
|
writer.writeUInt32BE(id);
|
|
|
|
} else {
|
|
|
|
writer.writeUInt32BE(1);
|
|
|
|
writer.writeUInt32BE(id);
|
|
|
|
writer.writeUInt64BE(size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether the atom is a parent element.
|
|
|
|
*
|
|
|
|
* \remarks This information is not read from the atom header. Some
|
|
|
|
* atoms are simply known to be parents whereas all other
|
|
|
|
* are considered as non-parents.
|
|
|
|
*/
|
|
|
|
bool Mp4Atom::isParent() const
|
|
|
|
{
|
|
|
|
using namespace Mp4AtomIds;
|
|
|
|
// some atom ids are known to be parents
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (id()) {
|
|
|
|
case Movie:
|
|
|
|
case Track:
|
2019-04-17 17:51:04 +02:00
|
|
|
case Edit:
|
2018-03-07 01:17:50 +01:00
|
|
|
case Media:
|
|
|
|
case MediaInformation:
|
2019-04-15 18:19:16 +02:00
|
|
|
case MediaInformationHeader:
|
2018-03-07 01:17:50 +01:00
|
|
|
case DataInformation:
|
|
|
|
case SampleTable:
|
|
|
|
case UserData:
|
|
|
|
case Meta:
|
|
|
|
case ItunesList:
|
|
|
|
case MovieFragment:
|
|
|
|
case TrackFragment:
|
2019-04-16 21:46:55 +02:00
|
|
|
case TrackReference:
|
2018-03-07 01:17:50 +01:00
|
|
|
case MovieExtends:
|
|
|
|
case DataReference:
|
|
|
|
case Mp4AtomIds::AvcConfiguration:
|
|
|
|
case FourccIds::Mpeg4Audio:
|
|
|
|
case FourccIds::AmrNarrowband:
|
|
|
|
case FourccIds::Amr:
|
|
|
|
case FourccIds::Drms:
|
|
|
|
case FourccIds::Alac:
|
|
|
|
case FourccIds::WindowsMediaAudio:
|
|
|
|
case FourccIds::Ac3:
|
|
|
|
case FourccIds::EAc3:
|
|
|
|
case FourccIds::DolbyMpl:
|
|
|
|
case FourccIds::Dts:
|
|
|
|
case FourccIds::DtsH:
|
|
|
|
case FourccIds::DtsE:
|
2015-04-22 19:22:01 +02:00
|
|
|
return true;
|
|
|
|
default:
|
2018-03-07 01:17:50 +01:00
|
|
|
if (parent()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
// some atom ids are known to contain parents
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (parent()->id()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
case ItunesList:
|
|
|
|
return true;
|
2018-03-07 01:17:50 +01:00
|
|
|
default:;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether the atom is a padding element.
|
|
|
|
*
|
|
|
|
* \remarks This information is not read from the atom header. Atoms with
|
|
|
|
* the IDs "free" and "skip" are considered as padding.
|
|
|
|
*/
|
|
|
|
bool Mp4Atom::isPadding() const
|
|
|
|
{
|
|
|
|
using namespace Mp4AtomIds;
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (id()) {
|
|
|
|
case Free:
|
|
|
|
case Skip:
|
2015-04-22 19:22:01 +02:00
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the offset of the first child (relative to the start offset of this atom).
|
|
|
|
*
|
|
|
|
* \remarks This information is not read from the atom header. The offsets are known
|
|
|
|
* for specific atoms.
|
2019-12-30 22:54:11 +01:00
|
|
|
* \remarks This method returns zero for non-parent atoms which have no children.
|
|
|
|
* \remarks Children with variable offset such as the "esds"-atom must be denoted!
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t Mp4Atom::firstChildOffset() const
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
|
|
|
using namespace Mp4AtomIds;
|
2015-06-12 02:35:50 +02:00
|
|
|
using namespace FourccIds;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (isParent()) {
|
|
|
|
switch (id()) {
|
|
|
|
case Meta:
|
2018-07-09 14:03:41 +02:00
|
|
|
if (parent() && parent()->id() == Mp4AtomIds::UserData) {
|
|
|
|
return headerSize() + 0x4u;
|
|
|
|
}
|
|
|
|
return headerSize();
|
2018-03-07 01:17:50 +01:00
|
|
|
case DataReference:
|
|
|
|
return headerSize() + 0x8u;
|
|
|
|
default:
|
|
|
|
return headerSize();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
} else {
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (id()) {
|
|
|
|
case SampleDescription:
|
|
|
|
return headerSize() + 0x08u;
|
|
|
|
default:
|
|
|
|
return 0x00u;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} // namespace TagParser
|