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
4#include <qmediametadata.h>
5
6#include <QtCore/qdatetime.h>
7#include <QtCore/qtimezone.h>
8#include <QtGui/qimage.h>
9#include <QtCore/quuid.h>
10#include <QtMultimedia/private/qmediametadata_p.h>
11#include <QtMultimedia/private/qwindowsmultimediautils_p.h>
12
13#include <guiddef.h>
14#include <cguid.h>
15#include <mfapi.h>
16#include <mfidl.h>
17#include <propvarutil.h>
18#include <propkey.h>
19
20
21#include "mfmetadata_p.h"
22
23//#define DEBUG_MEDIAFOUNDATION
24
25static const PROPERTYKEY PROP_KEY_NULL = {GUID_NULL, 0};
26
27static QVariant convertValue(const PROPVARIANT& var)
28{
29 QVariant value;
30 switch (var.vt) {
31 case VT_LPWSTR:
32 value = QString::fromUtf16(reinterpret_cast<const char16_t *>(var.pwszVal));
33 break;
34 case VT_UI4:
35 value = uint(var.ulVal);
36 break;
37 case VT_UI8:
38 value = qulonglong(var.uhVal.QuadPart);
39 break;
40 case VT_BOOL:
41 value = bool(var.boolVal);
42 break;
43 case VT_FILETIME:
44 SYSTEMTIME t;
45 if (!FileTimeToSystemTime(&var.filetime, &t))
46 break;
47
48 value = QDateTime(QDate(t.wYear, t.wMonth, t.wDay),
49 QTime(t.wHour, t.wMinute, t.wSecond, t.wMilliseconds),
50 QTimeZone(QTimeZone::UTC));
51 break;
52 case VT_STREAM:
53 {
54 STATSTG stat;
55 if (FAILED(var.pStream->Stat(&stat, STATFLAG_NONAME)))
56 break;
57 void *data = malloc(stat.cbSize.QuadPart);
58 ULONG read = 0;
59 if (FAILED(var.pStream->Read(data, stat.cbSize.QuadPart, &read))) {
60 free(data);
61 break;
62 }
63 value = QImage::fromData((const uchar*)data, read);
64 free(data);
65 }
66 break;
67 case VT_VECTOR | VT_LPWSTR:
68 QStringList vList;
69 for (ULONG i = 0; i < var.calpwstr.cElems; ++i)
70 vList.append(QString::fromUtf16(reinterpret_cast<const char16_t *>(var.calpwstr.pElems[i])));
71 value = vList;
72 break;
73 }
74 return value;
75}
76
77static QVariant metaDataValue(IPropertyStore *content, const PROPERTYKEY &key)
78{
79 QVariant value;
80
81 PROPVARIANT var;
82 PropVariantInit(&var);
83 HRESULT hr = S_FALSE;
84 if (content)
85 hr = content->GetValue(key, &var);
86
87 if (SUCCEEDED(hr)) {
88 value = convertValue(var);
89
90 // some metadata needs to be reformatted
91 if (value.isValid() && content) {
92 if (key == PKEY_Media_ClassPrimaryID /*QMediaMetaData::MediaType*/) {
93 QString v = value.toString();
94 if (v == QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}"))
95 value = QStringLiteral("Music");
96 else if (v == QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}"))
97 value = QStringLiteral("Video");
98 else if (v == QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}"))
99 value = QStringLiteral("Audio");
100 else if (v == QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}"))
101 value = QStringLiteral("Other");
102 } else if (key == PKEY_Media_Duration) {
103 // duration is provided in 100-nanosecond units, convert to milliseconds
104 value = (value.toLongLong() + 10000) / 10000;
105 } else if (key == PKEY_Video_Compression) {
106 value = int(QWindowsMultimediaUtils::codecForVideoFormat(value.toUuid()));
107 } else if (key == PKEY_Audio_Format) {
108 value = int(QWindowsMultimediaUtils::codecForAudioFormat(value.toUuid()));
109 } else if (key == PKEY_Video_FrameHeight /*Resolution*/) {
110 QSize res;
111 res.setHeight(value.toUInt());
112 if (content && SUCCEEDED(content->GetValue(PKEY_Video_FrameWidth, &var)))
113 res.setWidth(convertValue(var).toUInt());
114 value = res;
115 } else if (key == PKEY_Video_Orientation) {
116 uint orientation = 0;
117 if (content && SUCCEEDED(content->GetValue(PKEY_Video_Orientation, &var)))
118 orientation = convertValue(var).toUInt();
119 value = orientation;
120 } else if (key == PKEY_Video_FrameRate) {
121 value = value.toReal() / 1000.f;
122 }
123 }
124 }
125
126 PropVariantClear(&var);
127 return value;
128}
129
130QMediaMetaData MFMetaData::fromNative(IMFMediaSource* mediaSource)
131{
132 QMediaMetaData metaData;
133
134 IPropertyStore *content = nullptr;
135 if (!SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&content))))
136 return metaData;
137
138 Q_ASSERT(content);
139 DWORD cProps;
140 if (SUCCEEDED(content->GetCount(&cProps))) {
141 for (DWORD i = 0; i < cProps; i++)
142 {
143 PROPERTYKEY key;
144 if (FAILED(content->GetAt(i, &key)))
145 continue;
146 QMediaMetaData::Key mediaKey;
147 if (key == PKEY_Author) {
148 mediaKey = QMediaMetaData::Author;
149 } else if (key == PKEY_Title) {
150 mediaKey = QMediaMetaData::Title;
151// } else if (key == PKEY_Media_SubTitle) {
152// mediaKey = QMediaMetaData::SubTitle;
153// } else if (key == PKEY_ParentalRating) {
154// mediaKey = QMediaMetaData::ParentalRating;
155 } else if (key == PKEY_Media_EncodingSettings) {
156 mediaKey = QMediaMetaData::Description;
157 } else if (key == PKEY_Copyright) {
158 mediaKey = QMediaMetaData::Copyright;
159 } else if (key == PKEY_Comment) {
160 mediaKey = QMediaMetaData::Comment;
161 } else if (key == PKEY_Media_ProviderStyle) {
162 mediaKey = QMediaMetaData::Genre;
163 } else if (key == PKEY_Media_DateEncoded) {
164 mediaKey = QMediaMetaData::Date;
165// } else if (key == PKEY_Rating) {
166// mediaKey = QMediaMetaData::UserRating;
167// } else if (key == PKEY_Keywords) {
168// mediaKey = QMediaMetaData::Keywords;
169 } else if (key == PKEY_Language) {
170 mediaKey = QMediaMetaData::Language;
171 } else if (key == PKEY_Media_Publisher) {
172 mediaKey = QMediaMetaData::Publisher;
173 } else if (key == PKEY_Media_ClassPrimaryID) {
174 mediaKey = QMediaMetaData::MediaType;
175 } else if (key == PKEY_Media_Duration) {
176 mediaKey = QMediaMetaData::Duration;
177 } else if (key == PKEY_Audio_EncodingBitrate) {
178 mediaKey = QMediaMetaData::AudioBitRate;
179 } else if (key == PKEY_Audio_Format) {
180 mediaKey = QMediaMetaData::AudioCodec;
181// } else if (key == PKEY_Media_AverageLevel) {
182// mediaKey = QMediaMetaData::AverageLevel;
183// } else if (key == PKEY_Audio_ChannelCount) {
184// mediaKey = QMediaMetaData::ChannelCount;
185// } else if (key == PKEY_Audio_PeakValue) {
186// mediaKey = QMediaMetaData::PeakValue;
187// } else if (key == PKEY_Audio_SampleRate) {
188// mediaKey = QMediaMetaData::SampleRate;
189 } else if (key == PKEY_Music_AlbumTitle) {
190 mediaKey = QMediaMetaData::AlbumTitle;
191 } else if (key == PKEY_Music_AlbumArtist) {
192 mediaKey = QMediaMetaData::AlbumArtist;
193 } else if (key == PKEY_Music_Artist) {
194 mediaKey = QMediaMetaData::ContributingArtist;
195 } else if (key == PKEY_Music_Composer) {
196 mediaKey = QMediaMetaData::Composer;
197// } else if (key == PKEY_Music_Conductor) {
198// mediaKey = QMediaMetaData::Conductor;
199// } else if (key == PKEY_Music_Lyrics) {
200// mediaKey = QMediaMetaData::Lyrics;
201// } else if (key == PKEY_Music_Mood) {
202// mediaKey = QMediaMetaData::Mood;
203 } else if (key == PKEY_Music_TrackNumber) {
204 mediaKey = QMediaMetaData::TrackNumber;
205 } else if (key == PKEY_Music_Genre) {
206 mediaKey = QMediaMetaData::Genre;
207 } else if (key == PKEY_ThumbnailStream) {
208 QVariant val = metaDataValue(content, key);
209 if (val.canConvert<QImage>())
210 QtMultimediaPrivate::setCoverArtImage(metaData, val.value<QImage>());
211 continue;
212 } else if (key == PKEY_Video_FrameHeight) {
213 mediaKey = QMediaMetaData::Resolution;
214 } else if (key == PKEY_Video_Orientation) {
215 mediaKey = QMediaMetaData::Orientation;
216 } else if (key == PKEY_Video_FrameRate) {
217 mediaKey = QMediaMetaData::VideoFrameRate;
218 } else if (key == PKEY_Video_EncodingBitrate) {
219 mediaKey = QMediaMetaData::VideoBitRate;
220 } else if (key == PKEY_Video_Compression) {
221 mediaKey = QMediaMetaData::VideoCodec;
222// } else if (key == PKEY_Video_Director) {
223// mediaKey = QMediaMetaData::Director;
224// } else if (key == PKEY_Media_Writer) {
225// mediaKey = QMediaMetaData::Writer;
226 } else {
227 continue;
228 }
229 metaData.insert(mediaKey, metaDataValue(content, key));
230 }
231 }
232
233 content->Release();
234
235 return metaData;
236}
237
238static REFPROPERTYKEY propertyKeyForMetaDataKey(QMediaMetaData::Key key)
239{
240 switch (key) {
241 case QMediaMetaData::Key::Title:
242 return PKEY_Title;
243 case QMediaMetaData::Key::Author:
244 return PKEY_Author;
245 case QMediaMetaData::Key::Comment:
246 return PKEY_Comment;
247 case QMediaMetaData::Key::Genre:
248 return PKEY_Music_Genre;
249 case QMediaMetaData::Key::Copyright:
250 return PKEY_Copyright;
251 case QMediaMetaData::Key::Publisher:
252 return PKEY_Media_Publisher;
253 case QMediaMetaData::Key::Url:
254 return PKEY_Media_AuthorUrl;
255 case QMediaMetaData::Key::AlbumTitle:
256 return PKEY_Music_AlbumTitle;
257 case QMediaMetaData::Key::AlbumArtist:
258 return PKEY_Music_AlbumArtist;
259 case QMediaMetaData::Key::TrackNumber:
260 return PKEY_Music_TrackNumber;
261 case QMediaMetaData::Key::Date:
262 return PKEY_Media_DateEncoded;
263 case QMediaMetaData::Key::Composer:
264 return PKEY_Music_Composer;
265 case QMediaMetaData::Key::Duration:
266 return PKEY_Media_Duration;
267 case QMediaMetaData::Key::Language:
268 return PKEY_Language;
269 case QMediaMetaData::Key::Description:
270 return PKEY_Media_EncodingSettings;
271 case QMediaMetaData::Key::AudioBitRate:
272 return PKEY_Audio_EncodingBitrate;
273 case QMediaMetaData::Key::ContributingArtist:
274 return PKEY_Music_Artist;
275#if QT_DEPRECATED_SINCE(6, 12)
276 case QtMultimediaPrivate::deprecatedThumbnailImage:
277#endif
278 case QMediaMetaData::Key::CoverArtImage:
279 return PKEY_ThumbnailStream;
280 case QMediaMetaData::Key::Orientation:
281 return PKEY_Video_Orientation;
282 case QMediaMetaData::Key::VideoFrameRate:
283 return PKEY_Video_FrameRate;
284 case QMediaMetaData::Key::VideoBitRate:
285 return PKEY_Video_EncodingBitrate;
286 case QMediaMetaData::MediaType:
287 return PKEY_Media_ClassPrimaryID;
288 default:
289 return PROP_KEY_NULL;
290 }
291}
292
293static void setStringProperty(IPropertyStore *content, REFPROPERTYKEY key, const QString &value)
294{
295 PROPVARIANT propValue = {};
296 if (SUCCEEDED(InitPropVariantFromString(reinterpret_cast<LPCWSTR>(value.utf16()), &propValue))) {
297 if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
298 content->SetValue(key, propValue);
299 PropVariantClear(&propValue);
300 }
301}
302
303static void setUInt32Property(IPropertyStore *content, REFPROPERTYKEY key, quint32 value)
304{
305 PROPVARIANT propValue = {};
306 if (SUCCEEDED(InitPropVariantFromUInt32(ULONG(value), &propValue))) {
307 if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
308 content->SetValue(key, propValue);
309 PropVariantClear(&propValue);
310 }
311}
312
313static void setUInt64Property(IPropertyStore *content, REFPROPERTYKEY key, quint64 value)
314{
315 PROPVARIANT propValue = {};
316 if (SUCCEEDED(InitPropVariantFromUInt64(ULONGLONG(value), &propValue))) {
317 if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
318 content->SetValue(key, propValue);
319 PropVariantClear(&propValue);
320 }
321}
322
323static void setFileTimeProperty(IPropertyStore *content, REFPROPERTYKEY key, const FILETIME *ft)
324{
325 PROPVARIANT propValue = {};
326 if (SUCCEEDED(InitPropVariantFromFileTime(ft, &propValue))) {
327 if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue)))
328 content->SetValue(key, propValue);
329 PropVariantClear(&propValue);
330 }
331}
332
333void MFMetaData::toNative(const QMediaMetaData &metaData, IPropertyStore *content)
334{
335 if (content) {
336
337 for (const auto &key : metaData.keys()) {
338
339 QVariant value = metaData.value(key);
340
341 if (key == QMediaMetaData::Key::MediaType) {
342
343 QString strValue = metaData.stringValue(key);
344 QString v;
345
346 // Sets property to one of the MediaClassPrimaryID values defined by Microsoft:
347 // https://docs.microsoft.com/en-us/windows/win32/wmformat/wm-mediaprimaryid
348 if (strValue == QLatin1String("Music"))
349 v = QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}");
350 else if (strValue == QLatin1String("Video"))
351 v = QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}");
352 else if (strValue == QLatin1String("Audio"))
353 v = QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}");
354 else
355 v = QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}");
356
357 setStringProperty(content, PKEY_Media_ClassPrimaryID, v);
358
359 } else if (key == QMediaMetaData::Key::Duration) {
360
361 setUInt64Property(content, PKEY_Media_Duration, value.toULongLong() * 10000);
362
363 } else if (key == QMediaMetaData::Key::Resolution) {
364
365 QSize res = value.toSize();
366 setUInt32Property(content, PKEY_Video_FrameWidth, quint32(res.width()));
367 setUInt32Property(content, PKEY_Video_FrameHeight, quint32(res.height()));
368
369 } else if (key == QMediaMetaData::Key::Orientation) {
370
371 setUInt32Property(content, PKEY_Video_Orientation, value.toUInt());
372
373 } else if (key == QMediaMetaData::Key::VideoFrameRate) {
374
375 qreal fps = value.toReal();
376 setUInt32Property(content, PKEY_Video_FrameRate, quint32(fps * 1000));
377
378 } else if (key == QMediaMetaData::Key::TrackNumber) {
379
380 setUInt32Property(content, PKEY_Music_TrackNumber, value.toUInt());
381
382 } else if (key == QMediaMetaData::Key::AudioBitRate) {
383
384 setUInt32Property(content, PKEY_Audio_EncodingBitrate, value.toUInt());
385
386 } else if (key == QMediaMetaData::Key::VideoBitRate) {
387
388 setUInt32Property(content, PKEY_Video_EncodingBitrate, value.toUInt());
389
390 } else if (key == QMediaMetaData::Key::Date) {
391
392 // Convert QDateTime to FILETIME by converting to 100-nsecs since
393 // 01/01/1970 UTC and adding the difference from 1601 to 1970.
394 ULARGE_INTEGER t = {};
395 t.QuadPart = ULONGLONG(value.toDateTime().toUTC().toMSecsSinceEpoch() * 10000
396 + 116444736000000000LL);
397
398 FILETIME ft = {};
399 ft.dwHighDateTime = t.HighPart;
400 ft.dwLowDateTime = t.LowPart;
401
402 setFileTimeProperty(content, PKEY_Media_DateEncoded, &ft);
403
404 } else {
405
406 // By default use as string and let PSCoerceToCanonicalValue()
407 // do validation and type conversion.
408 REFPROPERTYKEY propKey = propertyKeyForMetaDataKey(key);
409
410 if (propKey != PROP_KEY_NULL) {
411 QString strValue = metaData.stringValue(key);
412 if (!strValue.isEmpty())
413 setStringProperty(content, propKey, strValue);
414 }
415 }
416 }
417 }
418}
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)