Revision control
++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qffmpegmediametadata_p.h"
++ #include <QDebug>
++ #include <QtCore/qdatetime.h>
++ #include <qstringlist.h>
++ #include <qurl.h>
++ #include <qlocale.h>
++
++ #include <qloggingcategory.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(qLcMetaData, "qt.multimedia.ffmpeg.metadata")
++
++ namespace {
++
++ struct ffmpegTagToMetaDataKey
++ {
++ const char *tag;
++ QMediaMetaData::Key key;
++ };
++
++ constexpr ffmpegTagToMetaDataKey ffmpegTagToMetaDataKey[] = {
++ { "title", QMediaMetaData::Title },
++ { "comment", QMediaMetaData::Comment },
++ { "description", QMediaMetaData::Description },
++ { "genre", QMediaMetaData::Genre },
++ { "date", QMediaMetaData::Date },
++
++ { "language", QMediaMetaData::Language },
++
++ { "copyright", QMediaMetaData::Copyright },
++
++ // Music
++ { "album", QMediaMetaData::AlbumTitle },
++ { "album_artist", QMediaMetaData::AlbumArtist },
++ { "artist", QMediaMetaData::ContributingArtist },
++ { "track", QMediaMetaData::TrackNumber },
++
++ // Movie
++ { "performer", QMediaMetaData::LeadPerformer },
++
++ { nullptr, QMediaMetaData::Title }
++ };
++
++ } // namespace
++
++ static QMediaMetaData::Key tagToKey(const char *tag)
++ {
++ const auto *map = ffmpegTagToMetaDataKey;
++ while (map->tag) {
++ if (!strcmp(map->tag, tag))
++ return map->key;
++ ++map;
++ }
++ return QMediaMetaData::Key(-1);
++ }
++
++ static const char *keyToTag(QMediaMetaData::Key key)
++ {
++ const auto *map = ffmpegTagToMetaDataKey;
++ while (map->tag) {
++ if (map->key == key)
++ return map->tag;
++ ++map;
++ }
++ return nullptr;
++ }
++
++ static QDateTime getRecordingTime(const AVDictionary *tags)
++ {
++ constexpr std::array prioritizedDateTags = {
++ "date", // Time of work creation provided by FFmpeg
++ "com.apple.quicktime.creationdate", // QuickTime creation time
++ "year", // Year of work, prioritized after more specific times
++ "creation_time", // Time of file creation or encoding, prioritized last
++ };
++
++ AVDictionaryEntry *entry = nullptr;
++
++ for (const char *dateTag : prioritizedDateTags) {
++ using namespace std::string_view_literals;
++ entry = av_dict_get(tags, dateTag, nullptr, 0);
++ if (!entry)
++ continue;
++ else if (entry->key == "year"sv)
++ return QDateTime(QDate(QByteArray(entry->value).toInt(), 1, 1), QTime(0, 0, 0));
++ else
++ return QDateTime::fromString(QString::fromUtf8(entry->value), Qt::ISODate);
++ }
++
++ return QDateTime();
++ }
++
++ //internal
++ void QFFmpegMetaData::addEntry(QMediaMetaData &metaData, AVDictionaryEntry *entry)
++ {
++ qCDebug(qLcMetaData) << " checking:" << entry->key << entry->value;
++ QByteArray tag(entry->key);
++ QMediaMetaData::Key key = tagToKey(tag.toLower().constData());
++ if (key == QMediaMetaData::Key(-1))
++ return;
++ qCDebug(qLcMetaData) << " adding" << key;
++
++ auto *map = &metaData;
++
++ int metaTypeId = keyType(key).id();
++ switch (metaTypeId) {
++ case qMetaTypeId<QString>():
++ map->insert(key, QString::fromUtf8(entry->value));
++ return;
++ case qMetaTypeId<QStringList>():
++ map->insert(key, QString::fromUtf8(entry->value).split(QLatin1Char(',')));
++ return;
++ case qMetaTypeId<QDateTime>():
++ // Dates have their own handling
++ return;
++ case qMetaTypeId<QUrl>():
++ map->insert(key, QUrl::fromEncoded(entry->value));
++ return;
++ case qMetaTypeId<qint64>():
++ map->insert(key, (qint64)QByteArray(entry->value).toLongLong());
++ return;
++ case qMetaTypeId<int>():
++ map->insert(key, QByteArray(entry->value).toInt());
++ return;
++ case qMetaTypeId<qreal>():
++ map->insert(key, (qreal)QByteArray(entry->value).toDouble());
++ return;
++ default:
++ break;
++ }
++ if (metaTypeId == qMetaTypeId<QLocale::Language>()) {
++ map->insert(key, QVariant::fromValue(QLocale::codeToLanguage(QString::fromUtf8(entry->value), QLocale::ISO639Part2)));
++ }
++ }
++
++
++ QMediaMetaData QFFmpegMetaData::fromAVMetaData(const AVDictionary *tags)
++ {
++ QMediaMetaData metaData;
++ AVDictionaryEntry *entry = nullptr;
++ while ((entry = av_dict_get(tags, "", entry, AV_DICT_IGNORE_SUFFIX)))
++ addEntry(metaData, entry);
++
++ // Get date from provided tags in order of priority
++ QDateTime date = getRecordingTime(tags);
++ if (!date.isNull())
++ metaData.insert(QMediaMetaData::Date, date);
++
++ return metaData;
++ }
++
++ QByteArray QFFmpegMetaData::value(const QMediaMetaData &metaData, QMediaMetaData::Key key)
++ {
++ const int metaTypeId = keyType(key).id();
++ const QVariant val = metaData.value(key);
++ switch (metaTypeId) {
++ case qMetaTypeId<QString>():
++ return val.toString().toUtf8();
++ case qMetaTypeId<QStringList>():
++ return val.toStringList().join(u",").toUtf8();
++ case qMetaTypeId<QDateTime>():
++ return val.toDateTime().toString(Qt::ISODate).toUtf8();
++ case qMetaTypeId<QUrl>():
++ return val.toUrl().toEncoded();
++ case qMetaTypeId<qint64>():
++ case qMetaTypeId<int>():
++ return QByteArray::number(val.toLongLong());
++ case qMetaTypeId<qreal>():
++ return QByteArray::number(val.toDouble());
++ default:
++ break;
++ }
++ if (metaTypeId == qMetaTypeId<QLocale::Language>())
++ return QLocale::languageToCode(val.value<QLocale::Language>(), QLocale::ISO639Part2).toUtf8();
++ return {};
++ }
++
++
++ AVDictionary *QFFmpegMetaData::toAVMetaData(const QMediaMetaData &metaData)
++ {
++ AVDictionary *dict = nullptr;
++ for (const auto &&[k, v] : metaData.asKeyValueRange()) {
++ const char *key = ::keyToTag(k);
++ if (!key)
++ continue;
++ QByteArray val = value(metaData, k);
++ if (val.isEmpty())
++ continue;
++ av_dict_set(&dict, key, val.constData(), 0);
++ }
++ return dict;
++ }
++
++
++
++ QT_END_NAMESPACE