mirror of
https://github.com/taglib/taglib.git
synced 2026-06-07 06:50:32 -04:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a27dca557e | ||
|
|
8fcda2daa2 | ||
|
|
c32f7c7f86 | ||
|
|
e23d97c580 | ||
|
|
b8b91fd072 | ||
|
|
e83e02da2e | ||
|
|
0b5296e20e | ||
|
|
83fdf27cd7 | ||
|
|
7010d112ba |
@@ -77,6 +77,8 @@ public:
|
||||
offset_t flacStart { 0 };
|
||||
offset_t streamStart { 0 };
|
||||
bool scanned { false };
|
||||
bool hasiXML { false };
|
||||
bool hasBEXT { false };
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -279,6 +281,10 @@ bool FLAC::File::save()
|
||||
payload.append(ByteVector::fromUInt(xml.size(), false));
|
||||
payload.append(xml);
|
||||
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::Application, payload));
|
||||
d->hasiXML = true;
|
||||
}
|
||||
else {
|
||||
d->hasiXML = false;
|
||||
}
|
||||
if(!d->bextData.isEmpty()) {
|
||||
ByteVector payload;
|
||||
@@ -287,6 +293,10 @@ bool FLAC::File::save()
|
||||
payload.append(ByteVector::fromUInt(d->bextData.size(), false));
|
||||
payload.append(d->bextData);
|
||||
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::Application, payload));
|
||||
d->hasBEXT = true;
|
||||
}
|
||||
else {
|
||||
d->hasBEXT = false;
|
||||
}
|
||||
|
||||
// Replace metadata blocks
|
||||
@@ -532,12 +542,12 @@ bool FLAC::File::hasID3v2Tag() const
|
||||
|
||||
bool FLAC::File::hasiXMLData() const
|
||||
{
|
||||
return !d->iXMLData.isEmpty();
|
||||
return d->hasiXML;
|
||||
}
|
||||
|
||||
bool FLAC::File::hasBEXTData() const
|
||||
{
|
||||
return !d->bextData.isEmpty();
|
||||
return d->hasBEXT;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -719,14 +729,18 @@ void FLAC::File::scan()
|
||||
}
|
||||
|
||||
if(innerId == "iXML") {
|
||||
if(d->iXMLData.isEmpty())
|
||||
if(!d->hasiXML) {
|
||||
d->hasiXML = true;
|
||||
d->iXMLData = String(innerData, String::UTF8);
|
||||
}
|
||||
else
|
||||
debug("FLAC::File::scan() -- multiple iXML blocks found, discarding");
|
||||
}
|
||||
else if(innerId == "bext") {
|
||||
if(d->bextData.isEmpty())
|
||||
if(!d->hasBEXT) {
|
||||
d->hasBEXT = true;
|
||||
d->bextData = innerData;
|
||||
}
|
||||
else
|
||||
debug("FLAC::File::scan() -- multiple BEXT blocks found, discarding");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "ebmlmkchapters.h"
|
||||
#include "ebmlstringelement.h"
|
||||
#include <atomic>
|
||||
#include "ebmluintelement.h"
|
||||
#include "matroskachapters.h"
|
||||
#include "matroskachapteredition.h"
|
||||
@@ -33,6 +34,20 @@ using namespace TagLib;
|
||||
|
||||
namespace {
|
||||
|
||||
// Counter for synthesising a unique ChapterUID when the source file omits
|
||||
// the MkChapterUID element (out-of-spec but produced by some muxers, e.g.
|
||||
// older FFmpeg or audiobook generators). MKVToolNix / MediaInfo / FFmpeg
|
||||
// all tolerate UID-less chapters; without this synth, the call sites in
|
||||
// MkChapters::parse() filter such chapters out via the `chapter.uid()`
|
||||
// check and they are silently lost.
|
||||
//
|
||||
// High bit set as a marker; counter starts at 1 and increments per atom
|
||||
// so each parsed UID-less chapter gets a distinct value within the
|
||||
// process lifetime. Real ChapterUIDs are random 64-bit values from
|
||||
// muxers; the (1ULL << 63) | n encoding makes collision practically
|
||||
// impossible while keeping the distinction local to TagLib.
|
||||
static std::atomic<Matroska::Chapter::UID> synthesisedChapterUidCounter{1};
|
||||
|
||||
Matroska::Chapter parseChapterAtom(
|
||||
const std::unique_ptr<EBML::Element> &atomElement)
|
||||
{
|
||||
@@ -67,6 +82,13 @@ Matroska::Chapter parseChapterAtom(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Spec-noncompliant: ChapterAtom missing ChapterUID. Synthesise a
|
||||
// process-unique UID so the chapter survives downstream `chapter.uid()`
|
||||
// filters. See synthesisedChapterUidCounter comment above.
|
||||
if(chapterUid == 0)
|
||||
chapterUid = (1ULL << 63) |
|
||||
synthesisedChapterUidCounter.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
return Matroska::Chapter(chapterTimeStart, chapterTimeEnd, chapterDisplays,
|
||||
chapterUid, chapterHidden);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "ebmlmksegment.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ebmlutils.h"
|
||||
#include "matroskafile.h"
|
||||
#include "matroskatag.h"
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "ebmlutils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include "tbytevector.h"
|
||||
#include "matroskafile.h"
|
||||
#include "tutils.h"
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "ebmlvoidelement.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ebmlutils.h"
|
||||
#include "tbytevector.h"
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "matroskaattachments.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "matroskaattachedfile.h"
|
||||
#include "ebmlmkattachments.h"
|
||||
#include "ebmlmasterelement.h"
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace TagLib {
|
||||
Time timeStart() const;
|
||||
|
||||
/*!
|
||||
* Returns the timestamp of the start of the chapter in nanoseconds.
|
||||
* Returns the timestamp of the end of the chapter in nanoseconds.
|
||||
*/
|
||||
Time timeEnd() const;
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "matroskachapters.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "matroskachapteredition.h"
|
||||
#include "ebmlstringelement.h"
|
||||
#include "ebmlbinaryelement.h"
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "matroskafile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "matroskatag.h"
|
||||
#include "matroskaattachments.h"
|
||||
#include "matroskaattachedfile.h"
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "matroskaseekhead.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ebmlmkseekhead.h"
|
||||
#include "ebmlbinaryelement.h"
|
||||
#include "ebmluintelement.h"
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "matroskatag.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
|
||||
#include "ebmlmasterelement.h"
|
||||
#include "ebmlstringelement.h"
|
||||
#include "ebmlbinaryelement.h"
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#include "modfile.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "tstringlist.h"
|
||||
#include "tdebug.h"
|
||||
#include "tpropertymap.h"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "mp4atom.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <utility>
|
||||
|
||||
@@ -50,15 +50,15 @@ MP4::Chapter::Chapter(const Chapter &other) :
|
||||
|
||||
MP4::Chapter::Chapter(Chapter &&other) noexcept = default;
|
||||
|
||||
MP4::Chapter::Chapter::~Chapter() = default;
|
||||
MP4::Chapter::~Chapter() = default;
|
||||
|
||||
MP4::Chapter &MP4::Chapter::Chapter::operator=(const Chapter &other)
|
||||
MP4::Chapter &MP4::Chapter::operator=(const Chapter &other)
|
||||
{
|
||||
Chapter(other).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MP4::Chapter &MP4::Chapter::Chapter::operator=(
|
||||
MP4::Chapter &MP4::Chapter::operator=(
|
||||
Chapter &&other) noexcept = default;
|
||||
|
||||
bool MP4::Chapter::operator==(const Chapter &other) const
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "textidentificationframe.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#include "mpegfile.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "taglib_config.h"
|
||||
#include "id3v2framefactory.h"
|
||||
#include "tdebug.h"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "tagunion.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "tstringlist.h"
|
||||
|
||||
@@ -74,6 +74,7 @@ class TestFLAC : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testReadBEXTRiffWrapped);
|
||||
CPPUNIT_TEST(testWriteiXMLAndBEXT);
|
||||
CPPUNIT_TEST(testWriteEmptyClearsiXMLAndBEXT);
|
||||
CPPUNIT_TEST(testHasiXMLAndBEXTReflectFileState);
|
||||
CPPUNIT_TEST(testRoundTripPreservesUnknownApplicationBlock);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
@@ -833,6 +834,45 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void testHasiXMLAndBEXTReflectFileState()
|
||||
{
|
||||
// hasiXMLData() / hasBEXTData() must report whether the *file* carries an
|
||||
// iXML / bext APPLICATION block, not whether in-memory payload happens to
|
||||
// be non-empty. Regression test for an issue where the accessors were
|
||||
// implemented as !data.isEmpty() and so flipped on as soon as set*Data()
|
||||
// was called, before save(), and missed a real-but-empty block.
|
||||
ScopedFileCopy copy("silence-44-s", ".flac");
|
||||
const string newname = copy.fileName();
|
||||
|
||||
{
|
||||
FLAC::File f(newname.c_str());
|
||||
CPPUNIT_ASSERT(!f.hasiXMLData());
|
||||
CPPUNIT_ASSERT(!f.hasBEXTData());
|
||||
f.setiXMLData("<BWFXML/>");
|
||||
f.setBEXTData(ByteVector("bext"));
|
||||
// File hasn't been saved yet — file still has no blocks.
|
||||
CPPUNIT_ASSERT(!f.hasiXMLData());
|
||||
CPPUNIT_ASSERT(!f.hasBEXTData());
|
||||
f.save();
|
||||
// After save the blocks are on disk.
|
||||
CPPUNIT_ASSERT(f.hasiXMLData());
|
||||
CPPUNIT_ASSERT(f.hasBEXTData());
|
||||
}
|
||||
{
|
||||
FLAC::File f(newname.c_str());
|
||||
CPPUNIT_ASSERT(f.hasiXMLData());
|
||||
CPPUNIT_ASSERT(f.hasBEXTData());
|
||||
f.setiXMLData(String());
|
||||
f.setBEXTData(ByteVector());
|
||||
// In-memory payload now empty but the file still carries both blocks.
|
||||
CPPUNIT_ASSERT(f.hasiXMLData());
|
||||
CPPUNIT_ASSERT(f.hasBEXTData());
|
||||
f.save();
|
||||
CPPUNIT_ASSERT(!f.hasiXMLData());
|
||||
CPPUNIT_ASSERT(!f.hasBEXTData());
|
||||
}
|
||||
}
|
||||
|
||||
void testRoundTripPreservesUnknownApplicationBlock()
|
||||
{
|
||||
// Source: silence-44-s with an extra APPLICATION/"SMED" block injected
|
||||
|
||||
Reference in New Issue
Block a user