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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | struct { |
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 | |
61 | static 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 | |
72 | static 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 |
84 | static 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 | |
189 | QGstreamerMetaData 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 | |
197 | void 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 | |
261 | void 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 | |
273 | QT_END_NAMESPACE |
274 | |