1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qffmpegmediametadata_p.h"
5#include <QDebug>
6#include <QtCore/qdatetime.h>
7#include <qstringlist.h>
8#include <qurl.h>
9#include <qlocale.h>
10
11#include <qloggingcategory.h>
12
13QT_BEGIN_NAMESPACE
14
15static Q_LOGGING_CATEGORY(qLcMetaData, "qt.multimedia.ffmpeg.metadata")
16
17namespace {
18
19struct ffmpegTagToMetaDataKey
20{
21 const char *tag;
22 QMediaMetaData::Key key;
23};
24
25constexpr ffmpegTagToMetaDataKey ffmpegTagToMetaDataKey[] = {
26 { .tag: "title", .key: QMediaMetaData::Title },
27 { .tag: "comment", .key: QMediaMetaData::Comment },
28 { .tag: "description", .key: QMediaMetaData::Description },
29 { .tag: "genre", .key: QMediaMetaData::Genre },
30 { .tag: "date", .key: QMediaMetaData::Date },
31 { .tag: "year", .key: QMediaMetaData::Date },
32 { .tag: "creation_time", .key: QMediaMetaData::Date },
33
34 { .tag: "language", .key: QMediaMetaData::Language },
35
36 { .tag: "copyright", .key: QMediaMetaData::Copyright },
37
38 // Music
39 { .tag: "album", .key: QMediaMetaData::AlbumTitle },
40 { .tag: "album_artist", .key: QMediaMetaData::AlbumArtist },
41 { .tag: "artist", .key: QMediaMetaData::ContributingArtist },
42 { .tag: "track", .key: QMediaMetaData::TrackNumber },
43
44 // Movie
45 { .tag: "performer", .key: QMediaMetaData::LeadPerformer },
46
47 { .tag: nullptr, .key: QMediaMetaData::Title }
48};
49
50}
51
52static QMediaMetaData::Key tagToKey(const char *tag)
53{
54 const auto *map = ffmpegTagToMetaDataKey;
55 while (map->tag) {
56 if (!strcmp(s1: map->tag, s2: tag))
57 return map->key;
58 ++map;
59 }
60 return QMediaMetaData::Key(-1);
61}
62
63static const char *keyToTag(QMediaMetaData::Key key)
64{
65 const auto *map = ffmpegTagToMetaDataKey;
66 while (map->tag) {
67 if (map->key == key)
68 return map->tag;
69 ++map;
70 }
71 return nullptr;
72}
73
74//internal
75void QFFmpegMetaData::addEntry(QMediaMetaData &metaData, AVDictionaryEntry *entry)
76{
77 qCDebug(qLcMetaData) << " checking:" << entry->key << entry->value;
78 QByteArray tag(entry->key);
79 QMediaMetaData::Key key = tagToKey(tag: tag.toLower());
80 if (key == QMediaMetaData::Key(-1))
81 return;
82 qCDebug(qLcMetaData) << " adding" << key;
83
84 auto *map = &metaData;
85
86 int metaTypeId = keyType(key).id();
87 switch (metaTypeId) {
88 case qMetaTypeId<QString>():
89 map->insert(k: key, value: QString::fromUtf8(utf8: entry->value));
90 return;
91 case qMetaTypeId<QStringList>():
92 map->insert(k: key, value: QString::fromUtf8(utf8: entry->value).split(sep: QLatin1Char(',')));
93 return;
94 case qMetaTypeId<QDateTime>(): {
95 QDateTime date;
96 if (!qstrcmp(str1: entry->key, str2: "year")) {
97 if (map->keys().contains(t: QMediaMetaData::Date))
98 return;
99 date = QDateTime(QDate(QByteArray(entry->value).toInt(), 1, 1), QTime(0, 0, 0));
100 } else {
101 date = QDateTime::fromString(string: QString::fromUtf8(utf8: entry->value), format: Qt::ISODate);
102 }
103 map->insert(k: key, value: date);
104 return;
105 }
106 case qMetaTypeId<QUrl>():
107 map->insert(k: key, value: QUrl::fromEncoded(input: entry->value));
108 return;
109 case qMetaTypeId<qint64>():
110 map->insert(k: key, value: (qint64)QByteArray(entry->value).toLongLong());
111 return;
112 case qMetaTypeId<int>():
113 map->insert(k: key, value: QByteArray(entry->value).toInt());
114 return;
115 case qMetaTypeId<qreal>():
116 map->insert(k: key, value: (qreal)QByteArray(entry->value).toDouble());
117 return;
118 default:
119 break;
120 }
121 if (metaTypeId == qMetaTypeId<QLocale::Language>()) {
122 map->insert(k: key, value: QVariant::fromValue(value: QLocale::codeToLanguage(languageCode: QString::fromUtf8(utf8: entry->value), codeTypes: QLocale::ISO639Part2)));
123 }
124}
125
126
127QMediaMetaData QFFmpegMetaData::fromAVMetaData(const AVDictionary *tags)
128{
129 QMediaMetaData metaData;
130 AVDictionaryEntry *entry = nullptr;
131 while ((entry = av_dict_get(m: tags, key: "", prev: entry, AV_DICT_IGNORE_SUFFIX)))
132 addEntry(metaData, entry);
133
134 return metaData;
135}
136
137QByteArray QFFmpegMetaData::value(const QMediaMetaData &metaData, QMediaMetaData::Key key)
138{
139 const int metaTypeId = keyType(key).id();
140 const QVariant val = metaData.value(k: key);
141 switch (metaTypeId) {
142 case qMetaTypeId<QString>():
143 return val.toString().toUtf8();
144 case qMetaTypeId<QStringList>():
145 return val.toStringList().join(sep: u",").toUtf8();
146 case qMetaTypeId<QDateTime>():
147 return val.toDateTime().toString(format: Qt::ISODate).toUtf8();
148 case qMetaTypeId<QUrl>():
149 return val.toUrl().toEncoded();
150 case qMetaTypeId<qint64>():
151 case qMetaTypeId<int>():
152 return QByteArray::number(val.toLongLong());
153 case qMetaTypeId<qreal>():
154 return QByteArray::number(val.toDouble());
155 default:
156 break;
157 }
158 if (metaTypeId == qMetaTypeId<QLocale::Language>())
159 return QLocale::languageToCode(language: val.value<QLocale::Language>(), codeTypes: QLocale::ISO639Part2).toUtf8();
160 return {};
161}
162
163
164AVDictionary *QFFmpegMetaData::toAVMetaData(const QMediaMetaData &metaData)
165{
166 AVDictionary *dict = nullptr;
167 for (const auto &&[k, v] : metaData.asKeyValueRange()) {
168 const char *key = ::keyToTag(key: k);
169 if (!key)
170 continue;
171 QByteArray val = value(metaData, key: k);
172 if (val.isEmpty())
173 continue;
174 av_dict_set(pm: &dict, key, value: val.constData(), flags: 0);
175 }
176 return dict;
177}
178
179
180
181QT_END_NAMESPACE
182

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/qffmpegmediametadata.cpp