summaryrefslogtreecommitdiffstats
path: root/src/track
diff options
context:
space:
mode:
authorJan Holthuis <jan.holthuis@ruhr-uni-bochum.de>2020-05-06 15:07:49 +0200
committerJan Holthuis <jan.holthuis@ruhr-uni-bochum.de>2020-05-06 15:12:04 +0200
commitd9c53c569bc053b173261bd296fa5412d737fad0 (patch)
treebee4254f764953b764a66421f528073879bba324 /src/track
parent5355a23abc302bd47902aebfcf48f7d9c03c7997 (diff)
parent62d114f67f96f027a74f40786a02ba46029ee9ea (diff)
Merge branch 'master' of github.com:mixxxdj/mixxx into serato-markers-integration-loops
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/bpm.cpp2
-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/replaygain.cpp2
-rw-r--r--src/track/serato/cueinfoimporter.cpp17
-rw-r--r--src/track/serato/cueinfoimporter.h18
-rw-r--r--src/track/serato/markers.cpp368
-rw-r--r--src/track/serato/markers.h27
-rw-r--r--src/track/serato/markers2.cpp337
-rw-r--r--src/track/serato/markers2.h41
-rw-r--r--src/track/serato/tags.cpp44
-rw-r--r--src/track/serato/tags.h33
-rw-r--r--src/track/taglib/trackmetadata.h13
-rw-r--r--src/track/taglib/trackmetadata_ape.cpp334
-rw-r--r--src/track/taglib/trackmetadata_ape.h29
-rw-r--r--src/track/taglib/trackmetadata_common.cpp289
-rw-r--r--src/track/taglib/trackmetadata_common.h231
-rw-r--r--src/track/taglib/trackmetadata_file.cpp185
-rw-r--r--src/track/taglib/trackmetadata_file.h64
-rw-r--r--src/track/taglib/trackmetadata_id3v2.cpp1296
-rw-r--r--src/track/taglib/trackmetadata_id3v2.h29
-rw-r--r--src/track/taglib/trackmetadata_mp4.cpp467
-rw-r--r--src/track/taglib/trackmetadata_mp4.h29
-rw-r--r--src/track/taglib/trackmetadata_riff.cpp20
-rw-r--r--src/track/taglib/trackmetadata_riff.h21
-rw-r--r--src/track/taglib/trackmetadata_xiph.cpp617
-rw-r--r--src/track/taglib/trackmetadata_xiph.h34
-rw-r--r--src/track/track.cpp77
-rw-r--r--src/track/track.h13
-rw-r--r--src/track/trackmetadata.cpp2
-rw-r--r--src/track/trackmetadatataglib.cpp2948
-rw-r--r--src/track/trackmetadatataglib.h153
-rw-r--r--src/track/trackrecord.cpp2
43 files changed, 4616 insertions, 3389 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 48aa6ffb86..37dc5e51fe 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)
@@ -722,3 +724,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/bpm.cpp b/src/track/bpm.cpp
index fdd280590f..647a2a924b 100644
--- a/src/track/bpm.cpp
+++ b/src/track/bpm.cpp
@@ -57,4 +57,4 @@ double Bpm::normalizeValue(double value) {
}
}
-} //namespace mixxx
+} // namespace mixxx
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/replaygain.cpp b/src/track/replaygain.cpp
index c6fe10d7ae..9fe1a84cc1 100644
--- a/src/track/replaygain.cpp
+++ b/src/track/replaygain.cpp
@@ -156,4 +156,4 @@ CSAMPLE ReplayGain::normalizePeak(CSAMPLE peak) {
}
}
-} //namespace mixxx
+} // namespace mixxx
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 a3bf082265..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