Introduce MatroskaTrackHeaderMaker
First step to implement applying changed track meta-data for Matroska
This commit is contained in:
parent
f7ce96f4f9
commit
8d08314e3b
|
@ -816,11 +816,17 @@ void MatroskaContainer::internalMakeFile()
|
|||
|
||||
// define variables needed for precalculation of "Tags"- and "Attachments"-element
|
||||
vector<MatroskaTagMaker> tagMaker;
|
||||
tagMaker.reserve(tags().size());
|
||||
uint64 tagElementsSize = 0;
|
||||
uint64 tagsSize;
|
||||
vector<MatroskaAttachmentMaker> attachmentMaker;
|
||||
attachmentMaker.reserve(m_attachments.size());
|
||||
uint64 attachedFileElementsSize = 0;
|
||||
uint64 attachmentsSize;
|
||||
vector<MatroskaTrackHeaderMaker> trackHeaderMaker;
|
||||
trackHeaderMaker.reserve(tracks().size());
|
||||
uint64 trackHeaderElementsSize = 0;
|
||||
uint64 trackHeaderSize;
|
||||
|
||||
// define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
|
||||
// current segment index
|
||||
|
@ -904,6 +910,23 @@ void MatroskaContainer::internalMakeFile()
|
|||
}
|
||||
attachmentsSize = attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
|
||||
|
||||
// calculate size of "Tracks"-element
|
||||
for(auto &track : tracks()) {
|
||||
track->invalidateNotifications();
|
||||
try {
|
||||
trackHeaderMaker.emplace_back(track->prepareMakingHeader());
|
||||
if(trackHeaderMaker.back().requiredSize() > 3) {
|
||||
// a track header of 3 bytes size is empty and can be skipped
|
||||
trackHeaderElementsSize += trackHeaderMaker.back().requiredSize();
|
||||
}
|
||||
} catch(const Failure &) {
|
||||
// nothing to do because notifications will be added anyways
|
||||
}
|
||||
addNotifications(*track);
|
||||
}
|
||||
trackHeaderSize = trackHeaderElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(trackHeaderElementsSize) + trackHeaderElementsSize : 0;
|
||||
|
||||
|
||||
// inspect layout of original file
|
||||
// - number of segments
|
||||
// - position of tags relative to the media data
|
||||
|
@ -1063,17 +1086,26 @@ calculateSegmentSize:
|
|||
}
|
||||
}
|
||||
|
||||
// pretend writing "Tracks"- and "Chapters"-element
|
||||
for(const auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::Tracks, MatroskaIds::Chapters}) {
|
||||
for(level1Element = level0Element->childById(id), index = 0; level1Element; level1Element = level1Element->siblingById(id), ++index) {
|
||||
// update offset in "SeekHead"-element
|
||||
if(segment.seekInfo.push(index, id, currentPosition + segment.totalDataSize)) {
|
||||
goto calculateSegmentSize;
|
||||
} else {
|
||||
// add size of element
|
||||
level1Element->makeBuffer();
|
||||
segment.totalDataSize += level1Element->totalSize();
|
||||
}
|
||||
// pretend writing "Tracks"-element
|
||||
if(trackHeaderSize) {
|
||||
// update offsets in "SeekHead"-element
|
||||
if(segment.seekInfo.push(0, MatroskaIds::Tracks, currentPosition + segment.totalDataSize)) {
|
||||
goto calculateSegmentSize;
|
||||
} else {
|
||||
// add size of "Tracks"-element
|
||||
segment.totalDataSize += trackHeaderSize;
|
||||
}
|
||||
}
|
||||
|
||||
// pretend writing "Chapters"-element
|
||||
for(level1Element = level0Element->childById(MatroskaIds::Chapters), index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Chapters), ++index) {
|
||||
// update offset in "SeekHead"-element
|
||||
if(segment.seekInfo.push(index, MatroskaIds::Chapters, currentPosition + segment.totalDataSize)) {
|
||||
goto calculateSegmentSize;
|
||||
} else {
|
||||
// add size of element
|
||||
level1Element->makeBuffer();
|
||||
segment.totalDataSize += level1Element->totalSize();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1502,7 +1534,6 @@ nonRewriteCalculations:
|
|||
default:
|
||||
level2Element->copyBuffer(outputStream);
|
||||
level2Element->discardBuffer();
|
||||
//level2Element->copyEntirely(outputStream);
|
||||
}
|
||||
}
|
||||
// -> write "Title"-element
|
||||
|
@ -1517,13 +1548,21 @@ nonRewriteCalculations:
|
|||
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp, appInfo, appInfoElementDataSize);
|
||||
}
|
||||
|
||||
// write "Tracks"- and "Chapters"-element
|
||||
for(const auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::Tracks, MatroskaIds::Chapters}) {
|
||||
for(level1Element = level0Element->childById(id); level1Element; level1Element = level1Element->siblingById(id)) {
|
||||
level1Element->copyBuffer(outputStream);
|
||||
level1Element->discardBuffer();
|
||||
//level1Element->copyEntirely(outputStream);
|
||||
// write "Tracks"-element
|
||||
if(trackHeaderElementsSize) {
|
||||
outputWriter.writeUInt32BE(MatroskaIds::Tracks);
|
||||
sizeLength = EbmlElement::makeSizeDenotation(trackHeaderElementsSize, buff);
|
||||
outputStream.write(buff, sizeLength);
|
||||
for(auto &maker : trackHeaderMaker) {
|
||||
maker.make(outputStream);
|
||||
}
|
||||
// no need to add notifications; this has been done when creating the maker
|
||||
}
|
||||
|
||||
// write "Chapters"-element
|
||||
for(level1Element = level0Element->childById(MatroskaIds::Chapters); level1Element; level1Element = level1Element->siblingById(MatroskaIds::Chapters)) {
|
||||
level1Element->copyBuffer(outputStream);
|
||||
level1Element->discardBuffer();
|
||||
}
|
||||
|
||||
if(newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
|
||||
|
@ -1535,7 +1574,7 @@ nonRewriteCalculations:
|
|||
for(auto &maker : tagMaker) {
|
||||
maker.make(outputStream);
|
||||
}
|
||||
// no need to add notifications; this has been done when creating the make
|
||||
// no need to add notifications; this has been done when creating the maker
|
||||
}
|
||||
// write "Attachments"-element
|
||||
if(attachmentsSize) {
|
||||
|
@ -1545,7 +1584,7 @@ nonRewriteCalculations:
|
|||
for(auto &maker : attachmentMaker) {
|
||||
maker.make(outputStream);
|
||||
}
|
||||
// no need to add notifications; this has been done when creating the make
|
||||
// no need to add notifications; this has been done when creating the maker
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1562,12 +1601,11 @@ nonRewriteCalculations:
|
|||
|
||||
// write padding / "Void"-element
|
||||
if(segment.newPadding) {
|
||||
|
||||
// calculate length
|
||||
uint64 voidLength;
|
||||
if(segment.newPadding < 64) {
|
||||
sizeLength = 1;
|
||||
*buff = (voidLength = segment.newPadding - 2) | 0x80;
|
||||
*buff = static_cast<char>(voidLength = segment.newPadding - 2) | 0x80;
|
||||
} else {
|
||||
sizeLength = 8;
|
||||
BE::getBytes(static_cast<uint64>((voidLength = segment.newPadding - 9) | 0x100000000000000), buff);
|
||||
|
@ -1584,7 +1622,6 @@ nonRewriteCalculations:
|
|||
// write media data / "Cluster"-elements
|
||||
level1Element = level0Element->childById(MatroskaIds::Cluster);
|
||||
if(rewriteRequired) {
|
||||
|
||||
// update status, check whether the operation has been aborted
|
||||
if(isAborted()) {
|
||||
throw OperationAbortedException();
|
||||
|
|
|
@ -115,11 +115,12 @@ void MatroskaTag::parse(EbmlElement &tagElement)
|
|||
* \brief Prepares making.
|
||||
* \returns Returns a MatroskaTagMaker object which can be used to actually make the tag.
|
||||
* \remarks The tag must NOT be mutated after making is prepared when it is intended to actually
|
||||
* make the tag using the make method of the returned object.
|
||||
* make the tag using the make() method of the returned object.
|
||||
* \throws Throws Media::Failure or a derived exception when a making error occurs.
|
||||
*
|
||||
* This method might be useful when it is necessary to know the size of the tag before making it.
|
||||
* \sa make()
|
||||
* \todo Make inline in next major release.
|
||||
*/
|
||||
MatroskaTagMaker MatroskaTag::prepareMaking()
|
||||
{
|
||||
|
@ -132,6 +133,7 @@ MatroskaTagMaker MatroskaTag::prepareMaking()
|
|||
* \throws Throws Media::Failure or a derived exception when a making
|
||||
* error occurs.
|
||||
* \sa prepareMaking()
|
||||
* \todo Make inline in next major release.
|
||||
*/
|
||||
void MatroskaTag::make(ostream &stream)
|
||||
{
|
||||
|
|
|
@ -435,6 +435,7 @@ void MatroskaTrack::internalParseHeader()
|
|||
addNotification(NotificationType::Critical, "AVC configuration is invalid.", context);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
@ -458,4 +459,41 @@ void MatroskaTrack::internalParseHeader()
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \class Media::MatroskaTrackHeaderMaker
|
||||
* \brief The MatroskaTrackHeaderMaker class helps writing Matroska "TrackEntry"-elements storing track header information.
|
||||
*
|
||||
* An instance can be obtained using the MatroskaTrack::prepareMakingHeader() method.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \brief Prepares making the header for the specified \a track.
|
||||
* \sa See MatroskaTrack::prepareMakingHeader() for more information.
|
||||
*/
|
||||
MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track) :
|
||||
m_track(track)
|
||||
{
|
||||
m_track.m_trackElement->makeBuffer();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the associated tag.
|
||||
*/
|
||||
uint64 MatroskaTrackHeaderMaker::requiredSize() const
|
||||
{
|
||||
return m_track.m_trackElement->totalSize();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Saves the header for the track (specified when constructing the object) to the
|
||||
* specified \a stream (makes a "TrackEntry"-element).
|
||||
* \throws Throws std::ios_base::failure when an IO error occurs.
|
||||
* \throws Throws Assumes the data is already validated and thus does NOT
|
||||
* throw Media::Failure or a derived exception.
|
||||
*/
|
||||
void MatroskaTrackHeaderMaker::make(ostream &stream) const
|
||||
{
|
||||
m_track.m_trackElement->copyBuffer(stream);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,35 @@ namespace Media {
|
|||
|
||||
class EbmlElement;
|
||||
class MatroskaContainer;
|
||||
class MatroskaTrack;
|
||||
|
||||
class TAG_PARSER_EXPORT MatroskaTrackHeaderMaker
|
||||
{
|
||||
friend class MatroskaTrack;
|
||||
|
||||
public:
|
||||
void make(std::ostream &stream) const;
|
||||
const MatroskaTrack &track() const;
|
||||
uint64 requiredSize() const;
|
||||
|
||||
private:
|
||||
MatroskaTrackHeaderMaker(const MatroskaTrack &track);
|
||||
|
||||
const MatroskaTrack &m_track;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Returns the number of bytes which will be written when making the track.
|
||||
*/
|
||||
inline const MatroskaTrack &MatroskaTrackHeaderMaker::track() const
|
||||
{
|
||||
return m_track;
|
||||
}
|
||||
|
||||
class TAG_PARSER_EXPORT MatroskaTrack : public AbstractTrack
|
||||
{
|
||||
friend class MatroskaContainer;
|
||||
friend class MatroskaTrackHeaderMaker;
|
||||
|
||||
public:
|
||||
MatroskaTrack(EbmlElement &trackElement);
|
||||
|
@ -19,6 +44,8 @@ public:
|
|||
TrackType type() const;
|
||||
|
||||
static MediaFormat codecIdToMediaFormat(const std::string &codecId);
|
||||
MatroskaTrackHeaderMaker prepareMakingHeader() const;
|
||||
void makeHeader(std::ostream &stream) const;
|
||||
|
||||
protected:
|
||||
void internalParseHeader();
|
||||
|
@ -27,6 +54,37 @@ private:
|
|||
EbmlElement *m_trackElement;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Prepares making header.
|
||||
* \returns Returns a MatroskaTrackHeaderMaker object which can be used to actually make the track
|
||||
* header.
|
||||
* \remarks The track must NOT be mutated after making is prepared when it is intended to actually
|
||||
* make the tag using the make() method of the returned object.
|
||||
* \throws Throws Media::Failure or a derived exception when a making error occurs.
|
||||
*
|
||||
* This method might be useful when it is necessary to know the size of the track header before making
|
||||
* it.
|
||||
* \sa make()
|
||||
* \todo Make inline in next major release.
|
||||
*/
|
||||
inline MatroskaTrackHeaderMaker MatroskaTrack::prepareMakingHeader() const
|
||||
{
|
||||
return MatroskaTrackHeaderMaker(*this);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Writes header information to the specified \a stream (makes a "TrackEntry"-element).
|
||||
* \throws Throws std::ios_base::failure when an IO error occurs.
|
||||
* \throws Throws Media::Failure or a derived exception when a making
|
||||
* error occurs.
|
||||
* \sa prepareMaking()
|
||||
* \todo Make inline in next major release.
|
||||
*/
|
||||
inline void MatroskaTrack::makeHeader(std::ostream &stream) const
|
||||
{
|
||||
prepareMakingHeader().make(stream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // MEDIA_MATROSKATRACK_H
|
||||
|
|
|
@ -33,7 +33,7 @@ void OverallTests::checkMkvTestfile1()
|
|||
{
|
||||
CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Matroska);
|
||||
const auto tracks = m_fileInfo.tracks();
|
||||
CPPUNIT_ASSERT(tracks.size() == 2);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
|
||||
for(const auto &track : tracks) {
|
||||
switch(track->id()) {
|
||||
case 2422994868:
|
||||
|
@ -167,6 +167,17 @@ void OverallTests::checkMkvTestfile4()
|
|||
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Vorbis);
|
||||
CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
|
||||
CPPUNIT_ASSERT(track->channelCount() == 2);
|
||||
switch(m_tagStatus) {
|
||||
case TagStatus::Original:
|
||||
case TagStatus::Removed:
|
||||
CPPUNIT_ASSERT_EQUAL("und"s, track->language());
|
||||
break;
|
||||
case TagStatus::TestMetaDataPresent:
|
||||
// not implemented yet, so currently still undefined instead of German
|
||||
CPPUNIT_ASSERT_EQUAL("und"s, track->language());
|
||||
//CPPUNIT_ASSERT_EQUAL("ger"s, track->language());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
CPPUNIT_FAIL("unknown track ID");
|
||||
|
@ -520,11 +531,18 @@ void OverallTests::checkMkvConstraints()
|
|||
*/
|
||||
void OverallTests::setMkvTestMetaData()
|
||||
{
|
||||
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
|
||||
auto *container = static_cast<MatroskaContainer *>(m_fileInfo.container());
|
||||
|
||||
// change the present tag
|
||||
const string fileName(m_fileInfo.fileName());
|
||||
if(fileName == "test4.mkv") {
|
||||
// test4.mkv has no tag, so one must be created first
|
||||
m_fileInfo.container()->createTag(TagTarget(50));
|
||||
container->createTag(TagTarget(50));
|
||||
// also change language of track "3171450505" to German
|
||||
MatroskaTrack *track = container->trackById(3171450505);
|
||||
CPPUNIT_ASSERT(track);
|
||||
track->setLanguage("ger");
|
||||
} else if(fileName == "handbrake-chapters-2.mkv") {
|
||||
// remove 2nd tag
|
||||
m_fileInfo.removeTag(m_fileInfo.tags().at(1));
|
||||
|
@ -535,21 +553,17 @@ void OverallTests::setMkvTestMetaData()
|
|||
// add an additional tag targeting the first track
|
||||
TagTarget::IdContainerType trackIds;
|
||||
trackIds.emplace_back(m_fileInfo.tracks().at(0)->id());
|
||||
if(Tag *newTag = m_fileInfo.container()->createTag(TagTarget(30, trackIds))) {
|
||||
newTag->setValue(KnownField::Album, m_testAlbum);
|
||||
newTag->setValue(KnownField::PartNumber, m_testPartNumber);
|
||||
newTag->setValue(KnownField::TotalParts, m_testTotalParts);
|
||||
} else {
|
||||
CPPUNIT_FAIL("can not create tag");
|
||||
}
|
||||
Tag *newTag = container->createTag(TagTarget(30, trackIds));
|
||||
CPPUNIT_ASSERT_MESSAGE("create tag", newTag);
|
||||
newTag->setValue(KnownField::Album, m_testAlbum);
|
||||
newTag->setValue(KnownField::PartNumber, m_testPartNumber);
|
||||
newTag->setValue(KnownField::TotalParts, m_testTotalParts);
|
||||
// assign an attachment
|
||||
if(AbstractAttachment *attachment = m_fileInfo.container()->createAttachment()) {
|
||||
attachment->setFile(TestUtilities::testFilePath("matroska_wave1/logo3_256x256.png"));
|
||||
attachment->setMimeType("image/png");
|
||||
attachment->setName("cover.jpg");
|
||||
} else {
|
||||
CPPUNIT_FAIL("can not create attachment");
|
||||
}
|
||||
AbstractAttachment *attachment = container->createAttachment();
|
||||
CPPUNIT_ASSERT_MESSAGE("create attachment", attachment);
|
||||
attachment->setFile(TestUtilities::testFilePath("matroska_wave1/logo3_256x256.png"));
|
||||
attachment->setMimeType("image/png");
|
||||
attachment->setName("cover.jpg");
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
Loading…
Reference in New Issue