improved handling of unsupported files

additionally:
- added detection for QuickTime files
- minor adjustments
This commit is contained in:
Martchus 2016-03-14 21:56:27 +01:00
parent e0437c0a43
commit ddf9ef02f8
7 changed files with 72 additions and 78 deletions

View File

@ -207,8 +207,9 @@ startParsingSignature:
// continue reading signature
goto startParsingSignature;
case ContainerFormat::Mp4: {
// EBML/Matroska is handled using Mp4Container instance
case ContainerFormat::Mp4:
case ContainerFormat::QuickTime: {
// MP4/QuickTime is handled using Mp4Container instance
m_container = make_unique<Mp4Container>(*this, m_containerOffset);
NotificationList notifications;
try {
@ -512,7 +513,7 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
// check if tags need to be created/adjusted/removed
bool targetsRequired = !requiredTargets.empty() && (requiredTargets.size() != 1 && requiredTargets.front().isEmpty());
bool targetsSupported = false;
if(m_container) {
if(areTagsSupported() && m_container) {
// container object takes care of tag management
if(targetsRequired) {
// check whether container supports targets
@ -723,54 +724,17 @@ const char *MediaFileInfo::containerFormatAbbreviation() const
*/
const char *MediaFileInfo::mimeType() const
{
MediaType mediaType;
switch(m_containerFormat) {
case ContainerFormat::Asf:
return "video/x-ms-asf";
case ContainerFormat::Gif87a:
case ContainerFormat::Gif89a:
return "image/gif";
case ContainerFormat::Jpeg:
return "image/jpeg";
case ContainerFormat::Png:
return "image/png";
case ContainerFormat::MpegAudioFrames:
return "audio/mpeg";
case ContainerFormat::Mp4:
if(hasTracksOfType(MediaType::Video)) {
return "video/mp4";
}
return "audio/mp4";
case ContainerFormat::Ogg:
if(hasTracksOfType(MediaType::Video)) {
return "video/ogg";
}
return "audio/ogg";
case ContainerFormat::Matroska:
if(hasTracksOfType(MediaType::Video)) {
return "video/x-matroska";
}
return "audio/x-matroska";
case ContainerFormat::Bzip2:
return "application/x-bzip";
case ContainerFormat::Gzip:
return "application/gzip";
case ContainerFormat::Lha:
return "application/x-lzh-compressed";
case ContainerFormat::Rar:
return "application/x-rar-compressed";
case ContainerFormat::Lzip:
return "application/x-lzip";
case ContainerFormat::Zip:
return "application/zip";
case ContainerFormat::SevenZ:
return "application/x-7z-compressed";
case ContainerFormat::WindowsBitmap:
return "image/bmp";
case ContainerFormat::WindowsIcon:
return "image/vnd.microsoft.icon";
mediaType = hasTracksOfType(MediaType::Video) ? MediaType::Video : MediaType::Audio;
break;
default:
return "";
mediaType = MediaType::Unknown;
}
return Media::containerMimeType(m_containerFormat, mediaType);
}
/*!
@ -781,7 +745,7 @@ const char *MediaFileInfo::mimeType() const
*
* \remarks The MediaFileInfo keeps the ownership over the returned
* pointers. The returned Tracks will be destroyed when the
* MediaFileInfo gets invalidated.
* MediaFileInfo is invalidated.
*
* \sa parseTracks()
*/
@ -950,7 +914,7 @@ bool MediaFileInfo::removeAllId3v2Tags()
* \returns Returns the first ID3v2 tag of the current file.
*
* \remarks The MediaFileInfo keeps the ownership over the created tag. It will be
* destroyed when the MediaFileInfo gets invalidated.
* destroyed when the MediaFileInfo is invalidated.
*
* \sa applyChanges()
*/
@ -1066,9 +1030,6 @@ bool MediaFileInfo::areTracksSupported() const
*/
bool MediaFileInfo::areTagsSupported() const
{
if(hasAnyTag()) {
return true;
}
switch(m_containerFormat) {
case ContainerFormat::Mp4:
case ContainerFormat::MpegAudioFrames:
@ -1076,9 +1037,12 @@ bool MediaFileInfo::areTagsSupported() const
case ContainerFormat::Matroska:
case ContainerFormat::Webm:
case ContainerFormat::Adts:
// these container formats are supported
return true;
default:
return false;
// the container format is unsupported
// -> an ID3 tag might be already present, in this case the tags are considered supported
return !m_container && (hasId3v1Tag() || hasId3v2Tag());
}
}
@ -1087,12 +1051,12 @@ bool MediaFileInfo::areTagsSupported() const
*
* \remarks The MediaFileInfo keeps the ownership over the returned
* pointer. The returned MP4 tag will be destroyed when the
* MediaFileInfo gets invalidated.
* MediaFileInfo is invalidated.
*/
Mp4Tag *MediaFileInfo::mp4Tag() const
{
// simply return the first tag here since MP4 files never contain multiple tags
return m_containerFormat == ContainerFormat::Mp4 && m_container && m_container->tagCount() > 0 ? static_cast<Mp4Container *>(m_container.get())->tags().front().get() : nullptr;
return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container && m_container->tagCount() > 0 ? static_cast<Mp4Container *>(m_container.get())->tags().front().get() : nullptr;
}
/*!
@ -1100,7 +1064,7 @@ Mp4Tag *MediaFileInfo::mp4Tag() const
*
* \remarks The MediaFileInfo keeps the ownership over the returned
* pointers. The returned Matroska tags will be destroyed when the
* MediaFileInfo gets invalidated.
* MediaFileInfo is invalidated.
*/
const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
{
@ -1117,7 +1081,7 @@ const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
* \brief Returns all chapters assigned to the current file.
*
* \remarks The MediaFileInfo keeps the ownership over the chapters which will be
* destroyed when the MediaFileInfo gets invalidated.
* destroyed when the MediaFileInfo is invalidated.
*/
vector<AbstractChapter *> MediaFileInfo::chapters() const
{
@ -1136,7 +1100,7 @@ vector<AbstractChapter *> MediaFileInfo::chapters() const
* \brief Returns all attachments assigned to the current file.
*
* \remarks The MediaFileInfo keeps the ownership over the attachments which will be
* destroyed when the MediaFileInfo gets invalidated.
* destroyed when the MediaFileInfo is invalidated.
*/
vector<AbstractAttachment *> MediaFileInfo::attachments() const
{
@ -1318,7 +1282,7 @@ void MediaFileInfo::mergeId3v2Tags()
* Previous elements of the vector will not be cleared.
*
* \remarks The MediaFileInfo keeps the ownership over the tags which will be
* destroyed when the MediaFileInfo gets invalidated.
* destroyed when the MediaFileInfo is invalidated.
*/
void MediaFileInfo::tags(vector<Tag *> &tags) const
{
@ -1338,7 +1302,7 @@ void MediaFileInfo::tags(vector<Tag *> &tags) const
* \brief Returns all tags assigned to the current file.
*
* \remarks The MediaFileInfo keeps the ownership over the tags which will be
* destroyed when the MediaFileInfo gets invalidated.
* destroyed when the MediaFileInfo is invalidated.
*/
vector<Tag *> MediaFileInfo::tags() const
{
@ -1376,7 +1340,7 @@ void MediaFileInfo::makeMp3File()
stream().seekp(-128, ios_base::end);
try {
m_id3v1Tag->make(stream());
} catch(Failure &) {
} catch(const Failure &) {
addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
}
} else {
@ -1400,7 +1364,7 @@ void MediaFileInfo::makeMp3File()
stream().seekp(0, ios_base::end);
try {
m_id3v1Tag->make(stream());
} catch(Failure &) {
} catch(const Failure &) {
addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
}
} else {
@ -1515,7 +1479,7 @@ void MediaFileInfo::makeMp3File()
updateStatus("Writing ID3v1 tag ...");
try {
m_id3v1Tag->make(stream());
} catch(Failure &) {
} catch(const Failure &) {
addNotification(NotificationType::Warning, "Unable to write ID3v1 tag.", context);
}
}

View File

@ -59,7 +59,7 @@ void Mp4Atom::internalParse()
invalidateStatus();
static const string context("parsing MP4 atom");
if(maxTotalSize() < minimumElementSize()) {
addNotification(NotificationType::Critical, "Atom is smaller then 8 byte and hence invalid. The maximum size within the parent atom is " + numberToString(maxTotalSize()) + ".", context);
addNotification(NotificationType::Critical, "Atom is smaller then 8 byte and hence invalid. The remaining size within the parent atom is " + numberToString(maxTotalSize()) + ".", context);
throw TruncatedDataException();
}
stream().seekg(startOffset());

View File

@ -57,7 +57,7 @@ void Mp4Container::internalParseHeader()
m_doctype = reader().readString(4);
m_version = reader().readUInt32BE();
} else {
m_doctype = "mp41";
m_doctype.clear();
m_version = 0;
}
}

View File

@ -180,7 +180,7 @@ void Mp4Tag::parse(Mp4Atom &metaAtom)
Mp4Atom *subAtom = nullptr;
try {
metaAtom.childById(Mp4AtomIds::HandlerReference);
} catch(Failure &) {
} catch(const Failure &) {
addNotification(NotificationType::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
}
if(subAtom) {
@ -206,7 +206,7 @@ void Mp4Tag::parse(Mp4Atom &metaAtom)
}
try {
subAtom = metaAtom.childById(Mp4AtomIds::ItunesList);
} catch(Failure &) {
} catch(const Failure &) {
addNotification(NotificationType::Critical, "Unable to parse child atoms of meta atom (stores hdlr and ilst atoms).", context);
}
if(subAtom) {
@ -217,7 +217,7 @@ void Mp4Tag::parse(Mp4Atom &metaAtom)
tagField.invalidateNotifications();
tagField.reparse(*child);
fields().insert(pair<fieldType::identifierType, fieldType>(child->id(), tagField));
} catch(Failure &) {
} catch(const Failure &) {
}
addNotifications(context, *child);
addNotifications(context, tagField);

View File

@ -1165,6 +1165,7 @@ void Mp4Track::internalParseHeader()
addNotification(NotificationType::Critical, "\"trak\"-atom is null.", context);
throw InvalidDataException();
}
// get atoms
try {
if(!(m_tkhdAtom = m_trakAtom->childById(TrackHeader))) {
@ -1207,11 +1208,13 @@ void Mp4Track::internalParseHeader()
addNotification(NotificationType::Critical, "No \"stsz\"/\"stz2\"-atom found.", context);
throw InvalidDataException();
}
} catch(Failure &) {
} catch(const Failure &) {
addNotification(NotificationType::Critical, "Unable to parse relevant atoms.", context);
throw InvalidDataException();
}
BinaryReader &reader = m_trakAtom->reader();
// read tkhd atom
m_istream->seekg(m_tkhdAtom->startOffset() + 8); // seek to beg, skip size and name
byte atomVersion = reader.readByte(); // read version
@ -1236,8 +1239,9 @@ void Mp4Track::internalParseHeader()
m_modificationTime = DateTime();
m_id = 0;
}
// read mdhd atom
m_istream->seekg(m_mdhdAtom->startOffset() + 8); // seek to beg, skip size and name
m_istream->seekg(m_mdhdAtom->dataOffset()); // seek to beg, skip size and name
atomVersion = reader.readByte(); // read version
m_istream->seekg(3, ios_base::cur); // skip flags
switch(atomVersion) {
@ -1258,12 +1262,17 @@ void Mp4Track::internalParseHeader()
m_timeScale = 0;
m_duration = TimeSpan();
}
uint16 rawLanguage = reader.readUInt16BE();
char buff[3];
buff[0] = ((rawLanguage & 0x7C00) >> 0xA) + 0x60;
buff[1] = ((rawLanguage & 0x03E0) >> 0x5) + 0x60;
buff[2] = ((rawLanguage & 0x001F) >> 0x0) + 0x60;
m_language = string(buff, 3);
uint16 tmp = reader.readUInt16BE();
if(tmp) {
char buff[3];
buff[0] = ((tmp & 0x7C00) >> 0xA) + 0x60;
buff[1] = ((tmp & 0x03E0) >> 0x5) + 0x60;
buff[2] = ((tmp & 0x001F) >> 0x0) + 0x60;
m_language = string(buff, 3);
} else {
m_language.clear();
}
// read hdlr atom
// -> seek to begin skipping size, name, version, flags and reserved bytes
m_istream->seekg(m_hdlrAtom->dataOffset() + 8);
@ -1284,19 +1293,26 @@ void Mp4Track::internalParseHeader()
default:
m_mediaType = MediaType::Unknown;
}
// name
// -> name
m_istream->seekg(12, ios_base::cur); // skip reserved bytes
m_name = reader.readTerminatedString(m_hdlrAtom->totalSize() - 12 - 4 - 12, 0);
if((tmp = m_istream->peek()) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
// assume size prefixed string (seems to appear in QuickTime files)
m_istream->seekg(1, ios_base::cur);
m_name = reader.readString(tmp);
} else {
// assume null terminated string (appears in MP4 files)
m_name = reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
}
// read stco atom (only chunk count)
m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
m_istream->seekg(m_stcoAtom->dataOffset() + 4);
m_chunkCount = reader.readUInt32BE();
// read stsd atom
m_istream->seekg(m_stsdAtom->dataOffset() + 4); // seek to beg, skip size, name, version and flags
uint32 entryCount = reader.readUInt32BE();
Mp4Atom *esDescParentAtom = nullptr;
uint16 tmp;
if(entryCount > 0) {
try {
for(Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom; codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
@ -1456,6 +1472,7 @@ void Mp4Track::internalParseHeader()
addNotification(NotificationType::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context);
}
}
// read stsz atom which holds the sample size table
m_sampleSizes.clear();
m_size = m_sampleCount = 0;
@ -1527,6 +1544,7 @@ void Mp4Track::internalParseHeader()
}
}
}
// no sample sizes found, search for trun atoms
uint64 totalDuration = 0;
for(Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(MovieFragment, true); moofAtom; moofAtom = moofAtom->siblingById(MovieFragment, false)) {
@ -1654,6 +1672,7 @@ void Mp4Track::internalParseHeader()
}
}
}
// set duration from "trun-information" if the duration has not been determined yet
if(m_duration.isNull() && totalDuration) {
uint32 timeScale = m_timeScale;
@ -1664,10 +1683,12 @@ void Mp4Track::internalParseHeader()
m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(timeScale));
}
}
// caluculate average bitrate
if(m_bitrate < 0.01 && m_bitrate > -0.01) {
m_bitrate = (static_cast<double>(m_size) * 0.0078125) / m_duration.totalSeconds();
}
// read stsc atom (only number of entries)
m_istream->seekg(m_stscAtom->dataOffset() + 4);
m_sampleToChunkEntryCount = reader.readUInt32BE();

