From f94843614f1f1b2b87b7e5f95c7dcd5b642592ca Mon Sep 17 00:00:00 2001 From: complexlogic Date: Tue, 5 Aug 2025 06:42:16 -0700 Subject: [PATCH] Initial cues work --- taglib/matroska/ebml/ebmlmkcues.cpp | 81 ++++++++++++++ taglib/matroska/ebml/ebmlmkcues.h | 51 +++++++++ taglib/matroska/matroskacues.cpp | 161 ++++++++++++++++++++++++++++ taglib/matroska/matroskacues.h | 119 ++++++++++++++++++++ 4 files changed, 412 insertions(+) create mode 100644 taglib/matroska/ebml/ebmlmkcues.cpp create mode 100644 taglib/matroska/ebml/ebmlmkcues.h create mode 100644 taglib/matroska/matroskacues.cpp create mode 100644 taglib/matroska/matroskacues.h diff --git a/taglib/matroska/ebml/ebmlmkcues.cpp b/taglib/matroska/ebml/ebmlmkcues.cpp new file mode 100644 index 00000000..cf93a41f --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkcues.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmkcues.h" +#include "ebmluintelement.h" +#include "ebmlstringelement.h" +#include "ebmlutils.h" +#include "matroskafile.h" +#include "matroskacues.h" +#include "tdebug.h" +#include "tutils.h" + +using namespace TagLib; + +Matroska::Cues* EBML::MkCues::parse() +{ + auto cues = new Matroska::Cues(); + cues->setOffset(offset); + cues->setSize(getSize()); + cues->setID(id); + + for (auto cuesChild : elements) { + if (cuesChild->getId() != ElementIDs::MkCuePoint) + continue; + auto cuePointElement = static_cast(cuesChild); + auto cuePoint = new Matroska::CuePoint(); + + for (auto cuePointChild : *cuePointElement) { + Id id = cuePointChild->getId(); + if (id == ElementIDs::MkCueTime) + cuePoint->setTime(static_cast(cuePointChild)->getValue()); + else if (id == ElementIDs::MkCueTrackPositions) { + auto cueTrack = new Matroska::CueTrack(); + auto cueTrackElement = static_cast(cuePointChild); + for (auto cueTrackChild : *cueTrackElement) { + Id trackId = cueTrackChild->getId(); + if (trackId == ElementIDs::MkCueTrack) + cueTrack->setTrackNumber(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueClusterPosition) + cueTrack->setClusterPosition(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueRelativePosition) + cueTrack->setRelativePosition(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueDuration) + cueTrack->setDuration(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueBlockNumber) + cueTrack->setBlockNumber(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueCodecState) + cueTrack->setCodecState(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueReference) { + auto cueReference = static_cast(cueTrackChild); + for (auto cueReferenceChild : *cueReference) { + if (cueReferenceChild->getId() != ElementIDs::MkCueReference) + continue; + cueTrack->addReferenceTime(static_cast(cueReferenceChild)->getValue()); + } + } + } + cuePoint->addCueTrack(cueTrack); + } + } + cues->addCuePoint(cuePoint); + } + return cues; +} diff --git a/taglib/matroska/ebml/ebmlmkcues.h b/taglib/matroska/ebml/ebmlmkcues.h new file mode 100644 index 00000000..78a194dc --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkcues.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + +#ifndef TAGLIB_EBMLMKCUES_H +#define TAGLIB_EBMLMKCUES_H +#ifndef DO_NOT_DOCUMENT + +namespace TagLib { + namespace Matroska { + class Cues; + } + //class Matroska::Tag; + namespace EBML { + class MkCues : public MasterElement + { + public: + MkCues(int sizeLength, offset_t dataSize, offset_t offset) + : MasterElement(ElementIDs::MkCues, sizeLength, dataSize, offset) + {} + MkCues() + : MasterElement(ElementIDs::MkCues, 0, 0, 0) + {} + + Matroska::Cues* parse(); + + }; + } +} +#endif +#endif diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp new file mode 100644 index 00000000..ef046017 --- /dev/null +++ b/taglib/matroska/matroskacues.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskacues.h" +#include "ebmlelement.h" +#include "ebmlmkcues.h" +#include "ebmlmasterelement.h" +#include "ebmluintelement.h" +#include "tlist.h" +#include "tdebug.h" +#include "tfile.h" + +using namespace TagLib; + +Matroska::Cues::Cues() +: Element(ElementIDs::MkCues) +{ + cuePoints.setAutoDelete(true); +} + +ByteVector Matroska::Cues::renderInternal() +{ + EBML::MkCues cues; + for (auto &cuePoint : cuePoints) { + auto cuePointElement = new EBML::MasterElement(EBML::ElementIDs::MkCuePoint); + auto timestamp = new EBML::UIntElement(EBML::ElementIDs::MkCueTime); + timestamp->setValue(cuePoint->getTime()); + cuePointElement->appendElement(timestamp); + + auto trackList = cuePoint->cueTrackList(); + for (auto &cueTrack : trackList) { + auto cueTrackElement = new EBML::MasterElement(EBML::ElementIDs::MkCueTrackPositions); + + // Track number + auto trackNumber = new EBML::UIntElement(EBML::ElementIDs::MkCueTrack); + trackNumber->setValue(cueTrack->getTrackNumber()); + cueTrackElement->appendElement(trackNumber); + + // Cluster position + auto clusterPosition = new EBML::UIntElement(EBML::ElementIDs::MkCueClusterPosition); + clusterPosition->setValue(cueTrack->getClusterPosition()); + cueTrackElement->appendElement(clusterPosition); + + // Todo - other elements + + + // Reference times + auto referenceTimes = cueTrack->referenceTimes(); + if (!referenceTimes.isEmpty()) { + auto cueReference = new EBML::MasterElement(EBML::ElementIDs::MkCueReference); + for (auto reference : referenceTimes) { + auto refTime = new EBML::UIntElement(EBML::ElementIDs::MkCueRefTime); + refTime->setValue(reference); + cueReference->appendElement(refTime); + } + cueTrackElement->appendElement(cueReference); + } + cuePointElement->appendElement(cueTrackElement); + } + } + return cues.render(); +} + +bool Matroska::Cues::render() +{ + if (!needsRender) + return true; + + + setData(cues.render()); + needsRender = false; + return true; +} + +bool Matroska::Cues::sizeChanged(Element &caller, offset_t delta) +{ + offset_t offset = caller.offset(); + for (auto cuePoint : cuePoints) + needsRender |= cuePoint->adjustOffset(offset, delta); + return true; +} + +bool Matroska::Cues::isValid(TagLib::File &file, offset_t segmentDataOffset) const +{ + for (const auto cuePoint : cuePoints) { + if (!cuePoint->isValid(file, segmentDataOffset)) + return false; + } + return true; +} + +Matroska::CuePoint::CuePoint() +{ + cueTracks.setAutoDelete(true); +} + +bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) const +{ + for (const auto track : cueTracks) { + if (!track->isValid(file, segmentDataOffset)) + return false; + } + return true; +} + +bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) +{ + bool ret = false; + for (auto cueTrack : cueTracks) + ret |= cueTrack->adjustOffset(offset, delta); + + return ret; +} + +bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) const +{ + if (!trackNumber) { + debug("Cue track number not set"); + return false; + } + if (!clusterPosition) { + debug("Cue track cluster position not set"); + return false; + } + file.seek(segmentDataOffset + clusterPosition); + if (EBML::Element::readId(file) != EBML::ElementIDs::MkCluster) { + debug("No cluster found at position"); + return false; + } + if (codecState) { + file.seek(segmentDataOffset + codecState); + if (EBML::Element::readId(file) != EBML::ElementIDs::MkCodecState) { + debug("No codec state found at position"); + return false; + } + } + return true; +} + +bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta) +{ + return false; +} + diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h new file mode 100644 index 00000000..57ed29f2 --- /dev/null +++ b/taglib/matroska/matroskacues.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKACUES_H +#define HAS_MATROSKACUES_H +#ifndef DO_NOT_DOCUMENT + +#include +#include +#include + +#include "tlist.h" +#include "matroskaelement.h" + +namespace TagLib { + class File; + namespace EBML { + class MkCues; + } + + namespace Matroska { + class CuePoint; + class CueTrack; + class Cues : public Element + { + public: + using CuePointList = List; + Cues(); + ~Cues() override = default; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + void addCuePoint(CuePoint *cuePoint) { cuePoints.append(cuePoint); } + CuePointList cuePointList() { return cuePoints; } + bool sizeChanged(Element &caller, offset_t delta) override; + bool render() override; + + + private: + friend class EBML::MkCues; + ByteVector renderInternal(); + bool needsRender = false; + + List cuePoints; + }; + + class CuePoint + { + public: + using CueTrackList = List; + using Time = unsigned long long; + CuePoint(); + ~CuePoint() = default; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + void addCueTrack(CueTrack *cueTrack) { cueTracks.append(cueTrack); } + CueTrackList cueTrackList() const { return cueTracks; } + void setTime(Time time) { this->time = time; } + Time getTime() const { return time; } + bool adjustOffset(offset_t offset, offset_t delta); + + + private: + CueTrackList cueTracks; + Time time = 0; + }; + + class CueTrack + { + public: + using ReferenceTimeList = List; + CueTrack() = default; + ~CueTrack() = default; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + void setTrackNumber(unsigned int trackNumber) { this->trackNumber = trackNumber; } + unsigned int getTrackNumber() const { return trackNumber; } + void setClusterPosition(offset_t clusterPosition) { this->clusterPosition = clusterPosition; } + offset_t getClusterPosition() const { return clusterPosition; } + void setRelativePosition(offset_t relativePosition) { this->relativePosition = relativePosition; } + offset_t getRelativePosition() const { return relativePosition; } + void setCodecState(offset_t codecState) { this->codecState = codecState; } + offset_t getCodecState() const { return codecState; } + void setBlockNumber(unsigned int blockNumber) { this->blockNumber = blockNumber; } + unsigned int getBlockNumber() const { return blockNumber; } + void setDuration(unsigned long long duration) { this->duration = duration; } + unsigned long long getDuration() const { return duration; } + void addReferenceTime(unsigned long long refTime) { refTimes.append(refTime); } + ReferenceTimeList referenceTimes() const { return refTimes; } + bool adjustOffset(offset_t offset, offset_t delta); + + + private: + unsigned int trackNumber = 0; + offset_t clusterPosition = 0; + offset_t relativePosition = 0; + unsigned int blockNumber = 0; + unsigned long long duration = 0; + offset_t codecState = 0; + ReferenceTimeList refTimes; + }; + } +} + +#endif +#endif