diff options
author | Jan Holthuis <jan.holthuis@ruhr-uni-bochum.de> | 2020-12-07 21:29:21 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-07 21:29:21 +0100 |
commit | 21461e28592d1f0de140e60c66651e0854e94414 (patch) | |
tree | 2bf7dc2823147042e0cd6c80af60bf9194c936a5 /src/track | |
parent | 6b5da3c00ca0ae845ff954e119025e3d4b73613e (diff) | |
parent | 8a0d754655c374a69353bda7a138ecda28524ed6 (diff) |
Merge pull request #3413 from uklotzde/track-metadata-export-with-stream-info
Export track metadata with accurate stream info
Diffstat (limited to 'src/track')
-rw-r--r-- | src/track/taglib/trackmetadata_file.cpp | 26 | ||||
-rw-r--r-- | src/track/track.cpp | 127 | ||||
-rw-r--r-- | src/track/track.h | 18 | ||||
-rw-r--r-- | src/track/trackmetadata.cpp | 71 | ||||
-rw-r--r-- | src/track/trackmetadata.h | 23 |
5 files changed, 140 insertions, 125 deletions
diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index d9e95a6b66..f3b1ee7ff0 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -25,22 +25,22 @@ void readAudioProperties( DEBUG_ASSERT(pTrackMetadata); // NOTE(uklotzde): All audio properties will be updated - // with the actual (and more precise) values when reading - // the audio data for this track. Often those properties - // stored in tags don't match with the corresponding - // audio data in the file. - pTrackMetadata->setChannelCount( - audio::ChannelCount(audioProperties.channels())); - pTrackMetadata->setSampleRate( - audio::SampleRate(audioProperties.sampleRate())); - pTrackMetadata->setBitrate( - audio::Bitrate(audioProperties.bitrate())); + // with the actual (and more precise) values when opening + // the audio source for this track. Often those properties + // stored in tags are imprecise and don't match the actual + // audio data of the stream. + pTrackMetadata->setStreamInfo(audio::StreamInfo { + audio::SignalInfo{ + audio::ChannelCount(audioProperties.channels()), + audio::SampleRate(audioProperties.sampleRate()), + }, + audio::Bitrate(audioProperties.bitrate()), #if (TAGLIB_HAS_LENGTH_IN_MILLISECONDS) - const auto duration = Duration::fromMillis(audioProperties.lengthInMilliseconds()); + Duration::fromMillis(audioProperties.lengthInMilliseconds()), #else - const auto duration = Duration::fromSeconds(audioProperties.length()); + Duration::fromSeconds(audioProperties.length()), #endif - pTrackMetadata->setDuration(duration); + }); } } // anonymous namespace diff --git a/src/track/track.cpp b/src/track/track.cpp index de08c4ff4f..54e24bd75a 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -1,7 +1,6 @@ #include "track/track.h" #include <QDirIterator> -#include <QMutexLocker> #include <atomic> #include "engine/engine.h" @@ -420,18 +419,18 @@ void Track::setDateAdded(const QDateTime& dateAdded) { void Track::setDuration(mixxx::Duration duration) { QMutexLocker lock(&m_qMutex); - VERIFY_OR_DEBUG_ASSERT(!m_streamInfo || - m_streamInfo->getDuration() <= mixxx::Duration::empty() || - m_streamInfo->getDuration() == duration) { + VERIFY_OR_DEBUG_ASSERT(!m_streamInfoFromSource || + m_streamInfoFromSource->getDuration() <= mixxx::Duration::empty() || + m_streamInfoFromSource->getDuration() == duration) { kLogger.warning() << "Cannot override stream duration:" - << m_streamInfo->getDuration() + << m_streamInfoFromSource->getDuration() << "->" << duration; return; } if (compareAndSet( - m_record.refMetadata().ptrDuration(), + m_record.refMetadata().refStreamInfo().ptrDuration(), duration)) { markDirtyAndUnlock(&lock); } @@ -443,25 +442,19 @@ void Track::setDuration(double duration) { double Track::getDuration(DurationRounding rounding) const { QMutexLocker lock(&m_qMutex); + const auto durationSeconds = + m_record.getMetadata().getStreamInfo().getDuration().toDoubleSeconds(); switch (rounding) { case DurationRounding::SECONDS: - return std::round(m_record.getMetadata().getDuration().toDoubleSeconds()); + return std::round(durationSeconds); default: - return m_record.getMetadata().getDuration().toDoubleSeconds(); + return durationSeconds; } } QString Track::getDurationText(mixxx::Duration::Precision precision) const { - double duration; - if (precision == mixxx::Duration::Precision::SECONDS) { - // Round to full seconds before formatting for consistency: - // getDurationText() should always display the same number - // as getDuration(DurationRounding::SECONDS) = getDurationInt() - duration = getDuration(DurationRounding::SECONDS); - } else { - duration = getDuration(DurationRounding::NONE); - } - return mixxx::Duration::formatTime(duration, precision); + QMutexLocker lock(&m_qMutex); + return m_record.getMetadata().getDurationText(precision); } QString Track::getTitle() const { @@ -654,38 +647,39 @@ void Track::setType(const QString& sType) { int Track::getSampleRate() const { QMutexLocker lock(&m_qMutex); - return m_record.getMetadata().getSampleRate(); + return m_record.getMetadata().getStreamInfo().getSignalInfo().getSampleRate(); } int Track::getChannels() const { QMutexLocker lock(&m_qMutex); - return m_record.getMetadata().getChannelCount(); + return m_record.getMetadata().getStreamInfo().getSignalInfo().getChannelCount(); } int Track::getBitrate() const { QMutexLocker lock(&m_qMutex); - return m_record.getMetadata().getBitrate(); + return m_record.getMetadata().getStreamInfo().getBitrate(); } QString Track::getBitrateText() const { - return QString("%1").arg(getBitrate()); + QMutexLocker lock(&m_qMutex); + return m_record.getMetadata().getBitrateText(); } void Track::setBitrate(int iBitrate) { QMutexLocker lock(&m_qMutex); const mixxx::audio::Bitrate bitrate(iBitrate); - VERIFY_OR_DEBUG_ASSERT(!m_streamInfo || - !m_streamInfo->getBitrate().isValid() || - m_streamInfo->getBitrate() == bitrate) { + VERIFY_OR_DEBUG_ASSERT(!m_streamInfoFromSource || + !m_streamInfoFromSource->getBitrate().isValid() || + m_streamInfoFromSource->getBitrate() == bitrate) { kLogger.warning() << "Cannot override stream bitrate:" - << m_streamInfo->getBitrate() + << m_streamInfoFromSource->getBitrate() << "->" << bitrate; return; } if (compareAndSet( - m_record.refMetadata().ptrBitrate(), + m_record.refMetadata().refStreamInfo().ptrBitrate(), bitrate)) { markDirtyAndUnlock(&lock); } @@ -796,10 +790,10 @@ void Track::setCuePoint(CuePosition cue) { void Track::shiftCuePositionsMillis(double milliseconds) { QMutexLocker lock(&m_qMutex); - VERIFY_OR_DEBUG_ASSERT(m_streamInfo) { + VERIFY_OR_DEBUG_ASSERT(m_streamInfoFromSource) { return; } - double frames = m_streamInfo->getSignalInfo().millis2frames(milliseconds); + double frames = m_streamInfoFromSource->getSignalInfo().millis2frames(milliseconds); for (const CuePointer& pCue : qAsConst(m_cuePoints)) { pCue->shiftPositionFrames(frames); } @@ -930,7 +924,7 @@ Track::ImportStatus Track::importBeats( // existing cue points. m_pBeatsImporterPending.reset(); return ImportStatus::Complete; - } else if (m_streamInfo) { + } else if (m_streamInfoFromSource) { // Replace existing cue points with imported cue // points immediately importPendingBeatsMarkDirtyAndUnlock(&lock); @@ -964,14 +958,14 @@ bool Track::importPendingBeatsWhileLocked() { } // The sample rate can only be trusted after the audio // stream has been opened. - DEBUG_ASSERT(m_streamInfo); + DEBUG_ASSERT(m_streamInfoFromSource); // The sample rate is supposed to be consistent - DEBUG_ASSERT(m_streamInfo->getSignalInfo().getSampleRate() == - m_record.getMetadata().getSampleRate()); + DEBUG_ASSERT(m_streamInfoFromSource->getSignalInfo().getSampleRate() == + m_record.getMetadata().getStreamInfo().getSignalInfo().getSampleRate()); mixxx::BeatsPointer pBeats(new mixxx::BeatMap(*this, - static_cast<SINT>(m_streamInfo->getSignalInfo().getSampleRate()), + static_cast<SINT>(m_streamInfoFromSource->getSignalInfo().getSampleRate()), m_pBeatsImporterPending->importBeatsAndApplyTimingOffset( - getLocation(), *m_streamInfo))); + getLocation(), *m_streamInfoFromSource))); DEBUG_ASSERT(m_pBeatsImporterPending->isEmpty()); m_pBeatsImporterPending.reset(); return setBeatsWhileLocked(pBeats); @@ -1005,7 +999,7 @@ Track::ImportStatus Track::importCueInfos( // existing cue points. m_pCueInfoImporterPending.reset(); return ImportStatus::Complete; - } else if (m_streamInfo) { + } else if (m_streamInfoFromSource) { // Replace existing cue points with imported cue // points immediately importPendingCueInfosMarkDirtyAndUnlock(&lock); @@ -1092,18 +1086,18 @@ bool Track::importPendingCueInfosWhileLocked() { } // The sample rate can only be trusted after the audio // stream has been opened. - DEBUG_ASSERT(m_streamInfo); + DEBUG_ASSERT(m_streamInfoFromSource); const auto sampleRate = - m_streamInfo->getSignalInfo().getSampleRate(); + m_streamInfoFromSource->getSignalInfo().getSampleRate(); // The sample rate is supposed to be consistent DEBUG_ASSERT(sampleRate == - m_record.getMetadata().getSampleRate()); + m_record.getMetadata().getStreamInfo().getSignalInfo().getSampleRate()); const auto trackId = m_record.getId(); QList<CuePointer> cuePoints; cuePoints.reserve(m_pCueInfoImporterPending->size()); const auto cueInfos = m_pCueInfoImporterPending->importCueInfosAndApplyTimingOffset( - getLocation(), m_streamInfo->getSignalInfo()); + getLocation(), m_streamInfoFromSource->getSignalInfo()); for (const auto& cueInfo : cueInfos) { CuePointer pCue(new Cue(cueInfo, sampleRate, true)); // While this method could be called from any thread, @@ -1453,48 +1447,45 @@ void Track::setAudioProperties( mixxx::audio::SampleRate sampleRate, mixxx::audio::Bitrate bitrate, mixxx::Duration duration) { + setAudioProperties(mixxx::audio::StreamInfo{ + mixxx::audio::SignalInfo{ + channelCount, + sampleRate, + }, + bitrate, + duration, + }); +} + +void Track::setAudioProperties( + const mixxx::audio::StreamInfo& streamInfo) { QMutexLocker lock(&m_qMutex); - DEBUG_ASSERT(!m_streamInfo); - bool dirty = false; - if (compareAndSet( - m_record.refMetadata().ptrChannelCount(), - channelCount)) { - dirty = true; - } + // These properties are stored separately in the database + // and are also imported from file tags. They will be + // overriden by the actual properties from the audio + // source later. + DEBUG_ASSERT(!m_streamInfoFromSource); if (compareAndSet( - m_record.refMetadata().ptrSampleRate(), - sampleRate)) { - dirty = true; - } - if (compareAndSet( - m_record.refMetadata().ptrBitrate(), - bitrate)) { - dirty = true; - } - if (compareAndSet( - m_record.refMetadata().ptrDuration(), - duration)) { - dirty = true; - } - if (dirty) { + m_record.refMetadata().ptrStreamInfo(), + streamInfo)) { markDirtyAndUnlock(&lock); } } -void Track::updateAudioPropertiesFromStream( +void Track::updateStreamInfoFromSource( mixxx::audio::StreamInfo&& streamInfo) { QMutexLocker lock(&m_qMutex); - VERIFY_OR_DEBUG_ASSERT(!m_streamInfo || - *m_streamInfo == streamInfo) { + VERIFY_OR_DEBUG_ASSERT(!m_streamInfoFromSource || + *m_streamInfoFromSource == streamInfo) { kLogger.warning() << "Varying stream properties:" - << *m_streamInfo + << *m_streamInfoFromSource << "->" << streamInfo; } - bool updated = m_record.refMetadata().updateAudioPropertiesFromStream( + bool updated = m_record.refMetadata().updateStreamInfoFromSource( streamInfo); - m_streamInfo = std::make_optional(std::move(streamInfo)); + m_streamInfoFromSource = std::make_optional(std::move(streamInfo)); bool importBeats = m_pBeatsImporterPending && !m_pBeatsImporterPending->isEmpty(); bool importCueInfos = m_pCueInfoImporterPending && !m_pCueInfoImporterPending->isEmpty(); diff --git a/src/track/track.h b/src/track/track.h index 84da9037ee..a229852c5c 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -2,6 +2,7 @@ #include <QList> #include <QMutex> +#include <QMutexLocker> #include <QObject> #include <QUrl> @@ -348,6 +349,8 @@ class Track : public QObject { mixxx::audio::SampleRate sampleRate, mixxx::audio::Bitrate bitrate, mixxx::Duration duration); + void setAudioProperties( + const mixxx::audio::StreamInfo& streamInfo); signals: void waveformUpdated(); @@ -430,10 +433,15 @@ class Track : public QObject { mixxx::MetadataSourcePointer pMetadataSource); // Information about the actual properties of the - // audio stream is only available after opening it. - // On this occasion the audio properties of the track - // need to be updated to reflect these values. - void updateAudioPropertiesFromStream( + // audio stream is only available after opening the + // source at least once. On this occasion the metadata + // stream info of the track need to be updated to reflect + // these values. + bool hasStreamInfoFromSource() const { + QMutexLocker lock(&m_qMutex); + return static_cast<bool>(m_streamInfoFromSource); + } + void updateStreamInfoFromSource( mixxx::audio::StreamInfo&& streamInfo); // Mutex protecting access to object @@ -457,7 +465,7 @@ class Track : public QObject { // Reliable information about the PCM audio stream // that only becomes available when opening the // corresponding file. - std::optional<mixxx::audio::StreamInfo> m_streamInfo; + std::optional<mixxx::audio::StreamInfo> m_streamInfoFromSource; // The list of cue points for the track QList<CuePointer> m_cuePoints; diff --git a/src/track/trackmetadata.cpp b/src/track/trackmetadata.cpp index 692326ea0b..b36c00fb38 100644 --- a/src/track/trackmetadata.cpp +++ b/src/track/trackmetadata.cpp @@ -13,68 +13,91 @@ const Logger kLogger("TrackMetadata"); /*static*/ constexpr int TrackMetadata::kCalendarYearInvalid; -bool TrackMetadata::updateAudioPropertiesFromStream( +bool TrackMetadata::updateStreamInfoFromSource( const audio::StreamInfo& streamInfo) { bool changed = false; const auto streamChannelCount = streamInfo.getSignalInfo().getChannelCount(); if (streamChannelCount.isValid() && - streamChannelCount != getChannelCount()) { - if (getChannelCount().isValid()) { + streamChannelCount != getStreamInfo().getSignalInfo().getChannelCount()) { + if (getStreamInfo().getSignalInfo().getChannelCount().isValid()) { kLogger.debug() << "Modifying channel count:" - << getChannelCount() + << getStreamInfo().getSignalInfo().getChannelCount() << "->" << streamChannelCount; } - setChannelCount(streamChannelCount); + refStreamInfo().refSignalInfo().setChannelCount(streamChannelCount); changed = true; } const auto streamSampleRate = streamInfo.getSignalInfo().getSampleRate(); if (streamSampleRate.isValid() && - streamSampleRate != getSampleRate()) { - if (getSampleRate().isValid()) { + streamSampleRate != getStreamInfo().getSignalInfo().getSampleRate()) { + if (getStreamInfo().getSignalInfo().getSampleRate().isValid()) { kLogger.debug() << "Modifying sample rate:" - << getSampleRate() + << getStreamInfo().getSignalInfo().getSampleRate() << "->" << streamSampleRate; } - setSampleRate(streamSampleRate); + refStreamInfo().refSignalInfo().setSampleRate(streamSampleRate); changed = true; } const auto streamBitrate = streamInfo.getBitrate(); if (streamBitrate.isValid() && - streamBitrate != getBitrate()) { - if (getBitrate().isValid()) { + streamBitrate != getStreamInfo().getBitrate()) { + if (getStreamInfo().getSignalInfo().isValid()) { kLogger.debug() << "Modifying bitrate:" - << getBitrate() + << getStreamInfo().getSignalInfo() << "->" << streamBitrate; } - setBitrate(streamBitrate); + refStreamInfo().setBitrate(streamBitrate); changed = true; } const auto streamDuration = streamInfo.getDuration(); if (streamDuration > Duration::empty() && - streamDuration != getDuration()) { - if (getDuration() > Duration::empty()) { + streamDuration != getStreamInfo().getDuration()) { + if (getStreamInfo().getDuration() > Duration::empty()) { kLogger.debug() << "Modifying duration:" - << getDuration() + << getStreamInfo().getDuration() << "->" << streamDuration; } - setDuration(streamDuration); + refStreamInfo().setDuration(streamDuration); changed = true; } return changed; } +QString TrackMetadata::getBitrateText() const { + if (!getStreamInfo().getBitrate().isValid()) { + return QString(); + } + return QString::number(getStreamInfo().getBitrate()) + + QChar(' ') + + audio::Bitrate::unit(); +} + +QString TrackMetadata::getDurationText( + Duration::Precision precision) const { + double durationSeconds; + if (precision == Duration::Precision::SECONDS) { + // Round to full seconds before formatting for consistency + // getDurationText() should always display the same number + // as getDurationSecondsRounded() + durationSeconds = getDurationSecondsRounded(); + } else { + durationSeconds = getStreamInfo().getDuration().toDoubleSeconds(); + } + return Duration::formatTime(durationSeconds, precision); +} + int TrackMetadata::parseCalendarYear(const QString& year, bool* pValid) { const QDateTime dateTime(parseDateTime(year)); if (0 < dateTime.date().year()) { @@ -154,22 +177,16 @@ bool TrackMetadata::anyFileTagsModified( } bool operator==(const TrackMetadata& lhs, const TrackMetadata& rhs) { - return lhs.getAlbumInfo() == rhs.getAlbumInfo() && - lhs.getTrackInfo() == rhs.getTrackInfo() && - lhs.getChannelCount() == rhs.getChannelCount() && - lhs.getSampleRate() == rhs.getSampleRate() && - lhs.getBitrate() == rhs.getBitrate() && - lhs.getDuration() == rhs.getDuration(); + return lhs.getStreamInfo() == rhs.getStreamInfo() && + lhs.getAlbumInfo() == rhs.getAlbumInfo() && + lhs.getTrackInfo() == rhs.getTrackInfo(); } QDebug operator<<(QDebug dbg, const TrackMetadata& arg) { dbg << "TrackMetadata{"; + arg.dbgStreamInfo(dbg); arg.dbgTrackInfo(dbg); arg.dbgAlbumInfo(dbg); - arg.dbgBitrate(dbg); - arg.dbgChannelCount(dbg); - arg.dbgDuration(dbg); - arg.dbgSampleRate(dbg); dbg << '}'; return dbg; } diff --git a/src/track/trackmetadata.h b/src/track/trackmetadata.h index bae8ee188e..e01dc1b9cd 100644 --- a/src/track/trackmetadata.h +++ b/src/track/trackmetadata.h @@ -2,27 +2,18 @@ #include <QDateTime> -#include "audio/types.h" +#include "audio/streaminfo.h" #include "track/albuminfo.h" #include "track/trackinfo.h" namespace mixxx { -namespace audio { - -class StreamInfo; - -} // namespace audio - class TrackMetadata final { // Audio properties // - read-only // - stored in file tags // - adjusted when opening the audio stream (if available) - MIXXX_DECL_PROPERTY(audio::ChannelCount, channels, ChannelCount) - MIXXX_DECL_PROPERTY(audio::SampleRate, sampleRate, SampleRate) - MIXXX_DECL_PROPERTY(audio::Bitrate, bitrate, Bitrate) - MIXXX_DECL_PROPERTY(Duration, duration, Duration) + MIXXX_DECL_PROPERTY(audio::StreamInfo, streamInfo, StreamInfo) // Track properties // - read-write @@ -39,7 +30,7 @@ class TrackMetadata final { TrackMetadata& operator=(TrackMetadata&&) = default; TrackMetadata& operator=(const TrackMetadata&) = default; - bool updateAudioPropertiesFromStream( + bool updateStreamInfoFromSource( const audio::StreamInfo& streamInfo); // Adjusts floating-point values to match their string representation @@ -57,6 +48,14 @@ class TrackMetadata final { const TrackMetadata& importedFromFile, Bpm::Comparison cmpBpm = Bpm::Comparison::Default) const; + QString getBitrateText() const; + + double getDurationSecondsRounded() const { + return std::round(getStreamInfo().getDuration().toDoubleSeconds()); + } + QString getDurationText( + Duration::Precision precision) const; + // Parse an format date/time values according to ISO 8601 static QDate parseDate(const QString& str) { return QDate::fromString(str.trimmed().replace(" ", ""), Qt::ISODate); |