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);