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