diff --git a/mediafileinfo.cpp b/mediafileinfo.cpp index b2b4264..c4d07b8 100644 --- a/mediafileinfo.cpp +++ b/mediafileinfo.cpp @@ -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(*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(m_container.get())->tags().front().get() : nullptr; + return (m_containerFormat == ContainerFormat::Mp4 || m_containerFormat == ContainerFormat::QuickTime) && m_container && m_container->tagCount() > 0 ? static_cast(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 > &MediaFileInfo::matroskaTags() const { @@ -1117,7 +1081,7 @@ const vector > &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 MediaFileInfo::chapters() const { @@ -1136,7 +1100,7 @@ vector 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 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 &tags) const { @@ -1338,7 +1302,7 @@ void MediaFileInfo::tags(vector &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 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); } } diff --git a/mp4/mp4atom.cpp b/mp4/mp4atom.cpp index 54e765e..3aeefd9 100644 --- a/mp4/mp4atom.cpp +++ b/mp4/mp4atom.cpp @@ -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()); diff --git a/mp4/mp4container.cpp b/mp4/mp4container.cpp index 021c807..47b6761 100644 --- a/mp4/mp4container.cpp +++ b/mp4/mp4container.cpp @@ -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; } } diff --git a/mp4/mp4tag.cpp b/mp4/mp4tag.cpp index f98d1f1..2b945bc 100644 --- a/mp4/mp4tag.cpp +++ b/mp4/mp4tag.cpp @@ -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(child->id(), tagField)); - } catch(Failure &) { + } catch(const Failure &) { } addNotifications(context, *child); addNotifications(context, tagField); diff --git a/mp4/mp4track.cpp b/mp4/mp4track.cpp index 47d44f8..967ac20 100644 --- a/mp4/mp4track.cpp +++ b/mp4/mp4track.cpp @@ -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(totalDuration) / static_cast(timeScale)); } } + // caluculate average bitrate if(m_bitrate < 0.01 && m_bitrate > -0.01) { m_bitrate = (static_cast(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(); diff --git a/signature.cpp b/signature.cpp index 7859d22..1bc4502 100644 --- a/signature.cpp +++ b/signature.cpp @@ -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: diff --git a/signature.h b/signature.h index 1cb3e51..afb10a1 100644 --- a/signature.h +++ b/signature.h @@ -50,6 +50,7 @@ enum class ContainerFormat WindowsIcon, /**< Microsoft Windows Icon */ SevenZ, /**< 7z archive */ Lzip, /**< lz compressed file */ + QuickTime, /**< QuickTime container */ Zip /**< ZIP archive */ };