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