diff options
Diffstat (limited to 'src/track')
30 files changed, 1192 insertions, 339 deletions
diff --git a/src/track/beatfactory.cpp b/src/track/beatfactory.cpp index 92b2fd0591..a5fb85ff9d 100644 --- a/src/track/beatfactory.cpp +++ b/src/track/beatfactory.cpp @@ -6,35 +6,36 @@ #include "track/beatfactory.h" #include "track/beatutils.h" -BeatsPointer BeatFactory::loadBeatsFromByteArray(const Track& track, - QString beatsVersion, - QString beatsSubVersion, - const QByteArray& beatsSerialized) { +mixxx::BeatsPointer BeatFactory::loadBeatsFromByteArray(const Track& track, + QString beatsVersion, + QString beatsSubVersion, + const QByteArray& beatsSerialized) { if (beatsVersion == BEAT_GRID_1_VERSION || beatsVersion == BEAT_GRID_2_VERSION) { - BeatGrid* pGrid = new BeatGrid(track, 0, beatsSerialized); + mixxx::BeatGrid* pGrid = new mixxx::BeatGrid(track, 0, beatsSerialized); pGrid->setSubVersion(beatsSubVersion); qDebug() << "Successfully deserialized BeatGrid"; - return BeatsPointer(pGrid, &BeatFactory::deleteBeats); + return mixxx::BeatsPointer(pGrid, &BeatFactory::deleteBeats); } else if (beatsVersion == BEAT_MAP_VERSION) { - BeatMap* pMap = new BeatMap(track, 0, beatsSerialized); + mixxx::BeatMap* pMap = new mixxx::BeatMap(track, 0, beatsSerialized); pMap->setSubVersion(beatsSubVersion); qDebug() << "Successfully deserialized BeatMap"; - return BeatsPointer(pMap, &BeatFactory::deleteBeats); + return mixxx::BeatsPointer(pMap, &BeatFactory::deleteBeats); } qDebug() << "BeatFactory::loadBeatsFromByteArray could not parse serialized beats."; - return BeatsPointer(); + return mixxx::BeatsPointer(); } -BeatsPointer BeatFactory::makeBeatGrid(const Track& track, double dBpm, - double dFirstBeatSample) { - BeatGrid* pGrid = new BeatGrid(track, 0); +mixxx::BeatsPointer BeatFactory::makeBeatGrid( + const Track& track, double dBpm, double dFirstBeatSample) { + mixxx::BeatGrid* pGrid = new mixxx::BeatGrid(track, 0); pGrid->setGrid(dBpm, dFirstBeatSample); - return BeatsPointer(pGrid, &BeatFactory::deleteBeats); + return mixxx::BeatsPointer(pGrid, &BeatFactory::deleteBeats); } // static -QString BeatFactory::getPreferredVersion(const bool bEnableFixedTempoCorrection) { +QString BeatFactory::getPreferredVersion( + const bool bEnableFixedTempoCorrection) { if (bEnableFixedTempoCorrection) { return BEAT_GRID_2_VERSION; } @@ -42,55 +43,64 @@ QString BeatFactory::getPreferredVersion(const bool bEnableFixedTempoCorrection) } QString BeatFactory::getPreferredSubVersion( - const bool bEnableFixedTempoCorrection, - const bool bEnableOffsetCorrection, - const int iMinBpm, const int iMaxBpm, - const QHash<QString, QString> extraVersionInfo) { + const bool bEnableFixedTempoCorrection, + const bool bEnableOffsetCorrection, + const int iMinBpm, + const int iMaxBpm, + const QHash<QString, QString> extraVersionInfo) { const char* kSubVersionKeyValueSeparator = "="; const char* kSubVersionFragmentSeparator = "|"; QStringList fragments; // min/max BPM limits only apply to fixed-tempo assumption if (bEnableFixedTempoCorrection) { - fragments << QString("min_bpm%1%2").arg(kSubVersionKeyValueSeparator, - QString::number(iMinBpm)); - fragments << QString("max_bpm%1%2").arg(kSubVersionKeyValueSeparator, - QString::number(iMaxBpm)); + fragments << QString("min_bpm%1%2") + .arg(kSubVersionKeyValueSeparator, + QString::number(iMinBpm)); + fragments << QString("max_bpm%1%2") + .arg(kSubVersionKeyValueSeparator, + QString::number(iMaxBpm)); } QHashIterator<QString, QString> it(extraVersionInfo); while (it.hasNext()) { it.next(); if (it.key().contains(kSubVersionKeyValueSeparator) || - it.key().contains(kSubVersionFragmentSeparator) || - it.value().contains(kSubVersionKeyValueSeparator) || - it.value().contains(kSubVersionFragmentSeparator)) { - qDebug() << "ERROR: Your analyzer key/value contains invalid characters:" + it.key().contains(kSubVersionFragmentSeparator) || + it.value().contains(kSubVersionKeyValueSeparator) || + it.value().contains(kSubVersionFragmentSeparator)) { + qDebug() << "ERROR: Your analyzer key/value contains invalid " + "characters:" << it.key() << ":" << it.value() << "Skipping."; continue; } fragments << QString("%1%2%3").arg( - it.key(), kSubVersionKeyValueSeparator, it.value()); + it.key(), kSubVersionKeyValueSeparator, it.value()); } if (bEnableFixedTempoCorrection && bEnableOffsetCorrection) { fragments << QString("offset_correction%1%2") - .arg(kSubVersionKeyValueSeparator, QString::number(1)); + .arg(kSubVersionKeyValueSeparator, + QString::number(1)); } - fragments << QString("rounding%1%2"). - arg(kSubVersionKeyValueSeparator, QString::number(0.05)); + fragments << QString("rounding%1%2") + .arg(kSubVersionKeyValueSeparator, + QString::number(0.05)); std::sort(fragments.begin(), fragments.end()); - return (fragments.size() > 0) ? fragments.join(kSubVersionFragmentSeparator) : ""; + return (fragments.size() > 0) ? fragments.join(kSubVersionFragmentSeparator) + : ""; } - -BeatsPointer BeatFactory::makePreferredBeats( - const Track& track, QVector<double> beats, - const QHash<QString, QString> extraVersionInfo, - const bool bEnableFixedTempoCorrection, const bool bEnableOffsetCorrection, - const int iSampleRate, const int iTotalSamples, - const int iMinBpm, const int iMaxBpm) { +mixxx::BeatsPointer BeatFactory::makePreferredBeats(const Track& track, + QVector<double> beats, + const QHash<QString, QString> extraVersionInfo, + const bool bEnableFixedTempoCorrection, + const bool bEnableOffsetCorrection, + const int iSampleRate, + const int iTotalSamples, + const int iMinBpm, + const int iMaxBpm) { const QString version = getPreferredVersion(bEnableFixedTempoCorrection); const QString subVersion = getPreferredSubVersion(bEnableFixedTempoCorrection, bEnableOffsetCorrection, @@ -103,22 +113,22 @@ BeatsPointer BeatFactory::makePreferredBeats( double firstBeat = BeatUtils::calculateFixedTempoFirstBeat( bEnableOffsetCorrection, beats, iSampleRate, iTotalSamples, globalBpm); - BeatGrid* pGrid = new BeatGrid(track, iSampleRate); + mixxx::BeatGrid* pGrid = new mixxx::BeatGrid(track, iSampleRate); // firstBeat is in frames here and setGrid() takes samples. pGrid->setGrid(globalBpm, firstBeat * 2); pGrid->setSubVersion(subVersion); - return BeatsPointer(pGrid, &BeatFactory::deleteBeats); + return mixxx::BeatsPointer(pGrid, &BeatFactory::deleteBeats); } else if (version == BEAT_MAP_VERSION) { - BeatMap* pBeatMap = new BeatMap(track, iSampleRate, beats); + mixxx::BeatMap* pBeatMap = new mixxx::BeatMap(track, iSampleRate, beats); pBeatMap->setSubVersion(subVersion); - return BeatsPointer(pBeatMap, &BeatFactory::deleteBeats); + return mixxx::BeatsPointer(pBeatMap, &BeatFactory::deleteBeats); } else { qDebug() << "ERROR: Could not determine what type of beatgrid to create."; - return BeatsPointer(); + return mixxx::BeatsPointer(); } } -void BeatFactory::deleteBeats(Beats* pBeats) { +void BeatFactory::deleteBeats(mixxx::Beats* pBeats) { // BeatGrid/BeatMap objects have no parent and live in the same thread as // their associated TIO. QObject::deleteLater does not have the desired // effect when the QObject's thread does not have an event loop (i.e. when diff --git a/src/track/beatfactory.h b/src/track/beatfactory.h index 5e2eba252e..cb25021f19 100644 --- a/src/track/beatfactory.h +++ b/src/track/beatfactory.h @@ -8,12 +8,13 @@ class BeatFactory { public: - static BeatsPointer loadBeatsFromByteArray(const Track& track, - QString beatsVersion, - QString beatsSubVersion, - const QByteArray& beatsSerialized); - static BeatsPointer makeBeatGrid(const Track& track, - double dBpm, double dFirstBeatSample); + static mixxx::BeatsPointer loadBeatsFromByteArray(const Track& track, + QString beatsVersion, + QString beatsSubVersion, + const QByteArray& beatsSerialized); + static mixxx::BeatsPointer makeBeatGrid(const Track& track, + double dBpm, + double dFirstBeatSample); static QString getPreferredVersion(const bool bEnableFixedTempoCorrection); @@ -23,16 +24,18 @@ class BeatFactory { const int iMinBpm, const int iMaxBpm, const QHash<QString, QString> extraVersionInfo); - static BeatsPointer makePreferredBeats( - const Track& track, QVector<double> beats, - const QHash<QString, QString> extraVersionInfo, - const bool bEnableFixedTempoCorrection, - const bool bEnableOffsetCorrection, - const int iSampleRate, const int iTotalSamples, - const int iMinBpm, const int iMaxBpm); + static mixxx::BeatsPointer makePreferredBeats(const Track& track, + QVector<double> beats, + const QHash<QString, QString> extraVersionInfo, + const bool bEnableFixedTempoCorrection, + const bool bEnableOffsetCorrection, + const int iSampleRate, + const int iTotalSamples, + const int iMinBpm, + const int iMaxBpm); private: - static void deleteBeats(Beats* pBeats); + static void deleteBeats(mixxx::Beats* pBeats); }; #endif /* BEATFACTORY_H */ diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index c76b015393..815314f736 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -11,6 +11,8 @@ struct BeatGridData { double firstBeat; }; +namespace mixxx { + class BeatGridIterator : public BeatIterator { public: BeatGridIterator(double dBeatLength, double dFirstBeat, double dEndSample) @@ -362,3 +364,5 @@ void BeatGrid::setBpm(double dBpm) { locker.unlock(); emit updated(); } + +} // namespace mixxx diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h index d671129f7f..f1f09faf28 100644 --- a/src/track/beatgrid.h +++ b/src/track/beatgrid.h @@ -10,6 +10,8 @@ #define BEAT_GRID_1_VERSION "BeatGrid-1.0" #define BEAT_GRID_2_VERSION "BeatGrid-2.0" +namespace mixxx { + // BeatGrid is an implementation of the Beats interface that implements an // infinite grid of beats, aligned to a song simply by a starting offset of the // first beat and the song's average beats-per-minute. @@ -94,5 +96,6 @@ class BeatGrid final : public Beats { double m_dBeatLength; }; +} // namespace mixxx #endif /* BEATGRID_H */ diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index e75ce2da0b..661f79e9f9 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -30,6 +30,8 @@ bool BeatLessThan(const Beat& beat1, const Beat& beat2) { return beat1.frame_position() < beat2.frame_position(); } +namespace mixxx { + class BeatMapIterator : public BeatIterator { public: BeatMapIterator(BeatList::const_iterator start, BeatList::const_iterator end) @@ -723,3 +725,5 @@ double BeatMap::calculateBpm(const Beat& startBeat, const Beat& stopBeat) const return BeatUtils::calculateBpm(beatvect, m_iSampleRate, 0, 9999); } + +} // namespace mixxx diff --git a/src/track/beatmap.h b/src/track/beatmap.h index bb1403eb74..5d1ce7df57 100644 --- a/src/track/beatmap.h +++ b/src/track/beatmap.h @@ -18,6 +18,8 @@ typedef QList<mixxx::track::io::Beat> BeatList; +namespace mixxx { + class BeatMap final : public Beats { public: // Construct a BeatMap. iSampleRate may be provided if a more accurate @@ -109,4 +111,5 @@ class BeatMap final : public Beats { BeatList m_beats; }; +} // namespace mixxx #endif /* BEATMAP_H_ */ diff --git a/src/track/beats.cpp b/src/track/beats.cpp index 80510c4e5d..307f0a6b9a 100644 --- a/src/track/beats.cpp +++ b/src/track/beats.cpp @@ -1,7 +1,7 @@ #include "track/beats.h" - +namespace mixxx { int Beats::numBeatsInRange(double dStartSample, double dEndSample) { double dLastCountedBeat = 0.0; @@ -53,3 +53,4 @@ double Beats::findNBeatsFromSample(double fromSample, double beats) const { return nthBeat; }; +} // namespace mixxx diff --git a/src/track/beats.h b/src/track/beats.h index 5c93d65e43..1ec09096d7 100644 --- a/src/track/beats.h +++ b/src/track/beats.h @@ -14,6 +14,8 @@ namespace { double kMaxBpm = 500; } +namespace mixxx { + class Beats; typedef QSharedPointer<Beats> BeatsPointer; @@ -171,4 +173,5 @@ class Beats : public QObject { void updated(); }; +} // namespace mixxx #endif /* BEATS_H */ diff --git a/src/track/cueinfo.h b/src/track/cueinfo.h index 0ff1b95482..94e38320f6 100644 --- a/src/track/cueinfo.h +++ b/src/track/cueinfo.h @@ -2,6 +2,7 @@ // cueinfo.h // Created 2020-02-28 by Jan Holthuis +#include "audio/signalinfo.h" #include "util/color/rgbcolor.h" #include "util/optional.h" diff --git a/src/track/cueinfoimporter.cpp b/src/track/cueinfoimporter.cpp new file mode 100644 index 0000000000..867b3b8f96 --- /dev/null +++ b/src/track/cueinfoimporter.cpp @@ -0,0 +1,68 @@ +#include "track/cueinfoimporter.h" + +namespace mixxx { + +CueInfoImporter::CueInfoImporter(const QList<CueInfo>& cueInfos) + : m_cueInfos(cueInfos) { +} + +double CueInfoImporter::guessTimingOffsetMillis( + const QString& filePath, + const audio::SignalInfo& signalInfo) const { + Q_UNUSED(filePath); + Q_UNUSED(signalInfo); + return 0; +}; + +void CueInfoImporter::append(const CueInfo& cueInfo) { + m_cueInfos.append(cueInfo); +} + +void CueInfoImporter::append(const QList<CueInfo>& cueInfos) { + m_cueInfos.append(cueInfos); +} + +int CueInfoImporter::size() const { + return m_cueInfos.size(); +} + +bool CueInfoImporter::isEmpty() const { + return m_cueInfos.isEmpty(); +} + +QList<CueInfo> CueInfoImporter::importCueInfosWithCorrectTiming( + const QString& filePath, + const audio::SignalInfo& signalInfo) { + // Consume the collected cue points during the import + QList<CueInfo> cueInfos = m_cueInfos; + m_cueInfos.clear(); + + // Do not calculate offset if we don't have any cues to import + if (cueInfos.isEmpty()) { + return {}; + } + + double timingOffsetMillis = guessTimingOffsetMillis(filePath, signalInfo); + + // If we don't have any offset, we can just return the CueInfo objects + // unchanged. + if (timingOffsetMillis == 0) { + return cueInfos; + } + + // Create list of CueInfo object with correct positions + for (CueInfo& cueInfo : cueInfos) { + if (cueInfo.getStartPositionMillis()) { + cueInfo.setStartPositionMillis( + *cueInfo.getStartPositionMillis() + timingOffsetMillis); + } + if (cueInfo.getEndPositionMillis()) { + cueInfo.setEndPositionMillis( + *cueInfo.getEndPositionMillis() + timingOffsetMillis); + } + } + + return cueInfos; +} + +} // namespace mixxx diff --git a/src/track/cueinfoimporter.h b/src/track/cueinfoimporter.h new file mode 100644 index 0000000000..c2f3648184 --- /dev/null +++ b/src/track/cueinfoimporter.h @@ -0,0 +1,40 @@ +#pragma once + +#include <memory> + +#include "track/cueinfo.h" + +namespace mixxx { + +/// Importer class for CueInfo objects that can correct timing offsets when the +/// signal info (channel number, sample rate, bitrate) is known. +class CueInfoImporter { + public: + CueInfoImporter() = default; + explicit CueInfoImporter(const QList<CueInfo>& cueInfos); + virtual ~CueInfoImporter() = default; + + /// Returns audio signal dependent timing offset correction. + /// The default implementation just returns 0, but this can be overridden + /// in subclasses. + virtual double guessTimingOffsetMillis( + const QString& filePath, + const audio::SignalInfo& signalInfo) const; + + void append(const CueInfo& cueInfo); + void append(const QList<CueInfo>& cueInfos); + + int size() const; + bool isEmpty() const; + + QList<CueInfo> importCueInfosWithCorrectTiming( + const QString& filePath, + const audio::SignalInfo& signalInfo); + + private: + QList<CueInfo> m_cueInfos; +}; + +typedef std::shared_ptr<CueInfoImporter> CueInfoImporterPointer; + +} // namespace mixxx diff --git a/src/track/keyutils.cpp b/src/track/keyutils.cpp index 1a23eec2d9..87c2148542 100644 --- a/src/track/keyutils.cpp +++ b/src/track/keyutils.cpp @@ -407,6 +407,11 @@ ChromaticKey KeyUtils::scaleKeySteps(ChromaticKey key, int key_changes) { // static mixxx::track::io::key::ChromaticKey KeyUtils::calculateGlobalKey( const KeyChangeList& key_changes, const int iTotalSamples, int iSampleRate) { + if (key_changes.size() == 1) { + qDebug() << keyDebugName(key_changes[0].first); + return key_changes[0].first; + } + const int iTotalFrames = iTotalSamples / 2; QMap<mixxx::track::io::key::ChromaticKey, double> key_histogram; diff --git a/src/track/serato/cueinfoimporter.cpp b/src/track/serato/cueinfoimporter.cpp new file mode 100644 index 0000000000..30aaeee56a --- /dev/null +++ b/src/track/serato/cueinfoimporter.cpp @@ -0,0 +1,17 @@ +#include "track/serato/cueinfoimporter.h" + +#include "track/serato/tags.h" + +namespace mixxx { + +/// This method simply calls SeratoTags::guessTimingOffsetMillis() and returns +/// its result. We also need the timing offset for exporting our cues to +/// Serato, so the actual cue offset calculation remains a static method of +/// the SeratoTags for the time being. +double SeratoCueInfoImporter::guessTimingOffsetMillis( + const QString& filePath, + const audio::SignalInfo& signalInfo) const { + return SeratoTags::guessTimingOffsetMillis(filePath, signalInfo); +} + +} // namespace mixxx diff --git a/src/track/serato/cueinfoimporter.h b/src/track/serato/cueinfoimporter.h new file mode 100644 index 0000000000..1310e8bba8 --- /dev/null +++ b/src/track/serato/cueinfoimporter.h @@ -0,0 +1,18 @@ +#pragma once + +#include "track/cueinfoimporter.h" + +namespace mixxx { + +class SeratoCueInfoImporter : public CueInfoImporter { + public: + using CueInfoImporter::CueInfoImporter; + + ~SeratoCueInfoImporter() override = default; + + double guessTimingOffsetMillis( + const QString& filePath, + const audio::SignalInfo& signalInfo) const override; +}; + +} // namespace mixxx diff --git a/src/track/serato/markers.cpp b/src/track/serato/markers.cpp index a9454e7125..ebf0a3d904 100644 --- a/src/track/serato/markers.cpp +++ b/src/track/serato/markers.cpp @@ -3,14 +3,22 @@ #include <QtEndian> #include "track/serato/tags.h" +#include "util/logger.h" namespace { +mixxx::Logger kLogger("SeratoMarkers"); + const int kNumEntries = 14; const int kLoopEntryStartIndex = 5; -const int kEntrySize = 22; +const int kEntrySizeID3 = 22; +const int kEntrySizeMP4 = 19; const quint16 kVersion = 0x0205; +const QByteArray kSeratoMarkersBase64EncodedPrefix = QByteArray( + "application/octet-stream\x00\x00Serato Markers_\x00", + 24 + 2 + 15 + 1); + // These functions convert between a custom 4-byte format (that we'll call // "serato32" for brevity) and 3-byte plaintext (both quint32). // Serato's custom format inserts a single null bit after every 7 payload @@ -66,12 +74,11 @@ quint32 serato32fromUint24(quint32 value) { namespace mixxx { -QByteArray SeratoMarkersEntry::dump() const { +QByteArray SeratoMarkersEntry::dumpID3() const { QByteArray data; - data.resize(kEntrySize); + data.resize(kEntrySizeID3); QDataStream stream(&data, QIODevice::WriteOnly); - stream.setVersion(QDataStream::Qt_5_0); stream.setByteOrder(QDataStream::BigEndian); stream << static_cast<quint8>((m_hasStartPosition ? 0x00 : 0x7F)) << static_cast<quint32>( @@ -87,10 +94,27 @@ QByteArray SeratoMarkersEntry::dump() const { return data; } -SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) { - if (data.length() != kEntrySize) { - qWarning() << "Parsing SeratoMarkersEntry failed:" - << "Length" << data.length() << "!=" << kEntrySize; +QByteArray SeratoMarkersEntry::dumpMP4() const { + QByteArray data; + data.resize(kEntrySizeMP4); + + QDataStream stream(&data, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::BigEndian); + stream << static_cast<quint32>(m_startPosition) + << static_cast<quint32>(m_endPosition); + stream.writeRawData("\x00\xFF\xFF\xFF\xFF\x00", 6); + stream << static_cast<quint8>(qRed(m_color)) + << static_cast<quint8>(qGreen(m_color)) + << static_cast<quint8>(qBlue(m_color)) + << static_cast<quint8>(m_type) + << static_cast<quint8>(m_isLocked); + return data; +} + +SeratoMarkersEntryPointer SeratoMarkersEntry::parseID3(const QByteArray& data) { + if (data.length() != kEntrySizeID3) { + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "Length" << data.length() << "!=" << kEntrySizeID3; return nullptr; } @@ -104,14 +128,13 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) { char buffer[6]; QDataStream stream(data); - stream.setVersion(QDataStream::Qt_5_0); stream.setByteOrder(QDataStream::BigEndian); stream >> startPositionStatus >> startPositionSerato32 >> endPositionStatus >> endPositionSerato32; if (stream.readRawData(buffer, sizeof(buffer)) != sizeof(buffer)) { - qWarning() << "Parsing SeratoMarkersEntry failed:" - << "unable to read bytes 10..16"; + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "unable to read bytes 10..16"; return nullptr; } @@ -125,8 +148,8 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) { if (!hasStartPosition) { // Start position not set if (startPositionSerato32 != 0x7F7F7F7F) { - qWarning() << "Parsing SeratoMarkersEntry failed:" - << "startPosition != 0x7F7F7F7F"; + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "startPosition != 0x7F7F7F7F"; return nullptr; } @@ -140,8 +163,8 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) { if (!hasEndPosition) { // End position not set if (endPositionSerato32 != 0x7F7F7F7F) { - qWarning() << "Parsing SeratoMarkersEntry failed:" - << "endPosition != 0x7F7F7F7F"; + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "endPosition != 0x7F7F7F7F"; return nullptr; } @@ -151,20 +174,20 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) { // Make sure that the unknown (and probably unused) bytes have the expected value if (strncmp(buffer, "\x00\x7F\x7F\x7F\x7F\x7F", sizeof(buffer)) != 0) { - qWarning() << "Parsing SeratoMarkersEntry failed:" - << "Unexpected value at offset 10"; + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "Unexpected value at offset 10"; return nullptr; } if (stream.status() != QDataStream::Status::Ok) { - qWarning() << "Parsing SeratoMarkersEntry failed:" - << "Stream read failed with status" << stream.status(); + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "Stream read failed with status" << stream.status(); return nullptr; } if (!stream.atEnd()) { - qWarning() << "Parsing SeratoMarkersEntry failed:" - << "Unexpected trailing data"; + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "Unexpected trailing data"; return nullptr; } @@ -176,21 +199,100 @@ SeratoMarkersEntryPointer SeratoMarkersEntry::parse(const QByteArray& data) { color, type, isLocked)); - qDebug() << "SeratoMarkersEntry" << *pEntry; + kLogger.trace() << "SeratoMarkersEntry" << *pEntry; + return pEntry; +} + +SeratoMarkersEntryPointer SeratoMarkersEntry::parseMP4(const QByteArray& data) { + if (data.length() != kEntrySizeMP4) { + kLogger.warning() << "Parsing SeratoMarkersEntry (MP4) failed:" + << "Length" << data.length() << "!=" << kEntrySizeMP4; + return nullptr; + } + + quint32 startPosition; + quint32 endPosition; + char buffer[6]; + quint8 colorRed; + quint8 colorGreen; + quint8 colorBlue; + quint8 type; + bool isLocked; + + QDataStream stream(data); + stream.setByteOrder(QDataStream::BigEndian); + stream >> startPosition >> endPosition; + + if (stream.readRawData(buffer, sizeof(buffer)) != sizeof(buffer)) { + kLogger.warning() << "Parsing SeratoMarkersEntry (MP4) failed:" + << "unable to read bytes 8..14"; + return nullptr; + } + + stream >> colorRed >> colorGreen >> colorBlue >> type >> isLocked; + const RgbColor color = RgbColor(qRgb(colorRed, colorGreen, colorBlue)); + + // Make sure that the unknown (and probably unused) bytes have the expected value + if (strncmp(buffer, "\x00\xFF\xFF\xFF\xFF\x00", sizeof(buffer)) != 0) { + kLogger.warning() << "Parsing SeratoMarkersEntry (MP4) failed:" + << "Unexpected value at offset 8"; + return nullptr; + } + + if (stream.status() != QDataStream::Status::Ok) { + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "Stream read failed with status" << stream.status(); + return nullptr; + } + + if (!stream.atEnd()) { + kLogger.warning() << "Parsing SeratoMarkersEntry failed:" + << "Unexpected trailing data"; + return nullptr; + } + + SeratoMarkersEntryPointer pEntry = + SeratoMarkersEntryPointer(new SeratoMarkersEntry( + true, + startPosition, + type == static_cast<quint8>(TypeId::Loop), + endPosition, + color, + type, + isLocked)); + kLogger.trace() << "SeratoMarkersEntry" << *pEntry; return pEntry; } +// static bool SeratoMarkers::parse( + SeratoMarkers* seratoMarkers, const QByteArray& data, taglib::FileType fileType) { + VERIFY_OR_DEBUG_ASSERT(seratoMarkers) { + return false; + } + + switch (fileType) { + case taglib::FileType::MP3: + case taglib::FileType::AIFF: + return parseID3(seratoMarkers, data); + case taglib::FileType::MP4: + return parseMP4(seratoMarkers, data); + default: + return false; + } +} + +// static +bool S |