Implement complex properties for Matroska

This commit is contained in:
Urs Fleisch
2025-08-19 16:47:04 +02:00
parent 8d02484804
commit 98bc68d16e
10 changed files with 336 additions and 17 deletions

View File

@ -18,8 +18,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef HAS_MATROSKAATTACHEDFILE_H
#define HAS_MATROSKAATTACHEDFILE_H
#ifndef TAGLIB_MATROSKAATTACHEDFILE_H
#define TAGLIB_MATROSKAATTACHEDFILE_H
#include "taglib_export.h"

View File

@ -53,6 +53,11 @@ const Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFi
return d->files;
}
Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFiles()
{
return d->files;
}
bool Matroska::Attachments::render()
{
EBML::MkAttachments attachments;

View File

@ -18,8 +18,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef HAS_MATROSKAATTACHMENTS_H
#define HAS_MATROSKAATTACHMENTS_H
#ifndef TAGLIB_MATROSKAATTACHMENTS_H
#define TAGLIB_MATROSKAATTACHMENTS_H
#include <memory>
#include "taglib_export.h"
@ -56,6 +56,7 @@ namespace TagLib {
// private Element implementation
bool render() override;
AttachedFileList &attachedFiles();
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<AttachmentsPrivate> d;

View File

@ -18,8 +18,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef HAS_MATROSKACUES_H
#define HAS_MATROSKACUES_H
#ifndef TAGLIB_MATROSKACUES_H
#define TAGLIB_MATROSKACUES_H
#ifndef DO_NOT_DOCUMENT
#include "tlist.h"

View File

@ -18,8 +18,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef HAS_MATROSKAELEMENT_H
#define HAS_MATROSKAELEMENT_H
#ifndef TAGLIB_MATROSKAELEMENT_H
#define TAGLIB_MATROSKAELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include <memory>

View File

@ -21,6 +21,7 @@
#include "matroskafile.h"
#include "matroskatag.h"
#include "matroskaattachments.h"
#include "matroskaattachedfile.h"
#include "matroskaseekhead.h"
#include "matroskasegment.h"
#include "ebmlutils.h"
@ -29,9 +30,9 @@
#include "tlist.h"
#include "tdebug.h"
#include "tagutils.h"
#include "tpropertymap.h"
#include <memory>
#include <vector>
using namespace TagLib;
@ -110,6 +111,132 @@ Matroska::Tag *Matroska::File::tag(bool create) const
return d->tag.get();
}
PropertyMap Matroska::File::properties() const
{
return d->tag ? d->tag->properties() : PropertyMap();
}
void Matroska::File::removeUnsupportedProperties(const StringList &properties)
{
if(d->tag) {
d->tag->removeUnsupportedProperties(properties);
}
}
PropertyMap Matroska::File::setProperties(const PropertyMap &properties)
{
if(!d->tag) {
d->tag = std::make_unique<Tag>();
}
return d->tag->setProperties(properties);
}
namespace {
String keyForAttachedFile(const Matroska::AttachedFile *attachedFile)
{
if(!attachedFile) {
return {};
}
if(attachedFile->mediaType().startsWith("image/")) {
return "PICTURE";
}
if(!attachedFile->mediaType().isEmpty()) {
return attachedFile->mediaType();
}
if(!attachedFile->fileName().isEmpty()) {
return attachedFile->fileName();
}
return String::fromLongLong(attachedFile->uid());
}
unsigned long long stringToULongLong(const String &str, bool *ok)
{
const wchar_t *beginPtr = str.toCWString();
wchar_t *endPtr;
errno = 0;
const unsigned long long value = ::wcstoull(beginPtr, &endPtr, 10);
if(ok) {
*ok = errno == 0 && endPtr > beginPtr && *endPtr == L'\0';
}
return value;
}
}
StringList Matroska::File::complexPropertyKeys() const
{
StringList keys = TagLib::File::complexPropertyKeys();
if(d->attachments) {
const auto &attachedFiles = d->attachments->attachedFileList();
for(const auto &attachedFile : attachedFiles) {
String key = keyForAttachedFile(attachedFile);
if(!key.isEmpty() && !keys.contains(key)) {
keys.append(key);
}
}
}
return keys;
}
List<VariantMap> Matroska::File::complexProperties(const String &key) const
{
List<VariantMap> props = TagLib::File::complexProperties(key);
if(d->attachments) {
const auto &attachedFiles = d->attachments->attachedFileList();
for(const auto &attachedFile : attachedFiles) {
if(keyForAttachedFile(attachedFile) == key) {
VariantMap property;
property.insert("data", attachedFile->data());
property.insert("mimeType", attachedFile->mediaType());
property.insert("description", attachedFile->description());
property.insert("fileName", attachedFile->fileName());
property.insert("uid", attachedFile->uid());
props.append(property);
}
}
}
return props;
}
bool Matroska::File::setComplexProperties(const String &key, const List<VariantMap> &value)
{
if(TagLib::File::setComplexProperties(key, value)) {
return true;
}
attachments(true)->clear();
for(const auto &property : value) {
auto mimeType = property.value("mimeType").value<String>();
auto data = property.value("data").value<ByteVector>();
auto fileName = property.value("fileName").value<String>();
auto uid = property.value("uid").value<unsigned long long>();
bool ok;
unsigned long long uidKey;
if(key.upper() == "PICTURE" && !mimeType.startsWith("image/")) {
mimeType = data.startsWith("\x89PNG\x0d\x0a\x1a\x0a")
? "image/png" : "image/jpeg";
}
else if(mimeType.isEmpty() && key.find("/") != -1) {
mimeType = key;
}
else if(fileName.isEmpty() && key.find(".") != -1) {
fileName = key;
}
else if(!uid && ((uidKey = stringToULongLong(key, &ok))) && ok) {
uid = uidKey;
}
auto attachedFile = new AttachedFile;
attachedFile->setData(data);
attachedFile->setMediaType(mimeType);
attachedFile->setDescription(property.value("description").value<String>());
attachedFile->setFileName(fileName);
attachedFile->setUID(uid);
d->attachments->addAttachedFile(attachedFile);
}
return true;
}
Matroska::Attachments *Matroska::File::attachments(bool create) const
{
if(!d->attachments && create)

View File

@ -18,8 +18,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef HAS_MATROSKAFILE_H
#define HAS_MATROSKAFILE_H
#ifndef TAGLIB_MATROSKAFILE_H
#define TAGLIB_MATROSKAFILE_H
#include "taglib_export.h"
#include "tfile.h"
@ -30,23 +30,118 @@ namespace TagLib::Matroska {
class Properties;
class Tag;
class Attachments;
/*!
* Implementation of TagLib::File for Matroska.
*/
class TAGLIB_EXPORT File : public TagLib::File
{
public:
/*!
* Constructs a Matroska file from \a file. If \a readProperties is \c true the
* file's audio properties will also be read.
*
* The \a readStyle parameter is currently unused.
*/
explicit File(FileName file, bool readProperties = true,
Properties::ReadStyle readStyle = Properties::Average);
/*!
* Constructs a Matroska file from \a stream. If \a readProperties is \c true the
* file's audio properties will also be read.
*
* The \a readStyle parameter is currently unused.
*/
explicit File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle readStyle = Properties::Average);
/*!
* Destroys this instance of the File.
*/
~File() override;
File(const File &) = delete;
File &operator=(const File &) = delete;
AudioProperties *audioProperties() const override;
/*!
* Returns a pointer to the tag of the file.
*
* It will create a tag if one does not exist and returns a valid pointer.
*
* \note The tag <b>is still</b> owned by the Matroska::File and should not
* be deleted by the user. It will be deleted when the file (object) is
* destroyed.
*/
TagLib::Tag *tag() const override;
Attachments *attachments(bool create = false) const;
/*!
* Returns a pointer to the Matroska tag of the file.
*
* If \a create is \c false this may return a null pointer if there is no tag.
* If \a create is \c true it will create a tag if one does not exist and
* returns a valid pointer.
*
* \note The tag <b>is still</b> owned by the Matroska::File and should not
* be deleted by the user. It will be deleted when the file (object)
* destroyed.
*/
Tag *tag(bool create) const;
/*!
* Implements the reading part of the unified property interface.
*/
PropertyMap properties() const override;
void removeUnsupportedProperties(const StringList &properties) override;
/*!
* Implements the writing part of the unified tag dictionary interface.
*/
PropertyMap setProperties(const PropertyMap &) override;
/*!
* Returns the keys for attached files, "PICTURE" for images, the media
* type, file name or UID for other attached files.
* The names of the binary simple tags are included too.
*/
StringList complexPropertyKeys() const override;
/*!
* Get the pictures stored in the attachments as complex properties
* for \a key "PICTURE". Other attached files can be retrieved, by
* media type, file name or UID.
* The attached files are returned as maps with keys "data", "mimeType",
* "description", "fileName, "uid".
* Binary simple tags can be retrieved as maps with keys "data", "name",
* "targetTypeValue", "language", "defaultLanguage".
*/
List<VariantMap> complexProperties(const String &key) const override;
/*!
* Set attached files as complex properties \a value, e.g. pictures for
* \a key "PICTURE" with the maps in \a value having keys "data", "mimeType",
* "description", "fileName, "uid". For other attached files, the mime type,
* file name or UID can be used as the \a key.
* Maps with keys "name" (with the same value as \a key) and "data" are
* stored as binary simple tags with additional keys "targetTypeValue",
* "language", "defaultLanguage".
*/
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
/*!
* Returns the Matroska::Properties for this file. If no audio properties
* were read then this will return a null pointer.
*/
AudioProperties *audioProperties() const override;
/*!
* Save the file.
*
* This returns \c true if the save was successful.
*/
bool save() override;
//PropertyMap properties() const override { return PropertyMap(); }
//void removeUnsupportedProperties(const StringList &properties) override { }
Attachments *attachments(bool create = false) const;
/*!
* Returns whether or not the given \a stream can be opened as a Matroska

View File

@ -18,8 +18,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef HAS_MATROSKASIMPLETAG_H
#define HAS_MATROSKASIMPLETAG_H
#ifndef TAGLIB_MATROSKASIMPLETAG_H
#define TAGLIB_MATROSKASIMPLETAG_H
#include <memory>
#include "tag.h"

View File

@ -430,6 +430,73 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap)
return unsupportedProperties;
}
void Matroska::Tag::removeUnsupportedProperties(const StringList& properties)
{
d->removeSimpleTags(
[&properties](const SimpleTag* t) {
return properties.contains(t->name());
}
);
}
StringList Matroska::Tag::complexPropertyKeys() const
{
StringList keys;
for(const SimpleTag *t : std::as_const(d->tags)) {
if(auto tBinary = dynamic_cast<const SimpleTagBinary*>(t)) {
keys.append(tBinary->name());
}
}
return keys;
}
List<VariantMap> Matroska::Tag::complexProperties(const String& key) const
{
List<VariantMap> props;
if(key.upper() != "PICTURE") { // Pictures are handled at the file level
for(const SimpleTag *t : std::as_const(d->tags)) {
if(auto tBinary = dynamic_cast<const SimpleTagBinary*>(t)) {
VariantMap property;
property.insert("data", tBinary->value());
property.insert("name", tBinary->name());
property.insert("targetTypeValue", tBinary->targetTypeValue());
property.insert("language", tBinary->language());
property.insert("defaultLanguage", tBinary->defaultLanguageFlag());
props.append(property);
}
}
}
return TagLib::Tag::complexProperties(key);
}
bool Matroska::Tag::setComplexProperties(const String& key, const List<VariantMap>& value)
{
if(key.upper() == "PICTURE") {
// Pictures are handled at the file level
return false;
}
d->removeSimpleTags(
[&key](const SimpleTag* t) {
return t->name() == key && dynamic_cast<const SimpleTagBinary*>(t) != nullptr;
}
);
bool result = false;
for(const auto &property : value) {
if(property.value("name").value<String>() == key && property.contains("data")) {
auto *t = new SimpleTagBinary;
t->setTargetTypeValue(static_cast<SimpleTag::TargetTypeValue>(
property.value("targetTypeValue", 0).value<int>()));
t->setName(key);
t->setValue(property.value("data").value<ByteVector>());
t->setLanguage(property.value("language").value<String>());
t->setDefaultLanguageFlag(property.value("defaultLanguage", true).value<bool>());
d->tags.append(t);
result = true;
}
}
return result;
}
PropertyMap Matroska::Tag::properties() const
{
PropertyMap properties;

View File

@ -63,6 +63,30 @@ namespace TagLib {
bool isEmpty() const override;
PropertyMap properties() const override;
PropertyMap setProperties(const PropertyMap &propertyMap) override;
void removeUnsupportedProperties(const StringList& properties) override;
/*!
* Returns the names of the binary simple tags.
*/
StringList complexPropertyKeys() const override;
/*!
* Get the binary simple tags as maps with keys "data", "name",
* "targetTypeValue", "language", "defaultLanguage".
* The attached files such as pictures with key "PICTURE" are available
* with Matroska::File::complexProperties().
*/
List<VariantMap> complexProperties(const String& key) const override;
/*!
* Set the binary simple tags as maps with keys "data", "name",
* "targetTypeValue", "language", "defaultLanguage".
* The attached files such as pictures with key "PICTURE" can be set
* with Matroska::File::setComplexProperties().
*
* Returns \c true if \c key can be stored as binary simple tags.
*/
bool setComplexProperties(const String& key, const List<VariantMap>& value) override;
void addSimpleTag(SimpleTag *tag);
void removeSimpleTag(SimpleTag *tag);