Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the SeratoMarkers_ GEOB tag (Hotcues/Loops/Track Color) #2495

Merged
merged 48 commits into from
Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b8d84c7
track/serato/markers2: Move Serato Markers2 code into serato directory
Holzhaus Feb 8, 2020
5a2c9ac
track/serato/markers2: Apply clang-format formatting fixes
Holzhaus Feb 8, 2020
054ec17
track/serato/markers: Add parser for "Serato Markers_" tag
Holzhaus Feb 8, 2020
ed9bb38
tests/serato: Move Serato test data into data directory
Holzhaus Feb 9, 2020
5147df7
tests: Add tests for "Serato Markers_" parser
Holzhaus Feb 9, 2020
b8e4ad8
track/trackinfo: Add support for reading "Serato Markers_" tags
Holzhaus Feb 9, 2020
2d65a58
track/serato/markers: Improve QDebug operator<< support
Holzhaus Feb 9, 2020
86902ab
track/serato/markers: Add comment documenting the color format
Holzhaus Feb 10, 2020
ad886ff
track/serato/markers_: Remove unused allocatedSize code
Holzhaus Feb 10, 2020
42491e4
track/serato/markers2: Use one initialization per line
Holzhaus Feb 10, 2020
f4bd9ff
track/serato/markers: Use one initialization per line
Holzhaus Feb 10, 2020
0205ed3
track/serato/markers: Return SeratoMarkersEntryPointer in parse()
Holzhaus Feb 10, 2020
96ab998
track/serato/markers: Use SeratoMarkersEntryPointer for QList
Holzhaus Feb 10, 2020
0ee2787
track/serato/markers: Replace usage of QByteArray::mid() with QDataSt…
Holzhaus Feb 11, 2020
af1f284
track/serato/markers: Improve position parsing and update tests
Holzhaus Feb 11, 2020
56a25fc
tests/seratomarkerstest: Reorder lines to reflect actual byte order
Holzhaus Feb 11, 2020
e63387d
track/serato/markers: Remove obsolete QStringLiteral #include
Holzhaus Feb 11, 2020
f65abf8
track/serato/markers: Use QDataStream also in SeratoMarkers::parse()
Holzhaus Feb 11, 2020
273201f
track/serato/markers: Make sure that QDataStreams were not read past end
Holzhaus Feb 11, 2020
5b327da
Merge branch 'master' of github.com:mixxxdj/mixxx into serato-markers_
Holzhaus Feb 11, 2020
4716b9e
test/seratomarkerstest: Fix formatting and use const references
Holzhaus Feb 11, 2020
035ffa7
track/serato/markers: Use std::move for setEntries()
Holzhaus Feb 11, 2020
ed4aa7d
track/serato/markers2: Break initialization line for unknown entries
Holzhaus Feb 11, 2020
e78de7f
track/serato/markers2: Rename ::data() to ::dump()
Holzhaus Feb 11, 2020
a6e233e
track/serato/markers: Rename ::data() to ::dump()
Holzhaus Feb 11, 2020
b76dc45
track/serato/markers: Rename color* functions to seratoColor*
Holzhaus Feb 11, 2020
46f9549
track/serato/markers2: Use one initialization per line
Holzhaus Feb 12, 2020
51fad8e
track/serato/markers: Add separate bools to determine if position is set
Holzhaus Feb 12, 2020
7274cdb
track/trackmetadatataglib: Add missing write call for "Serato Markers_"
Holzhaus Feb 12, 2020
998bfea
test/seratomarkerstest: Fix tests and add checks for `has<x>Position()`
Holzhaus Feb 12, 2020
849ee75
track/serato/markers: Add check that end of stream is reached
Holzhaus Feb 12, 2020
39ad2a9
track/serato/markers: Rename trackColor to more fitting trackColorMask
Holzhaus Feb 12, 2020
05f9e32
track/serato/markers2: Use QRgb instead of QColor for colors
Holzhaus Feb 12, 2020
b9706ff
track/serato/markers: Add TypeId enum class
Holzhaus Feb 12, 2020
bcc3aa6
track/serato/markers2: Add TypeId enum ordered by entry occurrence
Holzhaus Jan 30, 2020
e5ff804
track/serato/markers: Map type 0 to Cue (for unset cues)
Holzhaus Feb 12, 2020
be24da2
track/serato/markers: Check number of entries and types
Holzhaus Feb 12, 2020
e5b2511
track/serato: Use trackColor naming instead of trackColorMask
Holzhaus Feb 13, 2020
1faeb9d
Merge branch 'master' of github.com:mixxxdj/mixxx into serato-markers_
Holzhaus Feb 13, 2020
4e957b6
track/serato/markers2: Use RgbColor instead of plain QRgb
Holzhaus Feb 15, 2020
016557e
track/serato/markers: Use RgbColor instead of plain QRgb
Holzhaus Feb 15, 2020
1ffdb24
track/serato/markers2: Remove expensive entry length default implemen…
Holzhaus Feb 15, 2020
46a7ac2
track/serato/markers2: Use QDataStream for SeratoMarkers2::dump()
Holzhaus Feb 15, 2020
57ff560
track/serato/markers2: Use QDataStream for parsing CUE/LOOP entries
Holzhaus Feb 15, 2020
0495add
test/seratomarkers2test: Break long lines
Holzhaus Feb 16, 2020
936dd3b
track/serato/markers: Cast quint8 to quint32 before bitshifting
Holzhaus Feb 16, 2020
fef7493
track/serato: Use static_cast if necessary
Holzhaus Feb 16, 2020
c9a38e5
track/serato/markers: Add some comments for typeId values
Holzhaus Feb 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/track/keyutils.cpp
src/track/playcounter.cpp
src/track/replaygain.cpp
src/track/seratomarkers2.cpp
src/track/serato/markers.cpp
src/track/serato/markers2.cpp
src/track/track.cpp
src/track/trackfile.cpp
src/track/trackinfo.cpp
Expand Down Expand Up @@ -961,6 +962,7 @@ add_executable(mixxx-test
src/test/sampleutiltest.cpp
src/test/schemamanager_test.cpp
src/test/searchqueryparsertest.cpp
src/test/seratomarkerstest.cpp
src/test/seratomarkers2test.cpp
src/test/signalpathtest.cpp
src/test/skincontext_test.cpp
Expand Down
3 changes: 2 additions & 1 deletion build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,8 @@ def sources(self, build):
"src/track/keyutils.cpp",
"src/track/playcounter.cpp",
"src/track/replaygain.cpp",
"src/track/seratomarkers2.cpp",
"src/track/serato/markers.cpp",
"src/track/serato/markers2.cpp",
"src/track/track.cpp",
"src/track/globaltrackcache.cpp",
"src/track/trackfile.cpp",
Expand Down
Binary file added src/test/serato/data/markers_/analyzed.octet-stream
Binary file not shown.
Binary file not shown.
Binary file added src/test/serato/data/markers_/flips.octet-stream
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 2 additions & 2 deletions src/test/seratomarkers2test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <gtest/gtest.h>

#include "track/seratomarkers2.h"
#include "track/serato/markers2.h"
#include "util/memory.h"

#include <taglib/tstring.h>
Expand Down Expand Up @@ -213,7 +213,7 @@ TEST_F(SeratoMarkers2Test, ParseLoopEntry) {
}

TEST_F(SeratoMarkers2Test, ParseMarkers2Data) {
QDir dir("src/test/seratomarkers2-data");
QDir dir("src/test/serato/data/markers2");
dir.setFilter(QDir::Files);
dir.setNameFilters(QStringList() << "*.octet-stream");

Expand Down
76 changes: 76 additions & 0 deletions src/test/seratomarkerstest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <gtest/gtest.h>
#include <taglib/textidentificationframe.h>
#include <taglib/tstring.h>

#include <QDir>
#include <QtDebug>

#include "track/serato/markers.h"
#include "util/memory.h"

namespace {

class SeratoMarkersTest : public testing::Test {
protected:
void parseEntry(const QByteArray inputValue, bool valid, bool isSet, quint32 startPosition, quint32 endPosition, QRgb color, bool isLocked, quint8 type) {
const mixxx::SeratoMarkersEntry* pEntry = mixxx::SeratoMarkersEntry::parse(inputValue);
if (!pEntry) {
EXPECT_FALSE(valid);
return;
}
EXPECT_TRUE(valid);

EXPECT_EQ(isSet, pEntry->isSet());
EXPECT_EQ(startPosition, pEntry->getStartPosition());
EXPECT_EQ(endPosition, pEntry->getEndPosition());
EXPECT_EQ(color, pEntry->getColor());
EXPECT_EQ(isLocked, pEntry->isLocked());
EXPECT_EQ(type, pEntry->type());

EXPECT_EQ(inputValue, pEntry->data());
}

void parseMarkersData(const QByteArray inputValue, bool valid) {
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
mixxx::SeratoMarkers seratoMarkers;
bool parseOk = mixxx::SeratoMarkers::parse(&seratoMarkers, inputValue);
EXPECT_EQ(valid, parseOk);
if (!parseOk) {
return;
}

EXPECT_EQ(inputValue, seratoMarkers.data());
}
};

TEST_F(SeratoMarkersTest, ParseEntry) {
parseEntry(QByteArray("\x00\x00\x00\x00\x00\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x06\x30\x00\x00\x01", 21), false, false, -1, -1, QRgb(0x000000), false, 0);
parseEntry(QByteArray("\x00\x00\x00\x00\x00\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x06\x30\x00\x00\x01\x00", 22), true, true, 0, -1, QRgb(0xcc0000), false, 1);
parseEntry(QByteArray("\x00\x00\x0d\x2a\x58\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x06\x32\x10\x00\x01\x00", 22), true, true, 862808, -1, QRgb(0xcc8800), false, 1);
parseEntry(QByteArray("\x00\x00\x03\x54\x64\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x00\x00\x01\x4c\x01\x00", 22), true, true, 218212, -1, QRgb(0x0000cc), false, 1);
parseEntry(QByteArray("\x00\x00\x00\x00\x6c\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x06\x33\x18\x00\x01\x00", 22), true, true, 108, -1, QRgb(0xcccc00), false, 1);
parseEntry(QByteArray("\x00\x00\x00\x07\x77\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x00\x03\x18\x00\x01\x00", 22), true, true, 1911, -1, QRgb(0x00cc00), false, 1);
parseEntry(QByteArray("\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x00\x00\x00\x00\x01\x00", 22), true, false, -1, -1, QRgb(0x000000), false, 1);
parseEntry(QByteArray("\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x00\x7f\x7f\x7f\x7f\x7f\x00\x00\x00\x00\x03\x00", 22), true, false, -1, -1, QRgb(0x000000), false, 3);
}

TEST_F(SeratoMarkersTest, ParseMarkersData) {
QDir dir("src/test/serato/data/markers_");
dir.setFilter(QDir::Files);
dir.setNameFilters(QStringList() << "*.octet-stream");

QFileInfoList list = dir.entryInfoList();
for (int i = 0; i < list.size(); i++) {
QFileInfo fileInfo = list.at(i);
qDebug() << "--- File:" << fileInfo.fileName();
QFile file(dir.filePath(fileInfo.fileName()));
bool openOk = file.open(QIODevice::ReadOnly);
EXPECT_TRUE(openOk);
if (!openOk) {
continue;
}
QByteArray data = file.readAll();
parseMarkersData(data, true);
}
}

} // namespace
179 changes: 179 additions & 0 deletions src/track/serato/markers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#include "track/serato/markers.h"

#include <QtEndian>
#include <QStringLiteral>

namespace {

const int kEntrySize = 22;
const char* kVersion = "\x02\x05";
const int kVersionSize = 2;

inline
quint32 bytesToUInt32(const QByteArray& data) {
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
DEBUG_ASSERT(data.size() == sizeof(quint32));
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
return qFromBigEndian<quint32>(data.constData());
#else
return qFromBigEndian<quint32>(
reinterpret_cast<const uchar*>(data.constData()));
#endif
}

QRgb colorToRgb(quint8 w, quint8 x, quint8 y, quint8 z) {
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
quint8 b = (z & 0x7F) | ((y & 0x01) << 7);
quint8 g = ((y & 0x7F) >> 1) | ((x & 0x03) << 6);
quint8 r = ((x & 0x7F) >> 2) | ((w & 0x07) << 5);
return QRgb(r << 16) | (g << 8) | b;
}

QRgb colorToRgb(const QByteArray& data) {
DEBUG_ASSERT(data.size() == 4);
return colorToRgb(data.at(0), data.at(1), data.at(2), data.at(3));
}

quint32 colorFromRgb(quint8 r, quint8 g, quint8 b) {
quint8 z = b & 0x7F;
quint8 y = ((b >> 7) | (g << 1)) & 0x7F;
quint8 x = ((g >> 6) | (r << 2)) & 0x7F;
quint8 w = (r >> 5);
return (w << 24) | (x << 16) | (y << 8) | z;
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
}

quint32 colorFromRgb(QRgb rgb) {
return colorFromRgb(qRed(rgb), qGreen(rgb), qBlue(rgb));
}

}

namespace mixxx {

QByteArray SeratoMarkersEntry::data() const {
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
QByteArray data;
data.resize(kEntrySize);

QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);
stream << (quint8)(m_isSet ? '\x00' : '\x7F')
<< (quint32)((m_startPosition == -1) ? 0x7F7F7F7F : m_startPosition)
<< (quint8)((!m_isSet || m_type == 1) ? '\x7F' : m_isEnabled)
<< (quint32)((m_endPosition == -1) ? 0x7F7F7F7F : m_endPosition);
stream.writeRawData("\x00\x7F\x7F\x7F\x7F\x7F", 6);
stream << (quint32)colorFromRgb(m_color)
<< (quint8)m_type
<< (quint8)m_isLocked;
return data;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also required a lot comments, including examples.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


SeratoMarkersEntry* SeratoMarkersEntry::parse(const QByteArray& data) {
if (data.length() != kEntrySize) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "Length" << data.length() << "!=" << kEntrySize;
return nullptr;
}

const bool isSet = (data.at(0) == '\x7F') ? false : true;
const quint32 uStartPosition = bytesToUInt32(data.mid(1, 4));
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
if (uStartPosition > 0x7FFFFFFF) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "startPosition > 0x7FFFFFF";
return nullptr;
}
const int startPosition = (uStartPosition == 0x7F7F7F7F) ? -1 : static_cast<int>(uStartPosition);

const bool isEnabled = static_cast<bool>(data.at(5));

const quint32 uEndPosition = bytesToUInt32(data.mid(6, 4));
if (uEndPosition > 0x7FFFFFFF) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "endPosition > 0x7FFFFFF";
return nullptr;
}
const int endPosition = (uEndPosition == 0x7F7F7F7F) ? -1 : static_cast<int>(uEndPosition);

if (data.mid(10, 6).compare("\x00\x7F\x7F\x7F\x7F") == 0) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "Unexpected value at offset 10";
return nullptr;
}

