Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
mfmetadata.cpp
Go to the documentation of this file.
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// Qt-Security score:critical reason:data-parser
4
5#include <qmediametadata.h>
6
7#include <QtCore/qdatetime.h>
8#include <QtCore/qtimezone.h>
9#include <QtCore/quuid.h>
10#include <QtCore/private/qcomptr_p.h>
11#include <QtCore/private/qflatmap_p.h>
12#include <QtCore/qvarlengtharray.h>
13#include <QtGui/qimage.h>
14#include <QtMultimedia/private/qmediametadata_p.h>
15#include <QtMultimedia/private/qwindows_scopedpropvariant_p.h>
16#include <QtMultimedia/private/qwindowsmultimediautils_p.h>
17
18#include <guiddef.h>
19#include <cguid.h>
20#include <mfapi.h>
21#include <mfidl.h>
22#include <propvarutil.h>
23#include <propkey.h>
24#include <wmsdkidl.h>
25
26
27#include "mfmetadata_p.h"
28
29//#define DEBUG_MEDIAFOUNDATION
30
31static const PROPERTYKEY PROP_KEY_NULL = {GUID_NULL, 0};
32
33static QVariant convertValue(const PROPVARIANT& var)
34{
35 QVariant value;
36 switch (var.vt) {
37 case VT_LPWSTR:
38 value = QString::fromUtf16(reinterpret_cast<const char16_t *>(var.pwszVal));
39 break;
40 case VT_I4:
41 value = int(var.lVal);
42 break;
43 case VT_UI4:
44 value = uint(var.ulVal);
45 break;
46 case VT_UI8:
47 value = qulonglong(var.uhVal.QuadPart);
48 break;
49 case VT_BOOL:
50 value = bool(var.boolVal);
51 break;
52 case VT_FILETIME:
53 SYSTEMTIME t;
54 if (!FileTimeToSystemTime(&var.filetime, &t))
55 break;
56
57 value = QDateTime(QDate(t.wYear, t.wMonth, t.wDay),
58 QTime(t.wHour, t.wMinute, t.wSecond, t.wMilliseconds),
59 QTimeZone(QTimeZone::UTC));
60 break;
61 case VT_STREAM:
62 {
63 STATSTG stat;
64 if (FAILED(var.pStream->Stat(&stat, STATFLAG_NONAME)))
65 break;
66 void *data = malloc(stat.cbSize.QuadPart);
67 ULONG read = 0;
68 if (FAILED(var.pStream->Read(data, stat.cbSize.QuadPart, &read))) {
69 free(data);
70 break;
71 }
72 value = QImage::fromData((const uchar*)data, read);
73 free(data);
74 }
75 break;
76 case VT_VECTOR | VT_LPWSTR:
77 QStringList vList;
78 for (ULONG i = 0; i < var.calpwstr.cElems; ++i)
79 vList.append(QString::fromUtf16(reinterpret_cast<const char16_t *>(var.calpwstr.pElems[i])));
80 value = vList;
81 break;
82 }
83 return value;
84}
85
86static QVariant metaDataValue(IPropertyStore *content, const PROPERTYKEY &key)
87{
88 if (!content)
89 return {};
90
91 QtMultimediaPrivate::ScopedPropVariant pv;
92 if (FAILED(content->GetValue(key, pv.get())))
93 return {};
94
95 QVariant value = convertValue(pv.var);
96 if (!value.isValid())
97 return value;
98
99 // some metadata needs to be reformatted
100 if (key == PKEY_Media_ClassPrimaryID /*QMediaMetaData::MediaType*/) {
101 QString v = value.toString();
102 if (v == QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}"))
103 value = QStringLiteral("Music");
104 else if (v == QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}"))
105 value = QStringLiteral("Video");
106 else if (v == QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}"))
107 value = QStringLiteral("Audio");
108 else if (v == QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}"))
109 value = QStringLiteral("Other");
110 } else if (key == PKEY_Media_Duration) {
111 // duration is provided in 100-nanosecond units, convert to milliseconds
112 value = (value.toLongLong() + 10000) / 10000;
113 } else if (key == PKEY_Video_Compression) {
114 value = int(QWindowsMultimediaUtils::codecForVideoFormat(value.toUuid()));
115 } else if (key == PKEY_Audio_Format) {
116 value = int(QWindowsMultimediaUtils::codecForAudioFormat(value.toUuid()));
117 } else if (key == PKEY_Video_FrameHeight /*Resolution*/) {
118 QSize res;
119 res.setHeight(value.toUInt());
120 if (SUCCEEDED(content->GetValue(PKEY_Video_FrameWidth, pv.get())))
121 res.setWidth(convertValue(pv.var).toUInt());
122 value = res;
123 } else if (key == PKEY_Video_Orientation) {
124 uint orientation = 0;
125 if (SUCCEEDED(content->GetValue(PKEY_Video_Orientation, pv.get())))
126 orientation = convertValue(pv.var).toUInt();
127 value = orientation;
128 } else if (key == PKEY_Video_FrameRate) {
129 value = value.toReal() / 1000.f;
130 }
131
132 return value;
133}
134
135QMediaMetaData MFMetaData::fromNative(IMFMediaSource* mediaSource)
136{
137 QMediaMetaData metaData;
138
139 // Shell property handler first — provides the richest metadata
140 // (thumbnails, duration, codecs, resolution, bitrates, etc.)
141 // but only works for file:// sources.
142 ComPtr<IPropertyStore> content;
143 if (SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&content))))
144 metaData = fromNative(content.Get());
145
146 // IMFMetadataProvider fallback — works for all source types
147 // including byte streams (qrc://, QIODevice). Fills in any keys
148 // not already provided by IPropertyStore.
149 ComPtr<IMFMetadataProvider> provider;
150 if (SUCCEEDED(MFGetService(mediaSource, MF_METADATA_PROVIDER_SERVICE, IID_PPV_ARGS(&provider)))) {
151 ComPtr<IMFPresentationDescriptor> pd;
152 if (SUCCEEDED(mediaSource->CreatePresentationDescriptor(&pd))) {
153 ComPtr<IMFMetadata> metadata;
154 if (SUCCEEDED(provider->GetMFMetadata(pd.Get(), 0, 0, &metadata))) {
155 const QMediaMetaData mfData = fromNative(metadata.Get());
156 for (const auto &[key, value] : mfData.asKeyValueRange()) {
157 if (!metaData.value(key).isValid())
158 metaData.insert(key, value);
159 }
160 }
161 }
162 }
163
164 return metaData;
165}
166
167static QImage imageFromAsfFlatPicture(const BLOB &blob)
168{
169 if (blob.cbSize <= sizeof(ASF_FLAT_PICTURE))
170 return {};
171
172 const auto *pic = reinterpret_cast<const ASF_FLAT_PICTURE *>(blob.pBlobData);
173 const BYTE *p = blob.pBlobData + sizeof(ASF_FLAT_PICTURE);
174 const BYTE *end = blob.pBlobData + blob.cbSize;
175
176 // Skip MIME type (null-terminated UTF-16)
177 while (p + 1 < end && (p[0] || p[1]))
178 p += 2;
179 p += 2;
180 if (p > end)
181 return {};
182
183 // Skip description (null-terminated UTF-16)
184 while (p + 1 < end && (p[0] || p[1]))
185 p += 2;
186 p += 2;
187 if (p > end || pic->dwDataLen > static_cast<DWORD>(end - p))
188 return {};
189
190 QImage img;
191 img.loadFromData(p, pic->dwDataLen);
192 return img;
193}
194
195QMediaMetaData MFMetaData::fromNative(IMFMetadata *metadata)
196{
197 if (!metadata)
198 return {};
199
200 QtMultimediaPrivate::ScopedPropVariant names;
201 if (FAILED(metadata->GetAllPropertyNames(names.get())))
202 return {};
203
204 QMediaMetaData metaData;
205
206 // Property name strings match the Windows SDK g_wszWM* constants from
207 // wmsdkidl.h but are hardcoded here as they are missing in older MinGW
208 // variants of the Windows SDK.
209 static const QVarLengthFlatMap<QStringView, QMediaMetaData::Key, 12> nameToKey({
210 { u"Title", QMediaMetaData::Title },
211 { u"Author", QMediaMetaData::ContributingArtist },
212 { u"WM/AlbumTitle", QMediaMetaData::AlbumTitle },
213 { u"WM/AlbumArtist", QMediaMetaData::AlbumArtist },
214 { u"WM/Composer", QMediaMetaData::Composer },
215 { u"WM/Genre", QMediaMetaData::Genre },
216 { u"WM/TrackNumber", QMediaMetaData::TrackNumber },
217 { u"Description", QMediaMetaData::Description },
218 { u"Copyright", QMediaMetaData::Copyright },
219 { u"WM/Publisher", QMediaMetaData::Publisher },
220 { u"WM/Language", QMediaMetaData::Language },
221 { u"WM/AuthorURL", QMediaMetaData::Url },
222 });
223
224 if (names->vt == (VT_VECTOR | VT_LPWSTR)) {
225 for (ULONG i = 0; i < names->calpwstr.cElems; ++i) {
226 const QStringView name(names->calpwstr.pElems[i]);
227
228 // WM/Picture blob: ASF_FLAT_PICTURE header followed by
229 // MIME type string, description string, and image data.
230 if (name == u"WM/Picture") {
231 QtMultimediaPrivate::ScopedPropVariant value;
232 if (SUCCEEDED(metadata->GetProperty(names->calpwstr.pElems[i], value.get()))
233 && value->vt == VT_BLOB) {
234 QImage img = imageFromAsfFlatPicture(value->blob);
235 if (!img.isNull())
236 metaData.insert(QMediaMetaData::CoverArtImage, img);
237 }
238 continue;
239 }
240
241 auto it = nameToKey.find(name);
242 if (it == nameToKey.end())
243 continue;
244
245 QtMultimediaPrivate::ScopedPropVariant value;
246 if (SUCCEEDED(metadata->GetProperty(names->calpwstr.pElems[i], value.get()))) {
247 QVariant v = convertValue(value.var);
248 if (v.isValid())
249 metaData.insert(it.value(), v);
250 }
251 }
252 }
253
254 return metaData;
255}
256
257QMediaMetaData MFMetaData::fromNative(IPropertyStore *content)
258{
259 QMediaMetaData metaData;
260
261 if (!content)
262 return metaData;
263
264 DWORD cProps;
265 if (SUCCEEDED(content->GetCount(&cProps))) {
266 for (DWORD i = 0; i < cProps; i++)
267 {
268 PROPERTYKEY key;
269 if (FAILED(content->GetAt(i, &key)))
270 continue;
271 QMediaMetaData::Key mediaKey;
272 if (key == PKEY_Author) {
273 mediaKey = QMediaMetaData::Author;
274 } else if (key == PKEY_Title) {
275 mediaKey = QMediaMetaData::Title;
276// } else if (key == PKEY_Media_SubTitle) {
277// mediaKey = QMediaMetaData::SubTitle;
278// } else if (key == PKEY_ParentalRating) {
279// mediaKey = QMediaMetaData::ParentalRating;
280 } else if (key == PKEY_Media_EncodingSettings) {
281 mediaKey = QMediaMetaData::Description;
282 } else if (key == PKEY_Copyright) {
283 mediaKey = QMediaMetaData::Copyright;
284 } else if (key == PKEY_Comment) {
285 mediaKey = QMediaMetaData::Comment;
286 } else if (key == PKEY_Media_ProviderStyle) {
287 mediaKey = QMediaMetaData::Genre;
288 } else if (key == PKEY_Media_DateEncoded) {
289 mediaKey = QMediaMetaData::Date;
290// } else if (key == PKEY_Rating) {
291// mediaKey = QMediaMetaData::UserRating;
292// } else if (key == PKEY_Keywords) {
293// mediaKey = QMediaMetaData::Keywords;
294 } else if (key == PKEY_Language) {
295 mediaKey = QMediaMetaData::Language;
296 } else if (key == PKEY_Media_Publisher) {
297 mediaKey = QMediaMetaData::Publisher;
298 } else if (key == PKEY_Media_ClassPrimaryID) {
299 mediaKey = QMediaMetaData::MediaType;
300 } else if (key == PKEY_Media_Duration) {
301 mediaKey = QMediaMetaData::Duration;
302 } else if (key == PKEY_Audio_EncodingBitrate) {
303 mediaKey = QMediaMetaData::AudioBitRate;
304 } else if (key == PKEY_Audio_Format) {
305 mediaKey = QMediaMetaData::AudioCodec;
306// } else if (key == PKEY_Media_AverageLevel) {
307// mediaKey = QMediaMetaData::AverageLevel;
308// } else if (key == PKEY_Audio_ChannelCount) {
309// mediaKey = QMediaMetaData::ChannelCount;
310// } else if (key == PKEY_Audio_PeakValue) {
311// mediaKey = QMediaMetaData::PeakValue;
312// } else if (key == PKEY_Audio_SampleRate) {
313// mediaKey = QMediaMetaData::SampleRate;
314 } else if (key == PKEY_Music_AlbumTitle) {
315 mediaKey = QMediaMetaData::AlbumTitle;
316 } else if (key == PKEY_Music_AlbumArtist) {
317 mediaKey = QMediaMetaData::AlbumArtist;
318 } else if (key == PKEY_Music_Artist) {
319 mediaKey = QMediaMetaData::ContributingArtist;
320 } else if (key == PKEY_Music_Composer) {
321 mediaKey = QMediaMetaData::Composer;
322// } else if (key == PKEY_Music_Conductor) {
323// mediaKey = QMediaMetaData::Conductor;
324// } else if (key == PKEY_Music_Lyrics) {
325// mediaKey = QMediaMetaData::Lyrics;
326// } else if (key == PKEY_Music_Mood) {
327// mediaKey = QMediaMetaData::Mood;
328 } else if (key == PKEY_Music_TrackNumber) {
329 mediaKey = QMediaMetaData::TrackNumber;
330 } else if (key == PKEY_Music_Genre) {
331 mediaKey = QMediaMetaData::Genre;
332 } else if (key == PKEY_ThumbnailStream) {
333 QVariant val = metaDataValue(content, key);
334 if (val.canConvert<QImage>())
335 QtMultimediaPrivate::setCoverArtImage(metaData, val.value<QImage>());
336 continue;
337 } else if (key == PKEY_Video_FrameHeight) {
338 mediaKey = QMediaMetaData::Resolution;
339 } else if (key == PKEY_Video_Orientation) {
340 mediaKey = QMediaMetaData::Orientation;
341 } else if (key == PKEY_Video_FrameRate) {
342 mediaKey = QMediaMetaData::VideoFrameRate;
343 } else if (key == PKEY_Video_EncodingBitrate) {
344 mediaKey = QMediaMetaData::VideoBitRate;
345 } else if (key == PKEY_Video_Compression) {
346 mediaKey = QMediaMetaData::VideoCodec;
347// } else if (key == PKEY_Video_Director) {
348// mediaKey = QMediaMetaData::Director;
349// } else if (key == PKEY_Media_Writer) {
350// mediaKey = QMediaMetaData::Writer;
351 } else {
352 continue;
353 }
354 metaData.insert(mediaKey, metaDataValue(content, key));
355 }
356 }
357
358 return metaData;
359}
360
361static REFPROPERTYKEY propertyKeyForMetaDataKey(QMediaMetaData::Key key)
362{
363 switch (key) {
364 case QMediaMetaData::Key::Title:
365 return PKEY_Title;
366 case QMediaMetaData::Key::Author:
367 return PKEY_Author;
368 case QMediaMetaData::Key::Comment:
369 return PKEY_Comment;
370 case QMediaMetaData::Key::Genre:
371 return PKEY_Music_Genre;
372 case QMediaMetaData::Key::Copyright:
373 return PKEY_Copyright;
374 case QMediaMetaData::Key::Publisher:
375 return PKEY_Media_Publisher;
376 case QMediaMetaData::Key::Url:
377 return PKEY_Media_AuthorUrl;
378 case QMediaMetaData::Key::AlbumTitle:
379 return PKEY_Music_AlbumTitle;
380 case QMediaMetaData::Key::AlbumArtist:
381 return PKEY_Music_AlbumArtist;
382 case QMediaMetaData::Key::TrackNumber:
383 return PKEY_Music_TrackNumber;
384 case QMediaMetaData::Key::Date:
385 return PKEY_Media_DateEncoded;
386 case QMediaMetaData::Key::Composer:
387 return PKEY_Music_Composer;
388 case QMediaMetaData::Key::Duration:
389 return PKEY_Media_Duration;
390 case QMediaMetaData::Key::Language:
391 return PKEY_Language;
392 case QMediaMetaData::Key::Description:
393 return PKEY_Media_EncodingSettings;
394 case QMediaMetaData::Key::AudioBitRate:
395 return PKEY_Audio_EncodingBitrate;
396 case QMediaMetaData::Key::ContributingArtist:
397 return PKEY_Music_Artist;
398#if QT_DEPRECATED_SINCE(6, 12)
399 case QtMultimediaPrivate::deprecatedThumbnailImage:
400#endif
401 case QMediaMetaData::Key::CoverArtImage:
402 return PKEY_ThumbnailStream;
403 case QMediaMetaData::Key::Orientation:
404 return PKEY_Video_Orientation;
405 case QMediaMetaData::Key::VideoFrameRate:
406 return PKEY_Video_FrameRate;
407 case QMediaMetaData::Key::VideoBitRate:
408 return PKEY_Video_EncodingBitrate;
409 case QMediaMetaData::MediaType:
410 return PKEY_Media_ClassPrimaryID;
411 default:
412 return PROP_KEY_NULL;
413 }
414}
415
416static void setStringProperty(IPropertyStore *content, REFPROPERTYKEY key, const QString &value)
417{
418 QtMultimediaPrivate::ScopedPropVariant propValue;
419 if (SUCCEEDED(InitPropVariantFromString(reinterpret_cast<LPCWSTR>(value.utf16()), propValue.get()))) {
420 if (SUCCEEDED(PSCoerceToCanonicalValue(key, propValue.get())))
421 content->SetValue(key, propValue.var);
422 }
423}
424
425static void setUInt32Property(IPropertyStore *content, REFPROPERTYKEY key, quint32 value)
426{
427 QtMultimediaPrivate::ScopedPropVariant propValue;
428 if (SUCCEEDED(InitPropVariantFromUInt32(ULONG(value), propValue.get()))) {
429 if (SUCCEEDED(PSCoerceToCanonicalValue(key, propValue.get())))
430 content->SetValue(key, propValue.var);
431 }
432}
433
434static void setUInt64Property(IPropertyStore *content, REFPROPERTYKEY key, quint64 value)
435{
436 QtMultimediaPrivate::ScopedPropVariant propValue;
437 if (SUCCEEDED(InitPropVariantFromUInt64(ULONGLONG(value), propValue.get()))) {
438 if (SUCCEEDED(PSCoerceToCanonicalValue(key, propValue.get())))
439 content->SetValue(key, propValue.var);
440 }
441}
442
443static void setFileTimeProperty(IPropertyStore *content, REFPROPERTYKEY key, const FILETIME *ft)
444{
445 QtMultimediaPrivate::ScopedPropVariant propValue;
446 if (SUCCEEDED(InitPropVariantFromFileTime(ft, propValue.get()))) {
447 if (SUCCEEDED(PSCoerceToCanonicalValue(key, propValue.get())))
448 content->SetValue(key, propValue.var);
449 }
450}
451
452void MFMetaData::toNative(const QMediaMetaData &metaData, IPropertyStore *content)
453{
454 if (content) {
455
456 for (const auto &key : metaData.keys()) {
457
458 QVariant value = metaData.value(key);
459
460 if (key == QMediaMetaData::Key::MediaType) {
461
462 QString strValue = metaData.stringValue(key);
463 QString v;
464
465 // Sets property to one of the MediaClassPrimaryID values defined by Microsoft:
466 // https://docs.microsoft.com/en-us/windows/win32/wmformat/wm-mediaprimaryid
467 if (strValue == QLatin1String("Music"))
468 v = QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}");
469 else if (strValue == QLatin1String("Video"))
470 v = QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}");
471 else if (strValue == QLatin1String("Audio"))
472 v = QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}");
473 else
474 v = QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}");
475
476 setStringProperty(content, PKEY_Media_ClassPrimaryID, v);
477
478 } else if (key == QMediaMetaData::Key::Duration) {
479
480 setUInt64Property(content, PKEY_Media_Duration, value.toULongLong() * 10000);
481
482 } else if (key == QMediaMetaData::Key::Resolution) {
483
484 QSize res = value.toSize();
485 setUInt32Property(content, PKEY_Video_FrameWidth, quint32(res.width()));
486 setUInt32Property(content, PKEY_Video_FrameHeight, quint32(res.height()));
487
488 } else if (key == QMediaMetaData::Key::Orientation) {
489
490 setUInt32Property(content, PKEY_Video_Orientation, value.toUInt());
491
492 } else if (key == QMediaMetaData::Key::VideoFrameRate) {
493
494 qreal fps = value.toReal();
495 setUInt32Property(content, PKEY_Video_FrameRate, quint32(fps * 1000));
496
497 } else if (key == QMediaMetaData::Key::TrackNumber) {
498
499 setUInt32Property(content, PKEY_Music_TrackNumber, value.toUInt());
500
501 } else if (key == QMediaMetaData::Key::AudioBitRate) {
502
503 setUInt32Property(content, PKEY_Audio_EncodingBitrate, value.toUInt());
504
505 } else if (key == QMediaMetaData::Key::VideoBitRate) {
506
507 setUInt32Property(content, PKEY_Video_EncodingBitrate, value.toUInt());
508
509 } else if (key == QMediaMetaData::Key::Date) {
510
511 // Convert QDateTime to FILETIME by converting to 100-nsecs since
512 // 01/01/1970 UTC and adding the difference from 1601 to 1970.
513 ULARGE_INTEGER t = {};
514 t.QuadPart = ULONGLONG(value.toDateTime().toUTC().toMSecsSinceEpoch() * 10000
515 + 116444736000000000LL);
516
517 FILETIME ft = {};
518 ft.dwHighDateTime = t.HighPart;
519 ft.dwLowDateTime = t.LowPart;
520
521 setFileTimeProperty(content, PKEY_Media_DateEncoded, &ft);
522
523 } else {
524
525 // By default use as string and let PSCoerceToCanonicalValue()
526 // do validation and type conversion.
527 REFPROPERTYKEY propKey = propertyKeyForMetaDataKey(key);
528
529 if (propKey != PROP_KEY_NULL) {
530 QString strValue = metaData.stringValue(key);
531 if (!strValue.isEmpty())
532 setStringProperty(content, propKey, strValue);
533 }
534 }
535 }
536 }
537}
\inmodule QtMultimedia
static QImage imageFromAsfFlatPicture(const BLOB &blob)
static QVariant metaDataValue(IPropertyStore *content, const PROPERTYKEY &key)
static const PROPERTYKEY PROP_KEY_NULL
static void setUInt32Property(IPropertyStore *content, REFPROPERTYKEY key, quint32 value)
static void setUInt64Property(IPropertyStore *content, REFPROPERTYKEY key, quint64 value)
static QVariant convertValue(const PROPVARIANT &var)
static void setFileTimeProperty(IPropertyStore *content, REFPROPERTYKEY key, const FILETIME *ft)
static REFPROPERTYKEY propertyKeyForMetaDataKey(QMediaMetaData::Key key)
static void setStringProperty(IPropertyStore *content, REFPROPERTYKEY key, const QString &value)