summaryrefslogtreecommitdiffstats
path: root/src/track
diff options
context:
space:
mode:
authorDaniel Schürmann <daschuer@mixxx.org>2020-05-07 14:35:40 +0200
committerDaniel Schürmann <daschuer@mixxx.org>2020-05-07 14:46:32 +0200
commite96df66dd421ca48da3ca55da8b490d78663260f (patch)
tree6e9acf71c9e375fc689efe33234a65875ea62747 /src/track
parent6ebe787faf731ca21aea03bd59652f57013efce6 (diff)
parentd5be6d44272763cfdc1b19c4a8e5099df11b82dc (diff)
Merge remote-tracking branch 'upstream/master' into lp1874918
Diffstat (limited to 'src/track')
-rw-r--r--src/track/beatfactory.cpp98
-rw-r--r--src/track/beatfactory.h31
-rw-r--r--src/track/beatgrid.cpp4
-rw-r--r--src/track/beatgrid.h3
-rw-r--r--src/track/beatmap.cpp4
-rw-r--r--src/track/beatmap.h3
-rw-r--r--src/track/beats.cpp3
-rw-r--r--src/track/beats.h3
-rw-r--r--src/track/cueinfo.h1
-rw-r--r--src/track/cueinfoimporter.cpp68
-rw-r--r--src/track/cueinfoimporter.h40
-rw-r--r--src/track/keyutils.cpp5
-rw-r--r--src/track/serato/cueinfoimporter.cpp17
-rw-r--r--src/track/serato/cueinfoimporter.h18
-rw-r--r--src/track/serato/markers.cpp379
-rw-r--r--src/track/serato/markers.h27
-rw-r--r--src/track/serato/markers2.cpp349
-rw-r--r--src/track/serato/markers2.h41
-rw-r--r--src/track/serato/tags.cpp137
-rw-r--r--src/track/serato/tags.h33
-rw-r--r--src/track/taglib/trackmetadata_common.cpp48
-rw-r--r--src/track/taglib/trackmetadata_common.h24
-rw-r--r--src/track/taglib/trackmetadata_file.cpp1
-rw-r--r--src/track/taglib/trackmetadata_file.h7
-rw-r--r--src/track/taglib/trackmetadata_id3v2.cpp18
-rw-r--r--src/track/taglib/trackmetadata_mp4.cpp40
-rw-r--r--src/track/taglib/trackmetadata_xiph.cpp42
-rw-r--r--src/track/taglib/trackmetadata_xiph.h6
-rw-r--r--src/track/track.cpp68
-rw-r--r--src/track/track.h13
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 =
<