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 <QtMultimedia/qmediametadata.h> |
6 | #include <QtMultimedia/qtvideo.h> |
7 | #include <QtCore/qdebug.h> |
8 | #include <QtCore/qdatetime.h> |
9 | #include <QtCore/qlocale.h> |
10 | #include <QtCore/qtimezone.h> |
11 | #include <QtGui/qimage.h> |
12 | |
13 | #include <gst/gstversion.h> |
14 | #include <common/qgst_handle_types_p.h> |
15 | #include <common/qgstutils_p.h> |
16 | #include <qgstreamerformatinfo_p.h> |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | RotationResult parseRotationTag(std::string_view tag) |
21 | { |
22 | using namespace std::string_view_literals; |
23 | Q_ASSERT(!tag.empty()); |
24 | |
25 | if (tag[0] == 'r') { |
26 | if (tag == "rotate-90"sv) |
27 | return { .rotation: QtVideo::Rotation::Clockwise90, .flip: false }; |
28 | if (tag == "rotate-180"sv) |
29 | return { .rotation: QtVideo::Rotation::Clockwise180, .flip: false }; |
30 | if (tag == "rotate-270"sv) |
31 | return { .rotation: QtVideo::Rotation::Clockwise270, .flip: false }; |
32 | if (tag == "rotate-0"sv) |
33 | return { .rotation: QtVideo::Rotation::None, .flip: false }; |
34 | } |
35 | if (tag[0] == 'f') { |
36 | // To flip by horizontal axis is the same as to mirror by vertical axis |
37 | // and rotate by 180 degrees. |
38 | |
39 | if (tag == "flip-rotate-90"sv) |
40 | return { .rotation: QtVideo::Rotation::Clockwise270, .flip: true }; |
41 | if (tag == "flip-rotate-180"sv) |
42 | return { .rotation: QtVideo::Rotation::None, .flip: true }; |
43 | if (tag == "flip-rotate-270"sv) |
44 | return { .rotation: QtVideo::Rotation::Clockwise90, .flip: true }; |
45 | if (tag == "flip-rotate-0"sv) |
46 | return { .rotation: QtVideo::Rotation::Clockwise180, .flip: true }; |
47 | } |
48 | |
49 | qCritical() << "cannot parse orientation: {}"<< tag; |
50 | return { .rotation: QtVideo::Rotation::None, .flip: false }; |
51 | } |
52 | |
53 | namespace { |
54 | |
55 | namespace MetadataLookupImpl { |
56 | |
57 | #ifdef __cpp_lib_constexpr_algorithms |
58 | # define constexpr_lookup constexpr |
59 | #else |
60 | # define constexpr_lookup /*constexpr*/ |
61 | #endif |
62 | |
63 | struct MetadataKeyValuePair |
64 | { |
65 | const char *tag; |
66 | QMediaMetaData::Key key; |
67 | }; |
68 | |
69 | constexpr const char *toTag(const char *t) |
70 | { |
71 | return t; |
72 | } |
73 | constexpr const char *toTag(const MetadataKeyValuePair &kv) |
74 | { |
75 | return kv.tag; |
76 | } |
77 | |
78 | constexpr QMediaMetaData::Key toKey(QMediaMetaData::Key k) |
79 | { |
80 | return k; |
81 | } |
82 | constexpr QMediaMetaData::Key toKey(const MetadataKeyValuePair &kv) |
83 | { |
84 | return kv.key; |
85 | } |
86 | |
87 | constexpr auto compareByKey = [](const auto &lhs, const auto &rhs) { |
88 | return toKey(lhs) < toKey(rhs); |
89 | }; |
90 | |
91 | constexpr auto compareByTag = [](const auto &lhs, const auto &rhs) { |
92 | return std::strcmp(s1: toTag(lhs), s2: toTag(rhs)) < 0; |
93 | }; |
94 | |
95 | constexpr_lookup auto makeLookupTable() |
96 | { |
97 | std::array<MetadataKeyValuePair, 22> lookupTable{ ._M_elems: { |
98 | { GST_TAG_TITLE, .key: QMediaMetaData::Title }, |
99 | { GST_TAG_COMMENT, .key: QMediaMetaData::Comment }, |
100 | { GST_TAG_DESCRIPTION, .key: QMediaMetaData::Description }, |
101 | { GST_TAG_GENRE, .key: QMediaMetaData::Genre }, |
102 | { GST_TAG_DATE_TIME, .key: QMediaMetaData::Date }, |
103 | { GST_TAG_DATE, .key: QMediaMetaData::Date }, |
104 | |
105 | { GST_TAG_LANGUAGE_CODE, .key: QMediaMetaData::Language }, |
106 | |
107 | { GST_TAG_ORGANIZATION, .key: QMediaMetaData::Publisher }, |
108 | { GST_TAG_COPYRIGHT, .key: QMediaMetaData::Copyright }, |
109 | |
110 | // Media |
111 | { GST_TAG_DURATION, .key: QMediaMetaData::Duration }, |
112 | |
113 | // Audio |
114 | { GST_TAG_BITRATE, .key: QMediaMetaData::AudioBitRate }, |
115 | { GST_TAG_AUDIO_CODEC, .key: QMediaMetaData::AudioCodec }, |
116 | |
117 | // Music |
118 | { GST_TAG_ALBUM, .key: QMediaMetaData::AlbumTitle }, |
119 | { GST_TAG_ALBUM_ARTIST, .key: QMediaMetaData::AlbumArtist }, |
120 | { GST_TAG_ARTIST, .key: QMediaMetaData::ContributingArtist }, |
121 | { GST_TAG_TRACK_NUMBER, .key: QMediaMetaData::TrackNumber }, |
122 | |
123 | { GST_TAG_PREVIEW_IMAGE, .key: QMediaMetaData::ThumbnailImage }, |
124 | { GST_TAG_IMAGE, .key: QMediaMetaData::CoverArtImage }, |
125 | |
126 | // Image/Video |
127 | { .tag: "resolution", .key: QMediaMetaData::Resolution }, |
128 | { GST_TAG_IMAGE_ORIENTATION, .key: QMediaMetaData::Orientation }, |
129 | |
130 | // Video |
131 | { GST_TAG_VIDEO_CODEC, .key: QMediaMetaData::VideoCodec }, |
132 | |
133 | // Movie |
134 | { GST_TAG_PERFORMER, .key: QMediaMetaData::LeadPerformer }, |
135 | } }; |
136 | |
137 | std::sort(first: lookupTable.begin(), last: lookupTable.end(), |
138 | comp: [](const MetadataKeyValuePair &lhs, const MetadataKeyValuePair &rhs) { |
139 | return std::string_view(lhs.tag) < std::string_view(rhs.tag); |
140 | }); |
141 | return lookupTable; |
142 | } |
143 | |
144 | constexpr_lookup auto gstTagToMetaDataKey = makeLookupTable(); |
145 | constexpr_lookup auto metaDataKeyToGstTag = [] { |
146 | auto array = gstTagToMetaDataKey; |
147 | std::sort(first: array.begin(), last: array.end(), comp: compareByKey); |
148 | return array; |
149 | }(); |
150 | |
151 | } // namespace MetadataLookupImpl |
152 | |
153 | QMediaMetaData::Key tagToKey(const char *tag) |
154 | { |
155 | if (tag == nullptr) |
156 | return QMediaMetaData::Key(-1); |
157 | |
158 | using namespace MetadataLookupImpl; |
159 | auto foundIterator = std::lower_bound(first: gstTagToMetaDataKey.begin(), last: gstTagToMetaDataKey.end(), |
160 | val: tag, comp: compareByTag); |
161 | if (std::strcmp(s1: foundIterator->tag, s2: tag) == 0) |
162 | return foundIterator->key; |
163 | |
164 | return QMediaMetaData::Key(-1); |
165 | } |
166 | |
167 | const char *keyToTag(QMediaMetaData::Key key) |
168 | { |
169 | using namespace MetadataLookupImpl; |
170 | auto foundIterator = std::lower_bound(first: metaDataKeyToGstTag.begin(), last: metaDataKeyToGstTag.end(), |
171 | val: key, comp: compareByKey); |
172 | if (foundIterator->key == key) |
173 | return foundIterator->tag; |
174 | |
175 | return nullptr; |
176 | } |
177 | |
178 | #undef constexpr_lookup |
179 | |
180 | QDateTime parseDate(const GDate *date) |
181 | { |
182 | if (!g_date_valid(date)) |
183 | return {}; |
184 | |
185 | int year = g_date_get_year(date); |
186 | int month = g_date_get_month(date); |
187 | int day = g_date_get_day(date); |
188 | return QDateTime(QDate(year, month, day), QTime()); |
189 | } |
190 | |
191 | QDateTime parseDate(const GValue &val) |
192 | { |
193 | Q_ASSERT(G_VALUE_TYPE(&val) == G_TYPE_DATE); |
194 | const GDate *date = (const GDate *)g_value_get_boxed(value: &val); |
195 | return parseDate(date); |
196 | } |
197 | |
198 | QDateTime parseDateTime(const GstDateTime *dateTime) |
199 | { |
200 | int year = gst_date_time_has_year(datetime: dateTime) ? gst_date_time_get_year(datetime: dateTime) : 0; |
201 | int month = gst_date_time_has_month(datetime: dateTime) ? gst_date_time_get_month(datetime: dateTime) : 0; |
202 | int day = gst_date_time_has_day(datetime: dateTime) ? gst_date_time_get_day(datetime: dateTime) : 0; |
203 | int hour = 0; |
204 | int minute = 0; |
205 | int second = 0; |
206 | float tz = 0; |
207 | if (gst_date_time_has_time(datetime: dateTime)) { |
208 | hour = gst_date_time_get_hour(datetime: dateTime); |
209 | minute = gst_date_time_get_minute(datetime: dateTime); |
210 | second = gst_date_time_get_second(datetime: dateTime); |
211 | tz = gst_date_time_get_time_zone_offset(datetime: dateTime); |
212 | } |
213 | return QDateTime{ |
214 | QDate(year, month, day), |
215 | QTime(hour, minute, second), |
216 | QTimeZone(tz * 60 * 60), |
217 | }; |
218 | } |
219 | |
220 | QDateTime parseDateTime(const GValue &val) |
221 | { |
222 | Q_ASSERT(G_VALUE_TYPE(&val) == GST_TYPE_DATE_TIME); |
223 | const GstDateTime *dateTime = (const GstDateTime *)g_value_get_boxed(value: &val); |
224 | return parseDateTime(dateTime); |
225 | } |
226 | |
227 | QImage parseImage(const GValue &val) |
228 | { |
229 | Q_ASSERT(G_VALUE_TYPE(&val) == GST_TYPE_SAMPLE); |
230 | |
231 | GstSample *sample = (GstSample *)g_value_get_boxed(value: &val); |
232 | GstCaps *caps = gst_sample_get_caps(sample); |
233 | if (caps && !gst_caps_is_empty(caps)) { |
234 | GstStructure *structure = gst_caps_get_structure(caps, index: 0); |
235 | const gchar *name = gst_structure_get_name(structure); |
236 | if (QByteArray(name).startsWith(bv: "image/")) { |
237 | GstBuffer *buffer = gst_sample_get_buffer(sample); |
238 | if (buffer) { |
239 | GstMapInfo info; |
240 | gst_buffer_map(buffer, info: &info, flags: GST_MAP_READ); |
241 | QImage image = QImage::fromData(data: info.data, size: info.size, format: name); |
242 | gst_buffer_unmap(buffer, info: &info); |
243 | return image; |
244 | } |
245 | } |
246 | } |
247 | |
248 | return {}; |
249 | } |
250 | |
251 | std::optional<double> parseFractionAsDouble(const GValue &val) |
252 | { |
253 | Q_ASSERT(G_VALUE_TYPE(&val) == GST_TYPE_FRACTION); |
254 | |
255 | int nom = gst_value_get_fraction_numerator(value: &val); |
256 | int denom = gst_value_get_fraction_denominator(value: &val); |
257 | if (denom == 0) |
258 | return std::nullopt; |
259 | return double(nom) / double(denom); |
260 | } |
261 | |
262 | constexpr std::string_view extendedComment{ GST_TAG_EXTENDED_COMMENT }; |
263 | |
264 | void addTagsFromExtendedComment(const GstTagList *list, const gchar *tag, QMediaMetaData &metadata) |
265 | { |
266 | using namespace Qt::Literals; |
267 | assert(tag == extendedComment); |
268 | |
269 | int entryCount = gst_tag_list_get_tag_size(list, tag); |
270 | for (int i = 0; i != entryCount; ++i) { |
271 | const GValue *value = gst_tag_list_get_value_index(list, tag, index: i); |
272 | |
273 | const QLatin1StringView strValue{ g_value_get_string(value) }; |
274 | |
275 | auto equalIndex = strValue.indexOf(s: QLatin1StringView("=")); |
276 | if (equalIndex == -1) { |
277 | qDebug() << "Cannot parse GST_TAG_EXTENDED_COMMENT entry: "<< value; |
278 | continue; |
279 | } |
280 | |
281 | const QLatin1StringView key = strValue.first(n: equalIndex); |
282 | const QLatin1StringView valueString = strValue.last(n: strValue.size() - equalIndex - 1); |
283 | |
284 | if (key == "DURATION"_L1) { |
285 | QUniqueGstDateTimeHandle duration{ |
286 | gst_date_time_new_from_iso8601_string(string: valueString.data()), |
287 | }; |
288 | |
289 | if (duration) { |
290 | using namespace std::chrono; |
291 | |
292 | auto chronoDuration = hours(gst_date_time_get_hour(datetime: duration.get())) |
293 | + minutes(gst_date_time_get_minute(datetime: duration.get())) |
294 | + seconds(gst_date_time_get_second(datetime: duration.get())) |
295 | + microseconds(gst_date_time_get_microsecond(datetime: duration.get())); |
296 | |
297 | metadata.insert(k: QMediaMetaData::Duration, |
298 | value: QVariant::fromValue(value: round<milliseconds>(d: chronoDuration).count())); |
299 | } |
300 | } |
301 | } |
302 | } |
303 | |
304 | void addTagToMetaData(const GstTagList *list, const gchar *tag, void *userdata) |
305 | { |
306 | QMediaMetaData &metadata = *reinterpret_cast<QMediaMetaData *>(userdata); |
307 | |
308 | QMediaMetaData::Key key = tagToKey(tag); |
309 | if (key == QMediaMetaData::Key::Date) |
310 | return; // date/datetime are handled on a higher layer |
311 | |
312 | if (key == QMediaMetaData::Key(-1)) { |
313 | if (tag == extendedComment) |
314 | addTagsFromExtendedComment(list, tag, metadata); |
315 | |
316 | return; |
317 | } |
318 | |
319 | GValue val{}; |
320 | gst_tag_list_copy_value(dest: &val, list, tag); |
321 | |
322 | GType type = G_VALUE_TYPE(&val); |
323 | |
324 | if (auto entryCount = gst_tag_list_get_tag_size(list, tag) != 0; entryCount != 1) |
325 | qWarning() << "addTagToMetaData: invaled entry count for"<< tag << "-"<< entryCount; |
326 | |
327 | if (type == G_TYPE_STRING) { |
328 | const gchar *str_value = g_value_get_string(value: &val); |
329 | |
330 | switch (key) { |
331 | case QMediaMetaData::Language: { |
332 | metadata.insert(k: key, value: QVariant::fromValue(value: QGstUtils::codeToLanguage(str_value))); |
333 | break; |
334 | } |
335 | case QMediaMetaData::Orientation: { |
336 | RotationResult result = parseRotationTag(tag: str_value); |
337 | metadata.insert(k: key, value: QVariant::fromValue(value: result.rotation)); |
338 | break; |
339 | } |
340 | default: |
341 | metadata.insert(k: key, value: QString::fromUtf8(utf8: str_value)); |
342 | break; |
343 | }; |
344 | } else if (type == G_TYPE_INT) { |
345 | metadata.insert(k: key, value: g_value_get_int(value: &val)); |
346 | } else if (type == G_TYPE_UINT) { |
347 | metadata.insert(k: key, value: g_value_get_uint(value: &val)); |
348 | } else if (type == G_TYPE_LONG) { |
349 | metadata.insert(k: key, value: qint64(g_value_get_long(value: &val))); |
350 | } else if (type == G_TYPE_BOOLEAN) { |
351 | metadata.insert(k: key, value: g_value_get_boolean(value: &val)); |
352 | } else if (type == G_TYPE_CHAR) { |
353 | metadata.insert(k: key, value: g_value_get_schar(value: &val)); |
354 | } else if (type == G_TYPE_DOUBLE) { |
355 | metadata.insert(k: key, value: g_value_get_double(value: &val)); |
356 | } else if (type == G_TYPE_DATE) { |
357 | if (!metadata.keys().contains(t: key)) { |
358 | QDateTime date = parseDate(val); |
359 | if (date.isValid()) |
360 | metadata.insert(k: key, value: date); |
361 | } |
362 | } else if (type == GST_TYPE_DATE_TIME) { |
363 | QDateTime date = parseDateTime(val); |
364 | if (date.isValid()) |
365 | metadata.insert(k: key, value: parseDateTime(val)); |
366 | } else if (type == GST_TYPE_SAMPLE) { |
367 | QImage image = parseImage(val); |
368 | if (!image.isNull()) |
369 | metadata.insert(k: key, value: image); |
370 | } else if (type == GST_TYPE_FRACTION) { |
371 | std::optional<double> fraction = parseFractionAsDouble(val); |
372 | |
373 | if (fraction) |
374 | metadata.insert(k: key, value: *fraction); |
375 | } |
376 | |
377 | g_value_unset(value: &val); |
378 | } |
379 | |
380 | } // namespace |
381 | |
382 | QMediaMetaData taglistToMetaData(const QGstTagListHandle &handle) |
383 | { |
384 | QMediaMetaData m; |
385 | extendMetaDataFromTagList(m, handle); |
386 | return m; |
387 | } |
388 | |
389 | void extendMetaDataFromTagList(QMediaMetaData &metadata, const QGstTagListHandle &handle) |
390 | { |
391 | if (handle) { |
392 | // gstreamer has both GST_TAG_DATE_TIME and GST_TAG_DATE tags. |
393 | // if both are present, we use GST_TAG_DATE_TIME, else we fall back to GST_TAG_DATE |
394 | |
395 | auto readDateTime = [&]() -> std::optional<QDateTime> { |
396 | GstDateTime *dateTimeHandle{}; |
397 | gst_tag_list_get_date_time(list: handle.get(), GST_TAG_DATE_TIME, value: &dateTimeHandle); |
398 | if (dateTimeHandle) { |
399 | QDateTime ret = parseDateTime(dateTime: dateTimeHandle); |
400 | gst_date_time_unref(datetime: dateTimeHandle); |
401 | if (ret.isValid()) |
402 | return ret; |
403 | } |
404 | return std::nullopt; |
405 | }; |
406 | |
407 | auto readDate = [&]() -> std::optional<QDateTime> { |
408 | GDate *dateHandle{}; |
409 | gst_tag_list_get_date(list: handle.get(), GST_TAG_DATE, value: &dateHandle); |
410 | if (dateHandle) { |
411 | QDateTime ret = parseDate(date: dateHandle); |
412 | g_date_free(date: dateHandle); |
413 | if (ret.isValid()) |
414 | return ret; |
415 | } |
416 | return std::nullopt; |
417 | }; |
418 | |
419 | std::optional<QDateTime> date = readDateTime(); |
420 | if (!date) |
421 | date = readDate(); |
422 | |
423 | if (date) |
424 | metadata.insert(k: QMediaMetaData::Key::Date, value: *date); |
425 | |
426 | gst_tag_list_foreach(list: handle.get(), func: reinterpret_cast<GstTagForeachFunc>(&addTagToMetaData), |
427 | user_data: &metadata); |
428 | } |
429 | } |
430 | |
431 | static void applyMetaDataToTagSetter(const QMediaMetaData &metadata, GstTagSetter *element) |
432 | { |
433 | gst_tag_setter_reset_tags(setter: element); |
434 | |
435 | for (QMediaMetaData::Key key : metadata.keys()) { |
436 | const char *tagName = keyToTag(key); |
437 | if (!tagName) |
438 | continue; |
439 | const QVariant &tagValue = metadata.value(k: key); |
440 | |
441 | auto setTag = [&](const auto &value) { |
442 | gst_tag_setter_add_tags(element, GST_TAG_MERGE_REPLACE, tagName, value, nullptr); |
443 | }; |
444 | |
445 | switch (tagValue.typeId()) { |
446 | case QMetaType::QString: |
447 | setTag(tagValue.toString().toUtf8().constData()); |
448 | break; |
449 | case QMetaType::Int: |
450 | case QMetaType::LongLong: |
451 | setTag(tagValue.toInt()); |
452 | break; |
453 | case QMetaType::Double: |
454 | setTag(tagValue.toDouble()); |
455 | break; |
456 | |
457 | case QMetaType::QDateTime: { |
458 | // tagName does not properly disambiguate between GST_TAG_DATE_TIME and |
459 | // GST_TAG_DATE, as both map to QMediaMetaData::Key::Date. so we set it accordingly to |
460 | // the QVariant. |
461 | |
462 | QDateTime date = tagValue.toDateTime(); |
463 | |
464 | QGstGstDateTimeHandle dateTime{ |
465 | gst_date_time_new(tzoffset: date.offsetFromUtc() / 60. / 60., year: date.date().year(), |
466 | month: date.date().month(), day: date.date().day(), hour: date.time().hour(), |
467 | minute: date.time().minute(), seconds: date.time().second()), |
468 | QGstGstDateTimeHandle::HasRef, |
469 | }; |
470 | |
471 | gst_tag_setter_add_tags(setter: element, mode: GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME, |
472 | dateTime.get(), nullptr); |
473 | break; |
474 | } |
475 | case QMetaType::QDate: { |
476 | QDate date = tagValue.toDate(); |
477 | |
478 | QUniqueGDateHandle dateHandle{ |
479 | g_date_new_dmy(day: date.day(), month: GDateMonth(date.month()), year: date.year()), |
480 | }; |
481 | |
482 | gst_tag_setter_add_tags(setter: element, mode: GST_TAG_MERGE_REPLACE, GST_TAG_DATE, dateHandle.get(), |
483 | nullptr); |
484 | break; |
485 | } |
486 | default: { |
487 | if (tagValue.typeId() == qMetaTypeId<QLocale::Language>()) { |
488 | QByteArray language = QLocale::languageToCode(language: tagValue.value<QLocale::Language>(), |
489 | codeTypes: QLocale::ISO639Part2) |
490 | .toUtf8(); |
491 | setTag(language.constData()); |
492 | } |
493 | |
494 | break; |
495 | } |
496 | } |
497 | } |
498 | } |
499 | |
500 | void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstElement &element) |
501 | { |
502 | GstTagSetter *tagSetter = qGstSafeCast<GstTagSetter>(arg: element.element()); |
503 | if (tagSetter) |
504 | applyMetaDataToTagSetter(metadata, element: tagSetter); |
505 | else |
506 | qWarning() << "applyMetaDataToTagSetter failed: element not a GstTagSetter" |
507 | << element.name(); |
508 | } |
509 | |
510 | void applyMetaDataToTagSetter(const QMediaMetaData &metadata, const QGstBin &bin) |
511 | { |
512 | GstIterator *elements = gst_bin_iterate_all_by_interface(bin: bin.bin(), GST_TYPE_TAG_SETTER); |
513 | GValue item = {}; |
514 | |
515 | while (gst_iterator_next(it: elements, elem: &item) == GST_ITERATOR_OK) { |
516 | GstElement *element = static_cast<GstElement *>(g_value_get_object(value: &item)); |
517 | if (!element) |
518 | continue; |
519 | |
520 | GstTagSetter *tagSetter = qGstSafeCast<GstTagSetter>(arg: element); |
521 | |
522 | if (tagSetter) |
523 | applyMetaDataToTagSetter(metadata, element: tagSetter); |
524 | } |
525 | |
526 | gst_iterator_free(it: elements); |
527 | } |
528 | |
529 | void extendMetaDataFromCaps(QMediaMetaData &metadata, const QGstCaps &caps) |
530 | { |
531 | QGstStructureView structure = caps.at(index: 0); |
532 | |
533 | QMediaFormat::FileFormat fileFormat = QGstreamerFormatInfo::fileFormatForCaps(structure); |
534 | if (fileFormat != QMediaFormat::FileFormat::UnspecifiedFormat) { |
535 | // Container caps |
536 | metadata.insert(k: QMediaMetaData::FileFormat, value: fileFormat); |
537 | return; |
538 | } |
539 | |
540 | QMediaFormat::AudioCodec audioCodec = QGstreamerFormatInfo::audioCodecForCaps(structure); |
541 | if (audioCodec != QMediaFormat::AudioCodec::Unspecified) { |
542 | // Audio stream caps |
543 | metadata.insert(k: QMediaMetaData::AudioCodec, value: QVariant::fromValue(value: audioCodec)); |
544 | return; |
545 | } |
546 | |
547 | QMediaFormat::VideoCodec videoCodec = QGstreamerFormatInfo::videoCodecForCaps(structure); |
548 | if (videoCodec != QMediaFormat::VideoCodec::Unspecified) { |
549 | // Video stream caps |
550 | metadata.insert(k: QMediaMetaData::VideoCodec, value: QVariant::fromValue(value: videoCodec)); |
551 | std::optional<float> framerate = structure["framerate"].getFraction(); |
552 | if (framerate) |
553 | metadata.insert(k: QMediaMetaData::VideoFrameRate, value: *framerate); |
554 | |
555 | QSize resolution = structure.resolution(); |
556 | if (resolution.isValid()) |
557 | metadata.insert(k: QMediaMetaData::Resolution, value: resolution); |
558 | } |
559 | } |
560 | |
561 | QMediaMetaData capsToMetaData(const QGstCaps &caps) |
562 | { |
563 | QMediaMetaData metadata; |
564 | extendMetaDataFromCaps(metadata, caps); |
565 | return metadata; |
566 | } |
567 | |
568 | QT_END_NAMESPACE |
569 |
Definitions
- parseRotationTag
- MetadataKeyValuePair
- toTag
- toTag
- toKey
- toKey
- compareByKey
- compareByTag
- makeLookupTable
- gstTagToMetaDataKey
- metaDataKeyToGstTag
- tagToKey
- keyToTag
- parseDate
- parseDate
- parseDateTime
- parseDateTime
- parseImage
- parseFractionAsDouble
- extendedComment
- addTagsFromExtendedComment
- addTagToMetaData
- taglistToMetaData
- extendMetaDataFromTagList
- applyMetaDataToTagSetter
- applyMetaDataToTagSetter
- applyMetaDataToTagSetter
- extendMetaDataFromCaps
Learn to use CMake with our Intro Training
Find out more