예제 #1
0
void FileHelper::setRatingForID3v2(int rating, TagLib::ID3v2::Tag *tag)
{
	TagLib::ID3v2::FrameList l = tag->frameListMap()["POPM"];
	// If one wants to remove the existing rating
	if (rating == 0 && !l.isEmpty()) {
		tag->removeFrame(l.front());
	} else {
		TagLib::ID3v2::PopularimeterFrame *pf = nullptr;
		if (l.isEmpty()) {
			pf = new TagLib::ID3v2::PopularimeterFrame();
			tag->addFrame(pf);
		} else {
			pf = static_cast<TagLib::ID3v2::PopularimeterFrame*>(l.front());
		}
		switch (rating) {
		case 1:
			pf->setRating(1);
			break;
		case 2:
			pf->setRating(64);
			break;
		case 3:
			pf->setRating(128);
			break;
		case 4:
			pf->setRating(196);
			break;
		case 5:
			pf->setRating(255);
			break;
		}
	}
}
예제 #2
0
bool TagReader::SaveSongRatingToFile(
    const QString& filename, const pb::tagreader::SongMetadata& song) const {
  if (filename.isNull()) return false;

  qLog(Debug) << "Saving song rating tags to" << filename;
  if (song.rating() < 0) {
    // The FMPS spec says unrated == "tag not present". For us, no rating
    // results in rating being -1, so don't write anything in that case.
    // Actually, we should also remove tag set in this case, but in
    // Clementine it is not possible to unset rating i.e. make a song "unrated".
    qLog(Debug) << "Unrated: do nothing";
    return true;
  }

  std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));

  if (!fileref || fileref->isNull())  // The file probably doesn't exist
    return false;

  if (TagLib::MPEG::File* file =
          dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
    TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true);

    // Save as FMPS
    SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag);

    // Also save as POPM
    TagLib::ID3v2::PopularimeterFrame* frame = GetPOPMFrameFromTag(tag);
    frame->setRating(ConvertToPOPMRating(song.rating()));

  } else if (TagLib::FLAC::File* file =
                 dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
    TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true);
    SetFMPSRatingVorbisComments(vorbis_comments, song);
  } else if (TagLib::Ogg::XiphComment* tag =
                 dynamic_cast<TagLib::Ogg::XiphComment*>(
                     fileref->file()->tag())) {
    SetFMPSRatingVorbisComments(tag, song);
  }
#ifdef TAGLIB_WITH_ASF
  else if (TagLib::ASF::File* file =
               dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
    TagLib::ASF::Tag* tag = file->tag();
    tag->addAttribute("FMPS/Rating", NumberToASFAttribute(song.rating()));
  }
#endif
  else if (TagLib::MP4::File* file =
               dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
    TagLib::MP4::Tag* tag = file->tag();
    tag->itemListMap()[kMP4_FMPS_Rating_ID] = TagLib::StringList(
        QStringToTaglibString(QString::number(song.rating())));
  } else {
    // Nothing to save: stop now
    return true;
  }

  bool ret = fileref->save();
#ifdef Q_OS_LINUX
  if (ret) {
    // Linux: inotify doesn't seem to notice the change to the file unless we
    // change the timestamps as well. (this is what touch does)
    utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
  }
