Esempio n. 1
0
int CMp4File::create_text(CPlayerSession *psptr, 
			   text_query_t *tq, 
			   uint text_offset,
			   uint &start_desc)
{
  uint ix;
  //uint64_t IVLength;
  CPlayerMedia *mptr;
  codec_plugin_t *plugin;
  uint32_t verb = MP4GetVerbosity(m_mp4file);
  for (ix = 0; ix < text_offset; ix++) {
    if (tq[ix].enabled != 0) {
      CMp4TextByteStream *tbyte;
      mptr = new CPlayerMedia(psptr, TIMED_TEXT_SYNC);
      if (mptr == NULL) {
	return (-1);
      }

      /* check if ismacryp */
#if 0
      uint32_t verb = MP4GetVerbosity(m_mp4file);
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      if (MP4IsIsmaCrypMediaTrack(m_mp4file, aq[ix].track_id)) {
        IVLength = MP4GetTrackIntegerProperty(m_mp4file,
                    aq[ix].track_id, "mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.IV-length");
	abyte = new CMp4EncAudioByteStream(this, aq[ix].track_id, IVLength);
      } else {
	abyte = new CMp4AudioByteStream(this, aq[ix].track_id);
      }
      MP4SetVerbosity(m_mp4file, verb);
#else
      tbyte = new CMp4TextByteStream(this, tq[ix].track_id);
#endif

      int ret;
      plugin = check_for_text_codec(tq[ix].stream_type,
				    tq[ix].compressor,
				    NULL,
				    NULL,
				    0, 
				    &config);

      ret = mptr->create_text_plugin(plugin,
				     STREAM_TYPE_MP4_FILE,
				     tq[ix].compressor,
				     NULL, // sdp info
				     NULL, 
				     0);
      if (ret < 0) {
	mp4f_message(LOG_ERR, "Couldn't create text from plugin %s", 
		     plugin->c_name);
	psptr->set_message("Couldn't start text plugin %s", 
			   plugin->c_name);
	delete mptr;
	delete tbyte;
	return -1;
      }

      ret = mptr->create_media("text", tbyte);
      if (ret != 0) {
	return (-1);
      }
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      char *mp4info = MP4Info(m_mp4file, tq[ix].track_id);
      MP4SetVerbosity(m_mp4file, verb);
      char *temp = mp4info;
      while (*temp != '\0') {
	if (isspace(*temp)) *temp = ' ';
	if (!isprint(*temp)) *temp = '*';
	temp++;
      }
      psptr->set_session_desc(start_desc, mp4info);
      free(mp4info);
      start_desc++;
    } else {
      CHECK_AND_FREE(tq[ix].config);
    }
  }

  return 0;
}
Esempio n. 2
0
int CMp4File::create_audio(CPlayerSession *psptr, 
			   audio_query_t *aq, 
			   uint audio_offset,
			   uint &start_desc)
{
  uint ix;
  uint64_t IVLength;
  CPlayerMedia *mptr;
  codec_plugin_t *plugin;
  for (ix = 0; ix < audio_offset; ix++) {
    if (aq[ix].enabled != 0) {
      CMp4AudioByteStream *abyte;
      mptr = new CPlayerMedia(psptr, AUDIO_SYNC);
      if (mptr == NULL) {
	return (-1);
      }

      /* check if ismacryp */
      uint32_t verb = MP4GetVerbosity(m_mp4file);
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      if (MP4IsIsmaCrypMediaTrack(m_mp4file, aq[ix].track_id)) {
        MP4GetTrackIntegerProperty(m_mp4file,
                    aq[ix].track_id, "mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.IV-length", &IVLength);
	abyte = new CMp4EncAudioByteStream(this, aq[ix].track_id, IVLength);
      } else {
	abyte = new CMp4AudioByteStream(this, aq[ix].track_id);
      }
      MP4SetVerbosity(m_mp4file, verb);

      audio_info_t *ainfo;
      ainfo = (audio_info_t *)malloc(sizeof(audio_info_t));
      memset(ainfo, 0, sizeof(*ainfo));

      ainfo->freq = aq[ix].sampling_freq;
      if (aq[ix].chans != -1) {
	ainfo->chans = aq[ix].chans;
      }
      if ((aq[ix].type == MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE) ||
	  (aq[ix].type == MP4_PCM16_BIG_ENDIAN_AUDIO_TYPE)) {
	ainfo->bitspersample = 16;
      }

      int ret;
      plugin = check_for_audio_codec(STREAM_TYPE_MP4_FILE,
				     aq[ix].compressor, // media_data field
				     NULL,
				     aq[ix].type,
				     aq[ix].profile,
				     aq[ix].config,
				     aq[ix].config_len,
				     &config);

      ret = mptr->create_audio_plugin(plugin,
				      STREAM_TYPE_MP4_FILE,
				      aq[ix].compressor,
				      aq[ix].type, 
				      aq[ix].profile,
				      NULL, // sdp info
				      ainfo, // audio info
				      aq[ix].config,
				      aq[ix].config_len);
      if (ret < 0) {
	mp4f_message(LOG_ERR, "Couldn't create audio from plugin %s", 
		     plugin->c_name);
	psptr->set_message("Couldn't start audio plugin %s", 
			   plugin->c_name);
	delete mptr;
	delete abyte;
	return -1;
      }

      ret = mptr->create_media("audio", abyte);
      if (ret != 0) {
	return (-1);
      }
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      char *mp4info = MP4Info(m_mp4file, aq[ix].track_id);
      MP4SetVerbosity(m_mp4file, verb);
      char *temp = mp4info;
      while (*temp != '\0') {
	if (isspace(*temp)) *temp = ' ';
	if (!isprint(*temp)) *temp = '*';
	temp++;
      }
      psptr->set_session_desc(start_desc, mp4info);
      free(mp4info);
      start_desc++;
    } else {
      if (aq[ix].config != NULL) free((void *)aq[ix].config);
    }
  }

  return 0;
}
Esempio n. 3
0
int CMp4File::create_video(CPlayerSession *psptr, 
			   video_query_t *vq, 
			   uint video_offset,
			   uint &start_desc)
{
  uint ix;
  CPlayerMedia *mptr;
  codec_plugin_t *plugin;
  const char *file_kms_uri;
  const char *inuse_kms_uri;

  for (ix = 0; ix < video_offset; ix++) {
    if (vq[ix].enabled != 0) {

      mptr = new CPlayerMedia(psptr, VIDEO_SYNC);
      if (mptr == NULL) {
	return (-1);
      }
						  
      video_info_t *vinfo;
      vinfo = (video_info_t *)malloc(sizeof(video_info_t));
      vinfo->height = vq[ix].h;
      vinfo->width = vq[ix].w;
      plugin = check_for_video_codec(vq[ix].stream_type,
				     vq[ix].compressor,
				     NULL,
				     vq[ix].type,
				     vq[ix].profile,
				     vq[ix].config, 
				     vq[ix].config_len,
				     &config);

      int ret = mptr->create_video_plugin(plugin, 
					  vq[ix].stream_type,
					  vq[ix].compressor,
					  vq[ix].type,
					  vq[ix].profile,
					  NULL, // sdp info
					  vinfo, // video info
					  vq[ix].config,
					  vq[ix].config_len);

      if (ret < 0) {
	mp4f_message(LOG_ERR, "Failed to create plugin data");
	psptr->set_message("Failed to start plugin");
	delete mptr;
	return -1;
      }

      CMp4VideoByteStream *vbyte = NULL;
      uint64_t IVLength;
     
      /* check if clear-text or ismacryp.
       * in this context original format is encv 
       * and compressor specifies which codec
       */
      uint32_t verb = MP4GetVerbosity(m_mp4file);
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      if (strcasecmp(vq[ix].original_fmt, "avc1") == 0) {
	vbyte = new CMp4H264VideoByteStream(this, vq[ix].track_id);
      } else if (strcasecmp(vq[ix].original_fmt, "encv") == 0) {
        MP4GetTrackIntegerProperty(m_mp4file,
				   vq[ix].track_id, 
				   "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.IV-length", 
				   &IVLength);

      	if (strcasecmp(vq[ix].compressor, "mp4v") == 0) {
	vbyte = new CMp4EncVideoByteStream(this, vq[ix].track_id,IVLength);
	}
      	else if (((strcasecmp(vq[ix].compressor, "avc1") == 0) 
      	|| (strcasecmp(vq[ix].compressor, "264b")) == 0)) {
		vbyte = new CMp4EncH264VideoByteStream(this, vq[ix].track_id,IVLength);

		// check if file kms uri matches in-use kms uri
		// advisory only 
		MP4GetTrackStringProperty(m_mp4file, vq[ix].track_id, 
			"mdia.minf.stbl.stsd.encv.sinf.schi.iKMS.kms_URI", 
			&file_kms_uri);
		inuse_kms_uri = 
			((CMp4EncH264VideoByteStream *)vbyte)->get_inuse_kms_uri();
		if (file_kms_uri && inuse_kms_uri) {
			if (strcmp(file_kms_uri, inuse_kms_uri)) {
				mp4f_message(LOG_DEBUG, 
				"KMS in file (%s) does not match KMS in use (%s)\n", 
				file_kms_uri, inuse_kms_uri);
      		}
		}
	}
      } else {
	vbyte = new CMp4VideoByteStream(this, vq[ix].track_id);
      }
      MP4SetVerbosity(m_mp4file, verb);

      if (vbyte == NULL) {
	delete mptr;
	return (-1);
      }

      ret = mptr->create_media("video", vbyte);
      if (ret != 0) {
	return (-1);
      }
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      char *mp4info = MP4Info(m_mp4file, vq[ix].track_id);
      MP4SetVerbosity(m_mp4file, verb);
      char *temp = mp4info;
      while (*temp != '\0') {
	if (isspace(*temp)) *temp = ' ';
	if (!isprint(*temp)) *temp = '*';
	temp++;
      }
      psptr->set_session_desc(start_desc, mp4info);
      free(mp4info);
      start_desc++;
    } else {
      if (vq[ix].config != NULL) free((void *)vq[ix].config);
    }
  }
  return 0;
}
Esempio n. 4
0
int CMp4File::create_video(CPlayerSession *psptr, 
			   video_query_t *vq, 
			   uint video_offset,
			   uint &start_desc)
{
  uint ix;
  CPlayerMedia *mptr;
  codec_plugin_t *plugin;

  for (ix = 0; ix < video_offset; ix++) {
    if (vq[ix].enabled != 0) {

      mptr = new CPlayerMedia(psptr, VIDEO_SYNC);
      if (mptr == NULL) {
	return (-1);
      }
						  
      video_info_t *vinfo;
      vinfo = (video_info_t *)malloc(sizeof(video_info_t));
      vinfo->height = vq[ix].h;
      vinfo->width = vq[ix].w;
      plugin = check_for_video_codec(vq[ix].stream_type,
				     vq[ix].compressor,
				     NULL,
				     vq[ix].type,
				     vq[ix].profile,
				     vq[ix].config, 
				     vq[ix].config_len,
				     &config);

      int ret = mptr->create_video_plugin(plugin, 
					  vq[ix].stream_type,
					  vq[ix].compressor,
					  vq[ix].type,
					  vq[ix].profile,
					  NULL, // sdp info
					  vinfo, // video info
					  vq[ix].config,
					  vq[ix].config_len);

      if (ret < 0) {
	mp4f_message(LOG_ERR, "Failed to create plugin data");
	psptr->set_message("Failed to start plugin");
	delete mptr;
	return -1;
      }

      CMp4VideoByteStream *vbyte;
      uint64_t IVLength;
     
      /* check if ismacryp */
      uint32_t verb = MP4GetVerbosity(m_mp4file);
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      if (strcasecmp(vq[ix].compressor, "avc1") == 0) {
	vbyte = new CMp4H264VideoByteStream(this, vq[ix].track_id);
      } else if (strcasecmp(vq[ix].compressor, "encv") == 0) {
        MP4GetTrackIntegerProperty(m_mp4file,
				   vq[ix].track_id, 
				   "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.IV-length", 
				   &IVLength);
	vbyte = new CMp4EncVideoByteStream(this, vq[ix].track_id,IVLength);
      } else {
	vbyte = new CMp4VideoByteStream(this, vq[ix].track_id);
      }
      MP4SetVerbosity(m_mp4file, verb);

      if (vbyte == NULL) {
	delete mptr;
	return (-1);
      }

      ret = mptr->create_media("video", vbyte);
      if (ret != 0) {
	return (-1);
      }
      MP4SetVerbosity(m_mp4file, verb & ~(MP4_DETAILS_ERROR));
      char *mp4info = MP4Info(m_mp4file, vq[ix].track_id);
      MP4SetVerbosity(m_mp4file, verb);
      char *temp = mp4info;
      while (*temp != '\0') {
	if (isspace(*temp)) *temp = ' ';
	if (!isprint(*temp)) *temp = '*';
	temp++;
      }
      psptr->set_session_desc(start_desc, mp4info);
      free(mp4info);
      start_desc++;
    } else {
      if (vq[ix].config != NULL) free((void *)vq[ix].config);
    }
  }
  return 0;
}
bool MP4Metadata::ReadMetadata(CFErrorRef *error)
{
	// Start from scratch
	CFDictionaryRemoveAllValues(mMetadata);
	CFDictionaryRemoveAllValues(mChangedMetadata);

	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, FALSE, buf, PATH_MAX))
		return false;
	
	// Open the file for reading
	MP4FileHandle file = MP4Read(reinterpret_cast<const char *>(buf));
	
	if(MP4_INVALID_FILE_HANDLE == file) {
		if(error) {
			CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 
																			   32,
																			   &kCFTypeDictionaryKeyCallBacks,
																			   &kCFTypeDictionaryValueCallBacks);
			
			CFStringRef displayName = CreateDisplayNameForURL(mURL);
			CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, 
															   NULL, 
															   CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), 
															   displayName);
			
			CFDictionarySetValue(errorDictionary, 
								 kCFErrorLocalizedDescriptionKey, 
								 errorString);
			
			CFDictionarySetValue(errorDictionary, 
								 kCFErrorLocalizedFailureReasonKey, 
								 CFCopyLocalizedString(CFSTR("Not an MPEG-4 file"), ""));
			
			CFDictionarySetValue(errorDictionary, 
								 kCFErrorLocalizedRecoverySuggestionKey, 
								 CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			CFRelease(errorString), errorString = NULL;
			CFRelease(displayName), displayName = NULL;
			
			*error = CFErrorCreate(kCFAllocatorDefault, 
								   AudioMetadataErrorDomain, 
								   AudioMetadataFileFormatNotRecognizedError, 
								   errorDictionary);
			
			CFRelease(errorDictionary), errorDictionary = NULL;				
		}
		
		return false;
	}

	// Read the properties
	if(0 < MP4GetNumberOfTracks(file)) {
		// Should be type 'soun', media data name'mp4a'
		MP4TrackId trackID = MP4FindTrackId(file, 0);

		// Verify this is an MPEG-4 audio file
		if(MP4_INVALID_TRACK_ID == trackID || strncmp("soun", MP4GetTrackType(file, trackID), 4)) {
			MP4Close(file), file = NULL;
			
			if(error) {
				CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 
																				   32,
																				   &kCFTypeDictionaryKeyCallBacks,
																				   &kCFTypeDictionaryValueCallBacks);
				
				CFStringRef displayName = CreateDisplayNameForURL(mURL);
				CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, 
																   NULL, 
																   CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), 
																   displayName);
				
				CFDictionarySetValue(errorDictionary, 
									 kCFErrorLocalizedDescriptionKey, 
									 errorString);
				
				CFDictionarySetValue(errorDictionary, 
									 kCFErrorLocalizedFailureReasonKey, 
									 CFCopyLocalizedString(CFSTR("Not an MPEG-4 file"), ""));
				
				CFDictionarySetValue(errorDictionary, 
									 kCFErrorLocalizedRecoverySuggestionKey, 
									 CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
				
				CFRelease(errorString), errorString = NULL;
				CFRelease(displayName), displayName = NULL;
				
				*error = CFErrorCreate(kCFAllocatorDefault, 
									   AudioMetadataErrorDomain, 
									   AudioMetadataFileFormatNotSupportedError, 
									   errorDictionary);
				
				CFRelease(errorDictionary), errorDictionary = NULL;				
			}
			
			return false;
		}
		
		MP4Duration mp4Duration = MP4GetTrackDuration(file, trackID);
		uint32_t mp4TimeScale = MP4GetTrackTimeScale(file, trackID);
		
		CFNumberRef totalFrames = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &mp4Duration);
		CFDictionarySetValue(mMetadata, kPropertiesTotalFramesKey, totalFrames);
		CFRelease(totalFrames), totalFrames = NULL;
		
		CFNumberRef sampleRate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &mp4TimeScale);
		CFDictionarySetValue(mMetadata, kPropertiesSampleRateKey, sampleRate);
		CFRelease(sampleRate), sampleRate = NULL;
		
		double length = static_cast<double>(mp4Duration / mp4TimeScale);
		CFNumberRef duration = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &length);
		CFDictionarySetValue(mMetadata, kPropertiesDurationKey, duration);
		CFRelease(duration), duration = NULL;

		// "mdia.minf.stbl.stsd.*[0].channels"
		int channels = MP4GetTrackAudioChannels(file, trackID);
		CFNumberRef channelsPerFrame = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &channels);
		CFDictionaryAddValue(mMetadata, kPropertiesChannelsPerFrameKey, channelsPerFrame);
		CFRelease(channelsPerFrame), channelsPerFrame = NULL;

		// ALAC files
		if(MP4HaveTrackAtom(file, trackID, "mdia.minf.stbl.stsd.alac")) {
			CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("Apple Lossless"));
			
			uint64_t sampleSize;
			uint8_t *decoderConfig;
			uint32_t decoderConfigSize;
			if(MP4GetTrackBytesProperty(file, trackID, "mdia.minf.stbl.stsd.alac.alac.decoderConfig", &decoderConfig, &decoderConfigSize) && 28 <= decoderConfigSize) {
				// The ALAC magic cookie seems to have the following layout (28 bytes, BE):
				// Byte 10: Sample size
				// Bytes 25-28: Sample rate
				CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, decoderConfig + 9);
				CFDictionaryAddValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel);
				CFRelease(bitsPerChannel), bitsPerChannel = NULL;

				double losslessBitrate = static_cast<double>(mp4TimeScale * channels * sampleSize) / 1000;
				CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate);
				CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate);
				CFRelease(bitrate), bitrate = NULL;

				free(decoderConfig), decoderConfig = NULL;
			}			
			else if(MP4GetTrackIntegerProperty(file, trackID, "mdia.minf.stbl.stsd.alac.sampleSize", &sampleSize)) {
				CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &sampleSize);
				CFDictionaryAddValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel);
				CFRelease(bitsPerChannel), bitsPerChannel = NULL;

				double losslessBitrate = static_cast<double>(mp4TimeScale * channels * sampleSize) / 1000;
				CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate);
				CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate);
				CFRelease(bitrate), bitrate = NULL;
			}
		}

		// AAC files
		if(MP4HaveTrackAtom(file, trackID, "mdia.minf.stbl.stsd.mp4a")) {
			CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("AAC"));

			// "mdia.minf.stbl.stsd.*.esds.decConfigDescr.avgBitrate"
			uint32_t trackBitrate = MP4GetTrackBitRate(file, trackID) / 1000;
			CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &trackBitrate);
			CFDictionaryAddValue(mMetadata, kPropertiesBitrateKey, bitrate);
			CFRelease(bitrate), bitrate = NULL;
			
		}
	}
	// No valid tracks in file
	else {
		MP4Close(file), file = NULL;
		
		if(error) {
			CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 
																			   32,
																			   &kCFTypeDictionaryKeyCallBacks,
																			   &kCFTypeDictionaryValueCallBacks);
			
			CFStringRef displayName = CreateDisplayNameForURL(mURL);
			CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, 
															   NULL, 
															   CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), 
															   displayName);
			
			CFDictionarySetValue(errorDictionary, 
								 kCFErrorLocalizedDescriptionKey, 
								 errorString);
			
			CFDictionarySetValue(errorDictionary, 
								 kCFErrorLocalizedFailureReasonKey, 
								 CFCopyLocalizedString(CFSTR("Not an MPEG-4 file"), ""));
			
			CFDictionarySetValue(errorDictionary, 
								 kCFErrorLocalizedRecoverySuggestionKey, 
								 CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			CFRelease(errorString), errorString = NULL;
			CFRelease(displayName), displayName = NULL;
			
			*error = CFErrorCreate(kCFAllocatorDefault, 
								   AudioMetadataErrorDomain, 
								   AudioMetadataFileFormatNotSupportedError, 
								   errorDictionary);
			
			CFRelease(errorDictionary), errorDictionary = NULL;				
		}
		
		return false;
	}

	// Read the tags
	const MP4Tags *tags = MP4TagsAlloc();

	if(NULL == tags) {
		MP4Close(file), file = NULL;

		if(error)
			*error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL);

		return false;
	}
	
	MP4TagsFetch(tags, file);
	
	// Album title
	if(tags->album) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->album, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataAlbumTitleKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Artist
	if(tags->artist) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->artist, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataArtistKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Album Artist
	if(tags->albumArtist) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->albumArtist, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataAlbumArtistKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Genre
	if(tags->genre) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->genre, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataGenreKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Release date
	if(tags->releaseDate) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->releaseDate, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataReleaseDateKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Composer
	if(tags->composer) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->composer, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataComposerKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Comment
	if(tags->comments) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->comments, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataCommentKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Track title
	if(tags->name) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->name, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataTitleKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Track number
	if(tags->track) {
		if(tags->track->index) {
			CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->track->index);
			CFDictionarySetValue(mMetadata, kMetadataTrackNumberKey, num);
			CFRelease(num), num = NULL;
		}
		
		if(tags->track->total) {
			CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->track->total);
			CFDictionarySetValue(mMetadata, kMetadataTrackTotalKey, num);
			CFRelease(num), num = NULL;
		}
	}
	
	// Disc number
	if(tags->disk) {
		if(tags->disk->index) {
			CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->disk->index);
			CFDictionarySetValue(mMetadata, kMetadataDiscNumberKey, num);
			CFRelease(num), num = NULL;
		}
		
		if(tags->disk->total) {
			CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->disk->total);
			CFDictionarySetValue(mMetadata, kMetadataDiscTotalKey, num);
			CFRelease(num), num = NULL;
		}
	}
	
	// Compilation
	if(tags->compilation)
		CFDictionarySetValue(mMetadata, kMetadataCompilationKey, *(tags->compilation) ? kCFBooleanTrue : kCFBooleanFalse);
	
	// BPM
	if(tags->tempo) {
		
	}
	
	// Lyrics
	if(tags->lyrics) {
		CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->lyrics, kCFStringEncodingUTF8);
		CFDictionarySetValue(mMetadata, kMetadataLyricsKey, str);
		CFRelease(str), str = NULL;
	}
	
	// Album art
	if(tags->artworkCount) {
		for(uint32_t i = 0; i < tags->artworkCount; ++i) {
			CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(tags->artwork[i].data), tags->artwork[i].size);
			CFDictionarySetValue(mMetadata, kAlbumArtFrontCoverKey, data);
			CFRelease(data), data = NULL;
		}
	}
	
	// ReplayGain
	// Reference loudness
	MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_reference_loudness");
	if(NULL != items) {
		float referenceLoudnessValue;
		if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &referenceLoudnessValue)) {
			CFNumberRef referenceLoudness = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &referenceLoudnessValue);
			CFDictionaryAddValue(mMetadata, kReplayGainReferenceLoudnessKey, referenceLoudness);
			CFRelease(referenceLoudness), referenceLoudness = NULL;
		}
		
		MP4ItmfItemListFree(items), items = NULL;
	}
	
	// Track gain
	items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_gain");
	if(NULL != items) {
		float trackGainValue;
		if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &trackGainValue)) {
			CFNumberRef trackGain = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &trackGainValue);
			CFDictionaryAddValue(mMetadata, kReplayGainTrackGainKey, trackGain);
			CFRelease(trackGain), trackGain = NULL;
		}
		
		MP4ItmfItemListFree(items), items = NULL;
	}		
	
	// Track peak
	items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_peak");
	if(NULL != items) {
		float trackPeakValue;
		if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &trackPeakValue)) {
			CFNumberRef trackPeak = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &trackPeakValue);
			CFDictionaryAddValue(mMetadata, kReplayGainTrackPeakKey, trackPeak);
			CFRelease(trackPeak), trackPeak = NULL;
		}
		
		MP4ItmfItemListFree(items), items = NULL;
	}		
	
	// Album gain
	items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_gain");
	if(NULL != items) {
		float albumGainValue;
		if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &albumGainValue)) {
			CFNumberRef albumGain = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &albumGainValue);
			CFDictionaryAddValue(mMetadata, kReplayGainAlbumGainKey, albumGain);
			CFRelease(albumGain), albumGain = NULL;
		}
		
		MP4ItmfItemListFree(items), items = NULL;
	}		
	
	// Album peak
	items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_peak");
	if(NULL != items) {
		float albumPeakValue;
		if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &albumPeakValue)) {
			CFNumberRef albumPeak = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &albumPeakValue);
			CFDictionaryAddValue(mMetadata, kReplayGainAlbumPeakKey, albumPeak);
			CFRelease(albumPeak), albumPeak = NULL;
		}
		
		MP4ItmfItemListFree(items), items = NULL;
	}

	// Clean up
	MP4TagsFree(tags), tags = NULL;
	MP4Close(file), file = NULL;

	return true;
}