From 0a2b948f262004c9b9f04f6bef413e1b943cb81e Mon Sep 17 00:00:00 2001 From: Martchus Date: Sat, 6 May 2023 13:04:01 +0200 Subject: [PATCH] Detect APE tags, emit according messages and update README It is likely not worth adding support for APE tags at this point. However, it still makes sense to acknowledge the presence of them despite not being actually supported to avoid possible confusion. With this change, APE tags at the beginning of the file will be dropped when applying changes as the container offset is updated when skipping the tag. I suppose that makes sense considering putting those tags at the beginning is not recommended anyways. The diag messages and README have been updated accordingly. --- README.md | 8 ++++++++ mediafileinfo.cpp | 39 +++++++++++++++++++++++++++++++++++++-- signature.cpp | 5 +++++ signature.h | 1 + 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4938b8..2349094 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,19 @@ The tag library can read and write the following tag formats: * iTunes-style MP4/M4A tags (MP4-DASH is supported) * ID3v1 and ID3v2 tags * conversion between ID3v1 and different versions of ID3v2 is possible + * mainly for use in MP3 files but can be added to any kind of file * Vorbis, Opus and FLAC comments in Ogg streams * cover art via "METADATA_BLOCK_PICTURE" is supported * Vorbis comments and "METADATA_BLOCK_PICTURE" in raw FLAC streams * Matroska/WebM tags and attachments +Further remarks: + +* Unsupported file contents (such as unsupported tag formats) are *generally* preserved as-is. +* Note that APE tags are *not* supported. APE tags in the beginning of a file are strongly + unrecommended and thus discarded when applying changes. APE tags at the end of the file + are preserved as-is when applying changes. + ## File layout options ### Tag position The library allows you to choose whether tags should be placed at the beginning or at diff --git a/mediafileinfo.cpp b/mediafileinfo.cpp index 8f08233..407f81d 100644 --- a/mediafileinfo.cpp +++ b/mediafileinfo.cpp @@ -254,6 +254,23 @@ startParsingSignature: static_cast(m_container.get())->setChecksumValidationEnabled(isForcingFullParse()); break; case ContainerFormat::Unknown: + case ContainerFormat::ApeTag: + // skip APE tag if the specified size makes sense at all + if (m_containerFormat == ContainerFormat::ApeTag) { + if (const auto apeEnd = m_containerOffset + 32 + LE::toUInt32(buff + 12); apeEnd <= static_cast(size())) { + // take record of APE tag + diag.emplace_back(DiagLevel::Critical, + argsToString("Found an APE tag at the beginning of the file at offset ", m_containerOffset, + ". This tag format is not supported and the tag will therefore be ignored. It will NOT be preserved when saving as " + "placing an APE tag at the beginning of a file is strongly unrecommended."), + context); + // continue reading signature + m_containerOffset = apeEnd; + goto startParsingSignature; + } + m_containerFormat = ContainerFormat::Unknown; + } + // check for magic numbers at odd offsets // -> check for tar (magic number at offset 0x101) if (size() > 0x107) { @@ -378,12 +395,14 @@ void MediaFileInfo::parseTags(Diagnostics &diag, AbortableProgressFeedback &prog static const string context("parsing tag"); // check for ID3v1 tag - if (size() >= 128) { + auto effectiveSize = static_cast(size()); + if (effectiveSize >= 128) { m_id3v1Tag = make_unique(); try { - stream().seekg(-128, ios_base::end); + stream().seekg(effectiveSize - 128, std::ios_base::beg); m_id3v1Tag->parse(stream(), diag); m_fileStructureFlags += MediaFileStructureFlags::ActualExistingId3v1Tag; + effectiveSize -= 128; } catch (const NoDataFoundException &) { m_id3v1Tag.reset(); } catch (const OperationAbortedException &) { @@ -395,6 +414,22 @@ void MediaFileInfo::parseTags(Diagnostics &diag, AbortableProgressFeedback &prog } } + // check for APE tag at the end of the file (APE tags a the beginning are already covered when parsing the container format) + if (constexpr auto apeHeaderSize = 32; effectiveSize >= apeHeaderSize) { + const auto footerOffset = effectiveSize - apeHeaderSize; + char buffer[apeHeaderSize]; + stream().seekg(footerOffset, std::ios_base::beg); + stream().read(buffer, sizeof(buffer)); + if (BE::toUInt64(buffer) == 0x4150455441474558ul /* APETAGEX */) { + // take record of APE tag + const auto tagSize = static_cast(LE::toUInt32(buffer + 12)); + diag.emplace_back(DiagLevel::Warning, + argsToString("Found an APE tag at the end of the file at offset ", (footerOffset - tagSize), + ". This tag format is not supported and the tag will therefore be ignored. It will be preserved when saving as-is."), + context); + } + } + // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format m_id3v2Tags.clear(); for (const auto offset : m_actualId3v2TagOffsets) { diff --git a/signature.cpp b/signature.cpp index fb3d3eb..d786de8 100644 --- a/signature.cpp +++ b/signature.cpp @@ -13,6 +13,7 @@ namespace TagParser { * \brief Holds 64-bit signatures. */ enum Sig64 : std::uint64_t { + ApeTag = 0x4150455441474558ul, // APETAGEX Ar = 0x213C617263683E0A, Asf1 = 0x3026B2758E66CF11ul, Asf2 = 0xA6D900AA0062CE6Cul, @@ -126,6 +127,8 @@ ContainerFormat parseSignature(std::string_view buffer) } // return corresponding container format switch (sig) { // check 64-bit signatures + case ApeTag: + return ContainerFormat::ApeTag; case Ar: return ContainerFormat::Ar; case Asf1: @@ -490,6 +493,8 @@ std::string_view containerFormatName(ContainerFormat containerFormat) return "Zstandard compressed file"; case ContainerFormat::Id2v2Tag: return "ID3v2 tag"; + case ContainerFormat::ApeTag: + return "APE tag"; default: return "unknown"; } diff --git a/signature.h b/signature.h index aacd9de..b7cfd50 100644 --- a/signature.h +++ b/signature.h @@ -67,6 +67,7 @@ enum class ContainerFormat : unsigned int { Zip, /**< ZIP archive */ Aiff, /**< Audio Interchange File Format */ Zstd, /**< Zstandard-compressed data */ + ApeTag, /**< APE tag */ }; TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize);