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
Changes from 1 commit
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
144 changes: 88 additions & 56 deletions src/track/serato/markers2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

#include <QtEndian>

namespace {
QString zeroTerminatedUtf8StringtoQString(QDataStream* stream) {
DEBUG_ASSERT(stream);

QByteArray data;
quint8 byte = '\xFF';
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
while (byte != '\x00') {
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
*stream >> byte;
data.append(byte);
Holzhaus marked this conversation as resolved.
Show resolved Hide resolved
if (stream->status() != QDataStream::Status::Ok) {
return QString();
}
}
return QString::fromUtf8(data);
}
} // namespace

namespace mixxx {

SeratoMarkers2EntryPointer SeratoMarkers2BpmlockEntry::parse(const QByteArray& data) {
Expand Down Expand Up @@ -84,54 +101,62 @@ SeratoMarkers2EntryPointer SeratoMarkers2CueEntry::parse(const QByteArray& data)
return nullptr;
}

// CUE entry fields in order of appearance
quint8 unknownField1;
quint8 index;
quint32 position;
quint8 unknownField2;
quint8 rawRgbRed;
quint8 rawRgbGreen;
quint8 rawRgbBlue;
quint16 unknownField3;

QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);

stream >> unknownField1;

// Unknown field, make sure it's 0 in case it's a
// null-terminated string
if (data.at(0) != '\x00') {
if (unknownField1 != '\x00') {
qWarning() << "Parsing SeratoMarkers2CueEntry failed:"
<< "Byte 0: " << data.at(0) << "!= '\\0'";
return nullptr;
}

const quint8 index = data.at(1);
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
const auto position = qFromBigEndian<quint32>(data.mid(2, 6));
#else
const auto position = qFromBigEndian<quint32>(
reinterpret_cast<const uchar*>(data.mid(2, 6).constData()));
#endif
stream >> index >> position >> unknownField2;

// Unknown field, make sure it's 0 in case it's a
// null-terminated string
if (data.at(6) != '\x00') {
if (unknownField2 != '\x00') {
qWarning() << "Parsing SeratoMarkers2CueEntry failed:"
<< "Byte 6: " << data.at(6) << "!= '\\0'";
return nullptr;
}

RgbColor color = RgbColor(qRgb(
static_cast<quint8>(data.at(7)),
static_cast<quint8>(data.at(8)),
static_cast<quint8>(data.at(9))));
stream >> rawRgbRed >> rawRgbGreen >> rawRgbBlue >> unknownField3;
RgbColor color = RgbColor(qRgb(rawRgbRed, rawRgbGreen, rawRgbBlue));

// Unknown field(s), make sure it's 0 in case it's a
// null-terminated string
if (data.at(10) != '\x00' || data.at(11) != '\x00') {
if (unknownField3 != 0x0000) {
qWarning() << "Parsing SeratoMarkers2CueEntry failed:"
<< "Bytes 10-11:" << data.mid(10, 2) << "!= \"\\0\\0\"";
<< "Bytes 10-11:" << unknownField3 << "!= \"\\0\\0\"";
return nullptr;
}

int labelEndPos = data.indexOf('\x00', 12);
if (labelEndPos < 0) {
qWarning() << "Parsing SeratoMarkers2CueEntry failed:"
<< "Label end position not found";
QString label = zeroTerminatedUtf8StringtoQString(&stream);

if (stream.status() != QDataStream::Status::Ok) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "Stream read failed with status" << stream.status();
return nullptr;
}
QString label(data.mid(12, labelEndPos - 12));

if (data.length() > labelEndPos + 1) {
qWarning() << "Parsing SeratoMarkers2CueEntry failed:"
<< "Trailing content" << data.mid(labelEndPos + 1);
if (!stream.atEnd()) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "Unexpected trailing data";
return nullptr;
}

Expand Down Expand Up @@ -175,63 +200,70 @@ SeratoMarkers2EntryPointer SeratoMarkers2LoopEntry::parse(const QByteArray& data
return nullptr;
}

// LOOP entry fields in order of appearance
quint8 unknownField1;
quint8 index;
quint32 startPosition;
quint32 endPosition;
quint32 unknownField2;
quint32 unknownField3;
quint8 unknownField4;
bool locked;

QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_0);
stream.setByteOrder(QDataStream::BigEndian);

stream >> unknownField1;
// Unknown field, make sure it's 0 in case it's a
// null-terminated string
if (data.at(0) != '\x00') {
if (unknownField1 != '\x00') {
qWarning() << "Parsing SeratoMarkers2LoopEntry failed:"
<< "Byte 0: " << data.at(0) << "!= '\\0'";
<< "Byte 0: " << unknownField1 << "!= '\\0'";
return nullptr;
}

const quint8 index = data.at(1);
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
const auto startposition = qFromBigEndian<quint32>(data.mid(2, 6));
const auto endposition = qFromBigEndian<quint32>(data.mid(6, 10));
#else
const auto startposition = qFromBigEndian<quint32>(
reinterpret_cast<const uchar*>(data.mid(2, 6).constData()));
const auto endposition = qFromBigEndian<quint32>(
reinterpret_cast<const uchar*>(data.mid(6, 10).constData()));
#endif
stream >> index >> startPosition >> endPosition >> unknownField2;
// Unknown field, make sure it contains the expected "default" value
if (unknownField2 != 0xffffffff) {
qWarning() << "Parsing SeratoMarkers2LoopEntry failed:"
<< "Invalid magic value" << unknownField2 << "at offset 10";
return nullptr;
}

stream >> unknownField3;
// Unknown field, make sure it contains the expected "default" value
if (data.at(10) != '\xff' ||
data.at(11) != '\xff' ||
data.at(12) != '\xff' ||
data.at(13) != '\xff' ||
data.at(14) != '\x00' ||
data.at(15) != '\x27' ||
data.at(16) != '\xaa' ||
data.at(17) != '\xe1') {
if (unknownField3 != 0x0027aae1) {
qWarning() << "Parsing SeratoMarkers2LoopEntry failed:"
<< "Invalid magic value " << data.mid(10, 16);
<< "Invalid magic value" << unknownField3 << "at offset 14";
return nullptr;
}

stream >> unknownField4;
// Unknown field, make sure it's 0 in case it's a
// null-terminated string
if (data.at(18) != '\x00') {
if (unknownField4 != '\x00') {
qWarning() << "Parsing SeratoMarkers2LoopEntry failed:"
<< "Byte 18:" << data.at(18) << "!= '\\0'";
<< "Byte 18:" << unknownField4 << "!= '\\0'";
return nullptr;
}

const bool locked = data.at(19);
stream >> locked;
QString label = zeroTerminatedUtf8StringtoQString(&stream);

int labelEndPos = data.indexOf('\x00', 20);
if (labelEndPos < 0) {
qWarning() << "Parsing SeratoMarkers2LoopEntry failed:"
<< "Label end position not found";
if (stream.status() != QDataStream::Status::Ok) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "Stream read failed with status" << stream.status();
return nullptr;
}
QString label(data.mid(20, labelEndPos - 20));

if (data.length() > labelEndPos + 1) {
qWarning() << "Parsing SeratoMarkers2LoopEntry failed:"
<< "Trailing content" << data.mid(labelEndPos + 1);
if (!stream.atEnd()) {
qWarning() << "Parsing SeratoMarkersEntry failed:"
<< "Unexpected trailing data";
return nullptr;
}

SeratoMarkers2LoopEntry* pEntry = new SeratoMarkers2LoopEntry(index, startposition, endposition, locked, label);
SeratoMarkers2LoopEntry* pEntry = new SeratoMarkers2LoopEntry(index, startPosition, endPosition, locked, label);
qDebug() << "SeratoMarkers2LoopEntry" << *pEntry;
return SeratoMarkers2EntryPointer(pEntry);
}
Expand Down