#endif  // Q_OS_LINUX
  return ret;
}
bool SFB::Audio::SetID3v2TagFromMetadata(const Metadata& metadata, TagLib::ID3v2::Tag *tag, bool setAlbumArt)
{
	if(nullptr == tag)
		return false;
	
	// Use UTF-8 as the default encoding
	(TagLib::ID3v2::FrameFactory::instance())->setDefaultTextEncoding(TagLib::String::UTF8);

	// Album title
	tag->setAlbum(TagLib::StringFromCFString(metadata.GetAlbumTitle()));

	// Artist
	tag->setArtist(TagLib::StringFromCFString(metadata.GetArtist()));
	
	// Composer
	tag->removeFrames("TCOM");
	if(metadata.GetComposer()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TCOM", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(metadata.GetComposer()));
		tag->addFrame(frame);
	}
	
	// Genre
	tag->setGenre(TagLib::StringFromCFString(metadata.GetGenre()));
	
	// Date
#if 1
	int year = 0;
	if(metadata.GetReleaseDate())
		year = CFStringGetIntValue(metadata.GetReleaseDate());
	tag->setYear((TagLib::uint)year);
#else
	// TODO: Parse the release date into components and set the frame appropriately
	tag->removeFrames("TDRC");
	if(metadata.GetReleaseDate()) {
		/*
		 The timestamp fields are based on a subset of ISO 8601. When being as
		 precise as possible the format of a time string is
		 yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of
		 24), ":", minutes, ":", seconds), but the precision may be reduced by
		 removing as many time indicators as wanted. Hence valid timestamps
		 are
		 yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and
		 yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use
		 the slash character as described in 8601, and for multiple non-
		 contiguous dates, use multiple strings, if allowed by the frame
		 definition.
		*/
//		year = CFStringGetIntValue(metadata.GetReleaseDate());
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TDRC", TagLib::String::Latin1);
		frame->setText("");
		tag->addFrame(frame);
	}
#endif

	// Comment
	tag->setComment(TagLib::StringFromCFString(metadata.GetComment()));
	
	// Album artist
	tag->removeFrames("TPE2");
	if(metadata.GetAlbumArtist()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPE2", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(metadata.GetAlbumArtist()));
		tag->addFrame(frame);
	}
	
	// Track title
	tag->setTitle(TagLib::StringFromCFString(metadata.GetTitle()));
	
	// BPM
	tag->removeFrames("TBPM");
	if(metadata.GetBPM()) {
		SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), metadata.GetBPM());

		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TBPM", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(str));
		tag->addFrame(frame);
	}

	// Rating
	tag->removeFrames("POPM");
	CFNumberRef rating = metadata.GetRating();
	if(rating) {
		TagLib::ID3v2::PopularimeterFrame *frame = new TagLib::ID3v2::PopularimeterFrame();

		int i;
		if(CFNumberGetValue(rating, kCFNumberIntType, &i)) {
			frame->setRating(i);
			tag->addFrame(frame);
		}
	}

	// Track number and total tracks
	tag->removeFrames("TRCK");
	CFNumberRef trackNumber	= metadata.GetTrackNumber();
	CFNumberRef trackTotal	= metadata.GetTrackTotal();
	if(trackNumber && trackTotal) {
		SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@/%@"), trackNumber, trackTotal);

		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(str));
		tag->addFrame(frame);
	}
	else if(trackNumber) {		
		SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), trackNumber);
		
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(str));
		tag->addFrame(frame);
	}
	else if(trackTotal) {		
		SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("/%@"), trackTotal);
		
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(str));
		tag->addFrame(frame);
	}
	
	// Compilation
	// iTunes uses the TCMP frame for this, which isn't in the standard, but we'll use it for compatibility
	tag->removeFrames("TCMP");
	if(metadata.GetCompilation()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TCMP", TagLib::String::Latin1);
		frame->setText(CFBooleanGetValue(metadata.GetCompilation()) ? "1" : "0");
		tag->addFrame(frame);
	}
	
	// Disc number and total discs
	tag->removeFrames("TPOS");
	CFNumberRef discNumber	= metadata.GetDiscNumber();
	CFNumberRef discTotal	= metadata.GetDiscTotal();
	if(discNumber && discTotal) {
		SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@/%@"), discNumber, discTotal);
		
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(str));
		tag->addFrame(frame);
	}
	else if(discNumber) {		
		SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), discNumber);
		
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(str));
		tag->addFrame(frame);
	}
	else if(discTotal) {		
		SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("/%@"), discTotal);
		
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(str));
		tag->addFrame(frame);
	}
	
	// Lyrics
	tag->removeFrames("USLT");
	if(metadata.GetLyrics()) {
		auto frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(TagLib::String::UTF8);
		frame->setText(TagLib::StringFromCFString(metadata.GetLyrics()));
		tag->addFrame(frame);
	}

	tag->removeFrames("TSRC");
	if(metadata.GetISRC()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSRC", TagLib::String::Latin1);
		frame->setText(TagLib::StringFromCFString(metadata.GetISRC()));
		tag->addFrame(frame);
	}

	// MusicBrainz
	auto musicBrainzReleaseIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "MusicBrainz Album Id");
	if(nullptr != musicBrainzReleaseIDFrame)
		tag->removeFrame(musicBrainzReleaseIDFrame);

	CFStringRef musicBrainzReleaseID = metadata.GetMusicBrainzReleaseID();
	if(musicBrainzReleaseID) {
		auto frame = new TagLib::ID3v2::UserTextIdentificationFrame();
		frame->setDescription("MusicBrainz Album Id");
		frame->setText(TagLib::StringFromCFString(musicBrainzReleaseID));
		tag->addFrame(frame);
	}


	auto musicBrainzRecordingIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Track Id");
	if(nullptr != musicBrainzRecordingIDFrame)
		tag->removeFrame(musicBrainzRecordingIDFrame);

	CFStringRef musicBrainzRecordingID = metadata.GetMusicBrainzRecordingID();
	if(musicBrainzRecordingID) {
		auto frame = new TagLib::ID3v2::UserTextIdentificationFrame();
		frame->setDescription("MusicBrainz Track Id");
		frame->setText(TagLib::StringFromCFString(musicBrainzRecordingID));
		tag->addFrame(frame);
	}

	// Sorting and grouping
	tag->removeFrames("TSOT");
	if(metadata.GetTitleSortOrder()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOT", TagLib::String::UTF8);
		frame->setText(TagLib::StringFromCFString(metadata.GetTitleSortOrder()));
		tag->addFrame(frame);
	}
	
	tag->removeFrames("TSOA");
	if(metadata.GetAlbumTitleSortOrder()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOA", TagLib::String::UTF8);
		frame->setText(TagLib::StringFromCFString(metadata.GetAlbumTitleSortOrder()));
		tag->addFrame(frame);
	}

	tag->removeFrames("TSOP");
	if(metadata.GetArtistSortOrder()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOP", TagLib::String::UTF8);
		frame->setText(TagLib::StringFromCFString(metadata.GetArtistSortOrder()));
		tag->addFrame(frame);
	}

	tag->removeFrames("TSO2");
	if(metadata.GetAlbumArtistSortOrder()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSO2", TagLib::String::UTF8);
		frame->setText(TagLib::StringFromCFString(metadata.GetAlbumArtistSortOrder()));
		tag->addFrame(frame);
	}

	tag->removeFrames("TSOC");
	if(metadata.GetComposerSortOrder()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOC", TagLib::String::UTF8);
		frame->setText(TagLib::StringFromCFString(metadata.GetComposerSortOrder()));
		tag->addFrame(frame);
	}

	tag->removeFrames("TIT1");
	if(metadata.GetGrouping()) {
		auto frame = new TagLib::ID3v2::TextIdentificationFrame("TIT1", TagLib::String::UTF8);
		frame->setText(TagLib::StringFromCFString(metadata.GetGrouping()));
		tag->addFrame(frame);
	}

	// ReplayGain
	CFNumberRef trackGain = metadata.GetReplayGainTrackGain();
	CFNumberRef trackPeak = metadata.GetReplayGainTrackPeak();
	CFNumberRef albumGain = metadata.GetReplayGainAlbumGain();
	CFNumberRef albumPeak = metadata.GetReplayGainAlbumPeak();
	
	// Write TXXX frames
	auto trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_track_gain");
	auto trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_track_peak");
	auto albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_album_gain");
	auto albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_album_peak");
	
	if(nullptr != trackGainFrame)
		tag->removeFrame(trackGainFrame);
	
	if(nullptr != trackPeakFrame)
		tag->removeFrame(trackPeakFrame);
	
	if(nullptr != albumGainFrame)
		tag->removeFrame(albumGainFrame);
	
	if(nullptr != albumPeakFrame)
		tag->removeFrame(albumPeakFrame);
	
	if(trackGain) {
		SFB::CFString str = CreateStringFromNumberWithFormat(trackGain, kCFNumberDoubleType, CFSTR("%+2.2f dB"));
		
		auto frame = new TagLib::ID3v2::UserTextIdentificationFrame();
		frame->setDescription("replaygain_track_gain");
		frame->setText(TagLib::StringFromCFString(str));		
		tag->addFrame(frame);
	}
	
	if(trackPeak) {		
		SFB::CFString str = CreateStringFromNumberWithFormat(trackPeak, kCFNumberDoubleType, CFSTR("%1.8f dB"));
		
		auto frame = new TagLib::ID3v2::UserTextIdentificationFrame();
		frame->setDescription("replaygain_track_peak");
		frame->setText(TagLib::StringFromCFString(str));		
		tag->addFrame(frame);
	}
	
	if(albumGain) {
		SFB::CFString str = CreateStringFromNumberWithFormat(albumGain, kCFNumberDoubleType, CFSTR("%+2.2f dB"));

		auto frame = new TagLib::ID3v2::UserTextIdentificationFrame();		
		frame->setDescription("replaygain_album_gain");
		frame->setText(TagLib::StringFromCFString(str));		
		tag->addFrame(frame);
	}
	
	if(albumPeak) {
		SFB::CFString str = CreateStringFromNumberWithFormat(albumPeak, kCFNumberDoubleType, CFSTR("%1.8f dB"));
		
		auto frame = new TagLib::ID3v2::UserTextIdentificationFrame();		
		frame->setDescription("replaygain_album_peak");
		frame->setText(TagLib::StringFromCFString(str));		
		tag->addFrame(frame);
	}
	
	// Also write the RVA2 frames
	tag->removeFrames("RVA2");
	if(trackGain) {
		auto relativeVolume = new TagLib::ID3v2::RelativeVolumeFrame();
		
		float f;
		CFNumberGetValue(trackGain, kCFNumberFloatType, &f);
		
		relativeVolume->setIdentification(TagLib::String("track", TagLib::String::Latin1));
		relativeVolume->setVolumeAdjustment(f, TagLib::ID3v2::RelativeVolumeFrame::MasterVolume);
		
		tag->addFrame(relativeVolume);
	}
	
	if(albumGain) {
		auto relativeVolume = new TagLib::ID3v2::RelativeVolumeFrame();

		float f;
		CFNumberGetValue(albumGain, kCFNumberFloatType, &f);

		relativeVolume->setIdentification(TagLib::String("album", TagLib::String::Latin1));
		relativeVolume->setVolumeAdjustment(f, TagLib::ID3v2::RelativeVolumeFrame::MasterVolume);
		
		tag->addFrame(relativeVolume);
	}

	// Album art
	if(setAlbumArt) {
		tag->removeFrames("APIC");

		for(auto attachedPicture : metadata.GetAttachedPictures()) {
			SFB::CGImageSource imageSource = CGImageSourceCreateWithData(attachedPicture->GetData(), nullptr);
			if(!imageSource)
				continue;

			TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame;

			// Convert the image's UTI into a MIME type
			SFB::CFString mimeType = UTTypeCopyPreferredTagWithClass(CGImageSourceGetType(imageSource), kUTTagClassMIMEType);
			if(mimeType)
				frame->setMimeType(TagLib::StringFromCFString(mimeType));

			frame->setPicture(TagLib::ByteVector((const char *)CFDataGetBytePtr(attachedPicture->GetData()), (TagLib::uint)CFDataGetLength(attachedPicture->GetData())));
			frame->setType((TagLib::ID3v2::AttachedPictureFrame::Type)attachedPicture->GetType());
			if(attachedPicture->GetDescription())
				frame->setDescription(TagLib::StringFromCFString(attachedPicture->GetDescription()));
			tag->addFrame(frame);
		}
	}

	return true;
}
bool
ID3v2TagHelper::setTags( const Meta::FieldHash &changes )
{
    bool modified = TagHelper::setTags( changes );

    foreach( const qint64 key, changes.keys() )
    {
        QVariant value = changes.value( key );
        TagLib::ByteVector field( fieldName( key ).toCString() );

        if( !field.isNull() && !field.isEmpty() )
        {
            if( key == Meta::valHasCover )
                continue;
            else if( key == Meta::valUniqueId )
            {
                QPair< UIDType, QString > uidPair = splitUID( value.toString() );
                if( uidPair.first == UIDInvalid )
                    continue;

                TagLib::String owner  = uidFieldName( uidPair.first );
                TagLib::ByteVector uid( uidPair.second.toAscii().data() );
                TagLib::ID3v2::FrameList list = m_tag->frameList();

                for( TagLib::ID3v2::FrameList::ConstIterator it = list.begin(); it != list.end(); ++it )
                {
                    if( ( *it )->frameID() == field )
                    {
                        TagLib::ID3v2::UniqueFileIdentifierFrame *frame =
                                dynamic_cast< TagLib::ID3v2::UniqueFileIdentifierFrame * >( *it );
                        if( !frame )
                            continue;

                        if( frame->owner() == owner )
                        {
                            m_tag->removeFrame( frame );
                            modified = true;
                            break;
                        }
                    }
                }

                if ( !uid.isEmpty() )
                {
                    m_tag->addFrame( new TagLib::ID3v2::UniqueFileIdentifierFrame( owner, uid ) );
                    modified = true;
                }
                continue;
            }

            TagLib::String tValue = Qt4QStringToTString( ( key == Meta::valCompilation )
                                                         ? QString::number( value.toInt() )
                                                         : value.toString() );
            if( tValue.isEmpty() )
                m_tag->removeFrames( field );
            else
            {
                TagLib::ID3v2::TextIdentificationFrame *frame = NULL;
                if( !m_tag->frameListMap()[field].isEmpty() )
                    frame = dynamic_cast< TagLib::ID3v2::TextIdentificationFrame * >(
                                                  m_tag->frameListMap()[field].front()
                                                                                    );
                if( !frame )
                {
                    frame = new TagLib::ID3v2::TextIdentificationFrame( field );
                    m_tag->addFrame( frame );
                }
                // note: TagLib is smart enough to automatically set UTF8 encoding if needed.
                frame->setText( tValue );
            }
            modified = true;
        }
        else if( key == Meta::valScore || key == Meta::valRating || key == Meta::valPlaycount )
        {
            TagLib::String description;
            TagLib::String tValue;

            if( key == Meta::valRating )
            {
                description = fmpsFieldName( FMPSRating );
                tValue = Qt4QStringToTString( QString::number( value.toFloat() / 10.0 ) );
            }
            else if( key == Meta::valScore )
            {
                description = fmpsFieldName( FMPSScore );
                tValue = Qt4QStringToTString( QString::number( value.toFloat() / 100.0 ) );
            }
            else if( key == Meta::valPlaycount )
            {
                description = fmpsFieldName( FMPSPlayCount );
                tValue = Qt4QStringToTString( QString::number( value.toInt() ) );
            }

            if( key == Meta::valRating || key == Meta::valPlaycount )
            {
                TagLib::ID3v2::PopularimeterFrame *popFrame = NULL;
                if( !m_tag->frameListMap()[POPM_Frame].isEmpty() )
                    popFrame = dynamic_cast< TagLib::ID3v2::PopularimeterFrame * >( m_tag->frameListMap()[POPM_Frame].front() );

                if( !popFrame )
                {
                    popFrame = new TagLib::ID3v2::PopularimeterFrame( POPM_Frame );
                    m_tag->addFrame( popFrame );
                }

                if( key == Meta::valRating )
                    popFrame->setRating( qBound(0, int(qRound(value.toDouble() / 10.0 * 256)), 255) );
                else
                    popFrame->setCounter( value.toInt() );

                modified = true;
            }

            TagLib::ID3v2::FrameList list = m_tag->frameList();
            for( TagLib::ID3v2::FrameList::ConstIterator it = list.begin(); it != list.end(); ++it )
            {
                if( ( *it )->frameID() == TXXX_Frame )
                {
                    TagLib::ID3v2::UserTextIdentificationFrame *frame =
                            dynamic_cast< TagLib::ID3v2::UserTextIdentificationFrame * >( *it );
                    if( !frame )
                        continue;

                    if( frame->description() == description )
                    {
                        m_tag->removeFrame( frame );
                        modified = true;
                        break;
                    }
                }
            }

            if( value.toBool() )
            {
                TagLib::ID3v2::UserTextIdentificationFrame *frame =
                        new TagLib::ID3v2::UserTextIdentificationFrame( TXXX_Frame );

                frame->setDescription( description );
                frame->setText( tValue );
                m_tag->addFrame( frame );
                modified = true;
            }
        }
    }

    return modified;
}