diff options
author | André de la Rocha <andre.rocha@qt.io> | 2021-06-11 20:58:19 +0200 |
---|---|---|
committer | André de la Rocha <andre.rocha@qt.io> | 2021-06-16 16:51:45 +0200 |
commit | f6dc1df986dacbc018a482f7e21b93bda6fc69fc (patch) | |
tree | 0fc780f65086eae3cbc7195bd05c7d87e4b3b61e | |
parent | e1f0c82576325a220a9dd1775c7b28dff60905c9 (diff) |
Add metadata to captured media on Windows
Change-Id: I41957e8f8ba2d8ab59e206e14db349d6fbf13a6c
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r-- | src/multimedia/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/multimedia/platform/windows/common/mfmetadata.cpp (renamed from src/multimedia/platform/windows/player/mfmetadata.cpp) | 209 | ||||
-rw-r--r-- | src/multimedia/platform/windows/common/mfmetadata_p.h (renamed from src/multimedia/platform/windows/player/mfmetadata_p.h) | 1 | ||||
-rw-r--r-- | src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp | 47 | ||||
-rw-r--r-- | src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h | 7 | ||||
-rw-r--r-- | src/multimedia/platform/windows/player/mfplayersession.cpp | 2 |
6 files changed, 245 insertions, 23 deletions
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt index f60e13be7..47851c296 100644 --- a/src/multimedia/CMakeLists.txt +++ b/src/multimedia/CMakeLists.txt @@ -135,6 +135,7 @@ qt_internal_extend_target(Multimedia CONDITION QT_FEATURE_wmf platform/windows/audio/qwindowsaudiosource.cpp platform/windows/audio/qwindowsaudiosource_p.h platform/windows/audio/qwindowsaudiosink.cpp platform/windows/audio/qwindowsaudiosink_p.h platform/windows/audio/qwindowsaudioutils.cpp platform/windows/audio/qwindowsaudioutils_p.h + platform/windows/common/mfmetadata.cpp platform/windows/common/mfmetadata_p.h platform/windows/common/qwindowsmultimediautils.cpp platform/windows/common/qwindowsmultimediautils_p.h platform/windows/common/qwindowsiupointer_p.h platform/windows/decoder/mfaudiodecodercontrol.cpp platform/windows/decoder/mfaudiodecodercontrol_p.h @@ -147,7 +148,6 @@ qt_internal_extend_target(Multimedia CONDITION QT_FEATURE_wmf platform/windows/mfstream.cpp platform/windows/mfstream_p.h platform/windows/player/mfactivate.cpp platform/windows/player/mfactivate_p.h platform/windows/player/mfevrvideowindowcontrol.cpp platform/windows/player/mfevrvideowindowcontrol_p.h - platform/windows/player/mfmetadata.cpp platform/windows/player/mfmetadata_p.h platform/windows/player/mfplayercontrol.cpp platform/windows/player/mfplayercontrol_p.h platform/windows/player/mfplayersession.cpp platform/windows/player/mfplayersession_p.h platform/windows/player/mftvideo.cpp platform/windows/player/mftvideo_p.h diff --git a/src/multimedia/platform/windows/player/mfmetadata.cpp b/src/multimedia/platform/windows/common/mfmetadata.cpp index d64b3fa2f..587d757d6 100644 --- a/src/multimedia/platform/windows/player/mfmetadata.cpp +++ b/src/multimedia/platform/windows/common/mfmetadata.cpp @@ -43,9 +43,10 @@ #include <mfapi.h> #include <mfidl.h> +#include <propvarutil.h> +#include <propkey.h> #include "mfmetadata_p.h" -#include "Propkey.h" //#define DEBUG_MEDIAFOUNDATION @@ -176,21 +177,21 @@ static QVariant convertValue(const PROPVARIANT& var) return value; } -static QVariant metaDataValue(IPropertyStore *m_content, const PROPERTYKEY &key) +static QVariant metaDataValue(IPropertyStore *content, const PROPERTYKEY &key) { QVariant value; PROPVARIANT var; PropVariantInit(&var); HRESULT hr = S_FALSE; - if (m_content) - hr = m_content->GetValue(key, &var); + if (content) + hr = content->GetValue(key, &var); if (SUCCEEDED(hr)) { value = convertValue(var); // some metadata needs to be reformatted - if (value.isValid() && m_content) { + if (value.isValid() && content) { if (key == PKEY_Media_ClassPrimaryID /*QMediaMetaData::MediaType*/) { QString v = value.toString(); if (v == QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}")) @@ -211,12 +212,12 @@ static QVariant metaDataValue(IPropertyStore *m_content, const PROPERTYKEY &key) } else if (key == PKEY_Video_FrameHeight /*Resolution*/) { QSize res; res.setHeight(value.toUInt()); - if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_FrameWidth, &var))) + if (content && SUCCEEDED(content->GetValue(PKEY_Video_FrameWidth, &var))) res.setWidth(convertValue(var).toUInt()); value = res; } else if (key == PKEY_Video_Orientation) { uint orientation = 0; - if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_Orientation, &var))) + if (content && SUCCEEDED(content->GetValue(PKEY_Video_Orientation, &var))) orientation = convertValue(var).toUInt(); value = orientation; } else if (key == PKEY_Video_FrameRate) { @@ -233,17 +234,17 @@ QMediaMetaData MFMetaData::fromNative(IMFMediaSource* mediaSource) { QMediaMetaData metaData; - IPropertyStore *m_content = nullptr; - if (!SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&m_content)))) + IPropertyStore *content = nullptr; + if (!SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&content)))) return metaData; - Q_ASSERT(m_content); + Q_ASSERT(content); DWORD cProps; - if (SUCCEEDED(m_content->GetCount(&cProps))) { + if (SUCCEEDED(content->GetCount(&cProps))) { for (DWORD i = 0; i < cProps; i++) { PROPERTYKEY key; - if (FAILED(m_content->GetAt(i, &key))) + if (FAILED(content->GetAt(i, &key))) continue; QMediaMetaData::Key mediaKey; if (key == PKEY_Author) { @@ -325,11 +326,191 @@ QMediaMetaData MFMetaData::fromNative(IMFMediaSource* mediaSource) } else { continue; } - metaData.insert(mediaKey, metaDataValue(m_content, key)); + metaData.insert(mediaKey, metaDataValue(content, key)); } } - m_content->Release(); + content->Release(); return metaData; } + +static REFPROPERTYKEY propertyKeyForMetaDataKey(QMediaMetaData::Key key) +{ + switch (key) { + case QMediaMetaData::Key::Title: + return PKEY_Title; + case QMediaMetaData::Key::Author: + return PKEY_Author; + case QMediaMetaData::Key::Comment: + return PKEY_Comment; + case QMediaMetaData::Key::Genre: + return PKEY_Music_Genre; + case QMediaMetaData::Key::Copyright: + return PKEY_Copyright; + case QMediaMetaData::Key::Publisher: + return PKEY_Media_Publisher; + case QMediaMetaData::Key::Url: + return PKEY_Media_AuthorUrl; + case QMediaMetaData::Key::AlbumTitle: + return PKEY_Music_AlbumTitle; + case QMediaMetaData::Key::AlbumArtist: + return PKEY_Music_AlbumArtist; + case QMediaMetaData::Key::TrackNumber: + return PKEY_Music_TrackNumber; + case QMediaMetaData::Key::Date: + return PKEY_Media_DateEncoded; + case QMediaMetaData::Key::Composer: + return PKEY_Music_Composer; + case QMediaMetaData::Key::Duration: + return PKEY_Media_Duration; + case QMediaMetaData::Key::Language: + return PKEY_Language; + case QMediaMetaData::Key::Description: + return PKEY_Media_EncodingSettings; + case QMediaMetaData::Key::AudioBitRate: + return PKEY_Audio_EncodingBitrate; + case QMediaMetaData::Key::ContributingArtist: + return PKEY_Music_Artist; + case QMediaMetaData::Key::ThumbnailImage: + return PKEY_ThumbnailStream; + case QMediaMetaData::Key::Orientation: + return PKEY_Video_Orientation; + case QMediaMetaData::Key::VideoFrameRate: + return PKEY_Video_FrameRate; + case QMediaMetaData::Key::VideoBitRate: + return PKEY_Video_EncodingBitrate; + case QMediaMetaData::MediaType: + return PKEY_Media_ClassPrimaryID; + default: + return PKEY_Null; + } +} + +static void setStringProperty(IPropertyStore *content, REFPROPERTYKEY key, const QString &value) +{ + PROPVARIANT propValue = {}; + if (SUCCEEDED(InitPropVariantFromString(reinterpret_cast<LPCWSTR>(value.utf16()), &propValue))) { + if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue))) + content->SetValue(key, propValue); + PropVariantClear(&propValue); + } +} + +static void setUInt32Property(IPropertyStore *content, REFPROPERTYKEY key, quint32 value) +{ + PROPVARIANT propValue = {}; + if (SUCCEEDED(InitPropVariantFromUInt32(ULONG(value), &propValue))) { + if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue))) + content->SetValue(key, propValue); + PropVariantClear(&propValue); + } +} + +static void setUInt64Property(IPropertyStore *content, REFPROPERTYKEY key, quint64 value) +{ + PROPVARIANT propValue = {}; + if (SUCCEEDED(InitPropVariantFromUInt64(ULONGLONG(value), &propValue))) { + if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue))) + content->SetValue(key, propValue); + PropVariantClear(&propValue); + } +} + +static void setFileTimeProperty(IPropertyStore *content, REFPROPERTYKEY key, const FILETIME *ft) +{ + PROPVARIANT propValue = {}; + if (SUCCEEDED(InitPropVariantFromFileTime(ft, &propValue))) { + if (SUCCEEDED(PSCoerceToCanonicalValue(key, &propValue))) + content->SetValue(key, propValue); + PropVariantClear(&propValue); + } +} + +void MFMetaData::toNative(const QMediaMetaData &metaData, IPropertyStore *content) +{ + if (content) { + + for (const auto &key : metaData.keys()) { + + QVariant value = metaData.value(key); + + if (key == QMediaMetaData::Key::MediaType) { + + QString strValue = metaData.stringValue(key); + QString v; + + // Sets property to one of the MediaClassPrimaryID values defined by Microsoft: + // https://docs.microsoft.com/en-us/windows/win32/wmformat/wm-mediaprimaryid + if (strValue == QLatin1String("Music")) + v = QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}"); + else if (strValue == QLatin1String("Video")) + v = QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}"); + else if (strValue == QLatin1String("Audio")) + v = QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}"); + else + v = QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}"); + + setStringProperty(content, PKEY_Media_ClassPrimaryID, v); + + } else if (key == QMediaMetaData::Key::Duration) { + + setUInt64Property(content, PKEY_Media_Duration, value.toULongLong() * 10000); + + } else if (key == QMediaMetaData::Key::Resolution) { + + QSize res = value.toSize(); + setUInt32Property(content, PKEY_Video_FrameWidth, quint32(res.width())); + setUInt32Property(content, PKEY_Video_FrameHeight, quint32(res.height())); + + } else if (key == QMediaMetaData::Key::Orientation) { + + setUInt32Property(content, PKEY_Video_Orientation, value.toUInt()); + + } else if (key == QMediaMetaData::Key::VideoFrameRate) { + + qreal fps = value.toReal(); + setUInt32Property(content, PKEY_Video_FrameRate, quint32(fps * 1000)); + + } else if (key == QMediaMetaData::Key::TrackNumber) { + + setUInt32Property(content, PKEY_Music_TrackNumber, value.toUInt()); + + } else if (key == QMediaMetaData::Key::AudioBitRate) { + + setUInt32Property(content, PKEY_Audio_EncodingBitrate, value.toUInt()); + + } else if (key == QMediaMetaData::Key::VideoBitRate) { + + setUInt32Property(content, PKEY_Video_EncodingBitrate, value.toUInt()); + + } else if (key == QMediaMetaData::Key::Date) { + + // Convert QDateTime to FILETIME by converting to 100-nsecs since + // 01/01/1970 UTC and adding the difference from 1601 to 1970. + ULARGE_INTEGER t = {}; + t.QuadPart = ULONGLONG(value.toDateTime().toUTC().toMSecsSinceEpoch() * 10000 + + 116444736000000000LL); + + FILETIME ft = {}; + ft.dwHighDateTime = t.HighPart; + ft.dwLowDateTime = t.LowPart; + + setFileTimeProperty(content, PKEY_Media_DateEncoded, &ft); + + } else { + + // By default use as string and let PSCoerceToCanonicalValue() + // do validation and type conversion. + REFPROPERTYKEY propKey = propertyKeyForMetaDataKey(key); + + if (propKey != PKEY_Null) { + QString strValue = metaData.stringValue(key); + if (!strValue.isEmpty()) + setStringProperty(content, propKey, strValue); + } + } + } + } +} + diff --git a/src/multimedia/platform/windows/player/mfmetadata_p.h b/src/multimedia/platform/windows/common/mfmetadata_p.h index 813b42cf3..d1846e9c5 100644 --- a/src/multimedia/platform/windows/player/mfmetadata_p.h +++ b/src/multimedia/platform/windows/common/mfmetadata_p.h @@ -60,6 +60,7 @@ class MFMetaData { public: static QMediaMetaData fromNative(IMFMediaSource* mediaSource); + static void toNative(const QMediaMetaData &metaData, IPropertyStore *content); }; #endif diff --git a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp index 1ecd8af0b..6018c8974 100644 --- a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp +++ b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder.cpp @@ -41,9 +41,11 @@ #include "qwindowsmediadevicesession_p.h" #include "qwindowsmediacapture_p.h" +#include "mfmetadata_p.h" #include <QtCore/QUrl> #include <QtCore/QMimeType> #include <Mferror.h> +#include <shobjidl.h> QT_BEGIN_NAMESPACE @@ -131,18 +133,18 @@ void QWindowsMediaEncoder::setState(QMediaRecorder::RecorderState state) const QString path = (m_outputLocation.scheme() == QLatin1String("file") ? m_outputLocation.path() : m_outputLocation.toString()); - QString fileName = m_storageLocation.generateFileName(path, audioOnly - ? QWindowsStorageLocation::Audio - : QWindowsStorageLocation::Video, - QLatin1String("clip_"), - m_settings.mimeType().preferredSuffix()); + m_fileName = m_storageLocation.generateFileName(path, audioOnly + ? QWindowsStorageLocation::Audio + : QWindowsStorageLocation::Video, + QLatin1String("clip_"), + m_settings.mimeType().preferredSuffix()); - if (m_mediaDeviceSession->startRecording(fileName, audioOnly)) { + if (m_mediaDeviceSession->startRecording(m_fileName, audioOnly)) { m_state = QMediaRecorder::RecordingState; m_lastStatus = QMediaRecorder::StartingStatus; - actualLocationChanged(QUrl::fromLocalFile(fileName)); + actualLocationChanged(QUrl::fromLocalFile(m_fileName)); stateChanged(m_state); statusChanged(m_lastStatus); @@ -203,6 +205,35 @@ void QWindowsMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *sessi onCameraChanged(); } +void QWindowsMediaEncoder::setMetaData(const QMediaMetaData &metaData) +{ + m_metaData = metaData; +} + +QMediaMetaData QWindowsMediaEncoder::metaData() const +{ + return m_metaData; +} + +void QWindowsMediaEncoder::saveMetadata() +{ + if (!m_metaData.isEmpty()) { + + const QString nativeFileName = QDir::toNativeSeparators(m_fileName); + + IPropertyStore *store = nullptr; + + if (SUCCEEDED(SHGetPropertyStoreFromParsingName(reinterpret_cast<LPCWSTR>(nativeFileName.utf16()), + nullptr, GPS_READWRITE, IID_PPV_ARGS(&store)))) { + + MFMetaData::toNative(m_metaData, store); + + store->Commit(); + store->Release(); + } + } +} + void QWindowsMediaEncoder::onDurationChanged(qint64 duration) { m_duration = duration; @@ -235,6 +266,8 @@ void QWindowsMediaEncoder::onRecordingStarted() void QWindowsMediaEncoder::onRecordingStopped() { + saveMetadata(); + auto lastState = m_state; auto lastStatus = m_lastStatus; m_state = QMediaRecorder::StoppedState; diff --git a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h index ffa73b428..315e26e7e 100644 --- a/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h +++ b/src/multimedia/platform/windows/mediacapture/qwindowsmediaencoder_p.h @@ -79,6 +79,9 @@ public: void setEncoderSettings(const QMediaEncoderSettings &settings) override; + void setMetaData(const QMediaMetaData &metaData) override; + QMediaMetaData metaData() const override; + void setCaptureSession(QPlatformMediaCaptureSession *session); public Q_SLOTS: @@ -92,6 +95,8 @@ private Q_SLOTS: void onStreamingError(int errorCode); private: + void saveMetadata(); + QWindowsMediaCaptureService *m_captureService = nullptr; QWindowsMediaDeviceSession *m_mediaDeviceSession = nullptr; QUrl m_outputLocation; @@ -99,6 +104,8 @@ private: QMediaRecorder::Status m_lastStatus = QMediaRecorder::StoppedStatus; QMediaEncoderSettings m_settings; QWindowsStorageLocation m_storageLocation; + QString m_fileName; + QMediaMetaData m_metaData; qint64 m_duration = 0; }; diff --git a/src/multimedia/platform/windows/player/mfplayersession.cpp b/src/multimedia/platform/windows/player/mfplayersession.cpp index 1fa05c33a..50a022fc3 100644 --- a/src/multimedia/platform/windows/player/mfplayersession.cpp +++ b/src/multimedia/platform/windows/player/mfplayersession.cpp @@ -53,7 +53,7 @@ #include "mfplayercontrol_p.h" #include "mfevrvideowindowcontrol_p.h" #include "mfvideorenderercontrol_p.h" -#include "mfmetadata_p.h" +#include <private/mfmetadata_p.h> #include "mfplayersession_p.h" #include <mferror.h> |