void SoundSourceProxy::loadTrackMetadataAndCoverArt( bool withCoverArt, bool reloadFromFile) const { DEBUG_ASSERT(m_pTrack); if (!m_pSoundSource) { // Silently ignore requests for unsupported files qDebug() << "Unable to parse file tags without a SoundSource" << getUrl().toString(); return; } // Use the existing trackMetadata as default values. Otherwise // existing values in the library will be overwritten with // empty values if the corresponding file tags are missing. // Depending on the file type some kind of tags might even // not be supported at all and those would get lost! bool parsedFromFile = false; mixxx::TrackMetadata trackMetadata; m_pTrack->getTrackMetadata(&trackMetadata, &parsedFromFile); if (parsedFromFile && !reloadFromFile) { qDebug() << "Skip parsing of track metadata from file" << getUrl().toString(); return; // do not reload from file } // If parsing of the cover art image should be omitted the // 2nd output parameter must be set to nullptr. Cover art // is not reloaded from file once the metadata has been parsed! CoverInfoRelative coverInfoRelative; QImage coverImg; DEBUG_ASSERT(coverImg.isNull()); QImage* pCoverImg = (withCoverArt && !parsedFromFile) ? &coverImg : nullptr; bool parsedCoverArt = false; // Parse the tags stored in the audio file. if (m_pSoundSource && (m_pSoundSource->parseTrackMetadataAndCoverArt(&trackMetadata, pCoverImg) == OK)) { parsedFromFile = true; if (!coverImg.isNull()) { // Cover image has been parsed from the file // TODO() here we may introduce a duplicate hash code coverInfoRelative.hash = CoverArtUtils::calculateHash(coverImg); coverInfoRelative.coverLocation = QString(); coverInfoRelative.type = CoverInfo::METADATA; coverInfoRelative.source = CoverInfo::GUESSED; parsedCoverArt = true; } } else { qWarning() << "Failed to parse track metadata from file" << getUrl().toString(); if (parsedFromFile) { // Don't overwrite any existing metadata that once has // been parsed successfully from file. return; } } // If Artist or title fields are blank try to parse them // from the file name. // TODO(rryan): Should we re-visit this decision? if (trackMetadata.getArtist().isEmpty() || trackMetadata.getTitle().isEmpty()) { parseMetadataFromFileName(&trackMetadata, m_pTrack->getFileInfo().fileName()); } // Dump the trackMetadata extracted from the file back into the track. m_pTrack->setTrackMetadata(trackMetadata, parsedFromFile); if (parsedCoverArt) { m_pTrack->setCoverInfo(coverInfoRelative); } }
void SoundSourceProxy::updateTrackFromSource( ImportTrackMetadataMode importTrackMetadataMode) const { DEBUG_ASSERT(m_pTrack); if (getUrl().isEmpty()) { // Silently skip tracks without a corresponding file return; // abort } if (!m_pSoundSource) { kLogger.warning() << "Unable to update track from unsupported file type" << getUrl().toString(); return; // abort } // The SoundSource provides the actual type of the corresponding file m_pTrack->setType(m_pSoundSource->getType()); // Use the existing track metadata as default values. Otherwise // existing values in the library would be overwritten with empty // values if the corresponding file tags are missing. Depending // on the file type some kind of tags might even not be supported // at all and this information would get lost entirely otherwise! mixxx::TrackMetadata trackMetadata; bool metadataSynchronized = false; m_pTrack->getTrackMetadata(&trackMetadata, &metadataSynchronized); // If the file tags have already been parsed at least once, the // existing track metadata should not be updated implicitly, i.e. // if the user did not explicitly choose to (re-)import metadata // explicitly from this file. if (metadataSynchronized && (importTrackMetadataMode == ImportTrackMetadataMode::Once)) { if (kLogger.debugEnabled()) { kLogger.debug() << "Skip importing of track metadata and embedded cover art from file" << getUrl().toString(); } return; // abort } // Embedded cover art is imported together with the track's metadata. // But only if the user has not selected external cover art for this // track! QImage coverImg; DEBUG_ASSERT(coverImg.isNull()); QImage* pCoverImg; // pointer is also used as a flag const CoverInfoRelative coverInfoOld = m_pTrack->getCoverInfo(); // Only re-import cover art if it is save to update, e.g. never // discard a users's custom choice! We are using a whitelisting // filter here that explicitly checks all valid preconditions // when it is permissible to update/replace existing cover art. if (((coverInfoOld.source == CoverInfo::UNKNOWN) || (coverInfoOld.source == CoverInfo::GUESSED)) && ((coverInfoOld.type == CoverInfo::NONE) || (coverInfoOld.type == CoverInfo::METADATA))) { // Should import and update embedded cover art pCoverImg = &coverImg; } else { if (kLogger.debugEnabled()) { kLogger.debug() << "Skip importing of embedded cover art from file" << getUrl().toString(); } // Skip import of embedded cover art pCoverImg = nullptr; } // Parse the tags stored in the audio file auto metadataImported = m_pSoundSource->importTrackMetadataAndCoverImage( &trackMetadata, pCoverImg); if (metadataImported.first == mixxx::MetadataSource::ImportResult::Failed) { kLogger.warning() << "Failed to import track metadata" << (pCoverImg ? "and embedded cover art" : "") << "from file" << getUrl().toString(); // Continue for now, but the abort may follow shortly if the // track already has metadata (see below) } if (metadataSynchronized) { // Metadata has been synchronized successfully at least // once in the past. Only overwrite this information if // new data has actually been imported, otherwise abort // and preserve the existing data! if (metadataImported.first != mixxx::MetadataSource::ImportResult::Succeeded) { return; // abort } if (kLogger.debugEnabled()) { kLogger.debug() << "Updating track metadata" << (pCoverImg ? "and embedded cover art" : "") << "from file" << getUrl().toString(); } } else { DEBUG_ASSERT(pCoverImg); if (kLogger.debugEnabled()) { kLogger.debug() << "Initializing track metadata and embedded cover art from file" << getUrl().toString(); } } // Fallback: If artist or title fields are blank then try to populate // them from the file name. This might happen if tags are unavailable, // unreadable, or partially/completely missing. if (trackMetadata.getTrackInfo().getArtist().isEmpty() || trackMetadata.getTrackInfo().getTitle().isEmpty()) { kLogger.info() << "Adding missing artist/title from file name" << getUrl().toString(); if (parseMetadataFromFileName(&trackMetadata, m_pTrack->getFileInfo().fileName()) && metadataImported.second.isNull()) { // Since this is also some kind of metadata import, we mark the // track's metadata as synchronized with the time stamp of the file. metadataImported.second = m_pTrack->getFileInfo().lastModified(); } } m_pTrack->setTrackMetadata(trackMetadata, metadataImported.second); if (pCoverImg) { // If the pointer is not null then the cover art should be guessed // from the embedded metadata CoverInfoRelative coverInfoNew; DEBUG_ASSERT(coverInfoNew.coverLocation.isNull()); if (pCoverImg->isNull()) { // Cover art will be cleared DEBUG_ASSERT(coverInfoNew.source == CoverInfo::UNKNOWN); DEBUG_ASSERT(coverInfoNew.type == CoverInfo::NONE); if (kLogger.debugEnabled()) { kLogger.debug() << "No embedded cover art found in file" << getUrl().toString(); } } else { coverInfoNew.source = CoverInfo::GUESSED; coverInfoNew.type = CoverInfo::METADATA; // TODO(XXX) here we may introduce a duplicate hash code coverInfoNew.hash = CoverArtUtils::calculateHash(coverImg); if (kLogger.debugEnabled()) { kLogger.debug() << "Embedded cover art found in file" << getUrl().toString(); } } m_pTrack->setCoverInfo(coverInfoNew); } }