1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-16 00:34:19 +00:00

WavAudioFormat: Add ASWG metadata support

This commit is contained in:
Oli 2022-04-05 12:45:33 +01:00 committed by Tom Poole
parent 61f3c1dd98
commit 1abb704ce0
2 changed files with 389 additions and 1 deletions

View file

@ -161,6 +161,91 @@ const char* const WavAudioFormat::riffInfoWatermarkURL = "IWMU";
const char* const WavAudioFormat::riffInfoWrittenBy = "IWRI";
const char* const WavAudioFormat::riffInfoYear = "YEAR";
const char* const WavAudioFormat::aswgContentType = "contentType";
const char* const WavAudioFormat::aswgProject = "project";
const char* const WavAudioFormat::aswgOriginator = "originator";
const char* const WavAudioFormat::aswgOriginatorStudio = "originatorStudio";
const char* const WavAudioFormat::aswgNotes = "notes";
const char* const WavAudioFormat::aswgSession = "session";
const char* const WavAudioFormat::aswgState = "state";
const char* const WavAudioFormat::aswgEditor = "editor";
const char* const WavAudioFormat::aswgMixer = "mixer";
const char* const WavAudioFormat::aswgFxChainName = "fxChainName";
const char* const WavAudioFormat::aswgChannelConfig = "channelConfig";
const char* const WavAudioFormat::aswgAmbisonicFormat = "ambisonicFormat";
const char* const WavAudioFormat::aswgAmbisonicChnOrder = "ambisonicChnOrder";
const char* const WavAudioFormat::aswgAmbisonicNorm = "ambisonicNorm";
const char* const WavAudioFormat::aswgMicType = "micType";
const char* const WavAudioFormat::aswgMicConfig = "micConfig";
const char* const WavAudioFormat::aswgMicDistance = "micDistance";
const char* const WavAudioFormat::aswgRecordingLoc = "recordingLoc";
const char* const WavAudioFormat::aswgIsDesigned = "isDesigned";
const char* const WavAudioFormat::aswgRecEngineer = "recEngineer";
const char* const WavAudioFormat::aswgRecStudio = "recStudio";
const char* const WavAudioFormat::aswgImpulseLocation = "impulseLocation";
const char* const WavAudioFormat::aswgCategory = "category";
const char* const WavAudioFormat::aswgSubCategory = "subCategory";
const char* const WavAudioFormat::aswgCatId = "catId";
const char* const WavAudioFormat::aswgUserCategory = "userCategory";
const char* const WavAudioFormat::aswgUserData = "userData";
const char* const WavAudioFormat::aswgVendorCategory = "vendorCategory";
const char* const WavAudioFormat::aswgFxName = "fxName";
const char* const WavAudioFormat::aswgLibrary = "library";
const char* const WavAudioFormat::aswgCreatorId = "creatorId";
const char* const WavAudioFormat::aswgSourceId = "sourceId";
const char* const WavAudioFormat::aswgRmsPower = "rmsPower";
const char* const WavAudioFormat::aswgLoudness = "loudness";
const char* const WavAudioFormat::aswgLoudnessRange = "loudnessRange";
const char* const WavAudioFormat::aswgMaxPeak = "maxPeak";
const char* const WavAudioFormat::aswgSpecDensity = "specDensity";
const char* const WavAudioFormat::aswgZeroCrossRate = "zeroCrossRate";
const char* const WavAudioFormat::aswgPapr = "papr";
const char* const WavAudioFormat::aswgText = "text";
const char* const WavAudioFormat::aswgEfforts = "efforts";
const char* const WavAudioFormat::aswgEffortType = "effortType";
const char* const WavAudioFormat::aswgProjection = "projection";
const char* const WavAudioFormat::aswgLanguage = "language";
const char* const WavAudioFormat::aswgTimingRestriction = "timingRestriction";
const char* const WavAudioFormat::aswgCharacterName = "characterName";
const char* const WavAudioFormat::aswgCharacterGender = "characterGender";
const char* const WavAudioFormat::aswgCharacterAge = "characterAge";
const char* const WavAudioFormat::aswgCharacterRole = "characterRole";
const char* const WavAudioFormat::aswgActorName = "actorName";
const char* const WavAudioFormat::aswgActorGender = "actorGender";
const char* const WavAudioFormat::aswgDirector = "director";
const char* const WavAudioFormat::aswgDirection = "direction";
const char* const WavAudioFormat::aswgFxUsed = "fxUsed";
const char* const WavAudioFormat::aswgUsageRights = "usageRights";
const char* const WavAudioFormat::aswgIsUnion = "isUnion";
const char* const WavAudioFormat::aswgAccent = "accent";
const char* const WavAudioFormat::aswgEmotion = "emotion";
const char* const WavAudioFormat::aswgComposor = "composor";
const char* const WavAudioFormat::aswgArtist = "artist";
const char* const WavAudioFormat::aswgSongTitle = "songTitle";
const char* const WavAudioFormat::aswgGenre = "genre";
const char* const WavAudioFormat::aswgSubGenre = "subGenre";
const char* const WavAudioFormat::aswgProducer = "producer";
const char* const WavAudioFormat::aswgMusicSup = "musicSup";
const char* const WavAudioFormat::aswgInstrument = "instrument";
const char* const WavAudioFormat::aswgMusicPublisher = "musicPublisher";
const char* const WavAudioFormat::aswgRightsOwner = "rightsOwner";
const char* const WavAudioFormat::aswgIsSource = "isSource";
const char* const WavAudioFormat::aswgIsLoop = "isLoop";
const char* const WavAudioFormat::aswgIntensity = "intensity";
const char* const WavAudioFormat::aswgIsFinal = "isFinal";
const char* const WavAudioFormat::aswgOrderRef = "orderRef";
const char* const WavAudioFormat::aswgIsOst = "isOst";
const char* const WavAudioFormat::aswgIsCinematic = "isCinematic";
const char* const WavAudioFormat::aswgIsLicensed = "isLicensed";
const char* const WavAudioFormat::aswgIsDiegetic = "isDiegetic";
const char* const WavAudioFormat::aswgMusicVersion = "musicVersion";
const char* const WavAudioFormat::aswgIsrcId = "isrcId";
const char* const WavAudioFormat::aswgTempo = "tempo";
const char* const WavAudioFormat::aswgTimeSig = "timeSig";
const char* const WavAudioFormat::aswgInKey = "inKey";
const char* const WavAudioFormat::aswgBillingCode = "billingCode";
const char* const WavAudioFormat::aswgVersion = "IXML_VERSION";
const char* const WavAudioFormat::ISRC = "ISRC";
const char* const WavAudioFormat::internationalStandardRecordingCode = "international standard recording code";
const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info";
@ -856,6 +941,157 @@ namespace WavFileHelpers
}
};
//=============================================================================
namespace IXMLChunk
{
static const std::unordered_set<String> aswgMetadataKeys
{
WavAudioFormat::aswgContentType,
WavAudioFormat::aswgProject,
WavAudioFormat::aswgOriginator,
WavAudioFormat::aswgOriginatorStudio,
WavAudioFormat::aswgNotes,
WavAudioFormat::aswgSession,
WavAudioFormat::aswgState,
WavAudioFormat::aswgEditor,
WavAudioFormat::aswgMixer,
WavAudioFormat::aswgFxChainName,
WavAudioFormat::aswgChannelConfig,
WavAudioFormat::aswgAmbisonicFormat,
WavAudioFormat::aswgAmbisonicChnOrder,
WavAudioFormat::aswgAmbisonicNorm,
WavAudioFormat::aswgMicType,
WavAudioFormat::aswgMicConfig,
WavAudioFormat::aswgMicDistance,
WavAudioFormat::aswgRecordingLoc,
WavAudioFormat::aswgIsDesigned,
WavAudioFormat::aswgRecEngineer,
WavAudioFormat::aswgRecStudio,
WavAudioFormat::aswgImpulseLocation,
WavAudioFormat::aswgCategory,
WavAudioFormat::aswgSubCategory,
WavAudioFormat::aswgCatId,
WavAudioFormat::aswgUserCategory,
WavAudioFormat::aswgUserData,
WavAudioFormat::aswgVendorCategory,
WavAudioFormat::aswgFxName,
WavAudioFormat::aswgLibrary,
WavAudioFormat::aswgCreatorId,
WavAudioFormat::aswgSourceId,
WavAudioFormat::aswgRmsPower,
WavAudioFormat::aswgLoudness,
WavAudioFormat::aswgLoudnessRange,
WavAudioFormat::aswgMaxPeak,
WavAudioFormat::aswgSpecDensity,
WavAudioFormat::aswgZeroCrossRate,
WavAudioFormat::aswgPapr,
WavAudioFormat::aswgText,
WavAudioFormat::aswgEfforts,
WavAudioFormat::aswgEffortType,
WavAudioFormat::aswgProjection,
WavAudioFormat::aswgLanguage,
WavAudioFormat::aswgTimingRestriction,
WavAudioFormat::aswgCharacterName,
WavAudioFormat::aswgCharacterGender,
WavAudioFormat::aswgCharacterAge,
WavAudioFormat::aswgCharacterRole,
WavAudioFormat::aswgActorName,
WavAudioFormat::aswgActorGender,
WavAudioFormat::aswgDirector,
WavAudioFormat::aswgDirection,
WavAudioFormat::aswgFxUsed,
WavAudioFormat::aswgUsageRights,
WavAudioFormat::aswgIsUnion,
WavAudioFormat::aswgAccent,
WavAudioFormat::aswgEmotion,
WavAudioFormat::aswgComposor,
WavAudioFormat::aswgArtist,
WavAudioFormat::aswgSongTitle,
WavAudioFormat::aswgGenre,
WavAudioFormat::aswgSubGenre,
WavAudioFormat::aswgProducer,
WavAudioFormat::aswgMusicSup,
WavAudioFormat::aswgInstrument,
WavAudioFormat::aswgMusicPublisher,
WavAudioFormat::aswgRightsOwner,
WavAudioFormat::aswgIsSource,
WavAudioFormat::aswgIsLoop,
WavAudioFormat::aswgIntensity,
WavAudioFormat::aswgIsFinal,
WavAudioFormat::aswgOrderRef,
WavAudioFormat::aswgIsOst,
WavAudioFormat::aswgIsCinematic,
WavAudioFormat::aswgIsLicensed,
WavAudioFormat::aswgIsDiegetic,
WavAudioFormat::aswgMusicVersion,
WavAudioFormat::aswgIsrcId,
WavAudioFormat::aswgTempo,
WavAudioFormat::aswgTimeSig,
WavAudioFormat::aswgInKey,
WavAudioFormat::aswgBillingCode
};
static void addToMetadata (StringMap& destValues, const String& source)
{
if (auto xml = parseXML (source))
{
if (xml->hasTagName ("BWFXML"))
{
if (const auto* entry = xml->getChildByName (WavAudioFormat::aswgVersion))
destValues[WavAudioFormat::aswgVersion] = entry->getAllSubText();
if (const auto* aswgElement = xml->getChildByName ("ASWG"))
{
for (const auto* entry : aswgElement->getChildIterator())
{
const auto& tag = entry->getTagName();
if (aswgMetadataKeys.find (tag) != aswgMetadataKeys.end())
destValues[tag] = entry->getAllSubText();
}
}
}
}
}
static MemoryBlock createFrom (const StringMap& values)
{
auto createTextElement = [] (const StringRef& key, const StringRef& value)
{
auto* elem = new XmlElement (key);
elem->addTextElement (value);
return elem;
};
std::unique_ptr<XmlElement> aswgElement;
for (const auto& pair : values)
{
if (aswgMetadataKeys.find (pair.first) != aswgMetadataKeys.end())
{
if (aswgElement == nullptr)
aswgElement = std::make_unique<XmlElement> ("ASWG");
aswgElement->addChildElement (createTextElement (pair.first, pair.second));
}
}
MemoryOutputStream outputStream;
if (aswgElement != nullptr)
{
XmlElement xml ("BWFXML");
auto aswgVersion = getValueWithDefault (values, WavAudioFormat::aswgVersion, "3.01");
xml.addChildElement (createTextElement (WavAudioFormat::aswgVersion, aswgVersion));
xml.addChildElement (aswgElement.release());
xml.writeTo (outputStream);
outputStream.writeRepeatedByte (0, outputStream.getDataSize());
}
return outputStream.getMemoryBlock();
}
}
//==============================================================================
namespace AXMLChunk
{
@ -1136,6 +1372,12 @@ public:
input->readIntoMemoryBlock (axml, (ssize_t) length);
AXMLChunk::addToMetadata (dict, axml.toString());
}
else if (chunkType == chunkName ("iXML"))
{
MemoryBlock ixml;
input->readIntoMemoryBlock (ixml, (ssize_t) length);
IXMLChunk::addToMetadata (dict, ixml.toString());
}
else if (chunkType == chunkName ("LIST"))
{
auto subChunkType = input->readInt();
@ -1350,6 +1592,7 @@ public:
const auto map = toMap (metadataValues);
bwavChunk = BWAVChunk::createFrom (map);
ixmlChunk = IXMLChunk::createFrom (map);
axmlChunk = AXMLChunk::createFrom (map);
smplChunk = SMPLChunk::createFrom (map);
instChunk = InstChunk::createFrom (map);
@ -1420,7 +1663,7 @@ public:
}
private:
MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
MemoryBlock tempBlock, bwavChunk, ixmlChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
uint64 lengthInSamples = 0, bytesWritten = 0;
int64 headerPosition = 0;
bool writeFailed = false;
@ -1450,6 +1693,7 @@ private:
int64 riffChunkSize = (int64) (4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */
+ 8 + audioDataSize + (audioDataSize & 1)
+ chunkSize (bwavChunk)
+ chunkSize (ixmlChunk)
+ chunkSize (axmlChunk)
+ chunkSize (smplChunk)
+ chunkSize (instChunk)
@ -1532,6 +1776,7 @@ private:
}
writeChunk (bwavChunk, chunkName ("bext"));
writeChunk (ixmlChunk, chunkName ("iXML"));
writeChunk (axmlChunk, chunkName ("axml"));
writeChunk (smplChunk, chunkName ("smpl"));
writeChunk (instChunk, chunkName ("inst"), 7);
@ -1951,6 +2196,61 @@ struct WaveAudioFormatTests : public UnitTest
expect (a[WavAudioFormat::riffInfoSource] == "source");
expect (a[WavAudioFormat::internationalStandardRecordingCode] == "UUVVVXXYYYYY");
}
{
beginTest ("Files containing ASWG metadata read and write correctly");
MemoryBlock block;
StringPairArray meta;
for (const auto& key : WavFileHelpers::IXMLChunk::aswgMetadataKeys)
meta.set (key, "Test123&<>");
{
auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (new MemoryOutputStream (block, false), 48000, 1, 32, meta, 0));
expect (writer != nullptr);
}
expect ([&]
{
auto input = std::make_unique<MemoryInputStream> (block, false);
while (! input->isExhausted())
{
char chunkType[4] {};
auto pos = input->getPosition();
input->read (chunkType, 4);
if (memcmp (chunkType, "iXML", 4) == 0)
{
auto length = (uint32) input->readInt();
MemoryBlock xmlBlock;
input->readIntoMemoryBlock (xmlBlock, (ssize_t) length);
return parseXML (xmlBlock.toString()) != nullptr;
}
input->setPosition (pos + 1);
}
return false;
}());
{
auto reader = rawToUniquePtr (WavAudioFormat().createReaderFor (new MemoryInputStream (block, false), true));
expect (reader != nullptr);
for (const auto& key : meta.getAllKeys())
{
const auto oldValue = meta.getValue (key, "!");
const auto newValue = reader->metadataValues.getValue (key, "");
expectEquals (oldValue, newValue);
}
expect (reader->metadataValues.getValue (WavAudioFormat::aswgVersion, "") == "3.01");
}
}
}
private:

View file

@ -168,6 +168,94 @@ public:
static const char* const riffInfoWrittenBy; /**< Metadata property name used in INFO chunks. */
static const char* const riffInfoYear; /**< Metadata property name used in INFO chunks. */
//==============================================================================
// ASWG chunk properties:
static const char* const aswgContentType; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgProject; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgOriginator; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgOriginatorStudio; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgNotes; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgSession; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgState; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgEditor; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMixer; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgFxChainName; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgChannelConfig; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgAmbisonicFormat; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgAmbisonicChnOrder; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgAmbisonicNorm; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMicType; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMicConfig; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMicDistance; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgRecordingLoc; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsDesigned; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgRecEngineer; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgRecStudio; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgImpulseLocation; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgCategory; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgSubCategory; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgCatId; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgUserCategory; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgUserData; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgVendorCategory; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgFxName; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgLibrary; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgCreatorId; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgSourceId; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgRmsPower; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgLoudness; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgLoudnessRange; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMaxPeak; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgSpecDensity; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgZeroCrossRate; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgPapr; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgText; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgEfforts; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgEffortType; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgProjection; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgLanguage; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgTimingRestriction; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgCharacterName; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgCharacterGender; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgCharacterAge; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgCharacterRole; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgActorName; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgActorGender; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgDirector; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgDirection; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgFxUsed; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgUsageRights; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsUnion; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgAccent; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgEmotion; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgComposor; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgArtist; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgSongTitle; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgGenre; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgSubGenre; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgProducer; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMusicSup; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgInstrument; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMusicPublisher; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgRightsOwner; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsSource; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsLoop; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIntensity; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsFinal; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgOrderRef; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsOst; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsCinematic; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsLicensed; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsDiegetic; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgMusicVersion; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgIsrcId; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgTempo; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgTimeSig; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgInKey; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgBillingCode; /**< Metadata property name used in ASWG/iXML chunks. */
static const char* const aswgVersion; /**< Metadata property name used in ASWG/iXML chunks. */
//==============================================================================
/** Metadata property name used when reading an ISRC code from an AXML chunk. */
[[deprecated ("This string is identical to riffInfoSource, making it impossible to differentiate between the two")]]