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
avfmetadata.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <qdarwinformatsinfo_p.h>
6#include <avfmediaplayer_p.h>
7
8#include <QtCore/qbuffer.h>
9#include <QtCore/qiodevice.h>
10#include <QtCore/qdatetime.h>
11#include <QtCore/qlocale.h>
12#include <QtCore/qurl.h>
13#include <QtCore/private/qcore_mac_p.h>
14#include <QImage>
15#include <QtMultimedia/qvideoframe.h>
16
17#if __has_include(<AppKit/AppKit.h>)
18#import <AppKit/AppKit.h>
19#endif
20
21#import <CoreFoundation/CoreFoundation.h>
22
23QT_USE_NAMESPACE
24
33
35 // Title
36 { AVMetadataCommonIdentifierTitle, AVMetadataIdentifieriTunesMetadataSongName,
37 AVMetadataIdentifierQuickTimeMetadataTitle,
38 AVMetadataIdentifierID3MetadataTitleDescription,
39 nil, AVMetadata3GPUserDataKeyTitle },
40 // Author
41 { AVMetadataCommonIdentifierAuthor,AVMetadataIdentifieriTunesMetadataAuthor,
42 AVMetadataIdentifierQuickTimeMetadataAuthor, nil,
43 AVMetadataQuickTimeUserDataKeyAuthor, AVMetadata3GPUserDataKeyAuthor },
44 // Comment
45 { nil, AVMetadataIdentifieriTunesMetadataUserComment,
46 AVMetadataIdentifierQuickTimeMetadataComment, AVMetadataIdentifierID3MetadataComments,
47 AVMetadataQuickTimeUserDataKeyComment, nil },
48 // Description
49 { AVMetadataCommonIdentifierDescription,AVMetadataIdentifieriTunesMetadataDescription,
50 AVMetadataIdentifierQuickTimeMetadataDescription, nil,
51 AVMetadataQuickTimeUserDataKeyDescription, AVMetadata3GPUserDataKeyDescription },
52 // Genre
53 { nil, AVMetadataIdentifieriTunesMetadataUserGenre,
54 AVMetadataIdentifierQuickTimeMetadataGenre, nil,
55 AVMetadataQuickTimeUserDataKeyGenre, AVMetadata3GPUserDataKeyGenre },
56 // Date
57 { AVMetadataCommonIdentifierCreationDate, AVMetadataIdentifieriTunesMetadataReleaseDate,
58 AVMetadataIdentifierQuickTimeMetadataCreationDate, AVMetadataIdentifierID3MetadataDate,
59 AVMetadataQuickTimeUserDataKeyCreationDate, AVMetadataISOUserDataKeyDate },
60 // Language
61 { AVMetadataCommonIdentifierLanguage, nil, nil, AVMetadataIdentifierID3MetadataLanguage, nil, nil },
62 // Publisher
63 { AVMetadataCommonIdentifierPublisher, AVMetadataIdentifieriTunesMetadataPublisher,
64 AVMetadataIdentifierQuickTimeMetadataPublisher, AVMetadataIdentifierID3MetadataPublisher, nil, nil },
65 // Copyright
66 { AVMetadataCommonIdentifierCopyrights, AVMetadataIdentifieriTunesMetadataCopyright,
67 AVMetadataIdentifierQuickTimeMetadataCopyright, AVMetadataIdentifierID3MetadataCopyright,
68 AVMetadataQuickTimeUserDataKeyCopyright, AVMetadataISOUserDataKeyCopyright },
69 // Url
70 { nil, nil, nil, AVMetadataIdentifierID3MetadataOfficialAudioSourceWebpage, nil, nil },
71 // Duration
72 { nil, nil, nil, AVMetadataIdentifierID3MetadataLength, nil, nil },
73 // MediaType
74 { AVMetadataCommonIdentifierType, nil, nil, AVMetadataIdentifierID3MetadataContentType, nil, nil },
75 // FileFormat
76 { nil, nil, nil, AVMetadataIdentifierID3MetadataFileType, nil, nil },
77 // AudioBitRate
78 { nil, nil, nil, nil, nil, nil },
79 // AudioCodec
80 { nil, nil, nil, nil, nil, nil },
81 // VideoBitRate
82 { nil, nil, nil, nil, nil, nil },
83 // VideoCodec
84 { nil, nil, nil, nil, nil, nil },
85 // VideoFrameRate
86 { nil, nil, AVMetadataIdentifierQuickTimeMetadataCameraFrameReadoutTime, nil, nil, nil },
87 // AlbumTitle
88 { AVMetadataCommonIdentifierAlbumName, AVMetadataIdentifieriTunesMetadataAlbum,
89 AVMetadataIdentifierQuickTimeMetadataAlbum, AVMetadataIdentifierID3MetadataAlbumTitle,
90 AVMetadataQuickTimeUserDataKeyAlbum, AVMetadata3GPUserDataKeyAlbumAndTrack },
91 // AlbumArtist
92 { nil, AVMetadataIdentifieriTunesMetadataAlbumArtist, nil, nil,
93 AVMetadataQuickTimeUserDataKeyArtist, AVMetadata3GPUserDataKeyPerformer },
94 // ContributingArtist
95 { AVMetadataCommonIdentifierArtist, AVMetadataIdentifieriTunesMetadataArtist,
96 AVMetadataIdentifierQuickTimeMetadataArtist, nil, nil, nil },
97 // TrackNumber
98 { nil, AVMetadataIdentifieriTunesMetadataTrackNumber,
99 nil, AVMetadataIdentifierID3MetadataTrackNumber, nil, nil },
100 // Composer
101 { nil, AVMetadataIdentifieriTunesMetadataComposer,
102 AVMetadataIdentifierQuickTimeMetadataComposer, AVMetadataIdentifierID3MetadataComposer, nil, nil },
103 // LeadPerformer
104 { nil, AVMetadataIdentifieriTunesMetadataPerformer,
105 AVMetadataIdentifierQuickTimeMetadataPerformer, AVMetadataIdentifierID3MetadataLeadPerformer, nil, nil },
106 // ThumbnailImage
107 { nil, nil, nil, AVMetadataIdentifierID3MetadataAttachedPicture, nil, nil },
108 // CoverArtImage
109 { AVMetadataCommonIdentifierArtwork, AVMetadataIdentifieriTunesMetadataCoverArt,
110 AVMetadataIdentifierQuickTimeMetadataArtwork, nil, nil, nil },
111 // Orientation
112 { nil, nil, AVMetadataIdentifierQuickTimeMetadataVideoOrientation, nil, nil, nil },
113 // Resolution
114 { nil, nil, nil, nil, nil, nil },
115 // HasHdrContent
116 { nil, nil, nil, nil, nil, nil }
117};
118
119static AVMetadataIdentifier toIdentifier(QMediaMetaData::Key key, AVMetadataKeySpace keySpace)
120{
121 static_assert(sizeof(keyToAVMetaDataID) / sizeof(AVMetadataIDs) == QMediaMetaData::NumMetaData);
122
123 AVMetadataIdentifier identifier = nil;
124 if ([keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
125 identifier = keyToAVMetaDataID[key].iTunes;
126 } else if ([keySpace isEqualToString:AVMetadataKeySpaceID3]) {
127 identifier = keyToAVMetaDataID[key].ID3;
128 } else if ([keySpace isEqualToString:AVMetadataKeySpaceQuickTimeMetadata]) {
129 identifier = keyToAVMetaDataID[key].quickTime;
130 } else {
131 identifier = keyToAVMetaDataID[key].common;
132 }
133 return identifier;
134}
135
136static std::optional<QMediaMetaData::Key> toKey(AVMetadataItem *item)
137{
138 static_assert(sizeof(keyToAVMetaDataID) / sizeof(AVMetadataIDs) == QMediaMetaData::NumMetaData);
139
140 // The item identifier may be different than the ones we support,
141 // so check by common key first, as it will get the metadata
142 // irrespective of the format.
143 AVMetadataKey commonKey = item.commonKey;
144 if (commonKey.length != 0) {
145 if ([commonKey isEqualToString:AVMetadataCommonKeyTitle]) {
146 return QMediaMetaData::Title;
147 } else if ([commonKey isEqualToString:AVMetadataCommonKeyDescription]) {
148 return QMediaMetaData::Description;
149 } else if ([commonKey isEqualToString:AVMetadataCommonKeyPublisher]) {
150 return QMediaMetaData::Publisher;
151 } else if ([commonKey isEqualToString:AVMetadataCommonKeyCreationDate]) {
152 return QMediaMetaData::Date;
153 } else if ([commonKey isEqualToString:AVMetadataCommonKeyType]) {
154 return QMediaMetaData::MediaType;
155 } else if ([commonKey isEqualToString:AVMetadataCommonKeyLanguage]) {
156 return QMediaMetaData::Language;
157 } else if ([commonKey isEqualToString:AVMetadataCommonKeyCopyrights]) {
158 return QMediaMetaData::Copyright;
159 } else if ([commonKey isEqualToString:AVMetadataCommonKeyAlbumName]) {
160 return QMediaMetaData::AlbumTitle;
161 } else if ([commonKey isEqualToString:AVMetadataCommonKeyAuthor]) {
162 return QMediaMetaData::Author;
163 } else if ([commonKey isEqualToString:AVMetadataCommonKeyArtist]) {
164 return QMediaMetaData::ContributingArtist;
165 }
166 }
167
168 // Check by identifier if no common key found
169 // No need to check for the common keySpace since there's no common key
170 enum keySpaces { iTunes, QuickTime, QuickTimeUserData, IsoUserData, ID3, Other } itemKeySpace;
171 itemKeySpace = Other;
172 AVMetadataKeySpace keySpace = [item keySpace];
173 AVMetadataIdentifier identifier = [item identifier];
174
175 if ([keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
176 itemKeySpace = iTunes;
177 } else if ([keySpace isEqualToString:AVMetadataKeySpaceQuickTimeMetadata]) {
178 itemKeySpace = QuickTime;
179 } else if ([keySpace isEqualToString:AVMetadataKeySpaceQuickTimeUserData]) {
180 itemKeySpace = QuickTimeUserData;
181 } else if ([keySpace isEqualToString:AVMetadataKeySpaceISOUserData]) {
182 itemKeySpace = IsoUserData;
183 } else if (([keySpace isEqualToString:AVMetadataKeySpaceID3])) {
184 itemKeySpace = ID3;
185 }
186
187 for (int key = 0; key < QMediaMetaData::NumMetaData; key++) {
188 AVMetadataIdentifier idForKey = nil;
189 switch (itemKeySpace) {
190 case iTunes:
191 idForKey = keyToAVMetaDataID[key].iTunes;
192 break;
193 case QuickTime:
194 idForKey = keyToAVMetaDataID[key].quickTime;
195 break;
196 case ID3:
197 idForKey = keyToAVMetaDataID[key].ID3;
198 break;
199 case QuickTimeUserData:
200 idForKey = keyToAVMetaDataID[key].quickTimeUserData;
201 break;
202 case IsoUserData:
203 idForKey = keyToAVMetaDataID[key].isoUserData;
204 break;
205 default:
206 break;
207 }
208
209 if ([identifier isEqualToString:idForKey])
210 return QMediaMetaData::Key(key);
211 }
212
213 return std::nullopt;
214}
215
216static QMediaMetaData fromAVMetadata(NSArray *metadataItems)
217{
218 QMediaMetaData metadata;
219
220 for (AVMetadataItem* item in metadataItems) {
221 auto key = toKey(item);
222 if (!key)
223 continue;
224
225 const QString value = QString::fromNSString([item stringValue]);
226 if (!value.isNull())
227 metadata.insert(*key, value);
228 }
229 return metadata;
230}
231
232QMediaMetaData AVFMetaData::fromAsset(AVAsset *asset)
233{
234#ifdef QT_DEBUG_AVF
235 qDebug() << Q_FUNC_INFO;
236#endif
237 QMediaMetaData metadata = fromAVMetadata([asset metadata]);
238
239 // add duration
240 const CMTime time = [asset duration];
241 const qint64 duration = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f);
242 metadata.insert(QMediaMetaData::Duration, duration);
243
244 return metadata;
245}
246
247QMediaMetaData AVFMetaData::fromAssetTrack(AVAssetTrack *asset)
248{
249 QMediaMetaData metadata = fromAVMetadata([asset metadata]);
250 if ([asset.mediaType isEqualToString:AVMediaTypeAudio]) {
251 if (metadata.value(QMediaMetaData::Language).isNull()) {
252 auto *languageCode = asset.languageCode;
253 if (languageCode) {
254 // languageCode is encoded as ISO 639-2, which QLocale does not handle.
255 // Convert it to 639-1 first.
256 QCFString lang = CFLocaleCreateCanonicalLanguageIdentifierFromString(
257 kCFAllocatorDefault, (__bridge CFStringRef)languageCode);
258 metadata.insert(QMediaMetaData::Language, QLocale::codeToLanguage(QString{ lang }));
259 }
260 }
261 }
262 if ([asset.mediaType isEqualToString:AVMediaTypeVideo]) {
263 // add orientation
264 if (metadata.value(QMediaMetaData::Orientation).isNull()) {
265 QtVideo::Rotation angle = QtVideo::Rotation::None;
266 bool mirrored;
267 AVFMediaPlayer::videoOrientationForAssetTrack(asset, angle, mirrored);
268 Q_UNUSED(mirrored);
269 metadata.insert(QMediaMetaData::Orientation, int(angle));
270 }
271
272 // add HDR content
273 if (metadata.value(QMediaMetaData::HasHdrContent).isNull()) {
274 auto hasHdrContent = false;
275
276 NSArray *formatDescriptions = [asset formatDescriptions];
277 for (id formatDescription in formatDescriptions) {
278 NSDictionary *extensions = (__bridge NSDictionary *)CMFormatDescriptionGetExtensions((CMFormatDescriptionRef)formatDescription);
279 NSString *transferFunction = extensions[(__bridge NSString *)kCMFormatDescriptionExtension_TransferFunction];
280 if ([transferFunction isEqualToString:(__bridge NSString *)kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ]) {
281 hasHdrContent = true;
282 break;
283 }
284 }
285
286 metadata.insert(QMediaMetaData::HasHdrContent, hasHdrContent);
287 }
288 }
289 return metadata;
290}
291
292static AVMutableMetadataItem *setAVMetadataItemForKey(QMediaMetaData::Key key, const QVariant &value,
293 AVMetadataKeySpace keySpace = AVMetadataKeySpaceCommon)
294{
295 AVMetadataIdentifier identifier = toIdentifier(key, keySpace);
296 if (!identifier.length)
297 return nil;
298
299 AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
300 item.keySpace = keySpace;
301 item.identifier = identifier;
302
303 switch (key) {
304 case QMediaMetaData::ThumbnailImage:
305 case QMediaMetaData::CoverArtImage: {
306#if defined(Q_OS_MACOS)
307 QImage img = value.value<QImage>();
308 if (!img.isNull()) {
309 QByteArray arr;
310 QBuffer buffer(&arr);
311 buffer.open(QIODevice::WriteOnly);
312 img.save(&buffer);
313 NSData *data = arr.toNSData();
314 NSImage *nsImg = [[NSImage alloc] initWithData:data];
315 item.value = nsImg;
316 [nsImg release];
317 }
318#endif
319 break;
320 }
321 case QMediaMetaData::FileFormat: {
322 QMediaFormat::FileFormat qtFormat = value.value<QMediaFormat::FileFormat>();
323 AVFileType avFormat = QDarwinFormatInfo::avFileTypeForContainerFormat(qtFormat);
324 item.value = avFormat;
325 break;
326 }
327 case QMediaMetaData::Language: {
328 QString lang = QLocale::languageToCode(value.value<QLocale::Language>());
329 if (!lang.isEmpty())
330 item.value = lang.toNSString();
331 break;
332 }
333 case QMediaMetaData::Orientation: {
334 bool ok;
335 int rotation = value.toInt(&ok);
336 if (ok)
337 item.value = [NSNumber numberWithInt:rotation];
338 }
339 default: {
340 switch (value.typeId()) {
341 case QMetaType::QString: {
342 item.value = value.toString().toNSString();
343 break;
344 }
345 case QMetaType::Int: {
346 item.value = [NSNumber numberWithInt:value.toInt()];
347 break;
348 }
349 case QMetaType::LongLong: {
350 item.value = [NSNumber numberWithLongLong:value.toLongLong()];
351 break;
352 }
353 case QMetaType::Double: {
354 item.value = [NSNumber numberWithDouble:value.toDouble()];
355 break;
356 }
357 case QMetaType::QDate:
358 case QMetaType::QDateTime: {
359 item.value = value.toDateTime().toNSDate();
360 break;
361 }
362 case QMetaType::QUrl: {
363 item.value = value.toUrl().toNSURL();
364 break;
365 }
366 default:
367 break;
368 }
369 }
370 }
371
372 return item;
373}
374
375NSMutableArray<AVMetadataItem *> *AVFMetaData::toAVMetadataForFormat(QMediaMetaData metadata, AVFileType format)
376{
377 NSMutableArray<AVMetadataKeySpace> *keySpaces = [NSMutableArray<AVMetadataKeySpace> array];
378 if (format == AVFileTypeAppleM4A) {
379 [keySpaces addObject:AVMetadataKeySpaceiTunes];
380 } else if (format == AVFileTypeMPEGLayer3) {
381 [keySpaces addObject:AVMetadataKeySpaceID3];
382 [keySpaces addObject:AVMetadataKeySpaceiTunes];
383 } else if (format == AVFileTypeQuickTimeMovie) {
384 [keySpaces addObject:AVMetadataKeySpaceQuickTimeMetadata];
385 } else {
386 [keySpaces addObject:AVMetadataKeySpaceCommon];
387 }
388 NSMutableArray<AVMetadataItem *> *avMetaDataArr = [NSMutableArray array];
389 for (const auto &key : metadata.keys()) {
390 for (NSUInteger i = 0; i < [keySpaces count]; i++) {
391 const QVariant &value = metadata.value(key);
392 // set format-specific metadata
393 AVMetadataItem *item = setAVMetadataItemForKey(key, value, keySpaces[i]);
394 if (item)
395 [avMetaDataArr addObject:item];
396 }
397 }
398 return avMetaDataArr;
399}
static AVMetadataIdentifier toIdentifier(QMediaMetaData::Key key, AVMetadataKeySpace keySpace)
static AVMutableMetadataItem * setAVMetadataItemForKey(QMediaMetaData::Key key, const QVariant &value, AVMetadataKeySpace keySpace=AVMetadataKeySpaceCommon)
static std::optional< QMediaMetaData::Key > toKey(AVMetadataItem *item)
static QMediaMetaData fromAVMetadata(NSArray *metadataItems)
const AVMetadataIDs keyToAVMetaDataID[]
#define __has_include(x)
AVMetadataIdentifier quickTimeUserData
AVMetadataIdentifier common
AVMetadataIdentifier ID3
AVMetadataIdentifier iTunes
AVMetadataIdentifier isoUserData
AVMetadataIdentifier quickTime