diff --git a/basicfileinfo.cpp b/basicfileinfo.cpp index 3ab27d0..2d9beaa 100644 --- a/basicfileinfo.cpp +++ b/basicfileinfo.cpp @@ -67,9 +67,7 @@ void BasicFileInfo::open(bool readonly) void BasicFileInfo::reopen(bool readonly) { close(); - m_file.open(m_path.c_str(), readonly - ? ios_base::in | ios_base::binary - : ios_base::in | ios_base::out | ios_base::binary); + m_file.open(m_path, readonly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary); m_file.seekg(0, ios_base::end); m_size = m_file.tellg(); m_file.seekg(0, ios_base::beg); diff --git a/matroska/ebmlelement.cpp b/matroska/ebmlelement.cpp index f3e9c1d..6bba396 100644 --- a/matroska/ebmlelement.cpp +++ b/matroska/ebmlelement.cpp @@ -257,6 +257,43 @@ byte EbmlElement::makeId(GenericFileElement::identifierType id, char *buff) } } +/*! + * \brief Makes the size denotation for the specified \a size and stores it to \a buff. + * \param size Specifies the size to be denoted. + * \param buff Specifies the buffer to store the denotation. Must be at least 8 bytes long. + * \returns Returns the number of bytes written to \a buff. + * \throws Throws InvalidDataException() if \a size can not be represented. + */ +byte EbmlElement::makeSizeDenotation(uint64 size, char *buff) +{ + if(size < 126) { + *buff = static_cast(size | 0x80); + return 1; + } else if(size <= 16382ul) { + BE::getBytes(static_cast(size | 0x4000), buff); + return 2; + } else if(size <= 2097150ul) { + BE::getBytes(static_cast((size | 0x200000) << 0x08), buff); + return 3; + } else if(size <= 268435454ul) { + BE::getBytes(static_cast(size | 0x10000000), buff); + return 4; + } else if(size <= 34359738366ul) { + BE::getBytes(static_cast((size | 0x800000000) << 0x18), buff); + return 5; + } else if(size <= 4398046511102ul) { + BE::getBytes(static_cast((size | 0x40000000000) << 0x10), buff); + return 6; + } else if(size <= 562949953421310ul) { + BE::getBytes(static_cast((size | 0x2000000000000) << 0x08), buff); + return 7; + } else if(size <= 72057594037927934ul) { + BE::getBytes(static_cast(size | 0x100000000000000), buff); + return 8; + } + throw InvalidDataException(); +} + /*! * \brief Makes the size denotation for the specified \a size and stores it to \a buff. * \param size Specifies the size to be denoted. @@ -353,6 +390,40 @@ byte EbmlElement::makeUInteger(uint64 value, char *buff) } } +/*! + * \brief Writes \a value to \a buff. + * \returns Returns the number of bytes written to \a buff. + * \param minBytes Specifies the minimum number of bytes to use. + */ +byte EbmlElement::makeUInteger(uint64 value, char *buff, byte minBytes) +{ + if(minBytes <= 1 && value <= 0xFFul) { + *buff = static_cast(value); + return 1; + } else if(minBytes <= 2 && value <= 0xFFFFul) { + BE::getBytes(static_cast(value), buff); + return 2; + } else if(minBytes <= 3 && value <= 0xFFFFFFul) { + BE::getBytes(static_cast(value << 0x08), buff); + return 3; + } else if(minBytes <= 4 && value <= 0xFFFFFFFFul) { + BE::getBytes(static_cast(value), buff); + return 4; + } else if(minBytes <= 5 && value <= 0xFFFFFFFFFFul) { + BE::getBytes(static_cast(value << 0x18), buff); + return 5; + } else if(minBytes <= 6 && value <= 0xFFFFFFFFFFFFul) { + BE::getBytes(static_cast(value << 0x10), buff); + return 6; + } else if(minBytes <= 7 && value <= 0xFFFFFFFFFFFFFFul) { + BE::getBytes(static_cast(value << 0x08), buff); + return 7; + } else { + BE::getBytes(static_cast(value), buff); + return 8; + } +} + /*! * \brief Makes a simple EBML element. * \param stream Specifies the stream to write the data to. diff --git a/matroska/ebmlelement.h b/matroska/ebmlelement.h index cc288be..0f8d7b5 100644 --- a/matroska/ebmlelement.h +++ b/matroska/ebmlelement.h @@ -65,9 +65,11 @@ public: static byte calculateIdLength(identifierType id); static byte calculateSizeDenotationLength(uint64 size); static byte makeId(identifierType id, char *buff); - static byte makeSizeDenotation(uint64 size, char *buff, byte minBytes = 1); + static byte makeSizeDenotation(uint64 size, char *buff); + static byte makeSizeDenotation(uint64 size, char *buff, byte minBytes); static byte calculateUIntegerLength(uint64 integer); static byte makeUInteger(uint64 value, char *buff); + static byte makeUInteger(uint64 value, char *buff, byte minBytes); static void makeSimpleElement(std::ostream &stream, identifierType id, uint64 content); static void makeSimpleElement(std::ostream &stream, identifierType id, const std::string &content); static void makeSimpleElement(std::ostream &stream, GenericFileElement::identifierType id, const char *data, std::size_t dataSize); diff --git a/matroska/matroskacontainer.cpp b/matroska/matroskacontainer.cpp index 195a6db..d26b8bc 100644 --- a/matroska/matroskacontainer.cpp +++ b/matroska/matroskacontainer.cpp @@ -705,7 +705,6 @@ void MatroskaContainer::internalParseAttachments() struct SegmentData { SegmentData() : - element(nullptr), hasCrc32(false), cuesElement(nullptr), infoDataSize(0), @@ -715,11 +714,10 @@ struct SegmentData newPadding(0), sizeDenotationLength(0), totalDataSize(0), - totalSize(0) + totalSize(0), + newDataOffset(0) {} - // "Segment"-element object (original file) - EbmlElement *element; // whether CRC-32 checksum is present bool hasCrc32; // used to make "SeekHead"-element @@ -746,6 +744,8 @@ struct SegmentData uint64 totalDataSize; // total size of the segment data (in the new file, including header) uint64 totalSize; + // data offset of the segment in the new file + uint64 newDataOffset; }; void MatroskaContainer::internalMakeFile() @@ -861,8 +861,7 @@ void MatroskaContainer::internalMakeFile() // inspect layout of original file // - number of segments - // - media data / first cluster offset - // - last level 0 element and last "Segment"-element + // - position of tags relative to the media data try { for(bool firstClusterFound = false, firstTagFound = false; level0Element; level0Element = level0Element->nextSibling()) { level0Element->parse(); @@ -1098,7 +1097,7 @@ addCuesElementSize: segment.sizeDenotationLength = level0Element->headerSize() - 4; nonRewriteCalculations: - // pretend writing "Cluster"-elements assuming there is not rewrite required + // pretend writing "Cluster"-elements assuming there is no rewrite required // -> update offset in "SeakHead"-element if(segment.seekInfo.push(0, MatroskaIds::Cluster, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)) { goto calculateSegmentSize; @@ -1153,6 +1152,7 @@ nonRewriteCalculations: if(segment.sizeDenotationLength != (sizeLength = EbmlElement::calculateSizeDenotationLength(segment.totalDataSize))) { // assumption was wrong -> recalculate with new length segment.sizeDenotationLength = sizeLength; + level1Element = segment.firstClusterElement; goto nonRewriteCalculations; } @@ -1405,7 +1405,7 @@ nonRewriteCalculations: outputWriter.writeUInt32BE(MatroskaIds::Segment); sizeLength = EbmlElement::makeSizeDenotation(segment.totalDataSize, buff); outputStream.write(buff, sizeLength); - offset = outputStream.tellp(); // store segment data offset here + segment.newDataOffset = offset = outputStream.tellp(); // store segment data offset here // write CRC-32 element ... if(segment.hasCrc32) { @@ -1519,6 +1519,8 @@ nonRewriteCalculations: } } + // write media data / "Cluster"-elements + level1Element = level0Element->childById(MatroskaIds::Cluster); if(rewriteRequired) { // update status, check whether the operation has been aborted @@ -1527,7 +1529,6 @@ nonRewriteCalculations: } updateStatus("Writing clusters ...", static_cast(static_cast(outputStream.tellp()) - offset) / segment.totalDataSize); // write "Cluster"-element - level1Element = level0Element->childById(MatroskaIds::Cluster); for(auto clusterSizesIterator = segment.clusterSizes.cbegin(); level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster), ++clusterSizesIterator) { // calculate position of cluster in segment @@ -1557,6 +1558,29 @@ nonRewriteCalculations: } } } else { + // can't just skip existing "Cluster"-elements: "Position"-elements must be updated + for(; level1Element; level1Element = level1Element->nextSibling()) { + for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) { + switch(level2Element->id()) { + case MatroskaIds::Position: + // calculate new position + sizeLength = EbmlElement::makeUInteger(level1Element->startOffset() - segmentData.front().newDataOffset, buff, level2Element->dataSize()); + // new position can only applied if it doesn't need more bytes than the previous position + if(level2Element->dataSize() < sizeLength) { + // can't update position -> void position elements ("Position"-elements seem a bit useless anyways) + outputStream.seekp(level2Element->startOffset()); + outputStream.put(EbmlIds::Void); + } else { + // update position + outputStream.seekp(level2Element->dataOffset()); + outputStream.write(buff, sizeLength); + } + break; + default: + ; + } + } + } // skip existing "Cluster"-elements outputStream.seekp(segment.clusterEndOffset); } @@ -1612,12 +1636,33 @@ nonRewriteCalculations: // reparse what is written so far updateStatus("Reparsing output file ..."); + // -> report new size + fileInfo().reportSizeChanged(outputStream.tellp()); if(rewriteRequired) { - outputStream.close(); // the outputStream needs to be reopened to be able to read again + // report new size + fileInfo().reportSizeChanged(outputStream.tellp()); + // the outputStream needs to be reopened to be able to read again + outputStream.close(); outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary); setStream(outputStream); } else { - // TODO: truncate file + const auto newSize = static_cast(outputStream.tellp()); + if(newSize < fileInfo().size()) { + // file is smaller after the modification -> truncate + // -> close stream before truncating + outputStream.close(); + // -> truncate file + if(truncate(fileInfo().path().c_str(), newSize) == 0) { + fileInfo().reportSizeChanged(newSize); + } else { + addNotification(NotificationType::Critical, "Unable to truncate the file.", context); + } + // -> reopen the stream again + outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary); + } else { + // file is longer after the modification -> just report new size + fileInfo().reportSizeChanged(newSize); + } } reset(); try { diff --git a/matroska/matroskacues.cpp b/matroska/matroskacues.cpp index ffc86f2..4a2c77a 100644 --- a/matroska/matroskacues.cpp +++ b/matroska/matroskacues.cpp @@ -196,7 +196,7 @@ bool MatroskaCuePositionUpdater::updateSize(EbmlElement *element, int shift) // apply new size size = newSize; return updated; - } catch(out_of_range &) { + } catch(const out_of_range &) { // the element is out of the scope of the cue position updater (likely the Segment element) return shift; } diff --git a/mp4/mp4container.cpp b/mp4/mp4container.cpp index 4ce79fe..7ac7507 100644 --- a/mp4/mp4container.cpp +++ b/mp4/mp4container.cpp @@ -680,23 +680,29 @@ calculatePadding: // reparse what is written so far updateStatus("Reparsing output file ..."); if(rewriteRequired) { - outputStream.close(); // the outputStream needs to be reopened to be able to read again + // report new size + fileInfo().reportSizeChanged(outputStream.tellp()); + // the outputStream needs to be reopened to be able to read again + outputStream.close(); outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary); setStream(outputStream); } else { - // ensure invalid bytes at the end are truncated (if the modified file is smaller then the original file) const auto newSize = static_cast(outputStream.tellp()); if(newSize < fileInfo().size()) { - // close stream before truncating + // file is smaller after the modification -> truncate + // -> close stream before truncating outputStream.close(); - // truncate file + // -> truncate file if(truncate(fileInfo().path().c_str(), newSize) == 0) { fileInfo().reportSizeChanged(newSize); } else { addNotification(NotificationType::Critical, "Unable to truncate the file.", context); } - // reopen the stream again + // -> reopen the stream again outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary); + } else { + // file is longer after the modification -> just report new size + fileInfo().reportSizeChanged(newSize); } }