const QRgb color = colorToRgb(data.mid(16, 4));
const quint8 type = data.at(20);
const bool isLocked = static_cast<bool>(data.at(21));

SeratoMarkersEntry* pEntry = new SeratoMarkersEntry(
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
isSet,
startPosition,
isEnabled,
endPosition,
color,
type,
isLocked);
qDebug() << "SeratoMarkersEntry" << *pEntry;
return pEntry;
}

bool SeratoMarkers::parse(SeratoMarkers* seratoMarkers, const QByteArray& data) {
if (!data.startsWith(kVersion)) {
qWarning() << "Parsing SeratoMarkers_ failed:"
<< "Unknown Serato Markers_ tag version";
return false;
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
}

quint32 numEntries = bytesToUInt32(data.mid(2, 4));
QList<std::shared_ptr<SeratoMarkersEntry>> entries;

int offset = 6;
for (quint32 i = 0; i < numEntries; i++) {
if (data.size() <= (offset + kEntrySize)) {
qWarning() << "Parsing SeratoMarkers_ failed:"
<< "Incomplete entry" << data.mid(offset);
return false;
}

QByteArray entryData = data.mid(offset, kEntrySize);
SeratoMarkersEntryPointer pEntry = SeratoMarkersEntryPointer(
SeratoMarkersEntry::parse(entryData));
if (!pEntry) {
qWarning() << "Parsing SeratoMarkers_ failed:"
<< "Unable to parse entry!";
return false;
}
entries.append(pEntry);

offset += kEntrySize;
}

if (data.mid(offset).size() != 4) {
qWarning() << "Parsing SeratoMarkers_ failed:"
<< "Invalid footer" << data.mid(offset);
return false;
}

QRgb color = colorToRgb(data.mid(offset));

seratoMarkers->setEntries(entries);
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
seratoMarkers->setTrackColor(color);

return true;
}

QByteArray SeratoMarkers::data() const {
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
QByteArray data;
data.resize(kVersionSize + 2 * sizeof(quint32) + kEntrySize * m_entries.size());

QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);
stream.writeRawData(kVersion, kVersionSize);
stream << m_entries.size();
for (int i = 0; i < m_entries.size(); i++) {
SeratoMarkersEntryPointer pEntry = m_entries.at(i);
stream.writeRawData(pEntry->data(), kEntrySize);
}
stream << colorFromRgb(m_trackColor);
return data;
}

} //namespace mixxx
Loading