From 0b8a659bc21a60039bb311526efa209744cfb829 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Mon, 7 Feb 2022 15:33:50 -0800 Subject: [PATCH] TagLib: Implement support for APE tags on TTA TrueAudio will now read APE tags, and if I should start writing tags some day, will prefer creating APE tags if no tags exist. Signed-off-by: Christopher Snowhill --- .../taglib/taglib/trueaudio/trueaudiofile.cpp | 76 ++++++++++++++++++- .../taglib/taglib/trueaudio/trueaudiofile.h | 36 ++++++++- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp index e4de436ed..b3e136e98 100644 --- a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp +++ b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp @@ -39,12 +39,14 @@ #include "id3v1tag.h" #include "id3v2tag.h" #include "id3v2header.h" +#include "apefooter.h" +#include "apetag.h" using namespace TagLib; namespace { - enum { TrueAudioID3v2Index = 0, TrueAudioID3v1Index = 1 }; + enum { TrueAudioID3v2Index = 0, TrueAudioAPEIndex = 1, TrueAudioID3v1Index = 2 }; } class TrueAudio::File::FilePrivate @@ -55,6 +57,8 @@ public: ID3v2Location(-1), ID3v2OriginalSize(0), ID3v1Location(-1), + APELocation(-1), + APEOriginalSize(0), properties(0) {} ~FilePrivate() @@ -66,6 +70,9 @@ public: long ID3v2Location; long ID3v2OriginalSize; + long APELocation; + long APEOriginalSize; + long ID3v1Location; TagUnion tag; @@ -148,7 +155,10 @@ PropertyMap TrueAudio::File::setProperties(const PropertyMap &properties) if(ID3v1Tag()) ID3v1Tag()->setProperties(properties); - return ID3v2Tag(true)->setProperties(properties); + if(ID3v2Tag()) + ID3v2Tag()->setProperties(properties); + + return APETag(true)->setProperties(properties); } TrueAudio::Properties *TrueAudio::File::audioProperties() const @@ -180,6 +190,9 @@ bool TrueAudio::File::save() const ByteVector data = ID3v2Tag()->render(); insert(data, d->ID3v2Location, d->ID3v2OriginalSize); + if(d->APELocation >= 0) + d->APELocation += (static_cast(data.size()) - d->ID3v2OriginalSize); + if(d->ID3v1Location >= 0) d->ID3v1Location += (static_cast(data.size()) - d->ID3v2OriginalSize); @@ -192,6 +205,9 @@ bool TrueAudio::File::save() if(d->ID3v2Location >= 0) { removeBlock(d->ID3v2Location, d->ID3v2OriginalSize); + if(d->APELocation >= 0) + d->APELocation -= d->ID3v2OriginalSize; + if(d->ID3v1Location >= 0) d->ID3v1Location -= d->ID3v2OriginalSize; @@ -226,6 +242,35 @@ bool TrueAudio::File::save() } } + if(APETag() && !APETag()->isEmpty()) { + // APE tag is not empty. Update the old one or create a new one. + if(d->APELocation < 0) { + if(d->ID3v1Location >= 0) + d->APELocation = d->ID3v1Location; + else + d->APELocation = length(); + } + + const ByteVector data = APETag()->render(); + insert(data, d->APELocation, d->APEOriginalSize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast(data.size()) - d->APEOriginalSize); + + d->APEOriginalSize = data.size(); + } + else { + // APE tag is empty. Remove the old one. + + if(d->APELocation >= 0) { + removeBlock(d->APELocation, d->APEOriginalSize); + + if (d->ID3v1Location >= 0) { + d->ID3v1Location -= d->APEOriginalSize; + } + } + } + return true; } @@ -239,6 +284,11 @@ ID3v2::Tag *TrueAudio::File::ID3v2Tag(bool create) return d->tag.access(TrueAudioID3v2Index, create); } +APE::Tag *TrueAudio::File::APETag(bool create) +{ + return d->tag.access(TrueAudioAPEIndex, create); +} + void TrueAudio::File::strip(int tags) { if(tags & ID3v1) @@ -247,8 +297,11 @@ void TrueAudio::File::strip(int tags) if(tags & ID3v2) d->tag.set(TrueAudioID3v2Index, 0); + if(tags & APE) + d->tag.set(TrueAudioAPEIndex, 0); + if(!ID3v1Tag()) - ID3v2Tag(true); + APETag(true); } bool TrueAudio::File::hasID3v1Tag() const @@ -261,6 +314,11 @@ bool TrueAudio::File::hasID3v2Tag() const return (d->ID3v2Location >= 0); } +bool TrueAudio::File::hasAPETag() const +{ + return (d->APELocation >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// @@ -284,7 +342,17 @@ void TrueAudio::File::read(bool readProperties) d->tag.set(TrueAudioID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); if(d->ID3v1Location < 0) - ID3v2Tag(true); + APETag(true); + + // Look for an APE tag + + d->APELocation = Utils::findAPE(this, d->ID3v1Location); + + if(d->APELocation >= 0) { + d->tag.set(TrueAudioAPEIndex, new APE::Tag(this, d->APELocation)); + d->APEOriginalSize = APETag()->footer()->completeTagSize(); + d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize; + } // Look for TrueAudio metadata diff --git a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h index 1f664d25d..dea1f20f2 100644 --- a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h +++ b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h @@ -39,13 +39,14 @@ namespace TagLib { namespace ID3v2 { class Tag; class FrameFactory; } namespace ID3v1 { class Tag; } + namespace APE { class Tag; } //! An implementation of TrueAudio metadata /*! * This is implementation of TrueAudio metadata. * - * This supports ID3v1 and ID3v2 tags as well as reading stream + * This supports ID3v1, ID3v2, and APE tags as well as reading stream * properties from the file. */ @@ -74,6 +75,8 @@ namespace TagLib { ID3v1 = 0x0001, //! Matches ID3v2 tags. ID3v2 = 0x0002, + //! Matches APE tags. + APE = 0x0004, //! Matches all tag types. AllTags = 0xffff }; @@ -141,13 +144,14 @@ namespace TagLib { /*! * Implements the unified property interface -- export function. * If the file contains both ID3v1 and v2 tags, only ID3v2 will be - * converted to the PropertyMap. + * converted to the PropertyMap. If the file contains APE tags, + * only they will be converted to the PropertyMap. */ PropertyMap properties() const; /*! * Implements the unified property interface -- import function. - * Creates in ID3v2 tag if necessary. If an ID3v1 tag exists, it will + * Creates an APE tag if necessary. If an ID3v1 tag exists, it will * be updated as well, within the limitations of ID3v1. */ PropertyMap setProperties(const PropertyMap &); @@ -211,6 +215,25 @@ namespace TagLib { */ ID3v2::Tag *ID3v2Tag(bool create = false); + /*! + * Returns a pointer to the APE tag of the file. + * + * If \a create is false (the default) this may return a null pointer + * if there is no valid APE tag. If \a create is true it will create + * an APE tag if one does not exist and returns a valid pointer. + * + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file + * on disk actually has an APE tag. + * + * \note The Tag is still owned by the TTA::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + * + * \see hasAPETag() + */ + APE::Tag *APETag(bool create = false); + /*! * This will remove the tags that match the OR-ed together TagTypes from the * file. By default it removes all tags. @@ -235,6 +258,13 @@ namespace TagLib { */ bool hasID3v2Tag() const; + /*! + * Returns whether or not the file on disk actually has an APE tag. + * + * \see APETag() + */ + bool hasAPETag() const; + /*! * Returns whether or not the given \a stream can be opened as a TrueAudio * file.