mirror of
https://github.com/taglib/taglib.git
synced 2026-11-06 22:48:48 -05:00
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:
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user