1// Copyright (C) 2016 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 "qgstreamermetadata_p.h"
5#include <QDebug>
6#include <QtMultimedia/qmediametadata.h>
7#include <QtCore/qdatetime.h>
8#include <QtCore/qtimezone.h>
9
10#include <gst/gstversion.h>
11#include <qgstutils_p.h>
12#include <qlocale.h>
13
14QT_BEGIN_NAMESPACE
15
16struct {
17 const char *tag;
18 QMediaMetaData::Key key;
19} gstTagToMetaDataKey[] = {
20 { GST_TAG_TITLE, .key: QMediaMetaData::Title },
21 { GST_TAG_COMMENT, .key: QMediaMetaData::Comment },
22 { GST_TAG_DESCRIPTION, .key: QMediaMetaData::Description },
23 { GST_TAG_GENRE, .key: QMediaMetaData::Genre },
24 { GST_TAG_DATE_TIME, .key: QMediaMetaData::Date },
25 { GST_TAG_DATE, .key: QMediaMetaData::Date },
26
27 { GST_TAG_LANGUAGE_CODE, .key: QMediaMetaData::Language },
28
29 { GST_TAG_ORGANIZATION, .key: QMediaMetaData::Publisher },
30 { GST_TAG_COPYRIGHT, .key: QMediaMetaData::Copyright },
31
32 // Media
33 { GST_TAG_DURATION, .key: QMediaMetaData::Duration },
34
35 // Audio
36 { GST_TAG_BITRATE, .key: QMediaMetaData::AudioBitRate },
37 { GST_TAG_AUDIO_CODEC, .key: QMediaMetaData::AudioCodec },
38
39 // Music
40 { GST_TAG_ALBUM, .key: QMediaMetaData::AlbumTitle },
41 { GST_TAG_ALBUM_ARTIST, .key: QMediaMetaData::AlbumArtist },
42 { GST_TAG_ARTIST, .key: QMediaMetaData::ContributingArtist },
43 { GST_TAG_TRACK_NUMBER, .key: QMediaMetaData::TrackNumber },
44
45 { GST_TAG_PREVIEW_IMAGE, .key: QMediaMetaData::ThumbnailImage },
46 { GST_TAG_IMAGE, .key: QMediaMetaData::CoverArtImage },
47
48 // Image/Video
49 { .tag: "resolution", .key: QMediaMetaData::Resolution },
50 { GST_TAG_IMAGE_ORIENTATION, .key: QMediaMetaData::Orientation },
51
52 // Video
53 { GST_TAG_VIDEO_CODEC, .key: QMediaMetaData::VideoCodec },
54
55 // Movie
56 { GST_TAG_PERFORMER, .key: QMediaMetaData::LeadPerformer },
57
58 { .tag: nullptr, .key: QMediaMetaData::Title }
59};
60
61static QMediaMetaData::Key tagToKey(const char *tag)
62{
63 auto *map = gstTagToMetaDataKey;
64 while (map->tag) {
65 if (!strcmp(s1: map->tag, s2: tag))
66 return map->key;
67 ++map;
68 }
69 return QMediaMetaData::Key(-1);
70}
71
72static const char *keyToTag(QMediaMetaData::Key key)
73{
74 auto *map = gstTagToMetaDataKey;
75 while (map->tag) {
76 if (map->key == key)
77 return map->tag;
78 ++map;
79 }
80 return nullptr;
81}
82
83//internal
84static void addTagToMap(const GstTagList *list,
85 const gchar *tag,
86 gpointer user_data)
87{
88 QMediaMetaData::Key key = tagToKey(tag);
89 if (key == QMediaMetaData::Key(-1))
90 return;
91
92 auto *map = reinterpret_cast<QHash<QMediaMetaData::Key, QVariant>* >(user_data);
93
94 GValue val;
95 val.g_type = 0;
96 gst_tag_list_copy_value(dest: &val, list, tag);
97
98
99 switch( G_VALUE_TYPE(&val) ) {
100 case G_TYPE_STRING:
101 {
102 const gchar *str_value = g_value_get_string(value: &val);
103 if (key == QMediaMetaData::Language) {
104 map->insert(key, value: QVariant::fromValue(value: QLocale::codeToLanguage(languageCode: QString::fromUtf8(utf8: str_value), codeTypes: QLocale::ISO639Part2)));
105 break;
106 }
107 map->insert(key, value: QString::fromUtf8(utf8: str_value));
108 break;
109 }
110 case G_TYPE_INT:
111 map->insert(key, value: g_value_get_int(value: &val));
112 break;
113 case G_TYPE_UINT:
114 map->insert(key, value: g_value_get_uint(value: &val));
115 break;
116 case G_TYPE_LONG:
117 map->insert(key, value: qint64(g_value_get_long(value: &val)));
118 break;
119 case G_TYPE_BOOLEAN:
120 map->insert(key, value: g_value_get_boolean(value: &val));
121 break;
122 case G_TYPE_CHAR:
123 map->insert(key, value: g_value_get_schar(value: &val));
124 break;
125 case G_TYPE_DOUBLE:
126 map->insert(key, value: g_value_get_double(value: &val));
127 break;
128 default:
129 // GST_TYPE_DATE is a function, not a constant, so pull it out of the switch
130 if (G_VALUE_TYPE(&val) == G_TYPE_DATE) {
131 const GDate *date = (const GDate *)g_value_get_boxed(value: &val);
132 if (g_date_valid(date)) {
133 int year = g_date_get_year(date);
134 int month = g_date_get_month(date);
135 int day = g_date_get_day(date);
136 // don't insert if we already have a datetime.
137 if (!map->contains(key))
138 map->insert(key, value: QDateTime(QDate(year, month, day), QTime()));
139 }
140 } else if (G_VALUE_TYPE(&val) == GST_TYPE_DATE_TIME) {
141 const GstDateTime *dateTime = (const GstDateTime *)g_value_get_boxed(value: &val);
142 int year = gst_date_time_has_year(datetime: dateTime) ? gst_date_time_get_year(datetime: dateTime) : 0;
143 int month = gst_date_time_has_month(datetime: dateTime) ? gst_date_time_get_month(datetime: dateTime) : 0;
144 int day = gst_date_time_has_day(datetime: dateTime) ? gst_date_time_get_day(datetime: dateTime) : 0;
145 int hour = 0;
146 int minute = 0;
147 int second = 0;
148 float tz = 0;
149 if (gst_date_time_has_time(datetime: dateTime)) {
150 hour = gst_date_time_get_hour(datetime: dateTime);
151 minute = gst_date_time_get_minute(datetime: dateTime);
152 second = gst_date_time_get_second(datetime: dateTime);
153 tz = gst_date_time_get_time_zone_offset(datetime: dateTime);
154 }
155 QDateTime qDateTime(QDate(year, month, day), QTime(hour, minute, second),
156 QTimeZone(tz * 60 * 60));
157 map->insert(key, value: qDateTime);
158 } else if (G_VALUE_TYPE(&val) == GST_TYPE_SAMPLE) {
159 GstSample *sample = (GstSample *)g_value_get_boxed(value: &val);
160 GstCaps* caps = gst_sample_get_caps(sample);
161 if (caps && !gst_caps_is_empty(caps)) {
162 GstStructure *structure = gst_caps_get_structure(caps, index: 0);
163 const gchar *name = gst_structure_get_name(structure);
164 if (QByteArray(name).startsWith(bv: "image/")) {
165 GstBuffer *buffer = gst_sample_get_buffer(sample);
166 if (buffer) {
167 GstMapInfo info;
168 gst_buffer_map(buffer, info: &info, flags: GST_MAP_READ);
169 map->insert(key, value: QImage::fromData(data: info.data, size: info.size, format: name));
170 gst_buffer_unmap(buffer, info: &info);
171 }
172 }
173 }
174 } else if (G_VALUE_TYPE(&val) == GST_TYPE_FRACTION) {
175 int nom = gst_value_get_fraction_numerator(value: &val);
176 int denom = gst_value_get_fraction_denominator(value: &val);
177
178 if (denom > 0) {
179 map->insert(key, value: double(nom)/denom);
180 }
181 }
182 break;
183 }
184
185 g_value_unset(value: &val);
186}
187
188
189QGstreamerMetaData QGstreamerMetaData::fromGstTagList(const GstTagList *tags)
190{
191 QGstreamerMetaData m;
192 gst_tag_list_foreach(list: tags, func: addTagToMap, user_data: &m.data);
193 return m;
194}
195
196
197void QGstreamerMetaData::setMetaData(GstElement *element) const
198{
199 if (!GST_IS_TAG_SETTER(element))
200 return;
201
202 gst_tag_setter_reset_tags(GST_TAG_SETTER(element));
203
204 for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) {
205 const char *tagName = keyToTag(key: it.key());
206 if (!tagName)
207 continue;
208 const QVariant &tagValue = it.value();
209
210 switch (tagValue.typeId()) {
211 case QMetaType::QString:
212 gst_tag_setter_add_tags(GST_TAG_SETTER(element),
213 mode: GST_TAG_MERGE_REPLACE,
214 tag: tagName,
215 tagValue.toString().toUtf8().constData(),
216 nullptr);
217 break;
218 case QMetaType::Int:
219 case QMetaType::LongLong:
220 gst_tag_setter_add_tags(GST_TAG_SETTER(element),
221 mode: GST_TAG_MERGE_REPLACE,
222 tag: tagName,
223 tagValue.toInt(),
224 nullptr);
225 break;
226 case QMetaType::Double:
227 gst_tag_setter_add_tags(GST_TAG_SETTER(element),
228 mode: GST_TAG_MERGE_REPLACE,
229 tag: tagName,
230 tagValue.toDouble(),
231 nullptr);
232 break;
233 case QMetaType::QDate:
234 case QMetaType::QDateTime: {
235 QDateTime date = tagValue.toDateTime();
236 gst_tag_setter_add_tags(GST_TAG_SETTER(element),
237 mode: GST_TAG_MERGE_REPLACE,
238 tag: tagName,
239 gst_date_time_new(tzoffset: date.offsetFromUtc() / 60. / 60.,
240 year: date.date().year(), month: date.date().month(), day: date.date().day(),
241 hour: date.time().hour(), minute: date.time().minute(), seconds: date.time().second()),
242 nullptr);
243 break;
244 }
245 default: {
246 if (tagValue.typeId() == qMetaTypeId<QLocale::Language>()) {
247 QByteArray language = QLocale::languageToCode(language: tagValue.value<QLocale::Language>(), codeTypes: QLocale::ISO639Part2).toUtf8();
248 gst_tag_setter_add_tags(GST_TAG_SETTER(element),
249 mode: GST_TAG_MERGE_REPLACE,
250 tag: tagName,
251 language.constData(),
252 nullptr);
253 }
254
255 break;
256 }
257 }
258 }
259}
260
261void QGstreamerMetaData::setMetaData(GstBin *bin) const
262{
263 GstIterator *elements = gst_bin_iterate_all_by_interface(bin, GST_TYPE_TAG_SETTER);
264 GValue item = G_VALUE_INIT;
265 while (gst_iterator_next(it: elements, elem: &item) == GST_ITERATOR_OK) {
266 GstElement * const element = GST_ELEMENT(g_value_get_object(&item));
267 setMetaData(element);
268 }
269 gst_iterator_free(it: elements);
270}
271
272
273QT_END_NAMESPACE
274

source code of qtmultimedia/src/plugins/multimedia/gstreamer/common/qgstreamermetadata.cpp