AIFF: Support historic NAME/AUTH/(c)/ANNO text chunks (#1488)

(defaulting to Mac OS Roman charset (Windows codepage 10000),
overridable by user)

Before this commit, only ID3 tags were supported, but AIFF also historically
features 4 text chunks, see
https://web.archive.org/web/20180613135101/https://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/AIFF-1.3.pdf,
chapter "Text Chunks - Name, Author, Copyright, Annotation".
This commit is contained in:
MinnieTheMoocher
2026-11-03 19:56:12 +01:00
committed by GitHub
parent 52d0f7cc5e
commit ba4eba9d41
9 changed files with 959 additions and 70 deletions
+2
View File
@@ -175,6 +175,7 @@ if(WITH_RIFF)
set(tag_HDRS ${tag_HDRS}
riff/rifffile.h
riff/aiff/aifffile.h
riff/aiff/aiffnativetag.h
riff/aiff/aiffproperties.h
riff/wav/wavfile.h
riff/wav/wavproperties.h
@@ -412,6 +413,7 @@ if(WITH_RIFF)
set(aiff_SRCS
riff/aiff/aifffile.cpp
riff/aiff/aiffnativetag.cpp
riff/aiff/aiffproperties.cpp
)
+36 -1
View File
@@ -25,6 +25,7 @@
#include "aifffile.h"
#include "aiffnativetag.h"
#include "tdebug.h"
#include "tpropertymap.h"
#include "tagutils.h"
@@ -36,7 +37,8 @@ class RIFF::AIFF::File::FilePrivate
public:
FilePrivate(const ID3v2::FrameFactory *frameFactory)
: ID3v2FrameFactory(frameFactory ? frameFactory
: ID3v2::FrameFactory::instance())
: ID3v2::FrameFactory::instance()),
nativeTag(std::make_unique<NativeTag>())
{
}
@@ -45,8 +47,10 @@ public:
const ID3v2::FrameFactory *ID3v2FrameFactory;
std::unique_ptr<Properties> properties;
std::unique_ptr<ID3v2::Tag> tag;
std::unique_ptr<NativeTag> nativeTag;
bool hasID3v2 { false };
bool hasNative { false };
};
////////////////////////////////////////////////////////////////////////////////
@@ -90,6 +94,11 @@ ID3v2::Tag *RIFF::AIFF::File::tag() const
return d->tag.get();
}
RIFF::AIFF::NativeTag *RIFF::AIFF::File::nativeTag() const
{
return d->nativeTag.get();
}
PropertyMap RIFF::AIFF::File::properties() const
{
return d->tag->properties();
@@ -138,6 +147,16 @@ bool RIFF::AIFF::File::save(ID3v2::Version version)
d->hasID3v2 = true;
}
d->hasNative = false;
for(const auto &[id, payload] : d->nativeTag->render()) {
if(payload.isEmpty())
removeChunk(id);
else {
setChunkData(id, payload);
d->hasNative = true;
}
}
return true;
}
@@ -146,12 +165,19 @@ bool RIFF::AIFF::File::hasID3v2Tag() const
return d->hasID3v2;
}
bool RIFF::AIFF::File::hasNativeTag() const
{
return d->hasNative;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void RIFF::AIFF::File::read(bool readProperties)
{
NativeTag::FieldDataMap nativeFields;
for(unsigned int i = 0; i < chunkCount(); ++i) {
if(const ByteVector name = chunkName(i); name == "ID3 " || name == "id3 ") {
if(!d->tag) {
@@ -163,11 +189,20 @@ void RIFF::AIFF::File::read(bool readProperties)
debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found.");
}
}
else if(NativeTag::isNativeField(name)) {
if(nativeFields.contains(name))
debug("RIFF::AIFF::File::read() - Duplicate native field found.");
else
nativeFields[name] = chunkData(i);
}
}
if(!d->tag)
d->tag = std::make_unique<ID3v2::Tag>(nullptr, 0, d->ID3v2FrameFactory);
d->hasNative = !nativeFields.isEmpty();
d->nativeTag = std::make_unique<NativeTag>(nativeFields);
if(readProperties)
d->properties = std::make_unique<Properties>(this, Properties::Average);
}
+35 -1
View File
@@ -40,11 +40,15 @@ namespace TagLib {
* This is an implementation of AIFF metadata.
*
* This supports an ID3v2 tag as well as reading stream from the ID3 RIFF
* chunk as well as properties from the file.
* chunk as well as properties from the file, and the native text fields
* (NAME, AUTH, "(c) ", ANNO) an AIFF file carried before ID3v2 existed,
* which are reached through nativeTag().
*/
namespace AIFF {
class NativeTag;
//! An implementation of TagLib::File with AIFF specific methods
/*!
@@ -127,6 +131,13 @@ namespace TagLib {
/*!
* Saves the file.
*
* The ID3v2 tag is written back to its chunk, and each
* native field that holds text is written to its own chunk, encoded
* with the charset the native tag was read with, while a native field
* that holds none loses its chunk.
*
* \see nativeTag()
*/
bool save() override;
@@ -150,6 +161,29 @@ namespace TagLib {
*/
static bool isSupported(IOStream *stream);
/*!
* Returns the native text tag for this file: the NAME, AUTH, "(c) "
* and ANNO chunks an AIFF file held before ID3v2 existed.
*
* \note This always returns a valid pointer regardless of whether or
* not the file on disk has native text chunks. Use hasNativeTag() to
* check if the file on disk actually has them.
*
* \note This is a separate tag next to the ID3v2 one tag() returns;
* neither covers the other's fields.
*
* \see hasNativeTag()
*/
NativeTag *nativeTag() const;
/*!
* Returns whether or not the file on disk actually has native text
* chunks.
*
* \see nativeTag()
*/
bool hasNativeTag() const;
private:
void read(bool readProperties);
+296
View File
@@ -0,0 +1,296 @@
/***************************************************************************
* 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 "aiffnativetag.h"
#include <algorithm>
#include <array>
#include <utility>
#include "tcharset.h"
#include "tbytevector.h"
#include "tdebug.h"
#include "tutils.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace RIFF::AIFF;
namespace
{
//! The code page NativeTag::setAssumedCodePage() states.
//!
//! Used when a tag is read from a file, which declares none anywhere.
//! 0, the initial value, states none either and means Mac OS Roman, the
//! charset Macintosh software wrote these chunks in; any other number is
//! the one the caller stated itself.
unsigned int codePageAssumed = 0;
//! The charset the assumed code page selects, decided when a tag is read.
Charset::Type assumedCharset()
{
// No code page stated is Mac OS Roman: these chunks were written by
// Macintosh software.
const unsigned int codePage = codePageAssumed != 0 ? codePageAssumed : 10000;
if(const auto charset = Charset::typeForCodePage(codePage))
return *charset;
// setAssumedCodePage() only ever stores a page this build can convert, so
// the number reaching here with no table is the initial 0 of a build
// without the charset tables: its ASCII, the specification's own text,
// reads the same as ISO 8859-1.
return Charset::Type::Latin1;
}
//! The four native text chunks, which is also the field list of the class.
const std::array<ByteVector, 4> nativeFieldIds {
ByteVector("NAME"), ByteVector("AUTH"), ByteVector("(c) "), ByteVector("ANNO")
};
//! Reads the text \a data holds.
String decodeField(const ByteVector &data, Charset::Type charset)
{
// The chunk is text from end to end, so nothing is skipped here; decode()
// itself stops at a NUL, which is how a writer that terminated its text
// like a C string comes out without one.
return Charset::decode(data, charset);
}
const Map<ByteVector, String> propertyKeyForId = {
{"NAME", "TITLE"},
{"AUTH", "ARTIST"},
{"(c) ", "COPYRIGHT"},
{"ANNO", "COMMENT"}
};
} // namespace
class RIFF::AIFF::NativeTag::NativeTagPrivate
{
public:
Map<ByteVector, String> fieldListMap;
//! The charset the tag's code page selects, resolved once when the tag is
//! read, and the one its text is written back with.
Charset::Type charset { Charset::Type::Latin1 };
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
RIFF::AIFF::NativeTag::NativeTag() :
NativeTag(FieldDataMap())
{
}
RIFF::AIFF::NativeTag::NativeTag(const FieldDataMap &fields) :
d(std::make_unique<NativeTagPrivate>())
{
d->charset = assumedCharset();
for(const auto &[id, payload] : fields) {
if(isNativeField(id)) {
if(String text = decodeField(payload, d->charset); !text.isEmpty())
d->fieldListMap[id] = text;
}
}
}
RIFF::AIFF::NativeTag::~NativeTag() = default;
String RIFF::AIFF::NativeTag::title() const
{
return fieldText("NAME");
}
String RIFF::AIFF::NativeTag::artist() const
{
return fieldText("AUTH");
}
String RIFF::AIFF::NativeTag::album() const
{
return String();
}
String RIFF::AIFF::NativeTag::comment() const
{
return fieldText("ANNO");
}
String RIFF::AIFF::NativeTag::genre() const
{
return String();
}
unsigned int RIFF::AIFF::NativeTag::year() const
{
return 0;
}
unsigned int RIFF::AIFF::NativeTag::track() const
{
return 0;
}
void RIFF::AIFF::NativeTag::setTitle(const String &s)
{
setFieldText("NAME", s);
}
void RIFF::AIFF::NativeTag::setArtist(const String &s)
{
setFieldText("AUTH", s);
}
void RIFF::AIFF::NativeTag::setAlbum(const String &)
{
}
void RIFF::AIFF::NativeTag::setComment(const String &s)
{
setFieldText("ANNO", s);
}
void RIFF::AIFF::NativeTag::setGenre(const String &)
{
}
void RIFF::AIFF::NativeTag::setYear(unsigned int)
{
}
void RIFF::AIFF::NativeTag::setTrack(unsigned int)
{
}
bool RIFF::AIFF::NativeTag::isEmpty() const
{
return d->fieldListMap.isEmpty();
}
PropertyMap RIFF::AIFF::NativeTag::properties() const
{
PropertyMap props;
for(const auto &[id, val] : std::as_const(d->fieldListMap)) {
if(const String key = propertyKeyForId.value(id); !key.isEmpty())
props[key].append(val);
}
return props;
}
void RIFF::AIFF::NativeTag::removeUnsupportedProperties(const StringList &props)
{
// properties() reports unsupported data for none of its keys - all four
// chunks have one - so anything here is an id a caller passed itself.
for(const auto &id : props)
d->fieldListMap.erase(id.data(String::Latin1));
}
PropertyMap RIFF::AIFF::NativeTag::setProperties(const PropertyMap &props)
{
static const Map<String, ByteVector> idForPropertyKey = [] {
Map<String, ByteVector> map;
for(const auto &[id, key] : propertyKeyForId)
map[key] = id;
return map;
}();
const PropertyMap origProps = properties();
for(const auto &[key, _] : origProps) {
if(!props.contains(key) || props.value(key).isEmpty())
d->fieldListMap.erase(idForPropertyKey.value(key));
}
PropertyMap ignoredProps;
for(const auto &[key, val] : props) {
if(ByteVector id = idForPropertyKey.value(key);
!id.isEmpty() && !val.isEmpty()) {
d->fieldListMap[id] = val.front();
}
else {
ignoredProps.insert(key, val);
}
}
return ignoredProps;
}
bool RIFF::AIFF::NativeTag::isNativeField(const ByteVector &id)
{
return std::find(nativeFieldIds.begin(), nativeFieldIds.end(), id) !=
nativeFieldIds.end();
}
String RIFF::AIFF::NativeTag::fieldText(const ByteVector &id) const
{
if(d->fieldListMap.contains(id))
return d->fieldListMap[id];
return String();
}
void RIFF::AIFF::NativeTag::setFieldText(const ByteVector &id, const String &s)
{
if(!isNativeField(id)) {
debug("RIFF::AIFF::NativeTag::setFieldText() - Not a native AIFF field, ignored.");
return;
}
if(!s.isEmpty())
d->fieldListMap[id] = s;
else
removeField(id);
}
void RIFF::AIFF::NativeTag::removeField(const ByteVector &id)
{
if(d->fieldListMap.contains(id))
d->fieldListMap.erase(id);
}
RIFF::AIFF::NativeTag::FieldDataMap RIFF::AIFF::NativeTag::render() const
{
FieldDataMap chunks;
for(const auto &id : nativeFieldIds) {
chunks[id] = d->fieldListMap.contains(id)
? Charset::encode(d->fieldListMap[id], d->charset)
: ByteVector();
}
return chunks;
}
unsigned int RIFF::AIFF::NativeTag::assumedCodePage()
{
return codePageAssumed;
}
void RIFF::AIFF::NativeTag::setAssumedCodePage(unsigned int codePage)
{
if(codePage != 0 && !Charset::typeForCodePage(codePage)) {
debug(Utils::formatString(
"RIFF::AIFF::NativeTag::setAssumedCodePage() - Unsupported code page %u, ignored.",
codePage));
return;
}
codePageAssumed = codePage;
}
+193
View File
@@ -0,0 +1,193 @@
/***************************************************************************
* 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 TAGLIB_AIFFNATIVETAG_H
#define TAGLIB_AIFFNATIVETAG_H
#include "tmap.h"
#include "tstring.h"
#include "tbytevector.h"
#include "taglib_export.h"
#include "tag.h"
namespace TagLib::RIFF::AIFF {
//! The historic native text fields of an AIFF file.
/*!
* Before ID3v2 existed, an AIFF file carried its metadata in four text
* chunks of its own: NAME (the title), AUTH (the artist), "(c) " (the
* copyright) and ANNO (the comment). The text fills the chunk from end
* to end. A file may hold any subset of them, and this class is the
* subset a file held, plus whatever the caller sets on it.
*
* The AIFF specification gives these chunks no charset: they are bytes
* on a Macintosh filesystem, and the software that wrote them wrote Mac
* OS Roman, which is therefore what this class assumes. Because the
* file cannot say otherwise, the charset is what both reading and
* writing use - the same situation as ID3v1, and the same solution: the
* code page is resolved once when the tag is read and kept. A caller
* whose files hold another charset says so with setAssumedCodePage()
* before reading them, and a tag read and written under one code page
* keeps its text byte for byte.
*
* album(), genre(), year() and track() have no native field behind them:
* the getters are empty and the setters do nothing.
*/
class TAGLIB_EXPORT NativeTag : public Tag
{
public:
/*!
* The raw chunks a NativeTag is read from: a chunk id in the four this
* class understands, mapped to the contents of that chunk, which is
* its text from end to end. Ids this class does not know are ignored.
*/
using FieldDataMap = Map<ByteVector, ByteVector>;
/*!
* Constructs an empty native tag, whose text is read and written in
* the code page assumedCodePage() names.
*
* \see assumedCodePage()
*/
NativeTag();
/*!
* Constructs a native tag from the chunks \a fields, decoded with the
* code page assumedCodePage() names.
*
* The code page is decided here and kept: render() writes the text
* back in the same charset it was read with, which is what a file
* that declares no charset anywhere needs. A tag whose files hold
* another charset is read by naming that charset before this is
* constructed, so that it is the one kept.
*
* \see assumedCodePage()
* \see setAssumedCodePage()
*/
explicit NativeTag(const FieldDataMap &fields);
~NativeTag() override;
NativeTag(const NativeTag &) = delete;
NativeTag &operator=(const NativeTag &) = delete;
// Reimplementations
String title() const override;
String artist() const override;
String album() const override;
String comment() const override;
String genre() const override;
unsigned int year() const override;
unsigned int track() const override;
void setTitle(const String &s) override;
void setArtist(const String &s) override;
void setAlbum(const String &s) override;
void setComment(const String &s) override;
void setGenre(const String &s) override;
void setYear(unsigned int i) override;
void setTrack(unsigned int i) override;
bool isEmpty() const override;
PropertyMap properties() const override;
void removeUnsupportedProperties(const StringList &props) override;
PropertyMap setProperties(const PropertyMap &props) override;
/*!
* Whether \a id is one of the four native text chunks this class
* reads and writes: "NAME", "AUTH", "(c) " or "ANNO".
*/
static bool isNativeField(const ByteVector &id);
/*!
* Returns the value of the field with the id \a id, which has to be
* one of "NAME", "AUTH", "(c) " or "ANNO"; any other id is not a
* native field of this class and yields an empty string.
*/
String fieldText(const ByteVector &id) const;
/*!
* Sets the value of the field with the id \a id to \a s, where \a id
* has to be one of "NAME", "AUTH", "(c) " or "ANNO". An empty \a s
* removes the field, and with it its chunk. Another id is a chunk
* this class does not write and is ignored with a debug message.
*/
void setFieldText(const ByteVector &id, const String &s);
/*!
* Removes the field with the id \a id.
*/
void removeField(const ByteVector &id);
/*!
* Renders every native field back to binary data, ready to be written
* to disk: each of the four chunk ids mapped to the text of its
* chunk, encoded with the code page this tag was read with. An empty
* payload is a field that holds no text, whose chunk has to go.
*
* RIFF::AIFF::File::save() writes these payloads to the file, which is
* where the assumed charset reaches the disk: a tag read and written
* under one code page keeps its text byte for byte.
*/
FieldDataMap render() const;
/*!
* Returns the code page assumed for a native tag read from a file,
* which is 0 - no code page stated, and therefore Mac OS Roman - until
* setAssumedCodePage() states another one.
*
* \see setAssumedCodePage()
*/
static unsigned int assumedCodePage();
/*!
* Sets the code page \a codePage assumed for a native tag read from a
* file: the text of every chunk is decoded with it, and a tag read
* under it writes its text back with it too.
*
* \a codePage is a Windows code page number, the same numbering a CSET
* chunk of a RIFF file uses: 65001 assumes UTF-8 and 28591 assumes
* ISO 8859-1, while 0, the value to go back to, states no code page
* and therefore assumes Mac OS Roman, which is what a Macintosh
* writer's bytes are. This is where the caller says which of them its
* files hold, because an AIFF file has no place to say it itself.
*
* \note This is a process wide setting and applies to the tags read
* after the call. The charset is decided when a tag is constructed, so
* a tag already read keeps the one it was read with.
*
* \note A code page this library cannot convert is refused: the call
* then has no effect and a debug message is emitted.
*
* \see assumedCodePage()
*/
static void setAssumedCodePage(unsigned int codePage);
private:
class NativeTagPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<NativeTagPrivate> d;
};
} // namespace TagLib::RIFF::AIFF
#endif
@@ -89,6 +89,10 @@ PAGES = [
(1147, "EBCDIC1147", "IBM297 EBCDIC France with Euro", "CP1147"),
(1148, "EBCDIC1148", "IBM500 EBCDIC International with Euro", "CP1148"),
(1149, "EBCDIC1149", "IBM871 EBCDIC Icelandic with Euro", "CP1149"),
# Not a Windows ANSI page, but a Microsoft code page number all the same:
# 10000 is what Windows calls Mac OS Roman, the classic Macintosh charset.
(10000, "MacRoman", "Mac OS Roman", "MACINTOSH"),
]
def via_iconv(name):
+22 -1
View File
@@ -839,6 +839,26 @@ namespace TagLib::Charset::detail {
0x0111, 0x00F1, 0x0323, 0x00F3, 0x00F4, 0x01A1, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x01B0, 0x20AB, 0x00FF,
};
// 10000 Mac OS Roman
constexpr unsigned short MacRomanTable[256] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,
0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,
0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,
0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x0394, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,
0xE01E, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,
};
// 20127 US-ASCII
constexpr unsigned short ASCIITable[256] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
@@ -1400,6 +1420,7 @@ namespace TagLib::Charset::detail {
{ Type::Windows1256, 1256, Windows1256Table },
{ Type::Windows1257, 1257, Windows1257Table },
{ Type::Windows1258, 1258, Windows1258Table },
{ Type::MacRoman, 10000, MacRomanTable },
{ Type::ASCII, 20127, ASCIITable },
{ Type::EBCDIC273, 20273, EBCDIC273Table },
{ Type::EBCDIC277, 20277, EBCDIC277Table },
@@ -1430,7 +1451,7 @@ namespace TagLib::Charset::detail {
// Every Type but UTF8 has exactly one row.
static_assert(
std::size(codePageTable) == 66,
std::size(codePageTable) == 67,
"the code page table and Charset::Type disagree");
// A Type and the CSET code page number name the same row, so the
+68 -67
View File
@@ -57,73 +57,74 @@ namespace TagLib {
*/
enum class Type : unsigned int
{
EBCDIC037 = 37, //!< IBM EBCDIC US-Canada
OEM437 = 437, //!< IBM437 OEM US
EBCDIC500 = 500, //!< IBM EBCDIC International
OEM708 = 708, //!< ASMO-708 Arabic
OEM737 = 737, //!< IBM737 OEM Greek
OEM775 = 775, //!< IBM775 OEM Baltic
OEM850 = 850, //!< IBM850 OEM Latin 1
OEM852 = 852, //!< IBM852 OEM Latin 2
OEM855 = 855, //!< IBM855 OEM Cyrillic
OEM857 = 857, //!< IBM857 OEM Turkish
OEM858 = 858, //!< IBM858 OEM Latin 1 with Euro
OEM860 = 860, //!< IBM860 OEM Portuguese
OEM861 = 861, //!< IBM861 OEM Icelandic
OEM862 = 862, //!< DOS-862 OEM Hebrew
OEM863 = 863, //!< IBM863 OEM French Canadian
OEM864 = 864, //!< IBM864 OEM Arabic
OEM865 = 865, //!< IBM865 OEM Nordic
OEM866 = 866, //!< IBM866 OEM Russian
OEM869 = 869, //!< IBM869 OEM Modern Greek
Thai874 = 874, //!< Thai (Windows-874)
EBCDIC1047 = 1047, //!< IBM1047 EBCDIC Latin 1/Open System
EBCDIC1140 = 1140, //!< IBM037 EBCDIC US-Canada with Euro
EBCDIC1141 = 1141, //!< IBM273 EBCDIC Germany with Euro
EBCDIC1142 = 1142, //!< IBM277 EBCDIC Denmark-Norway with Euro
EBCDIC1143 = 1143, //!< IBM278 EBCDIC Finland-Sweden with Euro
EBCDIC1144 = 1144, //!< IBM280 EBCDIC Italy with Euro
EBCDIC1145 = 1145, //!< IBM284 EBCDIC Latin America-Spain with Euro
EBCDIC1146 = 1146, //!< IBM285 EBCDIC UK with Euro
EBCDIC1147 = 1147, //!< IBM297 EBCDIC France with Euro
EBCDIC1148 = 1148, //!< IBM500 EBCDIC International with Euro
EBCDIC1149 = 1149, //!< IBM871 EBCDIC Icelandic with Euro
Windows1250= 1250, //!< Windows-1250 Central European
Windows1251= 1251, //!< Windows-1251 Cyrillic
Windows1252= 1252, //!< Windows-1252 Western European
Windows1253= 1253, //!< Windows-1253 Greek
Windows1254= 1254, //!< Windows-1254 Turkish
Windows1255= 1255, //!< Windows-1255 Hebrew
Windows1256= 1256, //!< Windows-1256 Arabic
Windows1257= 1257, //!< Windows-1257 Baltic
Windows1258= 1258, //!< Windows-1258 Vietnamese
ASCII = 20127, //!< US-ASCII
EBCDIC273 = 20273, //!< IBM273 EBCDIC Germany
EBCDIC277 = 20277, //!< IBM277 EBCDIC Denmark-Norway
EBCDIC278 = 20278, //!< IBM278 EBCDIC Finland-Sweden
EBCDIC280 = 20280, //!< IBM280 EBCDIC Italy
EBCDIC284 = 20284, //!< IBM284 EBCDIC Latin America-Spain
EBCDIC285 = 20285, //!< IBM285 EBCDIC United Kingdom
EBCDIC290 = 20290, //!< IBM290 EBCDIC Japanese Katakana Extended
EBCDIC297 = 20297, //!< IBM297 EBCDIC France
EBCDIC420 = 20420, //!< IBM420 EBCDIC Arabic
EBCDIC423 = 20423, //!< IBM423 EBCDIC Greek
KOI8R = 20866, //!< KOI8-R
EBCDIC871 = 20871, //!< IBM871 EBCDIC Icelandic
EBCDIC1025 = 21025, //!< IBM1025 EBCDIC Cyrillic Serbian-Bulgarian
KOI8U = 21866, //!< KOI8-U
Latin1 = 28591, //!< ISO 8859-1
Latin2 = 28592, //!< ISO 8859-2
Latin3 = 28593, //!< ISO 8859-3
Latin4 = 28594, //!< ISO 8859-4
Latin5 = 28595, //!< ISO 8859-5
Latin6 = 28596, //!< ISO 8859-6
Latin7 = 28597, //!< ISO 8859-7
Latin8 = 28598, //!< ISO 8859-8 Hebrew (logical)
Latin9 = 28599, //!< ISO 8859-9
Latin13 = 28603, //!< ISO 8859-13
Latin15 = 28605, //!< ISO 8859-15
UTF8 = 65001 //!< UTF-8
EBCDIC037 = 37, //!< IBM EBCDIC US-Canada
OEM437 = 437, //!< IBM437 OEM US
EBCDIC500 = 500, //!< IBM EBCDIC International
OEM708 = 708, //!< ASMO-708 Arabic
OEM737 = 737, //!< IBM737 OEM Greek
OEM775 = 775, //!< IBM775 OEM Baltic
OEM850 = 850, //!< IBM850 OEM Latin 1
OEM852 = 852, //!< IBM852 OEM Latin 2
OEM855 = 855, //!< IBM855 OEM Cyrillic
OEM857 = 857, //!< IBM857 OEM Turkish
OEM858 = 858, //!< IBM858 OEM Latin 1 with Euro
OEM860 = 860, //!< IBM860 OEM Portuguese
OEM861 = 861, //!< IBM861 OEM Icelandic
OEM862 = 862, //!< DOS-862 OEM Hebrew
OEM863 = 863, //!< IBM863 OEM French Canadian
OEM864 = 864, //!< IBM864 OEM Arabic
OEM865 = 865, //!< IBM865 OEM Nordic
OEM866 = 866, //!< IBM866 OEM Russian
OEM869 = 869, //!< IBM869 OEM Modern Greek
Thai874 = 874, //!< Thai (Windows-874)
EBCDIC1047 = 1047, //!< IBM1047 EBCDIC Latin 1/Open System
EBCDIC1140 = 1140, //!< IBM037 EBCDIC US-Canada with Euro
EBCDIC1141 = 1141, //!< IBM273 EBCDIC Germany with Euro
EBCDIC1142 = 1142, //!< IBM277 EBCDIC Denmark-Norway with Euro
EBCDIC1143 = 1143, //!< IBM278 EBCDIC Finland-Sweden with Euro
EBCDIC1144 = 1144, //!< IBM280 EBCDIC Italy with Euro
EBCDIC1145 = 1145, //!< IBM284 EBCDIC Latin America-Spain with Euro
EBCDIC1146 = 1146, //!< IBM285 EBCDIC UK with Euro
EBCDIC1147 = 1147, //!< IBM297 EBCDIC France with Euro
EBCDIC1148 = 1148, //!< IBM500 EBCDIC International with Euro
EBCDIC1149 = 1149, //!< IBM871 EBCDIC Icelandic with Euro
Windows1250 = 1250, //!< Windows-1250 Central European
Windows1251 = 1251, //!< Windows-1251 Cyrillic
Windows1252 = 1252, //!< Windows-1252 Western European
Windows1253 = 1253, //!< Windows-1253 Greek
Windows1254 = 1254, //!< Windows-1254 Turkish
Windows1255 = 1255, //!< Windows-1255 Hebrew
Windows1256 = 1256, //!< Windows-1256 Arabic
Windows1257 = 1257, //!< Windows-1257 Baltic
Windows1258 = 1258, //!< Windows-1258 Vietnamese
MacRoman = 10000, //!< Mac OS Roman
ASCII = 20127, //!< US-ASCII
EBCDIC273 = 20273, //!< IBM273 EBCDIC Germany
EBCDIC277 = 20277, //!< IBM277 EBCDIC Denmark-Norway
EBCDIC278 = 20278, //!< IBM278 EBCDIC Finland-Sweden
EBCDIC280 = 20280, //!< IBM280 EBCDIC Italy
EBCDIC284 = 20284, //!< IBM284 EBCDIC Latin America-Spain
EBCDIC285 = 20285, //!< IBM285 EBCDIC United Kingdom
EBCDIC290 = 20290, //!< IBM290 EBCDIC Japanese Katakana Extended
EBCDIC297 = 20297, //!< IBM297 EBCDIC France
EBCDIC420 = 20420, //!< IBM420 EBCDIC Arabic
EBCDIC423 = 20423, //!< IBM423 EBCDIC Greek
KOI8R = 20866, //!< KOI8-R
EBCDIC871 = 20871, //!< IBM871 EBCDIC Icelandic
EBCDIC1025 = 21025, //!< IBM1025 EBCDIC Cyrillic Serbian-Bulgarian
KOI8U = 21866, //!< KOI8-U
Latin1 = 28591, //!< ISO 8859-1
Latin2 = 28592, //!< ISO 8859-2
Latin3 = 28593, //!< ISO 8859-3
Latin4 = 28594, //!< ISO 8859-4
Latin5 = 28595, //!< ISO 8859-5
Latin6 = 28596, //!< ISO 8859-6
Latin7 = 28597, //!< ISO 8859-7
Latin8 = 28598, //!< ISO 8859-8 Hebrew (logical)
Latin9 = 28599, //!< ISO 8859-9
Latin13 = 28603, //!< ISO 8859-13
Latin15 = 28605, //!< ISO 8859-15
UTF8 = 65001 //!< UTF-8
};
/*!
+303
View File
@@ -28,13 +28,104 @@
#include "tbytevectorlist.h"
#include "tag.h"
#include "taglib_config.h"
#include "aifffile.h"
#include "aiffnativetag.h"
#include "tbytevectorstream.h"
#include "tpropertymap.h"
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"
using namespace std;
using namespace TagLib;
#ifdef TAGLIB_WITH_CHARSETTABLES
namespace
{
// Puts back the code page a native tag assumes, so that a test stating
// another one does not carry it into the next test.
class RestoreAssumedCodePage
{
public:
RestoreAssumedCodePage() :
m_previous(RIFF::AIFF::NativeTag::assumedCodePage())
{
}
~RestoreAssumedCodePage()
{
RIFF::AIFF::NativeTag::setAssumedCodePage(m_previous);
}
private:
const unsigned int m_previous;
};
// One chunk of an AIFF file: its id, its size, its payload, and the pad
// byte that brings an odd sized payload to an even length.
ByteVector aiffChunk(const char *id, const ByteVector &payload)
{
ByteVector chunk = ByteVector(id, 4) + ByteVector::fromUInt(payload.size()) +
payload;
if(payload.size() % 2)
chunk.append(static_cast<char>(0));
return chunk;
}
// A minimal AIFF file: a COMM chunk describing one silent channel, and the
// NAME chunk holding \a name unless it is empty.
ByteVector aiffWithName(const ByteVector &name)
{
ByteVector body = aiffChunk("COMM",
ByteVector("\x00\x01\x00\x00\x00\x00\x00\x10"
"\x40\x0E\xAC\x44\x00\x00\x00\x00"
"\x00\x00", 18));
if(!name.isEmpty())
body.append(aiffChunk("NAME", name));
return ByteVector("FORM") + ByteVector::fromUInt(body.size() + 4) +
ByteVector("AIFF") + body;
}
// The payload of the first chunk called \a name in the AIFF file \a data,
// checked against the format itself instead of against TagLib: the FORM
// header's size has to cover exactly the file, and the chunks have to tile
// it, so a file TagLib has just written is validated independently of how
// TagLib would read it back.
ByteVector checkedRawChunk(const ByteVector &data, const char *name)
{
const ByteVector wanted(name);
CPPUNIT_ASSERT(data.size() >= 12u);
CPPUNIT_ASSERT(data.startsWith("FORM"));
const offset_t declaredEnd = static_cast<offset_t>(data.toUInt(4u)) + 8;
CPPUNIT_ASSERT_EQUAL(declaredEnd, static_cast<offset_t>(data.size()));
ByteVector found;
offset_t offset = 12;
while(offset + 8 <= declaredEnd) {
const ByteVector header = data.mid(static_cast<unsigned int>(offset), 8);
CPPUNIT_ASSERT_EQUAL(8u, header.size());
const unsigned int size = header.toUInt(4u);
const ByteVector payload = data.mid(static_cast<unsigned int>(offset + 8), size);
CPPUNIT_ASSERT_EQUAL(size, payload.size());
if(found.isEmpty() && header.startsWith(wanted))
found = payload;
// Chunks are padded to an even length, so an odd size is followed by a
// pad byte which is not counted in the size.
offset += 8 + size + (size % 2);
}
CPPUNIT_ASSERT_EQUAL(declaredEnd, offset);
return found;
}
} // namespace
#endif // TAGLIB_WITH_CHARSETTABLES
class TestAIFF : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestAIFF);
@@ -43,6 +134,13 @@ class TestAIFF : public CppUnit::TestFixture
CPPUNIT_TEST(testSaveID3v2);
CPPUNIT_TEST(testSaveID3v23);
CPPUNIT_TEST(testDuplicateID3v2);
CPPUNIT_TEST(testNativeFields);
CPPUNIT_TEST(testSaveNativeFields);
#ifdef TAGLIB_WITH_CHARSETTABLES
CPPUNIT_TEST(testNativeMacRomanRead);
CPPUNIT_TEST(testNativeMacRomanWrite);
CPPUNIT_TEST(testNativeCodePageOverride);
#endif // TAGLIB_WITH_CHARSETTABLES
CPPUNIT_TEST(testFuzzedFile1);
CPPUNIT_TEST(testFuzzedFile2);
CPPUNIT_TEST_SUITE_END();
@@ -146,6 +244,211 @@ public:
CPPUNIT_ASSERT_EQUAL(static_cast<offset_t>(-1), f.find("Title2"));
}
void testNativeFields()
{
// The four text chunks of a file that predates ID3v2.
RIFF::AIFF::File f(TEST_FILE_PATH_C("alaw.aifc"));
CPPUNIT_ASSERT(f.hasNativeTag());
CPPUNIT_ASSERT(!f.nativeTag()->isEmpty());
CPPUNIT_ASSERT_EQUAL(String("woodblock"), f.nativeTag()->title());
CPPUNIT_ASSERT_EQUAL(String("Prosonus"), f.nativeTag()->artist());
CPPUNIT_ASSERT_EQUAL(String("Copyright 1991, Prosonus"),
f.nativeTag()->fieldText("(c) "));
CPPUNIT_ASSERT(f.nativeTag()->comment().isEmpty());
// The native tag has no field behind these.
CPPUNIT_ASSERT(f.nativeTag()->album().isEmpty());
CPPUNIT_ASSERT_EQUAL(0u, f.nativeTag()->year());
f.nativeTag()->setAlbum("Album");
f.nativeTag()->setYear(2026);
CPPUNIT_ASSERT(f.nativeTag()->album().isEmpty());
CPPUNIT_ASSERT_EQUAL(0u, f.nativeTag()->year());
// The four chunks with their property keys, and nothing unsupported.
const PropertyMap props = f.nativeTag()->properties();
CPPUNIT_ASSERT_EQUAL(3u, props.size());
CPPUNIT_ASSERT_EQUAL(String("woodblock"), props.value("TITLE").front());
CPPUNIT_ASSERT_EQUAL(String("Prosonus"), props.value("ARTIST").front());
CPPUNIT_ASSERT_EQUAL(String("Copyright 1991, Prosonus"),
props.value("COPYRIGHT").front());
// A file with no native chunks holds an empty tag and says so.
RIFF::AIFF::File empty(TEST_FILE_PATH_C("empty.aiff"));
CPPUNIT_ASSERT(!empty.hasNativeTag());
CPPUNIT_ASSERT(empty.nativeTag()->isEmpty());
// Only the four text chunks are fields of this class: COMM is the audio
// layout, so neither reading nor writing it as a field does anything.
RIFF::AIFF::NativeTag::FieldDataMap fields;
fields["NAME"] = ByteVector("Title");
fields["COMM"] = ByteVector("not a field");
RIFF::AIFF::NativeTag tag(fields);
CPPUNIT_ASSERT_EQUAL(String("Title"), tag.title());
CPPUNIT_ASSERT(tag.fieldText("COMM").isEmpty());
tag.setFieldText("COMM", "ignored");
CPPUNIT_ASSERT(tag.fieldText("COMM").isEmpty());
CPPUNIT_ASSERT_EQUAL(1u, tag.properties().size());
}
void testSaveNativeFields()
{
ScopedFileCopy copy("empty", ".aiff");
const string newname = copy.fileName();
{
RIFF::AIFF::File f(newname.c_str());
CPPUNIT_ASSERT(!f.hasNativeTag());
f.nativeTag()->setTitle("TitleXXX");
f.nativeTag()->setArtist("Artist XXX");
f.nativeTag()->setFieldText("(c) ", "Copyright XXX");
f.nativeTag()->setComment("Comment XXX");
CPPUNIT_ASSERT(f.save());
CPPUNIT_ASSERT(f.hasNativeTag());
// The native tag is its own tag: writing it writes no ID3v2 chunk.
CPPUNIT_ASSERT(!f.hasID3v2Tag());
}
{
RIFF::AIFF::File f(newname.c_str());
CPPUNIT_ASSERT(f.hasNativeTag());
CPPUNIT_ASSERT_EQUAL(String("TitleXXX"), f.nativeTag()->title());
CPPUNIT_ASSERT_EQUAL(String("Artist XXX"), f.nativeTag()->artist());
CPPUNIT_ASSERT_EQUAL(String("Copyright XXX"),
f.nativeTag()->fieldText("(c) "));
CPPUNIT_ASSERT_EQUAL(String("Comment XXX"), f.nativeTag()->comment());
// A field that loses its text loses its chunk; the others stay.
f.nativeTag()->setTitle("");
CPPUNIT_ASSERT(f.save());
}
{
RIFF::AIFF::File f(newname.c_str());
CPPUNIT_ASSERT(f.hasNativeTag());
CPPUNIT_ASSERT(f.nativeTag()->title().isEmpty());
CPPUNIT_ASSERT_EQUAL(String("Artist XXX"), f.nativeTag()->artist());
}
{
RIFF::AIFF::File f(newname.c_str());
f.nativeTag()->setArtist("");
f.nativeTag()->setFieldText("(c) ", "");
f.nativeTag()->setComment("");
CPPUNIT_ASSERT(f.save());
CPPUNIT_ASSERT(!f.hasNativeTag());
}
{
RIFF::AIFF::File f(newname.c_str());
CPPUNIT_ASSERT(!f.hasNativeTag());
CPPUNIT_ASSERT(f.nativeTag()->isEmpty());
}
// Saving a file whose native chunks one never touched keeps them.
ScopedFileCopy aifcCopy("alaw", ".aifc");
{
RIFF::AIFF::File f(aifcCopy.fileName().c_str());
CPPUNIT_ASSERT(f.save());
}
{
RIFF::AIFF::File f(aifcCopy.fileName().c_str());
CPPUNIT_ASSERT(f.hasNativeTag());
CPPUNIT_ASSERT_EQUAL(String("woodblock"), f.nativeTag()->title());
CPPUNIT_ASSERT_EQUAL(String("Prosonus"), f.nativeTag()->artist());
}
}
#ifdef TAGLIB_WITH_CHARSETTABLES
void testNativeMacRomanRead()
{
RestoreAssumedCodePage restore;
// No code page stated is what a native tag assumes when the caller has
// said nothing, which is what this build starts with: the tag then reads
// Mac OS Roman.
CPPUNIT_ASSERT_EQUAL(0u, RIFF::AIFF::NativeTag::assumedCodePage());
{
ByteVectorStream stream(aiffWithName(ByteVector("Caf\x8E", 4)));
RIFF::AIFF::File f(&stream, false);
CPPUNIT_ASSERT_EQUAL(String(L"Caf\u00E9"), f.nativeTag()->title());
}
// Stating the Mac OS Roman code page itself reads the very same byte.
RIFF::AIFF::NativeTag::setAssumedCodePage(10000);
{
ByteVectorStream stream(aiffWithName(ByteVector("Caf\x8E", 4)));
RIFF::AIFF::File f(&stream, false);
CPPUNIT_ASSERT_EQUAL(String(L"Caf\u00E9"), f.nativeTag()->title());
}
// The caller saying its files are ISO 8859-1 reads that byte as that
// charset's character instead.
RIFF::AIFF::NativeTag::setAssumedCodePage(28591);
{
ByteVectorStream stream(aiffWithName(ByteVector("Caf\x8E", 4)));
RIFF::AIFF::File f(&stream, false);
CPPUNIT_ASSERT_EQUAL(String(L"Caf\u008E"), f.nativeTag()->title());
}
// Going back to no code page stated is going back to Mac OS Roman.
RIFF::AIFF::NativeTag::setAssumedCodePage(0);
CPPUNIT_ASSERT_EQUAL(0u, RIFF::AIFF::NativeTag::assumedCodePage());
{
ByteVectorStream stream(aiffWithName(ByteVector("Caf\x8E", 4)));
RIFF::AIFF::File f(&stream, false);
CPPUNIT_ASSERT_EQUAL(String(L"Caf\u00E9"), f.nativeTag()->title());
}
}
void testNativeMacRomanWrite()
{
RestoreAssumedCodePage restore;
ByteVectorStream stream(aiffWithName(ByteVector()));
{
RIFF::AIFF::File f(&stream, false);
CPPUNIT_ASSERT(!f.hasNativeTag());
f.nativeTag()->setTitle(L"Caf\u00E9");
CPPUNIT_ASSERT(f.save());
CPPUNIT_ASSERT(f.hasNativeTag());
}
// The bytes the assumed charset writes are Mac OS Roman, not ISO 8859-1.
CPPUNIT_ASSERT_EQUAL(ByteVector("Caf\x8E", 4),
checkedRawChunk(*stream.data(), "NAME"));
// A tag read and written under one code page keeps its text as it was.
ByteVectorStream reopened(*stream.data());
RIFF::AIFF::File f(&reopened, false);
CPPUNIT_ASSERT_EQUAL(String(L"Caf\u00E9"), f.nativeTag()->title());
}
void testNativeCodePageOverride()
{
RestoreAssumedCodePage restore;
// The caller says its files are UTF-8, so that is what both the writing
// and the reading of the tag does.
RIFF::AIFF::NativeTag::setAssumedCodePage(65001);
ByteVectorStream stream(aiffWithName(ByteVector()));
{
RIFF::AIFF::File f(&stream, false);
f.nativeTag()->setTitle(L"Caf\u00E9");
CPPUNIT_ASSERT(f.save());
}
CPPUNIT_ASSERT_EQUAL(ByteVector("Caf\xC3\xA9", 5),
checkedRawChunk(*stream.data(), "NAME"));
ByteVectorStream reopened(*stream.data());
RIFF::AIFF::File f(&reopened, false);
CPPUNIT_ASSERT_EQUAL(String(L"Caf\u00E9"), f.nativeTag()->title());
// A code page this library cannot convert is refused, so the one in
// effect stays.
RIFF::AIFF::NativeTag::setAssumedCodePage(999999);
CPPUNIT_ASSERT_EQUAL(65001u, RIFF::AIFF::NativeTag::assumedCodePage());
}
#endif // TAGLIB_WITH_CHARSETTABLES
void testFuzzedFile1()
{
RIFF::AIFF::File f(TEST_FILE_PATH_C("segfault.aif"));