View File

@ -47,6 +47,7 @@ enum Sig32 : uint32
Mp4 = 0x66747970u,
Ogg = 0x4F676753u,
PhotoshopDocument = 0x38425053u,
QuickTime = 0x6D6F6F76u,
Riff = 0x52494646u,
RiffWave =0x57415645u,
TiffBigEndian = 0x4D4D002Au,
@ -124,6 +125,8 @@ ContainerFormat parseSignature(const char *buffer, int bufferSize)
switch(sig & 0x00000000FFFFFFFF) { // check 32-bit signatures @ bit 31
case Mp4:
return ContainerFormat::Mp4;
case QuickTime:
return ContainerFormat::QuickTime;
default:
;
}
@ -285,6 +288,7 @@ const char *containerFormatAbbreviation(ContainerFormat containerFormat, MediaTy
case ContainerFormat::Bzip2: return "bz";
case ContainerFormat::Gzip: return "gz";
case ContainerFormat::Lzip: return "lz";
case ContainerFormat::QuickTime: return "mov";
case ContainerFormat::Zip: return "zip";
case ContainerFormat::SevenZ: return "7z";
default: return "";
@ -367,6 +371,8 @@ const char *containerFormatName(ContainerFormat containerFormat)
return "lzip compressed file";
case ContainerFormat::SevenZ:
return "7z archive";
case ContainerFormat::QuickTime:
return "Quick Time";
case ContainerFormat::Zip:
return "ZIP archive";
default:
@ -445,6 +451,8 @@ const char *containerMimeType(ContainerFormat containerFormat, MediaType mediaTy
return "application/x-rar-compressed";
case ContainerFormat::Lzip:
return "application/x-lzip";
case ContainerFormat::QuickTime:
return "video/quicktime";
case ContainerFormat::Zip:
return "application/zip";
case ContainerFormat::SevenZ:

View File

@ -50,6 +50,7 @@ enum class ContainerFormat
WindowsIcon, /**< Microsoft Windows Icon */
SevenZ, /**< 7z archive */
Lzip, /**< lz compressed file */
QuickTime, /**< QuickTime container */
Zip /**< ZIP archive */
};