diff options
author | Doris Verria <doris.verria@qt.io> | 2021-06-18 13:11:21 +0200 |
---|---|---|
committer | Doris Verria <doris.verria@qt.io> | 2021-06-23 13:11:09 +0200 |
commit | c91afc21c068d8c68d51bf3acb98e8a385b7db8c (patch) | |
tree | 28d3f65f5dc1f0cef0e88376eb61de65b9dfcd16 | |
parent | a6e014eb088cb9b7bf55bf87c814e75cd4037dc2 (diff) |
Add metadata to audio and video capturing on darwin
Add and retrieve common and format specific meta data.
Pick-to: 6.2
Change-Id: I3e3f0ffdff52b8cf6e2079fdc1536b130c65773f
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
-rw-r--r-- | src/multimedia/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/multimedia/platform/darwin/camera/avfmediaassetwriter.mm | 8 | ||||
-rw-r--r-- | src/multimedia/platform/darwin/camera/avfmediaencoder.mm | 10 | ||||
-rw-r--r-- | src/multimedia/platform/darwin/camera/avfmediaencoder_p.h | 6 | ||||
-rw-r--r-- | src/multimedia/platform/darwin/common/avfmetadata.mm | 370 | ||||
-rw-r--r-- | src/multimedia/platform/darwin/common/avfmetadata_p.h (renamed from src/multimedia/platform/darwin/mediaplayer/avfmetadata_p.h) | 6 | ||||
-rw-r--r-- | src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm | 2 | ||||
-rw-r--r-- | src/multimedia/platform/darwin/mediaplayer/avfmetadata.mm | 140 | ||||
-rw-r--r-- | tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp | 89 |
9 files changed, 488 insertions, 145 deletions
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt index ad9eb2767..145740410 100644 --- a/src/multimedia/CMakeLists.txt +++ b/src/multimedia/CMakeLists.txt @@ -360,7 +360,7 @@ qt_internal_extend_target(Multimedia CONDITION APPLE AND NOT WATCHOS platform/darwin/audio/qcoreaudioutils.mm platform/darwin/audio/qcoreaudioutils_p.h platform/darwin/mediaplayer/avfdisplaylink.mm platform/darwin/mediaplayer/avfdisplaylink_p.h platform/darwin/mediaplayer/avfmediaplayer.mm platform/darwin/mediaplayer/avfmediaplayer_p.h - platform/darwin/mediaplayer/avfmetadata.mm platform/darwin/mediaplayer/avfmetadata_p.h + platform/darwin/common/avfmetadata.mm platform/darwin/common/avfmetadata_p.h platform/darwin/mediaplayer/avfvideorenderercontrol.mm platform/darwin/mediaplayer/avfvideorenderercontrol_p.h platform/darwin/avfvideosink.mm platform/darwin/avfvideosink_p.h platform/darwin/avfvideobuffer.mm platform/darwin/avfvideobuffer_p.h diff --git a/src/multimedia/platform/darwin/camera/avfmediaassetwriter.mm b/src/multimedia/platform/darwin/camera/avfmediaassetwriter.mm index b7a3b3def..0523a7931 100644 --- a/src/multimedia/platform/darwin/camera/avfmediaassetwriter.mm +++ b/src/multimedia/platform/darwin/camera/avfmediaassetwriter.mm @@ -44,6 +44,7 @@ #include "avfcamerasession_p.h" #include "avfcameradebug_p.h" #include <private/qdarwinformatsinfo_p.h> +#include <private/avfmetadata_p.h> #include <QtCore/qmetaobject.h> #include <QtCore/qatomic.h> @@ -201,10 +202,17 @@ using AVFAtomicInt64 = QAtomicInteger<qint64>; if (m_cameraWriterInput) m_cameraWriterInput.data().transform = transform; + [self setMetaData:fileType]; + // Ready to start ... return true; } +- (void)setMetaData:(AVFileType)fileType +{ + m_assetWriter.data().metadata = AVFMetaData::toAVMetadataForFormat(m_delegate->metaData(), fileType); +} + - (void)start { [self setQueues]; diff --git a/src/multimedia/platform/darwin/camera/avfmediaencoder.mm b/src/multimedia/platform/darwin/camera/avfmediaencoder.mm index 96fa82503..ac182fe00 100644 --- a/src/multimedia/platform/darwin/camera/avfmediaencoder.mm +++ b/src/multimedia/platform/darwin/camera/avfmediaencoder.mm @@ -414,6 +414,16 @@ QMediaEncoderSettings AVFMediaEncoder::encoderSettings() const return s; } +void AVFMediaEncoder::setMetaData(const QMediaMetaData &metaData) +{ + m_metaData = metaData; +} + +QMediaMetaData AVFMediaEncoder::metaData() const +{ + return m_metaData; +} + void AVFMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *session) { AVFCameraService *captureSession = static_cast<AVFCameraService *>(session); diff --git a/src/multimedia/platform/darwin/camera/avfmediaencoder_p.h b/src/multimedia/platform/darwin/camera/avfmediaencoder_p.h index d76df772f..7c57768b0 100644 --- a/src/multimedia/platform/darwin/camera/avfmediaencoder_p.h +++ b/src/multimedia/platform/darwin/camera/avfmediaencoder_p.h @@ -58,6 +58,7 @@ #include <private/qplatformmediaencoder_p.h> #include <private/qvideooutputorientationhandler_p.h> +#include <QtMultimedia/qmediametadata.h> #include <QtCore/qglobal.h> #include <QtCore/qurl.h> @@ -90,6 +91,9 @@ public: void setEncoderSettings(const QMediaEncoderSettings &settings) override; QMediaEncoderSettings encoderSettings() const; + void setMetaData(const QMediaMetaData &) override; + QMediaMetaData metaData() const override; + AVFCameraService *cameraService() const { return m_service; } void setCaptureSession(QPlatformMediaCaptureSession *session); @@ -119,6 +123,8 @@ private: QMediaRecorder::Status m_lastStatus; QMediaEncoderSettings m_settings; + QMediaMetaData m_metaData; + NSDictionary *m_audioSettings; NSDictionary *m_videoSettings; QVideoOutputOrientationHandler m_orientationHandler; diff --git a/src/multimedia/platform/darwin/common/avfmetadata.mm b/src/multimedia/platform/darwin/common/avfmetadata.mm new file mode 100644 index 000000000..dac28143d --- /dev/null +++ b/src/multimedia/platform/darwin/common/avfmetadata.mm @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "avfmetadata_p.h" +#include <private/qdarwinformatsinfo_p.h> + +#include <QtCore/qbuffer.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qlocale.h> +#include <QtCore/qurl.h> +#include <QImage> + +#if QT_HAS_INCLUDE(<AppKit/AppKit.h>) +#include <AppKit/AppKit.h> +#endif + +#include <CoreFoundation/CoreFoundation.h> + +QT_USE_NAMESPACE + +struct AVMetadataIDs { + AVMetadataIdentifier common; + AVMetadataIdentifier iTunes; + AVMetadataIdentifier quickTime; + AVMetadataIdentifier ID3; +}; + +const AVMetadataIDs keyToAVMetaDataID[] = { + // Title + { AVMetadataCommonIdentifierTitle, AVMetadataIdentifieriTunesMetadataSongName, + AVMetadataIdentifierQuickTimeMetadataTitle, + AVMetadataIdentifierID3MetadataTitleDescription }, + // Author + { AVMetadataCommonIdentifierAuthor,AVMetadataIdentifieriTunesMetadataAuthor, + AVMetadataIdentifierQuickTimeMetadataAuthor, nil }, + // Comment + { nil, AVMetadataIdentifieriTunesMetadataUserComment, + AVMetadataIdentifierQuickTimeMetadataComment, AVMetadataIdentifierID3MetadataComments }, + // Description + { AVMetadataCommonIdentifierDescription,AVMetadataIdentifieriTunesMetadataDescription, + AVMetadataIdentifierQuickTimeMetadataDescription, nil }, + // Genre + { nil, AVMetadataIdentifieriTunesMetadataUserGenre, + AVMetadataIdentifierQuickTimeMetadataGenre, nil }, + // Date + { AVMetadataCommonIdentifierCreationDate, AVMetadataIdentifieriTunesMetadataReleaseDate, + AVMetadataIdentifierQuickTimeMetadataCreationDate, AVMetadataIdentifierID3MetadataDate }, + // Language + { AVMetadataCommonIdentifierLanguage, nil, nil, AVMetadataIdentifierID3MetadataLanguage }, + // Publisher + { AVMetadataCommonIdentifierPublisher, AVMetadataIdentifieriTunesMetadataPublisher, + AVMetadataIdentifierQuickTimeMetadataPublisher, AVMetadataIdentifierID3MetadataPublisher }, + // Copyright + { AVMetadataCommonIdentifierCopyrights, AVMetadataIdentifieriTunesMetadataCopyright, + AVMetadataIdentifierQuickTimeMetadataCopyright, AVMetadataIdentifierID3MetadataCopyright }, + // Url + { nil, nil, nil, AVMetadataIdentifierID3MetadataOfficialAudioSourceWebpage }, + // Duration + { nil, nil, nil, AVMetadataIdentifierID3MetadataLength }, + // MediaType + { AVMetadataCommonIdentifierType, nil, nil, AVMetadataIdentifierID3MetadataContentType }, + // FileFormat + { nil, nil, nil, AVMetadataIdentifierID3MetadataFileType }, + // AudioBitRate + { nil, nil, nil, nil }, + // AudioCodec + { nil, nil, nil, nil }, + // VideoBitRate + { nil, nil, nil, nil }, + // VideoCodec + { nil, nil, nil, nil }, + // VideoFrameRate + { nil, nil, AVMetadataIdentifierQuickTimeMetadataCameraFrameReadoutTime, nil }, + // AlbumTitle + { AVMetadataCommonIdentifierAlbumName, AVMetadataIdentifieriTunesMetadataAlbum, + AVMetadataIdentifierQuickTimeMetadataAlbum, AVMetadataIdentifierID3MetadataAlbumTitle }, + // AlbumArtist + { nil, AVMetadataIdentifieriTunesMetadataAlbumArtist, nil, nil }, + // ContributingArtist + { AVMetadataCommonIdentifierArtist, AVMetadataIdentifieriTunesMetadataArtist, + AVMetadataIdentifierQuickTimeMetadataArtist, nil }, + // TrackNumber + { nil, AVMetadataIdentifieriTunesMetadataTrackNumber, + nil, AVMetadataIdentifierID3MetadataTrackNumber }, + // Composer + { nil, AVMetadataIdentifieriTunesMetadataComposer, + AVMetadataIdentifierQuickTimeMetadataComposer, AVMetadataIdentifierID3MetadataComposer }, + // LeadPerformer + { nil, AVMetadataIdentifieriTunesMetadataPerformer, + AVMetadataIdentifierQuickTimeMetadataPerformer, AVMetadataIdentifierID3MetadataLeadPerformer }, + // ThumbnailImage + { nil, nil, nil, AVMetadataIdentifierID3MetadataAttachedPicture}, + // CoverArtImage + { AVMetadataCommonIdentifierArtwork, AVMetadataIdentifieriTunesMetadataCoverArt, + AVMetadataIdentifierQuickTimeMetadataArtwork, nil }, + // Orientation + { nil, nil, AVMetadataIdentifierQuickTimeMetadataDirectionFacing, nil }, + // Resolution + { nil, nil, nil, nil } +}; + +static AVMetadataIdentifier toIdentifier(QMediaMetaData::Key key, AVMetadataKeySpace keySpace) +{ + static_assert(sizeof(keyToAVMetaDataID)/sizeof(AVMetadataIDs) == QMediaMetaData::Key::Resolution + 1); + + AVMetadataIdentifier identifier = nil; + if ([keySpace isEqualToString:AVMetadataKeySpaceiTunes]) { + identifier = keyToAVMetaDataID[key].iTunes; + } else if ([keySpace isEqualToString:AVMetadataKeySpaceID3]) { + identifier = keyToAVMetaDataID[key].ID3; + } else if ([keySpace isEqualToString:AVMetadataKeySpaceQuickTimeMetadata]) { + identifier = keyToAVMetaDataID[key].quickTime; + } else { + identifier = keyToAVMetaDataID[key].common; + } + return identifier; +} + +static std::optional<QMediaMetaData::Key> toKey(AVMetadataItem *item) +{ + static_assert(sizeof(keyToAVMetaDataID)/sizeof(AVMetadataIDs) == QMediaMetaData::Key::Resolution + 1); + + // The item identifier may be different than the ones we support, + // so check by common key first, as it will get the metadata + // irrespective of the format. + AVMetadataKey commonKey = item.commonKey; + if (commonKey.length != 0) { + if ([commonKey isEqualToString:AVMetadataCommonKeyTitle]) { + return QMediaMetaData::Title; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyDescription]) { + return QMediaMetaData::Description; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyPublisher]) { + return QMediaMetaData::Publisher; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyCreationDate]) { + return QMediaMetaData::Date; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyType]) { + return QMediaMetaData::MediaType; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyLanguage]) { + return QMediaMetaData::Language; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyCopyrights]) { + return QMediaMetaData::Copyright; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyAlbumName]) { + return QMediaMetaData::AlbumTitle; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyAuthor]) { + return QMediaMetaData::Author; + } else if ([commonKey isEqualToString:AVMetadataCommonKeyArtist]) { + return QMediaMetaData::ContributingArtist; + } + } + + // Check by identifier if no common key found + // No need to check for the common keySpace since there's no common key + enum keySpaces { iTunes, QuickTime, ID3, Other } itemKeySpace; + itemKeySpace = Other; + AVMetadataKeySpace keySpace = [item keySpace]; + AVMetadataIdentifier identifier = [item identifier]; + + if ([keySpace isEqualToString:AVMetadataKeySpaceiTunes]) { + itemKeySpace = iTunes; + } else if ([keySpace isEqualToString:AVMetadataKeySpaceQuickTimeMetadata]) { + itemKeySpace = QuickTime; + } else if (([keySpace isEqualToString:AVMetadataKeySpaceID3])) { + itemKeySpace = ID3; + } + + for (int key = 0; key < QMediaMetaData::Resolution + 1; key++) { + AVMetadataIdentifier idForKey = nil; + switch (itemKeySpace) { + case iTunes: + idForKey = keyToAVMetaDataID[key].iTunes; + break; + case QuickTime: + idForKey = keyToAVMetaDataID[key].quickTime; + break; + case ID3: + idForKey = keyToAVMetaDataID[key].ID3; + break; + default: + break; + } + + if ([identifier isEqualToString:idForKey]) + return QMediaMetaData::Key(key); + } + + return std::nullopt; +} + +static QMediaMetaData fromAVMetadata(NSArray *metadataItems) +{ + QMediaMetaData metadata; + + for (AVMetadataItem* item in metadataItems) { + auto key = toKey(item); + if (!key) + continue; + + const QString value = QString::fromNSString([item stringValue]); + if (!value.isNull()) + metadata.insert(*key, value); + } + return metadata; +} + +QMediaMetaData AVFMetaData::fromAsset(AVAsset *asset) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + QMediaMetaData metadata = fromAVMetadata([asset metadata]); + + // add duration + const CMTime time = [asset duration]; + const qint64 duration = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f); + metadata.insert(QMediaMetaData::Duration, duration); + + return metadata; +} + +QMediaMetaData AVFMetaData::fromAssetTrack(AVAssetTrack *asset) +{ + QMediaMetaData metadata = fromAVMetadata([asset metadata]); + if (metadata.value(QMediaMetaData::Language).isNull()) { + auto *lang = asset.languageCode; + if (lang) + metadata.insert(QMediaMetaData::Language, QString::fromNSString(lang)); + } + return metadata; +} + +static AVMutableMetadataItem *setAVMetadataItemForKey(QMediaMetaData::Key key, const QVariant &value, + AVMetadataKeySpace keySpace = AVMetadataKeySpaceCommon) +{ + AVMetadataIdentifier identifier = toIdentifier(key, keySpace); + if (!identifier.length) + return nil; + + AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem]; + item.keySpace = keySpace; + item.identifier = identifier; + + switch (key) { + case QMediaMetaData::ThumbnailImage: + case QMediaMetaData::CoverArtImage: { +#if defined(Q_OS_MACOS) + QImage img = value.value<QImage>(); + if (!img.isNull()) { + QByteArray arr; + QBuffer buffer(&arr); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer); + NSData *data = arr.toNSData(); + NSImage *nsImg = [[NSImage alloc] initWithData:data]; + item.value = nsImg; + [nsImg release]; + } +#endif + break; + } + case QMediaMetaData::FileFormat: { + QMediaFormat::FileFormat qtFormat = value.value<QMediaFormat::FileFormat>(); + AVFileType avFormat = QDarwinFormatInfo::avFileTypeForContainerFormat(qtFormat); + item.value = avFormat; + break; + } + case QMediaMetaData::Language: { + QString lang = QLocale::languageToCode(value.value<QLocale::Language>()); + if (!lang.isEmpty()) + item.value = lang.toNSString(); + break; + } + default: { + switch (value.typeId()) { + case QMetaType::QString: { + item.value = value.toString().toNSString(); + break; + } + case QMetaType::Int: { + item.value = [NSNumber numberWithInt:value.toInt()]; + break; + } + case QMetaType::LongLong: { + item.value = [NSNumber numberWithLongLong:value.toLongLong()]; + break; + } + case QMetaType::Double: { + item.value = [NSNumber numberWithDouble:value.toDouble()]; + break; + } + case QMetaType::QDate: + case QMetaType::QDateTime: { + item.value = value.toDateTime().toNSDate(); + break; + } + case QMetaType::QUrl: { + item.value = value.toUrl().toNSURL(); + break; + } + default: + break; + } + } + } + + return item; +} + +NSMutableArray<AVMetadataItem *> *AVFMetaData::toAVMetadataForFormat(QMediaMetaData metadata, AVFileType format) +{ + NSMutableArray<AVMetadataKeySpace> *keySpaces = [NSMutableArray<AVMetadataKeySpace> array]; + if (format == AVFileTypeAppleM4A) { + [keySpaces addObject:AVMetadataKeySpaceiTunes]; + } else if (format == AVFileTypeMPEGLayer3) { + [keySpaces addObject:AVMetadataKeySpaceID3]; + [keySpaces addObject:AVMetadataKeySpaceiTunes]; + } else if (format == AVFileTypeQuickTimeMovie) { + [keySpaces addObject:AVMetadataKeySpaceQuickTimeMetadata]; + } else { + [keySpaces addObject:AVMetadataKeySpaceCommon]; + } + NSMutableArray<AVMetadataItem *> *avMetaDataArr = [NSMutableArray array]; + for (const auto &key : metadata.keys()) { + for (NSUInteger i = 0; i < [keySpaces count]; i++) { + const QVariant &value = metadata.value(key); + // set format-specific metadata + AVMetadataItem *item = setAVMetadataItemForKey(key, value, keySpaces[i]); + if (item) + [avMetaDataArr addObject:item]; + } + } + return avMetaDataArr; +} + diff --git a/src/multimedia/platform/darwin/mediaplayer/avfmetadata_p.h b/src/multimedia/platform/darwin/common/avfmetadata_p.h index 5af50a8ba..e983be531 100644 --- a/src/multimedia/platform/darwin/mediaplayer/avfmetadata_p.h +++ b/src/multimedia/platform/darwin/common/avfmetadata_p.h @@ -54,18 +54,18 @@ #include <QtMultimedia/QMediaMetaData> #include <QtCore/qvariant.h> -Q_FORWARD_DECLARE_OBJC_CLASS(AVAsset); -Q_FORWARD_DECLARE_OBJC_CLASS(AVAssetTrack); +#import <AVFoundation/AVFoundation.h> QT_BEGIN_NAMESPACE class AVFMediaPlayer; -class AVFMetaData : public QMediaMetaData +class AVFMetaData { public: static QMediaMetaData fromAsset(AVAsset *asset); static QMediaMetaData fromAssetTrack(AVAssetTrack *asset); + static NSMutableArray<AVMetadataItem *> *toAVMetadataForFormat(QMediaMetaData metaData, AVFileType format); }; QT_END_NAMESPACE diff --git a/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm b/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm index f60c61261..2d87a3e18 100644 --- a/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm +++ b/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm @@ -41,7 +41,7 @@ #include "avfmediaplayer_p.h" #include "avfvideorenderercontrol_p.h" #include <private/avfvideosink_p.h> -#include "avfmetadata_p.h" +#include <private/avfmetadata_p.h> #include "qaudiooutput.h" #include "qplatformaudiooutput_p.h" diff --git a/src/multimedia/platform/darwin/mediaplayer/avfmetadata.mm b/src/multimedia/platform/darwin/mediaplayer/avfmetadata.mm deleted file mode 100644 index c61c81252..000000000 --- a/src/multimedia/platform/darwin/mediaplayer/avfmetadata.mm +++ /dev/null @@ -1,140 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "avfmetadata_p.h" - -#include <QtMultimedia/qmediametadata.h> - -#import <AVFoundation/AVFoundation.h> - -QT_USE_NAMESPACE - -static std::optional<QMediaMetaData::Key> itemKey(AVMetadataItem *item) -{ - NSString *keyString = [item commonKey]; -// qDebug() << "metadatakey:" << QString::fromNSString(keyString) -// << QString::fromNSString([item identifier]) << QString::fromNSString([item description]); - - if (keyString.length != 0) { - if ([keyString isEqualToString:AVMetadataCommonKeyTitle]) { - return QMediaMetaData::Title; -// } else if ([keyString isEqualToString: AVMetadataCommonKeySubject]) { -// return QMediaMetaData::SubTitle; - } else if ([keyString isEqualToString: AVMetadataCommonKeyDescription]) { - return QMediaMetaData::Description; - } else if ([keyString isEqualToString: AVMetadataCommonKeyPublisher]) { - return QMediaMetaData::Publisher; - } else if ([keyString isEqualToString: AVMetadataCommonKeyCreationDate]) { - return QMediaMetaData::Date; - } else if ([keyString isEqualToString: AVMetadataCommonKeyType]) { - return QMediaMetaData::MediaType; - } else if ([keyString isEqualToString: AVMetadataCommonKeyLanguage]) { - return QMediaMetaData::Language; - } else if ([keyString isEqualToString: AVMetadataCommonKeyCopyrights]) { - return QMediaMetaData::Copyright; - } else if ([keyString isEqualToString: AVMetadataCommonKeyAlbumName]) { - return QMediaMetaData::AlbumTitle; - } else if ([keyString isEqualToString: AVMetadataCommonKeyAuthor]) { - return QMediaMetaData::Author; - } else if ([keyString isEqualToString: AVMetadataCommonKeyArtist]) { - return QMediaMetaData::ContributingArtist; -// } else if ([keyString isEqualToString: AVMetadataCommonKeyArtwork]) { -// return QMediaMetaData::PosterUrl; - } - } - - // check by identifier - NSString *id = [item identifier]; - if (!id) - return std::nullopt; - - if ([id isEqualToString: AVMetadataIdentifieriTunesMetadataUserComment] || - [id isEqualToString: AVMetadataIdentifierID3MetadataComments] || - [id isEqualToString: AVMetadataIdentifierQuickTimeMetadataComment]) - return QMediaMetaData::Comment; - - if ([id isEqualToString: AVMetadataIdentifierID3MetadataDate] || - [id isEqualToString: AVMetadataIdentifierISOUserDataDate]) - return QMediaMetaData::Date; - - - return std::nullopt; -} - -static QMediaMetaData fromAVMetaData(NSArray *metaDataItems) -{ - QMediaMetaData metaData; - - for (AVMetadataItem* item in metaDataItems) { - auto key = itemKey(item); - if (!key) - continue; - - const QString value = QString::fromNSString([item stringValue]); - if (!value.isNull()) - metaData.insert(*key, value); - } - return metaData; -} - -QMediaMetaData AVFMetaData::fromAsset(AVAsset *asset) -{ -#ifdef QT_DEBUG_AVF - qDebug() << Q_FUNC_INFO; -#endif - QMediaMetaData metaData = fromAVMetaData([asset metadata]); - - // add duration - const CMTime time = [asset duration]; - const qint64 duration = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f); - metaData.insert(QMediaMetaData::Duration, duration); - - return metaData; -} - -QMediaMetaData AVFMetaData::fromAssetTrack(AVAssetTrack *asset) -{ - QMediaMetaData metaData = fromAVMetaData([asset metadata]); - if (metaData.value(QMediaMetaData::Language).isNull()) { - auto *lang = asset.languageCode; - if (lang) - metaData.insert(QMediaMetaData::Language, QString::fromNSString(lang)); - } - return metaData; -} diff --git a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp index a656c1da0..4573c52b5 100644 --- a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp +++ b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp @@ -30,6 +30,8 @@ #include <QtTest/QtTest> #include <QtGui/QImageReader> +#include <QtCore/qurl.h> +#include <QtCore/qlocale.h> #include <QDebug> #include <private/qplatformcamera_p.h> @@ -41,6 +43,8 @@ #include <qobject.h> #include <qmediadevices.h> #include <qmediarecorder.h> +#include <qmediaplayer.h> +#include <qaudiooutput.h> QT_USE_NAMESPACE @@ -74,6 +78,9 @@ private slots: void testVideoRecording_data(); void testVideoRecording(); + + void testNativeMetadata(); + private: bool noCamera = false; }; @@ -456,6 +463,12 @@ void tst_QCameraBackend::testVideoRecording() recorder.setVideoResolution(320, 240); + // Insert metadata + QMediaMetaData metaData; + metaData.insert(QMediaMetaData::Author, QString::fromUtf8("Author")); + metaData.insert(QMediaMetaData::Date, QDateTime(QDateTime::currentDateTime())); + recorder.setMetaData(metaData); + QCOMPARE(recorder.status(), QMediaRecorder::StoppedStatus); camera->start(); @@ -474,6 +487,9 @@ void tst_QCameraBackend::testVideoRecording() QTRY_COMPARE(recorder.status(), QMediaRecorder::RecordingStatus); QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status()); QTest::qWait(200); + + QCOMPARE(recorder.metaData(), metaData); + recorderStatusSignal.clear(); recorder.stop(); bool foundFinalizingStatus = false; @@ -501,6 +517,79 @@ void tst_QCameraBackend::testVideoRecording() } } +void tst_QCameraBackend::testNativeMetadata() +{ + if (noCamera) + QSKIP("No camera available"); + + QMediaCaptureSession session; + QCameraDevice device = QMediaDevices::defaultVideoInput(); + QCamera camera(device); + session.setCamera(&camera); + + QMediaRecorder recorder; + session.setEncoder(&recorder); + + QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error, const QString &))); + QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); + + QCOMPARE(recorder.status(), QMediaRecorder::StoppedStatus); + + camera.start(); + if (device.isNull()) { + QVERIFY(!camera.isActive()); + return; + } + + QTRY_VERIFY(camera.isActive()); + QTRY_COMPARE(recorder.status(), QMediaRecorder::StoppedStatus); + + // Insert common metadata supported on all platforms + QMediaMetaData metaData; + metaData.insert(QMediaMetaData::Title, QString::fromUtf8("Title")); + metaData.insert(QMediaMetaData::Date, QDate::currentDate()); + metaData.insert(QMediaMetaData::Description, QString::fromUtf8("Description")); + + recorder.setMetaData(metaData); + + recorder.record(); + QTRY_COMPARE(recorder.status(), QMediaRecorder::RecordingStatus); + QTest::qWait(200); + + QCOMPARE(recorder.metaData(), metaData); + + recorder.stop(); + QTRY_COMPARE(recorder.status(), QMediaRecorder::StoppedStatus); + + QVERIFY(errorSignal.isEmpty()); + QVERIFY(recorderErrorSignal.isEmpty()); + + QString fileName = recorder.actualLocation().toLocalFile(); + QVERIFY(!fileName.isEmpty()); + QVERIFY(QFileInfo(fileName).size() > 0); + + // QMediaRecorder::metaData() can only test that QMediaMetaData is set properly on the recorder. + // Use QMediaPlayer to test that the native metadata is properly set on the track + QMediaPlayer player; + QAudioOutput output; + player.setAudioOutput(&output); + + QSignalSpy metadataChangedSpy(&player, SIGNAL(metaDataChanged())); + + player.setSource(QUrl::fromLocalFile(fileName)); + + QTRY_VERIFY(metadataChangedSpy.count() > 0); + + QCOMPARE(player.metaData().value(QMediaMetaData::Title).toString(), metaData.value(QMediaMetaData::Title).toString()); + QCOMPARE(player.metaData().value(QMediaMetaData::Date).toDateTime(), metaData.value(QMediaMetaData::Date).toDateTime()); + QCOMPARE(player.metaData().value(QMediaMetaData::Description).toString(), metaData.value(QMediaMetaData::Description).toString()); + + metadataChangedSpy.clear(); + + player.stop(); + QFile(fileName).remove(); +} + QTEST_MAIN(tst_QCameraBackend) #include "tst_qcamerabackend.moc" |