From 5fdb525db592c30cace4507b0b864394ff135873 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Sat, 27 Jun 2026 00:44:49 +0200 Subject: [PATCH 1/4] Harden Rekordbox USB import guards --- src/library/rekordbox/rekordboxfeature.cpp | 140 ++++++++++++++------- 1 file changed, 95 insertions(+), 45 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 855aa73ed450..e6f53c887aca 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -263,7 +263,16 @@ QString fromUtf16LeString(const std::string& toConvert) { QString fromUtf16BeString(const std::string& toConvert) { // Kaitai uses std::string as single container for all string encodings. - int length = static_cast(toConvert.length()) - 2; // strip off trailing nullbyte + int length = static_cast(toConvert.length()); + if (length < 2) { + return QString(); + } + if (toConvert[length - 1] == '\0' && toConvert[length - 2] == '\0') { + length -= 2; // strip off trailing nullbyte + } + if (length <= 0) { + return QString(); + } return QTextCodec::codecForName("UTF-16BE")->toUnicode(toConvert.data(), length); } @@ -273,6 +282,9 @@ QString fromUtf16BeString(const std::string& toConvert) { QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { QString text; + if (!deviceString || !deviceString->body()) { + return text; + } if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_short_ascii_t* shortAsciiString = @@ -875,6 +887,9 @@ void readAnalyze(TrackPointer track, int timingOffset, bool ignoreCues, const QString& anlzPath) { + if (!track || !sampleRate.isValid()) { + return; + } if (!QFile(anlzPath).exists()) { return; } @@ -884,15 +899,22 @@ void readAnalyze(TrackPointer track, std::ifstream ifs(anlzPath.toStdString(), std::ifstream::binary); kaitai::kstream ks(&ifs); - rekordbox_anlz_t anlz = rekordbox_anlz_t(&ks); + try { + rekordbox_anlz_t anlz = rekordbox_anlz_t(&ks); + if (!anlz.sections()) { + return; + } - const double sampleRateKhz = sampleRate / 1000.0; + const double sampleRateKhz = sampleRate / 1000.0; - QList memoryCuesAndLoops; - int lastHotCueIndex = 0; + QList memoryCuesAndLoops; + int lastHotCueIndex = 0; - for (const auto& section : *anlz.sections()) { - switch (section->fourcc()) { + for (const auto& section : *anlz.sections()) { + if (!section || !section->body()) { + continue; + } + switch (section->fourcc()) { case rekordbox_anlz_t::SECTION_TAGS_BEAT_GRID: { if (!ignoreCues) { break; @@ -901,6 +923,9 @@ void readAnalyze(TrackPointer track, auto* beatGridTag = static_cast( section->body()); + if (!beatGridTag || !beatGridTag->beats()) { + break; + } QVector beats; @@ -927,6 +952,9 @@ void readAnalyze(TrackPointer track, auto* cuesTag = static_cast( section->body()); + if (!cuesTag || !cuesTag->cues()) { + break; + } for (const auto& cueEntry : *cuesTag->cues()) { int time = static_cast(cueEntry->time()) - timingOffset; @@ -987,6 +1015,9 @@ void readAnalyze(TrackPointer track, auto* cuesExtendedTag = static_cast( section->body()); + if (!cuesExtendedTag || !cuesExtendedTag->cues()) { + break; + } for (const auto& cueExtendedEntry : *cuesExtendedTag->cues()) { int time = static_cast(cueExtendedEntry->time()) - timingOffset; @@ -1055,40 +1086,48 @@ void readAnalyze(TrackPointer track, } } - if (memoryCuesAndLoops.size() > 0) { - std::sort(memoryCuesAndLoops.begin(), - memoryCuesAndLoops.end(), - [](const memory_cue_loop_t& a, const memory_cue_loop_t& b) - -> bool { return a.startPosition < b.startPosition; }); - - bool mainCueFound = false; - - // Add memory cues and loops - for (int memoryCueOrLoopIndex = 0; - memoryCueOrLoopIndex < memoryCuesAndLoops.size(); - memoryCueOrLoopIndex++) { - memory_cue_loop_t memoryCueOrLoop = memoryCuesAndLoops[memoryCueOrLoopIndex]; - - if (!mainCueFound && !memoryCueOrLoop.endPosition.isValid()) { - // Set first chronological memory cue as Mixxx MainCue - track->setMainCuePosition(memoryCueOrLoop.startPosition); - CuePointer pMainCue = track->findCueByType(mixxx::CueType::MainCue); - pMainCue->setLabel(memoryCueOrLoop.comment); - pMainCue->setColor(*memoryCueOrLoop.color); - mainCueFound = true; - } else { - // Mixxx v2.4 will feature multiple loops, so these saved here will be usable - // For 2.3, Mixxx treats them as hotcues and the first one will be loaded as the single loop Mixxx supports - lastHotCueIndex++; - setHotCue( - track, - memoryCueOrLoop.startPosition, - memoryCueOrLoop.endPosition, - lastHotCueIndex, - memoryCueOrLoop.comment, - memoryCueOrLoop.color); + if (memoryCuesAndLoops.size() > 0) { + std::sort(memoryCuesAndLoops.begin(), + memoryCuesAndLoops.end(), + [](const memory_cue_loop_t& a, const memory_cue_loop_t& b) + -> bool { return a.startPosition < b.startPosition; }); + + bool mainCueFound = false; + + // Add memory cues and loops + for (int memoryCueOrLoopIndex = 0; + memoryCueOrLoopIndex < memoryCuesAndLoops.size(); + memoryCueOrLoopIndex++) { + memory_cue_loop_t memoryCueOrLoop = + memoryCuesAndLoops[memoryCueOrLoopIndex]; + + if (!mainCueFound && !memoryCueOrLoop.endPosition.isValid()) { + // Set first chronological memory cue as Mixxx MainCue + track->setMainCuePosition(memoryCueOrLoop.startPosition); + CuePointer pMainCue = track->findCueByType(mixxx::CueType::MainCue); + if (pMainCue) { + pMainCue->setLabel(memoryCueOrLoop.comment); + if (memoryCueOrLoop.color) { + pMainCue->setColor(*memoryCueOrLoop.color); + } + } + mainCueFound = true; + } else { + // Mixxx v2.4 will feature multiple loops, so these saved here will be usable + // For 2.3, Mixxx treats them as hotcues and the first one will be loaded as the single loop Mixxx supports + lastHotCueIndex++; + setHotCue( + track, + memoryCueOrLoop.startPosition, + memoryCueOrLoop.endPosition, + lastHotCueIndex, + memoryCueOrLoop.comment, + memoryCueOrLoop.color); + } } } + } catch (...) { + qWarning() << "Skipping malformed Rekordbox ANLZ import for" << anlzPath; } } @@ -1209,6 +1248,10 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { index, ColumnCache::COLUMN_TRACKLOCATIONSTABLE_LOCATION) .toString(); + if (!track) { + return track; + } + if (!QFile(location).exists()) { return track; } @@ -1263,18 +1306,25 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { #endif mixxx::audio::SampleRate sampleRate = track->getSampleRate(); + const bool importAnalyze = sampleRate.isValid(); + if (!importAnalyze) { + qWarning() << "Skipping Rekordbox ANLZ import with invalid sample rate for" + << location; + } QString anlzPath = getFieldVariant(index, ColumnCache::COLUMN_REKORDBOX_ANALYZE_PATH) .toString(); QString anlzPathExt = anlzPath.left(anlzPath.length() - 3) + "EXT"; - if (QFile(anlzPathExt).exists()) { - // Beatgrids appear to be only correct in legacy ANLZ file - readAnalyze(track, sampleRate, timingOffset, true, anlzPath); - readAnalyze(track, sampleRate, timingOffset, false, anlzPathExt); - } else { - readAnalyze(track, sampleRate, timingOffset, false, anlzPath); + if (importAnalyze) { + if (QFile(anlzPathExt).exists()) { + // Beatgrids appear to be only correct in legacy ANLZ file + readAnalyze(track, sampleRate, timingOffset, true, anlzPath); + readAnalyze(track, sampleRate, timingOffset, false, anlzPathExt); + } else { + readAnalyze(track, sampleRate, timingOffset, false, anlzPath); + } } // Assume that the key of the file the has been analyzed in Recordbox is correct From 145d607ffba7f5576f835d9ff94fab3a01fae4e5 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Mon, 14 Sep 2026 20:58:32 +0200 Subject: [PATCH 2/4] Fix clang-format spacing in Rekordbox import guard Remove the space before the template argument to repair the PR46 formatting-only CI failure. --- src/library/rekordbox/rekordboxfeature.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index e6f53c887aca..a23b2e756290 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -294,7 +294,7 @@ QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { rekordbox_pdb_t::device_sql_long_ascii_t* longAsciiString = static_cast(deviceString->body()); text = QString::fromStdString(longAsciiString->text()); - } else if (instanceof (deviceString->body())) { + } else if (instanceof(deviceString->body())) { rekordbox_pdb_t::device_sql_long_utf16le_t* longUtf16leString = static_cast(deviceString->body()); text = fromUtf16LeString(longUtf16leString->text()); From 473c3f823fe37d1a3e7c3666588de99e1f92343c Mon Sep 17 00:00:00 2001 From: 0cwa Date: Mon, 14 Sep 2026 22:11:28 +0200 Subject: [PATCH 3/4] Complete PR46 CI formatting correction --- src/library/rekordbox/rekordboxfeature.cpp | 304 +++++++++++---------- 1 file changed, 153 insertions(+), 151 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index a23b2e756290..0a5b18316b62 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -915,176 +915,176 @@ void readAnalyze(TrackPointer track, continue; } switch (section->fourcc()) { - case rekordbox_anlz_t::SECTION_TAGS_BEAT_GRID: { - if (!ignoreCues) { - break; - } + case rekordbox_anlz_t::SECTION_TAGS_BEAT_GRID: { + if (!ignoreCues) { + break; + } - auto* beatGridTag = - static_cast( - section->body()); - if (!beatGridTag || !beatGridTag->beats()) { - break; - } + auto* beatGridTag = + static_cast( + section->body()); + if (!beatGridTag || !beatGridTag->beats()) { + break; + } - QVector beats; + QVector beats; - for (const auto& beat : *beatGridTag->beats()) { - int time = static_cast(beat->time()) - timingOffset; - // Ensure no offset times are less than 1 - if (time < 1) { - time = 1; + for (const auto& beat : *beatGridTag->beats()) { + int time = static_cast(beat->time()) - timingOffset; + // Ensure no offset times are less than 1 + if (time < 1) { + time = 1; + } + beats << mixxx::audio::FramePos(sampleRateKhz * static_cast(time)); } - beats << mixxx::audio::FramePos(sampleRateKhz * static_cast(time)); - } - - const auto pBeats = mixxx::Beats::fromBeatPositions( - sampleRate, - beats, - mixxx::rekordboxconstants::beatsSubversion); - track->trySetBeats(pBeats); - } break; - case rekordbox_anlz_t::SECTION_TAGS_CUES: { - if (ignoreCues) { - break; - } - auto* cuesTag = - static_cast( - section->body()); - if (!cuesTag || !cuesTag->cues()) { - break; - } + const auto pBeats = mixxx::Beats::fromBeatPositions( + sampleRate, + beats, + mixxx::rekordboxconstants::beatsSubversion); + track->trySetBeats(pBeats); + } break; + case rekordbox_anlz_t::SECTION_TAGS_CUES: { + if (ignoreCues) { + break; + } - for (const auto& cueEntry : *cuesTag->cues()) { - int time = static_cast(cueEntry->time()) - timingOffset; - // Ensure no offset times are less than 1 - if (time < 1) { - time = 1; + auto* cuesTag = + static_cast( + section->body()); + if (!cuesTag || !cuesTag->cues()) { + break; } - const auto position = mixxx::audio::FramePos( - sampleRateKhz * static_cast(time)); - - switch (cuesTag->type()) { - case rekordbox_anlz_t::CUE_LIST_TYPE_MEMORY_CUES: { - switch (cueEntry->type()) { - case rekordbox_anlz_t::CUE_ENTRY_TYPE_MEMORY_CUE: { - memory_cue_loop_t memoryCue; - memoryCue.startPosition = position; - memoryCue.endPosition = mixxx::audio::kInvalidFramePos; - memoryCue.color = mixxx::RgbColor::nullopt(); - memoryCuesAndLoops << memoryCue; + + for (const auto& cueEntry : *cuesTag->cues()) { + int time = static_cast(cueEntry->time()) - timingOffset; + // Ensure no offset times are less than 1 + if (time < 1) { + time = 1; + } + const auto position = mixxx::audio::FramePos( + sampleRateKhz * static_cast(time)); + + switch (cuesTag->type()) { + case rekordbox_anlz_t::CUE_LIST_TYPE_MEMORY_CUES: { + switch (cueEntry->type()) { + case rekordbox_anlz_t::CUE_ENTRY_TYPE_MEMORY_CUE: { + memory_cue_loop_t memoryCue; + memoryCue.startPosition = position; + memoryCue.endPosition = mixxx::audio::kInvalidFramePos; + memoryCue.color = mixxx::RgbColor::nullopt(); + memoryCuesAndLoops << memoryCue; + } break; + case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { + int endTime = static_cast(cueEntry->loop_time()) - timingOffset; + // Ensure no offset times are less than 1 + if (endTime < 1) { + endTime = 1; + } + + memory_cue_loop_t loop; + loop.startPosition = position; + loop.endPosition = mixxx::audio::FramePos( + sampleRateKhz * static_cast(endTime)); + loop.color = mixxx::RgbColor::nullopt(); + memoryCuesAndLoops << loop; + } break; + } } break; - case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { - int endTime = static_cast(cueEntry->loop_time()) - timingOffset; - // Ensure no offset times are less than 1 - if (endTime < 1) { - endTime = 1; + case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { + int hotCueIndex = static_cast(cueEntry->hot_cue() - 1); + if (hotCueIndex > lastHotCueIndex) { + lastHotCueIndex = hotCueIndex; } - - memory_cue_loop_t loop; - loop.startPosition = position; - loop.endPosition = mixxx::audio::FramePos( - sampleRateKhz * static_cast(endTime)); - loop.color = mixxx::RgbColor::nullopt(); - memoryCuesAndLoops << loop; + setHotCue( + track, + position, + mixxx::audio::kInvalidFramePos, + hotCueIndex, + QString(), + mixxx::RgbColor::nullopt()); } break; } - } break; - case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { - int hotCueIndex = static_cast(cueEntry->hot_cue() - 1); - if (hotCueIndex > lastHotCueIndex) { - lastHotCueIndex = hotCueIndex; - } - setHotCue( - track, - position, - mixxx::audio::kInvalidFramePos, - hotCueIndex, - QString(), - mixxx::RgbColor::nullopt()); - } break; } - } - } break; - case rekordbox_anlz_t::SECTION_TAGS_CUES_2: { - if (ignoreCues) { - break; - } - - auto* cuesExtendedTag = - static_cast( - section->body()); - if (!cuesExtendedTag || !cuesExtendedTag->cues()) { - break; - } + } break; + case rekordbox_anlz_t::SECTION_TAGS_CUES_2: { + if (ignoreCues) { + break; + } - for (const auto& cueExtendedEntry : *cuesExtendedTag->cues()) { - int time = static_cast(cueExtendedEntry->time()) - timingOffset; - // Ensure no offset times are less than 1 - if (time < 1) { - time = 1; + auto* cuesExtendedTag = + static_cast( + section->body()); + if (!cuesExtendedTag || !cuesExtendedTag->cues()) { + break; } - const auto position = mixxx::audio::FramePos( - sampleRateKhz * static_cast(time)); - - switch (cuesExtendedTag->type()) { - case rekordbox_anlz_t::CUE_LIST_TYPE_MEMORY_CUES: { - switch (cueExtendedEntry->type()) { - case rekordbox_anlz_t::CUE_ENTRY_TYPE_MEMORY_CUE: { - memory_cue_loop_t memoryCue; - memoryCue.startPosition = position; - memoryCue.endPosition = mixxx::audio::kInvalidFramePos; - memoryCue.comment = fromUtf16BeString(cueExtendedEntry->comment()); - memoryCue.color = colorFromID(static_cast( - cueExtendedEntry->color_id())); - memoryCuesAndLoops << memoryCue; + + for (const auto& cueExtendedEntry : *cuesExtendedTag->cues()) { + int time = static_cast(cueExtendedEntry->time()) - timingOffset; + // Ensure no offset times are less than 1 + if (time < 1) { + time = 1; + } + const auto position = mixxx::audio::FramePos( + sampleRateKhz * static_cast(time)); + + switch (cuesExtendedTag->type()) { + case rekordbox_anlz_t::CUE_LIST_TYPE_MEMORY_CUES: { + switch (cueExtendedEntry->type()) { + case rekordbox_anlz_t::CUE_ENTRY_TYPE_MEMORY_CUE: { + memory_cue_loop_t memoryCue; + memoryCue.startPosition = position; + memoryCue.endPosition = mixxx::audio::kInvalidFramePos; + memoryCue.comment = fromUtf16BeString(cueExtendedEntry->comment()); + memoryCue.color = colorFromID(static_cast( + cueExtendedEntry->color_id())); + memoryCuesAndLoops << memoryCue; + } break; + case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { + int endTime = + static_cast( + cueExtendedEntry->loop_time()) - + timingOffset; + // Ensure no offset times are less than 1 + if (endTime < 1) { + endTime = 1; + } + + memory_cue_loop_t loop; + loop.startPosition = position; + loop.endPosition = mixxx::audio::FramePos( + sampleRateKhz * static_cast(endTime)); + loop.comment = fromUtf16BeString(cueExtendedEntry->comment()); + loop.color = colorFromID(static_cast(cueExtendedEntry->color_id())); + memoryCuesAndLoops << loop; + } break; + } } break; - case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { - int endTime = - static_cast( - cueExtendedEntry->loop_time()) - - timingOffset; - // Ensure no offset times are less than 1 - if (endTime < 1) { - endTime = 1; + case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { + int hotCueIndex = static_cast(cueExtendedEntry->hot_cue() - 1); + if (hotCueIndex > lastHotCueIndex) { + lastHotCueIndex = hotCueIndex; } - - memory_cue_loop_t loop; - loop.startPosition = position; - loop.endPosition = mixxx::audio::FramePos( - sampleRateKhz * static_cast(endTime)); - loop.comment = fromUtf16BeString(cueExtendedEntry->comment()); - loop.color = colorFromID(static_cast(cueExtendedEntry->color_id())); - memoryCuesAndLoops << loop; + setHotCue(track, + position, + mixxx::audio::kInvalidFramePos, + hotCueIndex, + fromUtf16BeString(cueExtendedEntry->comment()), + mixxx::RgbColor(qRgb( + static_cast( + cueExtendedEntry->color_red()), + static_cast( + cueExtendedEntry->color_green()), + static_cast(cueExtendedEntry + ->color_blue())))); } break; } - } break; - case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { - int hotCueIndex = static_cast(cueExtendedEntry->hot_cue() - 1); - if (hotCueIndex > lastHotCueIndex) { - lastHotCueIndex = hotCueIndex; - } - setHotCue(track, - position, - mixxx::audio::kInvalidFramePos, - hotCueIndex, - fromUtf16BeString(cueExtendedEntry->comment()), - mixxx::RgbColor(qRgb( - static_cast( - cueExtendedEntry->color_red()), - static_cast( - cueExtendedEntry->color_green()), - static_cast(cueExtendedEntry - ->color_blue())))); - } break; } + } break; + default: + break; } - } break; - default: - break; } - } if (memoryCuesAndLoops.size() > 0) { std::sort(memoryCuesAndLoops.begin(), @@ -1113,8 +1113,10 @@ void readAnalyze(TrackPointer track, } mainCueFound = true; } else { - // Mixxx v2.4 will feature multiple loops, so these saved here will be usable - // For 2.3, Mixxx treats them as hotcues and the first one will be loaded as the single loop Mixxx supports + // Mixxx v2.4 will feature multiple loops, so these saved + // here will be usable For 2.3, Mixxx treats them as hotcues + // and the first one will be loaded as the single loop Mixxx + // supports lastHotCueIndex++; setHotCue( track, From 9f6fa7d4f2e6e97214f372e04e926de724cee1e8 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Mon, 14 Sep 2026 22:15:38 +0200 Subject: [PATCH 4/4] Wrap long Rekordbox color assignment --- src/library/rekordbox/rekordboxfeature.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 0a5b18316b62..57d653008dac 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -1055,7 +1055,8 @@ void readAnalyze(TrackPointer track, loop.endPosition = mixxx::audio::FramePos( sampleRateKhz * static_cast(endTime)); loop.comment = fromUtf16BeString(cueExtendedEntry->comment()); - loop.color = colorFromID(static_cast(cueExtendedEntry->color_id())); + loop.color = colorFromID(static_cast( + cueExtendedEntry->color_id())); memoryCuesAndLoops << loop; } break; }