From 83e1b45e5cfbdef5f66ecc117a2a782bc43686c9 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Fri, 24 May 2019 21:36:57 +1000 Subject: [PATCH 01/36] Rekordbox library feature that reads tracks, playlists and folders from removable devices --- .DS_Store | Bin 0 -> 12292 bytes build/depends.py | 14 +- lib/.DS_Store | Bin 0 -> 8196 bytes lib/kaitai/LICENSE | 7 + lib/kaitai/custom_decoder.h | 16 + lib/kaitai/kaitaistream.cpp | 631 +++++++ lib/kaitai/kaitaistream.h | 250 +++ lib/kaitai/kaitaistruct.h | 20 + res/.DS_Store | Bin 0 -> 8196 bytes res/images/.DS_Store | Bin 0 -> 8196 bytes res/images/library/ic_library_rekordbox.svg | 46 + res/mixxx.qrc | 1 + res/schema.xml | 33 + src/.DS_Store | Bin 0 -> 10244 bytes src/database/mixxxdb.cpp | 2 +- src/library/.DS_Store | Bin 0 -> 8196 bytes src/library/library.cpp | 7 + src/library/rekordbox/.DS_Store | Bin 0 -> 6148 bytes src/library/rekordbox/rekordbox_pdb.cpp | 1008 +++++++++++ src/library/rekordbox/rekordbox_pdb.h | 1760 +++++++++++++++++++ src/library/rekordbox/rekordboxfeature.cpp | 932 ++++++++++ src/library/rekordbox/rekordboxfeature.h | 117 ++ 22 files changed, 4842 insertions(+), 2 deletions(-) create mode 100644 .DS_Store create mode 100644 lib/.DS_Store create mode 100644 lib/kaitai/LICENSE create mode 100755 lib/kaitai/custom_decoder.h create mode 100755 lib/kaitai/kaitaistream.cpp create mode 100755 lib/kaitai/kaitaistream.h create mode 100755 lib/kaitai/kaitaistruct.h create mode 100644 res/.DS_Store create mode 100644 res/images/.DS_Store create mode 100644 res/images/library/ic_library_rekordbox.svg create mode 100644 src/.DS_Store create mode 100644 src/library/.DS_Store create mode 100644 src/library/rekordbox/.DS_Store create mode 100644 src/library/rekordbox/rekordbox_pdb.cpp create mode 100644 src/library/rekordbox/rekordbox_pdb.h create mode 100755 src/library/rekordbox/rekordboxfeature.cpp create mode 100755 src/library/rekordbox/rekordboxfeature.h diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1c6f7a4f9c944dfcb65065bfae7805ee0ee965fb GIT binary patch literal 12292 zcmeHNd2AGA6n}4fFbB|Sxyn(xSf~YRX=$MaENn026tpd+TxGXAz`%63?CiFbQc^XF zQB*Vv5wAq!5y2y(F~(qwM}Wj5!6rb^Xw;Y(qsCy=_?PedzS-?|yG=Ym2+SliZ@%}w zZ@**Td*A!LZvX(RBmNSA0sxTcW>gyvkg5QR|;{EZjEkwC*m`$jvAjC z^bM-(Fq{=|R^ZB70T&I2*>5V{DPV#TZ@;cXzIM~F!o|gB(Up>#mOf096?OR1Wy_To zEvv)OJ6E-bjbOl9qlYY_En0@PE!erVH5gbO(wV$P%WUWM2Th|$WJrFjw7h4An}~@}#v9pRqGyguD7OGqjPpZdKXfry;Bi`q{7fwKpW0OQyP2bv={RgmilP zB}oiwx;j(Vyi$WjGZCC3rC8Q1xnYM9@NR(Cf4%`uN7X^t_NsTC#n0)?D69k)2N!6tjh0aW6JX| z*G?B-fILsJD9Pl}$3qc#paC|34(-qd`{90g01m+8@GQIp$KW`egj4Vld<>`IJbVM+ z!v**ieuLi;P{y&CjpK0w=HV4kJfe#{N zktXcd`QDPg~lF116Q6>wJI8eReBAKY{^>al6BpxkxP zgM|=G&O!)&(K~|!jOVtg$ELl4l7uc4O^J%8XhRGX&57@}>v?S2E2wA=$a|*Wv3o`v zp&;*__-J=n z1>Txn%vTE}LI;|Q|JIC|s-mS35RiFsR9qoZqrfw0#I=po*i5F%CC&_sWDu(LWC z3HUAkJM}^!$<$GcmfhW5I{RuKeIrcoZxM2y^Wd=JgTUBFtny|Hi$dhuCVQN-?N={NtJuP2VxG8gUVijl1R1|K} z+=Lj18JMtiZe)x_My5tBp4%2L--OwD99G^(VSTBcxw;ppq243@xZ zXa+xY5{`ZZ4#FW|#`h+n=o9cM^pW|Ug>&#V`~W|~Pw+GRfoV7lB|_3!+z8!dghd=l z&n6`8!D>R%wS=VW$pF`o32r9S+m1WXB-7iAw~^u9f&1|>LeWp+Q}_bDhR5)AJdW=O zjT_SV-W{NaQHS4@To1=6b(dEU2m1XRW|!YMq~9Ol&j0o%z`e0LNtqn=@QIo&_5+@U z(T0iI{eGWq8swe}Wajr#Nb}V~DY^+Lv(yrn4AN4BtW259@;+J`FDmnt1uQY7r3&dn z#luoSTDp+dD0Mt_r9sT|i?mI0b$gLgHt&(DorkaBJ5sX?q+&?wl|gBmTy#^SW-8|M zWX)_W#W|F!S%g)jW=zR$ri9Hc+{bUEq|IjT<4tVCAouazxQA43AKuLqH}~N|JcNh2 z>OGgBY)eEr`o!n^m2Hrxx5(GGUSXcTtb7i6`neaw)AQ*$p5F2Fm)X-t_0aM3%vn30 z{*ronu51aOKDrq=^h#WTG#XUgS;N)3AID8GZclcOIGrQTEAfbPpiN$y^FJpq$5ED( zmy7L6JTDjLC=>Q^`wNk)a)-DE@jv%5{J$??=l^v>fBzR#5a-2N0cQoSz7@dK+WOjR znpgDfB5{iBtldrbX1dwM>h%iBo6y5Pj^{Fd6i+<+TQkn{P<7U>S5V%J%A(o-_7MPO Y0GxBVA&&;wxtz2ACwxeHwdVi-0y6DcGXMYp literal 0 HcmV?d00001 diff --git a/build/depends.py b/build/depends.py index 1390b0ef025..643fe124c6d 100644 --- a/build/depends.py +++ b/build/depends.py @@ -486,6 +486,15 @@ def sources(self, build): def configure(self, build, conf): build.env.Append(CPPPATH="#lib/replaygain") +# For Rekordbox removable device binary database file parsing +class Kaitai(Dependence): + + def sources(self, build): + return ["lib/kaitai/kaitaistream.cpp"] + + def configure(self, build, conf): + build.env.Append(CPPDEFINES=['KS_STR_ENCODING_NONE']) + build.env.Append(CPPPATH="#lib/kaitai") class Ebur128Mit(Dependence): INTERNAL_PATH = 'lib/libebur128' @@ -1043,6 +1052,9 @@ def sources(self, build): "src/library/itunes/itunesfeature.cpp", "src/library/traktor/traktorfeature.cpp", + + "src/library/rekordbox/rekordboxfeature.cpp", + "src/library/rekordbox/rekordbox_pdb.cpp", "src/library/sidebarmodel.cpp", "src/library/library.cpp", @@ -1543,7 +1555,7 @@ def depends(self, build): FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib, ProtoBuf, Chromaprint, RubberBand, SecurityFramework, CoreServices, IOKit, QtScriptByteArray, Reverb, FpClassify, PortAudioRingBuffer, LAME, - QueenMaryDsp, OSXFilePathUrlBackport] + QueenMaryDsp, OSXFilePathUrlBackport, Kaitai] def post_dependency_check_configure(self, build, conf): """Sets up additional things in the Environment that must happen diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0d4562360217f377b49803412930a51bc99a4ef2 GIT binary patch literal 8196 zcmeI1U2GIp6oAj!7MSY*ovlE(P)ZC=-abA~|NT8`EiDVC;gOx6NGilpRFPj}I z!U%*B2qO?iAdEm5f&W7U=*;Ftxy89JN5eLZKp25LG6MYl5U0vzGLR)H#jArFkpht9 zDS#-{r#wK|!~>ZOWJyY4O81o60|urTq!=j8X*|xSlS~G(B&9S5l;(iJ${1uQC{`!C zxR?%@lrn6?2!s(>9s&OISqbNP<_>;E{{C5d#(O(k`PB*CKbH>rBd%*N_Vx6bWfrHjb-k|R>y~5Ygt29qH08{=wi0`uD-u^U~o#)@}7Cpv~}Mqn0>l$dQ)1BN8S!) zT<1j2EC|n{8Qsq0X_DD(+bQd^>5XSxJD+vD3E#ZvbIqjZ=KNlvoUq82>!!3?Za+NZ zTdw1cnK_Rn#dm(qhK=#<&G+u>I?}VSwnmHA-p$w~g~jo$M=UcpTC@!R+$r75n2upN zXU7YcXQgd(#L=^6H1a-U3(*>Ftr}As6s=L2$fvCfdCR{fv~07wm1) zp=$47>Ucr=q#X;C2M#iIBI9PWr1K z_1%VH8j8}G+$kSzW3p9t=yk6^JtW{LoPn}V{0hIp@9+mA-i0cz#u#qHCfte5*n<183lHKUJdFK#43Fc3IE)iG zjcLrFfmtlzCA^HY_!K^k&)~E8GQNTD;5@#I*YHERvq+i7A$eVH@nh;O`N~diQu~R8@6t5-o3Z;aQEUPtaxBcn7R=p!H)z)fKvuT5W7$pt8Y*@ zu`OkeEBPKJ)Xa+t=S5q!&=9Myi*Hj^jT57YOOB|91T_ou + +namespace kaitai { + +class custom_decoder { +public: + virtual ~custom_decoder() {}; + virtual std::string decode(std::string src) = 0; +}; + +} + +#endif diff --git a/lib/kaitai/kaitaistream.cpp b/lib/kaitai/kaitaistream.cpp new file mode 100755 index 00000000000..2a9f082e957 --- /dev/null +++ b/lib/kaitai/kaitaistream.cpp @@ -0,0 +1,631 @@ +#include + +#if defined(__APPLE__) +#include +#include +#define bswap_16(x) OSSwapInt16(x) +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) +#define __BYTE_ORDER BYTE_ORDER +#define __BIG_ENDIAN BIG_ENDIAN +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#elif defined(_MSC_VER) // !__APPLE__ +#include +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN +#define bswap_16(x) _byteswap_ushort(x) +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) +#else // !__APPLE__ or !_MSC_VER +#include +#include +#endif + +#include +#include +#include + +kaitai::kstream::kstream(std::istream* io) { + m_io = io; + init(); +} + +kaitai::kstream::kstream(std::string& data): m_io_str(data) { + m_io = &m_io_str; + init(); +} + +void kaitai::kstream::init() { + exceptions_enable(); + align_to_byte(); +} + +void kaitai::kstream::close() { + // m_io->close(); +} + +void kaitai::kstream::exceptions_enable() const { + m_io->exceptions( + std::istream::eofbit | + std::istream::failbit | + std::istream::badbit + ); +} + +// ======================================================================== +// Stream positioning +// ======================================================================== + +bool kaitai::kstream::is_eof() const { + if (m_bits_left > 0) { + return false; + } + char t; + m_io->exceptions( + std::istream::badbit + ); + m_io->get(t); + if (m_io->eof()) { + m_io->clear(); + exceptions_enable(); + return true; + } else { + m_io->unget(); + exceptions_enable(); + return false; + } +} + +void kaitai::kstream::seek(uint64_t pos) { + m_io->seekg(pos); +} + +uint64_t kaitai::kstream::pos() { + return m_io->tellg(); +} + +uint64_t kaitai::kstream::size() { + std::iostream::pos_type cur_pos = m_io->tellg(); + m_io->seekg(0, std::ios::end); + std::iostream::pos_type len = m_io->tellg(); + m_io->seekg(cur_pos); + return len; +} + +// ======================================================================== +// Integer numbers +// ======================================================================== + +// ------------------------------------------------------------------------ +// Signed +// ------------------------------------------------------------------------ + +int8_t kaitai::kstream::read_s1() { + char t; + m_io->get(t); + return t; +} + +// ........................................................................ +// Big-endian +// ........................................................................ + +int16_t kaitai::kstream::read_s2be() { + int16_t t; + m_io->read(reinterpret_cast(&t), 2); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_16(t); +#endif + return t; +} + +int32_t kaitai::kstream::read_s4be() { + int32_t t; + m_io->read(reinterpret_cast(&t), 4); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_32(t); +#endif + return t; +} + +int64_t kaitai::kstream::read_s8be() { + int64_t t; + m_io->read(reinterpret_cast(&t), 8); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_64(t); +#endif + return t; +} + +// ........................................................................ +// Little-endian +// ........................................................................ + +int16_t kaitai::kstream::read_s2le() { + int16_t t; + m_io->read(reinterpret_cast(&t), 2); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_16(t); +#endif + return t; +} + +int32_t kaitai::kstream::read_s4le() { + int32_t t; + m_io->read(reinterpret_cast(&t), 4); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_32(t); +#endif + return t; +} + +int64_t kaitai::kstream::read_s8le() { + int64_t t; + m_io->read(reinterpret_cast(&t), 8); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_64(t); +#endif + return t; +} + +// ------------------------------------------------------------------------ +// Unsigned +// ------------------------------------------------------------------------ + +uint8_t kaitai::kstream::read_u1() { + char t; + m_io->get(t); + return t; +} + +// ........................................................................ +// Big-endian +// ........................................................................ + +uint16_t kaitai::kstream::read_u2be() { + uint16_t t; + m_io->read(reinterpret_cast(&t), 2); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_16(t); +#endif + return t; +} + +uint32_t kaitai::kstream::read_u4be() { + uint32_t t; + m_io->read(reinterpret_cast(&t), 4); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_32(t); +#endif + return t; +} + +uint64_t kaitai::kstream::read_u8be() { + uint64_t t; + m_io->read(reinterpret_cast(&t), 8); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_64(t); +#endif + return t; +} + +// ........................................................................ +// Little-endian +// ........................................................................ + +uint16_t kaitai::kstream::read_u2le() { + uint16_t t; + m_io->read(reinterpret_cast(&t), 2); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_16(t); +#endif + return t; +} + +uint32_t kaitai::kstream::read_u4le() { + uint32_t t; + m_io->read(reinterpret_cast(&t), 4); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_32(t); +#endif + return t; +} + +uint64_t kaitai::kstream::read_u8le() { + uint64_t t; + m_io->read(reinterpret_cast(&t), 8); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_64(t); +#endif + return t; +} + +// ======================================================================== +// Floating point numbers +// ======================================================================== + +// ........................................................................ +// Big-endian +// ........................................................................ + +float kaitai::kstream::read_f4be() { + uint32_t t; + m_io->read(reinterpret_cast(&t), 4); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_32(t); +#endif + return reinterpret_cast(t); +} + +double kaitai::kstream::read_f8be() { + uint64_t t; + m_io->read(reinterpret_cast(&t), 8); +#if __BYTE_ORDER == __LITTLE_ENDIAN + t = bswap_64(t); +#endif + return reinterpret_cast(t); +} + +// ........................................................................ +// Little-endian +// ........................................................................ + +float kaitai::kstream::read_f4le() { + uint32_t t; + m_io->read(reinterpret_cast(&t), 4); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_32(t); +#endif + return reinterpret_cast(t); +} + +double kaitai::kstream::read_f8le() { + uint64_t t; + m_io->read(reinterpret_cast(&t), 8); +#if __BYTE_ORDER == __BIG_ENDIAN + t = bswap_64(t); +#endif + return reinterpret_cast(t); +} + +// ======================================================================== +// Unaligned bit values +// ======================================================================== + +void kaitai::kstream::align_to_byte() { + m_bits_left = 0; + m_bits = 0; +} + +uint64_t kaitai::kstream::read_bits_int(int n) { + int bits_needed = n - m_bits_left; + if (bits_needed > 0) { + // 1 bit => 1 byte + // 8 bits => 1 byte + // 9 bits => 2 bytes + int bytes_needed = ((bits_needed - 1) / 8) + 1; + if (bytes_needed > 8) + throw std::runtime_error("read_bits_int: more than 8 bytes requested"); + char buf[8]; + m_io->read(buf, bytes_needed); + for (int i = 0; i < bytes_needed; i++) { + uint8_t b = buf[i]; + m_bits <<= 8; + m_bits |= b; + m_bits_left += 8; + } + } + + // raw mask with required number of 1s, starting from lowest bit + uint64_t mask = get_mask_ones(n); + // shift mask to align with highest bits available in @bits + int shift_bits = m_bits_left - n; + mask <<= shift_bits; + // derive reading result + uint64_t res = (m_bits & mask) >> shift_bits; + // clear top bits that we've just read => AND with 1s + m_bits_left -= n; + mask = get_mask_ones(m_bits_left); + m_bits &= mask; + + return res; +} + +uint64_t kaitai::kstream::get_mask_ones(int n) { + if (n == 64) { + return 0xFFFFFFFFFFFFFFFF; + } else { + return ((uint64_t) 1 << n) - 1; + } +} + +// ======================================================================== +// Byte arrays +// ======================================================================== + +std::string kaitai::kstream::read_bytes(std::streamsize len) { + std::vector result(len); + + // NOTE: streamsize type is signed, negative values are only *supposed* to not be used. + // http://en.cppreference.com/w/cpp/io/streamsize + if (len < 0) { + throw std::runtime_error("read_bytes: requested a negative amount"); + } + + if (len > 0) { + m_io->read(&result[0], len); + } + + return std::string(result.begin(), result.end()); +} + +std::string kaitai::kstream::read_bytes_full() { + std::iostream::pos_type p1 = m_io->tellg(); + m_io->seekg(0, std::ios::end); + std::iostream::pos_type p2 = m_io->tellg(); + size_t len = p2 - p1; + + // Note: this requires a std::string to be backed with a + // contiguous buffer. Officially, it's a only requirement since + // C++11 (C++98 and C++03 didn't have this requirement), but all + // major implementations had contiguous buffers anyway. + std::string result(len, ' '); + m_io->seekg(p1); + m_io->read(&result[0], len); + + return result; +} + +std::string kaitai::kstream::read_bytes_term(char term, bool include, bool consume, bool eos_error) { + std::string result; + std::getline(*m_io, result, term); + if (m_io->eof()) { + // encountered EOF + if (eos_error) { + throw std::runtime_error("read_bytes_term: encountered EOF"); + } + } else { + // encountered terminator + if (include) + result.push_back(term); + if (!consume) + m_io->unget(); + } + return result; +} + +std::string kaitai::kstream::ensure_fixed_contents(std::string expected) { + std::string actual = read_bytes(expected.length()); + + if (actual != expected) { + // NOTE: I think printing it outright is not best idea, it could contain non-ascii charactes like backspace and beeps and whatnot. It would be better to print hexlified version, and also to redirect it to stderr. + throw std::runtime_error("ensure_fixed_contents: actual data does not match expected data"); + } + + return actual; +} + +std::string kaitai::kstream::bytes_strip_right(std::string src, char pad_byte) { + std::size_t new_len = src.length(); + + while (new_len > 0 && src[new_len - 1] == pad_byte) + new_len--; + + return src.substr(0, new_len); +} + +std::string kaitai::kstream::bytes_terminate(std::string src, char term, bool include) { + std::size_t new_len = 0; + std::size_t max_len = src.length(); + + while (new_len < max_len && src[new_len] != term) + new_len++; + + if (include && new_len < max_len) + new_len++; + + return src.substr(0, new_len); +} + +// ======================================================================== +// Byte array processing +// ======================================================================== + +std::string kaitai::kstream::process_xor_one(std::string data, uint8_t key) { + size_t len = data.length(); + std::string result(len, ' '); + + for (size_t i = 0; i < len; i++) + result[i] = data[i] ^ key; + + return result; +} + +std::string kaitai::kstream::process_xor_many(std::string data, std::string key) { + size_t len = data.length(); + size_t kl = key.length(); + std::string result(len, ' '); + + size_t ki = 0; + for (size_t i = 0; i < len; i++) { + result[i] = data[i] ^ key[ki]; + ki++; + if (ki >= kl) + ki = 0; + } + + return result; +} + +std::string kaitai::kstream::process_rotate_left(std::string data, int amount) { + size_t len = data.length(); + std::string result(len, ' '); + + for (size_t i = 0; i < len; i++) { + uint8_t bits = data[i]; + result[i] = (bits << amount) | (bits >> (8 - amount)); + } + + return result; +} + +#ifdef KS_ZLIB +#include + +std::string kaitai::kstream::process_zlib(std::string data) { + int ret; + + unsigned char *src_ptr = reinterpret_cast(&data[0]); + std::stringstream dst_strm; + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + ret = inflateInit(&strm); + if (ret != Z_OK) + throw std::runtime_error("process_zlib: inflateInit error"); + + strm.next_in = src_ptr; + strm.avail_in = data.length(); + + unsigned char outbuffer[ZLIB_BUF_SIZE]; + std::string outstring; + + // get the decompressed bytes blockwise using repeated calls to inflate + do { + strm.next_out = reinterpret_cast(outbuffer); + strm.avail_out = sizeof(outbuffer); + + ret = inflate(&strm, 0); + + if (outstring.size() < strm.total_out) + outstring.append(reinterpret_cast(outbuffer), strm.total_out - outstring.size()); + } while (ret == Z_OK); + + if (ret != Z_STREAM_END) { // an error occurred that was not EOF + std::ostringstream exc_msg; + exc_msg << "process_zlib: error #" << ret << "): " << strm.msg; + throw std::runtime_error(exc_msg.str()); + } + + if (inflateEnd(&strm) != Z_OK) + throw std::runtime_error("process_zlib: inflateEnd error"); + + return outstring; +} +#endif + +// ======================================================================== +// Misc utility methods +// ======================================================================== + +int kaitai::kstream::mod(int a, int b) { + if (b <= 0) + throw std::invalid_argument("mod: divisor b <= 0"); + int r = a % b; + if (r < 0) + r += b; + return r; +} + +#include +std::string kaitai::kstream::to_string(int val) { + // if int is 32 bits, "-2147483648" is the longest string representation + // => 11 chars + zero => 12 chars + // if int is 64 bits, "-9223372036854775808" is the longest + // => 20 chars + zero => 21 chars + char buf[25]; + int got_len = snprintf(buf, sizeof(buf), "%d", val); + + // should never happen, but check nonetheless + if (got_len > sizeof(buf)) + throw std::invalid_argument("to_string: integer is longer than string buffer"); + + return std::string(buf); +} + +#include +std::string kaitai::kstream::reverse(std::string val) { + std::reverse(val.begin(), val.end()); + + return val; +} + +// ======================================================================== +// Other internal methods +// ======================================================================== + +#ifndef KS_STR_DEFAULT_ENCODING +#define KS_STR_DEFAULT_ENCODING "UTF-8" +#endif + +#ifdef KS_STR_ENCODING_ICONV + +#include +#include +#include + +std::string kaitai::kstream::bytes_to_str(std::string src, std::string src_enc) { + iconv_t cd = iconv_open(KS_STR_DEFAULT_ENCODING, src_enc.c_str()); + + if (cd == (iconv_t) -1) { + if (errno == EINVAL) { + throw std::runtime_error("bytes_to_str: invalid encoding pair conversion requested"); + } else { + throw std::runtime_error("bytes_to_str: error opening iconv"); + } + } + + size_t src_len = src.length(); + size_t src_left = src_len; + + // Start with a buffer length of double the source length. + size_t dst_len = src_len * 2; + std::string dst(dst_len, ' '); + size_t dst_left = dst_len; + + char *src_ptr = &src[0]; + char *dst_ptr = &dst[0]; + + while (true) { + size_t res = iconv(cd, &src_ptr, &src_left, &dst_ptr, &dst_left); + + if (res == (size_t) -1) { + if (errno == E2BIG) { + // dst buffer is not enough to accomodate whole string + // enlarge the buffer and try again + size_t dst_used = dst_len - dst_left; + dst_left += dst_len; + dst_len += dst_len; + dst.resize(dst_len); + + // dst.resize might have allocated destination buffer in another area + // of memory, thus our previous pointer "dst" will be invalid; re-point + // it using "dst_used". + dst_ptr = &dst[dst_used]; + } else { + throw std::runtime_error("bytes_to_str: iconv error"); + } + } else { + // conversion successful + dst.resize(dst_len - dst_left); + break; + } + } + + if (iconv_close(cd) != 0) { + throw std::runtime_error("bytes_to_str: iconv close error"); + } + + return dst; +} +#elif defined(KS_STR_ENCODING_NONE) +std::string kaitai::kstream::bytes_to_str(std::string src, std::string src_enc) { + return src; +} +#else +#error Need to decide how to handle strings: please define one of: KS_STR_ENCODING_ICONV, KS_STR_ENCODING_NONE +#endif diff --git a/lib/kaitai/kaitaistream.h b/lib/kaitai/kaitaistream.h new file mode 100755 index 00000000000..9592771db04 --- /dev/null +++ b/lib/kaitai/kaitaistream.h @@ -0,0 +1,250 @@ +#ifndef KAITAI_STREAM_H +#define KAITAI_STREAM_H + +// Kaitai Struct runtime API version: x.y.z = 'xxxyyyzzz' decimal +#define KAITAI_STRUCT_VERSION 7000L + +#include +#include +#include +#include + +namespace kaitai { + +/** + * Kaitai Stream class (kaitai::kstream) is an implementation of + * Kaitai Struct stream API + * for C++/STL. It's implemented as a wrapper over generic STL std::istream. + * + * It provides a wide variety of simple methods to read (parse) binary + * representations of primitive types, such as integer and floating + * point numbers, byte arrays and strings, and also provides stream + * positioning / navigation methods with unified cross-language and + * cross-toolkit semantics. + * + * Typically, end users won't access Kaitai Stream class manually, but would + * describe a binary structure format using .ksy language and then would use + * Kaitai Struct compiler to generate source code in desired target language. + * That code, in turn, would use this class and API to do the actual parsing + * job. + */ +class kstream { +public: + /** + * Constructs new Kaitai Stream object, wrapping a given std::istream. + * \param io istream object to use for this Kaitai Stream + */ + kstream(std::istream* io); + + /** + * Constructs new Kaitai Stream object, wrapping a given in-memory data + * buffer. + * \param data data buffer to use for this Kaitai Stream + */ + kstream(std::string& data); + + void close(); + + /** @name Stream positioning */ + //@{ + /** + * Check if stream pointer is at the end of stream. Note that the semantics + * are different from traditional STL semantics: one does *not* need to do a + * read (which will fail) after the actual end of the stream to trigger EOF + * flag, which can be accessed after that read. It is sufficient to just be + * at the end of the stream for this method to return true. + * \return "true" if we are located at the end of the stream. + */ + bool is_eof() const; + + /** + * Set stream pointer to designated position. + * \param pos new position (offset in bytes from the beginning of the stream) + */ + void seek(uint64_t pos); + + /** + * Get current position of a stream pointer. + * \return pointer position, number of bytes from the beginning of the stream + */ + uint64_t pos(); + + /** + * Get total size of the stream in bytes. + * \return size of the stream in bytes + */ + uint64_t size(); + //@} + + /** @name Integer numbers */ + //@{ + + // ------------------------------------------------------------------------ + // Signed + // ------------------------------------------------------------------------ + + int8_t read_s1(); + + // ........................................................................ + // Big-endian + // ........................................................................ + + int16_t read_s2be(); + int32_t read_s4be(); + int64_t read_s8be(); + + // ........................................................................ + // Little-endian + // ........................................................................ + + int16_t read_s2le(); + int32_t read_s4le(); + int64_t read_s8le(); + + // ------------------------------------------------------------------------ + // Unsigned + // ------------------------------------------------------------------------ + + uint8_t read_u1(); + + // ........................................................................ + // Big-endian + // ........................................................................ + + uint16_t read_u2be(); + uint32_t read_u4be(); + uint64_t read_u8be(); + + // ........................................................................ + // Little-endian + // ........................................................................ + + uint16_t read_u2le(); + uint32_t read_u4le(); + uint64_t read_u8le(); + + //@} + + /** @name Floating point numbers */ + //@{ + + // ........................................................................ + // Big-endian + // ........................................................................ + + float read_f4be(); + double read_f8be(); + + // ........................................................................ + // Little-endian + // ........................................................................ + + float read_f4le(); + double read_f8le(); + + //@} + + /** @name Unaligned bit values */ + //@{ + + void align_to_byte(); + uint64_t read_bits_int(int n); + + //@} + + /** @name Byte arrays */ + //@{ + + std::string read_bytes(std::streamsize len); + std::string read_bytes_full(); + std::string read_bytes_term(char term, bool include, bool consume, bool eos_error); + std::string ensure_fixed_contents(std::string expected); + + static std::string bytes_strip_right(std::string src, char pad_byte); + static std::string bytes_terminate(std::string src, char term, bool include); + static std::string bytes_to_str(std::string src, std::string src_enc); + + //@} + + /** @name Byte array processing */ + //@{ + + /** + * Performs a XOR processing with given data, XORing every byte of input with a single + * given value. + * @param data data to process + * @param key value to XOR with + * @return processed data + */ + static std::string process_xor_one(std::string data, uint8_t key); + + /** + * Performs a XOR processing with given data, XORing every byte of input with a key + * array, repeating key array many times, if necessary (i.e. if data array is longer + * than key array). + * @param data data to process + * @param key array of bytes to XOR with + * @return processed data + */ + static std::string process_xor_many(std::string data, std::string key); + + /** + * Performs a circular left rotation shift for a given buffer by a given amount of bits, + * using groups of 1 bytes each time. Right circular rotation should be performed + * using this procedure with corrected amount. + * @param data source data to process + * @param amount number of bits to shift by + * @return copy of source array with requested shift applied + */ + static std::string process_rotate_left(std::string data, int amount); + +#ifdef KS_ZLIB + /** + * Performs an unpacking ("inflation") of zlib-compressed data with usual zlib headers. + * @param data data to unpack + * @return unpacked data + * @throws IOException + */ + static std::string process_zlib(std::string data); +#endif + + //@} + + /** + * Performs modulo operation between two integers: dividend `a` + * and divisor `b`. Divisor `b` is expected to be positive. The + * result is always 0 <= x <= b - 1. + */ + static int mod(int a, int b); + + /** + * Converts given integer `val` to a decimal string representation. + * Should be used in place of std::to_string() (which is available only + * since C++11) in older C++ implementations. + */ + static std::string to_string(int val); + + /** + * Reverses given string `val`, so that the first character becomes the + * last and the last one becomes the first. This should be used to avoid + * the need of local variables at the caller. + */ + static std::string reverse(std::string val); + +private: + std::istream* m_io; + std::istringstream m_io_str; + int m_bits_left; + uint64_t m_bits; + + void init(); + void exceptions_enable() const; + + static uint64_t get_mask_ones(int n); + + static const int ZLIB_BUF_SIZE = 128 * 1024; +}; + +} + +#endif diff --git a/lib/kaitai/kaitaistruct.h b/lib/kaitai/kaitaistruct.h new file mode 100755 index 00000000000..f8b848fdd1a --- /dev/null +++ b/lib/kaitai/kaitaistruct.h @@ -0,0 +1,20 @@ +#ifndef KAITAI_STRUCT_H +#define KAITAI_STRUCT_H + +#include + +namespace kaitai { + +class kstruct { +public: + kstruct(kstream *_io) { m__io = _io; } + virtual ~kstruct() {} +protected: + kstream *m__io; +public: + kstream *_io() { return m__io; } +}; + +} + +#endif diff --git a/res/.DS_Store b/res/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..29e422cf6f98f51cc539b16e4c8e6122ebdf8c58 GIT binary patch literal 8196 zcmeI1Pf*-s6u{qGpyZE8z_x()&sHc?6blPWX|1KpE;uk+K^Iv5w1w;@LTom{O?H7& zI%p3%T93A49rfUhI_k;lc&g)gQBPi&X**sV4_+Mg;Kifw%a=u2ibr)yzL|V4`QCf^ zzW4G=vM&n&7|&}FfO-HBXk${5sk=jCdbVw9A%CQtB$7XX4o-T(nNOQ`dXsf95M&_8 zK#+kT13?CY4E!H5K<{kUge~6tTpRR327(N1%M7sRLzp%uW4>JF(tC7J=am2?Sqb1Z z+NV0e^9lPh=F3$sJy$v>uO85MMZd&A?oRqJGbb7IN`ZJ?np`bTA`Gq}m zz!;Z7A7miNz~&6F+h+$DkOdWHOX~M;)AOd`xb5vXF;rGwv7=HDMM10*&m`yUg`}&x zc_*&srr6Q2>dvM8{Sn(X*Z0-*n4vFBD?1am<*J6I=R9Xa)5+v?%CIzhA#Ud_&GF8Y z2^ZK+>?NE$^bU2*T3tl`m8P&|> zP1V(VO_Lcpsf=yrvz9aN>WeNLnRM)&oA3sw6(#TJ&*)@t6!bpwa;BA9cBY|u*RU;T zOwTza8)NN!>7wva)$RwvO)ZBzkDiFH-CM2H)ZQmalawCIHJ&x}+~|U#xpNb$lhG~B zux3*Q!!gpPK4Pg^y(VOfqUTjtFU#ZkwDEl2aF>)uLGVWR?3Ja{8ZG0Lt+BId%4Y(% zKd@Jpr^?UFods%hO+G?C8}?ULkF zfybsi;SU~rRFcOtb~a0)I5pVn5BHpu<+E(&CsoHCn%6B_)i?h130lGJqg9W$@ufr9%3h%7CemY7{Lxajxmg50{ie3CUF$UFoiQXhtJ_TG%=4C@dbPlFX3f; z6W_wO@g4jCKf&wxDc-;@_|if}8VC7tvBxiIZ^JD|H2R;+La95`xJ&B)Z)v<;#Sz@O zYxn-9mPb0eAM0Jege4bk16O}X$>WCu;(=2H!y|UBuD;=Zd5^TO$Z;v(qlB8iC@@}> zN^X5XZWIU+Meq)#X8Djv94pAR&2pAn;zom+=*R6<@{eYnH}~m~M0lj_&>pl<^SjS^eL?_4og~KM_HMK?b&Q z2C%#@*%zk-Z1pN&wRV`c8QNH5cB@=^F4TGDI7zP@C%OKIA?=6Bl=;MbxymJtQ2WmtcA4!AbaXmXc4qrS zt!YfuM2w;_F&cT%_yqnW#s_13QX?el12#dU#s_`z(P%XB$#d_WZJ?zO`k)f#Cik9u z&$(yrJ>UIiZq6(cQ!A|3irOWMs0*Zgurcy0RKKDsdAYK<-C;Q(?N|$0Z8%` zKosgz9w2;@q0EGGUP|Fg&y?8%hOQW<7%1Jzp5)U>W+1G5q>@by^>cHoQh9e>OXtb)nX|Kx%{~5# zj10rC1C%d`^ws5II{svP-r-@!UM_|C^NUB5rDrUR}S^u0pRE|mQ?`Ep&3 zv7&E1WRasiX?2jh&p=f!uB+Hqb5E*n-+|1bh4tkuu5Hvd_f6Wqop-F>DccOj1`L1H za!uPkJxGYTfqlxh3Tiy&sXeYSZY@+Ms^a<vp2?qPviB8LSau|JeTZAy3l_DeqR36{*7vB)Pzl4erG>)?qUqz!p4+?RXTA z;c@K7UL3(38kom1Jc9)cu!wW`6h4j5;5mE=FW@D78{ffq@dNxAKfzD&OZ*DIlN*ec zNPA3eo zuP2jWkEDm7O^Fub6mQ@o=XCX!J2kzR*kw&GeGf`ZXKOi+l!a>Ih<=w|qYz_QINYi4 zQi#?pCZ2Wg9$jOrh{38D?oDetTTNtD#IT`J(^wg?Sz7OFChHpFt+c8KHI2V*s#r(c zsduOf!+*fRH{cR{2%i!AuEI6=0e*sC;7{V+8f3(~YTSz3a69h8-M9x+Sd06yDP-Vb zd=QUd7qPGx`)~jUaR^O3MI5y85%h_JQ#g$?_yj&FxOg64z!wD@U&o6}xYt=?os@WK zIrk>B1=sQ1(_|?z_C}{@bZa57N^Up7^Z!FD|Neh#1r_Z!LLfrmZy|tX9oddHa#`uK z9wS?l>Pf1+@Okr63Kwel6s{NPILXC745^ +image/svg+xml \ No newline at end of file diff --git a/res/mixxx.qrc b/res/mixxx.qrc index d711b8dcd19..377f47772f0 100644 --- a/res/mixxx.qrc +++ b/res/mixxx.qrc @@ -25,6 +25,7 @@ images/library/ic_library_recordings.svg images/library/ic_library_rhythmbox.svg images/library/ic_library_traktor.svg + images/library/ic_library_rekordbox.svg images/mixxx_logo.svg images/mixxx_icon.svg images/mixxx-icon-logo-symbolic.svg diff --git a/res/schema.xml b/res/schema.xml index 8b9040538e7..a934e7ff52f 100644 --- a/res/schema.xml +++ b/res/schema.xml @@ -447,4 +447,37 @@ METADATA ALTER TABLE cues ADD COLUMN source INTEGER DEFAULT 2 NOT NULL; + + + Tables for Rekordbox library feature + + + CREATE TABLE IF NOT EXISTS rekordbox_library ( + id INTEGER primary key AUTOINCREMENT, + rb_id integer, + artist varchar(48), title varchar(48), + album varchar(48), year varchar(16), + genre varchar(32), tracknumber varchar(3), + location varchar(512) UNIQUE, + comment varchar(60), + duration integer, + bitrate integer, + bpm float, + key varchar(6), + rating integer, + analyze_path varchar(512) UNIQUE, + device varchar(48) + ); + CREATE TABLE IF NOT EXISTS rekordbox_playlists ( + id INTEGER primary key, + name varchar(100) UNIQUE + ); + CREATE TABLE IF NOT EXISTS rekordbox_playlist_tracks ( + id INTEGER primary key AUTOINCREMENT, + playlist_id INTEGER REFERENCES rekordbox_playlists(id), + track_id INTEGER REFERENCES rekordbox_library(id), + position integer + ); + + diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8b184fc71f773aaea9a91aa3104fb5665ecb6352 GIT binary patch literal 10244 zcmeHMU2Ggz6+WNs#4|fH@i=bczb4tnY2w5tu@k#VT#~GR64!A|tlEy9AL8tKr^(Ru zuDi2q$8}v95FoW_8!8YKN~==(LQ_EEr-i~31rh;$D2v+iBLsy99uOc(k@`~3y?1tT z)=s4q5T%-tX3pMo?wNb>XV1J?* zMad&4Hseh#sU0YS2m}!bA`nC%h(Hj5cS8i2v&E$uN(Rp$0zm|V2oxe9=7%ufLPmVK zpro8S_#w9dEX!CeFaFK@0O=Fo*`wB4UOA1b8~IG+nd9?ZkcZm&$YJhZfg$jY1=hF zuWQvex9;mZGdX+r+&%O6KBC43e}15fqOpFC%Ni98G~rEmtAUn$CO^^k$tFEc7 zH0q6Y*YeWr25l@GbMMN!nb}-rg}rJ+*a)49a~B3uaq+ge{Y_1mmQ7*9I4LaMX@{S2 zO;erPYBcF~R2%kqBucYA)S}y-K71#CSgA2~h1xYcu8$<5F(;u^dqN$W-LH>Mt8gmi zzR&@!63zP9L~3%9$K%PCJG%_yv}pNA)XN;5a+2Ju%iMfrO>Old?$vQ&>x{=mpBf^K z>buJTG(=idN#{>rOWT=ChUpYJG)1#?fgYrX=psEzPth~(25S+ihbCRZXCe?j$#laIE`^c5yLs$g)}nAVjdsI zCvYF`$EWZGT*6oIReTNK#`p08et;M8GyF<*up|%L0aeaF@oWCI9Bik2*q&Y%wg(UO z{;y#BswJ?xy6T2CYd37#x_#H4eVv8RS@abb!%5;t0Ym~P4-EMouR^3ibzS|9A!CH` z<(x0hggn;ma)Bf*SH~zZZZaA)#*FP3Hye!_1KTc<@7mU6gzQqrur7-`T0(}sg5j#k zVq1GCWS23Xm340iTURo2l~vy#3WaY&$u>pk5M>- z^O(g)@G*(Ti})lSmU#Rko-RUPUmkZ#;?i>Hn;uFh6RG4`wh7dhQ-WM(jx_T}nr6k> z?|YFuiiWv}IbO`C;4c<4hqbEzNdLeG%>Mv;2(Fn(rxJ<5l;;W(I$q$DC8n|KVVH`V z@XazsyLxjXXU#Qcbv}QJ^J;m9sS>HU&a#SY=igFb7iJnwwXxCKq;v9JoztRqYopbu zX^SU?Tvg?2ds~xb%Gp6?sL2=o|Dc zdX}D}7nopv&II!Z`U|J{>-2X{^HL_5D%4{WHe(AXc`J6J4ejWXX+6M69m83f&UZ4c z%wi7rNNTx&2k{Ut;t@Q7C-Egb#RT&#zJurSU8a{G;xew_75o;z!)y2x-oRTWWhIvC ze}QlY^W*XLt5`0@dY*1ReGRPl-qzc9_}z^4SM#J)U31gsrk3`O1KkB66n*x^2)qtj z5}9DFH^X`87pH`xE4jY0%X2r@)z@xpuq<2PPLstgja)3@)X;L`UFQ3&9b#o=m&&5G z(`*xqD0_vn?l$*|RhC_*tnWAXi}jSfQohQFn5Lp7(N0gJW!lj6e;2wxrW~(}{>-3# zlior(OkNRf#5P_Jbs&OH^xz-<9)X^;5z7L=6k>?hq>s?+@c`kw*8{$HF&1^fT&I6E#{VE*6l|NjYY)-4kN literal 0 HcmV?d00001 diff --git a/src/database/mixxxdb.cpp b/src/database/mixxxdb.cpp index 737e64277c0..5a18ff73526 100644 --- a/src/database/mixxxdb.cpp +++ b/src/database/mixxxdb.cpp @@ -11,7 +11,7 @@ const QString MixxxDb::kDefaultSchemaFile(":/schema.xml"); //static -const int MixxxDb::kRequiredSchemaVersion = 29; +const int MixxxDb::kRequiredSchemaVersion = 30; namespace { diff --git a/src/library/.DS_Store b/src/library/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..244e8a7ad0156f49fc546be4c2bbcbe6a27f2979 GIT binary patch literal 8196 zcmeHMT}&NC6h7xd%Z^*h(w1_erM(R;7D_D?XrX^z?xhw|D-D;w(kkq|yTYc+F1@?= z@+V6Ci9TpGQDfA^7-M20eKKicj4{4hqc27ht43dZ@WB_2zWC;unFVP9AJw!w$;_EK zXU@!?Z@!()xfcL1kuw?rq5z;!Qf#dd}4u&2XbCY;Yw@D>;VH;3{nh~?qrYg=_KQUoR?C%14?(mU}p?66coFYUrbB~ zj7u4|Ap#)+s}bO@52L)@gv*q-ugKrMiL~ve$@+PE!nQoWq2U{h6cldQSfntfuwwRb zYRsKX`MRI;5_t*JiN)G}8en{7PYe6&8++R{8X$CUDYjVHTC#;0dyADesp zbr~H7Hw2X27VR5rhiZHIqzpBZT?+N)-=V6T9w{|0~y!OjXU0;Z%+AKGvv8hKPi-h7Ws1Bu(pN! zcTf11>v;WU)+0$MFBEUz5!+jPUsLPJ#Ny@>t(0wLWdnK3v(mQNH)$FE*ah9on2upN zqeBFm<6DnfW|oykK4iU)K5hyxHL6xBTD3BmOIufRmOm}ms#13|?YeTwpvOJu8r;*6 z^Q+WqMY+iJ$*f7Ou~=H7ZQfU-vR;oKSJM?HG<63U>%B~;r+G|j4mB{f&(_nXtr<)a zlbTzYI*^w!X=ejv+cBmNX58^{dStH@>q*VyQ}0*X743%d2;cib-SfLAOeferS*5BM zxW3&mOhZws{lg{0@I0;ytM1CXC`9tikcP#i#KZd={U>*YIt89~bZ%UdK=5lSP&|*(JwIEq+S9HC{jBWbpb$ z2G-+b;=keKx5fORwrt&2wte^B+JlFWo@ie_$lH$X7G|xalVFjgiJ;FCJtSqzqZO6v zPPS`3qW2Nu%qfMlMk`vZj8>G#_Nc1Hd83FSC#r@-Z6=0wHDryDjW5g&sf~;XtQCl% z+RTn9L{)8rw6?M1jL57NO6w^WSBSIPMzK&L$(RQG-D0cB_Yc&22NvKXqTSbUlgRcH z{6cj5lc-jNrC3f>+krcA7v4)`JAn0g5Ra@N+!^e|F6_Y+_F+HKPAA$KXyPbN;1%@H zC;B~s^Y|n_MfAIhFX7Ah3ciYONs28KuXBlVQsR4S6EC02I=1VKl68r)w>pNSI|qRR z8A_Dr|EJgg{r}DhIE*AjAVgpl0$A9Q>PV1!y(^#R+A*plRC(d^=A{%a)QEJPq)5j} kF8^Uj{TNN@~ literal 0 HcmV?d00001 diff --git a/src/library/library.cpp b/src/library/library.cpp index d95416941dc..66d0f3aad0a 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -20,6 +20,7 @@ #include "library/crate/cratefeature.h" #include "library/rhythmbox/rhythmboxfeature.h" #include "library/banshee/bansheefeature.h" +#include "library/rekordbox/rekordboxfeature.h" #include "library/recording/recordingfeature.h" #include "library/itunes/itunesfeature.h" #include "library/mixxxlibraryfeature.h" @@ -161,6 +162,12 @@ Library::Library( addFeature(new TraktorFeature(this, m_pTrackCollection)); } + // Rekordbox feature added persistently as the only way to enable it to + // dynamically appear/disappear when correctly prepared removable devices + // are mounted/unmounted would be to have some form of timed thread to check + // periodically. Not ideal perfomance wise. + addFeature(new RekordboxFeature(this, m_pTrackCollection)); + // On startup we need to check if all of the user's library folders are // accessible to us. If the user is using a database from <1.12.0 with // sandboxing then we will need them to give us permission. diff --git a/src/library/rekordbox/.DS_Store b/src/library/rekordbox/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0read_u4le(); + m_len_page = m__io->read_u4le(); + m_num_tables = m__io->read_u4le(); + m_next_unused_page = m__io->read_u4le(); + m__unnamed4 = m__io->read_u4le(); + m_sequence = m__io->read_u4le(); + m__unnamed6 = m__io->ensure_fixed_contents(std::string("\x00\x00\x00\x00", 4)); + int l_tables = num_tables(); + m_tables = new std::vector(); + m_tables->reserve(l_tables); + for (int i = 0; i < l_tables; i++) { + m_tables->push_back(new table_t(m__io, this, m__root)); + } +} + +rekordbox_pdb_t::~rekordbox_pdb_t() { + for (std::vector::iterator it = m_tables->begin(); it != m_tables->end(); ++it) { + delete *it; + } + delete m_tables; +} + +rekordbox_pdb_t::device_sql_string_t::device_sql_string_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::device_sql_string_t::_read() { + m_length_and_kind = m__io->read_u1(); + switch (length_and_kind()) { + case 64: { + m_body = new device_sql_long_ascii_t(m__io, this, m__root); + break; + } + case 144: { + m_body = new device_sql_long_utf16be_t(m__io, this, m__root); + break; + } + default: { + m_body = new device_sql_short_ascii_t(length_and_kind(), m__io, this, m__root); + break; + } + } +} + +rekordbox_pdb_t::device_sql_string_t::~device_sql_string_t() { + delete m_body; +} + +rekordbox_pdb_t::playlist_tree_row_t::playlist_tree_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_is_folder = false; + _read(); +} + +void rekordbox_pdb_t::playlist_tree_row_t::_read() { + m_parent_id = m__io->read_u4le(); + m__unnamed1 = m__io->read_bytes(4); + m_sort_order = m__io->read_u4le(); + m_id = m__io->read_u4le(); + m_raw_is_folder = m__io->read_u4le(); + m_name = new device_sql_string_t(m__io, this, m__root); +} + +rekordbox_pdb_t::playlist_tree_row_t::~playlist_tree_row_t() { + delete m_name; +} + +bool rekordbox_pdb_t::playlist_tree_row_t::is_folder() { + if (f_is_folder) + return m_is_folder; + m_is_folder = raw_is_folder() != 0; + f_is_folder = true; + return m_is_folder; +} + +rekordbox_pdb_t::color_row_t::color_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::color_row_t::_read() { + m__unnamed0 = m__io->read_bytes(5); + m_id = m__io->read_u2le(); + m__unnamed2 = m__io->read_u1(); + m_name = new device_sql_string_t(m__io, this, m__root); +} + +rekordbox_pdb_t::color_row_t::~color_row_t() { + delete m_name; +} + +rekordbox_pdb_t::device_sql_short_ascii_t::device_sql_short_ascii_t(uint8_t p_mangled_length, kaitai::kstream* p__io, rekordbox_pdb_t::device_sql_string_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_mangled_length = p_mangled_length; + f_length = false; + _read(); +} + +void rekordbox_pdb_t::device_sql_short_ascii_t::_read() { + n_text = true; + if ( ((kaitai::kstream::mod(mangled_length(), 2) > 0) && (length() >= 0)) ) { + n_text = false; + m_text = kaitai::kstream::bytes_to_str(m__io->read_bytes(length()), std::string("ascii")); + } +} + +rekordbox_pdb_t::device_sql_short_ascii_t::~device_sql_short_ascii_t() { + if (!n_text) { + } +} + +int32_t rekordbox_pdb_t::device_sql_short_ascii_t::length() { + if (f_length) + return m_length; + m_length = (((mangled_length() - 1) / 2) - 1); + f_length = true; + return m_length; +} + +rekordbox_pdb_t::album_row_t::album_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_name = false; + _read(); +} + +void rekordbox_pdb_t::album_row_t::_read() { + m__unnamed0 = m__io->read_u2le(); + m_index_shift = m__io->read_u2le(); + m__unnamed2 = m__io->read_u4le(); + m_artist_id = m__io->read_u4le(); + m_id = m__io->read_u4le(); + m__unnamed5 = m__io->read_u4le(); + m__unnamed6 = m__io->read_u1(); + m_ofs_name = m__io->read_u1(); +} + +rekordbox_pdb_t::album_row_t::~album_row_t() { + if (f_name) { + delete m_name; + } +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::album_row_t::name() { + if (f_name) + return m_name; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_name())); + m_name = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_name = true; + return m_name; +} + +rekordbox_pdb_t::page_t::page_t(kaitai::kstream* p__io, rekordbox_pdb_t::page_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_num_rows = false; + f_num_groups = false; + f_row_groups = false; + f_heap_pos = false; + f_is_data_page = false; + _read(); +} + +void rekordbox_pdb_t::page_t::_read() { + m__unnamed0 = m__io->ensure_fixed_contents(std::string("\x00\x00\x00\x00", 4)); + m_page_index = m__io->read_u4le(); + m_type = static_cast(m__io->read_u4le()); + m_next_page = new page_ref_t(m__io, this, m__root); + m__unnamed4 = m__io->read_u4le(); + m__unnamed5 = m__io->read_bytes(4); + m_num_rows_small = m__io->read_u1(); + m__unnamed7 = m__io->read_u1(); + m__unnamed8 = m__io->read_u1(); + m_page_flags = m__io->read_u1(); + m_free_size = m__io->read_u2le(); + m_used_size = m__io->read_u2le(); + m__unnamed12 = m__io->read_u2le(); + m_num_rows_large = m__io->read_u2le(); + m__unnamed14 = m__io->read_u2le(); + m__unnamed15 = m__io->read_u2le(); + n_heap = true; + if (false) { + n_heap = false; + m_heap = m__io->read_bytes_full(); + } +} + +rekordbox_pdb_t::page_t::~page_t() { + delete m_next_page; + if (!n_heap) { + } + if (f_row_groups && !n_row_groups) { + for (std::vector::iterator it = m_row_groups->begin(); it != m_row_groups->end(); ++it) { + delete *it; + } + delete m_row_groups; + } +} + +uint16_t rekordbox_pdb_t::page_t::num_rows() { + if (f_num_rows) + return m_num_rows; + m_num_rows = (( ((num_rows_large() > num_rows_small()) && (num_rows_large() != 8191)) ) ? (num_rows_large()) : (num_rows_small())); + f_num_rows = true; + return m_num_rows; +} + +int32_t rekordbox_pdb_t::page_t::num_groups() { + if (f_num_groups) + return m_num_groups; + m_num_groups = (((num_rows() - 1) / 16) + 1); + f_num_groups = true; + return m_num_groups; +} + +std::vector* rekordbox_pdb_t::page_t::row_groups() { + if (f_row_groups) + return m_row_groups; + n_row_groups = true; + if (is_data_page()) { + n_row_groups = false; + int l_row_groups = num_groups(); + m_row_groups = new std::vector(); + m_row_groups->reserve(l_row_groups); + for (int i = 0; i < l_row_groups; i++) { + m_row_groups->push_back(new row_group_t(i, m__io, this, m__root)); + } + } + f_row_groups = true; + return m_row_groups; +} + +int32_t rekordbox_pdb_t::page_t::heap_pos() { + if (f_heap_pos) + return m_heap_pos; + m_heap_pos = _io()->pos(); + f_heap_pos = true; + return m_heap_pos; +} + +bool rekordbox_pdb_t::page_t::is_data_page() { + if (f_is_data_page) + return m_is_data_page; + m_is_data_page = (page_flags() & 64) == 0; + f_is_data_page = true; + return m_is_data_page; +} + +rekordbox_pdb_t::row_group_t::row_group_t(uint16_t p_group_index, kaitai::kstream* p__io, rekordbox_pdb_t::page_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_group_index = p_group_index; + f_base = false; + f_row_present_flags = false; + f_rows = false; + _read(); +} + +void rekordbox_pdb_t::row_group_t::_read() { +} + +rekordbox_pdb_t::row_group_t::~row_group_t() { + if (f_row_present_flags) { + } + if (f_rows) { + for (std::vector::iterator it = m_rows->begin(); it != m_rows->end(); ++it) { + delete *it; + } + delete m_rows; + } +} + +int32_t rekordbox_pdb_t::row_group_t::base() { + if (f_base) + return m_base; + m_base = (_root()->len_page() - (group_index() * 36)); + f_base = true; + return m_base; +} + +uint16_t rekordbox_pdb_t::row_group_t::row_present_flags() { + if (f_row_present_flags) + return m_row_present_flags; + std::streampos _pos = m__io->pos(); + m__io->seek((base() - 4)); + m_row_present_flags = m__io->read_u2le(); + m__io->seek(_pos); + f_row_present_flags = true; + return m_row_present_flags; +} + +std::vector* rekordbox_pdb_t::row_group_t::rows() { + if (f_rows) + return m_rows; + int l_rows = ((group_index() < (_parent()->num_groups() - 1)) ? (16) : ((kaitai::kstream::mod((_parent()->num_rows() - 1), 16) + 1))); + m_rows = new std::vector(); + m_rows->reserve(l_rows); + for (int i = 0; i < l_rows; i++) { + m_rows->push_back(new row_ref_t(i, m__io, this, m__root)); + } + f_rows = true; + return m_rows; +} + +rekordbox_pdb_t::genre_row_t::genre_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::genre_row_t::_read() { + m_id = m__io->read_u4le(); + m_name = new device_sql_string_t(m__io, this, m__root); +} + +rekordbox_pdb_t::genre_row_t::~genre_row_t() { + delete m_name; +} + +rekordbox_pdb_t::artwork_row_t::artwork_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::artwork_row_t::_read() { + m_id = m__io->read_u4le(); + m_path = new device_sql_string_t(m__io, this, m__root); +} + +rekordbox_pdb_t::artwork_row_t::~artwork_row_t() { + delete m_path; +} + +rekordbox_pdb_t::device_sql_long_ascii_t::device_sql_long_ascii_t(kaitai::kstream* p__io, rekordbox_pdb_t::device_sql_string_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::device_sql_long_ascii_t::_read() { + m_length = m__io->read_u2le(); + m_text = kaitai::kstream::bytes_to_str(m__io->read_bytes(length()), std::string("ascii")); +} + +rekordbox_pdb_t::device_sql_long_ascii_t::~device_sql_long_ascii_t() { +} + +rekordbox_pdb_t::artist_row_t::artist_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_ofs_name_far = false; + f_name = false; + _read(); +} + +void rekordbox_pdb_t::artist_row_t::_read() { + m_subtype = m__io->read_u2le(); + m_index_shift = m__io->read_u2le(); + m_id = m__io->read_u4le(); + m__unnamed3 = m__io->read_u1(); + m_ofs_name_near = m__io->read_u1(); +} + +rekordbox_pdb_t::artist_row_t::~artist_row_t() { + if (f_ofs_name_far && !n_ofs_name_far) { + } + if (f_name) { + delete m_name; + } +} + +uint16_t rekordbox_pdb_t::artist_row_t::ofs_name_far() { + if (f_ofs_name_far) + return m_ofs_name_far; + n_ofs_name_far = true; + if (subtype() == 100) { + n_ofs_name_far = false; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + 10)); + m_ofs_name_far = m__io->read_u2le(); + m__io->seek(_pos); + } + f_ofs_name_far = true; + return m_ofs_name_far; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::artist_row_t::name() { + if (f_name) + return m_name; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ((subtype() == 100) ? (ofs_name_far()) : (ofs_name_near())))); + m_name = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_name = true; + return m_name; +} + +rekordbox_pdb_t::page_ref_t::page_ref_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_body = false; + _read(); +} + +void rekordbox_pdb_t::page_ref_t::_read() { + m_index = m__io->read_u4le(); +} + +rekordbox_pdb_t::page_ref_t::~page_ref_t() { + if (f_body) { + delete m__io__raw_body; + delete m_body; + } +} + +rekordbox_pdb_t::page_t* rekordbox_pdb_t::page_ref_t::body() { + if (f_body) + return m_body; + kaitai::kstream *io = _root()->_io(); + std::streampos _pos = io->pos(); + io->seek((_root()->len_page() * index())); + m__raw_body = io->read_bytes(_root()->len_page()); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new page_t(m__io__raw_body, this, m__root); + io->seek(_pos); + f_body = true; + return m_body; +} + +rekordbox_pdb_t::device_sql_long_utf16be_t::device_sql_long_utf16be_t(kaitai::kstream* p__io, rekordbox_pdb_t::device_sql_string_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::device_sql_long_utf16be_t::_read() { + m_length = m__io->read_u2le(); + m_text = kaitai::kstream::bytes_to_str(m__io->read_bytes((length() - 4)), std::string("utf-16be")); +} + +rekordbox_pdb_t::device_sql_long_utf16be_t::~device_sql_long_utf16be_t() { +} + +rekordbox_pdb_t::track_row_t::track_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_unknown_string_8 = false; + f_unknown_string_6 = false; + f_analyze_date = false; + f_file_path = false; + f_autoload_hotcues = false; + f_date_added = false; + f_unknown_string_3 = false; + f_texter = false; + f_kuvo_public = false; + f_mix_name = false; + f_unknown_string_5 = false; + f_unknown_string_4 = false; + f_message = false; + f_unknown_string_2 = false; + f_unknown_string_1 = false; + f_unknown_string_7 = false; + f_filename = false; + f_analyze_path = false; + f_comment = false; + f_release_date = false; + f_title = false; + _read(); +} + +void rekordbox_pdb_t::track_row_t::_read() { + m__unnamed0 = m__io->read_u2le(); + m_index_shift = m__io->read_u2le(); + m_bitmask = m__io->read_u4le(); + m_sample_rate = m__io->read_u4le(); + m_composer_id = m__io->read_u4le(); + m_file_size = m__io->read_u4le(); + m__unnamed6 = m__io->read_u4le(); + m__unnamed7 = m__io->read_u2le(); + m__unnamed8 = m__io->read_u2le(); + m_artwork_id = m__io->read_u4le(); + m_key_id = m__io->read_u4le(); + m_original_artist_id = m__io->read_u4le(); + m_label_id = m__io->read_u4le(); + m_remixer_id = m__io->read_u4le(); + m_bitrate = m__io->read_u4le(); + m_track_number = m__io->read_u4le(); + m_tempo = m__io->read_u4le(); + m_genre_id = m__io->read_u4le(); + m_album_id = m__io->read_u4le(); + m_artist_id = m__io->read_u4le(); + m_id = m__io->read_u4le(); + m_disc_number = m__io->read_u2le(); + m_play_count = m__io->read_u2le(); + m_year = m__io->read_u2le(); + m_sample_depth = m__io->read_u2le(); + m_duration = m__io->read_u2le(); + m__unnamed26 = m__io->read_u2le(); + m_color_id = m__io->read_u1(); + m_rating = m__io->read_u1(); + m__unnamed29 = m__io->read_u2le(); + m__unnamed30 = m__io->read_u2le(); + int l_ofs_strings = 21; + m_ofs_strings = new std::vector(); + m_ofs_strings->reserve(l_ofs_strings); + for (int i = 0; i < l_ofs_strings; i++) { + m_ofs_strings->push_back(m__io->read_u2le()); + } +} + +rekordbox_pdb_t::track_row_t::~track_row_t() { + delete m_ofs_strings; + if (f_unknown_string_8) { + delete m_unknown_string_8; + } + if (f_unknown_string_6) { + delete m_unknown_string_6; + } + if (f_analyze_date) { + delete m_analyze_date; + } + if (f_file_path) { + delete m_file_path; + } + if (f_autoload_hotcues) { + delete m_autoload_hotcues; + } + if (f_date_added) { + delete m_date_added; + } + if (f_unknown_string_3) { + delete m_unknown_string_3; + } + if (f_texter) { + delete m_texter; + } + if (f_kuvo_public) { + delete m_kuvo_public; + } + if (f_mix_name) { + delete m_mix_name; + } + if (f_unknown_string_5) { + delete m_unknown_string_5; + } + if (f_unknown_string_4) { + delete m_unknown_string_4; + } + if (f_message) { + delete m_message; + } + if (f_unknown_string_2) { + delete m_unknown_string_2; + } + if (f_unknown_string_1) { + delete m_unknown_string_1; + } + if (f_unknown_string_7) { + delete m_unknown_string_7; + } + if (f_filename) { + delete m_filename; + } + if (f_analyze_path) { + delete m_analyze_path; + } + if (f_comment) { + delete m_comment; + } + if (f_release_date) { + delete m_release_date; + } + if (f_title) { + delete m_title; + } +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_8() { + if (f_unknown_string_8) + return m_unknown_string_8; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(18))); + m_unknown_string_8 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_8 = true; + return m_unknown_string_8; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_6() { + if (f_unknown_string_6) + return m_unknown_string_6; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(9))); + m_unknown_string_6 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_6 = true; + return m_unknown_string_6; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::analyze_date() { + if (f_analyze_date) + return m_analyze_date; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(15))); + m_analyze_date = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_analyze_date = true; + return m_analyze_date; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::file_path() { + if (f_file_path) + return m_file_path; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(20))); + m_file_path = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_file_path = true; + return m_file_path; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::autoload_hotcues() { + if (f_autoload_hotcues) + return m_autoload_hotcues; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(7))); + m_autoload_hotcues = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_autoload_hotcues = true; + return m_autoload_hotcues; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::date_added() { + if (f_date_added) + return m_date_added; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(10))); + m_date_added = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_date_added = true; + return m_date_added; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_3() { + if (f_unknown_string_3) + return m_unknown_string_3; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(3))); + m_unknown_string_3 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_3 = true; + return m_unknown_string_3; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::texter() { + if (f_texter) + return m_texter; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(1))); + m_texter = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_texter = true; + return m_texter; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::kuvo_public() { + if (f_kuvo_public) + return m_kuvo_public; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(6))); + m_kuvo_public = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_kuvo_public = true; + return m_kuvo_public; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::mix_name() { + if (f_mix_name) + return m_mix_name; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(12))); + m_mix_name = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_mix_name = true; + return m_mix_name; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_5() { + if (f_unknown_string_5) + return m_unknown_string_5; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(8))); + m_unknown_string_5 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_5 = true; + return m_unknown_string_5; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_4() { + if (f_unknown_string_4) + return m_unknown_string_4; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(4))); + m_unknown_string_4 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_4 = true; + return m_unknown_string_4; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::message() { + if (f_message) + return m_message; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(5))); + m_message = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_message = true; + return m_message; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_2() { + if (f_unknown_string_2) + return m_unknown_string_2; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(2))); + m_unknown_string_2 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_2 = true; + return m_unknown_string_2; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_1() { + if (f_unknown_string_1) + return m_unknown_string_1; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(0))); + m_unknown_string_1 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_1 = true; + return m_unknown_string_1; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::unknown_string_7() { + if (f_unknown_string_7) + return m_unknown_string_7; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(13))); + m_unknown_string_7 = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_unknown_string_7 = true; + return m_unknown_string_7; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::filename() { + if (f_filename) + return m_filename; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(19))); + m_filename = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_filename = true; + return m_filename; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::analyze_path() { + if (f_analyze_path) + return m_analyze_path; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(14))); + m_analyze_path = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_analyze_path = true; + return m_analyze_path; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::comment() { + if (f_comment) + return m_comment; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(16))); + m_comment = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_comment = true; + return m_comment; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::release_date() { + if (f_release_date) + return m_release_date; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(11))); + m_release_date = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_release_date = true; + return m_release_date; +} + +rekordbox_pdb_t::device_sql_string_t* rekordbox_pdb_t::track_row_t::title() { + if (f_title) + return m_title; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->row_base() + ofs_strings()->at(17))); + m_title = new device_sql_string_t(m__io, this, m__root); + m__io->seek(_pos); + f_title = true; + return m_title; +} + +rekordbox_pdb_t::key_row_t::key_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::key_row_t::_read() { + m_id = m__io->read_u4le(); + m_id2 = m__io->read_u4le(); + m_name = new device_sql_string_t(m__io, this, m__root); +} + +rekordbox_pdb_t::key_row_t::~key_row_t() { + delete m_name; +} + +rekordbox_pdb_t::playlist_entry_row_t::playlist_entry_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::playlist_entry_row_t::_read() { + m_entry_index = m__io->read_u4le(); + m_track_id = m__io->read_u4le(); + m_playlist_id = m__io->read_u4le(); +} + +rekordbox_pdb_t::playlist_entry_row_t::~playlist_entry_row_t() { +} + +rekordbox_pdb_t::label_row_t::label_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::label_row_t::_read() { + m_id = m__io->read_u4le(); + m_name = new device_sql_string_t(m__io, this, m__root); +} + +rekordbox_pdb_t::label_row_t::~label_row_t() { + delete m_name; +} + +rekordbox_pdb_t::table_t::table_t(kaitai::kstream* p__io, rekordbox_pdb_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_pdb_t::table_t::_read() { + m_type = static_cast(m__io->read_u4le()); + m_empty_candidate = m__io->read_u4le(); + m_first_page = new page_ref_t(m__io, this, m__root); + m_last_page = new page_ref_t(m__io, this, m__root); +} + +rekordbox_pdb_t::table_t::~table_t() { + delete m_first_page; + delete m_last_page; +} + +rekordbox_pdb_t::row_ref_t::row_ref_t(uint16_t p_row_index, kaitai::kstream* p__io, rekordbox_pdb_t::row_group_t* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_row_index = p_row_index; + f_ofs_row = false; + f_row_base = false; + f_present = false; + f_body = false; + _read(); +} + +void rekordbox_pdb_t::row_ref_t::_read() { +} + +rekordbox_pdb_t::row_ref_t::~row_ref_t() { + if (f_ofs_row) { + } + if (f_body && !n_body) { + delete m_body; + } +} + +uint16_t rekordbox_pdb_t::row_ref_t::ofs_row() { + if (f_ofs_row) + return m_ofs_row; + std::streampos _pos = m__io->pos(); + m__io->seek((_parent()->base() - (6 + (2 * row_index())))); + m_ofs_row = m__io->read_u2le(); + m__io->seek(_pos); + f_ofs_row = true; + return m_ofs_row; +} + +int32_t rekordbox_pdb_t::row_ref_t::row_base() { + if (f_row_base) + return m_row_base; + m_row_base = (ofs_row() + _parent()->_parent()->heap_pos()); + f_row_base = true; + return m_row_base; +} + +bool rekordbox_pdb_t::row_ref_t::present() { + if (f_present) + return m_present; + m_present = ((((_parent()->row_present_flags() >> row_index()) & 1) != 0) ? (true) : (false)); + f_present = true; + return m_present; +} + +kaitai::kstruct* rekordbox_pdb_t::row_ref_t::body() { + if (f_body) + return m_body; + n_body = true; + if (present()) { + n_body = false; + std::streampos _pos = m__io->pos(); + m__io->seek(row_base()); + n_body = true; + switch (_parent()->_parent()->type()) { + case PAGE_TYPE_KEYS: { + n_body = false; + m_body = new key_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_GENRES: { + n_body = false; + m_body = new genre_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_PLAYLIST_ENTRIES: { + n_body = false; + m_body = new playlist_entry_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_TRACKS: { + n_body = false; + m_body = new track_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_PLAYLIST_TREE: { + n_body = false; + m_body = new playlist_tree_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_LABELS: { + n_body = false; + m_body = new label_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_ALBUMS: { + n_body = false; + m_body = new album_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_COLORS: { + n_body = false; + m_body = new color_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_ARTISTS: { + n_body = false; + m_body = new artist_row_t(m__io, this, m__root); + break; + } + case PAGE_TYPE_ARTWORK: { + n_body = false; + m_body = new artwork_row_t(m__io, this, m__root); + break; + } + } + m__io->seek(_pos); + } + f_body = true; + return m_body; +} diff --git a/src/library/rekordbox/rekordbox_pdb.h b/src/library/rekordbox/rekordbox_pdb.h new file mode 100644 index 00000000000..e8614d09344 --- /dev/null +++ b/src/library/rekordbox/rekordbox_pdb.h @@ -0,0 +1,1760 @@ +#ifndef REKORDBOX_PDB_H_ +#define REKORDBOX_PDB_H_ + +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "kaitaistruct.h" + +#include +#include + +#if KAITAI_STRUCT_VERSION < 7000L +#error "Incompatible Kaitai Struct C++/STL API: version 0.7 or later is required" +#endif + +/** + * This is a relational database format designed to be efficiently used + * by very low power devices (there were deployments on 16 bit devices + * with 32K of RAM). Today you are most likely to encounter it within + * the Pioneer Professional DJ ecosystem, because it is the format that + * their rekordbox software uses to write USB and SD media which can be + * mounted in DJ controllers and used to play and mix music. + * + * It has been reverse-engineered to facilitate sophisticated + * integrations with light and laser shows, videos, and other musical + * instruments, by supporting deep knowledge of what is playing and + * what is coming next through monitoring the network communications of + * the players. + * + * The file is divided into fixed-size blocks. The first block has a + * header that establishes the block size, and lists the tables + * available in the database, identifying their types and the index of + * the first of the series of linked pages that make up that table. + * + * Each table is made up of a series of rows which may be spread across + * any number of pages. The pages start with a header describing the + * page and linking to the next page. The rest of the page is used as a + * heap: rows are scattered around it, and located using an index + * structure that builds backwards from the end of the page. Each row + * of a given type has a fixed size structure which links to any + * variable-sized strings by their offsets within the page. + * + * As changes are made to the table, some records may become unused, + * and there may be gaps within the heap that are too small to be used + * by other data. There is a bit map in the row index that identifies + * which rows are actually present. Rows that are not present must be + * ignored: they do not contain valid (or even necessarily well-formed) + * data. + * + * The majority of the work in reverse-engineering this format was + * performed by @henrybetts and @flesniak, for which I am hugely + * grateful. @GreyCat helped me learn the intricacies (and best + * practices) of Kaitai far faster than I would have managed on my own. + * \sa Source + */ + +class rekordbox_pdb_t : public kaitai::kstruct { + +public: + class device_sql_string_t; + class playlist_tree_row_t; + class color_row_t; + class device_sql_short_ascii_t; + class album_row_t; + class page_t; + class row_group_t; + class genre_row_t; + class artwork_row_t; + class device_sql_long_ascii_t; + class artist_row_t; + class page_ref_t; + class device_sql_long_utf16be_t; + class track_row_t; + class key_row_t; + class playlist_entry_row_t; + class label_row_t; + class table_t; + class row_ref_t; + + enum page_type_t { + PAGE_TYPE_TRACKS = 0, + PAGE_TYPE_GENRES = 1, + PAGE_TYPE_ARTISTS = 2, + PAGE_TYPE_ALBUMS = 3, + PAGE_TYPE_LABELS = 4, + PAGE_TYPE_KEYS = 5, + PAGE_TYPE_COLORS = 6, + PAGE_TYPE_PLAYLIST_TREE = 7, + PAGE_TYPE_PLAYLIST_ENTRIES = 8, + PAGE_TYPE_UNKNOWN_9 = 9, + PAGE_TYPE_UNKNOWN_10 = 10, + PAGE_TYPE_UNKNOWN_11 = 11, + PAGE_TYPE_UNKNOWN_12 = 12, + PAGE_TYPE_ARTWORK = 13, + PAGE_TYPE_UNKNOWN_14 = 14, + PAGE_TYPE_UNKNOWN_15 = 15, + PAGE_TYPE_COLUMNS = 16, + PAGE_TYPE_UNKNOWN_17 = 17, + PAGE_TYPE_UNKNOWN_18 = 18, + PAGE_TYPE_HISTORY = 19 + }; + + rekordbox_pdb_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, rekordbox_pdb_t* p__root = 0); + +private: + void _read(); + +public: + ~rekordbox_pdb_t(); + + /** + * A variable length string which can be stored in a variety of + * different encodings. + */ + + class device_sql_string_t : public kaitai::kstruct { + + public: + + device_sql_string_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~device_sql_string_t(); + + private: + uint8_t m_length_and_kind; + kaitai::kstruct* m_body; + rekordbox_pdb_t* m__root; + kaitai::kstruct* m__parent; + + public: + + /** + * Mangled length of an ordinary ASCII string if odd, or a flag + * indicating another encoding with a longer length value to + * follow. + */ + uint8_t length_and_kind() const { return m_length_and_kind; } + kaitai::kstruct* body() const { return m_body; } + rekordbox_pdb_t* _root() const { return m__root; } + kaitai::kstruct* _parent() const { return m__parent; } + }; + + /** + * A row that holds a playlist name, ID, indication of whether it + * is an ordinary playlist or a folder of other playlists, a link + * to its parent folder, and its sort order. + */ + + class playlist_tree_row_t : public kaitai::kstruct { + + public: + + playlist_tree_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~playlist_tree_row_t(); + + private: + bool f_is_folder; + bool m_is_folder; + + public: + bool is_folder(); + + private: + uint32_t m_parent_id; + std::string m__unnamed1; + uint32_t m_sort_order; + uint32_t m_id; + uint32_t m_raw_is_folder; + device_sql_string_t* m_name; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * The ID of the `playlist_tree_row` in which this one can be + * found, or `0` if this playlist exists at the root level. + */ + uint32_t parent_id() const { return m_parent_id; } + std::string _unnamed1() const { return m__unnamed1; } + + /** + * The order in which the entries of this playlist are sorted. + */ + uint32_t sort_order() const { return m_sort_order; } + + /** + * The unique identifier by which this playlist or folder can + * be requested and linked from other rows. + */ + uint32_t id() const { return m_id; } + + /** + * Has a non-zero value if this is actually a folder rather + * than a playlist. + */ + uint32_t raw_is_folder() const { return m_raw_is_folder; } + + /** + * The variable-length string naming the playlist. + */ + device_sql_string_t* name() const { return m_name; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * A row that holds a color name and the associated ID. + */ + + class color_row_t : public kaitai::kstruct { + + public: + + color_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~color_row_t(); + + private: + std::string m__unnamed0; + uint16_t m_id; + uint8_t m__unnamed2; + device_sql_string_t* m_name; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + std::string _unnamed0() const { return m__unnamed0; } + + /** + * The unique identifier by which this color can be requested + * and linked from other rows (such as tracks). + */ + uint16_t id() const { return m_id; } + uint8_t _unnamed2() const { return m__unnamed2; } + + /** + * The variable-length string naming the color. + */ + device_sql_string_t* name() const { return m_name; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * An ASCII-encoded string up to 127 bytes long. + */ + + class device_sql_short_ascii_t : public kaitai::kstruct { + + public: + + device_sql_short_ascii_t(uint8_t p_mangled_length, kaitai::kstream* p__io, rekordbox_pdb_t::device_sql_string_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~device_sql_short_ascii_t(); + + private: + bool f_length; + int32_t m_length; + + public: + + /** + * The un-mangled length of the string, in bytes. + */ + int32_t length(); + + private: + std::string m_text; + bool n_text; + + public: + bool _is_null_text() { text(); return n_text; }; + + private: + uint8_t m_mangled_length; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::device_sql_string_t* m__parent; + + public: + + /** + * The content of the string. + */ + std::string text() const { return m_text; } + + /** + * Contains the actual length, incremented, doubled, and + * incremented again. Go figure. + */ + uint8_t mangled_length() const { return m_mangled_length; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::device_sql_string_t* _parent() const { return m__parent; } + }; + + /** + * A row that holds an album name and ID. + */ + + class album_row_t : public kaitai::kstruct { + + public: + + album_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~album_row_t(); + + private: + bool f_name; + device_sql_string_t* m_name; + + public: + + /** + * The name of this album. + */ + device_sql_string_t* name(); + + private: + uint16_t m__unnamed0; + uint16_t m_index_shift; + uint32_t m__unnamed2; + uint32_t m_artist_id; + uint32_t m_id; + uint32_t m__unnamed5; + uint8_t m__unnamed6; + uint8_t m_ofs_name; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * Some kind of magic word? Usually 0x80, 0x00. + */ + uint16_t _unnamed0() const { return m__unnamed0; } + + /** + * TODO name from @flesniak, but what does it mean? + */ + uint16_t index_shift() const { return m_index_shift; } + uint32_t _unnamed2() const { return m__unnamed2; } + + /** + * Identifies the artist associated with the album. + */ + uint32_t artist_id() const { return m_artist_id; } + + /** + * The unique identifier by which this album can be requested + * and linked from other rows (such as tracks). + */ + uint32_t id() const { return m_id; } + uint32_t _unnamed5() const { return m__unnamed5; } + + /** + * @flesniak says: "alwayx 0x03, maybe an unindexed empty string" + */ + uint8_t _unnamed6() const { return m__unnamed6; } + + /** + * The location of the variable-length name string, relative to + * the start of this row. + */ + uint8_t ofs_name() const { return m_ofs_name; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * A table page, consisting of a short header describing the + * content of the page and linking to the next page, followed by a + * heap in which row data is found. At the end of the page there is + * an index which locates all rows present in the heap via their + * offsets past the end of the page header. + */ + + class page_t : public kaitai::kstruct { + + public: + + page_t(kaitai::kstream* p__io, rekordbox_pdb_t::page_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~page_t(); + + private: + bool f_num_rows; + uint16_t m_num_rows; + + public: + + /** + * The number of rows on this page (controls the number of row + * index entries there are, but some of those may not be marked + * as present in the table due to deletion). + */ + uint16_t num_rows(); + + private: + bool f_num_groups; + int32_t m_num_groups; + + public: + + /** + * The number of row groups that are present in the index. Each + * group can hold up to sixteen rows. All but the final one + * will hold sixteen rows. + */ + int32_t num_groups(); + + private: + bool f_row_groups; + std::vector* m_row_groups; + bool n_row_groups; + + public: + bool _is_null_row_groups() { row_groups(); return n_row_groups; }; + + private: + + public: + + /** + * The actual row groups making up the row index. Each group + * can hold up to sixteen rows. Non-data pages do not have + * actual rows, and attempting to parse them can crash. + */ + std::vector* row_groups(); + + private: + bool f_heap_pos; + int32_t m_heap_pos; + + public: + int32_t heap_pos(); + + private: + bool f_is_data_page; + bool m_is_data_page; + + public: + bool is_data_page(); + + private: + std::string m__unnamed0; + uint32_t m_page_index; + page_type_t m_type; + page_ref_t* m_next_page; + uint32_t m__unnamed4; + std::string m__unnamed5; + uint8_t m_num_rows_small; + uint8_t m__unnamed7; + uint8_t m__unnamed8; + uint8_t m_page_flags; + uint16_t m_free_size; + uint16_t m_used_size; + uint16_t m__unnamed12; + uint16_t m_num_rows_large; + uint16_t m__unnamed14; + uint16_t m__unnamed15; + std::string m_heap; + bool n_heap; + + public: + bool _is_null_heap() { heap(); return n_heap; }; + + private: + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::page_ref_t* m__parent; + + public: + std::string _unnamed0() const { return m__unnamed0; } + + /** + * Matches the index we used to look up the page, sanity check? + */ + uint32_t page_index() const { return m_page_index; } + + /** + * Identifies the type of information stored in the rows of this page. + */ + page_type_t type() const { return m_type; } + + /** + * Index of the next page containing this type of rows. Points past + * the end of the file if there are no more. + */ + page_ref_t* next_page() const { return m_next_page; } + + /** + * @flesniak said: "sequence number (0->1: 8->13, 1->2: 22, 2->3: 27)" + */ + uint32_t _unnamed4() const { return m__unnamed4; } + std::string _unnamed5() const { return m__unnamed5; } + + /** + * Holds the value used for `num_rows` (see below) unless + * `num_rows_large` is larger (but not equal to `0x1fff`). This + * seems like some strange mechanism to deal with the fact that + * lots of tiny entries, such as are found in the + * `playlist_entries` table, are too big to count with a single + * byte. But why not just always use `num_rows_large`, then? + */ + uint8_t num_rows_small() const { return m_num_rows_small; } + + /** + * @flesniak said: "a bitmask (1st track: 32)" + */ + uint8_t _unnamed7() const { return m__unnamed7; } + + /** + * @flesniak said: "often 0, sometimes larger, esp. for pages + * with high real_entry_count (e.g. 12 for 101 entries)" + */ + uint8_t _unnamed8() const { return m__unnamed8; } + + /** + * @flesniak said: "strange pages: 0x44, 0x64; otherwise seen: 0x24, 0x34" + */ + uint8_t page_flags() const { return m_page_flags; } + + /** + * Unused space (in bytes) in the page heap, excluding the row + * index at end of page. + */ + uint16_t free_size() const { return m_free_size; } + + /** + * The number of bytes that are in use in the page heap. + */ + uint16_t used_size() const { return m_used_size; } + + /** + * @flesniak said: "(0->1: 2)" + */ + uint16_t _unnamed12() const { return m__unnamed12; } + + /** + * Holds the value used for `num_rows` (as described above) + * when that is too large to fit into `num_rows_small`, and + * that situation seems to be indicated when this value is + * larger than `num_rows_small`, but not equal to `0x1fff`. + * This seems like some strange mechanism to deal with the fact + * that lots of tiny entries, such as are found in the + * `playlist_entries` table, are too big to count with a single + * byte. But why not just always use this value, then? + */ + uint16_t num_rows_large() const { return m_num_rows_large; } + + /** + * @flesniak said: "1004 for strange blocks, 0 otherwise" + */ + uint16_t _unnamed14() const { return m__unnamed14; } + + /** + * @flesniak said: "always 0 except 1 for history pages, num + * entries for strange pages?" + */ + uint16_t _unnamed15() const { return m__unnamed15; } + std::string heap() const { return m_heap; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::page_ref_t* _parent() const { return m__parent; } + }; + + /** + * A group of row indices, which are built backwards from the end + * of the page. Holds up to sixteen row offsets, along with a bit + * mask that indicates whether each row is actually present in the + * table. + */ + + class row_group_t : public kaitai::kstruct { + + public: + + row_group_t(uint16_t p_group_index, kaitai::kstream* p__io, rekordbox_pdb_t::page_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~row_group_t(); + + private: + bool f_base; + int32_t m_base; + + public: + + /** + * The starting point of this group of row indices. + */ + int32_t base(); + + private: + bool f_row_present_flags; + uint16_t m_row_present_flags; + + public: + + /** + * Each bit specifies whether a particular row is present. The + * low order bit corresponds to the first row in this index, + * whose offset immediately precedes these flag bits. The + * second bit corresponds to the row whose offset precedes + * that, and so on. + */ + uint16_t row_present_flags(); + + private: + bool f_rows; + std::vector* m_rows; + + public: + + /** + * The row offsets in this group. + */ + std::vector* rows(); + + private: + uint16_t m_group_index; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::page_t* m__parent; + + public: + + /** + * Identifies which group is being generated. They build backwards + * from the end of the page. + */ + uint16_t group_index() const { return m_group_index; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::page_t* _parent() const { return m__parent; } + }; + + /** + * A row that holds a genre name and the associated ID. + */ + + class genre_row_t : public kaitai::kstruct { + + public: + + genre_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~genre_row_t(); + + private: + uint32_t m_id; + device_sql_string_t* m_name; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * The unique identifier by which this genre can be requested + * and linked from other rows (such as tracks). + */ + uint32_t id() const { return m_id; } + + /** + * The variable-length string naming the genre. + */ + device_sql_string_t* name() const { return m_name; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * A row that holds the path to an album art image file and the + * associated artwork ID. + */ + + class artwork_row_t : public kaitai::kstruct { + + public: + + artwork_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~artwork_row_t(); + + private: + uint32_t m_id; + device_sql_string_t* m_path; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * The unique identifier by which this art can be requested + * and linked from other rows (such as tracks). + */ + uint32_t id() const { return m_id; } + + /** + * The variable-length file path string at which the art file + * can be found. + */ + device_sql_string_t* path() const { return m_path; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * An ASCII-encoded string preceded by a two-byte length field. + */ + + class device_sql_long_ascii_t : public kaitai::kstruct { + + public: + + device_sql_long_ascii_t(kaitai::kstream* p__io, rekordbox_pdb_t::device_sql_string_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~device_sql_long_ascii_t(); + + private: + uint16_t m_length; + std::string m_text; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::device_sql_string_t* m__parent; + + public: + + /** + * Contains the length of the string in bytes. + */ + uint16_t length() const { return m_length; } + + /** + * The content of the string. + */ + std::string text() const { return m_text; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::device_sql_string_t* _parent() const { return m__parent; } + }; + + /** + * A row that holds an artist name and ID. + */ + + class artist_row_t : public kaitai::kstruct { + + public: + + artist_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~artist_row_t(); + + private: + bool f_ofs_name_far; + uint16_t m_ofs_name_far; + bool n_ofs_name_far; + + public: + bool _is_null_ofs_name_far() { ofs_name_far(); return n_ofs_name_far; }; + + private: + + public: + + /** + * For names that might be further than 0xff bytes from the + * start of this row, this holds a two-byte offset, and is + * signalled by the subtype value. + */ + uint16_t ofs_name_far(); + + private: + bool f_name; + device_sql_string_t* m_name; + + public: + + /** + * The name of this artist. + */ + device_sql_string_t* name(); + + private: + uint16_t m_subtype; + uint16_t m_index_shift; + uint32_t m_id; + uint8_t m__unnamed3; + uint8_t m_ofs_name_near; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * Usually 0x60, but 0x64 means we have a long name offset + * embedded in the row. + */ + uint16_t subtype() const { return m_subtype; } + + /** + * TODO name from @flesniak, but what does it mean? + */ + uint16_t index_shift() const { return m_index_shift; } + + /** + * The unique identifier by which this artist can be requested + * and linked from other rows (such as tracks). + */ + uint32_t id() const { return m_id; } + + /** + * @flesniak says: "always 0x03, maybe an unindexed empty string" + */ + uint8_t _unnamed3() const { return m__unnamed3; } + + /** + * The location of the variable-length name string, relative to + * the start of this row, unless subtype is 0x64. + */ + uint8_t ofs_name_near() const { return m_ofs_name_near; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * An index which points to a table page (its offset can be found + * by multiplying the index by the `page_len` value in the file + * header). This type allows the linked page to be lazy loaded. + */ + + class page_ref_t : public kaitai::kstruct { + + public: + + page_ref_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~page_ref_t(); + + private: + bool f_body; + page_t* m_body; + + public: + + /** + * When referenced, loads the specified page and parses its + * contents appropriately for the type of data it contains. + */ + page_t* body(); + + private: + uint32_t m_index; + rekordbox_pdb_t* m__root; + kaitai::kstruct* m__parent; + std::string m__raw_body; + kaitai::kstream* m__io__raw_body; + + public: + + /** + * Identifies the desired page number. + */ + uint32_t index() const { return m_index; } + rekordbox_pdb_t* _root() const { return m__root; } + kaitai::kstruct* _parent() const { return m__parent; } + std::string _raw_body() const { return m__raw_body; } + kaitai::kstream* _io__raw_body() const { return m__io__raw_body; } + }; + + /** + * A UTF-16BE-encoded string preceded by a two-byte length field. + */ + + class device_sql_long_utf16be_t : public kaitai::kstruct { + + public: + + device_sql_long_utf16be_t(kaitai::kstream* p__io, rekordbox_pdb_t::device_sql_string_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~device_sql_long_utf16be_t(); + + private: + uint16_t m_length; + std::string m_text; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::device_sql_string_t* m__parent; + + public: + + /** + * Contains the length of the string in bytes, including two trailing nulls. + */ + uint16_t length() const { return m_length; } + + /** + * The content of the string. + */ + std::string text() const { return m_text; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::device_sql_string_t* _parent() const { return m__parent; } + }; + + /** + * A row that describes a track that can be played, with many + * details about the music, and links to other tables like artists, + * albums, keys, etc. + */ + + class track_row_t : public kaitai::kstruct { + + public: + + track_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~track_row_t(); + + private: + bool f_unknown_string_8; + device_sql_string_t* m_unknown_string_8; + + public: + + /** + * A string of unknown purpose, usually empty. + */ + device_sql_string_t* unknown_string_8(); + + private: + bool f_unknown_string_6; + device_sql_string_t* m_unknown_string_6; + + public: + + /** + * A string of unknown purpose, usually empty. + */ + device_sql_string_t* unknown_string_6(); + + private: + bool f_analyze_date; + device_sql_string_t* m_analyze_date; + + public: + + /** + * A string containing the date this track was analyzed by rekordbox. + */ + device_sql_string_t* analyze_date(); + + private: + bool f_file_path; + device_sql_string_t* m_file_path; + + public: + + /** + * The file path of the track audio file. + */ + device_sql_string_t* file_path(); + + private: + bool f_autoload_hotcues; + device_sql_string_t* m_autoload_hotcues; + + public: + + /** + * A string whose value is always either empty or "ON", and + * which apparently for some insane reason is used, rather than + * a single bit somewhere, to control whether hot-cues are + * auto-loaded for the track. + */ + device_sql_string_t* autoload_hotcues(); + + private: + bool f_date_added; + device_sql_string_t* m_date_added; + + public: + + /** + * A string containing the date this track was added to the collection. + */ + device_sql_string_t* date_added(); + + private: + bool f_unknown_string_3; + device_sql_string_t* m_unknown_string_3; + + public: + + /** + * A string of unknown purpose; @flesniak said "strange + * strings, often zero length, sometimes low binary values + * 0x01/0x02 as content" + */ + device_sql_string_t* unknown_string_3(); + + private: + bool f_texter; + device_sql_string_t* m_texter; + + public: + + /** + * A string of unknown purpose, which @flesnik named. + */ + device_sql_string_t* texter(); + + private: + bool f_kuvo_public; + device_sql_string_t* m_kuvo_public; + + public: + + /** + * A string whose value is always either empty or "ON", and + * which apparently for some insane reason is used, rather than + * a single bit somewhere, to control whether the track + * information is visible on Kuvo. + */ + device_sql_string_t* kuvo_public(); + + private: + bool f_mix_name; + device_sql_string_t* m_mix_name; + + public: + + /** + * A string naming the remix of the track, if known. + */ + device_sql_string_t* mix_name(); + + private: + bool f_unknown_string_5; + device_sql_string_t* m_unknown_string_5; + + public: + + /** + * A string of unknown purpose. + */ + device_sql_string_t* unknown_string_5(); + + private: + bool f_unknown_string_4; + device_sql_string_t* m_unknown_string_4; + + public: + + /** + * A string of unknown purpose; @flesniak said "strange + * strings, often zero length, sometimes low binary values + * 0x01/0x02 as content" + */ + device_sql_string_t* unknown_string_4(); + + private: + bool f_message; + device_sql_string_t* m_message; + + public: + + /** + * A string of unknown purpose, which @flesnik named. + */ + device_sql_string_t* message(); + + private: + bool f_unknown_string_2; + device_sql_string_t* m_unknown_string_2; + + public: + + /** + * A string of unknown purpose; @flesniak said "thought + * tracknumber -> wrong!" + */ + device_sql_string_t* unknown_string_2(); + + private: + bool f_unknown_string_1; + device_sql_string_t* m_unknown_string_1; + + public: + + /** + * A string of unknown purpose, which has so far only been + * empty. + */ + device_sql_string_t* unknown_string_1(); + + private: + bool f_unknown_string_7; + device_sql_string_t* m_unknown_string_7; + + public: + + /** + * A string of unknown purpose, usually empty. + */ + device_sql_string_t* unknown_string_7(); + + private: + bool f_filename; + device_sql_string_t* m_filename; + + public: + + /** + * The file name of the track audio file. + */ + device_sql_string_t* filename(); + + private: + bool f_analyze_path; + device_sql_string_t* m_analyze_path; + + public: + + /** + * The file path of the track analysis, which allows rapid + * seeking to particular times in variable bit-rate files, + * jumping to particular beats, visual waveform previews, and + * stores cue points and loops. + */ + device_sql_string_t* analyze_path(); + + private: + bool f_comment; + device_sql_string_t* m_comment; + + public: + + /** + * The comment assigned to the track by the DJ, if any. + */ + device_sql_string_t* comment(); + + private: + bool f_release_date; + device_sql_string_t* m_release_date; + + public: + + /** + * A string containing the date this track was released, if known. + */ + device_sql_string_t* release_date(); + + private: + bool f_title; + device_sql_string_t* m_title; + + public: + + /** + * The title of the track. + */ + device_sql_string_t* title(); + + private: + uint16_t m__unnamed0; + uint16_t m_index_shift; + uint32_t m_bitmask; + uint32_t m_sample_rate; + uint32_t m_composer_id; + uint32_t m_file_size; + uint32_t m__unnamed6; + uint16_t m__unnamed7; + uint16_t m__unnamed8; + uint32_t m_artwork_id; + uint32_t m_key_id; + uint32_t m_original_artist_id; + uint32_t m_label_id; + uint32_t m_remixer_id; + uint32_t m_bitrate; + uint32_t m_track_number; + uint32_t m_tempo; + uint32_t m_genre_id; + uint32_t m_album_id; + uint32_t m_artist_id; + uint32_t m_id; + uint16_t m_disc_number; + uint16_t m_play_count; + uint16_t m_year; + uint16_t m_sample_depth; + uint16_t m_duration; + uint16_t m__unnamed26; + uint8_t m_color_id; + uint8_t m_rating; + uint16_t m__unnamed29; + uint16_t m__unnamed30; + std::vector* m_ofs_strings; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * Some kind of magic word? Usually 0x24, 0x00. + */ + uint16_t _unnamed0() const { return m__unnamed0; } + + /** + * TODO name from @flesniak, but what does it mean? + */ + uint16_t index_shift() const { return m_index_shift; } + + /** + * TODO what do the bits mean? + */ + uint32_t bitmask() const { return m_bitmask; } + + /** + * Playback sample rate of the audio file. + */ + uint32_t sample_rate() const { return m_sample_rate; } + + /** + * References a row in the artist table if the composer is + * known. + */ + uint32_t composer_id() const { return m_composer_id; } + + /** + * The length of the audio file, in bytes. + */ + uint32_t file_size() const { return m_file_size; } + + /** + * Some ID? Purpose as yet unknown. + */ + uint32_t _unnamed6() const { return m__unnamed6; } + + /** + * From @flesniak: "always 19048?" + */ + uint16_t _unnamed7() const { return m__unnamed7; } + + /** + * From @flesniak: "always 30967?" + */ + uint16_t _unnamed8() const { return m__unnamed8; } + + /** + * References a row in the artwork table if there is album art. + */ + uint32_t artwork_id() const { return m_artwork_id; } + + /** + * References a row in the keys table if the track has a known + * main musical key. + */ + uint32_t key_id() const { return m_key_id; } + + /** + * References a row in the artwork table if this is a cover + * performance and the original artist is known. + */ + uint32_t original_artist_id() const { return m_original_artist_id; } + + /** + * References a row in the labels table if the track has a + * known record label. + */ + uint32_t label_id() const { return m_label_id; } + + /** + * References a row in the artists table if the track has a + * known remixer. + */ + uint32_t remixer_id() const { return m_remixer_id; } + + /** + * Playback bit rate of the audio file. + */ + uint32_t bitrate() const { return m_bitrate; } + + /** + * The position of the track within an album. + */ + uint32_t track_number() const { return m_track_number; } + + /** + * The tempo at the start of the track in beats per minute, + * multiplied by 100. + */ + uint32_t tempo() const { return m_tempo; } + + /** + * References a row in the genres table if the track has a + * known musical genre. + */ + uint32_t genre_id() const { return m_genre_id; } + + /** + * References a row in the albums table if the track has a + * known album. + */ + uint32_t album_id() const { return m_album_id; } + + /** + * References a row in the artists table if the track has a + * known performer. + */ + uint32_t artist_id() const { return m_artist_id; } + + /** + * The id by which this track can be looked up; players will + * report this value in their status packets when they are + * playing the track. + */ + uint32_t id() const { return m_id; } + + /** + * The number of the disc on which this track is found, if it + * is known to be part of a multi-disc album. + */ + uint16_t disc_number() const { return m_disc_number; } + + /** + * The number of times this track has been played. + */ + uint16_t play_count() const { return m_play_count; } + + /** + * The year in which this track was released. + */ + uint16_t year() const { return m_year; } + + /** + * The number of bits per sample of the audio file. + */ + uint16_t sample_depth() const { return m_sample_depth; } + + /** + * The length, in seconds, of the track when played at normal + * speed. + */ + uint16_t duration() const { return m_duration; } + + /** + * From @flesniak: "always 41?" + */ + uint16_t _unnamed26() const { return m__unnamed26; } + + /** + * References a row in the colors table if the track has been + * assigned a color. + */ + uint8_t color_id() const { return m_color_id; } + + /** + * The number of stars to display for the track, 0 to 5. + */ + uint8_t rating() const { return m_rating; } + + /** + * From @flesniak: "always 1?" + */ + uint16_t _unnamed29() const { return m__unnamed29; } + + /** + * From @flesniak: "alternating 2 or 3" + */ + uint16_t _unnamed30() const { return m__unnamed30; } + + /** + * The location, relative to the start of this row, of a + * variety of variable-length strings. + */ + std::vector* ofs_strings() const { return m_ofs_strings; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * A row that holds a musical key and the associated ID. + */ + + class key_row_t : public kaitai::kstruct { + + public: + + key_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~key_row_t(); + + private: + uint32_t m_id; + uint32_t m_id2; + device_sql_string_t* m_name; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * The unique identifier by which this key can be requested + * and linked from other rows (such as tracks). + */ + uint32_t id() const { return m_id; } + + /** + * Seems to be a second copy of the ID? + */ + uint32_t id2() const { return m_id2; } + + /** + * The variable-length string naming the key. + */ + device_sql_string_t* name() const { return m_name; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * A row that associates a track with a position in a playlist. + */ + + class playlist_entry_row_t : public kaitai::kstruct { + + public: + + playlist_entry_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~playlist_entry_row_t(); + + private: + uint32_t m_entry_index; + uint32_t m_track_id; + uint32_t m_playlist_id; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * The position within the playlist represented by this entry. + */ + uint32_t entry_index() const { return m_entry_index; } + + /** + * The track found at this position in the playlist. + */ + uint32_t track_id() const { return m_track_id; } + + /** + * The playlist to which this entry belongs. + */ + uint32_t playlist_id() const { return m_playlist_id; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * A row that holds a label name and the associated ID. + */ + + class label_row_t : public kaitai::kstruct { + + public: + + label_row_t(kaitai::kstream* p__io, rekordbox_pdb_t::row_ref_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~label_row_t(); + + private: + uint32_t m_id; + device_sql_string_t* m_name; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_ref_t* m__parent; + + public: + + /** + * The unique identifier by which this label can be requested + * and linked from other rows (such as tracks). + */ + uint32_t id() const { return m_id; } + + /** + * The variable-length string naming the label. + */ + device_sql_string_t* name() const { return m_name; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_ref_t* _parent() const { return m__parent; } + }; + + /** + * Each table is a linked list of pages containing rows of a single + * type. This header describes the nature of the table and links to + * its pages by index. + */ + + class table_t : public kaitai::kstruct { + + public: + + table_t(kaitai::kstream* p__io, rekordbox_pdb_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~table_t(); + + private: + page_type_t m_type; + uint32_t m_empty_candidate; + page_ref_t* m_first_page; + page_ref_t* m_last_page; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t* m__parent; + + public: + + /** + * Identifies the kind of rows that are found in this table. + */ + page_type_t type() const { return m_type; } + uint32_t empty_candidate() const { return m_empty_candidate; } + + /** + * Links to the chain of pages making up that table. The first + * page seems to always contain similar garbage patterns and + * zero rows, but the next page it links to contains the start + * of the meaningful data rows. + */ + page_ref_t* first_page() const { return m_first_page; } + + /** + * Holds the index of the last page that makes up this table. + * When following the linked list of pages of the table, you + * either need to stop when you reach this page, or when you + * notice that the `next_page` link you followed took you to a + * page of a different `type`. + */ + page_ref_t* last_page() const { return m_last_page; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t* _parent() const { return m__parent; } + }; + + /** + * An offset which points to a row in the table, whose actual + * presence is controlled by one of the bits in + * `row_present_flags`. This instance allows the row itself to be + * lazily loaded, unless it is not present, in which case there is + * no content to be loaded. + */ + + class row_ref_t : public kaitai::kstruct { + + public: + + row_ref_t(uint16_t p_row_index, kaitai::kstream* p__io, rekordbox_pdb_t::row_group_t* p__parent = 0, rekordbox_pdb_t* p__root = 0); + + private: + void _read(); + + public: + ~row_ref_t(); + + private: + bool f_ofs_row; + uint16_t m_ofs_row; + + public: + + /** + * The offset of the start of the row (in bytes past the end of + * the page header). + */ + uint16_t ofs_row(); + + private: + bool f_row_base; + int32_t m_row_base; + + public: + + /** + * The location of this row relative to the start of the page. + * A variety of pointers (such as all device_sql_string values) + * are calculated with respect to this position. + */ + int32_t row_base(); + + private: + bool f_present; + bool m_present; + + public: + + /** + * Indicates whether the row index considers this row to be + * present in the table. Will be `false` if the row has been + * deleted. + */ + bool present(); + + private: + bool f_body; + kaitai::kstruct* m_body; + bool n_body; + + public: + bool _is_null_body() { body(); return n_body; }; + + private: + + public: + + /** + * The actual content of the row, as long as it is present. + */ + kaitai::kstruct* body(); + + private: + uint16_t m_row_index; + rekordbox_pdb_t* m__root; + rekordbox_pdb_t::row_group_t* m__parent; + + public: + + /** + * Identifies which row within the row index this reference + * came from, so the correct flag can be checked for the row + * presence and the correct row offset can be found. + */ + uint16_t row_index() const { return m_row_index; } + rekordbox_pdb_t* _root() const { return m__root; } + rekordbox_pdb_t::row_group_t* _parent() const { return m__parent; } + }; + +private: + uint32_t m__unnamed0; + uint32_t m_len_page; + uint32_t m_num_tables; + uint32_t m_next_unused_page; + uint32_t m__unnamed4; + uint32_t m_sequence; + std::string m__unnamed6; + std::vector* m_tables; + rekordbox_pdb_t* m__root; + kaitai::kstruct* m__parent; + +public: + + /** + * Unknown purpose, perhaps an unoriginal signature, seems to + * always have the value 0. + */ + uint32_t _unnamed0() const { return m__unnamed0; } + + /** + * The database page size, in bytes. Pages are referred to by + * index, so this size is needed to calculate their offset, and + * table pages have a row index structure which is built from the + * end of the page backwards, so finding that also requires this + * value. + */ + uint32_t len_page() const { return m_len_page; } + + /** + * Determines the number of table entries that are present. Each + * table is a linked list of pages containing rows of a particular + * type. + */ + uint32_t num_tables() const { return m_num_tables; } + + /** + * @flesinak said: "Not used as any `empty_candidate`, points + * past the end of the file." + */ + uint32_t next_unused_page() const { return m_next_unused_page; } + uint32_t _unnamed4() const { return m__unnamed4; } + + /** + * @flesniak said: "Always incremented by at least one, + * sometimes by two or three." + */ + uint32_t sequence() const { return m_sequence; } + std::string _unnamed6() const { return m__unnamed6; } + + /** + * Describes and links to the tables present in the database. + */ + std::vector* tables() const { return m_tables; } + rekordbox_pdb_t* _root() const { return m__root; } + kaitai::kstruct* _parent() const { return m__parent; } +}; + +#endif // REKORDBOX_PDB_H_ diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp new file mode 100755 index 00000000000..9e91c5c29c6 --- /dev/null +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -0,0 +1,932 @@ +// rekordboxfeature.cpp +// Created 05/24/2019 by Evan Dekker + +#include +#include +#include +#include +#include +#include + +#include "library/rekordbox/rekordboxfeature.h" + +#include "library/librarytablemodel.h" +#include "library/missingtablemodel.h" +#include "library/queryutil.h" +#include "library/trackcollection.h" +#include "library/treeitem.h" +#include "track/keyfactory.h" +#include "waveform/waveform.h" +#include "util/sandbox.h" +#include "util/file.h" + +#include "widget/wlibrary.h" +#include "widget/wlibrarytextbrowser.h" + +#define IS_RECORDBOX_DEVICE "::isRecordboxDevice::" +#define IS_NOT_RECORDBOX_DEVICE "::isNotRecordboxDevice::" + +RekordboxPlaylistModel::RekordboxPlaylistModel(QObject* parent, + TrackCollection* trackCollection, + QSharedPointer trackSource) + : BaseExternalPlaylistModel(parent, trackCollection, + "mixxx.db.model.rekordbox.playlistmodel", + "rekordbox_playlists", + "rekordbox_playlist_tracks", + trackSource) { +} + +TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { + qDebug() << "RekordboxTrackModel::getTrack"; + + TrackPointer track = BaseExternalPlaylistModel::getTrack(index); + + // Assume that the key of the file the has been analyzed in Recordbox is correct + // and prevent the AnalyzerKey from re-analyzing. + track->setKeys(KeyFactory::makeBasicKeysFromText(index.sibling( + index.row(), fieldIndex("key")).data().toString(), mixxx::track::io::key::USER)); + + return track; +} + +bool RekordboxPlaylistModel::isColumnHiddenByDefault(int column) { + if ( + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ID) + ) { + return true; + } + return BaseSqlTableModel::isColumnHiddenByDefault(column); +} + +RekordboxFeature::RekordboxFeature(QObject* parent, TrackCollection* trackCollection) + : BaseExternalLibraryFeature(parent, trackCollection), + m_pTrackCollection(trackCollection), + m_icon(":/images/library/ic_library_rekordbox.svg") { + QString tableName = "rekordbox_library"; + QString idColumn = "id"; + QStringList columns; + columns << "id" + << "artist" + << "title" + << "album" + << "year" + << "genre" + << "tracknumber" + << "location" + << "comment" + << "rating" + << "duration" + << "bitrate" + << "bpm" + << "key"; + m_trackSource = QSharedPointer( + new BaseTrackCache(m_pTrackCollection, tableName, idColumn, + columns, false)); + QStringList searchColumns; + searchColumns + << "artist" + << "title" + << "album" + << "year" + << "genre" + << "tracknumber" + << "location" + << "comment" + << "duration" + << "bitrate" + << "bpm" + << "key"; + m_trackSource->setSearchColumns(searchColumns); + + m_pRekordboxPlaylistModel = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); + + m_title = tr("Rekordbox"); + + m_database = QSqlDatabase::cloneDatabase(trackCollection->database(), + "REKORDBOX_SCANNER"); + + //Open the database connection in this thread. + if (!m_database.open()) { + qDebug() << "Failed to open database for Rekordbox scanner." + << m_database.lastError(); + } else { + //Clear any previous Rekordbox device entries if they exist + ScopedTransaction transaction(m_database); + clearTable("rekordbox_playlist_tracks"); + clearTable("rekordbox_library"); + clearTable("rekordbox_playlists"); + transaction.commit(); + } + + connect(&m_devicesFutureWatcher, SIGNAL(finished()), + this, SLOT(onRekordboxDevicesFound())); + connect(&m_tracksFutureWatcher, SIGNAL(finished()), + this, SLOT(onTracksFound())); + // initialize the model + m_childModel.setRootItem(std::make_unique(this)); + +} + +RekordboxFeature::~RekordboxFeature() { + m_database.close(); + m_devicesFuture.waitForFinished(); + m_tracksFuture.waitForFinished(); + delete m_pRekordboxPlaylistModel; +} + +void RekordboxFeature::bindWidget(WLibrary* libraryWidget, + KeyboardEventFilter* keyboard) { + Q_UNUSED(keyboard); + WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget); + edit->setHtml(formatRootViewHtml()); + edit->setOpenLinks(false); + connect(edit, SIGNAL(anchorClicked(const QUrl)), + this, SLOT(htmlLinkClicked(const QUrl))); + libraryWidget->registerView("REKORDBOXHOME", edit); +} + +void RekordboxFeature::htmlLinkClicked(const QUrl& link) { + if (QString(link.path()) == "refresh") { + activate(); + } else { + qDebug() << "Unknown link clicked" << link; + } +} + +BaseSqlTableModel* RekordboxFeature::getPlaylistModelForPlaylist(QString playlist) { + RekordboxPlaylistModel* model = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); + model->setPlaylist(playlist); + return model; +} + +QVariant RekordboxFeature::title() { + return m_title; +} + +QIcon RekordboxFeature::getIcon() { + return m_icon; +} + +bool RekordboxFeature::isSupported() { + return true; +} + +TreeItemModel* RekordboxFeature::getChildModel() { + return &m_childModel; +} + +QString RekordboxFeature::formatRootViewHtml() const { + QString title = tr("Rekordbox"); + QString summary = tr("Reads playlists and folders from Rekordbox prepared removable devices."); + + QString html; + QString refreshLink = tr("Check for attached Rekordbox devices (refresh)"); + html.append(QString("

%1

").arg(title)); + html.append(QString("

%1

").arg(summary)); + + //Colorize links in lighter blue, instead of QT default dark blue. + //Links are still different from regular text, but readable on dark/light backgrounds. + //https://bugs.launchpad.net/mixxx/+bug/1744816 + html.append(QString("%1") + .arg(refreshLink)); + return html; +} + +void RekordboxFeature::refreshLibraryModels() { +} + +void RekordboxFeature::activate() { + qDebug() << "RekordboxFeature::activate()"; + + // Usually the maximum number of threads + // is > 2 depending on the CPU cores + // Unfortunately, within VirtualBox + // the maximum number of allowed threads + // is 1 at all times We'll need to increase + // the number to > 1, otherwise importing the music collection + // takes place when the GUI threads terminates, i.e., on + // Mixxx shutdown. + QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 + // Let a worker thread do the XML parsing + m_devicesFuture = QtConcurrent::run(this, &RekordboxFeature::findRekordboxDevices); + m_devicesFutureWatcher.setFuture(m_devicesFuture); + m_title = tr("(loading) Rekordbox"); + //calls a slot in the sidebar model such that 'Rekordbox (isLoading)' is displayed. + emit(featureIsLoading(this, true)); + + emit(enableCoverArtDisplay(true)); + emit(switchToView("REKORDBOXHOME")); + +} + +void RekordboxFeature::activateChild(const QModelIndex& index) { + if (!index.isValid()) return; + + //access underlying TreeItem object + TreeItem *item = static_cast(index.internalPointer()); + if (!(item && item->getData().isValid())) { + return; + } + + // TreeItem list data holds 2 values in a QList and have different meanings. + // If the 2nd QList element IS_RECORDBOX_DEVICE, the 1st element is the + // filesystem device path, and the parseDeviceDB concurrent thread to parse + // the Rekcordbox database is initiated. If the 2nd element is + // IS_NOT_RECORDBOX_DEVICE, the 1st element is the playlist path and it is + // activated. + QList data = item->getData().toList(); + QString playlist = data[0].toString(); + bool doParseDeviceDB = data[1].toString() == IS_RECORDBOX_DEVICE; + + qDebug() << "RekordboxFeature::activateChild " << item->getLabel() + << " playlist: " << playlist << " doParseDeviceDB: " << doParseDeviceDB; + + if (doParseDeviceDB) { + qDebug() << "Parse Rekordbox Device DB: " << playlist; + + // Usually the maximum number of threads + // is > 2 depending on the CPU cores + // Unfortunately, within VirtualBox + // the maximum number of allowed threads + // is 1 at all times We'll need to increase + // the number to > 1, otherwise importing the music collection + // takes place when the GUI threads terminates, i.e., on + // Mixxx shutdown. + QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 + // Let a worker thread do the XML parsing + m_tracksFuture = QtConcurrent::run(this, &RekordboxFeature::parseDeviceDB, item); + m_tracksFutureWatcher.setFuture(m_tracksFuture); + + // This device is now a playlist element, future activations should treat is + // as such + data[1] = QVariant(IS_NOT_RECORDBOX_DEVICE); + item->setData(QVariant(data)); + } else { + qDebug() << "Activate Rekordbox Playlist: " << playlist; + m_pRekordboxPlaylistModel->setPlaylist(playlist); + emit(showTrackModel(m_pRekordboxPlaylistModel)); + } +} + +QList RekordboxFeature::findRekordboxDevices() { + QThread* thisThread = QThread::currentThread(); + thisThread->setPriority(QThread::LowPriority); + + QList foundDevices; + +#if defined(__WINDOWS__) + // Repopulate drive list + QFileInfoList drives = QDir::drives(); + // show drive letters + foreach (QFileInfo drive, drives) { + // Using drive.filePath() instead of drive.canonicalPath() as it + // freezes interface too much if there is a network share mounted + // (drive letter assigned) but unavailable + // + // drive.canonicalPath() make a system call to the underlying filesystem + // introducing delay if it is unreadable. + // drive.filePath() doesn't make any access to the filesystem and consequently + // shorten the delay + + QFileInfo rbDBFileInfo(drive.filePath() + PDB_PATH); + + if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { + TreeItem *foundDevice = new TreeItem(this); + QList data; + + QString displayPath = drive.filePath(); + if (displayPath.endsWith("/")) { + displayPath.chop(1); + } + + data << drive.filePath(); + data << IS_RECORDBOX_DEVICE; + + foundDevice->setLabel(displayPath); + foundDevice->setData(QVariant(data)); + + foundDevices << foundDevice; + } + } +#elif defined(__LINUX__) + // To get devices on Linux, we look for directories under /media and + // /run/media/$USER. + QFileInfoList devices; + + // Add folders under /media to devices. + devices += QDir("/media").entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); + + // Add folders under /run/media/$USER to devices. + QDir run_media_user_dir("/run/media/" + qgetenv("USER")); + devices += run_media_user_dir.entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); + + // Add folders under /run/media/$USER to devices. + QDir run_media_user_dir("/run/media/" + qgetenv("USER")); + devices += run_media_user_dir.entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); + + foreach(QFileInfo device, devices) { + QFileInfo rbDBFileInfo(device.filePath() + "/" + PDB_PATH); + + if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { + TreeItem *foundDevice = new TreeItem(this); + QList data; + + data << device.filePath(); + data << IS_RECORDBOX_DEVICE; + + foundDevice->setLabel(device.fileName()); + foundDevice->setData(QVariant(data)); + + foundDevices << foundDevice; + } + } +#else // __APPLE__ + QFileInfoList devices = QDir("/Volumes").entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); + + foreach(QFileInfo device, devices) { + QFileInfo rbDBFileInfo(device.filePath() + "/" + PDB_PATH); + + if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { + TreeItem *foundDevice = new TreeItem(this); + QList data; + + data << device.filePath(); + data << IS_RECORDBOX_DEVICE; + + foundDevice->setLabel(device.fileName()); + foundDevice->setData(QVariant(data)); + + foundDevices << foundDevice; + } + } +#endif + + return foundDevices; +} + +// Functions getText and parseDeviceDB are roughly based on the following Java file: +// https://github.com/Deep-Symmetry/crate-digger/blob/master/src/main/java/org/deepsymmetry/cratedigger/Database.java +// getText is needed because the strings in the PDB file "have a variety of obscure representations". + +std::string RekordboxFeature::getText(rekordbox_pdb_t::device_sql_string_t *deviceString) { + if (instanceof(deviceString->body())) { + rekordbox_pdb_t::device_sql_short_ascii_t *shortAsciiString = + static_cast(deviceString->body()); + return shortAsciiString->text(); + } else if (instanceof(deviceString->body())) { + rekordbox_pdb_t::device_sql_long_ascii_t *longAsciiString = + static_cast(deviceString->body()); + return longAsciiString->text(); + } else if (instanceof(deviceString->body())) { + rekordbox_pdb_t::device_sql_long_utf16be_t *longUtf16beString = + static_cast(deviceString->body()); + return longUtf16beString->text(); + } + + return std::string(""); +} + +QString RekordboxFeature::parseDeviceDB(TreeItem *deviceItem) { + QString device = deviceItem->getLabel(); + QString devicePath = deviceItem->getData().toList()[0].toString(); + + qDebug() << "parseDeviceDB device: " << device << " devicePath: " << devicePath; + + std::string pathBase = devicePath.toStdString() + "/"; + std::string dbPath = pathBase + std::string(PDB_PATH); + + if (!QFile(QString::fromStdString(dbPath)).exists()) { + return devicePath; + } + + //Give thread a low priority + QThread* thisThread = QThread::currentThread(); + thisThread->setPriority(QThread::LowPriority); + + ScopedTransaction transaction(m_database); + + QSqlQuery query(m_database); + query.prepare("INSERT INTO rekordbox_library (rb_id, artist, title, album, year," + "genre,comment,tracknumber,bpm, bitrate,duration, location," + "rating,key,analyze_path,device) VALUES (:rb_id, :artist, :title, :album, :year,:genre," + ":comment, :tracknumber,:bpm, :bitrate,:duration, :location," + ":rating,:key,:analyze_path,:device)"); + + int audioFilesCount = 0; + + // Create a playlist for all the tracks on a device + QSqlQuery queryInsertIntoDevicePlaylist(m_database); + queryInsertIntoDevicePlaylist.prepare("INSERT INTO rekordbox_playlists (name) " + "VALUES (:name)"); + + queryInsertIntoDevicePlaylist.bindValue(":name", devicePath); + + if (!queryInsertIntoDevicePlaylist.exec()) { + LOG_FAILED_QUERY(queryInsertIntoDevicePlaylist) + << "devicePath: " << devicePath; + return devicePath; + } + + QSqlQuery idQuery(m_database); + idQuery.prepare("select id from rekordbox_playlists where name=:path"); + idQuery.bindValue(":path", devicePath); + + if (!idQuery.exec()) { + LOG_FAILED_QUERY(idQuery) + << "devicePath: " << devicePath; + return devicePath; + } + + int playlistID = -1; + while (idQuery.next()) { + playlistID = idQuery.value(idQuery.record().indexOf("id")).toInt(); + } + + QSqlQuery queryInsertIntoDevicePlaylistTracks(m_database); + queryInsertIntoDevicePlaylistTracks.prepare( + "INSERT INTO rekordbox_playlist_tracks (playlist_id, track_id, position) " + "VALUES (:playlist_id, :track_id, :position)"); + + queryInsertIntoDevicePlaylistTracks.bindValue(":playlist_id", playlistID); + + std::ifstream ifs(dbPath, std::ifstream::binary); + kaitai::kstream ks(&ifs); + + rekordbox_pdb_t reckordboxDB = rekordbox_pdb_t(&ks); + + // There are other types of tables (eg. COLOR), these are the only ones we are + // interested at the moment. Perhaps when/if + // https://bugs.launchpad.net/mixxx/+bug/1100882 + // is completed, this can be revisted. + // Attempt was made to also recover HISTORY + // playlists (which are found on removable Rekordbox devices), however + // they didn't appear to contain valid row_ref_t structures. + const int totalTables = 8; + + rekordbox_pdb_t::page_type_t tableOrder[totalTables] = { + rekordbox_pdb_t::PAGE_TYPE_KEYS, + rekordbox_pdb_t::PAGE_TYPE_GENRES, + rekordbox_pdb_t::PAGE_TYPE_ARTISTS, + rekordbox_pdb_t::PAGE_TYPE_ALBUMS, + rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES, + rekordbox_pdb_t::PAGE_TYPE_TRACKS, + rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE, + rekordbox_pdb_t::PAGE_TYPE_HISTORY + }; + + std::map keysMap; + std::map genresMap; + std::map artistsMap; + std::map albumsMap; + std::map playlistNameMap; + std::map playlistIsFolderMap; + std::map> playlistTreeMap; + std::map> playlistTrackMap; + + bool folderOrPlaylistFound = false; + + for (int tableOrderIndex = 0; tableOrderIndex < totalTables; tableOrderIndex++) { + bool done = false; + + for ( + std::vector::iterator table = reckordboxDB.tables()->begin(); + table != reckordboxDB.tables()->end(); ++table + ) { + if ((*table)->type() == tableOrder[tableOrderIndex]) { + if (done) break; + + uint16_t lastIndex = (*table)->last_page()->index(); + rekordbox_pdb_t::page_ref_t *currentRef = (*table)->first_page(); + bool moreLeft = true; + + do { + rekordbox_pdb_t::page_t *page = currentRef->body(); + + if (page->is_data_page()) { + for ( + std::vector::iterator rowGroup = page->row_groups()->begin(); + rowGroup != page->row_groups()->end(); ++rowGroup + ) { + for ( + std::vector::iterator rowRef = (*rowGroup)->rows()->begin(); + rowRef != (*rowGroup)->rows()->end(); ++rowRef + ) { + if ((*rowRef)->present()) { + switch (tableOrder[tableOrderIndex]) { + case rekordbox_pdb_t::PAGE_TYPE_KEYS: { + // Key found, update map + rekordbox_pdb_t::key_row_t *key = + (rekordbox_pdb_t::key_row_t *)(*rowRef)->body(); + keysMap[key->id()] = getText(key->name()); + } + break; + case rekordbox_pdb_t::PAGE_TYPE_GENRES: { + // Genre found, update map + rekordbox_pdb_t::genre_row_t *genre = + (rekordbox_pdb_t::genre_row_t *)(*rowRef)->body(); + genresMap[genre->id()] = getText(genre->name()); + } + break; + case rekordbox_pdb_t::PAGE_TYPE_ARTISTS: { + // Artist found, update map + rekordbox_pdb_t::artist_row_t *artist = + (rekordbox_pdb_t::artist_row_t *)(*rowRef)->body(); + artistsMap[artist->id()] = getText(artist->name()); + } + break; + case rekordbox_pdb_t::PAGE_TYPE_ALBUMS: { + // Album found, update map + rekordbox_pdb_t::album_row_t *album = + (rekordbox_pdb_t::album_row_t *)(*rowRef)->body(); + albumsMap[album->id()] = getText(album->name()); + } + break; + case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES: { + // Playlist to track mapping found, update map + rekordbox_pdb_t::playlist_entry_row_t *playlistEntry = + (rekordbox_pdb_t::playlist_entry_row_t *)(*rowRef)->body(); + playlistTrackMap[playlistEntry->playlist_id()][playlistEntry->entry_index()] = + playlistEntry->track_id(); + } + break; + case rekordbox_pdb_t::PAGE_TYPE_TRACKS: { + // Track found, insert into database + rekordbox_pdb_t::track_row_t *track = (rekordbox_pdb_t::track_row_t *)(*rowRef)->body(); + + int rbID = (int)track->id(); + QString title = QString::fromStdString(getText(track->title())); + QString artist = QString::fromStdString(artistsMap[track->artist_id()]); + QString album = QString::fromStdString(albumsMap[track->album_id()]); + QString year = QString::number(track->year()); + QString genre = QString::fromStdString(genresMap[track->genre_id()]); + QString location = QString::fromStdString(pathBase + getText(track->file_path())); + float bpm = (float)track->tempo() / 100.0; + int bitrate = (int)track->bitrate(); + QString key = QString::fromStdString(keysMap[track->key_id()]); + int playtime = (int)track->duration(); + int rating = (int)track->rating(); + QString comment = QString::fromStdString(getText(track->comment())); + QString tracknumber = QString::number(track->track_number()); + QString anlzPath = QString::fromStdString(pathBase + getText(track->analyze_path())); + + query.bindValue(":rb_id", rbID); + query.bindValue(":artist", artist); + query.bindValue(":title", title); + query.bindValue(":album", album); + query.bindValue(":genre", genre); + query.bindValue(":year", year); + query.bindValue(":duration", playtime); + query.bindValue(":location", location); + query.bindValue(":rating", rating); + query.bindValue(":comment", comment); + query.bindValue(":tracknumber", tracknumber); + query.bindValue(":key", key); + query.bindValue(":bpm", bpm); + query.bindValue(":bitrate", bitrate); + query.bindValue(":analyze_path", anlzPath); + query.bindValue(":device", device); + + if (!query.exec()) { + LOG_FAILED_QUERY(query); + } + + int trackID = -1; + QSqlQuery finderQuery(m_database); + finderQuery.prepare("select id from rekordbox_library where rb_id=:rb_id and device=:device"); + finderQuery.bindValue(":rb_id", rbID); + finderQuery.bindValue(":device", device); + + if (!finderQuery.exec()) { + LOG_FAILED_QUERY(finderQuery) + << "rbID:" << rbID; + } + + if (finderQuery.next()) { + trackID = finderQuery.value(finderQuery.record().indexOf("id")).toInt(); + } + + // Insert into device all tracks playlist + queryInsertIntoDevicePlaylistTracks.bindValue(":track_id", trackID); + queryInsertIntoDevicePlaylistTracks.bindValue(":position", audioFilesCount); + + if (!queryInsertIntoDevicePlaylistTracks.exec()) { + LOG_FAILED_QUERY(queryInsertIntoDevicePlaylistTracks) + << "device playlistID:" << playlistID + << "trackID:" << trackID + << "position:" << audioFilesCount; + } + + audioFilesCount++; + } + break; + case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE: { + // Playlist tree node found, update map + rekordbox_pdb_t::playlist_tree_row_t *playlistTree = + (rekordbox_pdb_t::playlist_tree_row_t *)(*rowRef)->body(); + + playlistNameMap[playlistTree->id()] = getText(playlistTree->name()); + playlistIsFolderMap[playlistTree->id()] = playlistTree->is_folder(); + playlistTreeMap[playlistTree->parent_id()][playlistTree->sort_order()] = playlistTree->id(); + + folderOrPlaylistFound = true; + } + break; + default: + break; + } + } + } + } + } + + if (currentRef->index() == lastIndex) { + moreLeft = false; + } else { + currentRef = page->next_page(); + } + } while (moreLeft); + done = true; + } + } + } + + if (audioFilesCount > 0 || folderOrPlaylistFound) { + // If we have found anything, recursively build playlist/folder TreeItem children + // for the original device TreeItem + buildPlaylistTree(deviceItem, 0, playlistNameMap, playlistIsFolderMap, + playlistTreeMap, playlistTrackMap, devicePath, device); + } + + qDebug() << "Found: " << audioFilesCount << " audio files in Rekordbox device " << device; + + transaction.commit(); + + return devicePath; +} + +void RekordboxFeature::buildPlaylistTree( + TreeItem *parent, + uint32_t parentID, + std::map &playlistNameMap, + std::map &playlistIsFolderMap, + std::map> &playlistTreeMap, + std::map> &playlistTrackMap, + QString playlistPath, + QString device) { + + for (uint32_t childIndex = 0; childIndex < playlistTreeMap[parentID].size(); childIndex++) { + uint32_t childID = playlistTreeMap[parentID][childIndex]; + QString playlistItemName = QString::fromStdString(playlistNameMap[childID]); + + QString delimiter = "-->"; + QString currentPath = playlistPath + delimiter + playlistItemName; + + QList data; + + data << currentPath; + data << IS_NOT_RECORDBOX_DEVICE; + + TreeItem *child = parent->appendChild(playlistItemName, QVariant(data)); + + // Create a playlist for this child + QSqlQuery queryInsertIntoPlaylist(m_database); + queryInsertIntoPlaylist.prepare("INSERT INTO rekordbox_playlists (name) " + "VALUES (:name)"); + + queryInsertIntoPlaylist.bindValue(":name", currentPath); + + if (!queryInsertIntoPlaylist.exec()) { + LOG_FAILED_QUERY(queryInsertIntoPlaylist) + << "currentPath" << currentPath; + return; + } + + QSqlQuery idQuery(m_database); + idQuery.prepare("select id from rekordbox_playlists where name=:path"); + idQuery.bindValue(":path", currentPath); + + if (!idQuery.exec()) { + LOG_FAILED_QUERY(idQuery) + << "currentPath" << currentPath; + return; + } + + int playlistID = -1; + while (idQuery.next()) { + playlistID = idQuery.value(idQuery.record().indexOf("id")).toInt(); + } + + QSqlQuery queryInsertIntoPlaylistTracks(m_database); + queryInsertIntoPlaylistTracks.prepare( + "INSERT INTO rekordbox_playlist_tracks (playlist_id, track_id, position) " + "VALUES (:playlist_id, :track_id, :position)"); + + if (playlistTrackMap.count(childID)) { + // Add playlist tracks for children + for (uint32_t trackIndex = 1; trackIndex <= playlistTrackMap[childID].size(); trackIndex++) { + uint32_t rbTrackID = playlistTrackMap[childID][trackIndex]; + + int trackID = -1; + QSqlQuery finderQuery(m_database); + finderQuery.prepare("select id from rekordbox_library where rb_id=:rb_id and device=:device"); + finderQuery.bindValue(":rb_id", rbTrackID); + finderQuery.bindValue(":device", device); + + if (!finderQuery.exec()) { + LOG_FAILED_QUERY(finderQuery) + << "rbTrackID:" << rbTrackID + << "device:" << device; + return; + } + + if (finderQuery.next()) { + trackID = finderQuery.value(finderQuery.record().indexOf("id")).toInt(); + } + + queryInsertIntoPlaylistTracks.bindValue(":playlist_id", playlistID); + queryInsertIntoPlaylistTracks.bindValue(":track_id", trackID); + queryInsertIntoPlaylistTracks.bindValue(":position", (int)trackIndex); + + if (!queryInsertIntoPlaylistTracks.exec()) { + LOG_FAILED_QUERY(queryInsertIntoPlaylistTracks) + << "playlistID:" << playlistID + << "trackID:" << trackID + << "trackIndex:" << trackIndex; + + return; + } + } + } + + if (playlistIsFolderMap[childID]) { + // If this child is a folder (playlists are only leaf nodes), build playlist tree for it + buildPlaylistTree(child, childID, playlistNameMap, playlistIsFolderMap, + playlistTreeMap, playlistTrackMap, currentPath, device); + } + } +} + +void RekordboxFeature::clearTable(QString tableName) { + QSqlQuery query(m_database); + query.prepare("delete from " + tableName); + + if (!query.exec()) { + LOG_FAILED_QUERY(query) + << "tableName:" << tableName; + } else { + query.prepare("delete from sqlite_sequence where name='" + tableName + "'"); + + if (!query.exec()) { + LOG_FAILED_QUERY(query) + << "tableName:" << tableName; + } else { + qDebug() << "Rekordbox table entries of '" << tableName << "' have been cleared."; + } + } +} + +void RekordboxFeature::onRekordboxDevicesFound() { + QList foundDevices = m_devicesFuture.result(); + TreeItem *root = m_childModel.getRootItem(); + + if (foundDevices.size() == 0) { + // No Rekordbox devices found + ScopedTransaction transaction(m_database); + clearTable("rekordbox_playlist_tracks"); + clearTable("rekordbox_library"); + clearTable("rekordbox_playlists"); + transaction.commit(); + + if (root->childRows() > 0) { + // Devices have since been unmounted + m_childModel.removeRows(0, root->childRows()); + } + } else { + for (int deviceIndex = 0; deviceIndex < root->childRows(); deviceIndex++) { + TreeItem *child = root->child(deviceIndex); + bool removeChild = true; + + for (int foundDeviceIndex = 0; foundDeviceIndex < foundDevices.size(); foundDeviceIndex++) { + TreeItem *deviceFound = foundDevices[foundDeviceIndex]; + + if (deviceFound->getLabel() == child->getLabel()) { + removeChild = false; + break; + } + } + + if (removeChild) { + // Device has since been unmounted, cleanup DB + ScopedTransaction transaction(m_database); + + int trackID = -1; + int playlistID = -1; + QSqlQuery tracksQuery(m_database); + tracksQuery.prepare("select id from rekordbox_library where device=:device"); + tracksQuery.bindValue(":device", child->getLabel()); + + QSqlQuery deletePlaylistsQuery(m_database); + deletePlaylistsQuery.prepare("delete from rekordbox_playlists where id=:id"); + + QSqlQuery deletePlaylistTracksQuery(m_database); + deletePlaylistTracksQuery.prepare("delete from rekordbox_playlist_tracks where playlist_id=:playlist_id"); + + if (!tracksQuery.exec()) { + LOG_FAILED_QUERY(tracksQuery) + << "device:" << child->getLabel(); + } + + while (tracksQuery.next()) { + trackID = tracksQuery.value(tracksQuery.record().indexOf("id")).toInt(); + + QSqlQuery playlistTracksQuery(m_database); + playlistTracksQuery.prepare("select playlist_id from rekordbox_playlist_tracks where track_id=:track_id"); + playlistTracksQuery.bindValue(":track_id", trackID); + + if (!playlistTracksQuery.exec()) { + LOG_FAILED_QUERY(playlistTracksQuery) + << "trackID:" << trackID; + } + + while (playlistTracksQuery.next()) { + playlistID = playlistTracksQuery.value(playlistTracksQuery.record().indexOf("playlist_id")).toInt(); + + deletePlaylistsQuery.bindValue(":id", playlistID); + + if (!deletePlaylistsQuery.exec()) { + LOG_FAILED_QUERY(deletePlaylistsQuery) + << "playlistID:" << playlistID; + } + + deletePlaylistTracksQuery.bindValue(":playlist_id", playlistID); + + if (!deletePlaylistTracksQuery.exec()) { + LOG_FAILED_QUERY(deletePlaylistTracksQuery) + << "playlistID:" << playlistID; + } + + } + + } + + QSqlQuery deleteTracksQuery(m_database); + deleteTracksQuery.prepare("delete from rekordbox_library where device=:device"); + deleteTracksQuery.bindValue(":device", child->getLabel()); + + if (!deleteTracksQuery.exec()) { + LOG_FAILED_QUERY(deleteTracksQuery) + << "device:" << child->getLabel(); + } + + transaction.commit(); + + m_childModel.removeRows(deviceIndex, 1); + } + } + + QList childrenToAdd; + + for (int foundDeviceIndex = 0; foundDeviceIndex < foundDevices.size(); foundDeviceIndex++) { + TreeItem *deviceFound = foundDevices[foundDeviceIndex]; + bool addNewChild = true; + + for (int deviceIndex = 0; deviceIndex < root->childRows(); deviceIndex++) { + TreeItem *child = root->child(deviceIndex); + + if (deviceFound->getLabel() == child->getLabel()) { + // This device already exists in the TreeModel, don't add or parse is again + addNewChild = false; + } + } + + if (addNewChild) { + childrenToAdd << deviceFound; + } + } + + if (!childrenToAdd.empty()) { + m_childModel.insertTreeItemRows(childrenToAdd, 0); + } + } + + // calls a slot in the sidebarmodel such that 'isLoading' is removed from the feature title. + m_title = tr("Rekordbox"); + emit(featureLoadingFinished(this)); +} + +void RekordboxFeature::onTracksFound() { + qDebug() << "onTracksFound"; + m_childModel.triggerRepaint(); + + QString devicePlaylist = m_tracksFuture.result(); + + qDebug() << "Show Rekordbox Device Playlist: " << devicePlaylist; + + m_pRekordboxPlaylistModel->setPlaylist(devicePlaylist); + emit(showTrackModel(m_pRekordboxPlaylistModel)); +} diff --git a/src/library/rekordbox/rekordboxfeature.h b/src/library/rekordbox/rekordboxfeature.h new file mode 100755 index 00000000000..8ece0c91b3e --- /dev/null +++ b/src/library/rekordbox/rekordboxfeature.h @@ -0,0 +1,117 @@ +// rekordboxfeature.h +// Created 05/24/2019 by Evan Dekker + +// This feature reads tracks, playlists and folders from removable Recordbox +// prepared devices (USB drives, etc), by parsing the binary *.PDB files +// stored on each removable device. It does not read the locally stored +// Rekordbox database (Collection). + +// It draws heavily from the hard work completed here: + +// https://github.com/Deep-Symmetry/crate-digger + +// And uses the C++ Kaitai Struct binary parsing libraries: + +// http://kaitai.io +// https://github.com/kaitai-io/kaitai_struct +// https://github.com/kaitai-io/kaitai_struct_cpp_stl_runtime + +// The *.PDB C++ files: + +// rekordbox_pdb.h +// rekordbox_pdb.cpp + +// Were generated from the following structure definition file: + +// https://github.com/Deep-Symmetry/crate-digger/blob/master/src/main/kaitai/rekordbox_pdb.ksy + +#ifndef REKORDBOX_FEATURE_H +#define REKORDBOX_FEATURE_H + +#include +#include +#include +#include +#include +#include + +#include + +#include "library/baseexternallibraryfeature.h" +#include "library/baseexternaltrackmodel.h" +#include "library/baseexternalplaylistmodel.h" +#include "library/treeitemmodel.h" + +#include "library/rekordbox/rekordbox_pdb.h" + +#define PDB_PATH "PIONEER/rekordbox/export.pdb" + +class TrackCollection; +class BaseExternalPlaylistModel; + +class RekordboxPlaylistModel : public BaseExternalPlaylistModel { + public: + RekordboxPlaylistModel(QObject* parent, + TrackCollection* pTrackCollection, + QSharedPointer trackSource); + TrackPointer getTrack(const QModelIndex& index) const override; + virtual bool isColumnHiddenByDefault(int column); +}; + +class RekordboxFeature : public BaseExternalLibraryFeature { + Q_OBJECT + public: + RekordboxFeature(QObject* parent, TrackCollection*); + virtual ~RekordboxFeature(); + + QVariant title(); + QIcon getIcon(); + static bool isSupported(); + void bindWidget(WLibrary* libraryWidget, + KeyboardEventFilter* keyboard) override; + + TreeItemModel* getChildModel(); + + public slots: + void activate(); + void activateChild(const QModelIndex& index); + void refreshLibraryModels(); + void onRekordboxDevicesFound(); + void onTracksFound(); + + private slots: + void htmlLinkClicked(const QUrl& link); + + private: + QString formatRootViewHtml() const; + virtual BaseSqlTableModel* getPlaylistModelForPlaylist(QString playlist); + QList findRekordboxDevices(); + QString parseDeviceDB(TreeItem *deviceItem); + void buildPlaylistTree(TreeItem *parent, uint32_t parentID, + std::map &playlistNameMap, + std::map &playlistIsFolderMap, + std::map> &playlistTreeMap, + std::map> &playlistTrackMap, + QString playlistPath, QString device); + void clearTable(QString table_name); + template + inline bool instanceof(const T *ptr) {return dynamic_cast(ptr) != nullptr;} + std::string getText(rekordbox_pdb_t::device_sql_string_t *deviceString); + + TreeItemModel m_childModel; + TrackCollection* m_pTrackCollection; + // A separate db connection for the worker parsing thread + QSqlDatabase m_database; + RekordboxPlaylistModel* m_pRekordboxPlaylistModel; + + QFutureWatcher> m_devicesFutureWatcher; + QFuture> m_devicesFuture; + QFutureWatcher m_tracksFutureWatcher; + QFuture m_tracksFuture; + QString m_title; + + QSharedPointer m_trackSource; + QIcon m_icon; +}; + +#endif // REKORDBOX_FEATURE_H From 90762a1144b26c43c5267496b7ffdd61168d5230 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Fri, 24 May 2019 21:51:20 +1000 Subject: [PATCH 02/36] Rekordbox library feature that reads tracks, playlists and folders from removable devices --- .DS_Store | Bin 12292 -> 0 bytes .gitignore | 1 + lib/.DS_Store | Bin 8196 -> 0 bytes res/.DS_Store | Bin 8196 -> 0 bytes res/images/.DS_Store | Bin 8196 -> 0 bytes src/.DS_Store | Bin 10244 -> 0 bytes src/library/.DS_Store | Bin 8196 -> 0 bytes src/library/rekordbox/.DS_Store | Bin 6148 -> 0 bytes 8 files changed, 1 insertion(+) delete mode 100644 .DS_Store delete mode 100644 lib/.DS_Store delete mode 100644 res/.DS_Store delete mode 100644 res/images/.DS_Store delete mode 100644 src/.DS_Store delete mode 100644 src/library/.DS_Store delete mode 100644 src/library/rekordbox/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 1c6f7a4f9c944dfcb65065bfae7805ee0ee965fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12292 zcmeHNd2AGA6n}4fFbB|Sxyn(xSf~YRX=$MaENn026tpd+TxGXAz`%63?CiFbQc^XF zQB*Vv5wAq!5y2y(F~(qwM}Wj5!6rb^Xw;Y(qsCy=_?PedzS-?|yG=Ym2+SliZ@%}w zZ@**Td*A!LZvX(RBmNSA0sxTcW>gyvkg5QR|;{EZjEkwC*m`$jvAjC z^bM-(Fq{=|R^ZB70T&I2*>5V{DPV#TZ@;cXzIM~F!o|gB(Up>#mOf096?OR1Wy_To zEvv)OJ6E-bjbOl9qlYY_En0@PE!erVH5gbO(wV$P%WUWM2Th|$WJrFjw7h4An}~@}#v9pRqGyguD7OGqjPpZdKXfry;Bi`q{7fwKpW0OQyP2bv={RgmilP zB}oiwx;j(Vyi$WjGZCC3rC8Q1xnYM9@NR(Cf4%`uN7X^t_NsTC#n0)?D69k)2N!6tjh0aW6JX| z*G?B-fILsJD9Pl}$3qc#paC|34(-qd`{90g01m+8@GQIp$KW`egj4Vld<>`IJbVM+ z!v**ieuLi;P{y&CjpK0w=HV4kJfe#{N zktXcd`QDPg~lF116Q6>wJI8eReBAKY{^>al6BpxkxP zgM|=G&O!)&(K~|!jOVtg$ELl4l7uc4O^J%8XhRGX&57@}>v?S2E2wA=$a|*Wv3o`v zp&;*__-J=n z1>Txn%vTE}LI;|Q|JIC|s-mS35RiFsR9qoZqrfw0#I=po*i5F%CC&_sWDu(LWC z3HUAkJM}^!$<$GcmfhW5I{RuKeIrcoZxM2y^Wd=JgTUBFtny|Hi$dhuCVQN-?N={NtJuP2VxG8gUVijl1R1|K} z+=Lj18JMtiZe)x_My5tBp4%2L--OwD99G^(VSTBcxw;ppq243@xZ zXa+xY5{`ZZ4#FW|#`h+n=o9cM^pW|Ug>&#V`~W|~Pw+GRfoV7lB|_3!+z8!dghd=l z&n6`8!D>R%wS=VW$pF`o32r9S+m1WXB-7iAw~^u9f&1|>LeWp+Q}_bDhR5)AJdW=O zjT_SV-W{NaQHS4@To1=6b(dEU2m1XRW|!YMq~9Ol&j0o%z`e0LNtqn=@QIo&_5+@U z(T0iI{eGWq8swe}Wajr#Nb}V~DY^+Lv(yrn4AN4BtW259@;+J`FDmnt1uQY7r3&dn z#luoSTDp+dD0Mt_r9sT|i?mI0b$gLgHt&(DorkaBJ5sX?q+&?wl|gBmTy#^SW-8|M zWX)_W#W|F!S%g)jW=zR$ri9Hc+{bUEq|IjT<4tVCAouazxQA43AKuLqH}~N|JcNh2 z>OGgBY)eEr`o!n^m2Hrxx5(GGUSXcTtb7i6`neaw)AQ*$p5F2Fm)X-t_0aM3%vn30 z{*ronu51aOKDrq=^h#WTG#XUgS;N)3AID8GZclcOIGrQTEAfbPpiN$y^FJpq$5ED( zmy7L6JTDjLC=>Q^`wNk)a)-DE@jv%5{J$??=l^v>fBzR#5a-2N0cQoSz7@dK+WOjR znpgDfB5{iBtldrbX1dwM>h%iBo6y5Pj^{Fd6i+<+TQkn{P<7U>S5V%J%A(o-_7MPO Y0GxBVA&&;wxtz2ACwxeHwdVi-0y6DcGXMYp diff --git a/.gitignore b/.gitignore index ba9739c6688..6e4109cda75 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ lib/qtscript-bytearray/*.cc .idea *.vscode compile_commands.json +.DS_Store diff --git a/lib/.DS_Store b/lib/.DS_Store deleted file mode 100644 index 0d4562360217f377b49803412930a51bc99a4ef2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeI1U2GIp6oAj!7MSY*ovlE(P)ZC=-abA~|NT8`EiDVC;gOx6NGilpRFPj}I z!U%*B2qO?iAdEm5f&W7U=*;Ftxy89JN5eLZKp25LG6MYl5U0vzGLR)H#jArFkpht9 zDS#-{r#wK|!~>ZOWJyY4O81o60|urTq!=j8X*|xSlS~G(B&9S5l;(iJ${1uQC{`!C zxR?%@lrn6?2!s(>9s&OISqbNP<_>;E{{C5d#(O(k`PB*CKbH>rBd%*N_Vx6bWfrHjb-k|R>y~5Ygt29qH08{=wi0`uD-u^U~o#)@}7Cpv~}Mqn0>l$dQ)1BN8S!) zT<1j2EC|n{8Qsq0X_DD(+bQd^>5XSxJD+vD3E#ZvbIqjZ=KNlvoUq82>!!3?Za+NZ zTdw1cnK_Rn#dm(qhK=#<&G+u>I?}VSwnmHA-p$w~g~jo$M=UcpTC@!R+$r75n2upN zXU7YcXQgd(#L=^6H1a-U3(*>Ftr}As6s=L2$fvCfdCR{fv~07wm1) zp=$47>Ucr=q#X;C2M#iIBI9PWr1K z_1%VH8j8}G+$kSzW3p9t=yk6^JtW{LoPn}V{0hIp@9+mA-i0cz#u#qHCfte5*n<183lHKUJdFK#43Fc3IE)iG zjcLrFfmtlzCA^HY_!K^k&)~E8GQNTD;5@#I*YHERvq+i7A$eVH@nh;O`N~diQu~R8@6t5-o3Z;aQEUPtaxBcn7R=p!H)z)fKvuT5W7$pt8Y*@ zu`OkeEBPKJ)Xa+t=S5q!&=9Myi*Hj^jT57YOOB|91T_ouJF(tC7J=am2?Sqb1Z z+NV0e^9lPh=F3$sJy$v>uO85MMZd&A?oRqJGbb7IN`ZJ?np`bTA`Gq}m zz!;Z7A7miNz~&6F+h+$DkOdWHOX~M;)AOd`xb5vXF;rGwv7=HDMM10*&m`yUg`}&x zc_*&srr6Q2>dvM8{Sn(X*Z0-*n4vFBD?1am<*J6I=R9Xa)5+v?%CIzhA#Ud_&GF8Y z2^ZK+>?NE$^bU2*T3tl`m8P&|> zP1V(VO_Lcpsf=yrvz9aN>WeNLnRM)&oA3sw6(#TJ&*)@t6!bpwa;BA9cBY|u*RU;T zOwTza8)NN!>7wva)$RwvO)ZBzkDiFH-CM2H)ZQmalawCIHJ&x}+~|U#xpNb$lhG~B zux3*Q!!gpPK4Pg^y(VOfqUTjtFU#ZkwDEl2aF>)uLGVWR?3Ja{8ZG0Lt+BId%4Y(% zKd@Jpr^?UFods%hO+G?C8}?ULkF zfybsi;SU~rRFcOtb~a0)I5pVn5BHpu<+E(&CsoHCn%6B_)i?h130lGJqg9W$@ufr9%3h%7CemY7{Lxajxmg50{ie3CUF$UFoiQXhtJ_TG%=4C@dbPlFX3f; z6W_wO@g4jCKf&wxDc-;@_|if}8VC7tvBxiIZ^JD|H2R;+La95`xJ&B)Z)v<;#Sz@O zYxn-9mPb0eAM0Jege4bk16O}X$>WCu;(=2H!y|UBuD;=Zd5^TO$Z;v(qlB8iC@@}> zN^X5XZWIU+Meq)#X8Djv94pAR&2pAn;zom+=*R6<@{eYnH}~m~M0lj_&>pl<^SjS^eL?_4og~KM_HMK?b&Q z2C%#@*%zk-Z1pN&wRV`c8QNH5cB@=^F4TGDI7zP@C%OKIA?=6Bl=;MbxymJtQ2WmtcA4!AbaXmXc4qrS zt!YfuM2w;_F&cT%_yqnW#s_13QX?el12#dU#s_`z(P%XB$#d_WZJ?zO`k)f#Cik9u z&$(yrJ>UIiZq6(cQ!A|3irOWMs0*Zgurcy0RKKDsdAYK<-C;Q(?N|$0Z8%` zKosgz9w2;@q0EGGUP|Fg&y?8%hOQW<7%1Jzp5)U>W+1G5q>@by^>cHoQh9e>OXtb)nX|Kx%{~5# zj10rC1C%d`^ws5II{svP-r-@!UM_|C^NUB5rDrUR}S^u0pRE|mQ?`Ep&3 zv7&E1WRasiX?2jh&p=f!uB+Hqb5E*n-+|1bh4tkuu5Hvd_f6Wqop-F>DccOj1`L1H za!uPkJxGYTfqlxh3Tiy&sXeYSZY@+Ms^a<vp2?qPviB8LSau|JeTZAy3l_DeqR36{*7vB)Pzl4erG>)?qUqz!p4+?RXTA z;c@K7UL3(38kom1Jc9)cu!wW`6h4j5;5mE=FW@D78{ffq@dNxAKfzD&OZ*DIlN*ec zNPA3eo zuP2jWkEDm7O^Fub6mQ@o=XCX!J2kzR*kw&GeGf`ZXKOi+l!a>Ih<=w|qYz_QINYi4 zQi#?pCZ2Wg9$jOrh{38D?oDetTTNtD#IT`J(^wg?Sz7OFChHpFt+c8KHI2V*s#r(c zsduOf!+*fRH{cR{2%i!AuEI6=0e*sC;7{V+8f3(~YTSz3a69h8-M9x+Sd06yDP-Vb zd=QUd7qPGx`)~jUaR^O3MI5y85%h_JQ#g$?_yj&FxOg64z!wD@U&o6}xYt=?os@WK zIrk>B1=sQ1(_|?z_C}{@bZa57N^Up7^Z!FD|Neh#1r_Z!LLfrmZy|tX9oddHa#`uK z9wS?l>Pf1+@Okr63Kwel6s{NPILXC745^>XV1J?* zMad&4Hseh#sU0YS2m}!bA`nC%h(Hj5cS8i2v&E$uN(Rp$0zm|V2oxe9=7%ufLPmVK zpro8S_#w9dEX!CeFaFK@0O=Fo*`wB4UOA1b8~IG+nd9?ZkcZm&$YJhZfg$jY1=hF zuWQvex9;mZGdX+r+&%O6KBC43e}15fqOpFC%Ni98G~rEmtAUn$CO^^k$tFEc7 zH0q6Y*YeWr25l@GbMMN!nb}-rg}rJ+*a)49a~B3uaq+ge{Y_1mmQ7*9I4LaMX@{S2 zO;erPYBcF~R2%kqBucYA)S}y-K71#CSgA2~h1xYcu8$<5F(;u^dqN$W-LH>Mt8gmi zzR&@!63zP9L~3%9$K%PCJG%_yv}pNA)XN;5a+2Ju%iMfrO>Old?$vQ&>x{=mpBf^K z>buJTG(=idN#{>rOWT=ChUpYJG)1#?fgYrX=psEzPth~(25S+ihbCRZXCe?j$#laIE`^c5yLs$g)}nAVjdsI zCvYF`$EWZGT*6oIReTNK#`p08et;M8GyF<*up|%L0aeaF@oWCI9Bik2*q&Y%wg(UO z{;y#BswJ?xy6T2CYd37#x_#H4eVv8RS@abb!%5;t0Ym~P4-EMouR^3ibzS|9A!CH` z<(x0hggn;ma)Bf*SH~zZZZaA)#*FP3Hye!_1KTc<@7mU6gzQqrur7-`T0(}sg5j#k zVq1GCWS23Xm340iTURo2l~vy#3WaY&$u>pk5M>- z^O(g)@G*(Ti})lSmU#Rko-RUPUmkZ#;?i>Hn;uFh6RG4`wh7dhQ-WM(jx_T}nr6k> z?|YFuiiWv}IbO`C;4c<4hqbEzNdLeG%>Mv;2(Fn(rxJ<5l;;W(I$q$DC8n|KVVH`V z@XazsyLxjXXU#Qcbv}QJ^J;m9sS>HU&a#SY=igFb7iJnwwXxCKq;v9JoztRqYopbu zX^SU?Tvg?2ds~xb%Gp6?sL2=o|Dc zdX}D}7nopv&II!Z`U|J{>-2X{^HL_5D%4{WHe(AXc`J6J4ejWXX+6M69m83f&UZ4c z%wi7rNNTx&2k{Ut;t@Q7C-Egb#RT&#zJurSU8a{G;xew_75o;z!)y2x-oRTWWhIvC ze}QlY^W*XLt5`0@dY*1ReGRPl-qzc9_}z^4SM#J)U31gsrk3`O1KkB66n*x^2)qtj z5}9DFH^X`87pH`xE4jY0%X2r@)z@xpuq<2PPLstgja)3@)X;L`UFQ3&9b#o=m&&5G z(`*xqD0_vn?l$*|RhC_*tnWAXi}jSfQohQFn5Lp7(N0gJW!lj6e;2wxrW~(}{>-3# zlior(OkNRf#5P_Jbs&OH^xz-<9)X^;5z7L=6k>?hq>s?+@c`kw*8{$HF&1^fT&I6E#{VE*6l|NjYY)-4kN diff --git a/src/library/.DS_Store b/src/library/.DS_Store deleted file mode 100644 index 244e8a7ad0156f49fc546be4c2bbcbe6a27f2979..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMT}&NC6h7xd%Z^*h(w1_erM(R;7D_D?XrX^z?xhw|D-D;w(kkq|yTYc+F1@?= z@+V6Ci9TpGQDfA^7-M20eKKicj4{4hqc27ht43dZ@WB_2zWC;unFVP9AJw!w$;_EK zXU@!?Z@!()xfcL1kuw?rq5z;!Qf#dd}4u&2XbCY;Yw@D>;VH;3{nh~?qrYg=_KQUoR?C%14?(mU}p?66coFYUrbB~ zj7u4|Ap#)+s}bO@52L)@gv*q-ugKrMiL~ve$@+PE!nQoWq2U{h6cldQSfntfuwwRb zYRsKX`MRI;5_t*JiN)G}8en{7PYe6&8++R{8X$CUDYjVHTC#;0dyADesp zbr~H7Hw2X27VR5rhiZHIqzpBZT?+N)-=V6T9w{|0~y!OjXU0;Z%+AKGvv8hKPi-h7Ws1Bu(pN! zcTf11>v;WU)+0$MFBEUz5!+jPUsLPJ#Ny@>t(0wLWdnK3v(mQNH)$FE*ah9on2upN zqeBFm<6DnfW|oykK4iU)K5hyxHL6xBTD3BmOIufRmOm}ms#13|?YeTwpvOJu8r;*6 z^Q+WqMY+iJ$*f7Ou~=H7ZQfU-vR;oKSJM?HG<63U>%B~;r+G|j4mB{f&(_nXtr<)a zlbTzYI*^w!X=ejv+cBmNX58^{dStH@>q*VyQ}0*X743%d2;cib-SfLAOeferS*5BM zxW3&mOhZws{lg{0@I0;ytM1CXC`9tikcP#i#KZd={U>*YIt89~bZ%UdK=5lSP&|*(JwIEq+S9HC{jBWbpb$ z2G-+b;=keKx5fORwrt&2wte^B+JlFWo@ie_$lH$X7G|xalVFjgiJ;FCJtSqzqZO6v zPPS`3qW2Nu%qfMlMk`vZj8>G#_Nc1Hd83FSC#r@-Z6=0wHDryDjW5g&sf~;XtQCl% z+RTn9L{)8rw6?M1jL57NO6w^WSBSIPMzK&L$(RQG-D0cB_Yc&22NvKXqTSbUlgRcH z{6cj5lc-jNrC3f>+krcA7v4)`JAn0g5Ra@N+!^e|F6_Y+_F+HKPAA$KXyPbN;1%@H zC;B~s^Y|n_MfAIhFX7Ah3ciYONs28KuXBlVQsR4S6EC02I=1VKl68r)w>pNSI|qRR z8A_Dr|EJgg{r}DhIE*AjAVgpl0$A9Q>PV1!y(^#R+A*plRC(d^=A{%a)QEJPq)5j} kF8^Uj{TNN@~ diff --git a/src/library/rekordbox/.DS_Store b/src/library/rekordbox/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Sat, 25 May 2019 07:55:20 +1000 Subject: [PATCH 03/36] Fixed Appveyor Linux build failure --- src/library/rekordbox/rekordboxfeature.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 9e91c5c29c6..3a7c439e93a 100755 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -318,9 +318,9 @@ QList RekordboxFeature::findRekordboxDevices() { devices += QDir("/media").entryInfoList( QDir::AllDirs | QDir::NoDotAndDotDot); - // Add folders under /run/media/$USER to devices. - QDir run_media_user_dir("/run/media/" + qgetenv("USER")); - devices += run_media_user_dir.entryInfoList( + // Add folders under /media/$USER to devices. + QDir media_user_dir("/media/" + qgetenv("USER")); + devices += media_user_dir.entryInfoList( QDir::AllDirs | QDir::NoDotAndDotDot); // Add folders under /run/media/$USER to devices. From c7e97576044c128a0884c950ca3ecee191ae2c2e Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Sun, 26 May 2019 16:28:47 +1000 Subject: [PATCH 04/36] Addressed technical comments from PR #2119 --- src/library/rekordbox/rekordboxfeature.cpp | 1250 ++++++++++---------- src/library/rekordbox/rekordboxfeature.h | 39 +- 2 files changed, 650 insertions(+), 639 deletions(-) mode change 100755 => 100644 src/library/rekordbox/rekordboxfeature.cpp mode change 100755 => 100644 src/library/rekordbox/rekordboxfeature.h diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp old mode 100755 new mode 100644 index 3a7c439e93a..e02cb5f46f1 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -1,14 +1,14 @@ // rekordboxfeature.cpp // Created 05/24/2019 by Evan Dekker -#include -#include -#include #include +#include #include #include +#include #include "library/rekordbox/rekordboxfeature.h" +#include "library/rekordbox/rekordbox_pdb.h" #include "library/librarytablemodel.h" #include "library/missingtablemodel.h" @@ -16,9 +16,9 @@ #include "library/trackcollection.h" #include "library/treeitem.h" #include "track/keyfactory.h" -#include "waveform/waveform.h" -#include "util/sandbox.h" #include "util/file.h" +#include "util/sandbox.h" +#include "waveform/waveform.h" #include "widget/wlibrary.h" #include "widget/wlibrarytextbrowser.h" @@ -26,254 +26,34 @@ #define IS_RECORDBOX_DEVICE "::isRecordboxDevice::" #define IS_NOT_RECORDBOX_DEVICE "::isNotRecordboxDevice::" -RekordboxPlaylistModel::RekordboxPlaylistModel(QObject* parent, - TrackCollection* trackCollection, - QSharedPointer trackSource) - : BaseExternalPlaylistModel(parent, trackCollection, - "mixxx.db.model.rekordbox.playlistmodel", - "rekordbox_playlists", - "rekordbox_playlist_tracks", - trackSource) { -} - -TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { - qDebug() << "RekordboxTrackModel::getTrack"; - - TrackPointer track = BaseExternalPlaylistModel::getTrack(index); - - // Assume that the key of the file the has been analyzed in Recordbox is correct - // and prevent the AnalyzerKey from re-analyzing. - track->setKeys(KeyFactory::makeBasicKeysFromText(index.sibling( - index.row(), fieldIndex("key")).data().toString(), mixxx::track::io::key::USER)); - - return track; -} - -bool RekordboxPlaylistModel::isColumnHiddenByDefault(int column) { - if ( - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ID) - ) { - return true; - } - return BaseSqlTableModel::isColumnHiddenByDefault(column); -} - -RekordboxFeature::RekordboxFeature(QObject* parent, TrackCollection* trackCollection) - : BaseExternalLibraryFeature(parent, trackCollection), - m_pTrackCollection(trackCollection), - m_icon(":/images/library/ic_library_rekordbox.svg") { - QString tableName = "rekordbox_library"; - QString idColumn = "id"; - QStringList columns; - columns << "id" - << "artist" - << "title" - << "album" - << "year" - << "genre" - << "tracknumber" - << "location" - << "comment" - << "rating" - << "duration" - << "bitrate" - << "bpm" - << "key"; - m_trackSource = QSharedPointer( - new BaseTrackCache(m_pTrackCollection, tableName, idColumn, - columns, false)); - QStringList searchColumns; - searchColumns - << "artist" - << "title" - << "album" - << "year" - << "genre" - << "tracknumber" - << "location" - << "comment" - << "duration" - << "bitrate" - << "bpm" - << "key"; - m_trackSource->setSearchColumns(searchColumns); - - m_pRekordboxPlaylistModel = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); - - m_title = tr("Rekordbox"); - - m_database = QSqlDatabase::cloneDatabase(trackCollection->database(), - "REKORDBOX_SCANNER"); - - //Open the database connection in this thread. - if (!m_database.open()) { - qDebug() << "Failed to open database for Rekordbox scanner." - << m_database.lastError(); - } else { - //Clear any previous Rekordbox device entries if they exist - ScopedTransaction transaction(m_database); - clearTable("rekordbox_playlist_tracks"); - clearTable("rekordbox_library"); - clearTable("rekordbox_playlists"); - transaction.commit(); - } - - connect(&m_devicesFutureWatcher, SIGNAL(finished()), - this, SLOT(onRekordboxDevicesFound())); - connect(&m_tracksFutureWatcher, SIGNAL(finished()), - this, SLOT(onTracksFound())); - // initialize the model - m_childModel.setRootItem(std::make_unique(this)); +namespace { +const QString kPDBPath = "PIONEER/rekordbox/export.pdb"; +const QString kPLaylistPathDelimiter = "-->"; -} - -RekordboxFeature::~RekordboxFeature() { - m_database.close(); - m_devicesFuture.waitForFinished(); - m_tracksFuture.waitForFinished(); - delete m_pRekordboxPlaylistModel; -} - -void RekordboxFeature::bindWidget(WLibrary* libraryWidget, - KeyboardEventFilter* keyboard) { - Q_UNUSED(keyboard); - WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget); - edit->setHtml(formatRootViewHtml()); - edit->setOpenLinks(false); - connect(edit, SIGNAL(anchorClicked(const QUrl)), - this, SLOT(htmlLinkClicked(const QUrl))); - libraryWidget->registerView("REKORDBOXHOME", edit); -} +void clearTable(QSqlDatabase &database, QString tableName) { + QSqlQuery query(database); + query.prepare("delete from " + tableName); -void RekordboxFeature::htmlLinkClicked(const QUrl& link) { - if (QString(link.path()) == "refresh") { - activate(); + if (!query.exec()) { + LOG_FAILED_QUERY(query) + << "tableName:" << tableName; } else { - qDebug() << "Unknown link clicked" << link; - } -} - -BaseSqlTableModel* RekordboxFeature::getPlaylistModelForPlaylist(QString playlist) { - RekordboxPlaylistModel* model = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); - model->setPlaylist(playlist); - return model; -} - -QVariant RekordboxFeature::title() { - return m_title; -} - -QIcon RekordboxFeature::getIcon() { - return m_icon; -} - -bool RekordboxFeature::isSupported() { - return true; -} - -TreeItemModel* RekordboxFeature::getChildModel() { - return &m_childModel; -} - -QString RekordboxFeature::formatRootViewHtml() const { - QString title = tr("Rekordbox"); - QString summary = tr("Reads playlists and folders from Rekordbox prepared removable devices."); - - QString html; - QString refreshLink = tr("Check for attached Rekordbox devices (refresh)"); - html.append(QString("

%1

").arg(title)); - html.append(QString("

%1

").arg(summary)); - - //Colorize links in lighter blue, instead of QT default dark blue. - //Links are still different from regular text, but readable on dark/light backgrounds. - //https://bugs.launchpad.net/mixxx/+bug/1744816 - html.append(QString("%1") - .arg(refreshLink)); - return html; -} - -void RekordboxFeature::refreshLibraryModels() { -} - -void RekordboxFeature::activate() { - qDebug() << "RekordboxFeature::activate()"; - - // Usually the maximum number of threads - // is > 2 depending on the CPU cores - // Unfortunately, within VirtualBox - // the maximum number of allowed threads - // is 1 at all times We'll need to increase - // the number to > 1, otherwise importing the music collection - // takes place when the GUI threads terminates, i.e., on - // Mixxx shutdown. - QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 - // Let a worker thread do the XML parsing - m_devicesFuture = QtConcurrent::run(this, &RekordboxFeature::findRekordboxDevices); - m_devicesFutureWatcher.setFuture(m_devicesFuture); - m_title = tr("(loading) Rekordbox"); - //calls a slot in the sidebar model such that 'Rekordbox (isLoading)' is displayed. - emit(featureIsLoading(this, true)); - - emit(enableCoverArtDisplay(true)); - emit(switchToView("REKORDBOXHOME")); - -} - -void RekordboxFeature::activateChild(const QModelIndex& index) { - if (!index.isValid()) return; - - //access underlying TreeItem object - TreeItem *item = static_cast(index.internalPointer()); - if (!(item && item->getData().isValid())) { - return; - } - - // TreeItem list data holds 2 values in a QList and have different meanings. - // If the 2nd QList element IS_RECORDBOX_DEVICE, the 1st element is the - // filesystem device path, and the parseDeviceDB concurrent thread to parse - // the Rekcordbox database is initiated. If the 2nd element is - // IS_NOT_RECORDBOX_DEVICE, the 1st element is the playlist path and it is - // activated. - QList data = item->getData().toList(); - QString playlist = data[0].toString(); - bool doParseDeviceDB = data[1].toString() == IS_RECORDBOX_DEVICE; - - qDebug() << "RekordboxFeature::activateChild " << item->getLabel() - << " playlist: " << playlist << " doParseDeviceDB: " << doParseDeviceDB; - - if (doParseDeviceDB) { - qDebug() << "Parse Rekordbox Device DB: " << playlist; - - // Usually the maximum number of threads - // is > 2 depending on the CPU cores - // Unfortunately, within VirtualBox - // the maximum number of allowed threads - // is 1 at all times We'll need to increase - // the number to > 1, otherwise importing the music collection - // takes place when the GUI threads terminates, i.e., on - // Mixxx shutdown. - QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 - // Let a worker thread do the XML parsing - m_tracksFuture = QtConcurrent::run(this, &RekordboxFeature::parseDeviceDB, item); - m_tracksFutureWatcher.setFuture(m_tracksFuture); + query.prepare("delete from sqlite_sequence where name='" + tableName + "'"); - // This device is now a playlist element, future activations should treat is - // as such - data[1] = QVariant(IS_NOT_RECORDBOX_DEVICE); - item->setData(QVariant(data)); - } else { - qDebug() << "Activate Rekordbox Playlist: " << playlist; - m_pRekordboxPlaylistModel->setPlaylist(playlist); - emit(showTrackModel(m_pRekordboxPlaylistModel)); + if (!query.exec()) { + LOG_FAILED_QUERY(query) + << "tableName:" << tableName; + } else { + qDebug() << "Rekordbox table entries of '" << tableName << "' have been cleared."; + } } } -QList RekordboxFeature::findRekordboxDevices() { - QThread* thisThread = QThread::currentThread(); +QList findRekordboxDevices(RekordboxFeature *rekordboxFeature) { + QThread *thisThread = QThread::currentThread(); thisThread->setPriority(QThread::LowPriority); - QList foundDevices; + QList foundDevices; #if defined(__WINDOWS__) // Repopulate drive list @@ -289,10 +69,10 @@ QList RekordboxFeature::findRekordboxDevices() { // drive.filePath() doesn't make any access to the filesystem and consequently // shorten the delay - QFileInfo rbDBFileInfo(drive.filePath() + PDB_PATH); + QFileInfo rbDBFileInfo(drive.filePath() + kPDBPath); if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { - TreeItem *foundDevice = new TreeItem(this); + TreeItem *foundDevice = new TreeItem(rekordboxFeature); QList data; QString displayPath = drive.filePath(); @@ -316,23 +96,23 @@ QList RekordboxFeature::findRekordboxDevices() { // Add folders under /media to devices. devices += QDir("/media").entryInfoList( - QDir::AllDirs | QDir::NoDotAndDotDot); + QDir::AllDirs | QDir::NoDotAndDotDot); // Add folders under /media/$USER to devices. - QDir media_user_dir("/media/" + qgetenv("USER")); - devices += media_user_dir.entryInfoList( - QDir::AllDirs | QDir::NoDotAndDotDot); + QDir mediaUserDir("/media/" + qgetenv("USER")); + devices += mediaUserDir.entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); // Add folders under /run/media/$USER to devices. - QDir run_media_user_dir("/run/media/" + qgetenv("USER")); - devices += run_media_user_dir.entryInfoList( - QDir::AllDirs | QDir::NoDotAndDotDot); + QDir runMediaUserDir("/run/media/" + qgetenv("USER")); + devices += runMediaUserDir.entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); - foreach(QFileInfo device, devices) { - QFileInfo rbDBFileInfo(device.filePath() + "/" + PDB_PATH); + foreach (QFileInfo device, devices) { + QFileInfo rbDBFileInfo(device.filePath() + "/" + kPDBPath); if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { - TreeItem *foundDevice = new TreeItem(this); + TreeItem *foundDevice = new TreeItem(rekordboxFeature); QList data; data << device.filePath(); @@ -341,18 +121,17 @@ QList RekordboxFeature::findRekordboxDevices() { foundDevice->setLabel(device.fileName()); foundDevice->setData(QVariant(data)); - foundDevices << foundDevice; - } + foundDevices << foundDevice; + } } #else // __APPLE__ - QFileInfoList devices = QDir("/Volumes").entryInfoList( - QDir::AllDirs | QDir::NoDotAndDotDot); + QFileInfoList devices = QDir("/Volumes").entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); - foreach(QFileInfo device, devices) { - QFileInfo rbDBFileInfo(device.filePath() + "/" + PDB_PATH); + foreach (QFileInfo device, devices) { + QFileInfo rbDBFileInfo(device.filePath() + "/" + kPDBPath); if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { - TreeItem *foundDevice = new TreeItem(this); + TreeItem *foundDevice = new TreeItem(rekordboxFeature); QList data; data << device.filePath(); @@ -361,144 +140,243 @@ QList RekordboxFeature::findRekordboxDevices() { foundDevice->setLabel(device.fileName()); foundDevice->setData(QVariant(data)); - foundDevices << foundDevice; - } - } + foundDevices << foundDevice; + } + } #endif - return foundDevices; + return foundDevices; +} + +template +inline bool instanceof (const T *ptr) { + return dynamic_cast(ptr) != nullptr; } // Functions getText and parseDeviceDB are roughly based on the following Java file: // https://github.com/Deep-Symmetry/crate-digger/blob/master/src/main/java/org/deepsymmetry/cratedigger/Database.java // getText is needed because the strings in the PDB file "have a variety of obscure representations". -std::string RekordboxFeature::getText(rekordbox_pdb_t::device_sql_string_t *deviceString) { - if (instanceof(deviceString->body())) { - rekordbox_pdb_t::device_sql_short_ascii_t *shortAsciiString = - static_cast(deviceString->body()); - return shortAsciiString->text(); - } else if (instanceof(deviceString->body())) { - rekordbox_pdb_t::device_sql_long_ascii_t *longAsciiString = - static_cast(deviceString->body()); - return longAsciiString->text(); - } else if (instanceof(deviceString->body())) { - rekordbox_pdb_t::device_sql_long_utf16be_t *longUtf16beString = - static_cast(deviceString->body()); - return longUtf16beString->text(); +QString getText(rekordbox_pdb_t::device_sql_string_t *deviceString) { + if (instanceof (deviceString->body())) { + rekordbox_pdb_t::device_sql_short_ascii_t *shortAsciiString = + static_cast(deviceString->body()); + return QString::fromStdString(shortAsciiString->text()); + } else if (instanceof (deviceString->body())) { + rekordbox_pdb_t::device_sql_long_ascii_t *longAsciiString = + static_cast(deviceString->body()); + return QString::fromStdString(longAsciiString->text()); + } else if (instanceof (deviceString->body())) { + rekordbox_pdb_t::device_sql_long_utf16be_t *longUtf16beString = + static_cast(deviceString->body()); + return QString::fromStdString(longUtf16beString->text()); } - return std::string(""); + return QString(""); } -QString RekordboxFeature::parseDeviceDB(TreeItem *deviceItem) { - QString device = deviceItem->getLabel(); - QString devicePath = deviceItem->getData().toList()[0].toString(); - - qDebug() << "parseDeviceDB device: " << device << " devicePath: " << devicePath; - - std::string pathBase = devicePath.toStdString() + "/"; - std::string dbPath = pathBase + std::string(PDB_PATH); - - if (!QFile(QString::fromStdString(dbPath)).exists()) { - return devicePath; - } - - //Give thread a low priority - QThread* thisThread = QThread::currentThread(); - thisThread->setPriority(QThread::LowPriority); - - ScopedTransaction transaction(m_database); - - QSqlQuery query(m_database); - query.prepare("INSERT INTO rekordbox_library (rb_id, artist, title, album, year," - "genre,comment,tracknumber,bpm, bitrate,duration, location," - "rating,key,analyze_path,device) VALUES (:rb_id, :artist, :title, :album, :year,:genre," - ":comment, :tracknumber,:bpm, :bitrate,:duration, :location," - ":rating,:key,:analyze_path,:device)"); - - int audioFilesCount = 0; +int createDevicePLaylist(QSqlDatabase &database, QString devicePath) { + int playlistID = -1; - // Create a playlist for all the tracks on a device - QSqlQuery queryInsertIntoDevicePlaylist(m_database); - queryInsertIntoDevicePlaylist.prepare("INSERT INTO rekordbox_playlists (name) " - "VALUES (:name)"); + QSqlQuery queryInsertIntoDevicePlaylist(database); + queryInsertIntoDevicePlaylist.prepare( + "INSERT INTO rekordbox_playlists (name) " + "VALUES (:name)"); queryInsertIntoDevicePlaylist.bindValue(":name", devicePath); if (!queryInsertIntoDevicePlaylist.exec()) { LOG_FAILED_QUERY(queryInsertIntoDevicePlaylist) - << "devicePath: " << devicePath; - return devicePath; - } + << "devicePath: " << devicePath; + return playlistID; + } - QSqlQuery idQuery(m_database); + QSqlQuery idQuery(database); idQuery.prepare("select id from rekordbox_playlists where name=:path"); idQuery.bindValue(":path", devicePath); if (!idQuery.exec()) { LOG_FAILED_QUERY(idQuery) - << "devicePath: " << devicePath; - return devicePath; - } + << "devicePath: " << devicePath; + return playlistID; + } - int playlistID = -1; while (idQuery.next()) { playlistID = idQuery.value(idQuery.record().indexOf("id")).toInt(); - } + } - QSqlQuery queryInsertIntoDevicePlaylistTracks(m_database); - queryInsertIntoDevicePlaylistTracks.prepare( - "INSERT INTO rekordbox_playlist_tracks (playlist_id, track_id, position) " - "VALUES (:playlist_id, :track_id, :position)"); + return playlistID; +} - queryInsertIntoDevicePlaylistTracks.bindValue(":playlist_id", playlistID); +void insertTrack( + QSqlDatabase &database, + rekordbox_pdb_t::track_row_t *track, + QSqlQuery &query, + QSqlQuery &queryInsertIntoDevicePlaylistTracks, + QMap &artistsMap, + QMap &albumsMap, + QMap &genresMap, + QMap &keysMap, + QString devicePath, + QString device, + int audioFilesCount) { + int rbID = (int)track->id(); + QString title = getText(track->title()); + QString artist = artistsMap[track->artist_id()]; + QString album = albumsMap[track->album_id()]; + QString year = QString::number(track->year()); + QString genre = genresMap[track->genre_id()]; + QString location = devicePath + getText(track->file_path()); + float bpm = (float)track->tempo() / 100.0; + int bitrate = (int)track->bitrate(); + QString key = keysMap[track->key_id()]; + int playtime = (int)track->duration(); + int rating = (int)track->rating(); + QString comment = getText(track->comment()); + QString tracknumber = QString::number(track->track_number()); + QString anlzPath = devicePath + getText(track->analyze_path()); + + query.bindValue(":rb_id", rbID); + query.bindValue(":artist", artist); + query.bindValue(":title", title); + query.bindValue(":album", album); + query.bindValue(":genre", genre); + query.bindValue(":year", year); + query.bindValue(":duration", playtime); + query.bindValue(":location", location); + query.bindValue(":rating", rating); + query.bindValue(":comment", comment); + query.bindValue(":tracknumber", tracknumber); + query.bindValue(":key", key); + query.bindValue(":bpm", bpm); + query.bindValue(":bitrate", bitrate); + query.bindValue(":analyze_path", anlzPath); + query.bindValue(":device", device); - std::ifstream ifs(dbPath, std::ifstream::binary); - kaitai::kstream ks(&ifs); + if (!query.exec()) { + LOG_FAILED_QUERY(query); + } - rekordbox_pdb_t reckordboxDB = rekordbox_pdb_t(&ks); + int trackID = -1; + QSqlQuery finderQuery(database); + finderQuery.prepare("select id from rekordbox_library where rb_id=:rb_id and device=:device"); + finderQuery.bindValue(":rb_id", rbID); + finderQuery.bindValue(":device", device); - // There are other types of tables (eg. COLOR), these are the only ones we are - // interested at the moment. Perhaps when/if - // https://bugs.launchpad.net/mixxx/+bug/1100882 - // is completed, this can be revisted. - // Attempt was made to also recover HISTORY - // playlists (which are found on removable Rekordbox devices), however - // they didn't appear to contain valid row_ref_t structures. - const int totalTables = 8; + if (!finderQuery.exec()) { + LOG_FAILED_QUERY(finderQuery) + << "rbID:" << rbID; + } - rekordbox_pdb_t::page_type_t tableOrder[totalTables] = { - rekordbox_pdb_t::PAGE_TYPE_KEYS, - rekordbox_pdb_t::PAGE_TYPE_GENRES, - rekordbox_pdb_t::PAGE_TYPE_ARTISTS, - rekordbox_pdb_t::PAGE_TYPE_ALBUMS, - rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES, - rekordbox_pdb_t::PAGE_TYPE_TRACKS, - rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE, - rekordbox_pdb_t::PAGE_TYPE_HISTORY - }; - - std::map keysMap; - std::map genresMap; - std::map artistsMap; - std::map albumsMap; - std::map playlistNameMap; - std::map playlistIsFolderMap; - std::map> playlistTreeMap; - std::map> playlistTrackMap; + if (finderQuery.next()) { + trackID = finderQuery.value(finderQuery.record().indexOf("id")).toInt(); + } - bool folderOrPlaylistFound = false; + // Insert into device all tracks playlist + queryInsertIntoDevicePlaylistTracks.bindValue(":track_id", trackID); + queryInsertIntoDevicePlaylistTracks.bindValue(":position", audioFilesCount); - for (int tableOrderIndex = 0; tableOrderIndex < totalTables; tableOrderIndex++) { - bool done = false; + if (!queryInsertIntoDevicePlaylistTracks.exec()) { + LOG_FAILED_QUERY(queryInsertIntoDevicePlaylistTracks) + << "trackID:" << trackID + << "position:" << audioFilesCount; + } +} + +void buildPlaylistTree( + QSqlDatabase &database, + TreeItem *parent, + uint32_t parentID, + QMap &playlistNameMap, + QMap &playlistIsFolderMap, + QMap> &playlistTreeMap, + QMap> &playlistTrackMap, + QString playlistPath, + QString device); + +QString parseDeviceDB(QSqlDatabase &database, TreeItem *deviceItem) { + QString device = deviceItem->getLabel(); + QString devicePath = deviceItem->getData().toList()[0].toString(); + + qDebug() << "parseDeviceDB device: " << device << " devicePath: " << devicePath; + + QString dbPath = devicePath + "/" + kPDBPath; + + if (!QFile(dbPath).exists()) { + return devicePath; + } + + //Give thread a low priority + QThread *thisThread = QThread::currentThread(); + thisThread->setPriority(QThread::LowPriority); + + ScopedTransaction transaction(database); + + QSqlQuery query(database); + query.prepare( + "INSERT INTO rekordbox_library (rb_id, artist, title, album, year," + "genre,comment,tracknumber,bpm, bitrate,duration, location," + "rating,key,analyze_path,device) VALUES (:rb_id, :artist, :title, :album, :year,:genre," + ":comment, :tracknumber,:bpm, :bitrate,:duration, :location," + ":rating,:key,:analyze_path,:device)"); + + int audioFilesCount = 0; + + // Create a playlist for all the tracks on a device + int playlistID = createDevicePLaylist(database, devicePath); + + QSqlQuery queryInsertIntoDevicePlaylistTracks(database); + queryInsertIntoDevicePlaylistTracks.prepare( + "INSERT INTO rekordbox_playlist_tracks (playlist_id, track_id, position) " + "VALUES (:playlist_id, :track_id, :position)"); + + queryInsertIntoDevicePlaylistTracks.bindValue(":playlist_id", playlistID); + + std::ifstream ifs(dbPath.toStdString(), std::ifstream::binary); + kaitai::kstream ks(&ifs); + + rekordbox_pdb_t reckordboxDB = rekordbox_pdb_t(&ks); + + // There are other types of tables (eg. COLOR), these are the only ones we are + // interested at the moment. Perhaps when/if + // https://bugs.launchpad.net/mixxx/+bug/1100882 + // is completed, this can be revisted. + // Attempt was made to also recover HISTORY + // playlists (which are found on removable Rekordbox devices), however + // they didn't appear to contain valid row_ref_t structures. + const int totalTables = 8; + + rekordbox_pdb_t::page_type_t tableOrder[totalTables] = { + rekordbox_pdb_t::PAGE_TYPE_KEYS, + rekordbox_pdb_t::PAGE_TYPE_GENRES, + rekordbox_pdb_t::PAGE_TYPE_ARTISTS, + rekordbox_pdb_t::PAGE_TYPE_ALBUMS, + rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES, + rekordbox_pdb_t::PAGE_TYPE_TRACKS, + rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE, + rekordbox_pdb_t::PAGE_TYPE_HISTORY}; + + QMap keysMap; + QMap genresMap; + QMap artistsMap; + QMap albumsMap; + QMap playlistNameMap; + QMap playlistIsFolderMap; + QMap> playlistTreeMap; + QMap> playlistTrackMap; + + bool folderOrPlaylistFound = false; + + for (int tableOrderIndex = 0; tableOrderIndex < totalTables; tableOrderIndex++) { + bool done = false; for ( - std::vector::iterator table = reckordboxDB.tables()->begin(); - table != reckordboxDB.tables()->end(); ++table - ) { + std::vector::iterator table = reckordboxDB.tables()->begin(); + table != reckordboxDB.tables()->end(); + ++table) { if ((*table)->type() == tableOrder[tableOrderIndex]) { - if (done) break; + if (done) + break; uint16_t lastIndex = (*table)->last_page()->index(); rekordbox_pdb_t::page_ref_t *currentRef = (*table)->first_page(); @@ -509,135 +387,66 @@ QString RekordboxFeature::parseDeviceDB(TreeItem *deviceItem) { if (page->is_data_page()) { for ( - std::vector::iterator rowGroup = page->row_groups()->begin(); - rowGroup != page->row_groups()->end(); ++rowGroup - ) { + std::vector::iterator rowGroup = page->row_groups()->begin(); + rowGroup != page->row_groups()->end(); + ++rowGroup) { for ( - std::vector::iterator rowRef = (*rowGroup)->rows()->begin(); - rowRef != (*rowGroup)->rows()->end(); ++rowRef - ) { + std::vector::iterator rowRef = (*rowGroup)->rows()->begin(); + rowRef != (*rowGroup)->rows()->end(); + ++rowRef) { if ((*rowRef)->present()) { switch (tableOrder[tableOrderIndex]) { - case rekordbox_pdb_t::PAGE_TYPE_KEYS: { - // Key found, update map - rekordbox_pdb_t::key_row_t *key = + case rekordbox_pdb_t::PAGE_TYPE_KEYS: { + // Key found, update map + rekordbox_pdb_t::key_row_t *key = (rekordbox_pdb_t::key_row_t *)(*rowRef)->body(); - keysMap[key->id()] = getText(key->name()); - } - break; - case rekordbox_pdb_t::PAGE_TYPE_GENRES: { - // Genre found, update map - rekordbox_pdb_t::genre_row_t *genre = + keysMap[key->id()] = getText(key->name()); + } break; + case rekordbox_pdb_t::PAGE_TYPE_GENRES: { + // Genre found, update map + rekordbox_pdb_t::genre_row_t *genre = (rekordbox_pdb_t::genre_row_t *)(*rowRef)->body(); - genresMap[genre->id()] = getText(genre->name()); - } - break; - case rekordbox_pdb_t::PAGE_TYPE_ARTISTS: { - // Artist found, update map - rekordbox_pdb_t::artist_row_t *artist = + genresMap[genre->id()] = getText(genre->name()); + } break; + case rekordbox_pdb_t::PAGE_TYPE_ARTISTS: { + // Artist found, update map + rekordbox_pdb_t::artist_row_t *artist = (rekordbox_pdb_t::artist_row_t *)(*rowRef)->body(); - artistsMap[artist->id()] = getText(artist->name()); - } - break; - case rekordbox_pdb_t::PAGE_TYPE_ALBUMS: { - // Album found, update map - rekordbox_pdb_t::album_row_t *album = + artistsMap[artist->id()] = getText(artist->name()); + } break; + case rekordbox_pdb_t::PAGE_TYPE_ALBUMS: { + // Album found, update map + rekordbox_pdb_t::album_row_t *album = (rekordbox_pdb_t::album_row_t *)(*rowRef)->body(); - albumsMap[album->id()] = getText(album->name()); - } - break; - case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES: { - // Playlist to track mapping found, update map - rekordbox_pdb_t::playlist_entry_row_t *playlistEntry = + albumsMap[album->id()] = getText(album->name()); + } break; + case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES: { + // Playlist to track mapping found, update map + rekordbox_pdb_t::playlist_entry_row_t *playlistEntry = (rekordbox_pdb_t::playlist_entry_row_t *)(*rowRef)->body(); - playlistTrackMap[playlistEntry->playlist_id()][playlistEntry->entry_index()] = + playlistTrackMap[playlistEntry->playlist_id()][playlistEntry->entry_index()] = playlistEntry->track_id(); - } - break; - case rekordbox_pdb_t::PAGE_TYPE_TRACKS: { - // Track found, insert into database - rekordbox_pdb_t::track_row_t *track = (rekordbox_pdb_t::track_row_t *)(*rowRef)->body(); - - int rbID = (int)track->id(); - QString title = QString::fromStdString(getText(track->title())); - QString artist = QString::fromStdString(artistsMap[track->artist_id()]); - QString album = QString::fromStdString(albumsMap[track->album_id()]); - QString year = QString::number(track->year()); - QString genre = QString::fromStdString(genresMap[track->genre_id()]); - QString location = QString::fromStdString(pathBase + getText(track->file_path())); - float bpm = (float)track->tempo() / 100.0; - int bitrate = (int)track->bitrate(); - QString key = QString::fromStdString(keysMap[track->key_id()]); - int playtime = (int)track->duration(); - int rating = (int)track->rating(); - QString comment = QString::fromStdString(getText(track->comment())); - QString tracknumber = QString::number(track->track_number()); - QString anlzPath = QString::fromStdString(pathBase + getText(track->analyze_path())); - - query.bindValue(":rb_id", rbID); - query.bindValue(":artist", artist); - query.bindValue(":title", title); - query.bindValue(":album", album); - query.bindValue(":genre", genre); - query.bindValue(":year", year); - query.bindValue(":duration", playtime); - query.bindValue(":location", location); - query.bindValue(":rating", rating); - query.bindValue(":comment", comment); - query.bindValue(":tracknumber", tracknumber); - query.bindValue(":key", key); - query.bindValue(":bpm", bpm); - query.bindValue(":bitrate", bitrate); - query.bindValue(":analyze_path", anlzPath); - query.bindValue(":device", device); - - if (!query.exec()) { - LOG_FAILED_QUERY(query); - } - - int trackID = -1; - QSqlQuery finderQuery(m_database); - finderQuery.prepare("select id from rekordbox_library where rb_id=:rb_id and device=:device"); - finderQuery.bindValue(":rb_id", rbID); - finderQuery.bindValue(":device", device); - - if (!finderQuery.exec()) { - LOG_FAILED_QUERY(finderQuery) - << "rbID:" << rbID; - } - - if (finderQuery.next()) { - trackID = finderQuery.value(finderQuery.record().indexOf("id")).toInt(); - } - - // Insert into device all tracks playlist - queryInsertIntoDevicePlaylistTracks.bindValue(":track_id", trackID); - queryInsertIntoDevicePlaylistTracks.bindValue(":position", audioFilesCount); - - if (!queryInsertIntoDevicePlaylistTracks.exec()) { - LOG_FAILED_QUERY(queryInsertIntoDevicePlaylistTracks) - << "device playlistID:" << playlistID - << "trackID:" << trackID - << "position:" << audioFilesCount; - } - - audioFilesCount++; - } - break; - case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE: { - // Playlist tree node found, update map - rekordbox_pdb_t::playlist_tree_row_t *playlistTree = + } break; + case rekordbox_pdb_t::PAGE_TYPE_TRACKS: { + // Track found, insert into database + insertTrack( + database, (rekordbox_pdb_t::track_row_t *)(*rowRef)->body(), query, queryInsertIntoDevicePlaylistTracks, artistsMap, albumsMap, genresMap, keysMap, devicePath, device, audioFilesCount); + + audioFilesCount++; + } break; + case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE: { + // Playlist tree node found, update map + rekordbox_pdb_t::playlist_tree_row_t *playlistTree = (rekordbox_pdb_t::playlist_tree_row_t *)(*rowRef)->body(); - playlistNameMap[playlistTree->id()] = getText(playlistTree->name()); - playlistIsFolderMap[playlistTree->id()] = playlistTree->is_folder(); - playlistTreeMap[playlistTree->parent_id()][playlistTree->sort_order()] = playlistTree->id(); + playlistNameMap[playlistTree->id()] = getText(playlistTree->name()); + playlistIsFolderMap[playlistTree->id()] = playlistTree->is_folder(); + playlistTreeMap[playlistTree->parent_id()][playlistTree->sort_order()] = playlistTree->id(); - folderOrPlaylistFound = true; - } - break; - default: - break; + folderOrPlaylistFound = true; + } break; + default: + break; } } } @@ -658,8 +467,7 @@ QString RekordboxFeature::parseDeviceDB(TreeItem *deviceItem) { if (audioFilesCount > 0 || folderOrPlaylistFound) { // If we have found anything, recursively build playlist/folder TreeItem children // for the original device TreeItem - buildPlaylistTree(deviceItem, 0, playlistNameMap, playlistIsFolderMap, - playlistTreeMap, playlistTrackMap, devicePath, device); + buildPlaylistTree(database, deviceItem, 0, playlistNameMap, playlistIsFolderMap, playlistTreeMap, playlistTrackMap, devicePath, device); } qDebug() << "Found: " << audioFilesCount << " audio files in Rekordbox device " << device; @@ -669,78 +477,78 @@ QString RekordboxFeature::parseDeviceDB(TreeItem *deviceItem) { return devicePath; } -void RekordboxFeature::buildPlaylistTree( - TreeItem *parent, - uint32_t parentID, - std::map &playlistNameMap, - std::map &playlistIsFolderMap, - std::map> &playlistTreeMap, - std::map> &playlistTrackMap, - QString playlistPath, - QString device) { - - for (uint32_t childIndex = 0; childIndex < playlistTreeMap[parentID].size(); childIndex++) { +void buildPlaylistTree( + QSqlDatabase &database, + TreeItem *parent, + uint32_t parentID, + QMap &playlistNameMap, + QMap &playlistIsFolderMap, + QMap> &playlistTreeMap, + QMap> &playlistTrackMap, + QString playlistPath, + QString device) { + for (uint32_t childIndex = 0; childIndex < (uint32_t)playlistTreeMap[parentID].size(); childIndex++) { uint32_t childID = playlistTreeMap[parentID][childIndex]; - QString playlistItemName = QString::fromStdString(playlistNameMap[childID]); + QString playlistItemName = playlistNameMap[childID]; + + QString currentPath = playlistPath + kPLaylistPathDelimiter + playlistItemName; - QString delimiter = "-->"; - QString currentPath = playlistPath + delimiter + playlistItemName; - QList data; data << currentPath; - data << IS_NOT_RECORDBOX_DEVICE; + data << IS_NOT_RECORDBOX_DEVICE; TreeItem *child = parent->appendChild(playlistItemName, QVariant(data)); // Create a playlist for this child - QSqlQuery queryInsertIntoPlaylist(m_database); - queryInsertIntoPlaylist.prepare("INSERT INTO rekordbox_playlists (name) " - "VALUES (:name)"); + QSqlQuery queryInsertIntoPlaylist(database); + queryInsertIntoPlaylist.prepare( + "INSERT INTO rekordbox_playlists (name) " + "VALUES (:name)"); queryInsertIntoPlaylist.bindValue(":name", currentPath); if (!queryInsertIntoPlaylist.exec()) { - LOG_FAILED_QUERY(queryInsertIntoPlaylist) - << "currentPath" << currentPath; + LOG_FAILED_QUERY(queryInsertIntoPlaylist) + << "currentPath" << currentPath; return; - } + } - QSqlQuery idQuery(m_database); + QSqlQuery idQuery(database); idQuery.prepare("select id from rekordbox_playlists where name=:path"); idQuery.bindValue(":path", currentPath); if (!idQuery.exec()) { LOG_FAILED_QUERY(idQuery) - << "currentPath" << currentPath; + << "currentPath" << currentPath; return; - } + } int playlistID = -1; while (idQuery.next()) { playlistID = idQuery.value(idQuery.record().indexOf("id")).toInt(); - } + } - QSqlQuery queryInsertIntoPlaylistTracks(m_database); + QSqlQuery queryInsertIntoPlaylistTracks(database); queryInsertIntoPlaylistTracks.prepare( - "INSERT INTO rekordbox_playlist_tracks (playlist_id, track_id, position) " - "VALUES (:playlist_id, :track_id, :position)"); + "INSERT INTO rekordbox_playlist_tracks (playlist_id, track_id, position) " + "VALUES (:playlist_id, :track_id, :position)"); if (playlistTrackMap.count(childID)) { // Add playlist tracks for children - for (uint32_t trackIndex = 1; trackIndex <= playlistTrackMap[childID].size(); trackIndex++) { + for (uint32_t trackIndex = 1; trackIndex <= (uint32_t)playlistTrackMap[childID].size(); trackIndex++) { uint32_t rbTrackID = playlistTrackMap[childID][trackIndex]; int trackID = -1; - QSqlQuery finderQuery(m_database); + QSqlQuery finderQuery(database); finderQuery.prepare("select id from rekordbox_library where rb_id=:rb_id and device=:device"); finderQuery.bindValue(":rb_id", rbTrackID); finderQuery.bindValue(":device", device); if (!finderQuery.exec()) { - LOG_FAILED_QUERY(finderQuery) - << "rbTrackID:" << rbTrackID - << "device:" << device; + LOG_FAILED_QUERY(finderQuery) + << "rbTrackID:" << rbTrackID + << "device:" << device; return; } @@ -754,63 +562,344 @@ void RekordboxFeature::buildPlaylistTree( if (!queryInsertIntoPlaylistTracks.exec()) { LOG_FAILED_QUERY(queryInsertIntoPlaylistTracks) - << "playlistID:" << playlistID - << "trackID:" << trackID - << "trackIndex:" << trackIndex; + << "playlistID:" << playlistID + << "trackID:" << trackID + << "trackIndex:" << trackIndex; return; - } + } } } if (playlistIsFolderMap[childID]) { // If this child is a folder (playlists are only leaf nodes), build playlist tree for it - buildPlaylistTree(child, childID, playlistNameMap, playlistIsFolderMap, - playlistTreeMap, playlistTrackMap, currentPath, device); + buildPlaylistTree(database, child, childID, playlistNameMap, playlistIsFolderMap, playlistTreeMap, playlistTrackMap, currentPath, device); } } -} +} -void RekordboxFeature::clearTable(QString tableName) { - QSqlQuery query(m_database); - query.prepare("delete from " + tableName); +void clearDeviceTables(QSqlDatabase &database, TreeItem *child) { + ScopedTransaction transaction(database); - if (!query.exec()) { - LOG_FAILED_QUERY(query) - << "tableName:" << tableName; - } else { - query.prepare("delete from sqlite_sequence where name='" + tableName + "'"); + int trackID = -1; + int playlistID = -1; + QSqlQuery tracksQuery(database); + tracksQuery.prepare("select id from rekordbox_library where device=:device"); + tracksQuery.bindValue(":device", child->getLabel()); - if (!query.exec()) { - LOG_FAILED_QUERY(query) - << "tableName:" << tableName; - } else { - qDebug() << "Rekordbox table entries of '" << tableName << "' have been cleared."; + QSqlQuery deletePlaylistsQuery(database); + deletePlaylistsQuery.prepare("delete from rekordbox_playlists where id=:id"); + + QSqlQuery deletePlaylistTracksQuery(database); + deletePlaylistTracksQuery.prepare("delete from rekordbox_playlist_tracks where playlist_id=:playlist_id"); + + if (!tracksQuery.exec()) { + LOG_FAILED_QUERY(tracksQuery) + << "device:" << child->getLabel(); + } + + while (tracksQuery.next()) { + trackID = tracksQuery.value(tracksQuery.record().indexOf("id")).toInt(); + + QSqlQuery playlistTracksQuery(database); + playlistTracksQuery.prepare("select playlist_id from rekordbox_playlist_tracks where track_id=:track_id"); + playlistTracksQuery.bindValue(":track_id", trackID); + + if (!playlistTracksQuery.exec()) { + LOG_FAILED_QUERY(playlistTracksQuery) + << "trackID:" << trackID; } + + while (playlistTracksQuery.next()) { + playlistID = playlistTracksQuery.value(playlistTracksQuery.record().indexOf("playlist_id")).toInt(); + + deletePlaylistsQuery.bindValue(":id", playlistID); + + if (!deletePlaylistsQuery.exec()) { + LOG_FAILED_QUERY(deletePlaylistsQuery) + << "playlistID:" << playlistID; + } + + deletePlaylistTracksQuery.bindValue(":playlist_id", playlistID); + + if (!deletePlaylistTracksQuery.exec()) { + LOG_FAILED_QUERY(deletePlaylistTracksQuery) + << "playlistID:" << playlistID; + } + } + } + + QSqlQuery deleteTracksQuery(database); + deleteTracksQuery.prepare("delete from rekordbox_library where device=:device"); + deleteTracksQuery.bindValue(":device", child->getLabel()); + + if (!deleteTracksQuery.exec()) { + LOG_FAILED_QUERY(deleteTracksQuery) + << "device:" << child->getLabel(); + } + + transaction.commit(); +} + +} // anonymous namespace + +RekordboxPlaylistModel::RekordboxPlaylistModel(QObject *parent, + TrackCollection *trackCollection, + QSharedPointer trackSource) + : BaseExternalPlaylistModel(parent, trackCollection, "mixxx.db.model.rekordbox.playlistmodel", "rekordbox_playlists", "rekordbox_playlist_tracks", trackSource) { +} + +TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex &index) const { + qDebug() << "RekordboxTrackModel::getTrack"; + + TrackPointer track = BaseExternalPlaylistModel::getTrack(index); + + // Assume that the key of the file the has been analyzed in Recordbox is correct + // and prevent the AnalyzerKey from re-analyzing. + track->setKeys(KeyFactory::makeBasicKeysFromText(index.sibling( + index.row(), fieldIndex("key")) + .data() + .toString(), + mixxx::track::io::key::USER)); + + return track; +} + +bool RekordboxPlaylistModel::isColumnHiddenByDefault(int column) { + if ( + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ID)) { + return true; + } + return BaseSqlTableModel::isColumnHiddenByDefault(column); +} + +RekordboxFeature::RekordboxFeature(QObject *parent, TrackCollection *trackCollection) + : BaseExternalLibraryFeature(parent, trackCollection), + m_pTrackCollection(trackCollection), + m_icon(":/images/library/ic_library_rekordbox.svg") { + QString tableName = "rekordbox_library"; + QString idColumn = "id"; + QStringList columns; + columns << "id" + << "artist" + << "title" + << "album" + << "year" + << "genre" + << "tracknumber" + << "location" + << "comment" + << "rating" + << "duration" + << "bitrate" + << "bpm" + << "key"; + m_trackSource = QSharedPointer( + new BaseTrackCache(m_pTrackCollection, tableName, idColumn, columns, false)); + QStringList searchColumns; + searchColumns + << "artist" + << "title" + << "album" + << "year" + << "genre" + << "tracknumber" + << "location" + << "comment" + << "duration" + << "bitrate" + << "bpm" + << "key"; + m_trackSource->setSearchColumns(searchColumns); + + m_pRekordboxPlaylistModel = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); + + m_title = tr("Rekordbox"); + + m_database = QSqlDatabase::cloneDatabase(trackCollection->database(), + "REKORDBOX_SCANNER"); + + //Open the database connection in this thread. + if (!m_database.open()) { + qDebug() << "Failed to open database for Rekordbox scanner." + << m_database.lastError(); + } else { + //Clear any previous Rekordbox device entries if they exist + ScopedTransaction transaction(m_database); + clearTable(m_database, "rekordbox_playlist_tracks"); + clearTable(m_database, "rekordbox_library"); + clearTable(m_database, "rekordbox_playlists"); + transaction.commit(); + } + + connect(&m_devicesFutureWatcher, SIGNAL(finished()), this, SLOT(onRekordboxDevicesFound())); + connect(&m_tracksFutureWatcher, SIGNAL(finished()), this, SLOT(onTracksFound())); + // initialize the model + m_childModel.setRootItem(std::make_unique(this)); +} + +RekordboxFeature::~RekordboxFeature() { + m_database.close(); + m_devicesFuture.waitForFinished(); + m_tracksFuture.waitForFinished(); + delete m_pRekordboxPlaylistModel; +} + +void RekordboxFeature::bindWidget(WLibrary *libraryWidget, + KeyboardEventFilter *keyboard) { + Q_UNUSED(keyboard); + WLibraryTextBrowser *edit = new WLibraryTextBrowser(libraryWidget); + edit->setHtml(formatRootViewHtml()); + edit->setOpenLinks(false); + connect(edit, SIGNAL(anchorClicked(const QUrl)), this, SLOT(htmlLinkClicked(const QUrl))); + libraryWidget->registerView("REKORDBOXHOME", edit); +} + +void RekordboxFeature::htmlLinkClicked(const QUrl &link) { + if (QString(link.path()) == "refresh") { + activate(); + } else { + qDebug() << "Unknown link clicked" << link; + } +} + +BaseSqlTableModel *RekordboxFeature::getPlaylistModelForPlaylist(QString playlist) { + RekordboxPlaylistModel *model = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); + model->setPlaylist(playlist); + return model; +} + +QVariant RekordboxFeature::title() { + return m_title; +} + +QIcon RekordboxFeature::getIcon() { + return m_icon; +} + +bool RekordboxFeature::isSupported() { + return true; +} + +TreeItemModel *RekordboxFeature::getChildModel() { + return &m_childModel; +} + +QString RekordboxFeature::formatRootViewHtml() const { + QString title = tr("Rekordbox"); + QString summary = tr("Reads playlists and folders from Rekordbox prepared removable devices."); + + QString html; + QString refreshLink = tr("Check for attached Rekordbox devices (refresh)"); + html.append(QString("

%1

").arg(title)); + html.append(QString("

%1

").arg(summary)); + + //Colorize links in lighter blue, instead of QT default dark blue. + //Links are still different from regular text, but readable on dark/light backgrounds. + //https://bugs.launchpad.net/mixxx/+bug/1744816 + html.append(QString("%1") + .arg(refreshLink)); + return html; +} + +void RekordboxFeature::refreshLibraryModels() { +} + +void RekordboxFeature::activate() { + qDebug() << "RekordboxFeature::activate()"; + + // Usually the maximum number of threads + // is > 2 depending on the CPU cores + // Unfortunately, within VirtualBox + // the maximum number of allowed threads + // is 1 at all times We'll need to increase + // the number to > 1, otherwise importing the music collection + // takes place when the GUI threads terminates, i.e., on + // Mixxx shutdown. + QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 + // Let a worker thread do the XML parsing + m_devicesFuture = QtConcurrent::run(findRekordboxDevices, this); + m_devicesFutureWatcher.setFuture(m_devicesFuture); + m_title = tr("(loading) Rekordbox"); + //calls a slot in the sidebar model such that 'Rekordbox (isLoading)' is displayed. + emit(featureIsLoading(this, true)); + + emit(enableCoverArtDisplay(true)); + emit(switchToView("REKORDBOXHOME")); +} + +void RekordboxFeature::activateChild(const QModelIndex &index) { + if (!index.isValid()) + return; + + //access underlying TreeItem object + TreeItem *item = static_cast(index.internalPointer()); + if (!(item && item->getData().isValid())) { + return; + } + + // TreeItem list data holds 2 values in a QList and have different meanings. + // If the 2nd QList element IS_RECORDBOX_DEVICE, the 1st element is the + // filesystem device path, and the parseDeviceDB concurrent thread to parse + // the Rekcordbox database is initiated. If the 2nd element is + // IS_NOT_RECORDBOX_DEVICE, the 1st element is the playlist path and it is + // activated. + QList data = item->getData().toList(); + QString playlist = data[0].toString(); + bool doParseDeviceDB = data[1].toString() == IS_RECORDBOX_DEVICE; + + qDebug() << "RekordboxFeature::activateChild " << item->getLabel() + << " playlist: " << playlist << " doParseDeviceDB: " << doParseDeviceDB; + + if (doParseDeviceDB) { + qDebug() << "Parse Rekordbox Device DB: " << playlist; + + // Usually the maximum number of threads + // is > 2 depending on the CPU cores + // Unfortunately, within VirtualBox + // the maximum number of allowed threads + // is 1 at all times We'll need to increase + // the number to > 1, otherwise importing the music collection + // takes place when the GUI threads terminates, i.e., on + // Mixxx shutdown. + QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 + // Let a worker thread do the XML parsing + m_tracksFuture = QtConcurrent::run(parseDeviceDB, m_database, item); + m_tracksFutureWatcher.setFuture(m_tracksFuture); + + // This device is now a playlist element, future activations should treat is + // as such + data[1] = QVariant(IS_NOT_RECORDBOX_DEVICE); + item->setData(QVariant(data)); + } else { + qDebug() << "Activate Rekordbox Playlist: " << playlist; + m_pRekordboxPlaylistModel->setPlaylist(playlist); + emit(showTrackModel(m_pRekordboxPlaylistModel)); } } void RekordboxFeature::onRekordboxDevicesFound() { - QList foundDevices = m_devicesFuture.result(); + QList foundDevices = m_devicesFuture.result(); TreeItem *root = m_childModel.getRootItem(); if (foundDevices.size() == 0) { // No Rekordbox devices found - ScopedTransaction transaction(m_database); - clearTable("rekordbox_playlist_tracks"); - clearTable("rekordbox_library"); - clearTable("rekordbox_playlists"); - transaction.commit(); + ScopedTransaction transaction(m_database); + clearTable(m_database, "rekordbox_playlist_tracks"); + clearTable(m_database, "rekordbox_library"); + clearTable(m_database, "rekordbox_playlists"); + transaction.commit(); if (root->childRows() > 0) { // Devices have since been unmounted - m_childModel.removeRows(0, root->childRows()); - } + m_childModel.removeRows(0, root->childRows()); + } } else { for (int deviceIndex = 0; deviceIndex < root->childRows(); deviceIndex++) { TreeItem *child = root->child(deviceIndex); bool removeChild = true; - + for (int foundDeviceIndex = 0; foundDeviceIndex < foundDevices.size(); foundDeviceIndex++) { TreeItem *deviceFound = foundDevices[foundDeviceIndex]; @@ -820,76 +909,15 @@ void RekordboxFeature::onRekordboxDevicesFound() { } } - if (removeChild) { - // Device has since been unmounted, cleanup DB - ScopedTransaction transaction(m_database); - - int trackID = -1; - int playlistID = -1; - QSqlQuery tracksQuery(m_database); - tracksQuery.prepare("select id from rekordbox_library where device=:device"); - tracksQuery.bindValue(":device", child->getLabel()); - - QSqlQuery deletePlaylistsQuery(m_database); - deletePlaylistsQuery.prepare("delete from rekordbox_playlists where id=:id"); - - QSqlQuery deletePlaylistTracksQuery(m_database); - deletePlaylistTracksQuery.prepare("delete from rekordbox_playlist_tracks where playlist_id=:playlist_id"); - - if (!tracksQuery.exec()) { - LOG_FAILED_QUERY(tracksQuery) - << "device:" << child->getLabel(); - } - - while (tracksQuery.next()) { - trackID = tracksQuery.value(tracksQuery.record().indexOf("id")).toInt(); - - QSqlQuery playlistTracksQuery(m_database); - playlistTracksQuery.prepare("select playlist_id from rekordbox_playlist_tracks where track_id=:track_id"); - playlistTracksQuery.bindValue(":track_id", trackID); - - if (!playlistTracksQuery.exec()) { - LOG_FAILED_QUERY(playlistTracksQuery) - << "trackID:" << trackID; - } - - while (playlistTracksQuery.next()) { - playlistID = playlistTracksQuery.value(playlistTracksQuery.record().indexOf("playlist_id")).toInt(); - - deletePlaylistsQuery.bindValue(":id", playlistID); - - if (!deletePlaylistsQuery.exec()) { - LOG_FAILED_QUERY(deletePlaylistsQuery) - << "playlistID:" << playlistID; - } - - deletePlaylistTracksQuery.bindValue(":playlist_id", playlistID); - - if (!deletePlaylistTracksQuery.exec()) { - LOG_FAILED_QUERY(deletePlaylistTracksQuery) - << "playlistID:" << playlistID; - } - - } - - } - - QSqlQuery deleteTracksQuery(m_database); - deleteTracksQuery.prepare("delete from rekordbox_library where device=:device"); - deleteTracksQuery.bindValue(":device", child->getLabel()); - - if (!deleteTracksQuery.exec()) { - LOG_FAILED_QUERY(deleteTracksQuery) - << "device:" << child->getLabel(); - } - - transaction.commit(); + if (removeChild) { + // Device has since been unmounted, cleanup DB + clearDeviceTables(m_database, child); m_childModel.removeRows(deviceIndex, 1); } } - QList childrenToAdd; + QList childrenToAdd; for (int foundDeviceIndex = 0; foundDeviceIndex < foundDevices.size(); foundDeviceIndex++) { TreeItem *deviceFound = foundDevices[foundDeviceIndex]; @@ -928,5 +956,5 @@ void RekordboxFeature::onTracksFound() { qDebug() << "Show Rekordbox Device Playlist: " << devicePlaylist; m_pRekordboxPlaylistModel->setPlaylist(devicePlaylist); - emit(showTrackModel(m_pRekordboxPlaylistModel)); + emit(showTrackModel(m_pRekordboxPlaylistModel)); } diff --git a/src/library/rekordbox/rekordboxfeature.h b/src/library/rekordbox/rekordboxfeature.h old mode 100755 new mode 100644 index 8ece0c91b3e..251f33c34e1 --- a/src/library/rekordbox/rekordboxfeature.h +++ b/src/library/rekordbox/rekordboxfeature.h @@ -1,7 +1,7 @@ // rekordboxfeature.h // Created 05/24/2019 by Evan Dekker -// This feature reads tracks, playlists and folders from removable Recordbox +// This feature reads tracks, playlists and folders from removable Recordbox // prepared devices (USB drives, etc), by parsing the binary *.PDB files // stored on each removable device. It does not read the locally stored // Rekordbox database (Collection). @@ -28,32 +28,27 @@ #ifndef REKORDBOX_FEATURE_H #define REKORDBOX_FEATURE_H -#include -#include -#include #include -#include #include +#include +#include +#include #include #include "library/baseexternallibraryfeature.h" -#include "library/baseexternaltrackmodel.h" #include "library/baseexternalplaylistmodel.h" +#include "library/baseexternaltrackmodel.h" #include "library/treeitemmodel.h" -#include "library/rekordbox/rekordbox_pdb.h" - -#define PDB_PATH "PIONEER/rekordbox/export.pdb" - class TrackCollection; class BaseExternalPlaylistModel; class RekordboxPlaylistModel : public BaseExternalPlaylistModel { public: RekordboxPlaylistModel(QObject* parent, - TrackCollection* pTrackCollection, - QSharedPointer trackSource); + TrackCollection* pTrackCollection, + QSharedPointer trackSource); TrackPointer getTrack(const QModelIndex& index) const override; virtual bool isColumnHiddenByDefault(int column); }; @@ -68,7 +63,7 @@ class RekordboxFeature : public BaseExternalLibraryFeature { QIcon getIcon(); static bool isSupported(); void bindWidget(WLibrary* libraryWidget, - KeyboardEventFilter* keyboard) override; + KeyboardEventFilter* keyboard) override; TreeItemModel* getChildModel(); @@ -80,23 +75,11 @@ class RekordboxFeature : public BaseExternalLibraryFeature { void onTracksFound(); private slots: - void htmlLinkClicked(const QUrl& link); + void htmlLinkClicked(const QUrl& link); private: - QString formatRootViewHtml() const; - virtual BaseSqlTableModel* getPlaylistModelForPlaylist(QString playlist); - QList findRekordboxDevices(); - QString parseDeviceDB(TreeItem *deviceItem); - void buildPlaylistTree(TreeItem *parent, uint32_t parentID, - std::map &playlistNameMap, - std::map &playlistIsFolderMap, - std::map> &playlistTreeMap, - std::map> &playlistTrackMap, - QString playlistPath, QString device); - void clearTable(QString table_name); - template - inline bool instanceof(const T *ptr) {return dynamic_cast(ptr) != nullptr;} - std::string getText(rekordbox_pdb_t::device_sql_string_t *deviceString); + QString formatRootViewHtml() const; + virtual BaseSqlTableModel* getPlaylistModelForPlaylist(QString playlist); TreeItemModel m_childModel; TrackCollection* m_pTrackCollection; From 7503f98a06de5d7f84e1059c7fa2967197baa6a4 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Sun, 26 May 2019 16:30:56 +1000 Subject: [PATCH 05/36] Clang-format --- 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 e02cb5f46f1..2858deab1fc 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -7,8 +7,8 @@ #include #include -#include "library/rekordbox/rekordboxfeature.h" #include "library/rekordbox/rekordbox_pdb.h" +#include "library/rekordbox/rekordboxfeature.h" #include "library/librarytablemodel.h" #include "library/missingtablemodel.h" From 5343d44748055960f6669f28563a8ad266d6926a Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Thu, 1 Aug 2019 15:40:24 +1000 Subject: [PATCH 06/36] Addressed minor remarks --- res/schema.xml | 18 +- src/library/library.cpp | 214 +++++++++------------ src/library/rekordbox/rekordboxfeature.cpp | 29 ++- src/library/rekordbox/rekordboxfeature.h | 6 +- 4 files changed, 121 insertions(+), 146 deletions(-) diff --git a/res/schema.xml b/res/schema.xml index a934e7ff52f..79f64aa3ec5 100644 --- a/res/schema.xml +++ b/res/schema.xml @@ -455,22 +455,22 @@ METADATA CREATE TABLE IF NOT EXISTS rekordbox_library ( id INTEGER primary key AUTOINCREMENT, rb_id integer, - artist varchar(48), title varchar(48), - album varchar(48), year varchar(16), - genre varchar(32), tracknumber varchar(3), - location varchar(512) UNIQUE, - comment varchar(60), + artist text, title text, + album text, year text, + genre text, tracknumber text, + location text UNIQUE, + comment text, duration integer, bitrate integer, bpm float, - key varchar(6), + key text, rating integer, - analyze_path varchar(512) UNIQUE, - device varchar(48) + analyze_path text UNIQUE, + device text ); CREATE TABLE IF NOT EXISTS rekordbox_playlists ( id INTEGER primary key, - name varchar(100) UNIQUE + name text UNIQUE ); CREATE TABLE IF NOT EXISTS rekordbox_playlist_tracks ( id INTEGER primary key AUTOINCREMENT, diff --git a/src/library/library.cpp b/src/library/library.cpp index 66d0f3aad0a..436b61a4768 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -1,47 +1,46 @@ // library.cpp // Created 8/23/2009 by RJ Ryan (rryan@mit.edu) +#include #include #include #include -#include #include "database/mixxxdb.h" -#include "mixer/playermanager.h" +#include "library/autodj/autodjfeature.h" +#include "library/banshee/bansheefeature.h" +#include "library/browse/browsefeature.h" +#include "library/crate/cratefeature.h" +#include "library/itunes/itunesfeature.h" #include "library/library.h" #include "library/library_preferences.h" +#include "library/librarycontrol.h" #include "library/libraryfeature.h" #include "library/librarytablemodel.h" +#include "library/mixxxlibraryfeature.h" +#include "library/playlistfeature.h" +#include "library/recording/recordingfeature.h" +#include "library/rekordbox/rekordboxfeature.h" +#include "library/rhythmbox/rhythmboxfeature.h" +#include "library/setlogfeature.h" #include "library/sidebarmodel.h" #include "library/trackcollection.h" #include "library/trackmodel.h" -#include "library/browse/browsefeature.h" -#include "library/crate/cratefeature.h" -#include "library/rhythmbox/rhythmboxfeature.h" -#include "library/banshee/bansheefeature.h" -#include "library/rekordbox/rekordboxfeature.h" -#include "library/recording/recordingfeature.h" -#include "library/itunes/itunesfeature.h" -#include "library/mixxxlibraryfeature.h" -#include "library/autodj/autodjfeature.h" -#include "library/playlistfeature.h" #include "library/traktor/traktorfeature.h" -#include "library/librarycontrol.h" -#include "library/setlogfeature.h" +#include "mixer/playermanager.h" +#include "util/assert.h" #include "util/db/dbconnectionpooled.h" -#include "util/sandbox.h" #include "util/logger.h" -#include "util/assert.h" +#include "util/sandbox.h" -#include "widget/wtracktableview.h" #include "widget/wlibrary.h" #include "widget/wlibrarysidebar.h" #include "widget/wsearchlineedit.h" +#include "widget/wtracktableview.h" #include "controllers/keyboard/keyboardeventfilter.h" - namespace { const mixxx::Logger kLogger("Library"); @@ -67,17 +66,16 @@ Library::Library( mixxx::DbConnectionPoolPtr pDbConnectionPool, PlayerManager* pPlayerManager, RecordingManager* pRecordingManager) - : m_pConfig(pConfig), - m_pDbConnectionPool(pDbConnectionPool), - m_pSidebarModel(new SidebarModel(parent)), - m_pTrackCollection(new TrackCollection(pConfig)), - m_pLibraryControl(new LibraryControl(this)), - m_pMixxxLibraryFeature(nullptr), - m_pPlaylistFeature(nullptr), - m_pCrateFeature(nullptr), - m_pAnalysisFeature(nullptr), - m_scanner(pDbConnectionPool, m_pTrackCollection, pConfig) { - + : m_pConfig(pConfig), + m_pDbConnectionPool(pDbConnectionPool), + m_pSidebarModel(new SidebarModel(parent)), + m_pTrackCollection(new TrackCollection(pConfig)), + m_pLibraryControl(new LibraryControl(this)), + m_pMixxxLibraryFeature(nullptr), + m_pPlaylistFeature(nullptr), + m_pCrateFeature(nullptr), + m_pAnalysisFeature(nullptr), + m_scanner(pDbConnectionPool, m_pTrackCollection, pConfig) { QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); // TODO(XXX): Add a checkbox in the library preferences for checking @@ -96,17 +94,14 @@ Library::Library( m_pKeyNotation.reset(new ControlObject(ConfigKey(kConfigGroup, "key_notation"))); - connect(&m_scanner, SIGNAL(scanStarted()), - this, SIGNAL(scanStarted())); - connect(&m_scanner, SIGNAL(scanFinished()), - this, SIGNAL(scanFinished())); + connect(&m_scanner, SIGNAL(scanStarted()), this, SIGNAL(scanStarted())); + connect(&m_scanner, SIGNAL(scanFinished()), this, SIGNAL(scanFinished())); // Refresh the library models when the library (re)scan is finished. - connect(&m_scanner, SIGNAL(scanFinished()), - this, SLOT(slotRefreshLibraryModels())); + connect(&m_scanner, SIGNAL(scanFinished()), this, SLOT(slotRefreshLibraryModels())); // TODO(rryan) -- turn this construction / adding of features into a static // method or something -- CreateDefaultLibrary - m_pMixxxLibraryFeature = new MixxxLibraryFeature(this, m_pTrackCollection,m_pConfig); + m_pMixxxLibraryFeature = new MixxxLibraryFeature(this, m_pTrackCollection, m_pConfig); addFeature(m_pMixxxLibraryFeature); addFeature(new AutoDJFeature(this, pConfig, pPlayerManager, m_pTrackCollection)); @@ -115,58 +110,51 @@ Library::Library( m_pCrateFeature = new CrateFeature(this, m_pTrackCollection, m_pConfig); addFeature(m_pCrateFeature); BrowseFeature* browseFeature = new BrowseFeature( - this, pConfig, m_pTrackCollection, pRecordingManager); - connect(browseFeature, SIGNAL(scanLibrary()), - &m_scanner, SLOT(scan())); - connect(&m_scanner, SIGNAL(scanStarted()), - browseFeature, SLOT(slotLibraryScanStarted())); - connect(&m_scanner, SIGNAL(scanFinished()), - browseFeature, SLOT(slotLibraryScanFinished())); + this, pConfig, m_pTrackCollection, pRecordingManager); + connect(browseFeature, SIGNAL(scanLibrary()), &m_scanner, SLOT(scan())); + connect(&m_scanner, SIGNAL(scanStarted()), browseFeature, SLOT(slotLibraryScanStarted())); + connect(&m_scanner, SIGNAL(scanFinished()), browseFeature, SLOT(slotLibraryScanFinished())); addFeature(browseFeature); addFeature(new RecordingFeature(this, pConfig, m_pTrackCollection, pRecordingManager)); addFeature(new SetlogFeature(this, pConfig, m_pTrackCollection)); m_pAnalysisFeature = new AnalysisFeature(this, pConfig); - connect(m_pPlaylistFeature, &PlaylistFeature::analyzeTracks, - m_pAnalysisFeature, &AnalysisFeature::analyzeTracks); - connect(m_pCrateFeature, &CrateFeature::analyzeTracks, - m_pAnalysisFeature, &AnalysisFeature::analyzeTracks); + connect(m_pPlaylistFeature, &PlaylistFeature::analyzeTracks, m_pAnalysisFeature, &AnalysisFeature::analyzeTracks); + connect(m_pCrateFeature, &CrateFeature::analyzeTracks, m_pAnalysisFeature, &AnalysisFeature::analyzeTracks); addFeature(m_pAnalysisFeature); // Suspend a batch analysis while an ad-hoc analysis of // loaded tracks is in progress and resume it afterwards. - connect(pPlayerManager, &PlayerManager::trackAnalyzerProgress, - this, &Library::onPlayerManagerTrackAnalyzerProgress); - connect(pPlayerManager, &PlayerManager::trackAnalyzerIdle, - this, &Library::onPlayerManagerTrackAnalyzerIdle); + connect(pPlayerManager, &PlayerManager::trackAnalyzerProgress, this, &Library::onPlayerManagerTrackAnalyzerProgress); + connect(pPlayerManager, &PlayerManager::trackAnalyzerIdle, this, &Library::onPlayerManagerTrackAnalyzerIdle); //iTunes and Rhythmbox should be last until we no longer have an obnoxious //messagebox popup when you select them. (This forces you to reach for your //mouse or keyboard if you're using MIDI control and you scroll through them...) if (RhythmboxFeature::isSupported() && - pConfig->getValue(ConfigKey(kConfigGroup,"ShowRhythmboxLibrary"), true)) { + pConfig->getValue(ConfigKey(kConfigGroup, "ShowRhythmboxLibrary"), true)) { addFeature(new RhythmboxFeature(this, m_pTrackCollection)); } - if (pConfig->getValue(ConfigKey(kConfigGroup,"ShowBansheeLibrary"), true)) { + if (pConfig->getValue(ConfigKey(kConfigGroup, "ShowBansheeLibrary"), true)) { BansheeFeature::prepareDbPath(pConfig); if (BansheeFeature::isSupported()) { addFeature(new BansheeFeature(this, m_pTrackCollection, pConfig)); } } if (ITunesFeature::isSupported() && - pConfig->getValue(ConfigKey(kConfigGroup,"ShowITunesLibrary"), true)) { + pConfig->getValue(ConfigKey(kConfigGroup, "ShowITunesLibrary"), true)) { addFeature(new ITunesFeature(this, m_pTrackCollection)); } if (TraktorFeature::isSupported() && - pConfig->getValue(ConfigKey(kConfigGroup,"ShowTraktorLibrary"), true)) { + pConfig->getValue(ConfigKey(kConfigGroup, "ShowTraktorLibrary"), true)) { addFeature(new TraktorFeature(this, m_pTrackCollection)); } - // Rekordbox feature added persistently as the only way to enable it to + // TODO(XXX) Rekordbox feature added persistently as the only way to enable it to // dynamically appear/disappear when correctly prepared removable devices // are mounted/unmounted would be to have some form of timed thread to check // periodically. Not ideal perfomance wise. - addFeature(new RekordboxFeature(this, m_pTrackCollection)); + addFeature(new RekordboxFeature(this, m_pTrackCollection)); // On startup we need to check if all of the user's library folders are // accessible to us. If the user is using a database from <1.12.0 with @@ -199,7 +187,7 @@ Library::~Library() { delete m_pSidebarModel; QMutableListIterator features_it(m_features); - while(features_it.hasNext()) { + while (features_it.hasNext()) { LibraryFeature* feature = features_it.next(); features_it.remove(); delete feature; @@ -231,54 +219,40 @@ void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { // Setup the sources view pSidebarWidget->setModel(m_pSidebarModel); - connect(m_pSidebarModel, SIGNAL(selectIndex(const QModelIndex&)), - pSidebarWidget, SLOT(selectIndex(const QModelIndex&))); - connect(pSidebarWidget, SIGNAL(pressed(const QModelIndex&)), - m_pSidebarModel, SLOT(pressed(const QModelIndex&))); - connect(pSidebarWidget, SIGNAL(clicked(const QModelIndex&)), - m_pSidebarModel, SLOT(clicked(const QModelIndex&))); + connect(m_pSidebarModel, SIGNAL(selectIndex(const QModelIndex&)), pSidebarWidget, SLOT(selectIndex(const QModelIndex&))); + connect(pSidebarWidget, SIGNAL(pressed(const QModelIndex&)), m_pSidebarModel, SLOT(pressed(const QModelIndex&))); + connect(pSidebarWidget, SIGNAL(clicked(const QModelIndex&)), m_pSidebarModel, SLOT(clicked(const QModelIndex&))); // Lazy model: Let triangle symbol increment the model - connect(pSidebarWidget, SIGNAL(expanded(const QModelIndex&)), - m_pSidebarModel, SLOT(doubleClicked(const QModelIndex&))); + connect(pSidebarWidget, SIGNAL(expanded(const QModelIndex&)), m_pSidebarModel, SLOT(doubleClicked(const QModelIndex&))); - connect(pSidebarWidget, SIGNAL(rightClicked(const QPoint&, const QModelIndex&)), - m_pSidebarModel, SLOT(rightClicked(const QPoint&, const QModelIndex&))); + connect(pSidebarWidget, SIGNAL(rightClicked(const QPoint&, const QModelIndex&)), m_pSidebarModel, SLOT(rightClicked(const QPoint&, const QModelIndex&))); pSidebarWidget->slotSetFont(m_trackTableFont); - connect(this, SIGNAL(setTrackTableFont(QFont)), - pSidebarWidget, SLOT(slotSetFont(QFont))); + connect(this, SIGNAL(setTrackTableFont(QFont)), pSidebarWidget, SLOT(slotSetFont(QFont))); } void Library::bindWidget(WLibrary* pLibraryWidget, - KeyboardEventFilter* pKeyboard) { + KeyboardEventFilter* pKeyboard) { WTrackTableView* pTrackTableView = new WTrackTableView(pLibraryWidget, m_pConfig, m_pTrackCollection); pTrackTableView->installEventFilter(pKeyboard); - connect(this, SIGNAL(showTrackModel(QAbstractItemModel*)), - pTrackTableView, SLOT(loadTrackModel(QAbstractItemModel*))); - connect(pTrackTableView, SIGNAL(loadTrack(TrackPointer)), - this, SLOT(slotLoadTrack(TrackPointer))); - connect(pTrackTableView, SIGNAL(loadTrackToPlayer(TrackPointer, QString, bool)), - this, SLOT(slotLoadTrackToPlayer(TrackPointer, QString, bool))); + connect(this, SIGNAL(showTrackModel(QAbstractItemModel*)), pTrackTableView, SLOT(loadTrackModel(QAbstractItemModel*))); + connect(pTrackTableView, SIGNAL(loadTrack(TrackPointer)), this, SLOT(slotLoadTrack(TrackPointer))); + connect(pTrackTableView, SIGNAL(loadTrackToPlayer(TrackPointer, QString, bool)), this, SLOT(slotLoadTrackToPlayer(TrackPointer, QString, bool))); pLibraryWidget->registerView(m_sTrackViewName, pTrackTableView); - connect(this, SIGNAL(switchToView(const QString&)), - pLibraryWidget, SLOT(switchToView(const QString&))); + connect(this, SIGNAL(switchToView(const QString&)), pLibraryWidget, SLOT(switchToView(const QString&))); - connect(pTrackTableView, SIGNAL(trackSelected(TrackPointer)), - this, SIGNAL(trackSelected(TrackPointer))); + connect(pTrackTableView, SIGNAL(trackSelected(TrackPointer)), this, SIGNAL(trackSelected(TrackPointer))); - connect(this, SIGNAL(setTrackTableFont(QFont)), - pTrackTableView, SLOT(setTrackTableFont(QFont))); - connect(this, SIGNAL(setTrackTableRowHeight(int)), - pTrackTableView, SLOT(setTrackTableRowHeight(int))); - connect(this, SIGNAL(setSelectedClick(bool)), - pTrackTableView, SLOT(setSelectedClick(bool))); + connect(this, SIGNAL(setTrackTableFont(QFont)), pTrackTableView, SLOT(setTrackTableFont(QFont))); + connect(this, SIGNAL(setTrackTableRowHeight(int)), pTrackTableView, SLOT(setTrackTableRowHeight(int))); + connect(this, SIGNAL(setSelectedClick(bool)), pTrackTableView, SLOT(setSelectedClick(bool))); m_pLibraryControl->bindWidget(pLibraryWidget, pKeyboard); QListIterator feature_it(m_features); - while(feature_it.hasNext()) { + while (feature_it.hasNext()) { LibraryFeature* feature = feature_it.next(); feature->bindWidget(pLibraryWidget, pKeyboard); } @@ -296,26 +270,18 @@ void Library::addFeature(LibraryFeature* feature) { } m_features.push_back(feature); m_pSidebarModel->addLibraryFeature(feature); - connect(feature, SIGNAL(showTrackModel(QAbstractItemModel*)), - this, SLOT(slotShowTrackModel(QAbstractItemModel*))); - connect(feature, SIGNAL(switchToView(const QString&)), - this, SLOT(slotSwitchToView(const QString&))); - connect(feature, SIGNAL(loadTrack(TrackPointer)), - this, SLOT(slotLoadTrack(TrackPointer))); - connect(feature, SIGNAL(loadTrackToPlayer(TrackPointer, QString, bool)), - this, SLOT(slotLoadTrackToPlayer(TrackPointer, QString, bool))); - connect(feature, SIGNAL(restoreSearch(const QString&)), - this, SLOT(slotRestoreSearch(const QString&))); - connect(feature, SIGNAL(disableSearch()), - this, SLOT(slotDisableSearch())); - connect(feature, SIGNAL(enableCoverArtDisplay(bool)), - this, SIGNAL(enableCoverArtDisplay(bool))); - connect(feature, SIGNAL(trackSelected(TrackPointer)), - this, SIGNAL(trackSelected(TrackPointer))); + connect(feature, SIGNAL(showTrackModel(QAbstractItemModel*)), this, SLOT(slotShowTrackModel(QAbstractItemModel*))); + connect(feature, SIGNAL(switchToView(const QString&)), this, SLOT(slotSwitchToView(const QString&))); + connect(feature, SIGNAL(loadTrack(TrackPointer)), this, SLOT(slotLoadTrack(TrackPointer))); + connect(feature, SIGNAL(loadTrackToPlayer(TrackPointer, QString, bool)), this, SLOT(slotLoadTrackToPlayer(TrackPointer, QString, bool))); + connect(feature, SIGNAL(restoreSearch(const QString&)), this, SLOT(slotRestoreSearch(const QString&))); + connect(feature, SIGNAL(disableSearch()), this, SLOT(slotDisableSearch())); + connect(feature, SIGNAL(enableCoverArtDisplay(bool)), this, SIGNAL(enableCoverArtDisplay(bool))); + connect(feature, SIGNAL(trackSelected(TrackPointer)), this, SIGNAL(trackSelected(TrackPointer))); } void Library::onPlayerManagerTrackAnalyzerProgress( - TrackId /*trackId*/,AnalyzerProgress /*analyzerProgress*/) { + TrackId /*trackId*/, AnalyzerProgress /*analyzerProgress*/) { if (m_pAnalysisFeature) { m_pAnalysisFeature->suspendAnalysis(); } @@ -349,7 +315,7 @@ void Library::slotLoadTrack(TrackPointer pTrack) { void Library::slotLoadLocationToPlayer(QString location, QString group) { TrackPointer pTrack = m_pTrackCollection->getTrackDAO() - .getOrAddTrack(location, true, NULL); + .getOrAddTrack(location, true, NULL); if (pTrack) { emit(loadTrackToPlayer(pTrack, group)); } @@ -368,8 +334,8 @@ void Library::slotDisableSearch() { } void Library::slotRefreshLibraryModels() { - m_pMixxxLibraryFeature->refreshLibraryModels(); - m_pAnalysisFeature->refreshLibraryModels(); + m_pMixxxLibraryFeature->refreshLibraryModels(); + m_pAnalysisFeature->refreshLibraryModels(); } void Library::slotCreatePlaylist() { @@ -396,10 +362,9 @@ void Library::slotRequestAddDir(QString dir) { Sandbox::createSecurityToken(directory); if (!m_pTrackCollection->getDirectoryDAO().addDirectory(dir)) { - QMessageBox::information(0, tr("Add Directory to Library"), - tr("Could not add the directory to your library. Either this " - "directory is already in your library or you are currently " - "rescanning your library.")); + QMessageBox::information(0, tr("Add Directory to Library"), tr("Could not add the directory to your library. Either this " + "directory is already in your library or you are currently " + "rescanning your library.")); } // set at least one directory in the config file so that it will be possible // to downgrade from 1.12 @@ -410,19 +375,18 @@ void Library::slotRequestAddDir(QString dir) { void Library::slotRequestRemoveDir(QString dir, RemovalType removalType) { switch (removalType) { - case Library::HideTracks: - // Mark all tracks in this directory as deleted but DON'T purge them - // in case the user re-adds them manually. - m_pTrackCollection->getTrackDAO().markTracksAsMixxxDeleted(dir); - break; - case Library::PurgeTracks: - // The user requested that we purge all metadata. - m_pTrackCollection->purgeTracks(dir); - break; - case Library::LeaveTracksUnchanged: - default: - break; - + case Library::HideTracks: + // Mark all tracks in this directory as deleted but DON'T purge them + // in case the user re-adds them manually. + m_pTrackCollection->getTrackDAO().markTracksAsMixxxDeleted(dir); + break; + case Library::PurgeTracks: + // The user requested that we purge all metadata. + m_pTrackCollection->purgeTracks(dir); + break; + case Library::LeaveTracksUnchanged: + default: + break; } // Remove the directory from the directory list. diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 2858deab1fc..f4965a33b35 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -49,6 +49,7 @@ void clearTable(QSqlDatabase &database, QString tableName) { } } +// This function is executed in a separate thread other than the main thread QList findRekordboxDevices(RekordboxFeature *rekordboxFeature) { QThread *thisThread = QThread::currentThread(); thisThread->setPriority(QThread::LowPriority); @@ -172,7 +173,7 @@ QString getText(rekordbox_pdb_t::device_sql_string_t *deviceString) { return QString::fromStdString(longUtf16beString->text()); } - return QString(""); + return QString(); } int createDevicePLaylist(QSqlDatabase &database, QString devicePath) { @@ -220,18 +221,18 @@ void insertTrack( QString devicePath, QString device, int audioFilesCount) { - int rbID = (int)track->id(); + int rbID = static_cast(track->id()); QString title = getText(track->title()); QString artist = artistsMap[track->artist_id()]; QString album = albumsMap[track->album_id()]; QString year = QString::number(track->year()); QString genre = genresMap[track->genre_id()]; QString location = devicePath + getText(track->file_path()); - float bpm = (float)track->tempo() / 100.0; - int bitrate = (int)track->bitrate(); + float bpm = static_cast(track->tempo() / 100.0); + int bitrate = static_cast(track->bitrate()); QString key = keysMap[track->key_id()]; - int playtime = (int)track->duration(); - int rating = (int)track->rating(); + int playtime = static_cast(track->duration()); + int rating = static_cast(track->rating()); QString comment = getText(track->comment()); QString tracknumber = QString::number(track->track_number()); QString anlzPath = devicePath + getText(track->analyze_path()); @@ -294,7 +295,17 @@ void buildPlaylistTree( QString playlistPath, QString device); -QString parseDeviceDB(QSqlDatabase &database, TreeItem *deviceItem) { +QString parseDeviceDB(TrackCollection *trackCollection, TreeItem *deviceItem) { + QSqlDatabase database = QSqlDatabase::cloneDatabase(trackCollection->database(), + "REKORDBOX_PARSER"); + + //Open the database connection in this thread. + if (!database.open()) { + qDebug() << "Failed to open database for Rekordbox parser." + << database.lastError(); + return QString(); + } + QString device = deviceItem->getLabel(); QString devicePath = deviceItem->getData().toList()[0].toString(); @@ -558,7 +569,7 @@ void buildPlaylistTree( queryInsertIntoPlaylistTracks.bindValue(":playlist_id", playlistID); queryInsertIntoPlaylistTracks.bindValue(":track_id", trackID); - queryInsertIntoPlaylistTracks.bindValue(":position", (int)trackIndex); + queryInsertIntoPlaylistTracks.bindValue(":position", static_cast(trackIndex)); if (!queryInsertIntoPlaylistTracks.exec()) { LOG_FAILED_QUERY(queryInsertIntoPlaylistTracks) @@ -865,7 +876,7 @@ void RekordboxFeature::activateChild(const QModelIndex &index) { // Mixxx shutdown. QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 // Let a worker thread do the XML parsing - m_tracksFuture = QtConcurrent::run(parseDeviceDB, m_database, item); + m_tracksFuture = QtConcurrent::run(parseDeviceDB, m_pTrackCollection, item); m_tracksFutureWatcher.setFuture(m_tracksFuture); // This device is now a playlist element, future activations should treat is diff --git a/src/library/rekordbox/rekordboxfeature.h b/src/library/rekordbox/rekordboxfeature.h index 251f33c34e1..e7f2ef718fb 100644 --- a/src/library/rekordbox/rekordboxfeature.h +++ b/src/library/rekordbox/rekordboxfeature.h @@ -50,14 +50,14 @@ class RekordboxPlaylistModel : public BaseExternalPlaylistModel { TrackCollection* pTrackCollection, QSharedPointer trackSource); TrackPointer getTrack(const QModelIndex& index) const override; - virtual bool isColumnHiddenByDefault(int column); + bool isColumnHiddenByDefault(int column) override; }; class RekordboxFeature : public BaseExternalLibraryFeature { Q_OBJECT public: RekordboxFeature(QObject* parent, TrackCollection*); - virtual ~RekordboxFeature(); + ~RekordboxFeature() override; QVariant title(); QIcon getIcon(); @@ -79,7 +79,7 @@ class RekordboxFeature : public BaseExternalLibraryFeature { private: QString formatRootViewHtml() const; - virtual BaseSqlTableModel* getPlaylistModelForPlaylist(QString playlist); + BaseSqlTableModel* getPlaylistModelForPlaylist(QString playlist) override; TreeItemModel m_childModel; TrackCollection* m_pTrackCollection; From ba891b5d759617efe0caa6bd4fda6866c1d1fe3f Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Sat, 3 Aug 2019 22:14:00 +1000 Subject: [PATCH 07/36] Addressed SQL case and pooled DB connection --- res/schema.xml | 36 +++++++++++----------- src/library/rekordbox/rekordboxfeature.cpp | 30 ++++++++++-------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/res/schema.xml b/res/schema.xml index 79f64aa3ec5..8eb0076dac8 100644 --- a/res/schema.xml +++ b/res/schema.xml @@ -453,30 +453,30 @@ METADATA CREATE TABLE IF NOT EXISTS rekordbox_library ( - id INTEGER primary key AUTOINCREMENT, - rb_id integer, - artist text, title text, - album text, year text, - genre text, tracknumber text, - location text UNIQUE, - comment text, - duration integer, - bitrate integer, - bpm float, - key text, - rating integer, - analyze_path text UNIQUE, - device text + id INTEGER PRIMARY KEY AUTOINCREMENT, + rb_id INTEGER, + artist TEXT, title TEXT, + album TEXT, year TEXT, + genre TEXT, tracknumber TEXT, + location TEXT UNIQUE, + comment TEXT, + duration INTEGER, + bitrate INTEGER, + bpm FLOAT, + key TEXT, + rating INTEGER, + analyze_path TEXT UNIQUE, + device TEXT ); CREATE TABLE IF NOT EXISTS rekordbox_playlists ( - id INTEGER primary key, - name text UNIQUE + id INTEGER PRIMARY KEY, + name TEXT UNIQUE ); CREATE TABLE IF NOT EXISTS rekordbox_playlist_tracks ( - id INTEGER primary key AUTOINCREMENT, + id INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id INTEGER REFERENCES rekordbox_playlists(id), track_id INTEGER REFERENCES rekordbox_library(id), - position integer + position INTEGER ); diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index f4965a33b35..926e41403e4 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -10,6 +10,7 @@ #include "library/rekordbox/rekordbox_pdb.h" #include "library/rekordbox/rekordboxfeature.h" +#include "library/library.h" #include "library/librarytablemodel.h" #include "library/missingtablemodel.h" #include "library/queryutil.h" @@ -18,6 +19,8 @@ #include "track/keyfactory.h" #include "util/file.h" #include "util/sandbox.h" +#include "util/db/dbconnectionpooler.h" +#include "util/db/dbconnectionpooled.h" #include "waveform/waveform.h" #include "widget/wlibrary.h" @@ -295,17 +298,7 @@ void buildPlaylistTree( QString playlistPath, QString device); -QString parseDeviceDB(TrackCollection *trackCollection, TreeItem *deviceItem) { - QSqlDatabase database = QSqlDatabase::cloneDatabase(trackCollection->database(), - "REKORDBOX_PARSER"); - - //Open the database connection in this thread. - if (!database.open()) { - qDebug() << "Failed to open database for Rekordbox parser." - << database.lastError(); - return QString(); - } - +QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *deviceItem) { QString device = deviceItem->getLabel(); QString devicePath = deviceItem->getData().toList()[0].toString(); @@ -317,6 +310,17 @@ QString parseDeviceDB(TrackCollection *trackCollection, TreeItem *deviceItem) { return devicePath; } + // The pooler limits the lifetime all thread-local connections, that should be closed immediately before exiting this function. + const mixxx::DbConnectionPooler dbConnectionPooler(dbConnectionPool); + QSqlDatabase database = mixxx::DbConnectionPooled(dbConnectionPool); + + //Open the database connection in this thread. + if (!database.open()) { + qDebug() << "Failed to open database for Rekordbox parser." + << database.lastError(); + return QString(); + } + //Give thread a low priority QThread *thisThread = QThread::currentThread(); thisThread->setPriority(QThread::LowPriority); @@ -485,6 +489,8 @@ QString parseDeviceDB(TrackCollection *trackCollection, TreeItem *deviceItem) { transaction.commit(); + database.close(); + return devicePath; } @@ -876,7 +882,7 @@ void RekordboxFeature::activateChild(const QModelIndex &index) { // Mixxx shutdown. QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 // Let a worker thread do the XML parsing - m_tracksFuture = QtConcurrent::run(parseDeviceDB, m_pTrackCollection, item); + m_tracksFuture = QtConcurrent::run(parseDeviceDB, static_cast(parent())->dbConnectionPool(), item); m_tracksFutureWatcher.setFuture(m_tracksFuture); // This device is now a playlist element, future activations should treat is From 4578f35414d8a30f279089e34c83758e0ddc8041 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Sat, 3 Aug 2019 22:47:03 +1000 Subject: [PATCH 08/36] clang-format --- src/library/rekordbox/rekordboxfeature.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 926e41403e4..10d1c03d80f 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -17,10 +17,10 @@ #include "library/trackcollection.h" #include "library/treeitem.h" #include "track/keyfactory.h" +#include "util/db/dbconnectionpooled.h" +#include "util/db/dbconnectionpooler.h" #include "util/file.h" #include "util/sandbox.h" -#include "util/db/dbconnectionpooler.h" -#include "util/db/dbconnectionpooled.h" #include "waveform/waveform.h" #include "widget/wlibrary.h" @@ -882,7 +882,7 @@ void RekordboxFeature::activateChild(const QModelIndex &index) { // Mixxx shutdown. QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 // Let a worker thread do the XML parsing - m_tracksFuture = QtConcurrent::run(parseDeviceDB, static_cast(parent())->dbConnectionPool(), item); + m_tracksFuture = QtConcurrent::run(parseDeviceDB, static_cast(parent())->dbConnectionPool(), item); m_tracksFutureWatcher.setFuture(m_tracksFuture); // This device is now a playlist element, future activations should treat is From 633ef47e9465fa3b81a5ea2950a71a652d98eada Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Sun, 4 Aug 2019 17:19:47 +1000 Subject: [PATCH 09/36] Addressed ASSERT, static casts, loop revisions, removed DB close --- src/library/rekordbox/rekordboxfeature.cpp | 36 +++++++++------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 10d1c03d80f..ec303e9db4b 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -310,12 +310,13 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev return devicePath; } - // The pooler limits the lifetime all thread-local connections, that should be closed immediately before exiting this function. + // The pooler limits the lifetime all thread-local connections, + // that should be closed immediately before exiting this function. const mixxx::DbConnectionPooler dbConnectionPooler(dbConnectionPool); QSqlDatabase database = mixxx::DbConnectionPooled(dbConnectionPool); //Open the database connection in this thread. - if (!database.open()) { + VERIFY_OR_DEBUG_ASSERT(database.isOpen()) { qDebug() << "Failed to open database for Rekordbox parser." << database.lastError(); return QString(); @@ -383,21 +384,17 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev bool folderOrPlaylistFound = false; for (int tableOrderIndex = 0; tableOrderIndex < totalTables; tableOrderIndex++) { - bool done = false; + // bool done = false; for ( std::vector::iterator table = reckordboxDB.tables()->begin(); table != reckordboxDB.tables()->end(); ++table) { if ((*table)->type() == tableOrder[tableOrderIndex]) { - if (done) - break; - uint16_t lastIndex = (*table)->last_page()->index(); rekordbox_pdb_t::page_ref_t *currentRef = (*table)->first_page(); - bool moreLeft = true; - do { + while (true) { rekordbox_pdb_t::page_t *page = currentRef->body(); if (page->is_data_page()) { @@ -414,45 +411,45 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev case rekordbox_pdb_t::PAGE_TYPE_KEYS: { // Key found, update map rekordbox_pdb_t::key_row_t *key = - (rekordbox_pdb_t::key_row_t *)(*rowRef)->body(); + static_cast((*rowRef)->body()); keysMap[key->id()] = getText(key->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_GENRES: { // Genre found, update map rekordbox_pdb_t::genre_row_t *genre = - (rekordbox_pdb_t::genre_row_t *)(*rowRef)->body(); + static_cast((*rowRef)->body()); genresMap[genre->id()] = getText(genre->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_ARTISTS: { // Artist found, update map rekordbox_pdb_t::artist_row_t *artist = - (rekordbox_pdb_t::artist_row_t *)(*rowRef)->body(); + static_cast((*rowRef)->body()); artistsMap[artist->id()] = getText(artist->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_ALBUMS: { // Album found, update map rekordbox_pdb_t::album_row_t *album = - (rekordbox_pdb_t::album_row_t *)(*rowRef)->body(); + static_cast((*rowRef)->body()); albumsMap[album->id()] = getText(album->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES: { // Playlist to track mapping found, update map rekordbox_pdb_t::playlist_entry_row_t *playlistEntry = - (rekordbox_pdb_t::playlist_entry_row_t *)(*rowRef)->body(); + static_cast((*rowRef)->body()); playlistTrackMap[playlistEntry->playlist_id()][playlistEntry->entry_index()] = playlistEntry->track_id(); } break; case rekordbox_pdb_t::PAGE_TYPE_TRACKS: { // Track found, insert into database insertTrack( - database, (rekordbox_pdb_t::track_row_t *)(*rowRef)->body(), query, queryInsertIntoDevicePlaylistTracks, artistsMap, albumsMap, genresMap, keysMap, devicePath, device, audioFilesCount); + database, static_cast((*rowRef)->body()), query, queryInsertIntoDevicePlaylistTracks, artistsMap, albumsMap, genresMap, keysMap, devicePath, device, audioFilesCount); audioFilesCount++; } break; case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE: { // Playlist tree node found, update map rekordbox_pdb_t::playlist_tree_row_t *playlistTree = - (rekordbox_pdb_t::playlist_tree_row_t *)(*rowRef)->body(); + static_cast((*rowRef)->body()); playlistNameMap[playlistTree->id()] = getText(playlistTree->name()); playlistIsFolderMap[playlistTree->id()] = playlistTree->is_folder(); @@ -469,12 +466,11 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev } if (currentRef->index() == lastIndex) { - moreLeft = false; + break; } else { currentRef = page->next_page(); } - } while (moreLeft); - done = true; + } } } } @@ -489,8 +485,6 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev transaction.commit(); - database.close(); - return devicePath; } @@ -553,7 +547,7 @@ void buildPlaylistTree( if (playlistTrackMap.count(childID)) { // Add playlist tracks for children - for (uint32_t trackIndex = 1; trackIndex <= (uint32_t)playlistTrackMap[childID].size(); trackIndex++) { + for (uint32_t trackIndex = 1; trackIndex <= static_cast(playlistTrackMap[childID].size()); trackIndex++) { uint32_t rbTrackID = playlistTrackMap[childID][trackIndex]; int trackID = -1; From 12767345072298ef2d154a8e7e6b5d7c23bad2af Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Sun, 11 Aug 2019 14:54:34 +1000 Subject: [PATCH 10/36] m_database removed, reconfig of global thread pool removed, new clang-format, Rekordbox feature disable from preferences dialog --- src/library/library.cpp | 4 +- src/library/rekordbox/rekordboxfeature.cpp | 213 +++++++++----------- src/library/rekordbox/rekordboxfeature.h | 3 - src/preferences/dialog/dlgpreflibrary.cpp | 5 + src/preferences/dialog/dlgpreflibrarydlg.ui | 14 +- 5 files changed, 114 insertions(+), 125 deletions(-) diff --git a/src/library/library.cpp b/src/library/library.cpp index 436b61a4768..5cbc6806da4 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -154,7 +154,9 @@ Library::Library( // dynamically appear/disappear when correctly prepared removable devices // are mounted/unmounted would be to have some form of timed thread to check // periodically. Not ideal perfomance wise. - addFeature(new RekordboxFeature(this, m_pTrackCollection)); + if (pConfig->getValue(ConfigKey(kConfigGroup, "ShowRekordboxLibrary"), true)) { + addFeature(new RekordboxFeature(this, m_pTrackCollection)); + } // On startup we need to check if all of the user's library folders are // accessible to us. If the user is using a database from <1.12.0 with diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index ec303e9db4b..f79df72bed9 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -33,7 +33,7 @@ namespace { const QString kPDBPath = "PIONEER/rekordbox/export.pdb"; const QString kPLaylistPathDelimiter = "-->"; -void clearTable(QSqlDatabase &database, QString tableName) { +void clearTable(QSqlDatabase& database, QString tableName) { QSqlQuery query(database); query.prepare("delete from " + tableName); @@ -53,11 +53,11 @@ void clearTable(QSqlDatabase &database, QString tableName) { } // This function is executed in a separate thread other than the main thread -QList findRekordboxDevices(RekordboxFeature *rekordboxFeature) { - QThread *thisThread = QThread::currentThread(); +QList findRekordboxDevices(RekordboxFeature* rekordboxFeature) { + QThread* thisThread = QThread::currentThread(); thisThread->setPriority(QThread::LowPriority); - QList foundDevices; + QList foundDevices; #if defined(__WINDOWS__) // Repopulate drive list @@ -76,7 +76,7 @@ QList findRekordboxDevices(RekordboxFeature *rekordboxFeature) { QFileInfo rbDBFileInfo(drive.filePath() + kPDBPath); if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { - TreeItem *foundDevice = new TreeItem(rekordboxFeature); + TreeItem* foundDevice = new TreeItem(rekordboxFeature); QList data; QString displayPath = drive.filePath(); @@ -116,7 +116,7 @@ QList findRekordboxDevices(RekordboxFeature *rekordboxFeature) { QFileInfo rbDBFileInfo(device.filePath() + "/" + kPDBPath); if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { - TreeItem *foundDevice = new TreeItem(rekordboxFeature); + TreeItem* foundDevice = new TreeItem(rekordboxFeature); QList data; data << device.filePath(); @@ -135,7 +135,7 @@ QList findRekordboxDevices(RekordboxFeature *rekordboxFeature) { QFileInfo rbDBFileInfo(device.filePath() + "/" + kPDBPath); if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { - TreeItem *foundDevice = new TreeItem(rekordboxFeature); + TreeItem* foundDevice = new TreeItem(rekordboxFeature); QList data; data << device.filePath(); @@ -153,33 +153,33 @@ QList findRekordboxDevices(RekordboxFeature *rekordboxFeature) { } template -inline bool instanceof (const T *ptr) { - return dynamic_cast(ptr) != nullptr; +inline bool instanceof (const T* ptr) { + return dynamic_cast(ptr) != nullptr; } // Functions getText and parseDeviceDB are roughly based on the following Java file: // https://github.com/Deep-Symmetry/crate-digger/blob/master/src/main/java/org/deepsymmetry/cratedigger/Database.java // getText is needed because the strings in the PDB file "have a variety of obscure representations". -QString getText(rekordbox_pdb_t::device_sql_string_t *deviceString) { +QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { if (instanceof (deviceString->body())) { - rekordbox_pdb_t::device_sql_short_ascii_t *shortAsciiString = - static_cast(deviceString->body()); + rekordbox_pdb_t::device_sql_short_ascii_t* shortAsciiString = + static_cast(deviceString->body()); return QString::fromStdString(shortAsciiString->text()); } else if (instanceof (deviceString->body())) { - rekordbox_pdb_t::device_sql_long_ascii_t *longAsciiString = - static_cast(deviceString->body()); + rekordbox_pdb_t::device_sql_long_ascii_t* longAsciiString = + static_cast(deviceString->body()); return QString::fromStdString(longAsciiString->text()); } else if (instanceof (deviceString->body())) { - rekordbox_pdb_t::device_sql_long_utf16be_t *longUtf16beString = - static_cast(deviceString->body()); + rekordbox_pdb_t::device_sql_long_utf16be_t* longUtf16beString = + static_cast(deviceString->body()); return QString::fromStdString(longUtf16beString->text()); } return QString(); } -int createDevicePLaylist(QSqlDatabase &database, QString devicePath) { +int createDevicePLaylist(QSqlDatabase& database, QString devicePath) { int playlistID = -1; QSqlQuery queryInsertIntoDevicePlaylist(database); @@ -213,14 +213,14 @@ int createDevicePLaylist(QSqlDatabase &database, QString devicePath) { } void insertTrack( - QSqlDatabase &database, - rekordbox_pdb_t::track_row_t *track, - QSqlQuery &query, - QSqlQuery &queryInsertIntoDevicePlaylistTracks, - QMap &artistsMap, - QMap &albumsMap, - QMap &genresMap, - QMap &keysMap, + QSqlDatabase& database, + rekordbox_pdb_t::track_row_t* track, + QSqlQuery& query, + QSqlQuery& queryInsertIntoDevicePlaylistTracks, + QMap& artistsMap, + QMap& albumsMap, + QMap& genresMap, + QMap& keysMap, QString devicePath, QString device, int audioFilesCount) { @@ -288,17 +288,17 @@ void insertTrack( } void buildPlaylistTree( - QSqlDatabase &database, - TreeItem *parent, + QSqlDatabase& database, + TreeItem* parent, uint32_t parentID, - QMap &playlistNameMap, - QMap &playlistIsFolderMap, - QMap> &playlistTreeMap, - QMap> &playlistTrackMap, + QMap& playlistNameMap, + QMap& playlistIsFolderMap, + QMap>& playlistTreeMap, + QMap>& playlistTrackMap, QString playlistPath, QString device); -QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *deviceItem) { +QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* deviceItem) { QString device = deviceItem->getLabel(); QString devicePath = deviceItem->getData().toList()[0].toString(); @@ -323,7 +323,7 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev } //Give thread a low priority - QThread *thisThread = QThread::currentThread(); + QThread* thisThread = QThread::currentThread(); thisThread->setPriority(QThread::LowPriority); ScopedTransaction transaction(database); @@ -387,69 +387,69 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev // bool done = false; for ( - std::vector::iterator table = reckordboxDB.tables()->begin(); + std::vector::iterator table = reckordboxDB.tables()->begin(); table != reckordboxDB.tables()->end(); ++table) { if ((*table)->type() == tableOrder[tableOrderIndex]) { uint16_t lastIndex = (*table)->last_page()->index(); - rekordbox_pdb_t::page_ref_t *currentRef = (*table)->first_page(); + rekordbox_pdb_t::page_ref_t* currentRef = (*table)->first_page(); while (true) { - rekordbox_pdb_t::page_t *page = currentRef->body(); + rekordbox_pdb_t::page_t* page = currentRef->body(); if (page->is_data_page()) { for ( - std::vector::iterator rowGroup = page->row_groups()->begin(); + std::vector::iterator rowGroup = page->row_groups()->begin(); rowGroup != page->row_groups()->end(); ++rowGroup) { for ( - std::vector::iterator rowRef = (*rowGroup)->rows()->begin(); + std::vector::iterator rowRef = (*rowGroup)->rows()->begin(); rowRef != (*rowGroup)->rows()->end(); ++rowRef) { if ((*rowRef)->present()) { switch (tableOrder[tableOrderIndex]) { case rekordbox_pdb_t::PAGE_TYPE_KEYS: { // Key found, update map - rekordbox_pdb_t::key_row_t *key = - static_cast((*rowRef)->body()); + rekordbox_pdb_t::key_row_t* key = + static_cast((*rowRef)->body()); keysMap[key->id()] = getText(key->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_GENRES: { // Genre found, update map - rekordbox_pdb_t::genre_row_t *genre = - static_cast((*rowRef)->body()); + rekordbox_pdb_t::genre_row_t* genre = + static_cast((*rowRef)->body()); genresMap[genre->id()] = getText(genre->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_ARTISTS: { // Artist found, update map - rekordbox_pdb_t::artist_row_t *artist = - static_cast((*rowRef)->body()); + rekordbox_pdb_t::artist_row_t* artist = + static_cast((*rowRef)->body()); artistsMap[artist->id()] = getText(artist->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_ALBUMS: { // Album found, update map - rekordbox_pdb_t::album_row_t *album = - static_cast((*rowRef)->body()); + rekordbox_pdb_t::album_row_t* album = + static_cast((*rowRef)->body()); albumsMap[album->id()] = getText(album->name()); } break; case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_ENTRIES: { // Playlist to track mapping found, update map - rekordbox_pdb_t::playlist_entry_row_t *playlistEntry = - static_cast((*rowRef)->body()); + rekordbox_pdb_t::playlist_entry_row_t* playlistEntry = + static_cast((*rowRef)->body()); playlistTrackMap[playlistEntry->playlist_id()][playlistEntry->entry_index()] = playlistEntry->track_id(); } break; case rekordbox_pdb_t::PAGE_TYPE_TRACKS: { // Track found, insert into database insertTrack( - database, static_cast((*rowRef)->body()), query, queryInsertIntoDevicePlaylistTracks, artistsMap, albumsMap, genresMap, keysMap, devicePath, device, audioFilesCount); + database, static_cast((*rowRef)->body()), query, queryInsertIntoDevicePlaylistTracks, artistsMap, albumsMap, genresMap, keysMap, devicePath, device, audioFilesCount); audioFilesCount++; } break; case rekordbox_pdb_t::PAGE_TYPE_PLAYLIST_TREE: { // Playlist tree node found, update map - rekordbox_pdb_t::playlist_tree_row_t *playlistTree = - static_cast((*rowRef)->body()); + rekordbox_pdb_t::playlist_tree_row_t* playlistTree = + static_cast((*rowRef)->body()); playlistNameMap[playlistTree->id()] = getText(playlistTree->name()); playlistIsFolderMap[playlistTree->id()] = playlistTree->is_folder(); @@ -489,13 +489,13 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem *dev } void buildPlaylistTree( - QSqlDatabase &database, - TreeItem *parent, + QSqlDatabase& database, + TreeItem* parent, uint32_t parentID, - QMap &playlistNameMap, - QMap &playlistIsFolderMap, - QMap> &playlistTreeMap, - QMap> &playlistTrackMap, + QMap& playlistNameMap, + QMap& playlistIsFolderMap, + QMap>& playlistTreeMap, + QMap>& playlistTrackMap, QString playlistPath, QString device) { for (uint32_t childIndex = 0; childIndex < (uint32_t)playlistTreeMap[parentID].size(); childIndex++) { @@ -509,7 +509,7 @@ void buildPlaylistTree( data << currentPath; data << IS_NOT_RECORDBOX_DEVICE; - TreeItem *child = parent->appendChild(playlistItemName, QVariant(data)); + TreeItem* child = parent->appendChild(playlistItemName, QVariant(data)); // Create a playlist for this child QSqlQuery queryInsertIntoPlaylist(database); @@ -589,7 +589,7 @@ void buildPlaylistTree( } } -void clearDeviceTables(QSqlDatabase &database, TreeItem *child) { +void clearDeviceTables(QSqlDatabase& database, TreeItem* child) { ScopedTransaction transaction(database); int trackID = -1; @@ -654,13 +654,13 @@ void clearDeviceTables(QSqlDatabase &database, TreeItem *child) { } // anonymous namespace -RekordboxPlaylistModel::RekordboxPlaylistModel(QObject *parent, - TrackCollection *trackCollection, +RekordboxPlaylistModel::RekordboxPlaylistModel(QObject* parent, + TrackCollection* trackCollection, QSharedPointer trackSource) : BaseExternalPlaylistModel(parent, trackCollection, "mixxx.db.model.rekordbox.playlistmodel", "rekordbox_playlists", "rekordbox_playlist_tracks", trackSource) { } -TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex &index) const { +TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { qDebug() << "RekordboxTrackModel::getTrack"; TrackPointer track = BaseExternalPlaylistModel::getTrack(index); @@ -685,7 +685,7 @@ bool RekordboxPlaylistModel::isColumnHiddenByDefault(int column) { return BaseSqlTableModel::isColumnHiddenByDefault(column); } -RekordboxFeature::RekordboxFeature(QObject *parent, TrackCollection *trackCollection) +RekordboxFeature::RekordboxFeature(QObject* parent, TrackCollection* trackCollection) : BaseExternalLibraryFeature(parent, trackCollection), m_pTrackCollection(trackCollection), m_icon(":/images/library/ic_library_rekordbox.svg") { @@ -728,21 +728,13 @@ RekordboxFeature::RekordboxFeature(QObject *parent, TrackCollection *trackCollec m_title = tr("Rekordbox"); - m_database = QSqlDatabase::cloneDatabase(trackCollection->database(), - "REKORDBOX_SCANNER"); - - //Open the database connection in this thread. - if (!m_database.open()) { - qDebug() << "Failed to open database for Rekordbox scanner." - << m_database.lastError(); - } else { - //Clear any previous Rekordbox device entries if they exist - ScopedTransaction transaction(m_database); - clearTable(m_database, "rekordbox_playlist_tracks"); - clearTable(m_database, "rekordbox_library"); - clearTable(m_database, "rekordbox_playlists"); - transaction.commit(); - } + //Clear any previous Rekordbox device entries if they exist + QSqlDatabase database = m_pTrackCollection->database(); + ScopedTransaction transaction(database); + clearTable(database, "rekordbox_playlist_tracks"); + clearTable(database, "rekordbox_library"); + clearTable(database, "rekordbox_playlists"); + transaction.commit(); connect(&m_devicesFutureWatcher, SIGNAL(finished()), this, SLOT(onRekordboxDevicesFound())); connect(&m_tracksFutureWatcher, SIGNAL(finished()), this, SLOT(onTracksFound())); @@ -751,23 +743,22 @@ RekordboxFeature::RekordboxFeature(QObject *parent, TrackCollection *trackCollec } RekordboxFeature::~RekordboxFeature() { - m_database.close(); m_devicesFuture.waitForFinished(); m_tracksFuture.waitForFinished(); delete m_pRekordboxPlaylistModel; } -void RekordboxFeature::bindWidget(WLibrary *libraryWidget, - KeyboardEventFilter *keyboard) { +void RekordboxFeature::bindWidget(WLibrary* libraryWidget, + KeyboardEventFilter* keyboard) { Q_UNUSED(keyboard); - WLibraryTextBrowser *edit = new WLibraryTextBrowser(libraryWidget); + WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget); edit->setHtml(formatRootViewHtml()); edit->setOpenLinks(false); connect(edit, SIGNAL(anchorClicked(const QUrl)), this, SLOT(htmlLinkClicked(const QUrl))); libraryWidget->registerView("REKORDBOXHOME", edit); } -void RekordboxFeature::htmlLinkClicked(const QUrl &link) { +void RekordboxFeature::htmlLinkClicked(const QUrl& link) { if (QString(link.path()) == "refresh") { activate(); } else { @@ -775,8 +766,8 @@ void RekordboxFeature::htmlLinkClicked(const QUrl &link) { } } -BaseSqlTableModel *RekordboxFeature::getPlaylistModelForPlaylist(QString playlist) { - RekordboxPlaylistModel *model = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); +BaseSqlTableModel* RekordboxFeature::getPlaylistModelForPlaylist(QString playlist) { + RekordboxPlaylistModel* model = new RekordboxPlaylistModel(this, m_pTrackCollection, m_trackSource); model->setPlaylist(playlist); return model; } @@ -793,7 +784,7 @@ bool RekordboxFeature::isSupported() { return true; } -TreeItemModel *RekordboxFeature::getChildModel() { +TreeItemModel* RekordboxFeature::getChildModel() { return &m_childModel; } @@ -820,15 +811,6 @@ void RekordboxFeature::refreshLibraryModels() { void RekordboxFeature::activate() { qDebug() << "RekordboxFeature::activate()"; - // Usually the maximum number of threads - // is > 2 depending on the CPU cores - // Unfortunately, within VirtualBox - // the maximum number of allowed threads - // is 1 at all times We'll need to increase - // the number to > 1, otherwise importing the music collection - // takes place when the GUI threads terminates, i.e., on - // Mixxx shutdown. - QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 // Let a worker thread do the XML parsing m_devicesFuture = QtConcurrent::run(findRekordboxDevices, this); m_devicesFutureWatcher.setFuture(m_devicesFuture); @@ -840,12 +822,12 @@ void RekordboxFeature::activate() { emit(switchToView("REKORDBOXHOME")); } -void RekordboxFeature::activateChild(const QModelIndex &index) { +void RekordboxFeature::activateChild(const QModelIndex& index) { if (!index.isValid()) return; //access underlying TreeItem object - TreeItem *item = static_cast(index.internalPointer()); + TreeItem* item = static_cast(index.internalPointer()); if (!(item && item->getData().isValid())) { return; } @@ -866,17 +848,8 @@ void RekordboxFeature::activateChild(const QModelIndex &index) { if (doParseDeviceDB) { qDebug() << "Parse Rekordbox Device DB: " << playlist; - // Usually the maximum number of threads - // is > 2 depending on the CPU cores - // Unfortunately, within VirtualBox - // the maximum number of allowed threads - // is 1 at all times We'll need to increase - // the number to > 1, otherwise importing the music collection - // takes place when the GUI threads terminates, i.e., on - // Mixxx shutdown. - QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 // Let a worker thread do the XML parsing - m_tracksFuture = QtConcurrent::run(parseDeviceDB, static_cast(parent())->dbConnectionPool(), item); + m_tracksFuture = QtConcurrent::run(parseDeviceDB, static_cast(parent())->dbConnectionPool(), item); m_tracksFutureWatcher.setFuture(m_tracksFuture); // This device is now a playlist element, future activations should treat is @@ -891,15 +864,17 @@ void RekordboxFeature::activateChild(const QModelIndex &index) { } void RekordboxFeature::onRekordboxDevicesFound() { - QList foundDevices = m_devicesFuture.result(); - TreeItem *root = m_childModel.getRootItem(); + QList foundDevices = m_devicesFuture.result(); + TreeItem* root = m_childModel.getRootItem(); + + QSqlDatabase database = m_pTrackCollection->database(); if (foundDevices.size() == 0) { // No Rekordbox devices found - ScopedTransaction transaction(m_database); - clearTable(m_database, "rekordbox_playlist_tracks"); - clearTable(m_database, "rekordbox_library"); - clearTable(m_database, "rekordbox_playlists"); + ScopedTransaction transaction(database); + clearTable(database, "rekordbox_playlist_tracks"); + clearTable(database, "rekordbox_library"); + clearTable(database, "rekordbox_playlists"); transaction.commit(); if (root->childRows() > 0) { @@ -908,11 +883,11 @@ void RekordboxFeature::onRekordboxDevicesFound() { } } else { for (int deviceIndex = 0; deviceIndex < root->childRows(); deviceIndex++) { - TreeItem *child = root->child(deviceIndex); + TreeItem* child = root->child(deviceIndex); bool removeChild = true; for (int foundDeviceIndex = 0; foundDeviceIndex < foundDevices.size(); foundDeviceIndex++) { - TreeItem *deviceFound = foundDevices[foundDeviceIndex]; + TreeItem* deviceFound = foundDevices[foundDeviceIndex]; if (deviceFound->getLabel() == child->getLabel()) { removeChild = false; @@ -922,20 +897,20 @@ void RekordboxFeature::onRekordboxDevicesFound() { if (removeChild) { // Device has since been unmounted, cleanup DB - clearDeviceTables(m_database, child); + clearDeviceTables(database, child); m_childModel.removeRows(deviceIndex, 1); } } - QList childrenToAdd; + QList childrenToAdd; for (int foundDeviceIndex = 0; foundDeviceIndex < foundDevices.size(); foundDeviceIndex++) { - TreeItem *deviceFound = foundDevices[foundDeviceIndex]; + TreeItem* deviceFound = foundDevices[foundDeviceIndex]; bool addNewChild = true; for (int deviceIndex = 0; deviceIndex < root->childRows(); deviceIndex++) { - TreeItem *child = root->child(deviceIndex); + TreeItem* child = root->child(deviceIndex); if (deviceFound->getLabel() == child->getLabel()) { // This device already exists in the TreeModel, don't add or parse is again diff --git a/src/library/rekordbox/rekordboxfeature.h b/src/library/rekordbox/rekordboxfeature.h index e7f2ef718fb..e3693022467 100644 --- a/src/library/rekordbox/rekordboxfeature.h +++ b/src/library/rekordbox/rekordboxfeature.h @@ -32,7 +32,6 @@ #include #include #include -#include #include @@ -83,8 +82,6 @@ class RekordboxFeature : public BaseExternalLibraryFeature { TreeItemModel m_childModel; TrackCollection* m_pTrackCollection; - // A separate db connection for the worker parsing thread - QSqlDatabase m_database; RekordboxPlaylistModel* m_pRekordboxPlaylistModel; QFutureWatcher> m_devicesFutureWatcher; diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index 98b380eb42d..c0ca869ec2a 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -144,6 +144,7 @@ void DlgPrefLibrary::slotResetToDefaults() { checkBox_show_banshee->setChecked(true); checkBox_show_itunes->setChecked(true); checkBox_show_traktor->setChecked(true); + checkBox_show_rekordbox->setChecked(true); radioButton_dbclick_bottom->setChecked(false); checkBoxEditMetadataSelectedClicked->setChecked(PREF_LIBRARY_EDIT_METADATA_DEFAULT); radioButton_dbclick_top->setChecked(false); @@ -168,6 +169,8 @@ void DlgPrefLibrary::slotUpdate() { ConfigKey("[Library]","ShowITunesLibrary"), true)); checkBox_show_traktor->setChecked(m_pConfig->getValue( ConfigKey("[Library]","ShowTraktorLibrary"), true)); + checkBox_show_rekordbox->setChecked(m_pConfig->getValue( + ConfigKey("[Library]","ShowRekordboxLibrary"), true)); switch (m_pConfig->getValue( ConfigKey("[Library]","TrackLoadAction"), LOAD_TO_DECK)) { @@ -306,6 +309,8 @@ void DlgPrefLibrary::slotApply() { ConfigValue((int)checkBox_show_itunes->isChecked())); m_pConfig->set(ConfigKey("[Library]","ShowTraktorLibrary"), ConfigValue((int)checkBox_show_traktor->isChecked())); + m_pConfig->set(ConfigKey("[Library]","ShowRekordboxLibrary"), + ConfigValue((int)checkBox_show_rekordbox->isChecked())); int dbclick_status; if (radioButton_dbclick_bottom->isChecked()) { dbclick_status = ADD_TO_AUTODJ_BOTTOM; diff --git a/src/preferences/dialog/dlgpreflibrarydlg.ui b/src/preferences/dialog/dlgpreflibrarydlg.ui index a18380df2e3..c9ebc06d15e 100644 --- a/src/preferences/dialog/dlgpreflibrarydlg.ui +++ b/src/preferences/dialog/dlgpreflibrarydlg.ui @@ -327,14 +327,24 @@ - + + + + Show Rekordbox Library + + + true + + + + All external libraries shown are write protected. - + Qt::Horizontal From 5ef128519fcb3c76cc3260171d39556f1f6b9fb3 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 2 Sep 2019 15:23:08 +1000 Subject: [PATCH 11/36] Removed .DS_Store from gitignore --- .DS_Store | Bin 0 -> 10244 bytes .gitignore | 1 - src/.DS_Store | Bin 0 -> 8196 bytes src/library/.DS_Store | Bin 0 -> 8196 bytes 4 files changed, 1 deletion(-) create mode 100644 .DS_Store create mode 100644 src/.DS_Store create mode 100644 src/library/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d0868fee07d9fd3eafe21b6ed53aa0d69c90d53b GIT binary patch literal 10244 zcmeHMYitzP6+WL$U}k3)kAdI^7%w;>25-&!1=ti=zW`$>D7LYA1hea%glX5i&d#n4 zaosvqA5xkS5>?HsNu{Kf+Cth?eS|8lRH>;N)R&aH3h5*L(MnDKCF;BCkDhz)?6Nkv zYNIM3bw-+d=ALuU+ zlJcj63%LbgxrEj7;%7@dunc)J>d9Fp*}@l;D@)}HZ;Jut&iatoF6zlyC6znD+kC+5 z8Qu;BvUh%eNH!-#mGnzL0)7Opjetm-Qp%7`ZhWeE|DJT*Y$Wm;N=ld9vUHiI>zW?a z?;1Fro*Kx;vN^XamN_nJhhy2(aqoFBolX{>WAP)7JvC~r>Q1M!F(+kbq_dN-xygxP zCzVJ~b)|Euge%`OpbWC3QLA!#y0yI}9NHS035TYenwy)#q4r4g%#5y8+}^aa@6`C& z`=&oQ^PwlyjNtt$P}zbRK6!O|Nq4ea9Z!@z7N1blyHc25z%cvv_XkF;6_Y&bLlapi zopO)Z8JFd#Rp#xHN3HU)SaK|vjAiZ4WOBf{&vu8$(#hO-${oqtXR<aKT2{`r@3h&`nY4S@-L**9E(BMvtGgrIv~@?ccW!x^RUTLwxOI5a zah-V59-49z+0#d3?wFlQIH`M%aimgN=WfT&=;bBP=!2=)xII@7;TqZP44Qi9opj$C* z(B+vZO=BRUTdqF%0sfp)W3&X?G%KMGC1Y_rsZ`qo9h%jz4^OIaD&@|=Zmk?)ePk>> zKF;%TWzU^mhH+B#d?4m#4^7x99@Q({eR*Y7#U38jQDN&$B*d8NqV?))%K+3xBdVq| zlT=Y7Gu9xTU|dhoS$dGp(Zh6}K0}|U=jm(o9eRZ>(~s!K^eX+5eoKF#KhmG*FZ5Rs zbgV`ts!)wOY(WG~=)ex_#4dE>01o014r2%>F^U-CIE{OeK^8g8;N$oN9>6E@DLjpf z_%gnN7w|274=>~U_z8Z7U#S6>+0IuHZ`_v-`QFChzq`5F^D8;6f7iM@^Fw}ZypW>maEsS4H!d=D*Mbr{O1vF zEfe_B=BgPR#(JYpW29JKb)!+QF`TUu`L4|kM!+g%-4+gQ zZ)<&rtRSaX#0p=`C)zKHSs}Q5U*En}X8-?y83d1vWzxyy;k4@rf;n2?ifc?GMeK1M z(kKv$WRJ4SnhhHpB5fVJd*)PTnr0D8ES5kr^U6yuF}8(QRdp~}UQq7y+wbfztP`0UrS+PIm$R^1w*Xmtlf+jw4x22GDi>MeHg$A+{OHG3N}(m zONKau`#C>9${G3)K8?rlB%Z?O@I14`3-~I&i_6RpbND&l!0+*<8ghXr4&2LCfY z5v@1W1%sA=m?oX?s1KSJ?kHOo zxvZ^TQ)Tv=2Cv~R>2>)}{NJPWhcw44n78Pk0G2U2S1~d-Fpo4ce?-xRUhKhMUbEbZ zLFSJma@{h<{2{P8A+b5lYnKmWmJ#{@9>h63jPrPc@%c2K!DsOWe2G^x0;w20r zS0aEVJp(;m+*STyVqvf1tUbi{DZa%e+RZ8{UAU0P@hs(WJXgfIqCB2qop&%J{i3WF f{l?M&jK$W(<${!V8{|Ti<8eU5u|9su|c7=`G-ST!{ z3)E^gdNHD&6fo+=gAp~FppfXngC-KA7i&NdB*udt@nE9FgWt?-8))rK3<*2Q%x`wS z`DXU}z2EHS+s7C~d(moUteP>VP~~VWqUK);xAQuultd$Cgdl&$Ojckga$~_r&Yd$m z9*7x;8HgE(8HgE(8Mqx7pm#Pe$~^CVX^q>MftZ0ik^%mFNKoaNif~e*cyv%BQUF4p z0*FF=$^(Q?BEnRJlM;n1ttqnyM6QTZ3`lpfC-`*2RD_cfr8|RkXNY!2l%YWEPJRh7 zogpPLZes>w2Iexrk54&snZt6FyWfz%`#G{)%8j^A5H>YkMoDSef`#RZsw%3k9!?+i z$I_u07K3)PaFAd1nc>k~)bICwccyRV_Br-g)~M?6z0h<#yC9q$%cd!Z1|84x$J+g( zX9Z%Nv^0o~vPSjzcyeP)W1=NF(U=%-Zb@!xOl)pVPE4rEvNg?*bPo-mI63~*#M5VF zWQd*)tYTKAXXl3L2BRH0mxoz$emcybX2QH{k)|)+*}bbu>&+TVMk#_lBcbDa!9KeX z5N3^vfTHfs`(9tc=Ju>nnK#{h(KSQ6&2`hxNjn(K`)+aA3;67O!FSz0KXCXm`!k_^ zJmhocm@)?fzYul^$E;x#1ADJcYdWKLC#@W09NLmQ8cbErRaDolUc0`jb?f%dDWh`9 z(yF^P?Eo?8h0YU>T^Ja1tnlc5GsxSX<#GrkHb% z6`k;eQLiXdODmTc+Dd(uYJ8#`vS{DkzQtFxghgMeuThkPe7vJzQ!9tH27R4sm`Z;@ zwf!hBh{mjy}MN2A%r4G1e&UWR{CT)vi^r(ZQl3F?Vu(nO9M5CI?`@_Q& zPauce^bW=NT6vuB-D?Kno)O!l?Ro57D|LN8A8)fP+ftPJ)ai_nL4B%0%AtN*6>5E| zDe5b%j(10=vk*WZ$wY>?-?>U1NW+KiOaGI!d8oF)Co7 z2DMm$I;_J+B+-gC>_QKA<1wUh2#1ly5NvqxaSR0<$Fq12&*KH0!fSXPXYmH!!3X#d zAK_zsflK%vQ}_W_@hh(3cX@v$(~90beeV)+op9E{B}LPRH=*b=^C)`9&hBMe&uy&e zA1kDc?^#y8YVG0QQkoP2^z-xsC|Aj$;DPV3I`pB3{DFcm=QG3`zD)yoI;%F5biYLcAAn z5uf36e1-3D8CPb>ws)3nf1WGb(R9IceeVcarp11e UQ2F0K1jO(E`28=GcV{>M0MhFeNdN!< literal 0 HcmV?d00001 diff --git a/src/library/.DS_Store b/src/library/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..311df1d7c57af475dae87fd58001d3e3c93bf4fd GIT binary patch literal 8196 zcmeHMT}&KR6h3F6bbd-1uq_J}S)~x#0`eQEf-Wp=!Jm}4&;?4}&hAWM(qV?~%r3MP zv+*yQ7~4c2)ToIu#)sC#7psXceXI{AYDl#4!55=G_@dDlpFH>8fwTo4d{NWRP3E3E z=jYxz-~DE8&MpApP~PYOhys8_7n7Pw-6oCM*|niX0!=ANqUar2l9d3w z(U|H09uo^>Jdi~pc__^(ss{{0F(@%mgp)nS+)2g*Srk%)1B!6KU}X#{6y&Q@T#UN| z#)S;~5P=YZjR>&UN2a>ngfmpP-&DVQ3mMzZkoAkqf^B(zd;7N-DJk7nwq25CNiLU9 zCgKbHx{y~5Yc(7%dyN$b4nrLwM_pLe`T-(2#UX4-Reeu690nwIy>r%dv< z3g$3Hd6O1R$J{iKrOV}e_Qv+@Z|mwC7+T#~p;cDhqbSpaj^kUWEi*TJGsS;b{$*em+Px*L4{ir%9Y1gHv+1gL&o_}n?bZB{&d!kNN zr+mIf z1z*EA@ICwpKf%xN3;YJZ!yoV`Mo`5aSdKOL05)JFHsL|+#vbg&J{-U!coawRF&x7w zoW>bEi6%PeVh%mLfG^;S_!7R1Z{T})8L!|+cojbt&lOqYI9L>T-u7`+IB`CiF?QHS5=MiM%*?wsfva~ZJApSG?6tD*xDW>TM4mP*t)tE zRog~{6_I_16h$i~Mhok}BV;Wj?n*#0gB{B+)K|2A;-QoWrw3x;z$e z5sUaNKF{fQ5ij9u_&UCW?+S*k6K^D7Tp;kn&52h?<{aB~X34rn*;@m{yE_Mg5|K)j z)&GaL{{8>XNjS_TL?A?90|Hn&oE#n^^;VyJR%^%TIz<<2%&#aU5222^Fg-8FNv{84 fNaI*wiwCkOBw48a>puj9=RZR|JpVcS?%wWiELY30 literal 0 HcmV?d00001 From ec5b2c7897436457e3b49b25aafe0c49ddec0e01 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 2 Sep 2019 15:25:18 +1000 Subject: [PATCH 12/36] Removed .DS_Store from gitignore --- .DS_Store | Bin 10244 -> 0 bytes src/.DS_Store | Bin 8196 -> 0 bytes src/library/.DS_Store | Bin 8196 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 src/.DS_Store delete mode 100644 src/library/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index d0868fee07d9fd3eafe21b6ed53aa0d69c90d53b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMYitzP6+WL$U}k3)kAdI^7%w;>25-&!1=ti=zW`$>D7LYA1hea%glX5i&d#n4 zaosvqA5xkS5>?HsNu{Kf+Cth?eS|8lRH>;N)R&aH3h5*L(MnDKCF;BCkDhz)?6Nkv zYNIM3bw-+d=ALuU+ zlJcj63%LbgxrEj7;%7@dunc)J>d9Fp*}@l;D@)}HZ;Jut&iatoF6zlyC6znD+kC+5 z8Qu;BvUh%eNH!-#mGnzL0)7Opjetm-Qp%7`ZhWeE|DJT*Y$Wm;N=ld9vUHiI>zW?a z?;1Fro*Kx;vN^XamN_nJhhy2(aqoFBolX{>WAP)7JvC~r>Q1M!F(+kbq_dN-xygxP zCzVJ~b)|Euge%`OpbWC3QLA!#y0yI}9NHS035TYenwy)#q4r4g%#5y8+}^aa@6`C& z`=&oQ^PwlyjNtt$P}zbRK6!O|Nq4ea9Z!@z7N1blyHc25z%cvv_XkF;6_Y&bLlapi zopO)Z8JFd#Rp#xHN3HU)SaK|vjAiZ4WOBf{&vu8$(#hO-${oqtXR<aKT2{`r@3h&`nY4S@-L**9E(BMvtGgrIv~@?ccW!x^RUTLwxOI5a zah-V59-49z+0#d3?wFlQIH`M%aimgN=WfT&=;bBP=!2=)xII@7;TqZP44Qi9opj$C* z(B+vZO=BRUTdqF%0sfp)W3&X?G%KMGC1Y_rsZ`qo9h%jz4^OIaD&@|=Zmk?)ePk>> zKF;%TWzU^mhH+B#d?4m#4^7x99@Q({eR*Y7#U38jQDN&$B*d8NqV?))%K+3xBdVq| zlT=Y7Gu9xTU|dhoS$dGp(Zh6}K0}|U=jm(o9eRZ>(~s!K^eX+5eoKF#KhmG*FZ5Rs zbgV`ts!)wOY(WG~=)ex_#4dE>01o014r2%>F^U-CIE{OeK^8g8;N$oN9>6E@DLjpf z_%gnN7w|274=>~U_z8Z7U#S6>+0IuHZ`_v-`QFChzq`5F^D8;6f7iM@^Fw}ZypW>maEsS4H!d=D*Mbr{O1vF zEfe_B=BgPR#(JYpW29JKb)!+QF`TUu`L4|kM!+g%-4+gQ zZ)<&rtRSaX#0p=`C)zKHSs}Q5U*En}X8-?y83d1vWzxyy;k4@rf;n2?ifc?GMeK1M z(kKv$WRJ4SnhhHpB5fVJd*)PTnr0D8ES5kr^U6yuF}8(QRdp~}UQq7y+wbfztP`0UrS+PIm$R^1w*Xmtlf+jw4x22GDi>MeHg$A+{OHG3N}(m zONKau`#C>9${G3)K8?rlB%Z?O@I14`3-~I&i_6RpbND&l!0+*<8ghXr4&2LCfY z5v@1W1%sA=m?oX?s1KSJ?kHOo zxvZ^TQ)Tv=2Cv~R>2>)}{NJPWhcw44n78Pk0G2U2S1~d-Fpo4ce?-xRUhKhMUbEbZ zLFSJma@{h<{2{P8A+b5lYnKmWmJ#{@9>h63jPrPc@%c2K!DsOWe2G^x0;w20r zS0aEVJp(;m+*STyVqvf1tUbi{DZa%e+RZ8{UAU0P@hs(WJXgfIqCB2qop&%J{i3WF f{l?M&jK$W(<${!V8{|Ti<8eU5u|9su|c7=`G-ST!{ z3)E^gdNHD&6fo+=gAp~FppfXngC-KA7i&NdB*udt@nE9FgWt?-8))rK3<*2Q%x`wS z`DXU}z2EHS+s7C~d(moUteP>VP~~VWqUK);xAQuultd$Cgdl&$Ojckga$~_r&Yd$m z9*7x;8HgE(8HgE(8Mqx7pm#Pe$~^CVX^q>MftZ0ik^%mFNKoaNif~e*cyv%BQUF4p z0*FF=$^(Q?BEnRJlM;n1ttqnyM6QTZ3`lpfC-`*2RD_cfr8|RkXNY!2l%YWEPJRh7 zogpPLZes>w2Iexrk54&snZt6FyWfz%`#G{)%8j^A5H>YkMoDSef`#RZsw%3k9!?+i z$I_u07K3)PaFAd1nc>k~)bICwccyRV_Br-g)~M?6z0h<#yC9q$%cd!Z1|84x$J+g( zX9Z%Nv^0o~vPSjzcyeP)W1=NF(U=%-Zb@!xOl)pVPE4rEvNg?*bPo-mI63~*#M5VF zWQd*)tYTKAXXl3L2BRH0mxoz$emcybX2QH{k)|)+*}bbu>&+TVMk#_lBcbDa!9KeX z5N3^vfTHfs`(9tc=Ju>nnK#{h(KSQ6&2`hxNjn(K`)+aA3;67O!FSz0KXCXm`!k_^ zJmhocm@)?fzYul^$E;x#1ADJcYdWKLC#@W09NLmQ8cbErRaDolUc0`jb?f%dDWh`9 z(yF^P?Eo?8h0YU>T^Ja1tnlc5GsxSX<#GrkHb% z6`k;eQLiXdODmTc+Dd(uYJ8#`vS{DkzQtFxghgMeuThkPe7vJzQ!9tH27R4sm`Z;@ zwf!hBh{mjy}MN2A%r4G1e&UWR{CT)vi^r(ZQl3F?Vu(nO9M5CI?`@_Q& zPauce^bW=NT6vuB-D?Kno)O!l?Ro57D|LN8A8)fP+ftPJ)ai_nL4B%0%AtN*6>5E| zDe5b%j(10=vk*WZ$wY>?-?>U1NW+KiOaGI!d8oF)Co7 z2DMm$I;_J+B+-gC>_QKA<1wUh2#1ly5NvqxaSR0<$Fq12&*KH0!fSXPXYmH!!3X#d zAK_zsflK%vQ}_W_@hh(3cX@v$(~90beeV)+op9E{B}LPRH=*b=^C)`9&hBMe&uy&e zA1kDc?^#y8YVG0QQkoP2^z-xsC|Aj$;DPV3I`pB3{DFcm=QG3`zD)yoI;%F5biYLcAAn z5uf36e1-3D8CPb>ws)3nf1WGb(R9IceeVcarp11e UQ2F0K1jO(E`28=GcV{>M0MhFeNdN!< diff --git a/src/library/.DS_Store b/src/library/.DS_Store deleted file mode 100644 index 311df1d7c57af475dae87fd58001d3e3c93bf4fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMT}&KR6h3F6bbd-1uq_J}S)~x#0`eQEf-Wp=!Jm}4&;?4}&hAWM(qV?~%r3MP zv+*yQ7~4c2)ToIu#)sC#7psXceXI{AYDl#4!55=G_@dDlpFH>8fwTo4d{NWRP3E3E z=jYxz-~DE8&MpApP~PYOhys8_7n7Pw-6oCM*|niX0!=ANqUar2l9d3w z(U|H09uo^>Jdi~pc__^(ss{{0F(@%mgp)nS+)2g*Srk%)1B!6KU}X#{6y&Q@T#UN| z#)S;~5P=YZjR>&UN2a>ngfmpP-&DVQ3mMzZkoAkqf^B(zd;7N-DJk7nwq25CNiLU9 zCgKbHx{y~5Yc(7%dyN$b4nrLwM_pLe`T-(2#UX4-Reeu690nwIy>r%dv< z3g$3Hd6O1R$J{iKrOV}e_Qv+@Z|mwC7+T#~p;cDhqbSpaj^kUWEi*TJGsS;b{$*em+Px*L4{ir%9Y1gHv+1gL&o_}n?bZB{&d!kNN zr+mIf z1z*EA@ICwpKf%xN3;YJZ!yoV`Mo`5aSdKOL05)JFHsL|+#vbg&J{-U!coawRF&x7w zoW>bEi6%PeVh%mLfG^;S_!7R1Z{T})8L!|+cojbt&lOqYI9L>T-u7`+IB`CiF?QHS5=MiM%*?wsfva~ZJApSG?6tD*xDW>TM4mP*t)tE zRog~{6_I_16h$i~Mhok}BV;Wj?n*#0gB{B+)K|2A;-QoWrw3x;z$e z5sUaNKF{fQ5ij9u_&UCW?+S*k6K^D7Tp;kn&52h?<{aB~X34rn*;@m{yE_Mg5|K)j z)&GaL{{8>XNjS_TL?A?90|Hn&oE#n^^;VyJR%^%TIz<<2%&#aU5222^Fg-8FNv{84 fNaI*wiwCkOBw48a>puj9=RZR|JpVcS?%wWiELY30 From 7b3950a6c15585a0d80d5d92a307ce9899ff5963 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 2 Sep 2019 15:31:18 +1000 Subject: [PATCH 13/36] Fixed indented line --- src/library/rekordbox/rekordboxfeature.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index f79df72bed9..82eee43f076 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -667,11 +667,7 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { // Assume that the key of the file the has been analyzed in Recordbox is correct // and prevent the AnalyzerKey from re-analyzing. - track->setKeys(KeyFactory::makeBasicKeysFromText(index.sibling( - index.row(), fieldIndex("key")) - .data() - .toString(), - mixxx::track::io::key::USER)); + track->setKeys(KeyFactory::makeBasicKeysFromText(index.sibling(index.row(), fieldIndex("key")).data().toString(), mixxx::track::io::key::USER)); return track; } From f4187591fdcfcb81c08e48e97f4993a6c144f942 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 2 Sep 2019 15:33:10 +1000 Subject: [PATCH 14/36] Fixed spelling mistakes --- src/library/rekordbox/rekordboxfeature.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 82eee43f076..68161dbc4d5 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -179,7 +179,7 @@ QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { return QString(); } -int createDevicePLaylist(QSqlDatabase& database, QString devicePath) { +int createDevicePlaylist(QSqlDatabase& database, QString devicePath) { int playlistID = -1; QSqlQuery queryInsertIntoDevicePlaylist(database); @@ -339,7 +339,7 @@ QString parseDeviceDB(mixxx::DbConnectionPoolPtr dbConnectionPool, TreeItem* dev int audioFilesCount = 0; // Create a playlist for all the tracks on a device - int playlistID = createDevicePLaylist(database, devicePath); + int playlistID = createDevicePlaylist(database, devicePath); QSqlQuery queryInsertIntoDevicePlaylistTracks(database); queryInsertIntoDevicePlaylistTracks.prepare( From c8528c564720e8496040feaeb51e23c88f4307fe Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 2 Sep 2019 15:36:03 +1000 Subject: [PATCH 15/36] Link to github commit rather than file version --- 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 68161dbc4d5..76f450a36b9 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -158,7 +158,7 @@ inline bool instanceof (const T* ptr) { } // Functions getText and parseDeviceDB are roughly based on the following Java file: -// https://github.com/Deep-Symmetry/crate-digger/blob/master/src/main/java/org/deepsymmetry/cratedigger/Database.java +// https://github.com/Deep-Symmetry/crate-digger/commit/f09fa9fc097a2a428c43245ddd542ac1370c1adc // getText is needed because the strings in the PDB file "have a variety of obscure representations". QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { From 66f91a076fe535e7b533ca212f74acc540ff0c79 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 30 Sep 2019 11:42:58 +1000 Subject: [PATCH 16/36] Update for correct default sorting --- src/library/rekordbox/rekordboxfeature.cpp | 37 ++++++++++++++++++++++ src/library/rekordbox/rekordboxfeature.h | 3 ++ 2 files changed, 40 insertions(+) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 76f450a36b9..cf4e85a6a23 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -660,6 +660,43 @@ RekordboxPlaylistModel::RekordboxPlaylistModel(QObject* parent, : BaseExternalPlaylistModel(parent, trackCollection, "mixxx.db.model.rekordbox.playlistmodel", "rekordbox_playlists", "rekordbox_playlist_tracks", trackSource) { } +void RekordboxPlaylistModel::initSortColumnMapping() { + // Add a bijective mapping between the SortColumnIds and column indices + for (int i = 0; i < TrackModel::SortColumnId::NUM_SORTCOLUMNIDS; ++i) { + m_columnIndexBySortColumnId[i] = -1; + } + + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ARTIST] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_TITLE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TITLE); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ALBUM] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUM); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_ALBUMARTIST] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_YEAR] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_GENRE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GENRE); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COMPOSER] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_GROUPING] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_TRACKNUMBER] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_FILETYPE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_NATIVELOCATION] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COMMENT] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMMENT); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_DURATION] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_BITRATE] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_BPM] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_REPLAYGAIN] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_DATETIMEADDED] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_TIMESPLAYED] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_RATING] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_KEY] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_PREVIEW] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_COVERART] = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART); + m_columnIndexBySortColumnId[TrackModel::SortColumnId::SORTCOLUMN_POSITION] = fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_POSITION); + + m_sortColumnIdByColumnIndex.clear(); + for (int i = 0; i < TrackModel::SortColumnId::NUM_SORTCOLUMNIDS; ++i) { + TrackModel::SortColumnId sortColumn = static_cast(i); + m_sortColumnIdByColumnIndex.insert(m_columnIndexBySortColumnId[sortColumn], sortColumn); + } +} + TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { qDebug() << "RekordboxTrackModel::getTrack"; diff --git a/src/library/rekordbox/rekordboxfeature.h b/src/library/rekordbox/rekordboxfeature.h index e3693022467..177d5cd4bd9 100644 --- a/src/library/rekordbox/rekordboxfeature.h +++ b/src/library/rekordbox/rekordboxfeature.h @@ -50,6 +50,9 @@ class RekordboxPlaylistModel : public BaseExternalPlaylistModel { QSharedPointer trackSource); TrackPointer getTrack(const QModelIndex& index) const override; bool isColumnHiddenByDefault(int column) override; + + protected: + virtual void initSortColumnMapping(); }; class RekordboxFeature : public BaseExternalLibraryFeature { From 108904710772e5e74aae983c9f703e39ebb57853 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 30 Sep 2019 11:54:27 +1000 Subject: [PATCH 17/36] Fix Unicode string conversions --- src/library/rekordbox/rekordboxfeature.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index cf4e85a6a23..0b454057373 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -173,7 +173,9 @@ QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { } else if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_long_utf16be_t* longUtf16beString = static_cast(deviceString->body()); - return QString::fromStdString(longUtf16beString->text()); + QTextCodec *codec = QTextCodec::codecForName("UTF-16BE"); + std::string utf16be = longUtf16beString->text(); + return codec->toUnicode(QByteArray(utf16be.c_str(), utf16be.length())); } return QString(); From 4352e113212cf15127bb275ae66f6c2d018b0323 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 30 Sep 2019 16:37:15 +1000 Subject: [PATCH 18/36] Timing information (cues, hotcues, loops & beatgrids) --- build/depends.py | 20 ++- src/library/rekordbox/rekordboxfeature.cpp | 171 ++++++++++++++++++++- 2 files changed, 187 insertions(+), 4 deletions(-) diff --git a/build/depends.py b/build/depends.py index c8b4b52aa12..137cf26686b 100644 --- a/build/depends.py +++ b/build/depends.py @@ -491,6 +491,21 @@ def configure(self, build, conf): build.env.Append(CPPDEFINES=['KS_STR_ENCODING_NONE']) build.env.Append(CPPPATH="#lib/kaitai") +# For determining MP3 timing offset cases in Rekordbox library feature +class MP3GuessEnc(Dependence): + + def sources(self, build): + return [ + "lib/mp3guessenc-0.27.4/mp3guessenc.c", + "lib/mp3guessenc-0.27.4/tags.c", + "lib/mp3guessenc-0.27.4/decode.c", + "lib/mp3guessenc-0.27.4/bit_utils.c", + ] + + def configure(self, build, conf): + build.env.Append(CPPPATH='#lib/mp3guessenc-0.27.4/') + + class Ebur128Mit(Dependence): INTERNAL_PATH = 'lib/libebur128' INTERNAL_LINK = False @@ -1048,9 +1063,10 @@ def sources(self, build): "src/library/itunes/itunesfeature.cpp", "src/library/traktor/traktorfeature.cpp", - + "src/library/rekordbox/rekordboxfeature.cpp", "src/library/rekordbox/rekordbox_pdb.cpp", + "src/library/rekordbox/rekordbox_anlz.cpp", "src/library/sidebarmodel.cpp", "src/library/library.cpp", @@ -1553,7 +1569,7 @@ def depends(self, build): FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib, ProtoBuf, Chromaprint, RubberBand, SecurityFramework, CoreServices, IOKit, QtScriptByteArray, Reverb, FpClassify, PortAudioRingBuffer, LAME, - QueenMaryDsp, Kaitai] + QueenMaryDsp, Kaitai, MP3GuessEnc] def post_dependency_check_configure(self, build, conf): """Sets up additional things in the Environment that must happen diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 0b454057373..68f5323c2e0 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -7,15 +7,20 @@ #include #include +#include "library/rekordbox/rekordbox_anlz.h" #include "library/rekordbox/rekordbox_pdb.h" #include "library/rekordbox/rekordboxfeature.h" +#include + #include "library/library.h" #include "library/librarytablemodel.h" #include "library/missingtablemodel.h" #include "library/queryutil.h" #include "library/trackcollection.h" #include "library/treeitem.h" +#include "track/beatfactory.h" +#include "track/cue.h" #include "track/keyfactory.h" #include "util/db/dbconnectionpooled.h" #include "util/db/dbconnectionpooler.h" @@ -32,6 +37,7 @@ namespace { const QString kPDBPath = "PIONEER/rekordbox/export.pdb"; const QString kPLaylistPathDelimiter = "-->"; +const double kLongestPosition = 999999999.0; void clearTable(QSqlDatabase& database, QString tableName) { QSqlQuery query(database); @@ -654,6 +660,101 @@ void clearDeviceTables(QSqlDatabase& database, TreeItem* child) { transaction.commit(); } +void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, QString anlzPath) { + if (!QFile(anlzPath).exists()) { + return; + } + + qDebug() << "Rekordbox ANLZ path:" << anlzPath << " for: " << track->getTitle(); + + std::ifstream ifs(anlzPath.toStdString(), std::ifstream::binary); + kaitai::kstream ks(&ifs); + + rekordbox_anlz_t anlz = rekordbox_anlz_t(&ks); + + double cueLoadPosition = kLongestPosition; + double cueLoopPosition = kLongestPosition; + double loopLength = -1.0; + + for (std::vector::iterator section = anlz.sections()->begin(); section != anlz.sections()->end(); ++section) { + switch ((*section)->fourcc()) { + case rekordbox_anlz_t::SECTION_TAGS_BEAT_GRID: { + rekordbox_anlz_t::beat_grid_tag_t* beatGridTag = static_cast((*section)->body()); + + QVector beats; + + for (std::vector::iterator beat = beatGridTag->beats()->begin(); beat != beatGridTag->beats()->end(); ++beat) { + int time = static_cast((*beat)->time()) - timingOffset; + // Ensure no offset times are less than 1 + if (time < 1) { + time = 1; + } + beats << (sampleRate * static_cast(time)); + } + + QHash extraVersionInfo; + + BeatsPointer pBeats = BeatFactory::makePreferredBeats( + *track, beats, extraVersionInfo, false, false, sampleRate, 0, 0, 0); + + track->setBeats(pBeats); + } break; + case rekordbox_anlz_t::SECTION_TAGS_CUES: { + double sampleRateFrames = sampleRate * 2.0; + rekordbox_anlz_t::cue_tag_t* cuesTag = static_cast((*section)->body()); + + for (std::vector::iterator cueEntry = cuesTag->cues()->begin(); cueEntry != cuesTag->cues()->end(); ++cueEntry) { + int time = static_cast((*cueEntry)->time()) - timingOffset; + // Ensure no offset times are less than 1 + if (time < 1) { + time = 1; + } + double position = sampleRateFrames * 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: { + // As Mixxx can only have 1 saved cue point, use the first occurance of a memory cue relative to the start of the track + if (position < cueLoadPosition) { + cueLoadPosition = position; + } + } break; + case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { + // As Mixxx can only have 1 saved loop, use the first occurance of a memory loop relative to the start of the track + if (position < cueLoopPosition) { + cueLoopPosition = position; + loopLength = sampleRateFrames * static_cast((*cueEntry)->loop_time() - (*cueEntry)->time()); + } + } break; + } + } break; + case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { + CuePointer pCue(track->createAndAddCue()); + pCue->setPosition(position); + pCue->setHotCue(static_cast((*cueEntry)->hot_cue() - 1)); + pCue->setType(Cue::CUE); + } break; + } + } + } break; + default: + break; + } + } + + if (cueLoadPosition < kLongestPosition) { + track->setCuePoint(CuePosition(cueLoadPosition, Cue::MANUAL)); + } + if (cueLoopPosition < kLongestPosition) { + CuePointer pCue(track->createAndAddCue()); + pCue->setPosition(cueLoopPosition); + pCue->setLength(loopLength); + pCue->setSource(Cue::MANUAL); + pCue->setType(Cue::LOOP); + } +} + } // anonymous namespace RekordboxPlaylistModel::RekordboxPlaylistModel(QObject* parent, @@ -703,6 +804,57 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { qDebug() << "RekordboxTrackModel::getTrack"; TrackPointer track = BaseExternalPlaylistModel::getTrack(index); + QString location = index.sibling(index.row(), fieldIndex("location")).data().toString(); + + // The following code accounts for timing offsets required to + // correctly align timing information (cue points, loops, beatgrids) + // exported from Rekordbox. This is caused by different MP3 + // decoders treating MP3s encoded in a variety of different cases + // differently. The mp3guessenc library is used to determine which + // case the MP3 is clasified in. See the following PR for more + // detailed information: + // https://github.com/mixxxdj/mixxx/pull/2119 + + int timingOffset = 0; + + if (location.toLower().endsWith(".mp3")) { + int timingShiftCase = mp3guessenc_timing_shift_case(location.toStdString().c_str()); + + qDebug() << "Timing shift case:" << timingShiftCase << "for MP3 file:" << location; + + switch (timingShiftCase) { +#ifdef __COREAUDIO__ + case EXIT_CODE_CASE_A: + timingOffset = 13; + break; + case EXIT_CODE_CASE_B: + timingOffset = 11; + break; + case EXIT_CODE_CASE_C: + timingOffset = 26; + break; + case EXIT_CODE_CASE_D: + timingOffset = 50; + break; +#elif defined(__MAD__) + case EXIT_CODE_CASE_A: + case EXIT_CODE_CASE_D: + timingOffset = 26; + break; +#elif defined(__FFMPEG__) + case EXIT_CODE_CASE_D: + timingOffset = 26; + break; +#endif + } + } + + double sampleRate = static_cast(track->getSampleRate()) / 1000.0; + + QString anlzPath = index.sibling(index.row(), fieldIndex("analyze_path")).data().toString(); + readAnalyze(track, sampleRate, timingOffset, anlzPath); + QString anlzPathExt = anlzPath.left(anlzPath.length() - 3) + "EXT"; + readAnalyze(track, sampleRate, timingOffset, anlzPathExt); // Assume that the key of the file the has been analyzed in Recordbox is correct // and prevent the AnalyzerKey from re-analyzing. @@ -740,7 +892,8 @@ RekordboxFeature::RekordboxFeature(QObject* parent, TrackCollection* trackCollec << "duration" << "bitrate" << "bpm" - << "key"; + << "key" + << "analyze_path"; m_trackSource = QSharedPointer( new BaseTrackCache(m_pTrackCollection, tableName, idColumn, columns, false)); QStringList searchColumns; @@ -825,12 +978,26 @@ TreeItemModel* RekordboxFeature::getChildModel() { QString RekordboxFeature::formatRootViewHtml() const { QString title = tr("Rekordbox"); - QString summary = tr("Reads playlists and folders from Rekordbox prepared removable devices."); + QString summary = tr("Reads the following from Rekordbox prepared removable devices:"); + QString item1 = tr("Playlists"); + QString item2 = tr("Folders"); + QString item3 = tr("First memory cue"); + QString item4 = tr("First memory loop"); + QString item5 = tr("Hot cues"); + QString item6 = tr("Beatgrids"); QString html; QString refreshLink = tr("Check for attached Rekordbox devices (refresh)"); html.append(QString("

%1

").arg(title)); html.append(QString("

%1

").arg(summary)); + html.append(QString("
    ")); + html.append(QString("
  • %1
  • ").arg(item1)); + html.append(QString("
  • %1
  • ").arg(item2)); + html.append(QString("
  • %1
  • ").arg(item3)); + html.append(QString("
  • %1
  • ").arg(item4)); + html.append(QString("
  • %1
  • ").arg(item5)); + html.append(QString("
  • %1
  • ").arg(item6)); + html.append(QString("
")); //Colorize links in lighter blue, instead of QT default dark blue. //Links are still different from regular text, but readable on dark/light backgrounds. From 8876f5b8ec9af9fd9a948f2c2aa1483b2a486e12 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 30 Sep 2019 16:41:49 +1000 Subject: [PATCH 19/36] Add mp3guessenc lib --- .DS_Store | Bin 0 -> 12292 bytes lib/.DS_Store | Bin 0 -> 8196 bytes lib/mp3guessenc-0.27.4/.DS_Store | Bin 0 -> 6148 bytes lib/mp3guessenc-0.27.4/COPYING | 502 ++++ lib/mp3guessenc-0.27.4/ChangeLog | 364 +++ lib/mp3guessenc-0.27.4/DOCS | 63 + lib/mp3guessenc-0.27.4/Makefile | 93 + lib/mp3guessenc-0.27.4/TODO | 8 + lib/mp3guessenc-0.27.4/bit_utils.c | 316 ++ lib/mp3guessenc-0.27.4/bit_utils.h | 69 + lib/mp3guessenc-0.27.4/decode.c | 118 + lib/mp3guessenc-0.27.4/decode.h | 23 + lib/mp3guessenc-0.27.4/mp3g_io_config.h | 54 + lib/mp3guessenc-0.27.4/mp3guessenc.c | 3383 ++++++++++++++++++++++ lib/mp3guessenc-0.27.4/mp3guessenc.h | 261 ++ lib/mp3guessenc-0.27.4/scrambled.h | 4 + lib/mp3guessenc-0.27.4/tags.c | 1143 ++++++++ lib/mp3guessenc-0.27.4/tags.h | 112 + src/library/rekordbox/rekordbox_anlz.cpp | 317 ++ src/library/rekordbox/rekordbox_anlz.h | 644 ++++ 20 files changed, 7474 insertions(+) create mode 100644 .DS_Store create mode 100644 lib/.DS_Store create mode 100644 lib/mp3guessenc-0.27.4/.DS_Store create mode 100644 lib/mp3guessenc-0.27.4/COPYING create mode 100644 lib/mp3guessenc-0.27.4/ChangeLog create mode 100644 lib/mp3guessenc-0.27.4/DOCS create mode 100644 lib/mp3guessenc-0.27.4/Makefile create mode 100644 lib/mp3guessenc-0.27.4/TODO create mode 100644 lib/mp3guessenc-0.27.4/bit_utils.c create mode 100644 lib/mp3guessenc-0.27.4/bit_utils.h create mode 100644 lib/mp3guessenc-0.27.4/decode.c create mode 100644 lib/mp3guessenc-0.27.4/decode.h create mode 100644 lib/mp3guessenc-0.27.4/mp3g_io_config.h create mode 100644 lib/mp3guessenc-0.27.4/mp3guessenc.c create mode 100644 lib/mp3guessenc-0.27.4/mp3guessenc.h create mode 100644 lib/mp3guessenc-0.27.4/scrambled.h create mode 100644 lib/mp3guessenc-0.27.4/tags.c create mode 100644 lib/mp3guessenc-0.27.4/tags.h create mode 100644 src/library/rekordbox/rekordbox_anlz.cpp create mode 100644 src/library/rekordbox/rekordbox_anlz.h diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a4d976474f50c08de9e90a513ecaf8097f7eb1d0 GIT binary patch literal 12292 zcmeHNYitx%6h3GBVD3zTDX$hvVWm(Dlv-M#1zOm)P$*y|w9rx>bsr<0xScIKyDd;_ z>NA@7!bf}|zV!zXei)Q!3`UJYl=w)aqEQny>JNYTPV|qSd++XcyKNC4h{8-Vb7tEe*Knb-4eg)1C%FFq8e)74~%oO*>Lr`T@Gq(latgDNNO}>L;qJ z+w^f=rqO*_XT%Iy_9i226K&JS+U=q427ky}A2v9@P0#jwgZ^mH8!>8v!A5hBVK@6j z!DxqNw?vFx5#G{jhr*FM(b%TzQQO#HP=B+_SV2;Di%GZR-E?5efzgxl+%spFlvh^Q z_Kz8>XRG7YiOpT6ZTf=7#%?nZY2WO%{e~4VtsSjqz_23b6{Znp*{-9k$?|p>{n=yn zF*&)K))MuZJELY~w_czq@?QDcbfzCswg%|AmxcmdG9WlwzDAw4@b`y`H?qTy-APZPs+9X~jfK)iOm7u#G{l&j`x9EmSKMeJyM5l4_HUOVlbQ8;e);l+4Q_`!U>`gQhu|4_5st$NcoR;-yYLZw0bjyb@HKn~-@}ja z6S`2tEF6uKaRwIPEG)#ixBwSo1y*7;*5fK{z%{rYH)AVq#cgPyg&_>1jaT6fcq86~ zH{*TyC?3FrcnF`x=j402xXtmcL6Z0pUpm7fCPv7H3~~249^x@Mn30D_{0#6xl$qEZxN5Cf$+wYzzDqMjJ-lT?ZWN^!v0lQD)+ zP&}RF+@d?6M^fkLXu#3HPz_{K(?IV3N6Cn~>g*&VgdR?x`=1)k6aI1ICrq3)edg?W zWffI5iCj4)Jsy<7C#UTqQ)RaH+X=lycj8@m zH$H^>rF|u`-_?Wd4WAw`wywso6%1KBsD;nyX<$Pb{Jw5GW`b}co)5pDfS*iZquI7b zLYa*bZzO8hdc#}z)p~EF-4{D=3Wb7+bFXicz@ZcBLRQ3UT1Hq16EvhQTbt=ihq`M+ zQ7d4J`!q_1!stP5dTvioc|}FByS%cu*xfU~Yyq7wE?d;w%aoj1^Q#*!@7TSk=c?YT z@0WwbSYU#&@;Y&RU|@Iy)iKw-@!<`iYo=v2tXaEm7+3iF*g65EhxK0?FoEl71s503 zU$oS-Qh?8K;~7f=(IYv8a2{cv-0H_@d0KwlS5ie30^14TEkG=DYcqy|ZVqoXw^ld| z=q{SAG2zC-mH3n}uY^g!E!>B6$OQD3z|0UNQ20JonYH}}g<+=Y9v zN5JmeaIf&kedLc1<0E(&kK)Vt3ciXb@m>5#nq|U6FHM-KIKhtx8EZToY#-KG>sPI= zIXA{SI2wq8P~uCbM1wO90E5DYn8CHFY}!b+ETe>|+32JCr;u$VMFW0G(SW9ZiUzSs ze6B{dA+MsR$y=8)Q5e!Q#3ftEDtP5cPnX;!tcur&^i0WJ&T2#rO9%0!x;o@8&dCZmN?IJi8BGT zob|uIvE2Sgwm2AMUEn3RGcvhzQF41q#)AZ)AFkwe_5YP4|Nkc~%sDz5a5QjUY5>z$G_I(np~aUlQb)?|d_pe*pkH3Puw^6aXYT7-}kYH)+hzjukBuSc(ae@&O89f=53SoE6cbA!HzA zAY>q9AY>q9;Qzn?eY07UR{8D=Z`g+ngbdu446ye@j1Gp$0OtkrR|j>x1R!K30B>|o zbpX$a1(*zQULa4UF-7%&K`I6%28wj@$5=RFGQfF(A{|hq0|s}-ph7`@cgl6EYg1+PQTpJZ44x<8Q#&IerAUOLw^LzX!`rfutV9bdN`GtZMP z!=#YWwB;D?bcb7T43CeK5)OW&F)cbf+meXa#}ch`^|9He=0t0K>`+T$Zcdge_B0*s zJ(W8*GkbpSsW-%WF?eS{=?&|DbFGWFy{S&I_(*8@- zekV7MX|;Db>o9t;+WFl;v3& z+Z>#>41Z!o_p+vASkCwf;?eP~)0UZ+%Oao41CE|E7s^VtEfrB!9WG?7GX={(r`1T3 z7^_OHmbFi$QG@RLh-vc!WDEsC0+5|n8V2gbJh6m>Z3=5jP;uMF$S%+{wqpteie73m3f z@8i1X_f4A4zq9I=q!H$6Hw@FLN$wMSF4rXE!R|{?NrFzo0F2TfRg-W5UWAw6BD@T5 z!TWF-K87#h3S5P+;T!lCet@6gXZQtvh2P;1_!IuZ2&%XlORy5}#(S_9>u^6NuniC6 z5j={=@Hjq-eb|p9sAColG||CnoWWTTyk z0Prh1S<@p*Dh(ddNcyHK?JiXwTql)YD`8t#zIEI79o4&Q8=6~>v@dP#4I8;kYu5{m z>q-cR3u;j?Lc|r(%AM*i<<8ZlKS&ZZ&q_>3+NOn_(aMV0U8<@vB}x2}NmoM>yUZ`^ z_L4V3J~ps&Ky8vq(pnL}R1?a*5{XyaAiQnLVVUHu6$|hEN>U;fYa97Ma)+X@XN@HI zU&MF`K7ubuhzoEHek2k8Mgshs#3x}nMoE0TunMbjAIYyC8?X^uuajU8_TodB!T}t@ zGzl?7LNxI-PT(2LqmKm=57sKhsZ`#v zU1yx)7iGPCsPJn287LA{h_d>>d-ebS*MFkn3_=D%25uPxSlpHB>Y(9QdsVYqJ4VMT zI#^?I^8$Gabu5J0c{xrPl;hkM3w+4{=LPaq%HgSmqLRPme*}cz|M2_I1bCabe*hdv B-RJ-S literal 0 HcmV?d00001 diff --git a/lib/mp3guessenc-0.27.4/.DS_Store b/lib/mp3guessenc-0.27.4/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/lib/mp3guessenc-0.27.4/ChangeLog b/lib/mp3guessenc-0.27.4/ChangeLog new file mode 100644 index 00000000000..18fbd3f0760 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/ChangeLog @@ -0,0 +1,364 @@ + +mp3guessenc changelog +====================== + +version 0.27.4 (2018/07/15 "It's happened before it'll happen again") + - fixed detection of metadata tags at the file head which wrongly aborted when + a large padding area was found at the tail of the search buffer + + +version 0.27.3 (2018/02/11 "I Still Do") + - more efficient detection of metadata tags at the file head, now using a fast + memory buffer for seeking tag signatures. This allows detection even in + troubled files where tags are stored among bytes of junk. + - the program now returns a useful exit code after successful encoder + detection. It is a positive non zero value and it is internally used as an + index into an encoder_table, so it brings out the same information. The + exit code will be zero when no detection is performed or layerI/II streams + are analysed. It will be less than zero in case of errors. + - fixed wrong calculation in id3v2 footer detection + - forced message "Music CRC verification" when `-r' is used. When a valid lame + tag is found, a crc verification is performed, otherwise the message will + simply report "not performed". + - re-arranged "length of original audio" field into its own tag. When it comes + from the lame tag, then it'll be showed with the lame tag, when it comes + from ofl block, then it'll appear into the ofl block (created on purpose). + - lame music verification. A crc16 calculated over the whole audio bitstream + is also stored into the tag, so this is now used to validate the audio data. + Due to byte reflection, the computation is quite slow, so this is disabled + by default. You can force it with command line switch `-r'. + - lame tag verification. lame stores several useful information into its own + tag, such as a crc16 sum for valitading the tag itself. The issue is this + crc IS NOT the same involved into audio frame validation, but it's a similar + algorithm using byte reflection. This means every byte feeded into the + algorithm has to be reflected first, and the stored crc has to be reflected + before final comparison. (WTH!!!) + - compute "Length of original audio" using the number of frames reported by + Lame tag, not the detected frame amount (this may vary and leads to wrong + results). + NOTE: around 2003/2004, some development versions of lame created tags + reporting a wrong frame amount, that is the real number of frames created, + increased by 1 (the tag itself). Reading those tags leads to wrong sample + count estimation, because the number of frames stored is misleading. There + is no way of telling how many lame alpha/beta versions affected by that bug + got used at that time, so discrepancies may appear. When facing some + unmatched sample amount, use your common sense. + The bug was finally fixed into the stable 3.94 release (early 2004). + - fixed install command into Makefile + - fix for some wrong xing tags reporting a disabled 'original' flag while the + whole bitstream shows it enabled + + +version 0.27.2 (2017/08/25 "Roundabout") + - small changes into I/O header mp3g_io_config and now mp3guessenc can also + be built on AmigaOS and OS/2 (and it works, of course!) + - detection for mpeg multi channel streams! + ISO MPEG-2 provides specifications for a new bitstream for center, + surround channels, low frequency enhancement channel and multi lingual + audio, described into ancillary data of mpeg1 layerII streams (usually, + but it may also fit into other layers). + Poor free documentation available (and the standard itself has not been + widly adopted), nevertheless stream recognition works in almost any cases. + - fixes into tags.c for every integer field found into tags. Now any integer + field is read in an endian indipendent way, so the routines will work on + big-endian machines too + - fixes into crc16 calculation routine (never used till now) + - support for "reduced" lame tag written by Lavc when encoding layerIII + files. The encoding engine is lame indeed, nevertheless the tool fills some + tag fields with unreliable infos + - fix for some wrong lame tags reporting a disabled 'original' flag while the + whole bitstream shows it enabled + + +version 0.27.1 (2017/05/03 "Italian Espresso") + - read whole audio data from layerII streams and report ancillary amount + - read whole audio data from layerI streams and report ancillary amount + - retrieve bitrate from freeformat stream frames and reject those causing + bitrates > 640 kbps + - new fix for crc-protected vbr streams created by fhg encoders. Their VBRI + tag has a disabled CRC flag in contrast with the one stored into the real + audio stream, and this caused mp3guessenc to reject valid audio frames + - re-arranged the search for the very first frame. The two code blocks, one + seeking a corrupted freeframe index (written by ancient lame encoders) and + the other seeking for a vbr tag (both into the first frame) are now + merged together. This makes seeking valid frames among the junk even more + robust + - set every floating point variable as single precision (no need for double) + - fix off-by-one bug into memsrch which failed searches in some particular + cases + + +version 0.27 (2016/12/15, "Ancient Melodies Enchanting Lightnings Into Art") + - convert any bit read operations to memory (byte arrays) accesses, instead of + file stream access (UP TO 24% FASTER ANALYSIS!!!) + - shorten analysis of a last broken layerIII frame when even side information + is incomplete + - check validity of bit allocation area in layerI frames + - take apart side information analysis (for layerIII) and error check + - check crc16 validity in all layers + - better macro bounds around "seek option" + - restored warning message from broken frames also for non-layerIII + - changed most of the variable signed-ness: if something needs to have a sign, + then it will have, everything else won't + + +version 0.26.2 (2016/12/14, "Dark Black") + - fixes into mp3g_io_config.h for recent mingw compliancy + - fixed a misplaced (and very dangerous) buffer pointer + + +version 0.26.1 (2016/02/26, "Black") + - limited the most cpu-demanding computations to few challenging bitstreams, + so in most cases the analysis will run faster + - detect wave riff header for mpeg audio bitstreams wrapped into wav files. + - avoid warning message about uncomplete frame when non-layerIII + - fixed ape tag detection for 64 bit builds + - replaced some old forgotten off_t casts + - check for id3v2 at the end of the file (may be found for revisions >=4) + - detect broken/corrupted musicmatch tag, with extraction of minimal information + + +version 0.26 (2016/01/17, "Hotel Torino") + - the search for metadata tags is now a loop (so, more useless tags appeared, both + at the head and at the tail of several files) + - check for ape tags at the head of the file also + - print offset (hex) for any metadata tags found + - stop using `off_t' for bit pointers, created a new data type `bitoffs_t' which is + in fact a `long long'. This allows mp3guessenc to be cleaner and run smooth under + Android also, where the `stat' struct doesn't use `off_t' for file size (st_size) + but `long long', since `off_t' type is 32 bit long only. + - fix: check file size before beginning analysis, the file may be too large even when + its size doesn't make the `st_size' field (stat) overflow. (Check updated with + respect to the new data type, as above) + + +version 0.26preview (2015/02/19, "Let It Snow") +(this had to be 0.25.3 but the amount of new features +and changes in source code made me decide for the jump) + - Small fixes into Makefile + - Do not scan empty files + - Added detection for the ancient (yet free and open source) Blade Encoder. + - Added support for MusicMatch tag used (only) in early versions of MM Jukebox software. + - Added support for Lyrics3v2 tag + - Added support for Lyrics3v1 tag + - Enhanced robustness of mpeg frame detection - now after a freeformat frame mp3guessenc + seeks for more freeformat ones, and vice versa. + - Replaced `strstr' with the new, more reliable `memsrch' (I like it more than the GNU + extension `memmem') + - Added comfortable `-S' (skip) option you can use if you suspect the junk at the head of a + file can make difficult its recognition by mp3guessenc. When a long scanning can bring only + a meaningless bunch of frames and a ton of sync errors, well, try it! + While I'm on it, I want to say more about this feature. I always thought the ability to skip + a given amount of bytes could be useful, yet I never wanted to have it into mp3guessenc. In + a "perfect final" release, mp3guessenc should be able to recognize each and every kind of + tag/data/junk any mad encoding software added at the head of an mpeg stream, but truth is, + we're still not there. I can't even tell if it will remain available forever as other command + line options will, but now I really need it, and now it's there. + - Added a full set of command line options, offering flexible control about what mp3guessenc + goes to report after analysis. Old behaviour is still kept as long as you set no options + on the command line (or just type `-e' to show everything). For quiet detection, use `-g' + (show only the guessing) or even `-n' for absolutely silent operation - only the encoder + name will be printed. + + +version 0.25.2 (2014/07/16, "25 Days A Stranger") + - Same consistency enhancements of `skip_p23b' went into `skipback_p23b' + - More accuracy in `skip_p23b' which returned meaningless values when the subsequent block was + missing. This is now fixed and when the block is not present, the skip operation stops, + returning proper results + - Fixed behaviour with odd frames reporting they have part2+part3 data block with 0 bit + - Fixed calls to part2+part3 handling routines in order not to move the bit pointer toward + bits that are not yet loaded + - Replaced cryptic `A!' and `B!' alerts with intelligible warning messages. + - Fixed pointer handling into `skip_p23b', now p23b_pos can only have allowed values and + function calls got slighly modified + - Logical fixes into `skipback_p23b' which was always assuming p23b_pos was at the last bit of + the block + - Renamed `bitstreamcpy' to more friendly `p23b_cpy' + - Optimized search into `seek_p23b', now performing up to 2 cycles (the same function used to + cycle up to NP23B-1 times!) most of the times - BREEZING SPEED! + - Removed redundant seek code into `load_into_p23b' + - Added consistency check for endOfLastPart3 pointer (avoids resetting by mistake the enhanced + feature bytes) + - New handling for endOfLastPart3 pointer, bringing a few more ancillary data blocks detected + - Removed useless and redundant (and even harmful) seek before scan for ancillary data + - Print large file support status (yes/no). Yeah, I added this to 0.25.1 but forgot to mention + + +version 0.25.1 (2014/04/28, "Fuori Dal Tempo") + - Fixed a very old (and critical) bug in `load_into_p23b' + - Replaced the old `readOneByte_p23b' function with the shiny new `bitstreamcpy', useful + when collecting bytes from ancillary data. This new function is born with speed, efficiency + and reliability in mind. The old function returned one byte at a time, it was error-prone + and the whole process was slow. (for more detail, see comments to the functions themselves) + - Various fixes into `bit_utils' module in order to decrease complexity and increase speed. + - Fixed analysis of the very last frame for files having it shorter than expected + (see http://sourceforge.net/p/mp3guessenc/news/2014/04/unexpected-hassle-behind-a-simple-idea/). + Be aware that some subtle changes in your results may appear due to this enhanced + precision in mp3guessenc. In fact, now a new valid shorter frame may (or may not) slightly + decrease the overall data rate (this is always calculated as amount_of_data/duration); + further, the detected amount of ancillary data is likely to decrease because some data + once taken as useless padding, are now a compliant frame. (btw, I was wrong) + - Faster, more robust, more flexible `strencode' utility + + +version 0.25 final (2013/09/22, "Farewell") + - increased number of frames stored in memory due to most demanding streams (lsf) + - source cleanup + + +version 0.25 beta 2 (2013/05/24, "List Games") + - minor fixes + - print min/max global gain values + - small updates to the guessing engine + - new unique function for analyzing ancillary data + - decoded original length and encoder delay into OFL (original file length) block, hence + mp3guessenc is now able to show both these infos (this block may be found into streams + produced by fraunhofer encoders, such as mp3pro and mp3surround ones but also in recent + `plain' mp3 streams. Please note that in those cases VBRI tag often reports a wrong value + for encoder delay, thus a bug lies into the encoder, not mp3guessenc -- the program just + prints out what it reads). In case of mp3pro streams, both encoder delay and original + file length values (in sample units) refer to the original full-quality wave file. + + +version 0.25 beta 1 (2013/03/24, "Please Please Please Let Me Release What I Want") + - added a version check on the lame string found in xing vbr tag in order to + avoid reading a lame tag where there isn't one (first lame tag appeared in v3.90, + previous versions just added a signature string to the xing vbr tag) + - detection of Ape tag v1/v2 - actually read main data, then skip it + - increased buffer size due to the maximum length freeformat frames can have (5760 bytes + when encoding 8 kHz content into a 640 kbps stream -- quite strange, yet still feasible) + - fixed detection of mp3Surround streams encoded with OFL (original file length) feature + - added detection of mp3Surround streams too! + (mp3Surround is another extension of mp3 which brings multi channel audio out of a + regular mp3 stream. Almost an un-flexible (only encodes 5.1 channels - 44.1 kHz and + 48 kHz, and that's all) and unknown one, though) + - mp3guessenc is now able to detect mp3PRO streams! + (Well, the detection method is somewhat trivial but it works with any sample stream I + could test. Actually, I'm not aware of any other utility able to tell `normal' + mp3 streams from mp3PRO ones without the need of external closed libraries.) + - many integer variables are now declared as off_t (which can become 64 bit integers + on systems supporting them) for correct handling of large files (> 256MB) + Now mp3guessenc can scan files up to 2^60 bytes long. Thanks to Matthias Andree for + pointing me to a portable solution for large files support. + - show ancillary data infos (for layerIII only) + - fixed encoder delay info for both fhg and lame encodings + + +version 0.25 alpha 24 (2013/02/21, the "Owner Of A Lonely Release" release) + - added workaround for buggy lame vbr tag in freeformat streams + - enhanced detection of frame size in layerIII free format streams + - sync error counter now gets updated + - update GUESSING ENGINE! Added support for gogo, helix and updated detection for known + encoders (xing/lame/fhg) + - refined memory requirements: less buffers, better managed + - more comments in bit-handling routines (now everything is clear to me, eh eh) + - fixed out-of-bound write in `extract_lame_string' + - enhanced modularity: source was splitted into modules (so mp3guessenc.c is lighter now!) + - refined detection of lame string into the first frame (xing/lame tag) + - fixed regression in lame string detection into the very last frame + - added missing structure initialization + - added tag offset value (for xing/lame/vbri tags) + - replaced old `for' cycles with more efficient memset/memcpy routines + - replaced `stat' with `stat64' in order to avoid crashes when managing large files + - fixed detection of long/mixed/switch blocks in compliance with lame results (short blocks + were already correctly detected) + - mp3guessenc is now lame-preset aware + - fixed nice coherent alignment + + +version 0.25 alpha 23 (2012/04/22, the "Sparrow's Nightmare" Haiku Powered Re-release) + - minor modifications applied so now you can build and run mp3guessenc on Haiku R1 alpha3 also! + + +version 0.25 alpha 23 (2012/04/21, the "Sparrow's Nightmare" release) + - added a check so now the very last frame won't be read if broken (it will be reported as garbage) + - enlarged buffers in order to accomodate big free format frames (I see this as a workaround and I + will have to fix the whole memory allocation issue when I'm into the layer III decoding details) + - fixed framesize detection for the very first frame + - fixed accurate info tag detection + - offset of the first frame is now printed in hex base too + - updated to a comfortable time print + - redefined some macros, optimized integer variables + + +version 0.25 alpha 22 (2012/01/16, the "70 Years Are Not Enough" release) + - Reduced printed details if the file is not layerIII (part of the scan is not performed) + - Fixed details about JStereo encodings in layer I/II, now simple stereo frames are clearly printed. + - Updated frame histogram in order to also show custom frames generated in free format encodings + - Free format bitstreams are now supported! + - Changed the framesize handling: framesize values are now calculated before any scanning and so + the process is *very* fast! Before this modification, in a N-frame stream we had N calls to + get_framesize, now this routine is called just 14 times, regardless of the stream size! + - Optimized the size for bitrate arrays and histogram, since it can go up to 14, not 15 + (bitrate index 15 is invalid!) + - Added a check about file type, so now special files (directory/block/char dev) are no more scanned + + +version 0.25 alpha 21 (2011/12/26, the "Smells Like Release Spirit" release) + - `print_version()' has now two operation modes: brief and verbose. + - Detection (and warning about) of garbage/unrecognizable stuff at the head and tail of the mpeg file. + - Updated `scan' routine! Stripped off many fat statements, redundant checks, and several unused + variables, so now the code is light, fast and readable! + - A makefile appeared! Sooo comfortable :-) + - fixed a buffer overflow into extract_lame_string (sorry, my fault...) + - renamed `VbrTag.h' into `mp3guessenc.h' (I really thought I already did this before) + - fixed information about Mode Extension field (it changes its meaning depending upon actual layer) + - fixed compilation warnings even when running `gcc -Wextra ...' + + +version 0.25 alpha 20 (2011/11/30, the "A Release With No Name" release) + - fixed the `get_framesize' routine so it now does return the right value for ANY layer and ANY mpeg + version. + - slightly reworked the `scan' routine, now calling a `scan_layerIII' sub-routine from within itself + just when investigating layerIII streams. Now I can check layerI, layerII and layerIII streams, even + if layerI is not actually giving reliable results (now fixed). + - fixed the check for LAME tag into the very first mpeg frame (lame 3.99alpha up to 3.99.1 compliancy) + - stop checks if we already are at the end of bitstream + [This fix allowed several alerts to disappear, uhm...] + - more flexible resync_mp3(): we can now skip the fseek at the beginning just calling + resync_mp3(fp, &h, RESYNC_POS_CURRENT), which makes the function start searching from current + position + - modified checkid3v1 and checkid3v2 in order to perform (if needed) silent detection + - finally fixed *ALL* compilation warnings (I usually run `gcc -Wall ...') + - slightly changed summary output about L/R frames, M/S frames and average bitrate so now it's + similar to lame cli executable messages. + - enriched frame histogram! Now it takes into account for different frame sizes due to + padding (when it's used). Output example: + 160 kbps : 3693 (100.0%), size distr: [2035x522 bytes, 1658x523 bytes] + Here you can see a cbr 160 kbps encoded mp3 (44.1 kHz audio source). It uses 2035 frames without + padding (522 bites) and 1658 ones with padding (523 bytes). Of course, in VBR encoded files you + won't find any padding. + + +version 0.25 alpha 19 (2011/05/03) + - added workaround for a random-appearing stack smashing error also causing data corruption (fixing it is another story...) + - finally moved all segments of the code into their own subroutines, so now main() is light and (most important!) readable + + +version 0.25 alpha 18 (somewhere in the middle) + - fixed an insidious bug into ExtractI4 and ExtractI2 due to signedness of char + - completely rewritten extract_lame_string (thanks to Robert - lame-dev list on sourceforge.net) + + +version 0.25 alpha (until 2011/03/04) + - clarify software license + - extended support to all layers (I/II/III) (TO BE FINISHED, eventually make guessing optional) + - fixed detection of xing/lame vbr/cbr tag + - added details about lame tag + - added checks to head_check2 (now just `head_check') in order to perform the full range of integrity checks on mpeg header + - added an on-purpose routine to compute the frame size (get_framesize) + - faster resync_mp3 (less in/out calls) + - removed gotos in extract_lame_string (oh, yeah!) + - added check for not-allowed modes in LayerII streams + - added version information + - added basic support for command line options (to be extended) + - added accurate handling of ID3tag-1.0 and ID3tag-1.1 + - fixed detection for ID3tagV2 up to 2.3.0 (although it is still skipped at all) + - added nice alignment for output strings + - added comments to many parts of the code + - commented out several unused identifiers + - fixed most of the compilation warnings + - added detection of VBRI tag (added to vbr files by Fraunhofer encoders) + + diff --git a/lib/mp3guessenc-0.27.4/DOCS b/lib/mp3guessenc-0.27.4/DOCS new file mode 100644 index 00000000000..e10b2f499fe --- /dev/null +++ b/lib/mp3guessenc-0.27.4/DOCS @@ -0,0 +1,63 @@ + + * Sample MPEG-1 Audio Layer I, II, and III files for testing implementations +http://standards.iso.org/ittf/PubliclyAvailableStandards/s022691_ISO_IEC_11172-4_1995(E)_Compliance_Testing.zip + + * MPEG Audio Frame Header by Konrad Windszus +http://www.codeproject.com/KB/audio-video/mpegaudioinfo.asp + + * Mp3 Info Tag rev 1 specifications +http://gabriel.mp3-tech.org/mp3infotag.html + + * Mpeg Audio Frame Header +http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm + + * ID3 tag version 2.3.0 +http://www.id3.org/d3v2.3.0 + + * Maaate! The Australian audio analysis tools +http://maaate.sourceforge.net/ + + * Implementation details about an mp3 decoder +http://www.mp3-tech.org/programmer/docs/bitstream.zip + + * MPEG 1 layer 1/2/3 MPEG 2 2/3 Test Data +http://mpgedit.org/mpgedit/mpgedit/testdata/mpegdata.html + + * Lame Y switch +http://wiki.hydrogenaudio.org/index.php?title=LAME_Y_SWITCH + + * MP3 format at Hydrogenaudio Knowledgebase +http://wiki.hydrogenaudio.org/index.php?title=MP3 + + * APEv2 specification at Hydrogenaudio Knowledgebase +http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification + + * Home site of mp3PRO technology +http://www.mp3prozone.com + + * Introducing mp3Surround - also many samples +http://www.all4mp3.com/learn/mp3-surround.php + + * Wave File Specifications +http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + + * RIFF file format +https://www.daubnet.com/en/file-format-riff + + * Wav (RIFF) File Format Tutorial +http://www.topherlee.com/software/pcm-tut-wavformat.html + + * Definition of Lyrics3 tag +http://id3.org/Lyrics3 + + * Specification for the (new) Lyrics3 v2.00 tag +http://id3.org/Lyrics3v2 + + * Description of MusicMatch tag +https://github.com/dreamindustries/id3lib/blob/master/doc/musicmatch.txt + + * Old home page of the BladeEnc developer +http://home8.swipnet.se/~w-82625/default.htm + + * BladeEnc binaries for several OSs +http://www2.arnes.si/~mmilut/BladeEnc.html diff --git a/lib/mp3guessenc-0.27.4/Makefile b/lib/mp3guessenc-0.27.4/Makefile new file mode 100644 index 00000000000..47ad9b76752 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/Makefile @@ -0,0 +1,93 @@ +# +# Makefile +# +# If you wish, you can edit to suit your system. +# + +# choose build type +BUILD = release +#BUILD = debug + +# choose the target OS - `other' is good for both *nix, win32 systems and haiku +# 64 bit as well. +# NOTE: in haiku 32 bit with gcc2 some options need to be avoided since the compiler +# is quite old (gcc-2.95.3), with gcc4/5 the selected options won't raise any +# issue. If uncertain, select `haiku32_gcc2' +# UPDATE: the most recent nightly builds of haiku 32 bit adopted a different +# compiler naming scheme after suppressing the `setgcc' utility. Now gcc2 is +# called `gcc' and gcc4/5 is called `gcc-x86' and this Makefile is smart enough +# to set the right compiler name for you. +TARGET_OS = other +#TARGET_OS = haiku32_gcc2 +#TARGET_OS = haiku32_gcc4 + +# here you can edit the installation path (only one will be used) +# On *nix systems you may want to have mp3guessenc installed under /usr/local +# In Haiku it is useful to put executables under /boot/home/config/non-packaged +PREFIX = /usr/local +#PREFIX = /boot/home/config/non-packaged + +# common preferences +CC = gcc +#CC = tcc +#CC = clang +CFLAGS = -Wall +LDFLAGS = +LIBS = + +# no need to change anything below here +#-------------------------------------- + +# is make running into windows? +ifdef SYSTEMROOT + BIN_EXT = .exe +else + BIN_EXT = +endif + +ifeq ($(TARGET_OS),haiku32_gcc4) + ifeq ($(CC),gcc) + CC = gcc-x86 + endif +endif + +ifneq ($(TARGET_OS),haiku32_gcc2) + CFLAGS += -Wextra -fsingle-precision-constant +endif + +ifeq ($(BUILD),release) + CFLAGS += -O2 + LDFLAGS += -s +else + CFLAGS += -g + ifneq ($(TARGET_OS),haiku32_gcc2) + CFLAGS += -fno-stack-protector + endif +endif + +SRC0 = mp3guessenc.c bit_utils.c tags.c decode.c +OBJ0 = mp3guessenc.o bit_utils.o tags.o decode.o + +all: $(OBJ0) mp3guessenc +make: all + +mp3guessenc: $(OBJ0) + gcc $(LDFLAGS) -o mp3guessenc$(BIN_EXT) $(OBJ0) $(LIBS) + +mp3guessenc.o: mp3guessenc.c mp3guessenc.h bit_utils.h tags.h decode.h scrambled.h mp3g_io_config.h +bit_utils.o: bit_utils.c bit_utils.h mp3g_io_config.h +tags.o: tags.c tags.h mp3guessenc.h mp3g_io_config.h +decode.o: decode.c + +install: + mkdir -p $(PREFIX)/bin + cp -v -f mp3guessenc$(BIN_EXT) $(PREFIX)/bin/ + +clean: + rm -f $(OBJ0) mp3guessenc$(BIN_EXT) + +distclean: clean + +uninstall: + rm $(PREFIX)/bin/mp3guessenc$(BIN_EXT) + diff --git a/lib/mp3guessenc-0.27.4/TODO b/lib/mp3guessenc-0.27.4/TODO new file mode 100644 index 00000000000..cc1f66a419f --- /dev/null +++ b/lib/mp3guessenc-0.27.4/TODO @@ -0,0 +1,8 @@ +todo list: + - put the core functions into a library and make mp3guessenc a simple frontend/sample application + able to call library functions and get results into `streaminfo' and `detectioninfo' data structures + - add a README file ? + - add an AUTHORS file ? + - handling of more mpeg files provided on the same command line (WON'T DO) + - add support for SEEK feature in ID3tagV2.4.0 (lowest priority) + diff --git a/lib/mp3guessenc-0.27.4/bit_utils.c b/lib/mp3guessenc-0.27.4/bit_utils.c new file mode 100644 index 00000000000..ea4c2ba6a75 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/bit_utils.c @@ -0,0 +1,316 @@ +/* + * bit_utils is a support module which provides bit oriented utilities + * Copyright (C) 2013-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mp3g_io_config.h" +#include +#include "bit_utils.h" + +part23buf_t p23b[NP23B]; +int p23b_in=0,p23b_out=0; +int p23b_pos=0; /* offset of the current bit inside the current output block p23b[p23b_out] */ + + +/* extract a bit stream (up to 32 bit long) out of a byte array, + starting at a 'ptr'+'begin_ptr' bit */ +/* returns the required bit stream, which is long 'length' bits */ +/* the 'begin_ptr' (may be NULL) is moved to the next unread bit */ +unsigned int extract_bits(unsigned char *ptr, unsigned int *begin_ptr, char length) +{ + unsigned int available_bits; + unsigned int result = 0; + unsigned int pointer, byte_pointer; + + /* check */ + if (ptr != NULL && length > 0) + { + /* do some setup */ + if (begin_ptr != NULL) + { + pointer = *begin_ptr; + (*begin_ptr) += (unsigned int)length; + } + else + { + pointer = result; + } + byte_pointer = pointer / 8; /* first byte to access for bit extraction */ + + available_bits = 8 - (pointer%8); /* evaluate available bits into the current byte */ + if (available_bits < 8) + { + result = (unsigned int)ptr[byte_pointer] & ((1<>= (available_bits-length); + length = 0; + } + else + { + length -= available_bits; + } + } + + /* extract whole bytes (if any) */ + while (length >= 8) + { + result <<= 8; + result |= (unsigned int)ptr[byte_pointer]; + byte_pointer++; + length -= 8; + } + + /* copy remaining bits */ + if (length) + { + result <<= length; + result |= (unsigned int)ptr[byte_pointer] >> (8-length); + } + } + + return result; +} + + +/* + * `seek_p23b' searches for the requested bit inside the p23b structure + * (given `pos' as an absolute position) and seeks to that bit when it's found, + * returning 1 as OK (the indexes `p23b_out' and `p23b_pos' are updated as well). + * When not found, the function returns zero. + */ +char seek_p23b(bitoffs_t pos) +{ + int idx,rev_step=p23b_in-1; + char ret=0; + for(idx=0; idx 0) + { + /* store the offset of the beginning of part2 data (bits) with respect to the entire file stream */ + p23b[p23b_in].pos = bit_pos; + + if (block_len>LARGEST_FRAME) + { + printf("Warning: requested loading for a block too large - fixing.\n"); + block_len=LARGEST_FRAME; + } + + memcpy(p23b[p23b_in].buf, buff, block_len); + /* set length of data block */ + p23b[p23b_in].len=block_len*8; + + if (p23b_in == p23b_out) + p23b_pos = 0; + p23b_in = (p23b_in+1) % NP23B; + } +} + + +// read byte-aligned 8 bits +/* + * NOTE: the old `readOneByte_p23b' returns unreliable bytes! + * It stays assured that the caller has already checked for byte availability, + * so it goes straight on extracting and returning a byte. + * The wrong is, the byte availability comes from an approximation: + * if the start bit and the end bit differ by more than 8 bit, then there is + * (at least) an available byte BUT + * it happens the start bit and the end bit lie on different part2+part3 buffer entries + * and when it comes to extract the very last byte of the sequence, it may happen + * to extract a byte belonging to the beginning of the subsequent part2+part3 block + * because it is stored into the next part2+part3 buffer entry. The last-but-one byte + * is right, though. + * The reason is they do have a lot of bytes in between (even more than one hundred) + * but the actual length of remaining ancillary data is less than 8 bit. + */ + +/* + * `p23b_cpy' gets inspired by the well known `memcpy' string functions. + * Since I often need to copy several byte sequences, it appears faster to switch + * to memcpy and obtain a whole sequence copied in a row. + * Due to the new enhanced precision, using `p23b_cpy' mp3guessenc may now extract + * packet of ancillary data being 1 byte smaller than before (read above). + * Incidentally, the missing byte prevents for unwanted characters to appear into the + * encoder string (so now the reported string will always be `LAME3.96.1' instead of + * `LAME3.96.1w') for a cleaner encoder identification. + * Warning: the start bit has to be set BEFORE calling `p23b_cpy' (for example, with + * a `seek_p23b' call). + * Return value: how many bytes have been copied + */ +int p23b_cpy(unsigned char *buffer, bitoffs_t endbit, int buffer_size, int *byte_off) +{ + int copied=0; + +// p23b_pos = (p23b_pos+7)&~7; /* round to the next byte */ + *byte_off = p23b_pos % 8; + p23b_pos = p23b_pos&~7; + if (p23b_pos >= p23b[p23b_out].len) + { /* jump to the next entry */ + p23b_out = (p23b_out + 1) % NP23B; + p23b_pos = 0; + if (p23b_out == p23b_in) + buffer_size = 0; + } + + while (buffer_size) + { + if (endbit <= p23b[p23b_out].pos+(bitoffs_t)p23b_pos) + /* here I have nothing to copy from */ + break; + else + if (((p23b[p23b_out].len-p23b_pos)/8 <= buffer_size) && (p23b[p23b_out].pos+(bitoffs_t)p23b[p23b_out].len <= endbit)) + /* the closer bound is the block end */ + { + int len = (p23b[p23b_out].len-p23b_pos)/8; + memcpy(buffer+copied, p23b[p23b_out].buf+p23b_pos/8, len); + buffer_size -= len; + copied += len; + p23b_out = (p23b_out + 1) % NP23B; + p23b_pos = 0; + if (p23b_out == p23b_in) + break; + } + else + if ( (p23b[p23b_out].pos+(bitoffs_t)p23b_pos < endbit) && (endbit < p23b[p23b_out].pos+(bitoffs_t)p23b[p23b_out].len) && ((int)(endbit-p23b[p23b_out].pos-(bitoffs_t)p23b_pos)/8 <= buffer_size) ) + /* the closer bound is the `endbit' */ + { + memcpy(buffer+copied,p23b[p23b_out].buf+p23b_pos/8,(int)((endbit-p23b[p23b_out].pos-p23b_pos)/8)); + copied += (int)((endbit-p23b[p23b_out].pos-p23b_pos)/8); + p23b_pos = (int)(endbit-p23b[p23b_out].pos); + break; + } + else + if ((buffer_size < (p23b[p23b_out].len-p23b_pos)/8) && (p23b[p23b_out].pos+(bitoffs_t)p23b_pos+(bitoffs_t)(buffer_size)*8 < endbit)) + /* the closer bound is the buffer limit */ + { + memcpy(buffer+copied,p23b[p23b_out].buf+p23b_pos/8,buffer_size); + copied += buffer_size; + p23b_pos += buffer_size*8; + break; + } + } + + return copied; +} diff --git a/lib/mp3guessenc-0.27.4/bit_utils.h b/lib/mp3guessenc-0.27.4/bit_utils.h new file mode 100644 index 00000000000..974b5ba5d21 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/bit_utils.h @@ -0,0 +1,69 @@ +/* + * bit_utils is a support module which provides bit oriented utilities + * Copyright (C) 2013-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIT_UTILS_H +#define BIT_UTILS_H + +/* + * you can take 8*511 bit + * from the bit reservoir and at most 8*1440 bit from the current + * frame (320 kbps, 32 kHz), so 8*1951 bit is the largest possible + * value for MPEG1 and 2) + * UPDATE: the biggest supported frame is now 8*5760 bit due to a free + * format stream (MPEG2.5, 640 kbps, 8 kHz). I won't take into account the further 511 bytes + * from the bit reservoir because they are expected to be in previous blocks (if any). + * Further, due to `checkvbrinfotag' seeking for a new sync bit sequence, the buffer has to be + * large enough to contain a full frame and a new mpeg header (4 bytes). + */ + +#define LARGEST_FRAME (5760+4) + +// In the worst case, 9 bitstream frames have to be received before decoding can start. +/* + * UPDATE: the worst case is a MPEG2, 24000 Hz, 8kbps frame which is 24 bytes long (192 bits) + * Now, I know 32 bits (header), 16 bits (error protection) and 136 bits (side information) + * are needed for the static part, and 32+16+136=184. This means a single byte (8 bits) is available + * for storing part2 and part3 of audio data. + * A complex solution is needed, indeed the whole part of frame decoding needs to be reviewed but + * as a temporary solution, increasing the number of frames will do the trick + * ATM, the `More bits in reservoir are needed to decode this frame.' sentence has still to be + * expected. + */ +#define NP23B 25 //(9+3) + +/* + * This structure will store the bytes for part2 (scalefactors) + * and part3 (Huffman encoded data) of the main data + */ +typedef struct PART23BUF { + unsigned char buf[LARGEST_FRAME]; + int len; + bitoffs_t pos; +} part23buf_t; + + +unsigned int extract_bits(unsigned char *, unsigned int *, char); +char seek_p23b(bitoffs_t); +int skip_p23b(int); +int skipback_p23b(int); +bitoffs_t tell_p23b(void); +void load_into_p23b(unsigned char *, bitoffs_t, int); +int p23b_cpy(unsigned char *, bitoffs_t, int, int *); + +#endif diff --git a/lib/mp3guessenc-0.27.4/decode.c b/lib/mp3guessenc-0.27.4/decode.c new file mode 100644 index 00000000000..ad60ac4ed1a --- /dev/null +++ b/lib/mp3guessenc-0.27.4/decode.c @@ -0,0 +1,118 @@ +/* + * decode.c string decoding utilities + * Copyright (C) 2012-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +/* + * This takes the command line strings starting at optind and concatenate them in order + * to build a long string. Then this string will be compared with the actual key (codename). + * Returns 0 if the strings don't match, nonzero otherwise. + */ +char validate (int argc, char **argv, int optind, int maxlen, char *codename) +{ + char *source; + int avail=maxlen-1,i=0; + + if (optind1)) + { + strncat(source," ",avail); + strncat(source,argv[optind],avail-1); + optind++; + avail=maxlen-strlen(source)-1; + } + + /* command line string is now ready, now validate it */ + avail=strlen(codename); + + if ((int)strlen(source)==avail) + { + /* place white spaces corresponding to the white spaces in the codename + so the comparison will be easier */ + /* to be extended for other critical characters */ + for (i=0; i>4)&0x0f; + g2=(s[q]&0x0f)<<4; + + s[i]=(s[i]&0x0f)|g2; + s[q]=(s[q]&0xf0)|g1; + } + + if (len%2) + { + g1=(s[i]>>4)&0x0f; + g2=(s[i]&0x0f)<<4; + s[i]=g1|g2; + } + } +} + +/* + * This routine works as a wrapper around the real decoding function. + */ +char *decode (char *string, int length) +{ + scramble(string,length); + return string; +} + diff --git a/lib/mp3guessenc-0.27.4/decode.h b/lib/mp3guessenc-0.27.4/decode.h new file mode 100644 index 00000000000..97a5467fc41 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/decode.h @@ -0,0 +1,23 @@ +/* + * decode.h string decoding utilities (header file) + * Copyright (C) 2012-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +char validate (int, char **, int, int, char *); +char *decode (char *, int); + diff --git a/lib/mp3guessenc-0.27.4/mp3g_io_config.h b/lib/mp3guessenc-0.27.4/mp3g_io_config.h new file mode 100644 index 00000000000..63d8a3668a9 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/mp3g_io_config.h @@ -0,0 +1,54 @@ +/* + * mp3g_io_config - basic OS-related settings for mp3guessenc + * Copyright (C) 2015-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MP3G_IO_CONFIG_H_ +#define MP3G_IO_CONFIG_H_ + +/* This will enable 64 bit integers as file offset on 32 bit posix machines */ +/* Sadly, it won't work on Android, though */ +#define _FILE_OFFSET_BITS 64 + +#ifdef __MINGW32__ +#define __USE_MINGW_FSEEK /* Request mingw internal implementation of fseeko64 */ +#define __USE_MINGW_ANSI_STDIO 1 /* Select a more ANSI C99 compatible implementation of printf() and friends. */ +#define __have_typedef_off_t /* This will prevent types.h from defining the type off_t - I don't need it */ +#include +#define off_t off64_t +#define _off_t int /* But some functions still need a _off_t type which is 32 bit long */ +#endif /* __MINGW32__ */ + +#include + +#ifdef __MINGW32__ +#define fseeko fseeko64 +#define ftello ftello64 +#endif /* __MINGW32__ */ + +#if defined(OS2) || defined(_OS2) || defined(__OS2__) || defined(AMIGA) || defined(__amigaos__) +#define fseeko fseek +#define ftello ftell +#if defined(OS2) || defined(_OS2) || defined(__OS2__) +typedef long int off_t; +#endif /* os/2 */ +#endif /* os/2 or amiga */ + +typedef long long bitoffs_t; + +#endif /* MP3G_IO_CONFIG_H_ */ + diff --git a/lib/mp3guessenc-0.27.4/mp3guessenc.c b/lib/mp3guessenc-0.27.4/mp3guessenc.c new file mode 100644 index 00000000000..a3bd467b8bf --- /dev/null +++ b/lib/mp3guessenc-0.27.4/mp3guessenc.c @@ -0,0 +1,3383 @@ +/* + * mp3guessenc is an mp3 encoder-guesser + * Copyright (C) 2002-2010 Naoki Shibata + * Copyright (C) 2011-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* mp3guessenc version 0.2 by Naoki Shibata */ +/* This program is still under development. */ +/* Modifed by Evan Dekker 2019-09-26 */ + + +#define VER_PKG_NAME "mp3guessenc" +#define VER_MAJ 0 +#define VER_MIN 27 +#define VER_PATCH_LEV 4 +#define CODENAME "It's happened before it'll happen again" + +#define REL_TYPE 0 /* release type 0:stable, 1:alpha, 2:beta, 3:preview, 4:releasecandidate */ +#define REL_SUBNUM 1 + +#define ENABLE_SKIP_OPTION + + +/* + * release type is provided via RELEASE_STABLE/RELEASE_ALPHA/RELEASE_BETA/RELEASE_PREVIEW/RELEASE_RC macros + * + * So please, do not use REL_TYPE for release check! + * Again, check which is true among RELEASE_STABLE/RELEASE_ALPHA/RELEASE_BETA/RELEASE_PREVIEW/RELEASE_RC + */ + +#ifdef REL_TYPE +#define RELEASE_STABLE (REL_TYPE==0) +#define RELEASE_ALPHA (REL_TYPE==1) +#define RELEASE_BETA (REL_TYPE==2) +#define RELEASE_PREVIEW (REL_TYPE==3) +#define RELEASE_RC (REL_TYPE==4) +#else +#define RELEASE_STABLE 0 +#define RELEASE_ALPHA 1 +#define RELEASE_BETA 0 +#define RELEASE_PREVIEW 0 +#define RELEASE_RC 0 +#endif + +#if (RELEASE_STABLE) +#undef REL_SUBNUM +#endif + + +#include "mp3g_io_config.h" + +#include +#include +#include +#include +#include + +#include "mp3guessenc.h" +#include "bit_utils.h" +#ifdef CODENAME +#include "decode.h" +#include "scrambled.h" +#define CMD_CODENAME_LENGTH 80 /* max length of a string provided via command line */ +#endif + + +#define BLOCKCOUNT_LONG 0 +#define BLOCKCOUNT_SHORT 1 +#define BLOCKCOUNT_MIXED 2 +#define BLOCKCOUNT_SWITCH 3 +#define BLOCKCOUNT_TYPES 4 + +#define MODE_PLAIN_STEREO 0 +#define MODE_JOINT_STEREO 1 +#define MODE_DUAL_CHANNEL 2 +#define MODE_MONO 3 + +#define MPEG_CODE_MPEG2_5 0 +#define MPEG_CODE_RESERVED 1 +#define MPEG_CODE_MPEG2 2 +#define MPEG_CODE_MPEG1 3 + +#define SAMPLERATE_CODE_RESERVED 3 + +#define EMPHASIS_CODE_RESERVED 2 + +#define PRO_SIGN_BYTE1 0xC0 +#define PRO_SIGN_BYTE2 0x08 +#define SURR_SIGN_BYTE1 0xCF +#define SURR_SIGN_BYTE2 0x30 + +#define OFL_IN_MP3PRO_BYTE_1 0xE0 +#define OFL_IN_MP3PRO_BYTE_2 0xBA +#define OFL_IN_MP3SURROUND_BYTE_1 0xB4 +#define OFL_IN_MP3SURROUND_BYTE_2 0x08 +#define OFL_IN_PLAINMP3_BYTE_1 OFL_IN_MP3SURROUND_BYTE_1 +#define OFL_IN_PLAINMP3_BYTE_2 0x04 + +#define GLOBAL_GAIN_CHANNEL_LEFT 0 +#define GLOBAL_GAIN_CHANNEL_RIGHT 1 +#define GLOBAL_GAIN_CHANNEL_MONO GLOBAL_GAIN_CHANNEL_LEFT + +#define SIZEOF_OFF64_T 8 + +#define ENCODER_GUESS_GOGO 18 +#define ENCODER_GUESS_LAME_OLD 17 +#define ENCODER_GUESS_LAME 16 +#define ENCODER_GUESS_MP3PRO 15 +#define ENCODER_GUESS_MP3PRO_OFL 14 +#define ENCODER_GUESS_MP3SURR 13 +#define ENCODER_GUESS_MP3SURR_OFL 12 +#define ENCODER_GUESS_MP3S_ENC 11 +#define ENCODER_GUESS_FHG_FASTENC 10 +#define ENCODER_GUESS_FHG_MAYBE_FASTENC 9 +#define ENCODER_GUESS_FHG_ACM 8 +#define ENCODER_GUESS_FHG_MAYBE_L3ENC 7 +#define ENCODER_GUESS_XING_VERY_OLD 6 +#define ENCODER_GUESS_XING_NEW 5 +#define ENCODER_GUESS_XING_OLD 4 +#define ENCODER_GUESS_BLADE 3 +#define ENCODER_GUESS_HELIX 2 +#define ENCODER_GUESS_UNKNOWN 1 + +char *encoder_table[] = +{ + "unused entry", + "dist10 encoder or other encoder", + "Helix", + "BladeEnc", + "Xing (old)", + "Xing (new)", + "Xing (very old)", + "FhG (l3enc, fastenc or mp3enc)", + "FhG (ACM or producer pro)", + "FhG fastenc, mp3sEncoder or mp3 plugin", + "FhG fastenc", + "FhG mp3sEncoder with OFL", + "Fraunhofer IIS mp3Surround 5.1 encoder with OFL", + "Fraunhofer IIS mp3Surround 5.1 encoder", + "Fraunhofer IIS mp3PRO encoder with OFL", + "Fraunhofer IIS mp3PRO encoder", + "Lame", + "Lame (old) or m3e", + "Gogo" +}; + + +typedef enum +{ + HEAD_NO_TAG, + HEAD_ID3V2_TAG, + HEAD_APE_TAG, + HEAD_WAVERIFF_UNCOMPLETE_TAG, + HEAD_WAVERIFF_DATACHUNK_TAG +} head_metadata_tag_t; + + +int samples_tab[2][3] = {{ 384,1152,1152}, /* MPEG1: lI, lII, lIII */ + { 384,1152, 576}}; /* MPEG2/2.5: lI, lII, lIII */ + +int samplerate_tab [3][4] = {{11025,99999,22050,44100}, /* MPEG2.5, resv, MPEG2, MPEG1 */ + {12000,99999,24000,48000}, /* MPEG2.5, resv, MPEG2, MPEG1 */ + { 8000,99999,16000,32000}}; /* MPEG2.5, resv, MPEG2, MPEG1 */ + +int bitrate_tab[2][3][16] = { + { {128, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,}, /* MPEG1 lI */ + {128, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,}, /* MPEG1 lII */ + {128, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,} }, /* MPEG1 lIII */ + + { {128, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,}, /* MPEG2/2.5 lI */ + {128, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,}, /* MPEG2/2.5 lII */ + {128, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,} } /* MPEG2/2.5 lIII */ +}; + +unsigned char subband_limit[5]={27,30,8,12,30}; /* sub band limits for mpeg layerII */ +unsigned char subband_quanttable[5][30]= +{ + /* subband quantization table for mpeg layerII */ + {4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,0,0,0}, + {4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2}, + {4,4,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {4,4,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {4,4,4,4,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2} +}; + +mmtag_t musicmatch_tag; +unsigned char mp3g_storage[LARGEST_BUFFER]; + + +char malformed_part1(unsigned char *, currentFrame *); + +/* Mpeg header utilities */ + + +char head_check(unsigned int head, char relaxed) +/* + * Integrity checks for an mpeg header (4 bytes) + * Return values: + * 0: invalid header + * 1: valid header + * when `relaxed' is nonzero, then the check on bitrate index is skipped + * but only for layerIII ! + */ +{ + char result=0; + unsigned char ly=(head&HEADER_FIELD_LAYER) >>HEADER_FIELD_LAYER_SHIFT; /* layer index */ + unsigned char bi=(head&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT; /* bitrate index */ + unsigned char mp=(head&HEADER_FIELD_MPEG_ID)>>HEADER_FIELD_MPEG_ID_SHIFT; /* mpeg version */ + unsigned char md; /* audio mode */ + + if ((head&HEADER_FIELD_SYNC) == HEADER_FIELD_SYNC) /* check for sync bits */ + { + if (mp != MPEG_CODE_RESERVED) /* check for reserved value in mpeg version field */ + { + if (ly!=LAYER_CODE_RESERVED) /* check for reserved value in layer field */ + { + if (((head&HEADER_FIELD_SAMPRATE)>>HEADER_FIELD_SAMPRATE_SHIFT) != SAMPLERATE_CODE_RESERVED ) /* check for reserved value in sample rate field */ + { + if ((head&HEADER_FIELD_EMPHASIS) != EMPHASIS_CODE_RESERVED) /* check for reserved value in emphasis field */ + { + if ((relaxed && ly==LAYER_CODE_L_III) + || + bi!=BITRATE_INDEX_RESERVED) /* check for invalid bitrate index */ + { + result = 1; + } + } + } + } + } + } + + /* the header is valid and the mpeg frame is supported! */ + + if (result && ly==LAYER_CODE_L__II && mp==MPEG_CODE_MPEG1) + { + /* if mpeg 1 layerII, then check for not allowed bitrate-mode combinations */ + if (bi != BITRATE_INDEX_FREEFORMAT) /* free format bitstreams allow for any bitrate-mode combinations */ + { + md=(head&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT; + if ( + (bi<4 && md!=MODE_MONO) + || + (bi==5 && md!=MODE_MONO) + || + (bi>10 && md==MODE_MONO) + ) + { + result = 0; + } + else + { + /* all went ok, this mpeg1layerII header is valid */ + } + } + } + + return result; +} + +int get_framesize(unsigned int head) +/* + * "Just give me an audio mpeg header, + * I will give you in return its frame size" + * Works with mpeg1, mpeg2, mpeg2.5, all layers + * BUT not with freeformat frames + */ +{ + unsigned char lsf,layer,bitrate_index,srate_index,mpeg,padding,samplesfactor=1; + int size; + + lsf = 1-((head&HEADER_FIELD_LSF) >> HEADER_FIELD_LSF_SHIFT); + layer = 3-((head&HEADER_FIELD_LAYER)>> HEADER_FIELD_LAYER_SHIFT); /* this goes from 0 (layerI) up to 2 (layerIII) */ + bitrate_index = (head&HEADER_FIELD_BITRATE) >> HEADER_FIELD_BITRATE_SHIFT; + srate_index = (head&HEADER_FIELD_SAMPRATE)>> HEADER_FIELD_SAMPRATE_SHIFT; + mpeg = (head&HEADER_FIELD_MPEG_ID) >> HEADER_FIELD_MPEG_ID_SHIFT; + padding = (head&HEADER_FIELD_PADDING) >> HEADER_FIELD_PADDING_SHIFT; + +/* + * This `samplesfactor' is used to compensate for rounding errors when managing integers only. The + * size formula is right just without this (aside from the pad*4) but it keeps not returning + * multiple-of-4 values. So this way it is fixed for working in any case with layers II and III. + * For layer I, I just multiply by 4 before returning the size. + */ + if (!layer) samplesfactor=4; + + size = samples_tab[lsf][layer]/samplesfactor*bitrate_tab[lsf][layer][bitrate_index]*125/samplerate_tab[srate_index][mpeg]+(int)padding; + + if (!layer) + size *= 4; + + return size; +} + +float get_bitrate (unsigned int header, int framesize) +{ + unsigned char lsf,layer,srate_index,mpeg,padding/*,samplesfactor=1*/; + float bitrate; + + lsf = 1-((header&HEADER_FIELD_LSF) >> HEADER_FIELD_LSF_SHIFT); + layer = 3-((header&HEADER_FIELD_LAYER)>> HEADER_FIELD_LAYER_SHIFT); /* this goes from 0 (layerI) up to 2 (layerIII) */ + srate_index = (header&HEADER_FIELD_SAMPRATE)>> HEADER_FIELD_SAMPRATE_SHIFT; + mpeg = (header&HEADER_FIELD_MPEG_ID) >> HEADER_FIELD_MPEG_ID_SHIFT; + padding = (header&HEADER_FIELD_PADDING) >> HEADER_FIELD_PADDING_SHIFT; + + if (!layer) + padding *= 4; +// samplesfactor=4; + +// bitrate = (float)((framesize-padding)*samplerate_tab[srate_index][mpeg]*samplesfactor)/(125.0*(float)samples_tab[lsf][layer]); + bitrate = (float)((framesize-padding)*samplerate_tab[srate_index][mpeg])/(125.0*(float)samples_tab[lsf][layer]); + + return bitrate; +} + +char freeformat(unsigned int head) +{ + return (((head&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT)==BITRATE_INDEX_FREEFORMAT); +} + +char layerI(unsigned int head) +{ + return (((head&HEADER_FIELD_LAYER)>>HEADER_FIELD_LAYER_SHIFT)==LAYER_CODE_L___I); +} + +char layerII(unsigned int head) +{ + return (((head&HEADER_FIELD_LAYER)>>HEADER_FIELD_LAYER_SHIFT)==LAYER_CODE_L__II); +} + +char layerIII(unsigned int head) +{ + return (((head&HEADER_FIELD_LAYER)>>HEADER_FIELD_LAYER_SHIFT)==LAYER_CODE_L_III); +} + +/* unit for begin_ptr and length is bit */ +/* begin_ptr should have values in 1..7 - fix: it works with any values */ +/* begin_ptr may be NULL, when not NULL it is increased of 'length' amount */ +unsigned short crc_update(unsigned short old_crc, unsigned char *data, unsigned int *begin_ptr, unsigned int length) +{ +#define CRC16_POLYNOMIAL 0x8005 + + unsigned char idx, data_len; + unsigned int new_data, crc1=(unsigned int)old_crc, byte_length, + begin_offs=0, next_8multiple, jdx; + + if (begin_ptr != NULL && length > 0) + { + next_8multiple = (*begin_ptr+7) & ~7; + if ((next_8multiple - *begin_ptr) > 0) + { + new_data = (unsigned int)(data[*begin_ptr/8]); + new_data = new_data<<((*begin_ptr%8)+8); + if (length < (next_8multiple - *begin_ptr)) + data_len = (unsigned char)length; + else + data_len = (unsigned char)(next_8multiple - *begin_ptr); + /* 'data_len' is into [1..7] at most */ + + for (idx=0; idx>8)&0xFF); + curr_crc = crc_update(curr_crc, &dummy, NULL, 8); + dummy = (unsigned char)(header&0xFF); + curr_crc = crc_update(curr_crc, &dummy, NULL, 8); + + curr_crc = crc_update(curr_crc, chk_buffer, NULL, p1len- + 8*(sizeof(unsigned int)+sizeof(unsigned short))); + + return (curr_crc==target); +} + +unsigned char reflect_byte(unsigned char by) +{ + unsigned char reflected_nibble[16]={ + 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, + 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b, 0x07, 0x0f + }; + + return (unsigned char)( + /* reflect higher nibble */ + reflected_nibble[(by & 0xf0)>>4] + + /* reflect lower nibble */ + (reflected_nibble[by & 0x0f]<<4) ); +} + + +/* + * in 'crc_reflected_update' length is in bytes + */ +unsigned short crc_reflected_update(unsigned short old_crc, unsigned char *data, unsigned int length) +{ + unsigned char value1; + unsigned int idx; + + for (idx=0; idxmc.mc_stream_verified = 0; + return; + } + +//if (ext_bs_present) printf("n_ad_bytes=%d\n",extract_bits(buf, &pointer, 8)); + /* begin crc computation */ + pointer = start_pointer; + crc_new = crc_update(0xffff, buf, &pointer, MC_HEADER_LENGTH+8*ext_bs_present); + + pointer = start_pointer + MC_CENTER_POS + 8*ext_bs_present; + center = (unsigned char)extract_bits(buf, &pointer, 2); + + pointer = start_pointer + MC_SURROUND_POS + 8*ext_bs_present; + surround = (unsigned char)extract_bits(buf, &pointer, 2); + + fr_pr = &fr_param_table[surround][(center&1)]; + + /* total number of channels */ + channels = ch_start + fr_pr->mc_channels; + + pointer = start_pointer + MC_LFE_CHANNEL_POS + 8*ext_bs_present; + lfe_present = (unsigned char)extract_bits(buf, &pointer, 1); + + pointer = start_pointer + MC_NO_MULTI_LINGUAL_POS + 8*ext_bs_present; + no_of_multi_lingual_channels = (unsigned char)extract_bits(buf, &pointer, 3); + + pointer = start_pointer + MC_MULTI_LINGUAL_FS + 8*ext_bs_present; + multi_lingual_fs = (unsigned char)extract_bits(buf, &pointer, 1); + + pointer = start_pointer + MC_MULTI_LINGUAL_LAYER + 8*ext_bs_present; + multi_lingual_layer = (unsigned char)extract_bits(buf, &pointer, 1); + + pointer = start_pointer + MC_HEADER_LENGTH + 8*ext_bs_present; + crc_old = (unsigned short)extract_bits(buf, &pointer, MC_CRC_LENGTH); + + pointer = start_pointer + MC_TC_SBGR_SELECT_POS + 8*ext_bs_present; + tc_sbgr_select = (unsigned char)extract_bits(buf, &pointer, 1); + + pointer = start_pointer + MC_DYN_CROSS_ON_POS + 8*ext_bs_present; + dyn_cross_on = (unsigned char)extract_bits(buf, &pointer, 1); + + pointer = start_pointer + MC_PREDICTION_ON_POS + 8*ext_bs_present; + mc_prediction_on = (unsigned char)extract_bits(buf, &pointer, 1); + + if (tc_sbgr_select) + { + tc_alloc[0] = (unsigned char)extract_bits(buf, &pointer, fr_pr->alloc_bits); + for (idx=1; idx<12; idx++) + tc_alloc[idx] = tc_alloc[0]; + } + else + { + for (idx=0; idx<12; idx++) + tc_alloc[idx] = (unsigned char)extract_bits(buf, &pointer, fr_pr->alloc_bits); + } + + memset(dyn_cross_mode, 0, 12*sizeof(unsigned char)); + memset(dyn_second_stereo, 0, 12*sizeof(unsigned char)); + if (dyn_cross_on) + { + dyn_cross_LR = (unsigned char)extract_bits(buf, &pointer, 1); + for (idx=0; idx<12; idx++) + { + dyn_cross_mode[idx] = (unsigned char)extract_bits(buf, &pointer, fr_pr->dyn_cross_bits); + if (surround == 3) + dyn_second_stereo[idx] = (unsigned char)extract_bits(buf, &pointer, 1); + } + } + + if (mc_prediction_on) + for (idx=0; idx<8; idx++) + { + predict = (unsigned char)extract_bits(buf, &pointer, 1); + if (predict) + { + pointer += (2 * pred_coeff_table[fr_pr->pred_mode][dyn_cross_mode[idx]]); + } + } + + if (lfe_present) + pointer += 4; + + + /* scan stream */ + if (!dyn_cross_on) + { + for (idx=0; idxallocation[jdx][idx] = (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + else + current_frame->allocation[jdx][idx] = 0; + } + else + { + for (idx=0; idx= 12 && jdx == 2) + current_frame->allocation[jdx][idx] = 0; + else + if (surround == 3 && dyn_second_stereo[sbgr] == 1) + { + if (center != 0 && jdx == 4) + current_frame->allocation[jdx][idx] = + current_frame->allocation[3][idx]; + else + { + if (center == 0 && jdx == 3) + current_frame->allocation[jdx][idx] = + current_frame->allocation[2][idx]; + else + current_frame->allocation[jdx][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + } + } + else + current_frame->allocation[jdx][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + } + else + { + switch (fr_pr->dyn_cross_bits) + { + case 1: + { + /* channel modes 3/0 and 2/1 */ + if (center == 3 && idx >= 12) + /* 3/0 + phantom center */ + current_frame->allocation[2][idx] = 0; + else + if (tc_alloc[sbgr] == 1) + current_frame->allocation[2][idx] = + current_frame->allocation[0][idx]; + else + if (tc_alloc[sbgr] == 2 || dyn_cross_LR) + current_frame->allocation[2][idx] = + current_frame->allocation[1][idx]; + else + current_frame->allocation[2][idx] = + current_frame->allocation[0][idx]; + + if (surround == 3) + { + /* 3/0 and 2/0 */ + current_frame->allocation[3][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + if (dyn_second_stereo[sbgr] == 1) + current_frame->allocation[4][idx] = + current_frame->allocation[3][idx]; + else + current_frame->allocation[4][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + } + } + break; + + case 3: + { + /* channel modes 3/1 and 2/2 */ + if (center == 3 && idx >= 12) + current_frame->allocation[2][idx] = 0; + else + if (dyn_cross_mode[sbgr] == 1 || dyn_cross_mode[sbgr] == 4) + current_frame->allocation[2][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + else + if ((surround == 2 || tc_alloc[sbgr] == 1 || + tc_alloc[sbgr] == 5 || tc_alloc[sbgr] != 2) + && !dyn_cross_LR) + current_frame->allocation[2][idx] = + current_frame->allocation[0][idx]; + else + current_frame->allocation[2][idx] = + current_frame->allocation[1][idx]; + + if (dyn_cross_mode[sbgr] == 2) + current_frame->allocation[3][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + else + if (dyn_cross_mode[sbgr] == 4) + current_frame->allocation[3][idx] = + current_frame->allocation[2][idx]; + else + if ((surround == 2 || tc_alloc[sbgr] == 4 || + tc_alloc[sbgr] == 5 || tc_alloc[sbgr] < 3) + && dyn_cross_LR) + current_frame->allocation[3][idx] = + current_frame->allocation[1][idx]; + else + current_frame->allocation[3][idx] = + current_frame->allocation[0][idx]; + } + break; + + case 4: + { + /* channel mode 3/2 */ + if (center == 3 && idx >= 12) + current_frame->allocation[2][idx] = 0; + else + switch (dyn_cross_mode[sbgr]) + { + case 1: case 2: case 4: case 8: + case 9: case 10: case 11: case 12: + case 14: + current_frame->allocation[2][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + break; + case 3: case 5: case 6: case 7: + case 13: + if (tc_alloc[sbgr] == 1 || tc_alloc[sbgr] == 7) + current_frame->allocation[2][idx] = + current_frame->allocation[0][idx]; + else + if (tc_alloc[sbgr] == 2 || tc_alloc[sbgr] == 6 || dyn_cross_LR) + current_frame->allocation[2][idx] = + current_frame->allocation[1][idx]; + else + current_frame->allocation[2][idx] = + current_frame->allocation[0][idx]; + break; + default: + break; + } + + switch (dyn_cross_mode[sbgr]) + { + case 1: case 3: case 5: case 8: + case 10: case 13: + current_frame->allocation[3][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + break; + case 2: case 4: case 6: case 7: + case 12: + current_frame->allocation[3][idx] = + current_frame->allocation[0][idx]; + break; + case 9: case 11: case 14: + current_frame->allocation[3][idx] = + current_frame->allocation[2][idx]; + break; + default: + break; + } + + switch (dyn_cross_mode[sbgr]) + { + case 2: case 3: case 6: case 9: + current_frame->allocation[4][idx] = + (unsigned char)extract_bits(buf, &pointer, alloc_table[idx]); + break; + case 1: case 4: case 5: case 7: + case 11: + current_frame->allocation[4][idx] = + current_frame->allocation[1][idx]; + break; + case 10: case 12: case 14: + current_frame->allocation[4][idx] = + current_frame->allocation[2][idx]; + break; + case 8: case 13: + current_frame->allocation[4][idx] = + current_frame->allocation[3][idx]; + break; + default: + break; + } + } + break; + + default: + /* fr_pr->dyn_cross_bits == 0 */ + break; + } + } + } + } + + + for (idx=0; idxallocation[jdx][idx]) + /* two more bits for scalefactor selection info */ + pointer += 2; + + + /* end gathering data - now check the results */ + if (pointer-start_pointer <= ancill_len) + { + + start_pointer += MC_TC_SBGR_SELECT_POS + 8*ext_bs_present; + crc_new = crc_update(crc_new, buf, &start_pointer, pointer-start_pointer); + + + if (crc_new == crc_old) + { + //printf("Success!\n"); + si->mc.mc_stream_verified |= 1; + si->mc.extension = ext_bs_present; + si->mc.lfe = lfe_present; + si->mc.mc_channels = fr_pr->mc_channels; + si->mc.multi_lingual = no_of_multi_lingual_channels; + si->mc.multi_lingual_fs = multi_lingual_fs; + si->mc.multi_lingual_layer = multi_lingual_layer; + si->mc.configuration_value = (surround<<4)+center; + si->mc_verified_frames++; + } + else + { +// printf("Failure @ frame %d (read %d bit) :-(\n", si->totFrameNum,pointer-start_cancellare); + /* may this be a valid incomplete frame? */ + if (!layer2) + { + /* take into account we do NOT collect allocation data for layerIII + and for layerI they seem not to be consistent with those from + additional channels */ + if (si->mc.extension == ext_bs_present && + si->mc.lfe == lfe_present && + si->mc.mc_channels == fr_pr->mc_channels && + si->mc.multi_lingual == no_of_multi_lingual_channels && + si->mc.multi_lingual_fs == multi_lingual_fs && + si->mc.multi_lingual_layer == multi_lingual_layer && + si->mc.configuration_value == (surround<<4)+center) + { + si->mc.mc_stream_verified |= 0x80; + si->mc_coherent_frames++; + } + else + { + if (si->mc.extension == 0 && + si->mc.lfe == 0 && + si->mc.mc_channels == 0 && + si->mc.multi_lingual == 0 && + si->mc.multi_lingual_fs == 0 && + si->mc.multi_lingual_layer == 0 && + si->mc.configuration_value == 0) + { + si->mc.extension = ext_bs_present; + si->mc.lfe = lfe_present; + si->mc.mc_channels = fr_pr->mc_channels; + si->mc.multi_lingual = no_of_multi_lingual_channels; + si->mc.multi_lingual_fs = multi_lingual_fs; + si->mc.multi_lingual_layer = multi_lingual_layer; + si->mc.configuration_value = (surround<<4)+center; + si->mc.mc_stream_verified |= 0x80; + si->mc_coherent_frames = 1; + } + } + } + } + } + else + { +// printf("Incoherent data read, aborting (read %d bit, avail %d bit).\n",pointer-start_pointer,ancill_len); + if (ext_bs_present) + { + /* maybe ancillary room was not enough */ + if (si->mc.extension == ext_bs_present && + si->mc.lfe == lfe_present && + si->mc.mc_channels == fr_pr->mc_channels && + si->mc.multi_lingual == no_of_multi_lingual_channels && + si->mc.multi_lingual_fs == multi_lingual_fs && + si->mc.multi_lingual_layer == multi_lingual_layer && + si->mc.configuration_value == (surround<<4)+center) + { + si->mc.mc_stream_verified |= 0x80; + si->mc_coherent_frames++; + } + else + { + if (si->mc.extension == 0 && + si->mc.lfe == 0 && + si->mc.mc_channels == 0 && + si->mc.multi_lingual == 0 && + si->mc.multi_lingual_fs == 0 && + si->mc.multi_lingual_layer == 0 && + si->mc.configuration_value == 0) + { + si->mc.extension = ext_bs_present; + si->mc.lfe = lfe_present; + si->mc.mc_channels = fr_pr->mc_channels; + si->mc.multi_lingual = no_of_multi_lingual_channels; + si->mc.multi_lingual_fs = multi_lingual_fs; + si->mc.multi_lingual_layer = multi_lingual_layer; + si->mc.configuration_value = (surround<<4)+center; + si->mc.mc_stream_verified |= 0x80; + si->mc_coherent_frames = 1; + } + } + } + } + +} + + +off_t resync_mpeg(FILE *fp, off_t pos, unsigned char **storage, streamInfo *si, char relaxed) +/* + * Scan the mpeg stream for sync bits + * input params: + * fp the current file pointer + * pos a starting offset + * storage the resulting frame pointer (optional) + * it is guaranteed the buffer will be large enough + * to contain a whole mpeg frame + * si stream info pointer (optional) used to know + * the size of the frame just found + * relaxed when 1, makes the scan take as valid also layerIII + * headers having 1111b (reserved) in the bitrate field + * (old versions of lame used to set this in vbr + * tags of free format streams) + * return: + * the offset of the new frame found + * -1 in case of invalid pos / no valid header found + * storage when valid, it holds the new buffer pointer + * + */ +{ + unsigned int head; + int search_length, idx, min_buffer_size; + off_t result=-1; + static off_t actual_pos=-1; /* filepos of the first byte into mp3g_storage */ + static int actual_storage=0; /* how many valid bytes we store into mp3g_storage */ + + if (pos>=0 && fp!=NULL) + { + + /* first step: ensure under 'pos' I can read a valid byte stream */ + if (pos=actual_pos+actual_storage-(int)sizeof(unsigned int)) + { + fseeko(fp, pos, SEEK_SET); + actual_pos = pos; + actual_storage = fread(mp3g_storage, 1, LARGEST_BUFFER, fp); + } + + + search_length = SCAN_SIZE; + + idx = pos-actual_pos; + head = (unsigned int)mp3g_storage[idx+0]<<16 | + (unsigned int)mp3g_storage[idx+1]<< 8 | + (unsigned int)mp3g_storage[idx+2]; + + /* second step: cycle until I'll find a valid mpeg header */ + while (search_length>0 && actual_storage>3) + { + head = (head<<8) | (unsigned int)mp3g_storage[idx+3]; + + if (head_check(head, relaxed)) + { + result = actual_pos+(off_t)idx; + break; + } + + search_length--; + idx++; + + if (idx>actual_storage-(int)sizeof(unsigned int)) + { + /* seek at the beginning of the header being analyzed */ + fseeko(fp, actual_pos+(off_t)idx-sizeof(unsigned int), SEEK_SET); + actual_pos += (off_t)idx-sizeof(unsigned int); + idx = 0; + + if ((actual_storage = fread(mp3g_storage, 1, LARGEST_BUFFER, fp)) == 0) + /* eof reached */ + break; + + head = (unsigned int)mp3g_storage[idx+0]<<16 | + (unsigned int)mp3g_storage[idx+1]<< 8 | + (unsigned int)mp3g_storage[idx+2]; + } + } + + /* check result */ + if (result != -1) + { + /* a valid frame header was found */ + if (si == NULL) + { + min_buffer_size = LARGEST_FRAME; + } + else + { + if (head&HEADER_FIELD_PADDING) + { + min_buffer_size = si->padded[(head&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT].frameSize; + } + else + { + min_buffer_size = si->unpadded[(head&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT].frameSize; + } + } + + /* evaluate remaining bytes */ + if (actual_storage-idx < min_buffer_size && actual_storage == LARGEST_BUFFER) + { + /* the buffer is not large enough to hold a whole frame - re-fill! */ + actual_pos += idx; + idx = 0; + fseeko(fp, actual_pos, SEEK_SET); + if ((actual_storage = fread(mp3g_storage, 1, LARGEST_BUFFER, fp)) == 0) + { + /* this happens at the very end of the file (and no trailing metadata) */ + result = -1; + } + } + + /* return the buffer start pointer */ + if (storage != NULL) + { + *storage = &mp3g_storage[idx]; + } + } + } + + return result; +} + +/* + * this is used to set up `regular' bitrates only + * in case of free format bitstreams, the setup has already + * been done into main, thanks to 'checkvbrinfotag' routine + */ +void setup_framesize(unsigned int head, streamInfo *si, detectionInfo *di) +{ + unsigned int i, slot, headbase; + + if (di->lay==1) slot=4; + else slot=1; + + headbase=head&HEADER_ANY_BUT_BITRATE_AND_PADDING_FIELDS; + for (i=1;i<15;i++) + { + si->unpadded[i].frameSize=get_framesize(headbase|(i<padded[i].frameSize=si->unpadded[i].frameSize+slot; + } +} + +#if 0 +void show_me_everything(unsigned int header) +{ + int dummy, lsf, layer, mpeg; + + mpeg = (header&HEADER_FIELD_MPEG_ID)>>HEADER_FIELD_MPEG_ID_SHIFT; + printf("M%s",((mpeg==0)?"2.5":(mpeg==2)?"2":"1")); + lsf = (mpeg==0 || mpeg ==2); + + layer = (header&HEADER_FIELD_LAYER)>>HEADER_FIELD_LAYER_SHIFT; + printf("L%s ",((layer==3)?"I":(layer==2)?"II":"III")); + + dummy = (header&HEADER_FIELD_CRC)>>HEADER_FIELD_CRC_SHIFT; + if (!dummy) printf("CRC "); + + dummy = (header&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT; + if (dummy) + printf("%dkbps ",bitrate_tab[lsf][3-layer][dummy]); + else + printf("ff "); + + dummy = (header&HEADER_FIELD_SAMPRATE)>>HEADER_FIELD_SAMPRATE_SHIFT; + printf("%dHz ",samplerate_tab[dummy][mpeg]); + + dummy = (header&HEADER_FIELD_PADDING)>>HEADER_FIELD_PADDING_SHIFT; + if (dummy) printf("pad "); + + dummy = (header&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT; + printf("%dch ",(dummy==3)?1:2); + + dummy = (header&HEADER_FIELD_COPYRIGHT)>>HEADER_FIELD_COPYRIGHT_SHIFT; + printf("COPY:%s ",(dummy)?"yes":"no"); + + dummy = (header&HEADER_FIELD_ORIGINAL)>>HEADER_FIELD_ORIGINAL_SHIFT; + printf("ORIG:%s ",(dummy)?"yes":"no"); + + dummy = (header&HEADER_FIELD_EMPHASIS)>>HEADER_FIELD_EMPHASIS_SHIFT; + printf("emph:%s ",((dummy==0)?"none":(dummy==1)?"50/15ms":"CCITT J-17")); + + if (((header&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT)!=BITRATE_INDEX_FREEFORMAT) + printf("(%db)",get_framesize(header)); + printf("\n"); +} +#endif + +/* + * this will gather information from ancillary data into layerIII + * streams. Enhanced bitstreams (mp3pro/mp3surrond), OFL (original file + * length) blocks and lame signatures are detected here. + */ +void scan_ancillary_III(detectionInfo *di, streamInfo *si, int ancill_Len, unsigned char *buf) +{ + if (ancill_Len==0) + { /* no ancillary data into the current frame */ + /* this is not expected to happen both in mp3pro and mp3surround + streams, hence I reset the `enhanced features' bytes */ + di->enhSignature[0] = 0; + di->enhSignature[1] = 0; + } + else + { + /* check for an OFL block into the very first frame */ + if (si->totFrameNum==1 && di->lay==3 /* first frame in a layerIII stream */ + && + ( + (di->id!=MPEG_CODE_MPEG1 && ancill_Len>20 && (unsigned char)buf[0]==OFL_IN_MP3PRO_BYTE_1 && (unsigned char)buf[1]==OFL_IN_MP3PRO_BYTE_2) + || + (di->id==MPEG_CODE_MPEG1 && ancill_Len> 9 && (unsigned char)buf[0]==OFL_IN_MP3SURROUND_BYTE_1 && ((unsigned char)buf[1]==OFL_IN_MP3SURROUND_BYTE_2 || (unsigned char)buf[1]==OFL_IN_PLAINMP3_BYTE_2)) + ) + ) + { + di->ofl=1; + if (di->id!=MPEG_CODE_MPEG1) + { /* this is an mp3pro stream with OFL */ + /* at least 20 bytes of ancillary data into the very first frame are used for length information */ + si->ofl_encDelay=extract_bits(buf+14, NULL, 16); + si->ofl_orig_samples=extract_bits(buf+16, NULL, 32); + di->enhSignature[0] &= buf[0]; + di->enhSignature[1] &= buf[1]; + } + else + { + /* not an mp3pro stream */ + /* 10 bytes of ancillary data into the very first frame are used for length information */ + si->ofl_encDelay=extract_bits(buf+1, NULL, 16); + si->ofl_orig_samples=extract_bits(buf+3, NULL, 32); + if ((unsigned char)buf[1]==OFL_IN_MP3SURROUND_BYTE_2) + { + /* mp3Surround with OFL */ + si->ofl_encDelay+=576; + if (ancill_Len>10) di->enhSignature[0] &= buf[10]; + if (ancill_Len>11) di->enhSignature[1] &= buf[11]; + } + } + } + else + { + if (ancill_Len>0) di->enhSignature[0] = di->enhSignature[0] & buf[0]; + if (ancill_Len>1) di->enhSignature[1] = di->enhSignature[1] & buf[1]; + } + di->ancillaryData += (off_t)ancill_Len; + if (ancill_Len>si->ancill_max) si->ancill_max = ancill_Len; + if (ancill_Lenancill_min) si->ancill_min = ancill_Len; + extract_enc_string(di->encoder_string,buf,ancill_Len); + } +} + + +char read_side_information(unsigned int header, unsigned char *buff, currentFrame *current) +{ + int gg,bigvalues; + char gr,ngr=((header&HEADER_FIELD_LSF)>>HEADER_FIELD_LSF_SHIFT)+1,ch, + nch=(((header&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT)==MODE_MONO)?1:2, + window_switching_flag,block_type,mixed_block_flag,errors=0; + unsigned char mpegid=(header&HEADER_FIELD_MPEG_ID)>>HEADER_FIELD_MPEG_ID_SHIFT, + mode=(header&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT; + currentFrame local; + unsigned int pointer=0; + + memset((void *)&local, 0, sizeof(currentFrame)); + local.min_global_gain[0] = local.min_global_gain[1] = 255; + + if (mpegid==MPEG_CODE_MPEG1) + local.main_data_begin = extract_bits(buff,&pointer,9); + else + local.main_data_begin = extract_bits(buff,&pointer,8); + + + /* read private bits */ + if (mpegid==MPEG_CODE_MPEG1) + { + if (mode==MODE_MONO) + extract_bits(buff,&pointer,5); + else + extract_bits(buff,&pointer,3); + } + else + { + if (mode==MODE_MONO) + extract_bits(buff,&pointer,1); + else + extract_bits(buff,&pointer,2); + } + + /* read scalefactor selection information */ + if (mpegid == MPEG_CODE_MPEG1) + { + for(ch=0; ch 288) + errors++; + gg = extract_bits(buff,&pointer,8); /* global_gain */ + if (gg>local.max_global_gain[(int)ch]) local.max_global_gain[(int)ch]=(unsigned char)gg; + if (ggmain_data_begin = local.main_data_begin; + current->usesScfsi[0] = local.usesScfsi[0]; + current->usesScfsi[1] = local.usesScfsi[1]; + current->part2_3_length = local.part2_3_length; + current->min_global_gain[0] = local.min_global_gain[0]; + current->min_global_gain[1] = local.min_global_gain[1]; + current->max_global_gain[0] = local.max_global_gain[0]; + current->max_global_gain[1] = local.max_global_gain[1]; + current->blockCount[BLOCKCOUNT_LONG] = local.blockCount[BLOCKCOUNT_LONG]; + current->blockCount[BLOCKCOUNT_SHORT] = local.blockCount[BLOCKCOUNT_SHORT]; + current->blockCount[BLOCKCOUNT_MIXED] = local.blockCount[BLOCKCOUNT_MIXED]; + current->blockCount[BLOCKCOUNT_SWITCH] = local.blockCount[BLOCKCOUNT_SWITCH]; + current->usesScalefacScale = local.usesScalefacScale; + current->part1_length = pointer + 8 * (sizeof(unsigned int) + + ((header&HEADER_FIELD_CRC)? 0 : sizeof(unsigned short))); + } + + return errors; +} + + +void scan_layer__I(streamInfo *si, detectionInfo *di, currentFrame *current_frame, unsigned char *buf, int verbosity, unsigned char op_flag) +{ +/* + * this routine is heavily based upon the source code of libmad (layer12.c). + */ + unsigned int pointer; + int scalefact_len, ch, nch, sb, s, ancillaryLen; + + /* get the channel configuration */ + pointer = 24; + nch = ((extract_bits(buf, &pointer, 2) == MODE_MONO)? 1 : 2); + + /* scalefactors */ + scalefact_len = 0; + for (sb=0; sb<32; sb++) + for (ch=0; challocation[ch][sb]) + /* each scalefactor is 6 bit long */ + scalefact_len += 6; + } + + current_frame->part2_3_length = scalefact_len; + + /* samples */ + for (s=0; s<12; s++) + { + for (sb=0; sbbound; sb++) + for (ch=0; chpart2_3_length += current_frame->allocation[ch][sb]; + for (; sb<32; sb++) + current_frame->part2_3_length += current_frame->allocation[0][sb]; + } + + /* get padding - in layerI a slot is 4 byte long */ + pointer = 22; + s = extract_bits(buf, &pointer, 1); + + /* check frame integrity */ + if ((current_frame->part1_length+current_frame->part2_3_length) > + (current_frame->expected_framesize*8)) + { + /* something wrong here */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + { + if (current_frame->uncertain) + printf(" This frame is broken.\n\n"); + else + { + printf("Warning! abnormal length in frame %u (%s padding)\n" + "part1=%d part23=%d expected_size=%d\n", si->totFrameNum, ((s)?"with":"no"), + current_frame->part1_length, current_frame->part2_3_length, current_frame->expected_framesize); +#if 0 + for (ch=0; ch<2; ch++) + { + for (sb=0; sb<32; sb++) + printf(" %2d",current_frame->allocation[ch][sb]); + printf("\n"); + } +#endif + } + } + } + else + { + if (current_frame->uncertain) + { + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf(" This frame is valid.\n\n"); + } + current_frame->uncertain = 0; + ancillaryLen = (current_frame->expected_framesize*8) + - current_frame->part1_length - current_frame->part2_3_length; + + if (op_flag&OPERATIONAL_FLAG_DETECT_MC_IN_ALL_LAYERS) + scan_multichannel(si, buf, current_frame->part1_length+current_frame->part2_3_length, ancillaryLen, current_frame); + + ancillaryLen /= 8; + if (s) + { + /* this frame has a padding slot */ + ancillaryLen -= 4; + } + + if (ancillaryLen > 0) + { + di->ancillaryData += (off_t)ancillaryLen; + if (ancillaryLen>si->ancill_max) si->ancill_max = ancillaryLen; + if (ancillaryLenancill_min) si->ancill_min = ancillaryLen; + //printf("ancillary found frame %d, %d bytes\n",si->totFrameNum, ancillaryLen); + } + } +} + +void scan_layer_II(streamInfo *si, detectionInfo *di, currentFrame *current_frame, unsigned char *buf, int verbosity) +{ +/* + * this routine is heavily based upon the source code of libmad (layer12.c). + */ + unsigned char halfway_index[5][30]= + { + /* offset matrix for samples table */ + {5,5,5,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0}, + {5,5,5,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0}, + {3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3} + }; + unsigned char samples_table[6][15]= + { + /* samples table - derived from offset_table merged with qc_table (quantization classes) */ +// { 0, 1, 16 }, + { 5, 7, 48 }, +// { 0, 1, 2, 3, 4, 5, 16 }, + { 5, 7, 9, 10, 12, 15, 48 }, +// { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }, + { 5, 7, 9, 10, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42 }, +// { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 5, 7, 10, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45 }, +// { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16 }, + { 5, 7, 9, 10, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 48 }, +// { 0, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 } + { 5, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48 } + }; + unsigned int pointer; + int off, scf_len=0, sample_len=0, ancillaryLen; + unsigned char idx, jdx, sblimit, nch, gr; + + /* get the channel configuration */ + pointer = 24; + nch = ((extract_bits(buf, &pointer, 2) == MODE_MONO)? 1 : 2); + + sblimit = subband_limit[current_frame->index]; + + /* scalefactor section length */ + for (idx=0; idxallocation[jdx][idx]) + { + if (current_frame->scfsi[jdx][idx] == 2) + { + /* single scalefactor */ + scf_len += 6; + } + else + { + if (current_frame->scfsi[jdx][idx] == 0) + { + /* three scalefactors */ + scf_len += 18; + } + else + { + /* two scalefactors */ + scf_len += 12; + } + } + } + } + + for (gr=0; gr<12; gr++) + { + for (idx=0; idxbound; idx++) + for (jdx=0; jdxallocation[jdx][idx]) + { + off = halfway_index[current_frame->index][idx]; + sample_len += (int)samples_table[off][current_frame->allocation[jdx][idx]-1]; + } + for (; idxallocation[0][idx]) + { + off = halfway_index[current_frame->index][idx]; + sample_len += (int)samples_table[off][current_frame->allocation[0][idx]-1]; + } + } + + current_frame->part2_3_length = scf_len + sample_len; + + /* get padding - in layerII a slot is 8 bit long */ + pointer = 22; + idx = extract_bits(buf, &pointer, 1); + + /* check frame integrity */ + if ((current_frame->part1_length+current_frame->part2_3_length) > + (current_frame->expected_framesize*8)) + { + /* something wrong here */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + { + if (current_frame->uncertain) + printf(" This frame is broken.\n\n"); + else + { + printf("Warning! abnormal length in frame %u (%s padding)\n" + "part1=%d part23=%d expected_size=%d\n", si->totFrameNum, ((idx)?"with":"no"), + current_frame->part1_length, current_frame->part2_3_length, current_frame->expected_framesize); +#if 0 + for (jdx=0; jdx<2; jdx++) + { + for (sb=0; sb<32; sb++) + printf(" %2d",current_frame->allocation[jdx][sb]); + printf("\n"); + } +#endif + } + } + } + else + { + if (current_frame->uncertain) + { + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf(" This frame is valid.\n\n"); + } + current_frame->uncertain = 0; + ancillaryLen = (current_frame->expected_framesize*8) + - current_frame->part1_length - current_frame->part2_3_length; + + scan_multichannel(si, buf, current_frame->part1_length+current_frame->part2_3_length, ancillaryLen, current_frame); + + ancillaryLen /= 8; + if (idx) + { + /* this frame has a padding slot */ + ancillaryLen--; + } + + if (ancillaryLen > 0) + { + di->ancillaryData += (off_t)ancillaryLen; + if (ancillaryLen>si->ancill_max) si->ancill_max = ancillaryLen; + if (ancillaryLenancill_min) si->ancill_min = ancillaryLen; + } + } +} + +void scan_layerIII(streamInfo *si, detectionInfo *di, bitoffs_t startOfFrame, currentFrame *current_frame, + bitoffs_t *endOfLastPart3, unsigned char *buf, unsigned char *local_buff, int local_buff_size, + int verbosity, unsigned char op_flag) +{ + char things_went_ok=0; + bitoffs_t pos_main_data_begin; + + /* we've just read the side information */ + + load_into_p23b(buf+current_frame->part1_length/8, + startOfFrame+current_frame->part1_length, + current_frame->expected_framesize-current_frame->part1_length/8); + + /* first of all, I need to properly set `pos_main_data_begin', + that is, the offset of the first bit of part2+3 data of the + current frame -- + in case of insufficient data, `pos_main_data_begin' is set to -1 */ + if (current_frame->main_data_begin == 0) + { + pos_main_data_begin = startOfFrame+current_frame->part1_length; + } + else + { /* MAY THINGS BE EASIER ? Nope. + Whilst it may appear obvious, I cannot seek to `startOfFrame' position and + then skip back by `main_data_begin*8' bit, because the `startOfFrame' bit + belongs to the frame header, and thus it's not been loaded into p23b. + Instead, the bit `startOfFrame-1' belongs to the previous part2+3 and chances + are it's already into p23b + */ + + /* if main_data_begin != 0 then it has to be meant as a negative number => it's a real backpointer */ + if (seek_p23b(startOfFrame-1)) + { + if (skipback_p23b(current_frame->main_data_begin*8-1)) /* if so, then skip back */ + { + /* not enough data collected */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("frame %u : More bits in reservoir are needed to decode this frame.\n",si->totFrameNum); + pos_main_data_begin = -1; + } + else + { + /* the seek operation returned ok */ + pos_main_data_begin = tell_p23b(); + } + } + else + { + /* no data before current part1 data */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("frame %u : more bits in reservoir are needed to decode this frame.\n",si->totFrameNum); + pos_main_data_begin = -1; + } + } + /* I put here a simple check in order to prevent useless calls to `scan_ancillary' + * when I cannot have ancillary data. Of course, having endOfLastPart3 greater than + * pos_main_data_begin is wrong and a call to `scan_ancillary' may lead to reset (by + * mistake) the enhanced feature bytes while detection of mp3pro/mp3surround streams + * is ongoing. + * This is likely to happen when sync goes lost and a bunch of frames have to be skipped + */ + if (*endOfLastPart3 > pos_main_data_begin) + *endOfLastPart3 = -1; + + if (pos_main_data_begin != -1 /* was the beginning of main_data found? */ + && + *endOfLastPart3 != -1) /* was I able to seek the end of part2+3 data for the previous frame? */ + { + int ret; + if ((*endOfLastPart3)>0) + { + ret=seek_p23b(*endOfLastPart3); + skip_p23b(1); + } + else + { + ret=seek_p23b(-1*(*endOfLastPart3)); + } + if (ret) + { + int transferred, byte_off; + /* the function `p23b_cpy' will take care in rounding `*endOfLastPart3' + * to the next byte and when needed, move the bit pointer to the next block. + */ + transferred = p23b_cpy(local_buff,pos_main_data_begin,local_buff_size, &byte_off); + if (op_flag&OPERATIONAL_FLAG_DETECT_MC_IN_ALL_LAYERS) + scan_multichannel(si, local_buff, byte_off, transferred*8, current_frame); + if (byte_off > 0) + /* fixing ancillary start, since useful detection data get stored byte-aligned */ + scan_ancillary_III(di,si,transferred-1,local_buff+1); + else + scan_ancillary_III(di,si,transferred,local_buff); + } + } + *endOfLastPart3 = -1; + + /* this block skips part2+3 data and sets endOfLastPart3 for the next iteration */ + if (pos_main_data_begin != -1) + { + if (seek_p23b(pos_main_data_begin)) + { + if (current_frame->part2_3_length) /* is there audio data actually? sometimes there isn't. + Of course one can think this is due to data corruption, + anyway I can't say anything without trying to decode data */ + { + /* I will skip `part2_3_length-1' bits for a reason: + * skipping `part2_3_length' bits will make the bit pointer seek to the + * first bit of non-part2+3 data. + * In some cases, when a frame doesn't contain ancillary data at all, + * this bit happens to be on the next frame (in other words, the next + * array entry) which is not yet loaded into p23b and performing the + * seek can lead to unexpected results. + */ + if (skip_p23b(current_frame->part2_3_length-1) == 0) + { + *endOfLastPart3 = tell_p23b(); /* now `endoflastpart3' points to the very last bit of part2+3 */ + things_went_ok=1; + } + else + { + /* not enough data found for the seek-forward operation. + * this may be due to either a broken frame (which doesn't contain + * enough data) or data corruption (which caused a wrong value into + * part2_3_length) + */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + { + if (current_frame->uncertain) + printf(" This frame is broken.\n\n"); + else + printf("Frame %u may be corrupted.\n",si->totFrameNum); + } + } + } + else + { + *endOfLastPart3 = -1*tell_p23b(); /* set a noticeable value for `endOfLastPart3' ! */ + if (current_frame->uncertain && (verbosity&VERBOSE_FLAG_PROGRESS)) + /* this last frame is broken and it has part2_3_length=0 => I think it's corrupted */ + printf(" This frame is corrupted.\n\n"); + } + } + else + { + /* This is a very uncommon case: we cannot seek to the beginning of main + * data block, although we can calculate it. For this to happen, the frame + * has to have main_data_begin=0 so no backpointer (part2+3 begin into + * the frame itself, after the side information block) and the frame has + * to be broken at the end of the side information block (or before). + */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + { + if (current_frame->uncertain) + printf(" "); + printf("Audio data block is missing at all.\n\n"); + } + } + } + /* tell the user about the last frame */ + if (current_frame->uncertain && things_went_ok) + { + current_frame->uncertain=0; + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf(" This frame is valid.\n\n"); + } +} + +void scan(FILE *input_file, off_t *pos, streamInfo *si, detectionInfo *di, off_t id3pos, int verbosity, unsigned char op_flag) +{ + unsigned int head,head_pattern,mask; + off_t oldpos; + unsigned char local_buff[LARGEST_FRAME]; + unsigned char *buf; + /* `endOfLastPart3' is used in order to get started with the scan into ancillary data. + * When it stores a positive number, then it points to the last bit of a part2+part3 + * audio data block. It is used by seeking at `endOfLastPart3' and then skipping a single + * further bit (a non-audio-data bit is expected there). + * The value -1 means `unset'. + * Finally, any other negative number means an usable value. It is used by seeking at + * `-endOfLastPart3' but this time no skipping gets performed. This is useful for frames + * which don't have audio data block (part2+part3 length is zero), so in those cases + * the pointed bit is already a non-audio-data bit and the seek to `-endOfLastPart3' + * does not need a subsequent bit skip. + */ + bitoffs_t endOfLastPart3=-1; + currentFrame current_frame; + +/* + * I'm sure I'm about to read the first valid audio frame of the stream, + * since `scan' is called right after a successfull resync_mpeg(). + * Now resync_mpeg() is called again in order to get the value of `head' + * and to properly set the file pointer. + */ + + oldpos=resync_mpeg(input_file, *pos, &buf, NULL, 0); + head = extract_bits(buf, NULL, 32); + +/* + * Gathering basic properties of the mpeg stream, from now on I will assume + * the other frames have the same mpeg version/layer/freq/etc... + */ + + si->freeformat = (head&HEADER_FIELD_BITRATE)==BITRATE_INDEX_FREEFORMAT; + si->lsf = 1-((head&HEADER_FIELD_LSF) >> HEADER_FIELD_LSF_SHIFT); + di->id = (head&HEADER_FIELD_MPEG_ID) >> HEADER_FIELD_MPEG_ID_SHIFT; /* 0=mpeg2.5, 1=resvd, 2=mpeg2, 3=mpeg1 */ + di->freq = samplerate_tab[(head&HEADER_FIELD_SAMPRATE) >> HEADER_FIELD_SAMPRATE_SHIFT][di->id]; + di->lay = 4-((head&HEADER_FIELD_LAYER) >> HEADER_FIELD_LAYER_SHIFT); /* so it is 1=layerI, 2=layerII, 3=layerIII */ + di->mode = (head&HEADER_FIELD_CHANNELS) >> HEADER_FIELD_CHANNELS_SHIFT; /* channel mode (stereo, Jstereo, Dchannel, mono) */ + + si->crc_present = ((~head)&HEADER_FIELD_CRC) >> HEADER_FIELD_CRC_SHIFT; + si->crc_checked = 1; + si->copyright = (head&HEADER_FIELD_COPYRIGHT) >> HEADER_FIELD_COPYRIGHT_SHIFT; + si->original = (head&HEADER_FIELD_ORIGINAL) >> HEADER_FIELD_ORIGINAL_SHIFT; + si->emphasis = (head&HEADER_FIELD_EMPHASIS) >> HEADER_FIELD_EMPHASIS_SHIFT; + + setup_framesize(head, si, di); + + /* mask selection: looks like layer I/II when encoding jstereo, switch between different stereo + modes (they put 0x0, 0x1 or 0x2 in the channels field), while layer III only uses + jstereo frames (0x1 in the channels field) and puts further information into the mode + extension field */ + if (layerIII(head)||((head&HEADER_FIELD_CHANNELS>>HEADER_FIELD_CHANNELS_SHIFT)==MODE_MONO)) + mask=HEADER_CONSTANT_FIELDS_STRICT; + else + mask=HEADER_CONSTANT_FIELDS; + + /* using a bit mask will let me check a whole header in a single `if' statement */ + head_pattern = head & mask; + + + memset((void *)¤t_frame, 0, sizeof(currentFrame)); + current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]=current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]=255; + current_frame.maybe_garbage=1; + + while (*pos!=-1) + { + head = extract_bits(buf, NULL, 32); + + current_frame.maybe_garbage=0; /* when *pos!=-1 I am sure a valid header was found */ + + if (*pos!=oldpos) + { + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("Resync done at frame %u, skipped %d bytes.\n",si->totFrameNum,(int)(*pos-oldpos)); + endOfLastPart3=-1; + } + + + current_frame.bitrate_index=(head&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT; + + if (head&HEADER_FIELD_PADDING) + current_frame.expected_framesize=si->padded[(int)current_frame.bitrate_index].frameSize; + else + current_frame.expected_framesize=si->unpadded[(int)current_frame.bitrate_index].frameSize; + + /* now check the header we've just read */ + if ((head&mask)!=head_pattern || (freeformat(head)!=si->freeformat)) + { + si->nSyncError++; + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("Sync error at frame %u, offset %lld. Seeking for resync...\n",si->totFrameNum,(long long)*pos); + *pos=resync_mpeg(input_file, (*pos)+1, &buf, si, 0); + continue; + } + + /* checks on the very first part of the frame */ + if (malformed_part1(buf, ¤t_frame)) + { + si->nSyncError++; + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("Corrupted frame %u, offset %lld. Seeking for resync...\n",si->totFrameNum,(long long)*pos); + *pos=resync_mpeg(input_file, (*pos)+1, &buf, si, 0); + continue; + } + + /* check integrity for this frame */ + if (*pos+(off_t)current_frame.expected_framesize>id3pos) + { + if (*pos+(off_t)((current_frame.part1_length-1)/8+1) <= id3pos) + { + /* this frame has not the size we expected, maybe it's truncated. + part1 seems to be there, nevertheless warn the user and take care */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("Frame %u at offset %lld appears to be incomplete.\n %d bytes available, %d expected.\n",si->totFrameNum,(long long)*pos,(int)(id3pos-(*pos)),current_frame.expected_framesize); + current_frame.uncertain=1; + current_frame.expected_framesize=(int)(id3pos-(*pos)); /* fixing framesize */ + } + else + { + /* cannot read the very first part of the frame - surely broken */ + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("Frame %u at offset %lld is broken.\n %d bytes available, %d expected.\n\n",si->totFrameNum,(long long)*pos,(int)(id3pos-(*pos)),current_frame.expected_framesize); + endOfLastPart3 = -1; /* invalidate the search for ancillary - it makes no sense without a whole part1 information */ + break; + } + } + + + if (!(head&HEADER_FIELD_CRC)) + { + /* verify the crc */ + current_frame.crc = extract_bits(buf+sizeof(unsigned int), NULL, 16); + + if (!verify_crc(head, buf+sizeof(unsigned int)+sizeof(unsigned short), current_frame.part1_length, current_frame.crc)) + { + si->crc_checked = 0; + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf("CRC mismatch at frame %u, offset %lld. Resync...\n",si->totFrameNum,(long long)*pos); + *pos=resync_mpeg(input_file,(*pos)+1, &buf, si, 0); + continue; + } + } + + if (layerI(head)) + { + scan_layer__I(si, di, ¤t_frame, buf, verbosity, op_flag); + } + + if (layerII(head)) + { + scan_layer_II(si, di, ¤t_frame, buf, verbosity); + } + + if (layerIII(head)) + { + scan_layerIII(si,di,(bitoffs_t)(*pos)*8,¤t_frame,&endOfLastPart3,buf,local_buff,LARGEST_FRAME,verbosity,op_flag); + } + + if (current_frame.uncertain) + break; + + /* Now I am sure the frame is valid */ + + if (head&HEADER_FIELD_PADDING) + { + di->usesPadding = 1; + si->padded[(int)current_frame.bitrate_index].frameCount++; + } + else + si->unpadded[(int)current_frame.bitrate_index].frameCount++; + si->bitrateCount[(int)current_frame.bitrate_index]++; + + si->totFrameLen += (off_t)current_frame.expected_framesize; + si->totFrameNum++; + + if (layerIII(head)) /* skip useless computations in layers other than III - just a speed-up */ + { + if (current_frame.main_data_begin > si->reservoirMax) + si->reservoirMax=current_frame.main_data_begin; + di->usesScfsi=di->usesScfsi | current_frame.usesScfsi[0] | current_frame.usesScfsi[1]; + di->usesScalefacScale=di->usesScalefacScale | current_frame.usesScalefacScale; + + if (current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT] < si->min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]) + si->min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]=current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]; + if (current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT] < si->min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]) + si->min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]=current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]; + if (current_frame.max_global_gain[GLOBAL_GAIN_CHANNEL_LEFT] > si->max_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]) + si->max_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]=current_frame.max_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]; + if (current_frame.max_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT] > si->max_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]) + si->max_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]=current_frame.max_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]; + + di->blockCount[BLOCKCOUNT_LONG] +=current_frame.blockCount[BLOCKCOUNT_LONG]; + di->blockCount[BLOCKCOUNT_SHORT] +=current_frame.blockCount[BLOCKCOUNT_SHORT]; + di->blockCount[BLOCKCOUNT_MIXED] +=current_frame.blockCount[BLOCKCOUNT_MIXED]; + di->blockCount[BLOCKCOUNT_SWITCH]+=current_frame.blockCount[BLOCKCOUNT_SWITCH]; + + if (op_flag&OPERATIONAL_FLAG_VERIFY_MUSIC_CRC) + { + si->musicCRC = crc_reflected_update(si->musicCRC, buf, current_frame.expected_framesize); + } + } + + if ((head&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT == MODE_JOINT_STEREO) + { + /* if jstereo, then update mode_extension data */ + di->modeCount[(head&HEADER_FIELD_MODEXT)>>HEADER_FIELD_MODEXT_SHIFT]++; + /* when handling layer I/II we may find a stereo frame at the beginning, jstereo frames + (signaling the file is encoded as jstereo) may be encountered later */ + di->mode=MODE_JOINT_STEREO; + } + else + di->modeCount[4]++; + + *pos += (off_t)current_frame.expected_framesize; + oldpos = *pos; + + if (*pos == id3pos) + /* end of stream reached */ + break; + + *pos=resync_mpeg(input_file,*pos, &buf, si, 0); + + memset((void *)¤t_frame, 0, sizeof(currentFrame)); + current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]=current_frame.min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]=255; + current_frame.maybe_garbage=1; + + } /* END OF `while' CYCLE */ + + if (layerIII(head)) + { + int transferred=0; /* amount of ancillary bytes */ + if (endOfLastPart3 != -1) + { + int ret; + bitoffs_t end_of_last_frame; + seek_p23b((bitoffs_t)oldpos*8-1); /* replaced startOfFrame with oldpos */ + end_of_last_frame = tell_p23b()+1; + if (endOfLastPart3>0) + { + ret=seek_p23b(endOfLastPart3); + skip_p23b(1); + } + else + { + ret=seek_p23b(-1*endOfLastPart3); + } + + if (ret) + { + int byte_off; + /* we'll leave the rounding part to `p23b_cpy' */ + transferred = p23b_cpy(local_buff,end_of_last_frame,LARGEST_BUFFER,&byte_off); + if (op_flag&OPERATIONAL_FLAG_DETECT_MC_IN_ALL_LAYERS) + scan_multichannel(si, local_buff, byte_off, transferred*8, ¤t_frame); + if (byte_off > 0) + { + /* fixing ancillary start, since useful detection data get stored byte-aligned */ + scan_ancillary_III(di,si,--transferred,local_buff+1); + } + else + scan_ancillary_III(di,si,transferred,local_buff); + } + } + /* check whether ancillary data only hides into the very last frame - useful for Blade detection + * It is still valid where there aren't ancillary bytes at all */ + di->ancillaryOnlyIntoLastFrame = (di->ancillaryData == (off_t)transferred); + } + /* `oldpos' now stores the last `to-be-checked' byte offset, is it exactly the same as id3pos? */ + if (oldpos>HEADER_FIELD_CHANNELS_SHIFT) == MODE_JOINT_STEREO) + bound = 4 + ((header&HEADER_FIELD_MODEXT)>>HEADER_FIELD_MODEXT_SHIFT) * 4; + else + bound = 32; + + nch = (((header&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT) == MODE_MONO)? 1 : 2; + + for (sb=0; sballocation[ch][sb] = bitalloc + 1; + else + frame_desc->allocation[ch][sb] = bitalloc; + } + } + } + /* speed-up in case of errors */ + if (ch != nch) + break; + } + if (sb == bound) + { + /* everything went ok up to 'bound' */ + for (; sb<32; sb++) + { + bitalloc = extract_bits(buff, &pointer, 4); + if (bitalloc == 15) + { + errors++; + break; + } + else + { + if (frame_desc != NULL) + { + if (bitalloc) + frame_desc->allocation[0][sb] = frame_desc->allocation[1][sb] = bitalloc + 1; + else + frame_desc->allocation[0][sb] = frame_desc->allocation[1][sb] = bitalloc; + } + } + } + } + + if (frame_desc != NULL) + { + frame_desc->bound = bound; + frame_desc->part1_length = pointer + 8*(sizeof(unsigned int)+ + ((header&HEADER_FIELD_CRC)? 0 : sizeof(unsigned short))); + } + + return errors; +} + +char save_bit_allocation_scfsi_II(unsigned int header, unsigned char *buff, currentFrame *frame_desc) +{ +/* + * this routine is heavily based upon the source code of libmad (layer12.c). + */ + unsigned char index, sblimit, bound, idx, jdx, nch; + unsigned short nbit, samplerate, bitrate_perchannel; + unsigned int pointer; + + if (frame_desc != NULL) + { + /* layer II needs the most complex process in order + to identify how many bits will be checksummed */ + + nch = (((header&HEADER_FIELD_CHANNELS) >> HEADER_FIELD_CHANNELS_SHIFT)==MODE_MONO) ? 1 : 2; + samplerate = samplerate_tab[(header&HEADER_FIELD_SAMPRATE) >> HEADER_FIELD_SAMPRATE_SHIFT] + [(header&HEADER_FIELD_MPEG_ID) >> HEADER_FIELD_MPEG_ID_SHIFT]; + bitrate_perchannel = bitrate_tab[1-((header&HEADER_FIELD_LSF) >> HEADER_FIELD_LSF_SHIFT)] + [3-((header&HEADER_FIELD_LAYER) >> HEADER_FIELD_LAYER_SHIFT)] + [(header&HEADER_FIELD_BITRATE) >> HEADER_FIELD_BITRATE_SHIFT] + / nch; + + if (((header&HEADER_FIELD_LSF)>>HEADER_FIELD_LSF_SHIFT)==0) + { + /* mpeg2 */ + index = 4; + } + else + { + /* mpeg1 */ + if (((header&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT) == BITRATE_INDEX_FREEFORMAT) + { + if (samplerate == 48000) + index = 0; + else + index = 1; + } + else + { + if (bitrate_perchannel <= 48) + { + if (samplerate == 32000) + index = 3; + else + index = 2; + } + else + { + if (bitrate_perchannel <= 80) + index = 0; + else + { + if (samplerate == 48000) + index = 0; + else + index = 1; + } + } + } + } + + sblimit = subband_limit[index]; + if (((header&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT) == MODE_JOINT_STEREO) + bound = 4 + ((header&HEADER_FIELD_MODEXT)>>HEADER_FIELD_MODEXT_SHIFT) * 4; + else + bound = 30; + if (bound > sblimit) + bound = sblimit; + + /* decode bit allocations */ + pointer = 0; + for (idx=0; idxallocation[jdx][idx]=extract_bits(buff, &pointer, nbit); + } + } + for (; idxallocation[0][idx]= + frame_desc->allocation[1][idx]=extract_bits(buff, &pointer, nbit); + } + + /* each scalefactor selection info entry is 2 bit long */ + for (idx=0; idxallocation[jdx][idx]) + frame_desc->scfsi[jdx][idx]=extract_bits(buff, &pointer, 2); + + /* keep useful information */ + frame_desc->index = index; + frame_desc->bound = bound; + frame_desc->part1_length = pointer+8*(sizeof(unsigned int)+ + ((header&HEADER_FIELD_CRC)? 0 : sizeof(unsigned short))); + } + + return 0; +} + +char malformed_part1(unsigned char *buffer, currentFrame *frame_desc) +{ + char result=0; + int header=extract_bits(buffer, NULL, 32); + int offset=sizeof(unsigned int)+((header&HEADER_FIELD_CRC)? 0 : sizeof(unsigned short)); + + if (layerIII(header)) + { + result = read_side_information(header, buffer+offset, frame_desc); + } + else + { + if (layerI(header)) + { + result = check_bit_allocation_I(header, buffer+offset, frame_desc); + } + else + { + /* layer II has no reserved values in bit-allocation info block */ + /* Nevertheless, it is useful gathering some information for later use */ + result = save_bit_allocation_scfsi_II(header, buffer+offset, frame_desc); + } + } + + return result; +} + + +/* This routine prints lots of detailed information about the processed mpeg bitstream */ +void show_stream_info(streamInfo *si, detectionInfo *di, vbrtagdata_t *vbrTag, int verbosity, unsigned char op_flgs) +{ + int idx; + char max_len,blk_max_len,dummy_string[12]; + float duration,sum; + + max_len=sprintf(dummy_string,"%u",si->totFrameNum); + duration=(float)si->totFrameNum*(float)samples_tab[si->lsf][di->lay-1]/(float)di->freq; + + if (verbosity&VERBOSE_FLAG_VBR_TAG) + { + if (di->ofl) + { + printf("Original File Length block found.\n"); + printf(" Encoder delay : %u samples\n", si->ofl_encDelay); + printf(" Length of original audio : %u samples\n\n", si->ofl_orig_samples); + } + } + + if (verbosity&VERBOSE_FLAG_STREAM_DETAILS) + { + printf("Detected MPEG stream version %s layer ",!(di->id)? "2.5" : (di->id==MPEG_CODE_MPEG2)? "2" : "1"); + for (idx=0; idxlay; idx++) printf("I"); + printf(", details follow.\n File size : %lld bytes\n",(long long)si->filesize); + printf(" Audio stream size : %lld bytes",(long long)si->totFrameLen); + /* if the file has a header tag, then show the total size */ + if (vbrTag->infoTag!=TAG_NOTAG) + { + printf (" (including tag: %lld)",(long long)(si->totFrameLen+(off_t)vbrTag->frameSize)); + /* just a simple check */ + if ((si->totFrameLen+(off_t)vbrTag->frameSize) > si->filesize) + printf(" (!!!)"); + } + else + if (si->totFrameLen > si->filesize) + printf(" (!!!)"); + + printf("\n Length : %u:%02u:%02u.%03.0f",((int)duration/3600),(((int)duration%3600)/60),((int)duration%60),(1000*(duration-(int)duration))); + if (duration >= 60.0) + printf(" (%1.3f seconds)",duration); + printf("\n Data rate : "); + printf("%1.1f kbps",(float)si->totFrameLen*(float)di->freq*0.008/((float)si->totFrameNum*(float)samples_tab[si->lsf][di->lay-1])); + if (si->freeformat) printf(" (free format bitstream)"); + printf("\n Number of frames : %u",si->totFrameNum); + //max_len=printf("%d",si.totFrameNum); + if (di->lay==3) + printf("\n Blocks per frame : %d (granules per frame %d, channels per granule %d)",((di->id==MPEG_CODE_MPEG1)?2:1)*((di->mode==MODE_MONO)?1:2),(di->id==MPEG_CODE_MPEG1)?2:1,(di->mode==MODE_MONO)?1:2); + printf("\n Audio samples per frame : %d\n",samples_tab[si->lsf][di->lay-1]); + printf(" Audio frequency : %d Hz\n",di->freq); +// if (si->encDelay!=-1) +// printf(" Encoder delay : %d samples\n",si->encDelay); + //if (di.lameTag) +// if (di->lame_tag && vbrTag->encDelay!=-1) +// { +// si->orig_samples=si->totFrameNum*samples_tab[si->lsf][di->lay-1]-vbrTag->encDelay-vbrTag->encPadding; +// si->orig_samples=vbrTag->reported_frames*samples_tab[si->lsf][di->lay-1]-vbrTag->encDelay-vbrTag->encPadding; +// } +// if (si->orig_samples!=-1) +// printf(" Length of original audio : %d samples\n",si->orig_samples); + printf(" Encoding mode : "); + switch (di->mode) + { + case MODE_MONO: + printf("mono"); + break; + case MODE_DUAL_CHANNEL: + printf("dual channel"); + break; + case MODE_JOINT_STEREO: + printf("joint stereo"); + break; + case MODE_PLAIN_STEREO: + printf("stereo"); + break; + } + if (di->lay==3) + { + printf("\n Min global gain : "); + if (di->mode==MODE_MONO) /* Mono? */ + printf("c=%3d",si->min_global_gain[GLOBAL_GAIN_CHANNEL_MONO]); + else + printf("l=%3d r=%3d",si->min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT],si->min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]); + printf("\n Max global gain : "); + if (di->mode==MODE_MONO) /* Mono? */ + printf("c=%3d",si->max_global_gain[GLOBAL_GAIN_CHANNEL_MONO]); + else + printf("l=%3d r=%3d",si->max_global_gain[GLOBAL_GAIN_CHANNEL_LEFT],si->max_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]); + } + if ( + ((di->lay==2 && (si->mc.mc_stream_verified&1)) || + (di->lay==2 && si->mc_coherent_frames>si->totFrameNum*19/20) || + (di->lay!=2 && si->mc.mc_stream_verified)) + && (si->mc_verified_frames+si->mc_coherent_frames)>(si->totFrameNum*3/10) + ) + { + unsigned char ch=((di->mode==MODE_MONO)?1:2); + printf("\n Multi channel stream : "); + if (si->mc.mc_stream_verified == 1) + printf("yes"); + else + { + /* MSb is set! So, there are some coherent frames, although they're not validated */ + if ((si->mc_verified_frames+si->mc_coherent_frames) < si->totFrameNum) + printf("maybe (%d%% probability)",(si->mc_verified_frames+si->mc_coherent_frames)*100/si->totFrameNum); + else + printf("almost sure (99%% probability)"); + } + printf("\nMPEG-2 Audio Multichannel parameters.\n Audio bitstream extension : %s\n", (si->mc.extension)?"yes":"no"); + printf(" LFE : %s\n", (si->mc.lfe)?"yes":"no"); + printf(" Channels : %d\n", si->mc.mc_channels); + printf(" Multi lingual channels : %d\n", si->mc.multi_lingual); + if (si->mc.multi_lingual > 0) + { + printf(" Multi lingual stream layer : I%c\n", (si->mc.multi_lingual_layer)?' ':'I'); + printf(" Multi lingual stream frequency : %d\n", (si->mc.multi_lingual_fs)?di->freq/2:di->freq); + } + printf(" Channel configuration : "); + if ((si->mc.configuration_value&0xf0) == 0x30) + { + /* second stereo program */ +// printf("%d/0 and %d/0", ((si->mc.configuration_value&1)?ch+1:ch), +// ((si->mc.configuration_value&1)?si->mc.mc_channels-1:si->mc.mc_channels)); + printf("%d/0 and %d/0", ch+(si->mc.configuration_value&1), + si->mc.mc_channels-(si->mc.configuration_value&1)); + } + else + { +// printf("%d/%d", ((si->mc.configuration_value&1)?ch+1:ch), +// ((si->mc.configuration_value&1)?si->mc.mc_channels-1:si->mc.mc_channels)); + printf("%d/%d", ch+(si->mc.configuration_value&1), + si->mc.mc_channels-(si->mc.configuration_value&1)); + } + } + printf("\nFlags\n Error protection : %s", si->crc_present ? "yes":"no"); + if (si->crc_present) + printf(" (check %s)", (si->crc_checked)?"passed":"failed"); + printf("\n Copyrighted : %s\n",si->copyright ? "yes":"no"); + printf(" Original : %s\n",si->original ? "yes":"no"); + printf(" Emphasis : %s\n\n",si->emphasis == 0 ? "none" : si->emphasis == 1 ? "50/15ms" : "CCITT"); + + if (di->mode==MODE_JOINT_STEREO) /* Jstereo? */ + { + sum=(float)(di->modeCount[0]+di->modeCount[1]+di->modeCount[2]+di->modeCount[3]+di->modeCount[4]); + if (di->lay==3) + { + printf("Mode extension: stereo mode frame count\n"); + if (di->modeCount[0]) printf(" Simple stereo : %*d (%4.1f%%)\n",max_len,di->modeCount[0],(float)di->modeCount[0]*100.0/sum); + if (di->modeCount[1]) printf(" Intensity stereo : %*d (%4.1f%%)\n",max_len,di->modeCount[1],(float)di->modeCount[1]*100.0/sum); + if (di->modeCount[2]) printf(" Mid-side stereo : %*d (%4.1f%%)\n",max_len,di->modeCount[2],(float)di->modeCount[2]*100.0/sum); + if (di->modeCount[3]) printf(" Intensity and mid-side stereo : %*d (%4.1f%%)\n",max_len,di->modeCount[3],(float)di->modeCount[3]*100.0/sum); + printf(" -----------------------------\n sum "); + } + else + { + printf("Mode extension: intensity stereo applied in\n"); + if (di->modeCount[0]) printf(" Bands 4 to 31 : %*d (%4.1f%%)\n",max_len,di->modeCount[0],(float)di->modeCount[0]*100.0/sum); + if (di->modeCount[1]) printf(" Bands 8 to 31 : %*d (%4.1f%%)\n",max_len,di->modeCount[1],(float)di->modeCount[1]*100.0/sum); + if (di->modeCount[2]) printf(" Bands 12 to 31 : %*d (%4.1f%%)\n",max_len,di->modeCount[2],(float)di->modeCount[2]*100.0/sum); + if (di->modeCount[3]) printf(" Bands 16 to 31 : %*d (%4.1f%%)\n",max_len,di->modeCount[3],(float)di->modeCount[3]*100.0/sum); + if (di->modeCount[4]) printf("Simple stereo frames : %*d (%4.1f%%)\n",max_len,di->modeCount[4],(float)di->modeCount[4]*100.0/sum); + printf(" ------------------\n sum "); + } + printf(": %d\n\n",(int)sum); + } + + if (di->lay==3) + { + sum=(float)(di->blockCount[BLOCKCOUNT_LONG]+di->blockCount[BLOCKCOUNT_SHORT]+di->blockCount[BLOCKCOUNT_MIXED]+di->blockCount[BLOCKCOUNT_SWITCH]); + /* + * It is not hard to say that blk_max_len and max_len are related, since + * granule number is related to frame number. Granule number may be the same + * value as frame number if single channel and one granule per channel (mpeg2/2.5). + * It may be frame number times two if either two channels and one granule per + * channel or single channel and two granules per channel (mpeg1). + * At last, it may be the frame number times four when two channels and two + * granules per channel are used. + * So blk_max_len may require at most a single digit more than max_len. + */ + blk_max_len=max_len+1; + printf("Block usage\n"); + if (di->blockCount[BLOCKCOUNT_LONG]) printf(" Long block granules : %*d (%4.1f%%)\n",blk_max_len,di->blockCount[BLOCKCOUNT_LONG], (float)di->blockCount[BLOCKCOUNT_LONG]*100.0/sum); + if (di->blockCount[BLOCKCOUNT_MIXED]) printf(" Mixed block granules : %*d (%4.1f%%)\n",blk_max_len,di->blockCount[BLOCKCOUNT_MIXED], (float)di->blockCount[BLOCKCOUNT_MIXED]*100.0/sum); + if (di->blockCount[BLOCKCOUNT_SWITCH]) printf(" Switch block granules : %*d (%4.1f%%)\n",blk_max_len,di->blockCount[BLOCKCOUNT_SWITCH],(float)di->blockCount[BLOCKCOUNT_SWITCH]*100.0/sum); + if (di->blockCount[BLOCKCOUNT_SHORT]) printf(" Short block granules : %*d (%4.1f%%)\n",blk_max_len,di->blockCount[BLOCKCOUNT_SHORT], (float)di->blockCount[BLOCKCOUNT_SHORT]*100.0/sum); + printf(" ---------------------\n sum : %*d\n\n",blk_max_len,(int)sum); + } + } + + if (verbosity&VERBOSE_FLAG_ADVANCED_BITS) + { + printf("Ancillary data\n Total amount : %lld bytes (%3.1f%%)\n Bitrate : %1.1f kbps\n",(long long)di->ancillaryData,(float)di->ancillaryData/(float)si->totFrameLen*100.0,(duration!=0.0)?(float)di->ancillaryData*0.008/duration:0); + + if (di->ancillaryData) + printf(" Min packet : %d bytes\n Max packet : %d bytes\n",si->ancill_min,si->ancill_max); + if (di->lay==3) + { + printf("Max reservoir : %d bytes\n",si->reservoirMax); + printf("Scalefactor scaling used : %s\n",(di->usesScalefacScale)?"yes":"no"); + printf("Scalefactor selection information used : %s\n",(di->usesScfsi>0)?"yes":"no"); + } + } + if (verbosity&VERBOSE_FLAG_ADVANCED_BITS) + printf("Padding used %s: %s\n\n",(di->lay==3)?" ":"",(di->usesPadding)?"yes":"no"); + + if (verbosity&VERBOSE_FLAG_HISTOGRAM) + { + printf("Frame histogram\n"); + for(idx=0; idx<15; idx++) + if (si->bitrateCount[idx]) + { + if (idx==0) + printf("Custom frames : "); + else + printf("%4d kbps : ",bitrate_tab[si->lsf][di->lay-1][idx]); + printf("%*d (%4.1f%%), size distr: [",max_len,si->bitrateCount[idx],(float)si->bitrateCount[idx]*100.0/(float)si->totFrameNum); + + if (si->unpadded[idx].frameCount) + printf("%*d x%4d B",max_len,si->unpadded[idx].frameCount,si->unpadded[idx].frameSize); + + if ((si->unpadded[idx].frameCount)&&(si->padded[idx].frameCount)) + printf(", "); + + if (si->padded[idx].frameCount) + printf("%*d x%4d B",max_len,si->padded[idx].frameCount,si->padded[idx].frameSize); + + printf("]\n"); + } + printf("\n"); + } + + if (verbosity&VERBOSE_FLAG_PROGRESS) + printf(" %d header errors.\n\n",si->nSyncError); + + if (op_flgs&OPERATIONAL_FLAG_VERIFY_MUSIC_CRC) + { + printf("Music CRC verification "); + if (vbrTag->lameMusicCRC != -1) + printf("%s\n\n", (si->musicCRC==vbrTag->lameMusicCRC)?"passed":"failed"); + else + printf("not performed.\n\n"); + } + + if (di->encoder_string[0] != 0 && (verbosity&VERBOSE_FLAG_ENC_STRING)) + printf("Encoder string : %s\n\n",di->encoder_string); +} + +/* + * vbr detection + * returns 0 if cbr encoding + * otherwise (vbr) nonzero + */ +char detect_vbr_stream(streamInfo *si) +{ + int i,j=0; + for (i=1; i<15; i++) + { + if (si->bitrateCount[i]) + j++; + } + return (j>1); +} + +int guesser(detectionInfo *di) +{ + int ret; +/* + * Note: Xing and helix encoders count in their xing tag also the xing tag itself + * as a valid frame, resulting in a +1 difference with actual audio frames amount. + * You have the same behaviour with FhG encoders and their VBRI tag + */ + if (di->encoder_string[0]=='G' || di->encoder_string[0]=='L') + { + if (di->encoder_string[0]=='G') + ret = ENCODER_GUESS_GOGO; + else + ret = ENCODER_GUESS_LAME; + } + else + { + if (di->ancillaryData /* both the enhancements require more info, hidden into ancillary data */ + && + ( + (di->id!=MPEG_CODE_MPEG1 && (di->enhSignature[0]&PRO_SIGN_BYTE1)==PRO_SIGN_BYTE1 && (di->enhSignature[1]&PRO_SIGN_BYTE2)==PRO_SIGN_BYTE2) /* case of mp3PRO */ + || + (di->id==MPEG_CODE_MPEG1 && (di->freq==44100 || di->freq==48000) && (di->enhSignature[0]&SURR_SIGN_BYTE1)==SURR_SIGN_BYTE1 && (di->enhSignature[1]&SURR_SIGN_BYTE2)==SURR_SIGN_BYTE2) /* case of mp3Surround */ + ) + ) + { + if (di->id!=MPEG_CODE_MPEG1) + { + if (di->ofl) + ret = ENCODER_GUESS_MP3PRO_OFL; + else + ret = ENCODER_GUESS_MP3PRO; + } + else + { + if (di->ofl) + ret = ENCODER_GUESS_MP3SURR_OFL; + else + ret = ENCODER_GUESS_MP3SURR; + } + } + else + { + if (di->vbr_tag==TAG_VBRITAG) + { + /* presence of VBRI tag means we're dealing with a vbr stream produced by a fraunhofer encoder */ + if (di->ofl) + /* mp3sEncoder is the encoder for mp3surround but it can encode both mono and stereo streams as well */ + ret = ENCODER_GUESS_MP3S_ENC; + else + { + if (di->usesPadding) + ret = ENCODER_GUESS_FHG_MAYBE_FASTENC; /* both l3enc and mp3enc cannot encode vbr streams */ + else + ret = ENCODER_GUESS_FHG_ACM; + } + } + else + { + /* "...the Xing encoder never uses short blocks." */ + if (di->blockCount[BLOCKCOUNT_SHORT] == 0) + { /* helix encoder (which is based on xing source code) DOES use short blocks! */ + if (di->modeCount[1]) /* intensity stereo encoded frames */ + ret = ENCODER_GUESS_XING_VERY_OLD; + else + { + if (di->usesScfsi>0) + ret = ENCODER_GUESS_XING_NEW; + else + ret = ENCODER_GUESS_XING_OLD; + } + } + else + { + /* + * BladeEnc does NOT use scalefactor scaling. Instead, scalefactor selection information + * is sometimes used. It cannot create VBR files and it rarely adds ancillary data bits. + * When it does, it is only into the very last frame (and puts in only 0xFF bytes). + * Padding is always enabled. Further, it encodes just either Mono or Plain Stereo + * mode (no joint/dual). + * Oh yes, it's very VERY easy to add support for a further encoder when it's open + * source software :-) + * MusicMatch tag presence is checked in order to exclude bladeenc, since MM encoder + * was xing, not blade + */ + if (di->ancillaryOnlyIntoLastFrame && !di->usesScalefacScale && !di->vbr_stream && (di->mode==MODE_PLAIN_STEREO || di->mode==MODE_MONO) && !di->mm_tag) + ret = ENCODER_GUESS_BLADE; + else + { + if (di->usesScfsi>0) + { /* gogo-no-coda (?) seems to be a japanese fork of lame using asm-optimizations in + order to provide max encoding speed; it adds NO lame tag and a custom "GOGO" + string in ancillary bits. Further, it adds a xing tag in vbr encodings only. */ + if (di->usesScalefacScale) + { /* the encoder may be lame as well as gogo as helix -- xing tag may help now */ + if (di->vbr_tag==TAG_XINGTAG || di->vbr_tag==TAG_LAMECBRTAG) + { + if (di->lame_tag) + ret = ENCODER_GUESS_LAME; /* ScaleFactor scaling is used in Lame since version 3.84 beta on */ + else + { + if (di->frameCountDiffers) + ret = ENCODER_GUESS_HELIX; + else + ret = ENCODER_GUESS_GOGO; + } + } + else + ret = ENCODER_GUESS_HELIX; + } + else + ret = ENCODER_GUESS_LAME_OLD; /* Lame (up to) v3.83 beta didn't use it */ + } + else + { + if (di->usesScalefacScale) + { + if (di->ofl) + ret = ENCODER_GUESS_MP3S_ENC; + else + { + if (di->vbr_stream) + ret = ENCODER_GUESS_FHG_FASTENC; + else + { + if (di->usesPadding) + ret = ENCODER_GUESS_FHG_MAYBE_L3ENC; + else + ret = ENCODER_GUESS_FHG_ACM; + } + } + } + else + { + if (di->mm_tag) + ret = ENCODER_GUESS_XING_NEW; + else + ret = ENCODER_GUESS_UNKNOWN; + } + } + } + } + } + } + } + return ret; +} + + + +void print_version(char verbose) +{ + printf ("%s-%u.%u",VER_PKG_NAME,VER_MAJ,VER_MIN); + if (VER_PATCH_LEV!=0) printf(".%u",VER_PATCH_LEV); +#ifdef __MINGW32__ + printf("-%d.%d.%d", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); +#endif + if (RELEASE_STABLE) printf(" stable"); + if (RELEASE_ALPHA) printf(" alpha"); + if (RELEASE_BETA) printf(" beta"); + if (RELEASE_PREVIEW) printf(" preview"); + if (RELEASE_RC) printf(" RC"); +#ifdef REL_SUBNUM + if (REL_SUBNUM!=1) printf (" %u", REL_SUBNUM); +#endif + if (verbose) + { +#ifdef CODENAME + printf(", \"%s\".",CODENAME); +#endif + + printf("\nLarge file support: %s\n",(sizeof(off_t)==SIZEOF_OFF64_T)?"yes":"no"); + printf("Executable built on %s.", __DATE__); + } + printf("\n"); +} + +void usage() +{ + print_version(0); + printf("Show detailed information about mpeg audio files and guess the encoder used.\n" + "Usage:\t%s [ [OPTIONS] filename ] | [ -h ] | [ -V ]\n" + "Options:\n" + "-- controlling output\n" + " mp3guessenc can be very verbose when reporting scan results. Use the following\n" + " options to select what you want/don't want to see. The default is to show\n" + " everything, the guessed encoder name cannot be silenced.\n" + " -a\tshow advanced bit usage (ancillary, bit reservoir, scalefactor, padding)\n" + " -e\tshow everything (default)\n" + " -f\tshow frame histogram\n" + " -g\tshow the message `Maybe this file is encoded by...'\n" + " -i\tshow metadata info tags\n" + " -m\tshow mpeg stream details\n" + " -n\tshow nothing at all -- just the encoder name will be reported\n" + " -p\tshow progress messages\n" + " -s\twhen found, show encoder string\n" + " -v\tshow VBR info tag\n" + "-- operational\n" + " -c\tforce multi channel detection in all layers (default layerII only)\n" + " -h\tprint this help screen\n" + " -r\tverify music crc when lame tag is found (expensive!)\n" +#ifdef ENABLE_SKIP_OPTION + " -S n\tseek to a specified offset n before starting analysis\n" +#endif + " -V\tprint version information\n" + "\n",VER_PKG_NAME); +} + + +/* main */ +int mp3guessenc_timing_shift_case(const char *file) +//int mp3guessenc_main(int argc,char **argv) +{ + char verify_failed=0, ape_header; + id3tag id3; + unsigned char id3V2Maj, id3V2min; + unsigned char *buf; + int /*option,*/ id3v1size, id3v2size, apetagsize, apetagvers, apetagitems, + verbose_flags, lyrics3v1size, lyrics3v2size, wavechunk, datachunk; + unsigned char operational_flags=0; +#ifdef ENABLE_SKIP_OPTION + int force_skip=-1; +#endif + unsigned int head; + off_t id3pos=0, pos; + FILE *input_file; + detectionInfo di; + streamInfo si; + vbrtagdata_t TagData; + int return_value = EXIT_CODE_CASE_D; +#ifdef CODENAME +// char source[DATA_LEN]; +// int idx; +#endif +#ifdef _WIN32 + struct _stati64 fileStat; + #define stat _stati64 +#else + struct stat fileStat; +#endif /* _WIN32 */ + head_metadata_tag_t t_found=HEAD_NO_TAG; + off_t first_byte=-1, riff_at=-1; + int current_storage=0, start_search; + + /* reset some values */ + memset((void *)&di, 0, sizeof(detectionInfo)); + memset((void *)&si, 0, sizeof(streamInfo)); + + di.enhSignature[0]=di.enhSignature[1]=255; + si.ancill_min=LARGEST_BUFFER; +// si.encDelay=si.orig_samples=-1; + si.min_global_gain[GLOBAL_GAIN_CHANNEL_LEFT]=si.min_global_gain[GLOBAL_GAIN_CHANNEL_RIGHT]=255; + //verbose_flags = VERBOSE_FLAG_SHOW_EVERYTHING | VERBOSE_FLAG_UNTOUCHED; + verbose_flags = VERBOSE_FLAG_VBR_TAG; + + /* is `input_file' a regular file? */ +// if (stat(argv[optind],&fileStat)) + if (stat(file,&fileStat)) + { + printf("Error getting file attributes for `%s' (does it exist?), exiting.\n",file); + return EXIT_CODE_STAT_ERROR; + } + if ( + !S_ISREG(fileStat.st_mode) +#ifndef _WIN32 + && !S_ISLNK(fileStat.st_mode) +#endif + ) + { + printf("File `%s' is not a regular file, unable to proceed, exiting.\n",file); + return EXIT_CODE_INVALID_FILE; + } + if (fileStat.st_size == 0) + { + printf("File `%s' is zero bytes long, nothing to scan, exiting.\n",file); + return EXIT_CODE_INVALID_FILE; + } + + /* + * Will `bitoffs_t' data type be large enough for + * storage of bit pointers? + * I ask for `bitoffs_t' to be 64 bit and `off_t' may either + * be 32-bit or 64-bit long. + * sizeof(bitoffs_t)>sizeof(off_t) is OK for me, and I never expect + * it to be smaller. What if they are the same? + */ + if (sizeof(bitoffs_t) == sizeof(off_t)) + { + bitoffs_t dummy=(bitoffs_t)fileStat.st_size; + if ( + dummy > dummy*2 + || + dummy > dummy*4 + || + dummy > dummy*8 + ) + { + /* `bitoffs_t' data type has insufficient bits to handle this file */ + printf("File `%s' cannot be analyzed because its size is too large.\n",file); + return EXIT_CODE_CANNOT_HANDLE; + } + } + + + si.filesize = fileStat.st_size; + +// input_file = fopen(argv[optind],"rb"); + input_file = fopen(file,"rb"); +/* + if (input_file == NULL) + { + printf("Fatal: error opening `%s'.\nDouble-check the provided file name or ensure you have the proper access rights.\n", argv[optind]); + return EXIT_CODE_ACCESS_ERROR; + } + + if (verbose_flags&VERBOSE_FLAG_PROGRESS) + printf("Reading `%s'...\n",argv[optind]); +*/ + /* check if this file has metadata tags at its tail */ + id3pos = si.filesize; + + do + { + id3v1size = checkid3v1(input_file,id3pos,&id3); + if (id3v1size) + { + id3pos -= (off_t)id3v1size; + if (verbose_flags&VERBOSE_FLAG_METADATA) + { + printf("ID3tag v1.%c found (offset 0x%08X).\n",(!id3.comment[28]&&id3.comment[29])?'1':'0',(unsigned)id3pos); + show_id3v1(&id3); + } + } + + lyrics3v1size=checklyrics3v1(input_file,id3pos); + if (lyrics3v1size) + { + id3pos -= lyrics3v1size; + if (verbose_flags&VERBOSE_FLAG_METADATA) + printf("Lyrics3v1 tag found, %d bytes long (offset 0x%08X).\n\n",lyrics3v1size,(unsigned)id3pos); + } + + lyrics3v2size=checklyrics3v2(input_file,id3pos); + if (lyrics3v2size) + { + id3pos -= lyrics3v2size; + if (verbose_flags&VERBOSE_FLAG_METADATA) + printf("Lyrics3v2.00 tag found, %d bytes long (offset 0x%08X).\n\n",lyrics3v2size,(unsigned)id3pos); + } + + apetagsize=checkapetagx_tail(input_file,id3pos,&apetagvers,&apetagitems,&ape_header); + if (apetagsize) + { + id3pos -= apetagsize; + if (verbose_flags&VERBOSE_FLAG_METADATA) + printf("APE tag v%c found at the end of the stream (offset 0x%08X).\n%d items, %d bytes long, %s.\n\n", + (apetagvers==2000)?'2':'1',(unsigned)id3pos,apetagitems,apetagsize, + (ape_header)?"optional header present":"no header"); + } + + memset((void *)&musicmatch_tag, 0, sizeof(mmtag_t)); + checkmmtag(input_file, id3pos, &musicmatch_tag); + if (musicmatch_tag.tag_size) + { + di.mm_tag = 1; + id3pos -= musicmatch_tag.tag_size; + if (verbose_flags&VERBOSE_FLAG_METADATA) + { + printf("MusicMatch tag found: %u bytes long, metadata section is %u bytes long (offset 0x%08X).\nSoftware ver %s, tag ver %s, Xing encoder ver %s, %s.\n", + musicmatch_tag.tag_size,musicmatch_tag.metadata_size,(unsigned)id3pos,musicmatch_tag.mm_ver,musicmatch_tag.tag_ver,musicmatch_tag.enc_ver, + (musicmatch_tag.header_present)?"header present":"no header"); + if (musicmatch_tag.image_size) + { + printf("Image detected at offset %llu, size %u, extension %s\n",(long long)musicmatch_tag.image_offset,musicmatch_tag.image_size,musicmatch_tag.image_ext); + } + printf("\n"); + } + } + + id3v2size=checkid3v2_footer(input_file,id3pos,&id3V2Maj,&id3V2min); + if (id3v2size>0) + { + id3pos -= (off_t)id3v2size; + if (verbose_flags&VERBOSE_FLAG_METADATA) + { + /* an ID3v2 tag at the end should be revision 4 or newer */ + printf("ID3tag v2.%01u.%01u found at file tail (offset 0x%08X)\nID3tag v2 is %d bytes long.\n\n",id3V2Maj,id3V2min,(unsigned)id3pos,id3v2size); + } + } + else + { + if (id3v2size==-1) + { + if (verbose_flags&VERBOSE_FLAG_METADATA) + printf("Errors detected in id3V2 tag footer.\n\n"); + /* I will check the size of the tag in order to remain into this loop */ + /* so in this case it has to be fixed */ + id3v2size=0; + } + } + } + while (id3v1size!=0 || lyrics3v1size!=0 || lyrics3v2size!=0 || apetagsize!=0 || musicmatch_tag.tag_size!=0 || id3v2size!=0); + + /* now check if this file has metadata tags at its head. */ + pos = 0; +#ifdef ENABLE_SKIP_OPTION + if (force_skip != -1 && force_skip > 0) + pos = (off_t)force_skip; +#endif + + + do + { + int tag_pos, next_tag; + + if (t_found != HEAD_WAVERIFF_UNCOMPLETE_TAG) + t_found = HEAD_NO_TAG; + tag_pos = LARGEST_BUFFER; + + if (pos > (first_byte+(off_t)current_storage-HEAD_METADATA_MIN_IDENTIFICATION_LENGTH)) + { + /* need to fill the buffer */ + fseeko(input_file, pos, SEEK_SET); + first_byte = pos; + current_storage = fread(mp3g_storage, 1, LARGEST_BUFFER, input_file); + } + + start_search = (int)(pos - first_byte); + + if (t_found == HEAD_WAVERIFF_UNCOMPLETE_TAG) + { + /* here the search for subsequent chunks is performed */ + /* last one will be the `data' chunk */ + wavechunk=checkwaveriff_datachunk(&mp3g_storage[start_search], + &next_tag,&datachunk); + if (wavechunk > 0) + { + if (datachunk > 0) + { + t_found = HEAD_WAVERIFF_DATACHUNK_TAG; + tag_pos = next_tag; + } + else + { + /* an other chunk found */ + tag_pos = 0; + } + } + else + { + /* no further chuck found, quite odd */ + /* maybe the file is corrupted, abort */ + t_found = HEAD_NO_TAG; + } + } + else + { + /* here we seek for the very first RIFF-WAVE pattern */ + wavechunk=checkwaveriff(&mp3g_storage[start_search], + current_storage-start_search,&next_tag); + if (wavechunk > 0) + { + if (next_tag < tag_pos) + { + t_found = HEAD_WAVERIFF_UNCOMPLETE_TAG; + tag_pos = next_tag; + riff_at = pos + next_tag; + } + } + } + + id3v2size = checkid3v2(&mp3g_storage[start_search],current_storage-start_search, + &next_tag,&id3V2Maj,&id3V2min); + if (id3v2size > 0) + { + if (next_tag < tag_pos) + { + t_found = HEAD_ID3V2_TAG; + tag_pos = next_tag; + } + } + + apetagsize = checkapetagx_head(&mp3g_storage[start_search],current_storage-start_search, + &next_tag,&apetagvers,&apetagitems,&ape_header); + if (apetagsize > 0) + { + if (next_tag < tag_pos) + { + t_found = HEAD_APE_TAG; + tag_pos = next_tag; + } + } + + /* detection complete, now we will print tag information, if any */ + + if (t_found != HEAD_NO_TAG) + { + if (tag_pos) + printf("Unexpected data at %lld (length in bytes: %d)\n\n", (long long)pos, tag_pos); + + switch (t_found) + { + case HEAD_ID3V2_TAG: + if (verbose_flags&VERBOSE_FLAG_METADATA) + { + printf("ID3tag v2.%01u.%01u found (offset 0x%08X).\n",id3V2Maj,id3V2min,(unsigned)pos+tag_pos); + printf("ID3tag v2 is %d bytes long, skipping...\n\n",id3v2size); + } + pos += (off_t)(tag_pos+id3v2size); + break; + + case HEAD_APE_TAG: + if (verbose_flags&VERBOSE_FLAG_METADATA) + printf("APE tag v%c found at the head of the stream (offset 0x%08X).\n%d items, %d bytes long, %s.\n\n", + (apetagvers==2000)?'2':'1',(unsigned)pos+tag_pos,apetagitems,apetagsize, + (ape_header)?"optional header present":"no header"); + pos += (off_t)(tag_pos+apetagsize); + break; + + case HEAD_WAVERIFF_DATACHUNK_TAG: + if (verbose_flags&VERBOSE_FLAG_METADATA) + printf("Wave Riff header found (offset 0x%08X), size %d bytes.\nData chunk is %d bytes long.\n\n", + (unsigned)riff_at,(int)(pos+tag_pos+wavechunk-riff_at),datachunk); + pos += (off_t)(tag_pos+wavechunk); /* here `tag_pos' should be zero */ + /* check whether to expect pad byte */ + if (datachunk&1) + { + /* size is odd, so we expect to find a zero padding byte at the end */ + if (pos+datachunk+1==id3pos) + { + /* id3pos is at the end of the chunk, not of the REAL data */ + id3pos--; + } + } + break; + + case HEAD_WAVERIFF_UNCOMPLETE_TAG: + pos += (off_t)(tag_pos+wavechunk); + break; + + default: + break; + } + } + else + { + /* force update of the data buffer */ + current_storage = 0; + } + } + while (t_found != HEAD_NO_TAG || start_search != 0); + + /* due to this `fseek', I will assure the file pointer points */ + /* at the beginning of the non-id3v2 stuff */ + + fseeko(input_file,pos,SEEK_SET); + /* trick: metadata total size into id3v2size */ + id3v2size = pos; + + pos--; + + /* find first frame */ + /* + * lame encoder prior to version 3.94 beta (dec 15, 2003) used to set a wrong + * bitrate index (1111b) in the info header for free format streams + * I have to allow a more relaxed scan in order not to skip a valid info header + */ + + do + { + currentFrame local; + unsigned short target; + off_t pos_new; + unsigned int head_new, header_mask; + + memset((void *)&local, 0, sizeof(currentFrame)); + pos = resync_mpeg(input_file, pos+1, &buf, NULL, 1); + if (pos != -1) + { + if ((pos-id3v2size) > SCAN_SIZE) + { + /* too far - not allowed, set result of a failed search */ + pos = -1; + break; + } + + head = extract_bits(buf, NULL, 32); + if (malformed_part1(buf, &local)) + { + verify_failed = 1; + continue; + } + + if (!(head&HEADER_FIELD_CRC)) + { + verify_failed = 1; + /* verify the crc */ + target = extract_bits(buf+sizeof(unsigned int), NULL, 16); + if (!verify_crc(head, buf+sizeof(unsigned int)+sizeof(unsigned short), local.part1_length, target)) + { + /* not a valid frame - skip! */ + continue; + } + } + + + /* first of all, check for a vbr tag + this will work on any layerIII stream, even when we're dealing with + a freeformat stream created by an ancient lame enc */ + + /* search for the xing/lame/vbri tag */ + if (checkvbrinfotag(&TagData,buf,pos,di.encoder_string)) + { + /* vbr tag found - this means the mpeg frame is valid + eventually, checkvbrinfotag fixed the bitrate index into the mpeg header */ + head = TagData.header; + } + else + { + return_value = EXIT_CODE_CASE_A; + } + + if (((head&HEADER_FIELD_LAYER)>>HEADER_FIELD_LAYER_SHIFT)==LAYER_CODE_L_III && + ((head&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT)==BITRATE_INDEX_RESERVED) + { + /* now the header should have been fixed - sure this is junk, not audio data */ + verify_failed = 1; + continue; + } + + /* now, seek for the next valid frame and calculate the frame size */ + pos_new = pos; + if (layerIII(head)) + { + pos_new += local.part1_length/8; + /* the 'local' struct was filled in by the 'malformed_part1' function */ + if (local.part2_3_length/8 - local.main_data_begin > 0) + pos_new += local.part2_3_length/8 - local.main_data_begin; + pos_new--; /* this is needed to compensate for the plus one below... */ + } + else + { + /* layerI & layerII */ + pos_new += (local.part1_length+local.part2_3_length)/8 - 1; + } + + verify_failed = 0; + do + { + currentFrame candidate; + memset((void *)&candidate, 0, sizeof(currentFrame)); + + /* locate a new valid audio mpeg header */ + pos_new = resync_mpeg(input_file, pos_new+1, &buf, NULL, 0); + if (pos_new != -1) + { + if ((pos_new-pos) > SCAN_SIZE) + { + /* too far - not allowed */ + verify_failed = 1; + break; + } + + head_new = extract_bits(buf, NULL, 32); + if (malformed_part1(buf, &candidate)) + { + continue; + } + else + { + /* valid part1 ! */ + if (!(head_new&HEADER_FIELD_CRC)) + { + /* verify the crc */ + target = extract_bits(buf+sizeof(unsigned int), NULL, 16); + if (!verify_crc(head_new, buf+sizeof(unsigned int)+sizeof(unsigned short), candidate.part1_length, target)) + { + /* not a valid frame - skip! */ + continue; + } + } + /* no crc or valid crc */ + + /* select the right bitmask */ + if (((head&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT) == MODE_MONO) + { + header_mask = HEADER_CONSTANT_FIELDS_STRICT; + } + else + { + header_mask = HEADER_CONSTANT_FIELDS; + } + if (TagData.infoTag == TAG_VBRITAG) + { + /* the fake frame containing vbri tag always has the crc flag disabled, + regardless of the protection setting of the audio stream */ + header_mask &= ~HEADER_FIELD_CRC; + } + if (TagData.infoTag == TAG_LAMECBRTAG || TagData.infoTag == TAG_XINGTAG) + { + /* some versions of lame used to set the 'original' flag to zero into the + very first frame (the lame tag) regardless of the general flag set + into the real stream */ + header_mask &= ~HEADER_FIELD_ORIGINAL; + } + + if ( (head_new&header_mask) == (head&header_mask) ) + { + if (freeformat(head)) + { + /* it's a valid frame and it's also coherent with our previous one */ + if (get_bitrate(head, (int)(pos_new-pos)) <= BITRATE_MAX_ALLOWED_FREEFORMAT) + { + int slot=(((head&HEADER_FIELD_LAYER)>>HEADER_FIELD_LAYER_SHIFT)==LAYER_CODE_L___I)?4:1; + /* here the exact size of the first freeformat frame is evaluated */ + if (head&HEADER_FIELD_PADDING) + { + si.padded[0].frameSize=(int)(pos_new-pos); + si.unpadded[0].frameSize=(int)(pos_new-pos)-slot; + } + else + { + si.unpadded[0].frameSize=(int)(pos_new-pos); + si.padded[0].frameSize=(int)(pos_new-pos)+slot; + } + TagData.frameSize = (int)(pos_new-pos); + } + else + { + /* frame size says we're dealing with bitrate > 640.0 kbps */ + /* this clearly is wrong */ + verify_failed = 1; + break; + } + } + else + { + /* frame with 'normal' bitrate */ + if ((pos_new-pos) != get_framesize(head) + && + (head&HEADER_FIELD_EMPHASIS) != 0) + { + /* frames have junk in between and some unusual emphasis set - not good */ + verify_failed = 1; + break; + } + TagData.frameSize = get_framesize(head); + } + + verify_failed = 0; + if (TagData.infoTag != TAG_NOTAG) + { + /* meaningful tag found - it is not a frame to be analyzed */ + if (id3v2size != pos) + { + printf("Unexpected %d bytes before VBR tag.\n", (int)(pos-id3v2size)); + } + + pos = pos_new; + + if (verbose_flags&VERBOSE_FLAG_VBR_TAG) + { + //show_info_tag(&TagData); + return_value = check_timing_shift_case(&TagData); + + pos_new = -1; + pos = -1; + } + } + break; + } + else + { + /* uncoherent frame headers */ + continue; + } + } + } + else + { + if (freeformat(head)) + { + /* no more frames found - this can be troublesome for freeformat streams, + which require a frame size to be set anyway */ + int slot=(((head&HEADER_FIELD_LAYER)>>HEADER_FIELD_LAYER_SHIFT)==LAYER_CODE_L___I)?4:1; + off_t dummy=id3pos; + + while (get_bitrate(head, (int)(dummy-pos)) > BITRATE_MAX_ALLOWED_FREEFORMAT) + dummy--; + if (head&HEADER_FIELD_PADDING) + { + si.padded[0].frameSize=(int)(dummy-pos); + si.unpadded[0].frameSize=(int)(dummy-pos)-slot; + } + else + { + si.unpadded[0].frameSize=(int)(dummy-pos); + si.padded[0].frameSize=(int)(dummy-pos)+slot; + } + TagData.frameSize = (int)(dummy-pos); + } + } + } + while (pos_new!=-1); + } + } + while (pos!=-1 + && + verify_failed); + + if (pos == -1) + { + //printf("Cannot find valid mpeg header, scan failed.\n\n"); + fclose(input_file); + //return EXIT_CODE_NO_MPEG_AUDIO; + return return_value; + } + + /* does the mpeg stream start right after the heading junk ? */ + if (pos!=id3v2size && TagData.infoTag == TAG_NOTAG) + if (verbose_flags&VERBOSE_FLAG_PROGRESS) + printf("Unexpected data at %d (length in bytes: %d).\n",id3v2size,(int)(pos-(off_t)id3v2size)); + + + /* undo latest modifications to 'buf' */ + resync_mpeg(input_file, pos, &buf, NULL, 1); + + if (TagData.infoTag != TAG_NOTAG) + { + di.vbr_tag=TagData.infoTag; + di.lame_tag=(TagData.lametag[0]!=0); + + if (pos != (off_t)(TagData.tagStartsAt+TagData.frameSize)) + { + /* some junk between the tag and the very first audio frame */ + if (verbose_flags&VERBOSE_FLAG_VBR_TAG) + printf("Unexpected %d bytes between tag and the audio stream.\n",(int)(pos-(off_t)(TagData.tagStartsAt+TagData.frameSize))); + } + } + + if (verbose_flags&VERBOSE_FLAG_PROGRESS) + printf("First frame found at %lld (0x%08X).\n\n",(long long)pos,(unsigned)pos); + + scan(input_file,&pos,&si,&di,id3pos,verbose_flags, + /* performarce: do not calculate music crc when there is no valid crc sum */ + ((TagData.lameMusicCRC!=-1)?operational_flags:(operational_flags&~OPERATIONAL_FLAG_VERIFY_MUSIC_CRC))); + + fclose(input_file); +/* + if (si.totFrameNum) + { + show_stream_info(&si,&di,&TagData,verbose_flags,operational_flags); + + if (di.vbr_tag!=TAG_NOTAG) + di.frameCountDiffers=(si.totFrameNum!=TagData.reported_frames); + + di.vbr_stream=detect_vbr_stream(&si); + + if (di.lay==3) + { + int result; + if (verbose_flags&VERBOSE_FLAG_GUESSING) + printf("Maybe this file is encoded by "); + result = guesser(&di); + printf("%s\n\n", encoder_table[result]); + return result; + } + } + else + if (verbose_flags&VERBOSEFLAG_PROGRESS) + printf("No valid audio data.\n\n"); +*/ + return return_value; +} +/* +int mp3guessenc_timing_shift_case(const char *file) +{ + char *argv[] = {"", "-v", (char*)file}; + return mp3guessenc_main(3, argv); + } + */ \ No newline at end of file diff --git a/lib/mp3guessenc-0.27.4/mp3guessenc.h b/lib/mp3guessenc-0.27.4/mp3guessenc.h new file mode 100644 index 00000000000..5e7d3f002ef --- /dev/null +++ b/lib/mp3guessenc-0.27.4/mp3guessenc.h @@ -0,0 +1,261 @@ +/* + * mp3guessenc header + * Copyright (C) 2002-2010 Naoki Shibata + * Copyright (C) 2011-2018 Elio Blanca + * + * Xing VBR tagging for LAME. + * Copyright (c) 1999 A.L. Faber + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* Modifed by Evan Dekker 2019-09-26 */ + +#ifndef MP3GUESSENC_H +#define MP3GUESSENC_H + +#include "tags.h" + +#define EXIT_CODE_NO_ERROR 0 +#define EXIT_CODE_UNKNOWN_OPTION -1 +#define EXIT_CODE_STAT_ERROR -2 +#define EXIT_CODE_INVALID_FILE -3 +#define EXIT_CODE_ACCESS_ERROR -4 +#define EXIT_CODE_NO_MPEG_AUDIO -5 +#define EXIT_CODE_CANNOT_HANDLE -6 + +#define EXIT_CODE_CASE_A 1 +#define EXIT_CODE_CASE_B 2 +#define EXIT_CODE_CASE_C 3 +#define EXIT_CODE_CASE_D 4 + +#define HEADER_FIELD_SYNC 0xFFE00000 +#define HEADER_FIELD_SYNC_SHIFT 21 +#define HEADER_FIELD_MPEG_ID 0x180000 +#define HEADER_FIELD_MPEG_ID_SHIFT 19 +#define HEADER_FIELD_LSF 0x80000 /* this is not a *real* field, it is just a commodity for accessing lsf bit - 1:mpeg1, 0:mpeg2/2.5*/ +#define HEADER_FIELD_LSF_SHIFT 19 +#define HEADER_FIELD_LAYER 0x60000 +#define HEADER_FIELD_LAYER_SHIFT 17 +#define HEADER_FIELD_CRC 0x10000 +#define HEADER_FIELD_CRC_SHIFT 16 +#define HEADER_FIELD_BITRATE 0xF000 +#define HEADER_FIELD_BITRATE_SHIFT 12 +#define HEADER_FIELD_SAMPRATE 0xC00 +#define HEADER_FIELD_SAMPRATE_SHIFT 10 +#define HEADER_FIELD_PADDING 0x200 +#define HEADER_FIELD_PADDING_SHIFT 9 +#define HEADER_FIELD_PRIVATE 0x100 +#define HEADER_FIELD_PRIVATE_SHIFT 8 +#define HEADER_FIELD_CHANNELS 0xC0 +#define HEADER_FIELD_CHANNELS_SHIFT 6 +#define HEADER_FIELD_MODEXT 0x30 +#define HEADER_FIELD_MODEXT_SHIFT 4 +#define HEADER_FIELD_COPYRIGHT 0x8 +#define HEADER_FIELD_COPYRIGHT_SHIFT 3 +#define HEADER_FIELD_ORIGINAL 0x4 +#define HEADER_FIELD_ORIGINAL_SHIFT 2 +#define HEADER_FIELD_EMPHASIS 0x3 +#define HEADER_FIELD_EMPHASIS_SHIFT 0 + +#define HEADER_CONSTANT_FIELDS_STRICT ( \ + HEADER_FIELD_SYNC | \ + HEADER_FIELD_MPEG_ID | \ + HEADER_FIELD_LAYER | \ + HEADER_FIELD_CRC | \ + HEADER_FIELD_CHANNELS | \ + HEADER_FIELD_SAMPRATE | \ + HEADER_FIELD_COPYRIGHT | \ + HEADER_FIELD_ORIGINAL | \ + HEADER_FIELD_EMPHASIS) + +#define HEADER_CONSTANT_FIELDS ( \ + HEADER_FIELD_SYNC | \ + HEADER_FIELD_MPEG_ID | \ + HEADER_FIELD_LAYER | \ + HEADER_FIELD_CRC | \ + HEADER_FIELD_SAMPRATE | \ + HEADER_FIELD_COPYRIGHT | \ + HEADER_FIELD_ORIGINAL | \ + HEADER_FIELD_EMPHASIS) + +#define HEADER_ANY_BUT_BITRATE_AND_PADDING_FIELDS ( \ + HEADER_FIELD_SYNC | \ + HEADER_FIELD_MPEG_ID | \ + HEADER_FIELD_LAYER | \ + HEADER_FIELD_CRC | \ + HEADER_FIELD_SAMPRATE | \ + HEADER_FIELD_PRIVATE | \ + HEADER_FIELD_CHANNELS | \ + HEADER_FIELD_MODEXT | \ + HEADER_FIELD_COPYRIGHT | \ + HEADER_FIELD_ORIGINAL | \ + HEADER_FIELD_EMPHASIS) + + +#define VERBOSE_FLAG_UNTOUCHED 0x00010000 +#define VERBOSE_FLAG_GUESSING 0x00000001 +#define VERBOSE_FLAG_ENC_STRING 0x00000002 +#define VERBOSE_FLAG_HISTOGRAM 0x00000004 +#define VERBOSE_FLAG_ADVANCED_BITS 0x00000008 +#define VERBOSE_FLAG_STREAM_DETAILS 0x00000010 +#define VERBOSE_FLAG_VBR_TAG 0x00000020 +#define VERBOSE_FLAG_METADATA 0x00000040 +#define VERBOSE_FLAG_PROGRESS 0x00000080 + +#define VERBOSE_FLAG_SHOW_EVERYTHING (VERBOSE_FLAG_GUESSING | \ + VERBOSE_FLAG_ENC_STRING | \ + VERBOSE_FLAG_HISTOGRAM | \ + VERBOSE_FLAG_ADVANCED_BITS | \ + VERBOSE_FLAG_STREAM_DETAILS | \ + VERBOSE_FLAG_VBR_TAG | \ + VERBOSE_FLAG_METADATA | \ + VERBOSE_FLAG_PROGRESS) + +#define OPERATIONAL_FLAG_DETECT_MC_IN_ALL_LAYERS 0x80 +#define OPERATIONAL_FLAG_VERIFY_MUSIC_CRC 0x40 + +#define SCAN_SIZE 65536 /* default scan length is 64 kB when searching for a valid frame */ + +#define LAYER_CODE_RESERVED 0 +#define LAYER_CODE_L_III 1 +#define LAYER_CODE_L__II 2 +#define LAYER_CODE_L___I 3 + +#define BITRATE_INDEX_FREEFORMAT 0 +#define BITRATE_INDEX_RESERVED 15 + +#define BITRATE_MAX_ALLOWED_FREEFORMAT 640.0 + +#define LARGEST_BUFFER (10*1024) /* ensure this is larger than LARGEST_FRAME */ + + +typedef struct detectionInfo { + int blockCount[4]; + int modeCount[5]; + int freq; + off_t ancillaryData; + char usesScfsi; /* this must be set to 0 */ + char usesScalefacScale; /* this must be set to 0 */ + char usesPadding; /* this must be set to 0 */ + char vbr_stream; + char vbr_tag; + char lame_tag; + char mm_tag; + char frameCountDiffers; + char ancillaryOnlyIntoLastFrame; + unsigned char lay; /* layer */ + unsigned char id; /* mpeg id (1/2/2.5) */ + unsigned char mode; + char encoder_string[LAME_STRING_LENGTH]; +/* + * mp3PRO detection + * ---------------- + * The mp3PRO detection method searchs for a "signature" the encoder puts into its audio data. + * The encoder stores higher frequency information "hidden" into ancillary data of layerIII + * streams while creating mpeg2 streams only. These infos are splitted into small packets (min + * 6 bytes) and, given the first two bytes (big endian), they always show bits 15, 14 and 3 set. + * Having said that, identification of the encoder is straightforward, since Fraunhofer IIS is + * the patent owner and the only one providing encoders for mp3PRO. + */ + +/* + * The detection for mp3Surround (5.1 stream into a backward compatible mpeg1 layerIII stream) + * works the same way, just the signature is obviously different. + */ + unsigned char enhSignature[2]; /* these bytes of enhanced mp3 features must be initialized to 0xFF */ + char ofl; +} detectionInfo; + +typedef struct frameCounterCell { + int frameSize; + int frameCount; +} frameCounterCell; + +typedef struct mpegMultiChannel { + unsigned char mc_stream_verified; + unsigned char extension; + unsigned char lfe; + unsigned char mc_channels; + unsigned char multi_lingual; + unsigned char multi_lingual_fs; + unsigned char multi_lingual_layer; + unsigned char configuration_value; +} mpegMultiChannel; + +typedef struct streamInfo { + off_t filesize; + off_t totFrameLen; /* this must be set to 0 */ + unsigned int totFrameNum; /* this must be set to 0 */ + unsigned char lsf; + char crc_present; + char crc_checked; + char copyright; + char original; + char emphasis; + /* the following two fields are written by recent fhg encoders and then hidden into + ancillary bits - they differ from similar information taken from lame vbr tag */ + unsigned short ofl_encDelay; + unsigned int ofl_orig_samples; + int reservoirMax; /* this must be set to 0 */ + int bitrateCount[15]; + frameCounterCell unpadded[15]; + frameCounterCell padded[15]; + int nSyncError; + int ancill_min; + int ancill_max; + unsigned char min_global_gain[2]; + unsigned char max_global_gain[2]; + char freeformat; /* this must be set to 0 */ + mpegMultiChannel mc; + unsigned int mc_verified_frames; + unsigned int mc_coherent_frames; + unsigned short musicCRC; +} streamInfo; + +typedef struct currentFrame { + char maybe_garbage; + char uncertain; + char bitrate_index; + char usesScalefacScale; + unsigned char index; /* sub band quantization table index, used in layerII */ + char bound; /* sub band limit, used in layerI & II */ + int expected_framesize; /* bytes */ + int main_data_begin; /* backpointer - bytes */ + char usesScfsi[2]; + unsigned short crc; + unsigned char min_global_gain[2]; + unsigned char max_global_gain[2]; + int blockCount[4]; + int part1_length; /* bits - length up to the last crc-covered bit */ + int part2_3_length; /* bits - remaining audio bits */ + unsigned char allocation[5][32]; /* bit allocation data used in both layerI and II */ + unsigned char scfsi[2][30]; /* scale factor selection info - used in layerII */ +} currentFrame; + + +unsigned short crc_reflected_update(unsigned short, unsigned char *, unsigned int); +unsigned char reflect_byte(unsigned char); + +#ifdef __cplusplus +extern "C" { +#endif +int mp3guessenc_timing_shift_case(const char *); +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/lib/mp3guessenc-0.27.4/scrambled.h b/lib/mp3guessenc-0.27.4/scrambled.h new file mode 100644 index 00000000000..162865d3c27 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/scrambled.h @@ -0,0 +1,4 @@ + +#define DATA_LEN 6 +unsigned char scrambled_data[DATA_LEN]={172,239,86,103,38,4}; + diff --git a/lib/mp3guessenc-0.27.4/tags.c b/lib/mp3guessenc-0.27.4/tags.c new file mode 100644 index 00000000000..fb41272055a --- /dev/null +++ b/lib/mp3guessenc-0.27.4/tags.c @@ -0,0 +1,1143 @@ +/* + * tags is a support module which provides access to metadata tags (and xing/vbri/lame tags as well) + * Copyright (C) 2013-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +/* Modifed by Evan Dekker 2019-09-26 */ + +#include "mp3g_io_config.h" + +#include +#include +#include + +#include "bit_utils.h" +#include "mp3guessenc.h" + + +#define ID3V2_HEADER_LENGTH 10 +#define ID3V2_FOOTER_LENGTH ID3V2_HEADER_LENGTH +#define ID3V2_FLAGS_FOOTER_PRESENT 0x10 +#define ID3V2_ID_STRING "ID3" + +#define LYRICS3V1_TAG_MAXSIZE 5100 +#define LYRICS3_BEGIN_SIGNATURE "LYRICSBEGIN" +#define LYRICS3_BEGIN_SIGNATURE_LEN 11 +#define LYRICS3V1_END_SIGNATURE "LYRICSEND" +#define LYRICS3V2_END_SIGNATURE "LYRICS200" +#define LYRICS3_END_SIGNATURE_LEN 9 +#define LYRICS3V2_TAGSIZE_LEN 6 + +#define APETAGEX_SIGNATURE "APETAGEX" +#define APETAGEX_FLAGS_HEADER_PRESENT 0x80000000 +#define APETAGEX_FLAGS_THIS_IS_HEADER 0x20000000 + +#define MMTAG_FOOTER_SIZE 48 +#define MMTAG_DATA_OFFSETS_SIZE 20 +#define MMTAG_META_DATA_MINIMUM_SIZE 7868 +#define MMTAG_VERSION_INFO_SIZE 256 +#define MMTAG_HEADER_SIZE MMTAG_VERSION_INFO_SIZE +/* This `safe search size' is choosen on purpose. A larger buffer may lead to + * include the optional mmtag header and searching for the sync bytes will + * result in detecting that optional header as the mandatory version info block + * (unless one would do a further second search and after that dives into + * pointer calculations...) + * With this size and the metadata section at its maximum size (8132 bytes), we + * can find the mandatory version block at the head of the buffer (at most) */ +#define MMTAG_SAFE_SEARCH_SIZE 520 + +#define MMTAG_HEADER 0 +#define MMTAG_IMAGE_EXTENSION 1 +#define MMTAG_IMAGE_BINARY 2 +#define MMTAG_UNUSED 3 +#define MMTAG_VERSION_INFO 4 +#define MMTAG_AUDIO_METADATA 5 +#define MMTAG_OFFSET_ENTRIES 6 + +#define MMTAG_SIGNATURE "Brava Software Inc." +#define MMTAG_SIGNATURE_LEN 19 +#define MMTAG_SIGNATURE_OFFSET 0 +#define MMTAG_VERSION_LEN 4 +#define MMTAG_VERSION_OFFSET 32 + +#define MMTAG_VERSION_BLOCK_SYNC_STRING "18273645" +#define MMTAG_VERSION_BLOCK_SYNC_OFFSET 0 +#define MMTAG_VERSION_BLOCK_SYNC_LENGTH 8 +#define MMTAG_VERSION_BLOCK_SUBSECTION_LENGTH 10 +#define MMTAG_VERSION_BLOCK_XING_VER_OFFSET 10 +#define MMTAG_VERSION_BLOCK_MM_VER_OFFSET 20 +#define MMTAG_VERSION_BLOCK_STRING_LEN 4 + +#define MMTAG_IMAGE_EXTENSION_LEN 4 +#define MMTAG_IMAGE_SIZE_LEN 4 + +#define WAVE_RIFF_STRUCTURE_ID "RIFF" +#define WAVE_RIFF_IDS_LENGTH 4 +#define WAVE_RIFF_WAVE_ID "WAVE" +#define WAVE_RIFF_HEADER_LENGTH 12 +//#define WAVE_RIFF_FMT_ID "fmt " +//#define WAVE_RIFF_FACT_ID "fact" +#define WAVE_RIFF_DATA_ID "data" + +#define VBRI_TAG_START_OFFSET 36 +#define VBRI_TAG_ID_STRING "VBRI" +#define LAME_TAG_ID_STRING "Info" +#define XING_TAG_ID_STRING "Xing" +#define VBR_TAG_ID_STRING_LEN 4 + +int genre_last=147; +char *genre_list[]={ + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", + "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", + "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", + "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", + "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", + "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", + "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", + "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", + "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", + "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", + "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", + "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", + "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", + "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", + "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", + "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", + "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", + "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", + "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", + "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", + "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", + "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror", + "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", + "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", + "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", + "SynthPop", +}; + +typedef struct apefooter_t { + char preamble[8]; + unsigned char version[4]; + unsigned char tag_size[4]; + unsigned char item_count[4]; + unsigned char tag_flags[4]; + char reserved[8]; +} apefooter_t; + +typedef struct mmtag_tail_infos_t { + unsigned char image_extension[4]; + unsigned char image_binary[4]; + unsigned char unused[4]; + unsigned char version_info[4]; + unsigned char audio_metadata[4]; + unsigned char footer[MMTAG_FOOTER_SIZE]; +} mmtag_tail_infos_t; + +#if defined(__WATCOMC__) +/* Watcom compiler defaults alignment to 1 byte */ +typedef struct mmtag_partial_infos_t { +#else +typedef struct __attribute__((packed)) mmtag_partial_infos_t { +#endif + unsigned char empty[12]; + unsigned char sync[MMTAG_VERSION_BLOCK_SUBSECTION_LENGTH]; + unsigned char xing[MMTAG_VERSION_BLOCK_SUBSECTION_LENGTH]; + unsigned char musm[MMTAG_VERSION_BLOCK_SUBSECTION_LENGTH]; +} mmtag_partial_infos_t; + +typedef struct wave_riff_chunk_t { + char ckid[WAVE_RIFF_IDS_LENGTH]; + unsigned char cksize[4]; +} wave_riff_chunk_t; + +unsigned char sideinfo_tab[2][2] = {{32,17},{17,9}}; /* MPEG1 {2ch,1ch}, MPEG2/2.5 {2ch,1ch} */ + + +/* + * memsrch + * + * this function searches for a character string 'needle' into a raw byte sequence + * called 'haystack' which is 'haystack_len' bytes long + * + * return value: when found, the pointer to the first character of 'needle', + * else NULL + * Note: memsrch was developed because of unreliability of 'strstr' search algorithm + * into a raw byte sequence. Cannot use 'memmem' (which is very similar) because it + * is a GNU libc extension + */ +unsigned char *memsrch(unsigned char *haystack, int haystack_len, char *needle) +{ + unsigned char *p=NULL,*q=haystack; + int i=0,len=strlen(needle); + + if (len<=haystack_len) + { + haystack_len -= len - 1; + + while (i=len_before) + lame_string[idx]=0; + resultLen = idx; + } +#ifdef SHOW_LAME_STRINGS + printf("exiting - lame_string=%s\n",lame_string); +#endif + return resultLen; +} + +/////////////////////////////////////////////////////// +// VBR TAG related routines + +void reset_tag_data(vbrtagdata_t *p) +{ + memset((void *)p, 0, sizeof(vbrtagdata_t)); + p->tagId=NULL; + p->infoTag=TAG_NOTAG; + p->vbr_scale=-1; /* GOGO encoder does not put vbr quality value into its vbr tag, so I need here a flag */ + p->lameMusicCRC=-1; /* no music crc found */ +} + +unsigned int parse_vbri_tag(vbrtagdata_t *p, unsigned char *buf) +{ + unsigned int offset=4*8; + + p->tagId=VBRI_TAG_ID_STRING; + p->infoTag=TAG_VBRITAG; + p->version=extract_bits(buf, &offset, 16); + + p->encDelay=extract_bits(buf, &offset, 16); + + p->vbr_scale=extract_bits(buf, &offset, 16); + + p->bytes=extract_bits(buf, &offset, 32); + + p->reported_frames=extract_bits(buf, &offset, 32); + + p->tocEntries=extract_bits(buf, &offset, 16); + + offset+=2*8; + p->sizePerTocEntry=extract_bits(buf, &offset, 16); + + p->framesPerTocEntry=extract_bits(buf, &offset, 16); + + p->tocSize=(int)p->sizePerTocEntry*(int)p->tocEntries; + + /* switch to byte */ + offset = offset/8 + p->tocSize; + + return offset; +} + +unsigned int parse_xing_tag(vbrtagdata_t *p, unsigned char *buffer_start, unsigned int tag_start, char *lame_string) +{ +#define XING_FLAG_FRAMES 0x0001 +#define XING_FLAG_BYTES 0x0002 +#define XING_FLAG_TOC 0x0004 +#define XING_FLAG_VBR_SCALE 0x0008 + + unsigned char *buf=buffer_start+tag_start; + unsigned int offset=sizeof(unsigned int)*8, flags; + unsigned short sum; + + p->tagId = XING_TAG_ID_STRING; + if (buf[0]=='I') + p->infoTag = TAG_LAMECBRTAG; + else + p->infoTag = TAG_XINGTAG; + + flags = extract_bits(buf, &offset, 32); + + if (flags & XING_FLAG_FRAMES) + { + p->reported_frames = extract_bits(buf, &offset, 32); + } + + if (flags & XING_FLAG_BYTES) + { + p->bytes = extract_bits(buf, &offset, 32); + } + + /* switch to byte */ + offset /= 8; + + if (flags & XING_FLAG_TOC) + { + /* I know the toc entries amount */ + p->tocEntries = 100; + p->sizePerTocEntry = 1; + p->tocSize = 100; + offset+=100; + } + + if (buf[offset] == 'G' && buf[offset+1] == 'O' && buf[offset+2] == 'G' && buf[offset+3] == 'O') + { + /* gogo seems not to put a vbr quality value into its tag */ + offset += extract_enc_string(lame_string,buf+offset,4); + } + else + { + if (flags & XING_FLAG_VBR_SCALE) + { + p->vbr_scale = extract_bits(buf+offset, NULL, 32); + offset+=4; + } + } + +/* + * Starting with lame-3.99 alpha releases the devs changed the lame tag signature so it didn't + * start with the string `LAME', instead it just contained the capital `L' at the beginning and the + * following expected characters in the form `Lx.xxyzz', where x is a digit and z is an optional digit. + * Anyway the old signature `LAMEx.xxy' (y may be one of a/b/r) was restored with lame 3.99.2 release + * in order to keep lame tag readable by old sw/hw players. + */ + if ( + (buf[offset] == 'L' && buf[offset+1] == 'A' && buf[offset+2] == 'M' && buf[offset+3] == 'E') + || + (buf[offset] == 'L' && isdigit((int)buf[offset+1]) && buf[offset+2] == '.' && isdigit((int)buf[offset+3]) && isdigit((int)buf[offset+4])) + || + (tolower(buf[offset])=='l' && tolower(buf[offset+1])=='a' && tolower(buf[offset+2])=='v' && tolower(buf[offset+3])=='c') + ) + { + /* + * maybe we found a lame info tag + * warn: a version check has to be done because lame 3.90 was the first + * version able to write a real info tag. + * previous versions wrote just a string + * Updated `if' statement for better handling of upcoming 3.100 release + */ + if (buf[offset+1]=='A' && buf[offset+4]=='3' && buf[offset+5]=='.' && isdigit((int)buf[offset+6]) && buf[offset+6]<'9' && isdigit((int)buf[offset+7]) && !isdigit((int)buf[offset+8])) + /* old versions had more room for detailed strings due to lack of info tag */ + offset += extract_enc_string(lame_string,buf+offset,LAME_STRING_LENGTH); + else + { + memcpy(p->lametag, buf+offset, LAMETAGSIZE); + extract_enc_string(lame_string,buf+offset,9); + offset += LAMETAGSIZE; /* 'offset' is now at the very end of lame tag! */ + sum = crc_reflected_update(0, buffer_start, tag_start+offset-2); + p->lametagVerified = (sum == (unsigned short)((reflect_byte(buffer_start[tag_start+offset-1])<<8) + | reflect_byte(buffer_start[tag_start+offset-2]) ) ); + + if (p->lametagVerified) + p->lameMusicCRC = ((reflect_byte(buffer_start[tag_start+offset-3])<<8) + | reflect_byte(buffer_start[tag_start+offset-4]) ); + } + } + + return offset; +} + +char checkvbrinfotag(vbrtagdata_t *pTagData, unsigned char *buf, off_t pos, char *enc_string) +{ +/* + * Here we detect the info vbr/cbr tag (if any) -- the search is performed inside the buffer + * Return values: + * 0: no tag + * 1: tag found (details into pTagData) + */ + unsigned int temp; + unsigned char mono, lsf; + + reset_tag_data(pTagData); + + pTagData->header=extract_bits(buf, NULL, 32); /* store the header */ + lsf = 1-((pTagData->header&HEADER_FIELD_LSF)>>HEADER_FIELD_LSF_SHIFT); + mono =((pTagData->header&HEADER_FIELD_CHANNELS)>>HEADER_FIELD_CHANNELS_SHIFT)/3; + + if (memcmp(buf+VBRI_TAG_START_OFFSET, VBRI_TAG_ID_STRING, VBR_TAG_ID_STRING_LEN) == 0) + { + parse_vbri_tag(pTagData,buf+VBRI_TAG_START_OFFSET); + } + else + { + temp = sizeof(unsigned int)+(int)sideinfo_tab[lsf][mono]; /* placement of xing vbr tag doesn't take into account optional crc16 */ + + if ( + memcmp(&buf[temp], XING_TAG_ID_STRING, VBR_TAG_ID_STRING_LEN) == 0 + || + memcmp(&buf[temp], LAME_TAG_ID_STRING, VBR_TAG_ID_STRING_LEN) == 0 + ) + { + parse_xing_tag(pTagData, buf, temp, enc_string); + + if ((pTagData->lame_buggy_vbrheader=(((pTagData->header&HEADER_FIELD_BITRATE)>>HEADER_FIELD_BITRATE_SHIFT)==BITRATE_INDEX_RESERVED))) + /* Despite the bitrate field holds a buggy value, I know the header is valid + So, save this (in `lame_buggy_vbrheader' field) and set the right freeformat + bitrate index - I will need it later */ + pTagData->header &= ~HEADER_FIELD_BITRATE; + } + } + + if (pTagData->infoTag!=TAG_NOTAG) /* was any tag found? If so, I am sure I'm dealing with a layerIII stream */ + pTagData->tagStartsAt=pos; /* offset of the mpeg frame containing this tag */ + + return (pTagData->infoTag!=TAG_NOTAG); +} + +int check_timing_shift_case(vbrtagdata_t *p) +{ + if (p->infoTag!=TAG_VBRITAG) + { + if (p->lametag[0]==0 || (tolower(p->lametag[0])=='l' && tolower(p->lametag[1])=='a' && tolower(p->lametag[2])=='v' && tolower(p->lametag[3])=='c')) + { + return EXIT_CODE_CASE_B; + } + if (!p->lametagVerified) + { + return EXIT_CODE_CASE_C; + } + } + + return EXIT_CODE_CASE_D; +} + +void show_info_tag (vbrtagdata_t *p) +/* + * Here we show the technical details found into the VBRI/XING tag + * If a lame tag is found, then it's showed as well. + */ +{ + char lameTag=(p->lametag[0]!=0); + + printf("%s tag detected into the first frame (%d bytes long).\n",p->tagId,p->frameSize); + printf(" Tag offset : %lld (0x%08X)\n",(long long)p->tagStartsAt,(unsigned)p->tagStartsAt); + if (p->infoTag==TAG_VBRITAG) + { + printf(" Tag version : %d\n",p->version); + printf(" Encoder delay : %d samples\n",p->encDelay); + } + printf(" File size : %u bytes\n",p->bytes); + printf(" Number of frames : %u\n",p->reported_frames); + if (p->vbr_scale!=-1) /* GOGO doesn't add a vbr quality value */ + { + printf(" Quality : %u",p->vbr_scale); + if ((lameTag)&&(p->infoTag!=TAG_LAMECBRTAG)) + { /* sure the file is vbr encoded by lame, I just need to know whether it's abr or vbr */ + unsigned char lamemode=p->lametag[9]&15; + if ((lamemode>2)&&(lamemode<7)) + /* it's vbr, so let's print encoder options */ + printf(" (-q %d -V %d)",(100-p->vbr_scale)%10,(100-p->vbr_scale)/10); + } + printf("\n"); + } + printf(" TOC : "); + if (p->tocSize) printf("%d bytes (%d entries, %d byte%s each)\n",p->tocSize,p->tocEntries,p->sizePerTocEntry,(p->sizePerTocEntry==1)?"":"s"); + else printf("no\n"); + if (p->infoTag!=TAG_VBRITAG) + { + printf(" Lame tag : "); + if (lameTag) + { + unsigned char c,i; + unsigned short j; + unsigned int delay; + printf("yes"); + if (p->lame_buggy_vbrheader) printf(" (buggy bitrate field)"); + /* print details from lame tag */ + printf("\nLame tag details...\n Lame short string : "); + for(i=0;i<9;i++) + if (isprint((int)p->lametag[i])) putchar(p->lametag[i]); else putchar(' '); + c=p->lametag[9]>>4; + printf("\n Tag revision : "); + if (c!=15) printf("%d",c); + else printf("invalid!"); + c=p->lametag[9]&15; + printf("\n Bitrate strategy : "); + if ((c==2)||(c==9)) + printf("ABR, "); /* it's abr */ + else + { + if ((c==1)||(c==8)) + printf("CBR, "); /* it's cbr */ + else + if ((c>2)&&(c<7)) /* it's vbr */ + printf("VBR method %s, min ",(c==3)?"old/rh":(c==4)?"mtrh":/*if lame, c is 5*/"mt"); + } + c=p->lametag[20]; + if (c) + { + printf("%d kbps",c); + if (c==255) printf (" or higher"); + } + else + printf("unknown"); + c=p->lametag[10]; + printf("\n Lowpass value : "); + if (c) printf("%d",c*100); + else printf("unknown"); + c=p->lametag[19]; + printf("\n nspsytune : %s\n",(c&0x10)?"yes":"no"); + printf(" nssafejoint : %s\n",(c&0x20)?"yes":"no"); + printf(" nogap continued : %s\n",(c&0x40)?"yes":"no"); + printf(" nogap continuation : %s\n",(c&0x80)?"yes":"no"); + printf(" ATH type : %d\n",c&0xf); + delay=extract_bits((unsigned char *)p->lametag+20, NULL, 32); + p->encDelay=(short)((delay>>12)&0xfff); + p->encPadding=(short)(delay&0xfff); + printf(" Encoder delay (start) : %d samples\n",p->encDelay); + printf(" Encoder padding (end) : %d samples\n",p->encPadding); + printf(" [ Length of original audio : %u samples ]\n", + (p->reported_frames*576*((p->header&0x80000)?2:1))-p->encDelay-p->encPadding); + c=p->lametag[24]; + printf(" Encoding mode : "); + switch (c&0x1c) + { + case 24: + printf("intensity stereo"); + break; + case 20: + printf("auto"); + break; + case 16: + printf("forced MS stereo"); + break; + case 12: + printf("joint stereo"); + break; + case 8: + printf("dual channel"); + break; + case 4: + printf("simple LR stereo"); + break; + case 0: + printf("mono"); + break; + default: + printf("other"); + } + printf("\n Unwise settings : %sused\n",(c&32)?"":"not "); + printf(" Source frequency : "); + switch (c&0xc0) + { + case 128: + printf("48 kHz"); + break; + case 64: + printf("44.1 kHz"); + break; + case 0: + printf("32 kHz or below"); + break; + default: + printf("higher than 48 kHz"); + } + j=extract_bits((unsigned char *)p->lametag+26, NULL, 16)&0x7ff; /* 11 least significant bits are used for preset */ + printf("\n Preset : "); + if (j==0) + printf("No preset."); + else + { + if (j<321) + printf("%d kbps",j); + else + { + if (j>409 && j<501) + printf("V%d / VBR_%d",50-j/10,j-400); + else + { + if (j>999 && j<1008) + { + switch (j) + { + case 1000: + printf("R3mix."); + break; + case 1001: + printf("Standard."); + break; + case 1002: + printf("Extreme."); + break; + case 1003: + printf("Insane."); + break; + case 1004: + printf("Standard fast."); + break; + case 1005: + printf("Extreme fast."); + break; + case 1006: + printf("Medium."); + break; + case 1007: + printf("Medium fast."); + break; + } + } + else + printf("Unknown preset."); + } + } + } + printf("\n Originally encoded : %u bytes\n",extract_bits((unsigned char *)p->lametag+28, NULL, 32)); + printf(" Tag verification : %s\n", (p->lametagVerified?"passed":"failed")); + } + else + printf("no\n"); + } + printf("\n"); +} + +/////////////////////////////////////////////////////// + +void show_id3v1(id3tag *id3) +{ + unsigned char i,id3v11=(!id3->comment[28]&&id3->comment[29]); + + printf(" Title : "); + for(i=0;i<30;i++) + if (isprint(id3->title[i])) putchar(id3->title[i]); else putchar('.'); + + printf("\n Artist : "); + for(i=0;i<30;i++) + if (isprint(id3->artist[i])) putchar(id3->artist[i]); else putchar('.'); + + printf("\n Album : "); + for(i=0;i<30;i++) + if (isprint(id3->album[i])) putchar(id3->album[i]); else putchar('.'); + + printf("\n Year : "); + for(i=0;i<4;i++) + if (isprint(id3->year[i])) putchar(id3->year[i]); else putchar('.'); + + printf("\n Comment : "); + if (id3v11) + { + for(i=0;i<28;i++) + if (isprint(id3->comment[i])) putchar(id3->comment[i]); else putchar('.'); + printf("\n Track # : %u", id3->comment[29]); + } + else + for(i=0;i<30;i++) + if (isprint(id3->comment[i])) putchar(id3->comment[i]); else putchar('.'); + + printf("\n Genre : "); + if (id3->genre > genre_last) + printf("unknown"); + else + printf("%s",genre_list[id3->genre]); + printf("\n\n"); +} + +int checkid3v1(FILE *fi, off_t pos, id3tag *id3) +/* + * Here the info tag id3v1.x is detected + * This function returns either the offset of the tag + * or the offset of EOF if no tag is found, + * so the main cycle can stop scanning when this value has been reached + * Note: the file pointer IS modified and then it IS restored + */ +{ + off_t filepos; + int id3size=0; + + if (pos-(signed)sizeof(id3tag) >= 0) + { + filepos=ftello(fi); + fseeko(fi,pos-sizeof(id3tag),SEEK_SET); + + if (fread(id3,sizeof(id3tag),1,fi)==1) + { + if ((id3->tag[0] == 'T' && id3->tag[1] == 'A' && id3->tag[2] == 'G') + || + (id3->tag[0] == 't' && id3->tag[1] == 'a' && id3->tag[2] == 'g')) + id3size = sizeof(id3tag); + } + fseeko(fi,filepos,SEEK_SET); + } + return id3size; +} + +/* + * Since revision 4, ID3v2 may be found at the file tail also. + * Here we'll check for its presence seeking its footer, + * which is mandatory when the tag is placed at the bitstream end. + * + * If the tag is not found, return value is 0. + * If errors are detected, return value is -1. + * Note: this routine DOES read data from the input file + * but it DOES restore the file pointer before exiting. + */ +int checkid3v2_footer(FILE *fi, off_t pos, unsigned char *idMaj, unsigned char *idmin) +{ + unsigned char footer[ID3V2_FOOTER_LENGTH]; + int id3v2size=0; + off_t filepos=ftello(fi); + + if ((pos-ID3V2_FOOTER_LENGTH)>=0) + { + fseeko(fi,pos-ID3V2_FOOTER_LENGTH,SEEK_SET); + + if (fread(footer,ID3V2_FOOTER_LENGTH,1,fi)==1) + { + if (footer[0] == '3' && footer[1] == 'D' && footer[2] == 'I') + { + /* footer found! */ + *idMaj=footer[3]; + *idmin=footer[4]; + + if (!(footer[6]&0x80) && !(footer[7]&0x80) && !(footer[8]&0x80) && !(footer[9]&0x80) && !(footer[5]&0x0f)) + { + id3v2size = footer[6]*2097152+ + footer[7]*16384+ + footer[8]*128+ + footer[9] +ID3V2_HEADER_LENGTH +ID3V2_FOOTER_LENGTH; + } + else + id3v2size=-1; + } + } + + fseeko(fi,filepos,SEEK_SET); + } + return id3v2size; +} + + +unsigned int checkapetagx(apefooter_t *af) +{ + unsigned int ape_len = 0; + + if (!memcmp(af->preamble,APETAGEX_SIGNATURE,8)) + { + /* ape tag found! */ + ape_len=get_little_endian_uint(af->tag_size); + if ((get_little_endian_uint(af->tag_flags)&APETAGEX_FLAGS_HEADER_PRESENT)!=0) + { + /* tag contains a header */ + ape_len+=sizeof(apefooter_t); + } + } + + return ape_len; + +} + +/* + * check for ape tag at the end of the file + * when placed at the end of the file (between the very last frame and the id3v1 tag) + * ape tag can be both 1 and 2. Tag version is returned into `ape_vers', if found. + * search is started at file position `pos' where the end of mpeg stream is expected to be. + * return value is tag length if found, else zero. + * file pointer doesn't get modified + */ +int checkapetagx_tail(FILE *fi, off_t pos, int *ape_vers, int *ape_items, char *header_present) +{ + int ape_len=0; + apefooter_t apefooter; + off_t filepos; + + if ((pos-(signed)sizeof(apefooter_t))>=0) + { + filepos=ftello(fi); + fseeko(fi, pos-sizeof(apefooter_t), SEEK_SET); + if (fread((void *)&apefooter,sizeof(apefooter_t),1,fi)==1) + { + if ((ape_len=checkapetagx(&apefooter)) != 0) + { + *ape_vers=get_little_endian_uint(apefooter.version); + *ape_items=get_little_endian_uint(apefooter.item_count); + *header_present=((get_little_endian_uint(apefooter.tag_flags)&APETAGEX_FLAGS_HEADER_PRESENT)!=0); + } + } + fseeko(fi, filepos, SEEK_SET); + } + + return ape_len; +} + +int checklyrics3v1(FILE *fi, off_t pos) +{ + int lyr3_len=0; + unsigned char lyrics3v1_tag[LYRICS3V1_TAG_MAXSIZE]; + off_t filepos=ftello(fi); + unsigned char sign[LYRICS3_END_SIGNATURE_LEN],*p; + + fseeko(fi, pos-LYRICS3_END_SIGNATURE_LEN, SEEK_SET); + if (fread(sign,LYRICS3_END_SIGNATURE_LEN,1,fi)==1) + { + if (!memcmp(sign,LYRICS3V1_END_SIGNATURE,LYRICS3_END_SIGNATURE_LEN)) + { + fseeko(fi,pos-LYRICS3V1_TAG_MAXSIZE-LYRICS3_END_SIGNATURE_LEN,SEEK_SET); + if (fread(lyrics3v1_tag,1,LYRICS3V1_TAG_MAXSIZE,fi)==LYRICS3V1_TAG_MAXSIZE) + { + if ((p=memsrch(lyrics3v1_tag,LYRICS3V1_TAG_MAXSIZE,LYRICS3_BEGIN_SIGNATURE))!=NULL) + { + lyr3_len = LYRICS3V1_TAG_MAXSIZE-(int)(p-lyrics3v1_tag)+LYRICS3_END_SIGNATURE_LEN; + } + } + } + } + + fseeko(fi, filepos, SEEK_SET); + return lyr3_len; +} + +int checklyrics3v2(FILE *fi, off_t pos) +{ + int lyr3_len=0; + off_t filepos=ftello(fi); + unsigned char sign[LYRICS3V2_TAGSIZE_LEN+LYRICS3_END_SIGNATURE_LEN],*p; + + fseeko(fi, pos-LYRICS3_END_SIGNATURE_LEN-LYRICS3V2_TAGSIZE_LEN, SEEK_SET); + if (fread(sign,LYRICS3_END_SIGNATURE_LEN+LYRICS3V2_TAGSIZE_LEN,1,fi)==1) + { + if (!memcmp(&sign[LYRICS3V2_TAGSIZE_LEN],LYRICS3V2_END_SIGNATURE,LYRICS3_END_SIGNATURE_LEN)) + { + lyr3_len = strtol((char *)sign, NULL, 10) + LYRICS3_END_SIGNATURE_LEN + LYRICS3V2_TAGSIZE_LEN; + fseeko(fi, -lyr3_len, SEEK_CUR); + if (fread(sign,LYRICS3_BEGIN_SIGNATURE_LEN,1,fi)==1) + { + if ((p=memsrch(sign,LYRICS3V2_TAGSIZE_LEN+LYRICS3_END_SIGNATURE_LEN,LYRICS3_BEGIN_SIGNATURE))!=sign) + { + lyr3_len = 0; + } + } + else + { + lyr3_len = 0; + } + } + } + + fseeko(fi, filepos, SEEK_SET); + return lyr3_len; +} + +void checkmmtag(FILE *fi, off_t pos, mmtag_t *mmtag) +{ + mmtag_tail_infos_t mm_tail_infos; + unsigned char search[MMTAG_SAFE_SEARCH_SIZE], *p; + off_t filepos=ftello(fi); + unsigned int section_sizes[MMTAG_OFFSET_ENTRIES]; + int j; + + fseeko(fi, pos-MMTAG_FOOTER_SIZE-MMTAG_DATA_OFFSETS_SIZE, SEEK_SET); + if (fread((unsigned char *)&mm_tail_infos,sizeof(mmtag_tail_infos_t),1,fi)==1) + { + if (!memcmp(&mm_tail_infos.footer[MMTAG_SIGNATURE_OFFSET], MMTAG_SIGNATURE, MMTAG_SIGNATURE_LEN)) + { + memcpy(mmtag->tag_ver, &mm_tail_infos.footer[MMTAG_VERSION_OFFSET], MMTAG_VERSION_LEN); + + fseeko(fi, pos-MMTAG_FOOTER_SIZE-MMTAG_DATA_OFFSETS_SIZE-MMTAG_META_DATA_MINIMUM_SIZE-MMTAG_SAFE_SEARCH_SIZE, SEEK_SET); + if (fread(search,MMTAG_SAFE_SEARCH_SIZE,1,fi)==1) + { + if ((p=memsrch(search, MMTAG_SAFE_SEARCH_SIZE, MMTAG_VERSION_BLOCK_SYNC_STRING))!=NULL) + { + memcpy(mmtag->mm_ver, &p[MMTAG_VERSION_BLOCK_MM_VER_OFFSET], MMTAG_VERSION_BLOCK_STRING_LEN); + memcpy(mmtag->enc_ver, &p[MMTAG_VERSION_BLOCK_XING_VER_OFFSET], MMTAG_VERSION_BLOCK_STRING_LEN); + section_sizes[MMTAG_HEADER] = MMTAG_HEADER_SIZE; + section_sizes[MMTAG_IMAGE_EXTENSION] = get_little_endian_uint(mm_tail_infos.image_binary)- + get_little_endian_uint(mm_tail_infos.image_extension); + section_sizes[MMTAG_IMAGE_BINARY] = get_little_endian_uint(mm_tail_infos.unused)- + get_little_endian_uint(mm_tail_infos.image_binary); + section_sizes[MMTAG_UNUSED] = get_little_endian_uint(mm_tail_infos.version_info)- + get_little_endian_uint(mm_tail_infos.unused); + section_sizes[MMTAG_VERSION_INFO] = get_little_endian_uint(mm_tail_infos.audio_metadata)- + get_little_endian_uint(mm_tail_infos.version_info); + section_sizes[MMTAG_AUDIO_METADATA] = MMTAG_SAFE_SEARCH_SIZE-(int)(p-search)-MMTAG_VERSION_INFO_SIZE+MMTAG_META_DATA_MINIMUM_SIZE; + mmtag->metadata_size = section_sizes[MMTAG_AUDIO_METADATA]; + + mmtag->tag_size = MMTAG_DATA_OFFSETS_SIZE + MMTAG_FOOTER_SIZE; + for (j=MMTAG_IMAGE_EXTENSION; jtag_size += section_sizes[j]; + + fseeko(fi, pos-mmtag->tag_size-MMTAG_HEADER_SIZE, SEEK_SET); + if (fread(search,MMTAG_SAFE_SEARCH_SIZE,1,fi)==1) + { + memcpy(mmtag->image_ext, &search[MMTAG_HEADER_SIZE], MMTAG_IMAGE_EXTENSION_LEN); + mmtag->image_size = search[MMTAG_HEADER_SIZE+MMTAG_IMAGE_EXTENSION_LEN]+ + search[MMTAG_HEADER_SIZE+MMTAG_IMAGE_EXTENSION_LEN+1]*256+ + search[MMTAG_HEADER_SIZE+MMTAG_IMAGE_EXTENSION_LEN+2]*256*256+ + search[MMTAG_HEADER_SIZE+MMTAG_IMAGE_EXTENSION_LEN+3]*256*256*256; + mmtag->image_offset = pos-mmtag->tag_size+MMTAG_IMAGE_EXTENSION_LEN+MMTAG_IMAGE_SIZE_LEN; + mmtag->header_present = (memsrch(search, MMTAG_SAFE_SEARCH_SIZE, MMTAG_VERSION_BLOCK_SYNC_STRING)==search); + if (mmtag->header_present) + mmtag->tag_size += MMTAG_HEADER_SIZE; + } + } + } + } + } + + fseeko(fi, filepos, SEEK_SET); +} + +char checkmm_partial_tag(FILE *fi, off_t pos, mmtag_t *mmtag) +{ + mmtag_partial_infos_t mm_infos; + char present=0; + + off_t filepos=ftello(fi); + + fseeko(fi, pos, SEEK_SET); + if (fread((unsigned char *)&mm_infos,sizeof(mmtag_partial_infos_t),1,fi)==1) + { + if (!memcmp(&mm_infos.sync, MMTAG_VERSION_BLOCK_SYNC_STRING, MMTAG_VERSION_BLOCK_SYNC_LENGTH)) + { + memcpy(mmtag->mm_ver, mm_infos.musm, MMTAG_VERSION_BLOCK_STRING_LEN); + memcpy(mmtag->enc_ver, mm_infos.xing, MMTAG_VERSION_BLOCK_STRING_LEN); + present=1; + } + } + + fseeko(fi, filepos, SEEK_SET); + return present; +} + + +/* + * Here the info tag id3v2 is detected + * This function returns the size of the tag (if detected) + * so the main cycle can choose if either show it or skip it. + * Also, idMaj and idmin provide id3V2 major and minor version number. + * If the tag is not found, return value is 0. + * If errors are detected, return value is -1. + */ +int checkid3v2(unsigned char *buff, int length, int *next_tag, unsigned char *idMaj, unsigned char *idmin) +{ + unsigned char *id3header; + int id3v2size=0; + + if ((id3header=memsrch(buff, length, ID3V2_ID_STRING)) != NULL) + { + /* has ID3v2 */ + *idMaj=id3header[3]; + *idmin=id3header[4]; + if (!(id3header[6]&0x80) && !(id3header[7]&0x80) && !(id3header[8]&0x80) && !(id3header[9]&0x80) && !(id3header[5]&0x0f)) + { + id3v2size = id3header[6]*2097152+id3header[7]*16384+id3header[8]*128+id3header[9] +ID3V2_HEADER_LENGTH; + if (id3header[5]&ID3V2_FLAGS_FOOTER_PRESENT) + id3v2size += ID3V2_FOOTER_LENGTH; + *next_tag = (int)(id3header-buff); + } + else + id3v2size=-1; + } + + return id3v2size; +} + +int checkapetagx_head(unsigned char *buff, int length, int *next_tag, int *ape_vers, int *ape_items, char *header_present) +{ + int ape_len=0; + unsigned int hflags; + apefooter_t apefooter; + unsigned char *p_apefooter; + + if ((p_apefooter=memsrch(buff, length, APETAGEX_SIGNATURE)) != NULL) + { + memcpy((void *)&apefooter, p_apefooter, sizeof(apefooter_t)); + + if ((ape_len=checkapetagx(&apefooter)) != 0) + { + hflags = get_little_endian_uint(apefooter.tag_flags); + if ((hflags&APETAGEX_FLAGS_HEADER_PRESENT)!=0 && (hflags&APETAGEX_FLAGS_THIS_IS_HEADER)!=0) + { + *ape_vers=get_little_endian_uint(apefooter.version); + *ape_items=get_little_endian_uint(apefooter.item_count); + *header_present=1; + *next_tag = (int)(p_apefooter - buff); + } + else + { + /* very strange tag - header MUST be present when put at the file head! */ + /* I will assume this as a data corruption */ + ape_len = 0; + } + } + } + + return ape_len; +} + +int checkwaveriff_datachunk(unsigned char *buff, int *next_tag, int *datachunk) +{ + int chunk_len = 0; + + if ( + isprint(buff[0]) + && + isprint(buff[1]) + && + isprint(buff[2]) + && + isprint(buff[3]) + ) + { + *next_tag = 0; + if (memcmp(buff, WAVE_RIFF_DATA_ID, WAVE_RIFF_IDS_LENGTH)) + { + /* not the `data' chunk */ + chunk_len = get_little_endian_uint(&buff[4]) + WAVE_RIFF_IDS_LENGTH + sizeof(unsigned int); + *datachunk = 0; + } + else + { + /* `data' chunk found */ + chunk_len = WAVE_RIFF_IDS_LENGTH + sizeof(unsigned int); + *datachunk = get_little_endian_uint(&buff[4]); + } + } + + return chunk_len; +} + +int checkwaveriff(unsigned char *buff, int length, int *next_tag) +{ + int riff_len=0; + unsigned char *waveriff; + + if ((waveriff=memsrch(buff, length, WAVE_RIFF_STRUCTURE_ID)) != NULL) + { + /* "RIFF" found */ + if (memcmp(&waveriff[8], WAVE_RIFF_WAVE_ID, WAVE_RIFF_IDS_LENGTH)==0) + { + /* "WAVE" found */ + riff_len = WAVE_RIFF_HEADER_LENGTH; + *next_tag = (int)(waveriff - buff); + } + } + + return riff_len; +} diff --git a/lib/mp3guessenc-0.27.4/tags.h b/lib/mp3guessenc-0.27.4/tags.h new file mode 100644 index 00000000000..4bc609b7072 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/tags.h @@ -0,0 +1,112 @@ +/* + * tags is a support module which provides access to metadata tags (and xing/vbri/lame tags as well) + * Copyright (C) 2013-2018 Elio Blanca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +/* Modifed by Evan Dekker 2019-09-26 */ + +#ifndef TAGS_H +#define TAGS_H + +#define LAMETAGSIZE 36 +#define LAME_STRING_LENGTH 48 + +#define TAG_NOTAG 0 +#define TAG_VBRITAG 1 +#define TAG_XINGTAG 2 +#define TAG_LAMECBRTAG 3 + +/* + * this value MUST be carefully choosen! + * (or some calculation will be needed, so boring) + * Current identification routines work for id3v2, apetag and wave riff, + * (the metadata tags we can usually find at the very beginning) + * and they require 10 bytes, 32 bytes and 12 bytes (just look at the code) + * at least. + * Should a new tag appear, this value will have to be evaluated again. + */ +#define HEAD_METADATA_MIN_IDENTIFICATION_LENGTH 32 + +typedef struct id3tag { + unsigned char tag[3]; + unsigned char title[30]; + unsigned char artist[30]; + unsigned char album[30]; + unsigned char year[4]; + unsigned char comment[30]; + unsigned char genre; +} id3tag; + +/* structure for MusicMatch tag */ +typedef struct mmtag_t { + unsigned int tag_size; + unsigned int image_size; + unsigned int metadata_size; + off_t image_offset; + char mm_ver[5]; /* this field is actually four bytes long, this trick ensures there will always be a null string terminator */ + char tag_ver[5]; /* this field is actually four bytes long, this trick ensures there will always be a null string terminator */ + char enc_ver[5]; /* this field is actually four bytes long, this trick ensures there will always be a null string terminator */ + char image_ext[5];/* this field is actually four bytes long, this trick ensures there will always be a null string terminator */ + char header_present; +} mmtag_t; + +/* structure to receive extracted header */ +typedef struct vbrtagdata_t +{ + char *tagId; + off_t tagStartsAt; + unsigned int header; /* mpeg header of the frame containing the tag */ + int frameSize; /* I want to keep the size of the frame containing the tag */ + char infoTag; + short version; + /* The following two fields come from lame vbr tag - they differ from similar info + written by fhg encoders (stored into streamInfo structure) */ + short encDelay; /* encoder delay (start) */ + short encPadding; /* encoder padding (samples added at the end of the wave) */ + short tocEntries; + short sizePerTocEntry; + short framesPerTocEntry; + int tocSize; + unsigned int reported_frames; /* total bit stream frames from Vbr header data */ + unsigned int bytes; /* total bit stream bytes from Vbr header data*/ + int vbr_scale; /* encoded vbr scale from Vbr header data*/ + char lame_buggy_vbrheader; + unsigned char lametag[LAMETAGSIZE]; + char lametagVerified; + int lameMusicCRC; /* this is actually a ushort + but I need setting -1 for signaling empty value */ +} vbrtagdata_t; + + +int extract_enc_string(char *, unsigned char *, int); +char checkvbrinfotag(vbrtagdata_t *, unsigned char *, off_t, char *); +void show_info_tag (vbrtagdata_t *); +void show_id3v1(id3tag *); +int checkid3v1(FILE *, off_t, id3tag *); +int checkid3v2_footer(FILE *, off_t, unsigned char *, unsigned char *); +int checkapetagx_tail(FILE *, off_t, int *, int *, char *); +int checklyrics3v1(FILE *, off_t); +int checklyrics3v2(FILE *, off_t); +void checkmmtag(FILE *, off_t, mmtag_t *); +char checkmm_partial_tag(FILE *, off_t, mmtag_t *); +int checkid3v2(unsigned char *, int, int *, unsigned char *, unsigned char *); +int checkapetagx_head(unsigned char *, int, int *, int *, int *, char *); +int checkwaveriff(unsigned char *, int, int *); +int checkwaveriff_datachunk(unsigned char *, int *, int *); + +int check_timing_shift_case(vbrtagdata_t *); + +#endif diff --git a/src/library/rekordbox/rekordbox_anlz.cpp b/src/library/rekordbox/rekordbox_anlz.cpp new file mode 100644 index 00000000000..af90ebbcace --- /dev/null +++ b/src/library/rekordbox/rekordbox_anlz.cpp @@ -0,0 +1,317 @@ +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "rekordbox_anlz.h" + + + +rekordbox_anlz_t::rekordbox_anlz_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = this; + _read(); +} + +void rekordbox_anlz_t::_read() { + m__unnamed0 = m__io->ensure_fixed_contents(std::string("\x50\x4D\x41\x49", 4)); + m_len_header = m__io->read_u4be(); + m_len_file = m__io->read_u4be(); + m__unnamed3 = m__io->read_bytes((len_header() - _io()->pos())); + m_sections = new std::vector(); + { + int i = 0; + while (!m__io->is_eof()) { + m_sections->push_back(new tagged_section_t(m__io, this, m__root)); + i++; + } + } +} + +rekordbox_anlz_t::~rekordbox_anlz_t() { + for (std::vector::iterator it = m_sections->begin(); it != m_sections->end(); ++it) { + delete *it; + } + delete m_sections; +} + +rekordbox_anlz_t::path_tag_t::path_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::path_tag_t::_read() { + m_len_path = m__io->read_u4be(); + n_path = true; + if (len_path() > 1) { + n_path = false; + m_path = kaitai::kstream::bytes_to_str(m__io->read_bytes((len_path() - 2)), std::string("utf-16be")); + } +} + +rekordbox_anlz_t::path_tag_t::~path_tag_t() { + if (!n_path) { + } +} + +rekordbox_anlz_t::wave_preview_tag_t::wave_preview_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::wave_preview_tag_t::_read() { + m_len_preview = m__io->read_u4be(); + m__unnamed1 = m__io->read_u4be(); + m_data = m__io->read_bytes(len_preview()); +} + +rekordbox_anlz_t::wave_preview_tag_t::~wave_preview_tag_t() { +} + +rekordbox_anlz_t::beat_grid_tag_t::beat_grid_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::beat_grid_tag_t::_read() { + m__unnamed0 = m__io->read_u4be(); + m__unnamed1 = m__io->read_u4be(); + m_len_beats = m__io->read_u4be(); + int l_beats = len_beats(); + m_beats = new std::vector(); + m_beats->reserve(l_beats); + for (int i = 0; i < l_beats; i++) { + m_beats->push_back(new beat_grid_beat_t(m__io, this, m__root)); + } +} + +rekordbox_anlz_t::beat_grid_tag_t::~beat_grid_tag_t() { + for (std::vector::iterator it = m_beats->begin(); it != m_beats->end(); ++it) { + delete *it; + } + delete m_beats; +} + +rekordbox_anlz_t::wave_color_preview_tag_t::wave_color_preview_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::wave_color_preview_tag_t::_read() { + m_len_entry_bytes = m__io->read_u4be(); + m_len_entries = m__io->read_u4be(); + m__unnamed2 = m__io->read_u4be(); + m_entries = m__io->read_bytes((len_entries() * len_entry_bytes())); +} + +rekordbox_anlz_t::wave_color_preview_tag_t::~wave_color_preview_tag_t() { +} + +rekordbox_anlz_t::wave_scroll_tag_t::wave_scroll_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::wave_scroll_tag_t::_read() { + m_len_entry_bytes = m__io->read_u4be(); + m_len_entries = m__io->read_u4be(); + m__unnamed2 = m__io->read_u4be(); + m_entries = m__io->read_bytes((len_entries() * len_entry_bytes())); +} + +rekordbox_anlz_t::wave_scroll_tag_t::~wave_scroll_tag_t() { +} + +rekordbox_anlz_t::vbr_tag_t::vbr_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::vbr_tag_t::_read() { + m__unnamed0 = m__io->read_u4be(); + int l_index = 400; + m_index = new std::vector(); + m_index->reserve(l_index); + for (int i = 0; i < l_index; i++) { + m_index->push_back(m__io->read_u4be()); + } +} + +rekordbox_anlz_t::vbr_tag_t::~vbr_tag_t() { + delete m_index; +} + +rekordbox_anlz_t::cue_entry_t::cue_entry_t(kaitai::kstream* p__io, rekordbox_anlz_t::cue_tag_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::cue_entry_t::_read() { + m__unnamed0 = m__io->ensure_fixed_contents(std::string("\x50\x43\x50\x54", 4)); + m_len_header = m__io->read_u4be(); + m_len_entry = m__io->read_u4be(); + m_hot_cue = m__io->read_u4be(); + m_status = static_cast(m__io->read_u4be()); + m__unnamed5 = m__io->read_u4be(); + m_order_first = m__io->read_u2be(); + m_order_last = m__io->read_u2be(); + m_type = static_cast(m__io->read_u1()); + m__unnamed9 = m__io->read_bytes(3); + m_time = m__io->read_u4be(); + m_loop_time = m__io->read_u4be(); + m__unnamed12 = m__io->read_bytes(16); +} + +rekordbox_anlz_t::cue_entry_t::~cue_entry_t() { +} + +rekordbox_anlz_t::beat_grid_beat_t::beat_grid_beat_t(kaitai::kstream* p__io, rekordbox_anlz_t::beat_grid_tag_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::beat_grid_beat_t::_read() { + m_beat_number = m__io->read_u2be(); + m_tempo = m__io->read_u2be(); + m_time = m__io->read_u4be(); +} + +rekordbox_anlz_t::beat_grid_beat_t::~beat_grid_beat_t() { +} + +rekordbox_anlz_t::unknown_tag_t::unknown_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::unknown_tag_t::_read() { +} + +rekordbox_anlz_t::unknown_tag_t::~unknown_tag_t() { +} + +rekordbox_anlz_t::tagged_section_t::tagged_section_t(kaitai::kstream* p__io, rekordbox_anlz_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::tagged_section_t::_read() { + m_fourcc = m__io->read_s4be(); + m_len_header = m__io->read_u4be(); + m_len_tag = m__io->read_u4be(); + switch (fourcc()) { + case 1346588482: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new cue_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347900978: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new wave_preview_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347900980: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new wave_color_preview_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347895638: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new wave_preview_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347900979: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new wave_scroll_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347507290: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new beat_grid_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347830354: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new vbr_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347900981: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new wave_color_scroll_tag_t(m__io__raw_body, this, m__root); + break; + } + case 1347441736: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new path_tag_t(m__io__raw_body, this, m__root); + break; + } + default: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new unknown_tag_t(m__io__raw_body, this, m__root); + break; + } + } +} + +rekordbox_anlz_t::tagged_section_t::~tagged_section_t() { + delete m__io__raw_body; + delete m_body; +} + +rekordbox_anlz_t::wave_color_scroll_tag_t::wave_color_scroll_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::wave_color_scroll_tag_t::_read() { + m_len_entry_bytes = m__io->read_u4be(); + m_len_entries = m__io->read_u4be(); + m__unnamed2 = m__io->read_u4be(); + m_entries = m__io->read_bytes((len_entries() * len_entry_bytes())); +} + +rekordbox_anlz_t::wave_color_scroll_tag_t::~wave_color_scroll_tag_t() { +} + +rekordbox_anlz_t::cue_tag_t::cue_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::cue_tag_t::_read() { + m_type = static_cast(m__io->read_u4be()); + m_len_cues = m__io->read_u4be(); + m_memory_count = m__io->read_u4be(); + int l_cues = len_cues(); + m_cues = new std::vector(); + m_cues->reserve(l_cues); + for (int i = 0; i < l_cues; i++) { + m_cues->push_back(new cue_entry_t(m__io, this, m__root)); + } +} + +rekordbox_anlz_t::cue_tag_t::~cue_tag_t() { + for (std::vector::iterator it = m_cues->begin(); it != m_cues->end(); ++it) { + delete *it; + } + delete m_cues; +} diff --git a/src/library/rekordbox/rekordbox_anlz.h b/src/library/rekordbox/rekordbox_anlz.h new file mode 100644 index 00000000000..a86194a63fb --- /dev/null +++ b/src/library/rekordbox/rekordbox_anlz.h @@ -0,0 +1,644 @@ +#ifndef REKORDBOX_ANLZ_H_ +#define REKORDBOX_ANLZ_H_ + +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "kaitaistruct.h" + +#include +#include + +#if KAITAI_STRUCT_VERSION < 7000L +#error "Incompatible Kaitai Struct C++/STL API: version 0.7 or later is required" +#endif + +/** + * These files are created by rekordbox when analyzing audio tracks + * to facilitate DJ performance. They include waveforms, beat grids + * (information about the precise time at which each beat occurs), + * time indices to allow efficient seeking to specific positions + * inside variable bit-rate audio streams, and lists of memory cues + * and loop points. They are used by Pioneer professional DJ + * equipment. + * + * The format has been reverse-engineered to facilitate sophisticated + * integrations with light and laser shows, videos, and other musical + * instruments, by supporting deep knowledge of what is playing and + * what is coming next through monitoring the network communications + * of the players. + * \sa Source + */ + +class rekordbox_anlz_t : public kaitai::kstruct { + +public: + class path_tag_t; + class wave_preview_tag_t; + class beat_grid_tag_t; + class wave_color_preview_tag_t; + class wave_scroll_tag_t; + class vbr_tag_t; + class cue_entry_t; + class beat_grid_beat_t; + class unknown_tag_t; + class tagged_section_t; + class wave_color_scroll_tag_t; + class cue_tag_t; + + enum section_tags_t { + SECTION_TAGS_CUES_2 = 1346588466, + SECTION_TAGS_CUES = 1346588482, + SECTION_TAGS_PATH = 1347441736, + SECTION_TAGS_BEAT_GRID = 1347507290, + SECTION_TAGS_VBR = 1347830354, + SECTION_TAGS_WAVE_PREVIEW = 1347895638, + SECTION_TAGS_WAVE_TINY = 1347900978, + SECTION_TAGS_WAVE_SCROLL = 1347900979, + SECTION_TAGS_WAVE_COLOR_PREVIEW = 1347900980, + SECTION_TAGS_WAVE_COLOR_SCROLL = 1347900981 + }; + + enum cue_list_type_t { + CUE_LIST_TYPE_MEMORY_CUES = 0, + CUE_LIST_TYPE_HOT_CUES = 1 + }; + + enum cue_entry_type_t { + CUE_ENTRY_TYPE_MEMORY_CUE = 1, + CUE_ENTRY_TYPE_LOOP = 2 + }; + + enum cue_entry_status_t { + CUE_ENTRY_STATUS_DISABLED = 0, + CUE_ENTRY_STATUS_ENABLED = 1 + }; + + rekordbox_anlz_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, rekordbox_anlz_t* p__root = 0); + +private: + void _read(); + +public: + ~rekordbox_anlz_t(); + + /** + * Stores the file path of the audio file to which this analysis + * applies. + */ + + class path_tag_t : public kaitai::kstruct { + + public: + + path_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~path_tag_t(); + + private: + uint32_t m_len_path; + std::string m_path; + bool n_path; + + public: + bool _is_null_path() { path(); return n_path; }; + + private: + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + uint32_t len_path() const { return m_len_path; } + std::string path() const { return m_path; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * Stores a waveform preview image suitable for display above + * the touch strip for jumping to a track position. + */ + + class wave_preview_tag_t : public kaitai::kstruct { + + public: + + wave_preview_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~wave_preview_tag_t(); + + private: + uint32_t m_len_preview; + uint32_t m__unnamed1; + std::string m_data; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + + /** + * The length, in bytes, of the preview data itself. This is + * slightly redundant because it can be computed from the + * length of the tag. + */ + uint32_t len_preview() const { return m_len_preview; } + uint32_t _unnamed1() const { return m__unnamed1; } + + /** + * The actual bytes of the waveform preview. + */ + std::string data() const { return m_data; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * Holds a list of all the beats found within the track, recording + * their bar position, the time at which they occur, and the tempo + * at that point. + */ + + class beat_grid_tag_t : public kaitai::kstruct { + + public: + + beat_grid_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~beat_grid_tag_t(); + + private: + uint32_t m__unnamed0; + uint32_t m__unnamed1; + uint32_t m_len_beats; + std::vector* m_beats; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + uint32_t _unnamed0() const { return m__unnamed0; } + uint32_t _unnamed1() const { return m__unnamed1; } + + /** + * The number of beat entries which follow. + */ + uint32_t len_beats() const { return m_len_beats; } + + /** + * The entries of the beat grid. + */ + std::vector* beats() const { return m_beats; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * A larger, colorful waveform preview image suitable for display + * above the touch strip for jumping to a track position on newer + * high-resolution players. + */ + + class wave_color_preview_tag_t : public kaitai::kstruct { + + public: + + wave_color_preview_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~wave_color_preview_tag_t(); + + private: + uint32_t m_len_entry_bytes; + uint32_t m_len_entries; + uint32_t m__unnamed2; + std::string m_entries; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + + /** + * The size of each entry, in bytes. Seems to always be 6. + */ + uint32_t len_entry_bytes() const { return m_len_entry_bytes; } + + /** + * The number of waveform data points, each of which takes one + * byte for each of six channels of information. + */ + uint32_t len_entries() const { return m_len_entries; } + uint32_t _unnamed2() const { return m__unnamed2; } + std::string entries() const { return m_entries; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * A larger waveform image suitable for scrolling along as a track + * plays. + */ + + class wave_scroll_tag_t : public kaitai::kstruct { + + public: + + wave_scroll_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~wave_scroll_tag_t(); + + private: + uint32_t m_len_entry_bytes; + uint32_t m_len_entries; + uint32_t m__unnamed2; + std::string m_entries; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + + /** + * The size of each entry, in bytes. Seems to always be 1. + */ + uint32_t len_entry_bytes() const { return m_len_entry_bytes; } + + /** + * The number of waveform data points, each of which takes one + * byte. + */ + uint32_t len_entries() const { return m_len_entries; } + uint32_t _unnamed2() const { return m__unnamed2; } + std::string entries() const { return m_entries; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * Stores an index allowing rapid seeking to particular times + * within a variable-bitrate audio file. + */ + + class vbr_tag_t : public kaitai::kstruct { + + public: + + vbr_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~vbr_tag_t(); + + private: + uint32_t m__unnamed0; + std::vector* m_index; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + uint32_t _unnamed0() const { return m__unnamed0; } + std::vector* index() const { return m_index; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * A cue list entry. Can either represent a memory cue or a loop. + */ + + class cue_entry_t : public kaitai::kstruct { + + public: + + cue_entry_t(kaitai::kstream* p__io, rekordbox_anlz_t::cue_tag_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~cue_entry_t(); + + private: + std::string m__unnamed0; + uint32_t m_len_header; + uint32_t m_len_entry; + uint32_t m_hot_cue; + cue_entry_status_t m_status; + uint32_t m__unnamed5; + uint16_t m_order_first; + uint16_t m_order_last; + cue_entry_type_t m_type; + std::string m__unnamed9; + uint32_t m_time; + uint32_t m_loop_time; + std::string m__unnamed12; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::cue_tag_t* m__parent; + + public: + std::string _unnamed0() const { return m__unnamed0; } + uint32_t len_header() const { return m_len_header; } + uint32_t len_entry() const { return m_len_entry; } + + /** + * If zero, this is an ordinary memory cue, otherwise this a + * hot cue with the specified number. + */ + uint32_t hot_cue() const { return m_hot_cue; } + + /** + * If zero, this entry should be ignored. + */ + cue_entry_status_t status() const { return m_status; } + uint32_t _unnamed5() const { return m__unnamed5; } + + /** + * @flesniak says: "0xffff for first cue, 0,1,3 for next" + */ + uint16_t order_first() const { return m_order_first; } + + /** + * @flesniak says: "1,2,3 for first, second, third cue, 0xffff for last" + */ + uint16_t order_last() const { return m_order_last; } + + /** + * Indicates whether this is a memory cue or a loop. + */ + cue_entry_type_t type() const { return m_type; } + std::string _unnamed9() const { return m__unnamed9; } + + /** + * The position, in milliseconds, at which the cue point lies + * in the track. + */ + uint32_t time() const { return m_time; } + + /** + * The position, in milliseconds, at which the player loops + * back to the cue time if this is a loop. + */ + uint32_t loop_time() const { return m_loop_time; } + std::string _unnamed12() const { return m__unnamed12; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::cue_tag_t* _parent() const { return m__parent; } + }; + + /** + * Describes an individual beat in a beat grid. + */ + + class beat_grid_beat_t : public kaitai::kstruct { + + public: + + beat_grid_beat_t(kaitai::kstream* p__io, rekordbox_anlz_t::beat_grid_tag_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~beat_grid_beat_t(); + + private: + uint16_t m_beat_number; + uint16_t m_tempo; + uint32_t m_time; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::beat_grid_tag_t* m__parent; + + public: + + /** + * The position of the beat within its musical bar, where beat 1 + * is the down beat. + */ + uint16_t beat_number() const { return m_beat_number; } + + /** + * The tempo at the time of this beat, in beats per minute, + * multiplied by 100. + */ + uint16_t tempo() const { return m_tempo; } + + /** + * The time, in milliseconds, at which this beat occurs when + * the track is played at normal (100%) pitch. + */ + uint32_t time() const { return m_time; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::beat_grid_tag_t* _parent() const { return m__parent; } + }; + + class unknown_tag_t : public kaitai::kstruct { + + public: + + unknown_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~unknown_tag_t(); + + private: + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * A type-tagged file section, identified by a four-byte magic + * sequence, with a header specifying its length, and whose payload + * is determined by the type tag. + */ + + class tagged_section_t : public kaitai::kstruct { + + public: + + tagged_section_t(kaitai::kstream* p__io, rekordbox_anlz_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~tagged_section_t(); + + private: + int32_t m_fourcc; + uint32_t m_len_header; + uint32_t m_len_tag; + kaitai::kstruct* m_body; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t* m__parent; + std::string m__raw_body; + kaitai::kstream* m__io__raw_body; + + public: + + /** + * A tag value indicating what kind of section this is. + */ + int32_t fourcc() const { return m_fourcc; } + + /** + * The size, in bytes, of the header portion of the tag. + */ + uint32_t len_header() const { return m_len_header; } + + /** + * The size, in bytes, of this entire tag, counting the header. + */ + uint32_t len_tag() const { return m_len_tag; } + kaitai::kstruct* body() const { return m_body; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t* _parent() const { return m__parent; } + std::string _raw_body() const { return m__raw_body; } + kaitai::kstream* _io__raw_body() const { return m__io__raw_body; } + }; + + /** + * A larger, colorful waveform image suitable for scrolling along + * as a track plays on newer high-resolution hardware. Also + * contains a higher-resolution blue/white waveform. + */ + + class wave_color_scroll_tag_t : public kaitai::kstruct { + + public: + + wave_color_scroll_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~wave_color_scroll_tag_t(); + + private: + uint32_t m_len_entry_bytes; + uint32_t m_len_entries; + uint32_t m__unnamed2; + std::string m_entries; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + + /** + * The size of each entry, in bytes. Seems to always be 2. + */ + uint32_t len_entry_bytes() const { return m_len_entry_bytes; } + + /** + * The number of columns of waveform data (this matches the + * non-color waveform length. + */ + uint32_t len_entries() const { return m_len_entries; } + uint32_t _unnamed2() const { return m__unnamed2; } + std::string entries() const { return m_entries; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * Stores either a list of ordinary memory cues and loop points, or + * a list of hot cues and loop points. + */ + + class cue_tag_t : public kaitai::kstruct { + + public: + + cue_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~cue_tag_t(); + + private: + cue_list_type_t m_type; + uint32_t m_len_cues; + uint32_t m_memory_count; + std::vector* m_cues; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + + /** + * Identifies whether this tag stores ordinary or hot cues. + */ + cue_list_type_t type() const { return m_type; } + + /** + * The length of the cue list. + */ + uint32_t len_cues() const { return m_len_cues; } + + /** + * Unsure what this means. + */ + uint32_t memory_count() const { return m_memory_count; } + std::vector* cues() const { return m_cues; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + +private: + std::string m__unnamed0; + uint32_t m_len_header; + uint32_t m_len_file; + std::string m__unnamed3; + std::vector* m_sections; + rekordbox_anlz_t* m__root; + kaitai::kstruct* m__parent; + +public: + std::string _unnamed0() const { return m__unnamed0; } + + /** + * The number of bytes of this header section. + */ + uint32_t len_header() const { return m_len_header; } + + /** + * The number of bytes in the entire file. + */ + uint32_t len_file() const { return m_len_file; } + std::string _unnamed3() const { return m__unnamed3; } + + /** + * The remainder of the file is a sequence of type-tagged sections, + * identified by a four-byte magic sequence. + */ + std::vector* sections() const { return m_sections; } + rekordbox_anlz_t* _root() const { return m__root; } + kaitai::kstruct* _parent() const { return m__parent; } +}; + +#endif // REKORDBOX_ANLZ_H_ From d38d966f46218b1e7f0374f2aa40be96c7177a50 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 30 Sep 2019 16:42:09 +1000 Subject: [PATCH 20/36] Remove DS_Store files --- .DS_Store | Bin 12292 -> 0 bytes lib/.DS_Store | Bin 8196 -> 0 bytes lib/mp3guessenc-0.27.4/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 lib/.DS_Store delete mode 100644 lib/mp3guessenc-0.27.4/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a4d976474f50c08de9e90a513ecaf8097f7eb1d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12292 zcmeHNYitx%6h3GBVD3zTDX$hvVWm(Dlv-M#1zOm)P$*y|w9rx>bsr<0xScIKyDd;_ z>NA@7!bf}|zV!zXei)Q!3`UJYl=w)aqEQny>JNYTPV|qSd++XcyKNC4h{8-Vb7tEe*Knb-4eg)1C%FFq8e)74~%oO*>Lr`T@Gq(latgDNNO}>L;qJ z+w^f=rqO*_XT%Iy_9i226K&JS+U=q427ky}A2v9@P0#jwgZ^mH8!>8v!A5hBVK@6j z!DxqNw?vFx5#G{jhr*FM(b%TzQQO#HP=B+_SV2;Di%GZR-E?5efzgxl+%spFlvh^Q z_Kz8>XRG7YiOpT6ZTf=7#%?nZY2WO%{e~4VtsSjqz_23b6{Znp*{-9k$?|p>{n=yn zF*&)K))MuZJELY~w_czq@?QDcbfzCswg%|AmxcmdG9WlwzDAw4@b`y`H?qTy-APZPs+9X~jfK)iOm7u#G{l&j`x9EmSKMeJyM5l4_HUOVlbQ8;e);l+4Q_`!U>`gQhu|4_5st$NcoR;-yYLZw0bjyb@HKn~-@}ja z6S`2tEF6uKaRwIPEG)#ixBwSo1y*7;*5fK{z%{rYH)AVq#cgPyg&_>1jaT6fcq86~ zH{*TyC?3FrcnF`x=j402xXtmcL6Z0pUpm7fCPv7H3~~249^x@Mn30D_{0#6xl$qEZxN5Cf$+wYzzDqMjJ-lT?ZWN^!v0lQD)+ zP&}RF+@d?6M^fkLXu#3HPz_{K(?IV3N6Cn~>g*&VgdR?x`=1)k6aI1ICrq3)edg?W zWffI5iCj4)Jsy<7C#UTqQ)RaH+X=lycj8@m zH$H^>rF|u`-_?Wd4WAw`wywso6%1KBsD;nyX<$Pb{Jw5GW`b}co)5pDfS*iZquI7b zLYa*bZzO8hdc#}z)p~EF-4{D=3Wb7+bFXicz@ZcBLRQ3UT1Hq16EvhQTbt=ihq`M+ zQ7d4J`!q_1!stP5dTvioc|}FByS%cu*xfU~Yyq7wE?d;w%aoj1^Q#*!@7TSk=c?YT z@0WwbSYU#&@;Y&RU|@Iy)iKw-@!<`iYo=v2tXaEm7+3iF*g65EhxK0?FoEl71s503 zU$oS-Qh?8K;~7f=(IYv8a2{cv-0H_@d0KwlS5ie30^14TEkG=DYcqy|ZVqoXw^ld| z=q{SAG2zC-mH3n}uY^g!E!>B6$OQD3z|0UNQ20JonYH}}g<+=Y9v zN5JmeaIf&kedLc1<0E(&kK)Vt3ciXb@m>5#nq|U6FHM-KIKhtx8EZToY#-KG>sPI= zIXA{SI2wq8P~uCbM1wO90E5DYn8CHFY}!b+ETe>|+32JCr;u$VMFW0G(SW9ZiUzSs ze6B{dA+MsR$y=8)Q5e!Q#3ftEDtP5cPnX;!tcur&^i0WJ&T2#rO9%0!x;o@8&dCZmN?IJi8BGT zob|uIvE2Sgwm2AMUEn3RGcvhzQF41q#)AZ)AFkwe_5YP4|Nkc~%sDz5a5QjUY5>z$G_I(np~aUlQb)?|d_pe*pkH3Puw^6aXYT7-}kYH)+hzjukBuSc(ae@&O89f=53SoE6cbA!HzA zAY>q9AY>q9;Qzn?eY07UR{8D=Z`g+ngbdu446ye@j1Gp$0OtkrR|j>x1R!K30B>|o zbpX$a1(*zQULa4UF-7%&K`I6%28wj@$5=RFGQfF(A{|hq0|s}-ph7`@cgl6EYg1+PQTpJZ44x<8Q#&IerAUOLw^LzX!`rfutV9bdN`GtZMP z!=#YWwB;D?bcb7T43CeK5)OW&F)cbf+meXa#}ch`^|9He=0t0K>`+T$Zcdge_B0*s zJ(W8*GkbpSsW-%WF?eS{=?&|DbFGWFy{S&I_(*8@- zekV7MX|;Db>o9t;+WFl;v3& z+Z>#>41Z!o_p+vASkCwf;?eP~)0UZ+%Oao41CE|E7s^VtEfrB!9WG?7GX={(r`1T3 z7^_OHmbFi$QG@RLh-vc!WDEsC0+5|n8V2gbJh6m>Z3=5jP;uMF$S%+{wqpteie73m3f z@8i1X_f4A4zq9I=q!H$6Hw@FLN$wMSF4rXE!R|{?NrFzo0F2TfRg-W5UWAw6BD@T5 z!TWF-K87#h3S5P+;T!lCet@6gXZQtvh2P;1_!IuZ2&%XlORy5}#(S_9>u^6NuniC6 z5j={=@Hjq-eb|p9sAColG||CnoWWTTyk z0Prh1S<@p*Dh(ddNcyHK?JiXwTql)YD`8t#zIEI79o4&Q8=6~>v@dP#4I8;kYu5{m z>q-cR3u;j?Lc|r(%AM*i<<8ZlKS&ZZ&q_>3+NOn_(aMV0U8<@vB}x2}NmoM>yUZ`^ z_L4V3J~ps&Ky8vq(pnL}R1?a*5{XyaAiQnLVVUHu6$|hEN>U;fYa97Ma)+X@XN@HI zU&MF`K7ubuhzoEHek2k8Mgshs#3x}nMoE0TunMbjAIYyC8?X^uuajU8_TodB!T}t@ zGzl?7LNxI-PT(2LqmKm=57sKhsZ`#v zU1yx)7iGPCsPJn287LA{h_d>>d-ebS*MFkn3_=D%25uPxSlpHB>Y(9QdsVYqJ4VMT zI#^?I^8$Gabu5J0c{xrPl;hkM3w+4{=LPaq%HgSmqLRPme*}cz|M2_I1bCabe*hdv B-RJ-S diff --git a/lib/mp3guessenc-0.27.4/.DS_Store b/lib/mp3guessenc-0.27.4/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Tue, 1 Oct 2019 10:05:09 +1000 Subject: [PATCH 21/36] Add rekordboxfeature.h to library.cpp --- src/library/library.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/library/library.cpp b/src/library/library.cpp index 2e66b2d5ada..f33423030c8 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -26,6 +26,7 @@ #include "library/autodj/autodjfeature.h" #include "library/playlistfeature.h" #include "library/traktor/traktorfeature.h" +#include "library/rekordbox/rekordboxfeature.h" #include "library/librarycontrol.h" #include "library/setlogfeature.h" #include "util/db/dbconnectionpooled.h" From 9b1225103bba476a10958d2acfa2f7353b2ad5b0 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Tue, 1 Oct 2019 15:13:08 +1000 Subject: [PATCH 22/36] Fix AnalyzerSilence overwriting found Rekordbox memory cue --- src/engine/controls/cuecontrol.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 1e1fd43a5f1..6e97eade65d 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -356,6 +356,9 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { CuePosition cuePoint; if (pLoadCue) { cuePoint.setPosition(pLoadCue->getPosition()); + // Update load cue source to ensure AnalyzerSilence does not + // overwrite it + cuePoint.setSource(pLoadCue->getSource()); // adjust the track cue accordingly pNewTrack->setCuePoint(cuePoint); } else { From 7baf504f1a0e7d07998e6db1700a172763994d2d Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Tue, 1 Oct 2019 15:15:03 +1000 Subject: [PATCH 23/36] clang-format --- src/engine/controls/cuecontrol.cpp | 262 ++++++++++------------------- 1 file changed, 85 insertions(+), 177 deletions(-) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 6e97eade65d..8fb0587f87b 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -4,15 +4,15 @@ #include #include -#include "engine/enginebuffer.h" #include "engine/controls/cuecontrol.h" +#include "engine/enginebuffer.h" +#include "control/controlindicator.h" #include "control/controlobject.h" #include "control/controlpushbutton.h" -#include "control/controlindicator.h" -#include "vinylcontrol/defs_vinylcontrol.h" -#include "util/sample.h" #include "util/color/color.h" +#include "util/sample.h" +#include "vinylcontrol/defs_vinylcontrol.h" // TODO: Convert these doubles to a standard enum // and convert elseif logic to switch statements @@ -24,18 +24,18 @@ static const double CUE_MODE_MIXXX_NO_BLINK = 4.0; static const double CUE_MODE_CUP = 5.0; CueControl::CueControl(QString group, - UserSettingsPointer pConfig) : - EngineControl(group, pConfig), - m_bPreviewing(false), - // m_pPlay->toBoo() -> engine play state - // m_pPlay->set(1.0) -> emulate play button press - m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))), - m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))), - m_iCurrentlyPreviewingHotcues(0), - m_bypassCueSetByPlay(false), - m_iNumHotCues(NUM_HOT_CUES), - m_pLoadedTrack(), - m_mutex(QMutex::Recursive) { + UserSettingsPointer pConfig) + : EngineControl(group, pConfig), + m_bPreviewing(false), + // m_pPlay->toBoo() -> engine play state + // m_pPlay->set(1.0) -> emulate play button press + m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))), + m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))), + m_iCurrentlyPreviewingHotcues(0), + m_bypassCueSetByPlay(false), + m_iNumHotCues(NUM_HOT_CUES), + m_pLoadedTrack(), + m_mutex(QMutex::Recursive) { // To silence a compiler warning about CUE_MODE_PIONEER. Q_UNUSED(CUE_MODE_PIONEER); createControls(); @@ -43,9 +43,7 @@ CueControl::CueControl(QString group, m_pTrackSamples = ControlObject::getControl(ConfigKey(group, "track_samples")); m_pQuantizeEnabled = ControlObject::getControl(ConfigKey(group, "quantize")); - connect(m_pQuantizeEnabled, &ControlObject::valueChanged, - this, &CueControl::quantizeChanged, - Qt::DirectConnection); + connect(m_pQuantizeEnabled, &ControlObject::valueChanged, this, &CueControl::quantizeChanged, Qt::DirectConnection); m_pPrevBeat = ControlObject::getControl(ConfigKey(group, "beat_prev")); m_pNextBeat = ControlObject::getControl(ConfigKey(group, "beat_next")); @@ -60,58 +58,38 @@ CueControl::CueControl(QString group, m_pCueSet = new ControlPushButton(ConfigKey(group, "cue_set")); m_pCueSet->setButtonMode(ControlPushButton::TRIGGER); - connect(m_pCueSet, &ControlObject::valueChanged, - this, &CueControl::cueSet, - Qt::DirectConnection); + connect(m_pCueSet, &ControlObject::valueChanged, this, &CueControl::cueSet, Qt::DirectConnection); m_pCueClear = new ControlPushButton(ConfigKey(group, "cue_clear")); m_pCueClear->setButtonMode(ControlPushButton::TRIGGER); - connect(m_pCueClear, &ControlObject::valueChanged, - this, &CueControl::cueClear, - Qt::DirectConnection); + connect(m_pCueClear, &ControlObject::valueChanged, this, &CueControl::cueClear, Qt::DirectConnection); m_pCueGoto = new ControlPushButton(ConfigKey(group, "cue_goto")); - connect(m_pCueGoto, &ControlObject::valueChanged, - this, &CueControl::cueGoto, - Qt::DirectConnection); + connect(m_pCueGoto, &ControlObject::valueChanged, this, &CueControl::cueGoto, Qt::DirectConnection); m_pCueGotoAndPlay = new ControlPushButton(ConfigKey(group, "cue_gotoandplay")); - connect(m_pCueGotoAndPlay, &ControlObject::valueChanged, - this, &CueControl::cueGotoAndPlay, - Qt::DirectConnection); + connect(m_pCueGotoAndPlay, &ControlObject::valueChanged, this, &CueControl::cueGotoAndPlay, Qt::DirectConnection); m_pCuePlay = new ControlPushButton(ConfigKey(group, "cue_play")); - connect(m_pCuePlay, &ControlObject::valueChanged, - this, &CueControl::cuePlay, - Qt::DirectConnection); + connect(m_pCuePlay, &ControlObject::valueChanged, this, &CueControl::cuePlay, Qt::DirectConnection); m_pCueGotoAndStop = new ControlPushButton(ConfigKey(group, "cue_gotoandstop")); - connect(m_pCueGotoAndStop, &ControlObject::valueChanged, - this, &CueControl::cueGotoAndStop, - Qt::DirectConnection); + connect(m_pCueGotoAndStop, &ControlObject::valueChanged, this, &CueControl::cueGotoAndStop, Qt::DirectConnection); m_pCuePreview = new ControlPushButton(ConfigKey(group, "cue_preview")); - connect(m_pCuePreview, &ControlObject::valueChanged, - this, &CueControl::cuePreview, - Qt::DirectConnection); + connect(m_pCuePreview, &ControlObject::valueChanged, this, &CueControl::cuePreview, Qt::DirectConnection); m_pCueCDJ = new ControlPushButton(ConfigKey(group, "cue_cdj")); - connect(m_pCueCDJ, &ControlObject::valueChanged, - this, &CueControl::cueCDJ, - Qt::DirectConnection); + connect(m_pCueCDJ, &ControlObject::valueChanged, this, &CueControl::cueCDJ, Qt::DirectConnection); m_pCueDefault = new ControlPushButton(ConfigKey(group, "cue_default")); - connect(m_pCueDefault, &ControlObject::valueChanged, - this, &CueControl::cueDefault, - Qt::DirectConnection); + connect(m_pCueDefault, &ControlObject::valueChanged, this, &CueControl::cueDefault, Qt::DirectConnection); m_pPlayStutter = new ControlPushButton(ConfigKey(group, "play_stutter")); - connect(m_pPlayStutter, &ControlObject::valueChanged, - this, &CueControl::playStutter, - Qt::DirectConnection); + connect(m_pPlayStutter, &ControlObject::valueChanged, this, &CueControl::playStutter, Qt::DirectConnection); m_pCueIndicator = new ControlIndicator(ConfigKey(group, "cue_indicator")); m_pPlayIndicator = new ControlIndicator(ConfigKey(group, "play_indicator")); @@ -123,19 +101,13 @@ CueControl::CueControl(QString group, m_pIntroStartEnabled->setReadOnly(); m_pIntroStartSet = new ControlPushButton(ConfigKey(group, "intro_start_set")); - connect(m_pIntroStartSet, &ControlObject::valueChanged, - this, &CueControl::introStartSet, - Qt::DirectConnection); + connect(m_pIntroStartSet, &ControlObject::valueChanged, this, &CueControl::introStartSet, Qt::DirectConnection); m_pIntroStartClear = new ControlPushButton(ConfigKey(group, "intro_start_clear")); - connect(m_pIntroStartClear, &ControlObject::valueChanged, - this, &CueControl::introStartClear, - Qt::DirectConnection); + connect(m_pIntroStartClear, &ControlObject::valueChanged, this, &CueControl::introStartClear, Qt::DirectConnection); m_pIntroStartActivate = new ControlPushButton(ConfigKey(group, "intro_start_activate")); - connect(m_pIntroStartActivate, &ControlObject::valueChanged, - this, &CueControl::introStartActivate, - Qt::DirectConnection); + connect(m_pIntroStartActivate, &ControlObject::valueChanged, this, &CueControl::introStartActivate, Qt::DirectConnection); m_pIntroEndPosition = new ControlObject(ConfigKey(group, "intro_end_position")); m_pIntroEndPosition->set(-1.0); @@ -144,19 +116,13 @@ CueControl::CueControl(QString group, m_pIntroEndEnabled->setReadOnly(); m_pIntroEndSet = new ControlPushButton(ConfigKey(group, "intro_end_set")); - connect(m_pIntroEndSet, &ControlObject::valueChanged, - this, &CueControl::introEndSet, - Qt::DirectConnection); + connect(m_pIntroEndSet, &ControlObject::valueChanged, this, &CueControl::introEndSet, Qt::DirectConnection); m_pIntroEndClear = new ControlPushButton(ConfigKey(group, "intro_end_clear")); - connect(m_pIntroEndClear, &ControlObject::valueChanged, - this, &CueControl::introEndClear, - Qt::DirectConnection); + connect(m_pIntroEndClear, &ControlObject::valueChanged, this, &CueControl::introEndClear, Qt::DirectConnection); m_pIntroEndActivate = new ControlPushButton(ConfigKey(group, "intro_end_activate")); - connect(m_pIntroEndActivate, &ControlObject::valueChanged, - this, &CueControl::introEndActivate, - Qt::DirectConnection); + connect(m_pIntroEndActivate, &ControlObject::valueChanged, this, &CueControl::introEndActivate, Qt::DirectConnection); m_pOutroStartPosition = new ControlObject(ConfigKey(group, "outro_start_position")); m_pOutroStartPosition->set(-1.0); @@ -165,19 +131,13 @@ CueControl::CueControl(QString group, m_pOutroStartEnabled->setReadOnly(); m_pOutroStartSet = new ControlPushButton(ConfigKey(group, "outro_start_set")); - connect(m_pOutroStartSet, &ControlObject::valueChanged, - this, &CueControl::outroStartSet, - Qt::DirectConnection); + connect(m_pOutroStartSet, &ControlObject::valueChanged, this, &CueControl::outroStartSet, Qt::DirectConnection); m_pOutroStartClear = new ControlPushButton(ConfigKey(group, "outro_start_clear")); - connect(m_pOutroStartClear, &ControlObject::valueChanged, - this, &CueControl::outroStartClear, - Qt::DirectConnection); + connect(m_pOutroStartClear, &ControlObject::valueChanged, this, &CueControl::outroStartClear, Qt::DirectConnection); m_pOutroStartActivate = new ControlPushButton(ConfigKey(group, "outro_start_activate")); - connect(m_pOutroStartActivate, &ControlObject::valueChanged, - this, &CueControl::outroStartActivate, - Qt::DirectConnection); + connect(m_pOutroStartActivate, &ControlObject::valueChanged, this, &CueControl::outroStartActivate, Qt::DirectConnection); m_pOutroEndPosition = new ControlObject(ConfigKey(group, "outro_end_position")); m_pOutroEndPosition->set(-1.0); @@ -186,19 +146,13 @@ CueControl::CueControl(QString group, m_pOutroEndEnabled->setReadOnly(); m_pOutroEndSet = new ControlPushButton(ConfigKey(group, "outro_end_set")); - connect(m_pOutroEndSet, &ControlObject::valueChanged, - this, &CueControl::outroEndSet, - Qt::DirectConnection); + connect(m_pOutroEndSet, &ControlObject::valueChanged, this, &CueControl::outroEndSet, Qt::DirectConnection); m_pOutroEndClear = new ControlPushButton(ConfigKey(group, "outro_end_clear")); - connect(m_pOutroEndClear, &ControlObject::valueChanged, - this, &CueControl::outroEndClear, - Qt::DirectConnection); + connect(m_pOutroEndClear, &ControlObject::valueChanged, this, &CueControl::outroEndClear, Qt::DirectConnection); m_pOutroEndActivate = new ControlPushButton(ConfigKey(group, "outro_end_activate")); - connect(m_pOutroEndActivate, &ControlObject::valueChanged, - this, &CueControl::outroEndActivate, - Qt::DirectConnection); + connect(m_pOutroEndActivate, &ControlObject::valueChanged, this, &CueControl::outroEndActivate, Qt::DirectConnection); m_pVinylControlEnabled = new ControlProxy(group, "vinylcontrol_enabled"); m_pVinylControlMode = new ControlProxy(group, "vinylcontrol_mode"); @@ -249,30 +203,14 @@ void CueControl::createControls() { for (int i = 0; i < m_iNumHotCues; ++i) { HotcueControl* pControl = new HotcueControl(getGroup(), i); - connect(pControl, &HotcueControl::hotcuePositionChanged, - this, &CueControl::hotcuePositionChanged, - Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueSet, - this, &CueControl::hotcueSet, - Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueGoto, - this, &CueControl::hotcueGoto, - Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueGotoAndPlay, - this, &CueControl::hotcueGotoAndPlay, - Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueGotoAndStop, - this, &CueControl::hotcueGotoAndStop, - Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueActivate, - this, &CueControl::hotcueActivate, - Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueActivatePreview, - this, &CueControl::hotcueActivatePreview, - Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueClear, - this, &CueControl::hotcueClear, - Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcuePositionChanged, this, &CueControl::hotcuePositionChanged, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueSet, this, &CueControl::hotcueSet, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueGoto, this, &CueControl::hotcueGoto, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueGotoAndPlay, this, &CueControl::hotcueGotoAndPlay, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueGotoAndStop, this, &CueControl::hotcueGotoAndStop, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueActivate, this, &CueControl::hotcueActivate, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueActivatePreview, this, &CueControl::hotcueActivatePreview, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueClear, this, &CueControl::hotcueClear, Qt::DirectConnection); m_hotcueControls.append(pControl); } @@ -283,9 +221,7 @@ void CueControl::attachCue(CuePointer pCue, HotcueControl* pControl) { return; } detachCue(pControl); - connect(pCue.get(), &Cue::updated, - this, &CueControl::cueUpdated, - Qt::DirectConnection); + connect(pCue.get(), &Cue::updated, this, &CueControl::cueUpdated, Qt::DirectConnection); pControl->setCue(pCue); } @@ -306,7 +242,7 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { QMutexLocker lock(&m_mutex); if (m_pLoadedTrack) { disconnect(m_pLoadedTrack.get(), 0, this, 0); - for (const auto& pControl: m_hotcueControls) { + for (const auto& pControl : m_hotcueControls) { detachCue(pControl); } @@ -328,16 +264,12 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { } m_pLoadedTrack = pNewTrack; - connect(m_pLoadedTrack.get(), &Track::cuesUpdated, - this, &CueControl::trackCuesUpdated, - Qt::DirectConnection); + connect(m_pLoadedTrack.get(), &Track::cuesUpdated, this, &CueControl::trackCuesUpdated, Qt::DirectConnection); - connect(m_pLoadedTrack.get(), &Track::beatsUpdated, - this, &CueControl::trackBeatsUpdated, - Qt::DirectConnection); + connect(m_pLoadedTrack.get(), &Track::beatsUpdated, this, &CueControl::trackBeatsUpdated, Qt::DirectConnection); CuePointer pLoadCue; - for (const CuePointer& pCue: m_pLoadedTrack->getCuePoints()) { + for (const CuePointer& pCue : m_pLoadedTrack->getCuePoints()) { if (pCue->getType() == Cue::LOAD) { DEBUG_ASSERT(!pLoadCue); pLoadCue = pCue; @@ -349,16 +281,15 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { // Use pNewTrack from now, because m_pLoadedTrack might have been reset // immediately after leaving the locking scope! - // Because of legacy, we store the (load) cue point twice and need to // sync both values. // The Cue::LOAD from getCuePoints() has the priority CuePosition cuePoint; if (pLoadCue) { cuePoint.setPosition(pLoadCue->getPosition()); - // Update load cue source to ensure AnalyzerSilence does not - // overwrite it - cuePoint.setSource(pLoadCue->getSource()); + // Update load cue source to ensure AnalyzerSilence does not + // overwrite it + cuePoint.setSource(pLoadCue->getSource()); // adjust the track cue accordingly pNewTrack->setCuePoint(cuePoint); } else { @@ -398,7 +329,7 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { // If cue recall is ON and main cue point is set, seek to it. seekExact(m_pCuePoint->get()); } else if (!(m_pVinylControlEnabled->get() && - m_pVinylControlMode->get() == MIXXX_VCMODE_ABSOLUTE)) { + m_pVinylControlMode->get() == MIXXX_VCMODE_ABSOLUTE)) { // Otherwise, seek to zero unless vinylcontrol is on and // set to absolute. This allows users to load tracks and // have the needle-drop be maintained. @@ -421,14 +352,14 @@ void CueControl::loadCuesFromTrack() { if (!m_pLoadedTrack) return; - for (const CuePointer& pCue: m_pLoadedTrack->getCuePoints()) { + for (const CuePointer& pCue : m_pLoadedTrack->getCuePoints()) { if (!pLoadCue && pCue->getType() == Cue::LOAD) { pLoadCue = pCue; } else if (pCue->getType() == Cue::INTRO) { - DEBUG_ASSERT(!pIntroCue); // There should be only one INTRO cue + DEBUG_ASSERT(!pIntroCue); // There should be only one INTRO cue pIntroCue = pCue; } else if (pCue->getType() == Cue::OUTRO) { - DEBUG_ASSERT(!pOutroCue); // There should be only one OUTRO cue + DEBUG_ASSERT(!pOutroCue); // There should be only one OUTRO cue pOutroCue = pCue; } else if (pCue->getType() == Cue::CUE && pCue->getHotCue() != -1) { int hotcue = pCue->getHotCue(); @@ -572,8 +503,7 @@ void CueControl::hotcueSet(HotcueControl* pControl, double v) { CuePointer pCue(m_pLoadedTrack->createAndAddCue()); double closestBeat = m_pClosestBeat->get(); double cuePosition = - (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? - closestBeat : getSampleOfTrack().current; + (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? closestBeat : getSampleOfTrack().current; pCue->setPosition(cuePosition); pCue->setHotCue(hotcue); pCue->setLabel(""); @@ -592,7 +522,7 @@ void CueControl::hotcueSet(HotcueControl* pControl, double v) { // potentially invalid for vinyl control? bool playing = m_pPlay->toBool(); if (!playing && m_pQuantizeEnabled->toBool()) { - lock.unlock(); // prevent deadlock. + lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(cuePosition); } @@ -804,7 +734,7 @@ void CueControl::hintReader(HintVector* pHintList) { // this is called from the engine thread // it is no locking required, because m_hotcueControl is filled during the // constructor and getPosition()->get() is a ControlObject - for (const auto& pControl: m_hotcueControls) { + for (const auto& pControl : m_hotcueControls) { double position = pControl->getPosition(); if (position != -1) { cue_hint.frame = SampleUtil::floorPlayPosToFrame(position); @@ -823,8 +753,7 @@ void CueControl::cueSet(double v) { QMutexLocker lock(&m_mutex); double closestBeat = m_pClosestBeat->get(); - double cue = (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? - closestBeat : getSampleOfTrack().current; + double cue = (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? closestBeat : getSampleOfTrack().current; m_pCuePoint->set(cue); TrackPointer pLoadedTrack = m_pLoadedTrack; lock.unlock(); @@ -850,8 +779,7 @@ void CueControl::cueClear(double v) { } } -void CueControl::cueGoto(double v) -{ +void CueControl::cueGoto(double v) { if (!v) return; @@ -865,9 +793,9 @@ void CueControl::cueGoto(double v) seekAbs(cuePoint); } -void CueControl::cueGotoAndPlay(double v) -{ - if (!v) return; +void CueControl::cueGotoAndPlay(double v) { + if (!v) + return; cueGoto(v); QMutexLocker lock(&m_mutex); // Start playing if not already @@ -881,8 +809,7 @@ void CueControl::cueGotoAndPlay(double v) } } -void CueControl::cueGotoAndStop(double v) -{ +void CueControl::cueGotoAndStop(double v) { if (!v) return; @@ -896,8 +823,7 @@ void CueControl::cueGotoAndStop(double v) seekExact(cuePoint); } -void CueControl::cuePreview(double v) -{ +void CueControl::cuePreview(double v) { QMutexLocker lock(&m_mutex); if (v) { @@ -964,7 +890,7 @@ void CueControl::cueCDJ(double v) { // If quantize is enabled, jump to the cue point since it's not // necessarily where we currently are if (m_pQuantizeEnabled->toBool()) { - lock.unlock(); // prevent deadlock. + lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(m_pCuePoint->get()); } @@ -1039,7 +965,6 @@ void CueControl::cuePlay(double v) { // If not freely playing (i.e. stopped or platter IS being touched), press to go to cue and stop. // On release, start playing from cue point. - QMutexLocker lock(&m_mutex); const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching(); TrackAt trackAt = getTrackAt(); @@ -1063,12 +988,12 @@ void CueControl::cuePlay(double v) { // If quantize is enabled, jump to the cue point since it's not // necessarily where we currently are if (m_pQuantizeEnabled->toBool()) { - lock.unlock(); // prevent deadlock. + lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(m_pCuePoint->get()); } } - } else if (trackAt == TrackAt::Cue){ + } else if (trackAt == TrackAt::Cue) { m_bPreviewing = false; m_pPlay->set(1.0); lock.unlock(); @@ -1442,9 +1367,9 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) QMutexLocker lock(&m_mutex); double cueMode = m_pCueMode->get(); if ((cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) && - newPlay && playPossible && - !m_pPlay->toBool() && - !m_bypassCueSetByPlay) { + newPlay && playPossible && + !m_pPlay->toBool() && + !m_bypassCueSetByPlay) { // in Denon mode each play from pause moves the cue point // if not previewing cueSet(1.0); @@ -1487,7 +1412,7 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } } else if (cueMode == CUE_MODE_MIXXX || cueMode == CUE_MODE_MIXXX_NO_BLINK || - cueMode == CUE_MODE_NUMARK) { + cueMode == CUE_MODE_NUMARK) { m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); } else { // Flashing indicates that play is possible in Pioneer mode @@ -1679,12 +1604,11 @@ SeekOnLoadMode CueControl::getSeekOnLoadMode() { return seekOnLoadModeFromDouble(m_pSeekOnLoadMode->get()); } - ConfigKey HotcueControl::keyForControl(int hotcue, const char* name) { ConfigKey key; key.group = m_group; // Add one to hotcue so that we don't have a hotcue_0 - key.item = QLatin1String("hotcue_") % QString::number(hotcue+1) % "_" % name; + key.item = QLatin1String("hotcue_") % QString::number(hotcue + 1) % "_" % name; return key; } @@ -1695,9 +1619,7 @@ HotcueControl::HotcueControl(QString group, int i) m_bPreviewing(false), m_previewingPosition(-1) { m_hotcuePosition = new ControlObject(keyForControl(i, "position")); - connect(m_hotcuePosition, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcuePositionChanged, - Qt::DirectConnection); + connect(m_hotcuePosition, &ControlObject::valueChanged, this, &HotcueControl::slotHotcuePositionChanged, Qt::DirectConnection); m_hotcuePosition->set(-1); m_hotcueEnabled = new ControlObject(keyForControl(i, "enabled")); @@ -1712,39 +1634,25 @@ HotcueControl::HotcueControl(QString group, int i) Qt::DirectConnection); m_hotcueSet = new ControlPushButton(keyForControl(i, "set")); - connect(m_hotcueSet, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcueSet, - Qt::DirectConnection); + connect(m_hotcueSet, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueSet, Qt::DirectConnection); m_hotcueGoto = new ControlPushButton(keyForControl(i, "goto")); - connect(m_hotcueGoto, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcueGoto, - Qt::DirectConnection); + connect(m_hotcueGoto, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueGoto, Qt::DirectConnection); m_hotcueGotoAndPlay = new ControlPushButton(keyForControl(i, "gotoandplay")); - connect(m_hotcueGotoAndPlay, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcueGotoAndPlay, - Qt::DirectConnection); + connect(m_hotcueGotoAndPlay, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueGotoAndPlay, Qt::DirectConnection); m_hotcueGotoAndStop = new ControlPushButton(keyForControl(i, "gotoandstop")); - connect(m_hotcueGotoAndStop, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcueGotoAndStop, - Qt::DirectConnection); + connect(m_hotcueGotoAndStop, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueGotoAndStop, Qt::DirectConnection); m_hotcueActivate = new ControlPushButton(keyForControl(i, "activate")); - connect(m_hotcueActivate, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcueActivate, - Qt::DirectConnection); + connect(m_hotcueActivate, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueActivate, Qt::DirectConnection); m_hotcueActivatePreview = new ControlPushButton(keyForControl(i, "activate_preview")); - connect(m_hotcueActivatePreview, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcueActivatePreview, - Qt::DirectConnection); + connect(m_hotcueActivatePreview, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueActivatePreview, Qt::DirectConnection); m_hotcueClear = new ControlPushButton(keyForControl(i, "clear")); - connect(m_hotcueClear, &ControlObject::valueChanged, - this, &HotcueControl::slotHotcueClear, - Qt::DirectConnection); + connect(m_hotcueClear, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueClear, Qt::DirectConnection); } HotcueControl::~HotcueControl() { From 919c7d214fabb8ee9e3786599bfba84f4bdf0463 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Tue, 1 Oct 2019 15:33:06 +1000 Subject: [PATCH 24/36] Use QStringList for HTML --- src/library/rekordbox/rekordboxfeature.cpp | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 68f5323c2e0..7cc76e57f3b 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -179,7 +179,7 @@ QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { } else if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_long_utf16be_t* longUtf16beString = static_cast(deviceString->body()); - QTextCodec *codec = QTextCodec::codecForName("UTF-16BE"); + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); std::string utf16be = longUtf16beString->text(); return codec->toUnicode(QByteArray(utf16be.c_str(), utf16be.length())); } @@ -708,7 +708,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, QStrin // Ensure no offset times are less than 1 if (time < 1) { time = 1; - } + } double position = sampleRateFrames * static_cast(time); switch (cuesTag->type()) { @@ -979,24 +979,24 @@ TreeItemModel* RekordboxFeature::getChildModel() { QString RekordboxFeature::formatRootViewHtml() const { QString title = tr("Rekordbox"); QString summary = tr("Reads the following from Rekordbox prepared removable devices:"); - QString item1 = tr("Playlists"); - QString item2 = tr("Folders"); - QString item3 = tr("First memory cue"); - QString item4 = tr("First memory loop"); - QString item5 = tr("Hot cues"); - QString item6 = tr("Beatgrids"); + QStringList items; + + items + << tr("Playlists") + << tr("Folders") + << tr("First memory cue") + << tr("First memory loop") + << tr("Hot cues") + << tr("Beatgrids"); QString html; QString refreshLink = tr("Check for attached Rekordbox devices (refresh)"); html.append(QString("

%1

").arg(title)); html.append(QString("

%1

").arg(summary)); html.append(QString("
    ")); - html.append(QString("
  • %1
  • ").arg(item1)); - html.append(QString("
  • %1
  • ").arg(item2)); - html.append(QString("
  • %1
  • ").arg(item3)); - html.append(QString("
  • %1
  • ").arg(item4)); - html.append(QString("
  • %1
  • ").arg(item5)); - html.append(QString("
  • %1
  • ").arg(item6)); + for (int index = 0; index < items.size(); ++index) { + html.append(QString("
  • %1
  • ").arg(items[index])); + } html.append(QString("
")); //Colorize links in lighter blue, instead of QT default dark blue. From c11daf835262be642997917d6d9df7a61df5e6ef Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Tue, 1 Oct 2019 16:59:49 +1000 Subject: [PATCH 25/36] Update for HTML loop --- src/library/rekordbox/rekordboxfeature.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 7cc76e57f3b..f2e481cb10e 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -994,8 +994,8 @@ QString RekordboxFeature::formatRootViewHtml() const { html.append(QString("

%1

").arg(title)); html.append(QString("

%1

").arg(summary)); html.append(QString("
    ")); - for (int index = 0; index < items.size(); ++index) { - html.append(QString("
  • %1
  • ").arg(items[index])); + for (const auto& item : items) { + html.append(QString("
  • %1
  • ").arg(item)); } html.append(QString("
")); From 90c44b059b2b137c2723ca2e325d2c39dc6c797c Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Wed, 2 Oct 2019 09:21:37 +1000 Subject: [PATCH 26/36] Revert cuecontrol.cpp --- src/engine/controls/cuecontrol.cpp | 259 +++++++++++++++++++---------- 1 file changed, 174 insertions(+), 85 deletions(-) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 8fb0587f87b..1e1fd43a5f1 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -4,15 +4,15 @@ #include #include -#include "engine/controls/cuecontrol.h" #include "engine/enginebuffer.h" +#include "engine/controls/cuecontrol.h" -#include "control/controlindicator.h" #include "control/controlobject.h" #include "control/controlpushbutton.h" -#include "util/color/color.h" -#include "util/sample.h" +#include "control/controlindicator.h" #include "vinylcontrol/defs_vinylcontrol.h" +#include "util/sample.h" +#include "util/color/color.h" // TODO: Convert these doubles to a standard enum // and convert elseif logic to switch statements @@ -24,18 +24,18 @@ static const double CUE_MODE_MIXXX_NO_BLINK = 4.0; static const double CUE_MODE_CUP = 5.0; CueControl::CueControl(QString group, - UserSettingsPointer pConfig) - : EngineControl(group, pConfig), - m_bPreviewing(false), - // m_pPlay->toBoo() -> engine play state - // m_pPlay->set(1.0) -> emulate play button press - m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))), - m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))), - m_iCurrentlyPreviewingHotcues(0), - m_bypassCueSetByPlay(false), - m_iNumHotCues(NUM_HOT_CUES), - m_pLoadedTrack(), - m_mutex(QMutex::Recursive) { + UserSettingsPointer pConfig) : + EngineControl(group, pConfig), + m_bPreviewing(false), + // m_pPlay->toBoo() -> engine play state + // m_pPlay->set(1.0) -> emulate play button press + m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))), + m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))), + m_iCurrentlyPreviewingHotcues(0), + m_bypassCueSetByPlay(false), + m_iNumHotCues(NUM_HOT_CUES), + m_pLoadedTrack(), + m_mutex(QMutex::Recursive) { // To silence a compiler warning about CUE_MODE_PIONEER. Q_UNUSED(CUE_MODE_PIONEER); createControls(); @@ -43,7 +43,9 @@ CueControl::CueControl(QString group, m_pTrackSamples = ControlObject::getControl(ConfigKey(group, "track_samples")); m_pQuantizeEnabled = ControlObject::getControl(ConfigKey(group, "quantize")); - connect(m_pQuantizeEnabled, &ControlObject::valueChanged, this, &CueControl::quantizeChanged, Qt::DirectConnection); + connect(m_pQuantizeEnabled, &ControlObject::valueChanged, + this, &CueControl::quantizeChanged, + Qt::DirectConnection); m_pPrevBeat = ControlObject::getControl(ConfigKey(group, "beat_prev")); m_pNextBeat = ControlObject::getControl(ConfigKey(group, "beat_next")); @@ -58,38 +60,58 @@ CueControl::CueControl(QString group, m_pCueSet = new ControlPushButton(ConfigKey(group, "cue_set")); m_pCueSet->setButtonMode(ControlPushButton::TRIGGER); - connect(m_pCueSet, &ControlObject::valueChanged, this, &CueControl::cueSet, Qt::DirectConnection); + connect(m_pCueSet, &ControlObject::valueChanged, + this, &CueControl::cueSet, + Qt::DirectConnection); m_pCueClear = new ControlPushButton(ConfigKey(group, "cue_clear")); m_pCueClear->setButtonMode(ControlPushButton::TRIGGER); - connect(m_pCueClear, &ControlObject::valueChanged, this, &CueControl::cueClear, Qt::DirectConnection); + connect(m_pCueClear, &ControlObject::valueChanged, + this, &CueControl::cueClear, + Qt::DirectConnection); m_pCueGoto = new ControlPushButton(ConfigKey(group, "cue_goto")); - connect(m_pCueGoto, &ControlObject::valueChanged, this, &CueControl::cueGoto, Qt::DirectConnection); + connect(m_pCueGoto, &ControlObject::valueChanged, + this, &CueControl::cueGoto, + Qt::DirectConnection); m_pCueGotoAndPlay = new ControlPushButton(ConfigKey(group, "cue_gotoandplay")); - connect(m_pCueGotoAndPlay, &ControlObject::valueChanged, this, &CueControl::cueGotoAndPlay, Qt::DirectConnection); + connect(m_pCueGotoAndPlay, &ControlObject::valueChanged, + this, &CueControl::cueGotoAndPlay, + Qt::DirectConnection); m_pCuePlay = new ControlPushButton(ConfigKey(group, "cue_play")); - connect(m_pCuePlay, &ControlObject::valueChanged, this, &CueControl::cuePlay, Qt::DirectConnection); + connect(m_pCuePlay, &ControlObject::valueChanged, + this, &CueControl::cuePlay, + Qt::DirectConnection); m_pCueGotoAndStop = new ControlPushButton(ConfigKey(group, "cue_gotoandstop")); - connect(m_pCueGotoAndStop, &ControlObject::valueChanged, this, &CueControl::cueGotoAndStop, Qt::DirectConnection); + connect(m_pCueGotoAndStop, &ControlObject::valueChanged, + this, &CueControl::cueGotoAndStop, + Qt::DirectConnection); m_pCuePreview = new ControlPushButton(ConfigKey(group, "cue_preview")); - connect(m_pCuePreview, &ControlObject::valueChanged, this, &CueControl::cuePreview, Qt::DirectConnection); + connect(m_pCuePreview, &ControlObject::valueChanged, + this, &CueControl::cuePreview, + Qt::DirectConnection); m_pCueCDJ = new ControlPushButton(ConfigKey(group, "cue_cdj")); - connect(m_pCueCDJ, &ControlObject::valueChanged, this, &CueControl::cueCDJ, Qt::DirectConnection); + connect(m_pCueCDJ, &ControlObject::valueChanged, + this, &CueControl::cueCDJ, + Qt::DirectConnection); m_pCueDefault = new ControlPushButton(ConfigKey(group, "cue_default")); - connect(m_pCueDefault, &ControlObject::valueChanged, this, &CueControl::cueDefault, Qt::DirectConnection); + connect(m_pCueDefault, &ControlObject::valueChanged, + this, &CueControl::cueDefault, + Qt::DirectConnection); m_pPlayStutter = new ControlPushButton(ConfigKey(group, "play_stutter")); - connect(m_pPlayStutter, &ControlObject::valueChanged, this, &CueControl::playStutter, Qt::DirectConnection); + connect(m_pPlayStutter, &ControlObject::valueChanged, + this, &CueControl::playStutter, + Qt::DirectConnection); m_pCueIndicator = new ControlIndicator(ConfigKey(group, "cue_indicator")); m_pPlayIndicator = new ControlIndicator(ConfigKey(group, "play_indicator")); @@ -101,13 +123,19 @@ CueControl::CueControl(QString group, m_pIntroStartEnabled->setReadOnly(); m_pIntroStartSet = new ControlPushButton(ConfigKey(group, "intro_start_set")); - connect(m_pIntroStartSet, &ControlObject::valueChanged, this, &CueControl::introStartSet, Qt::DirectConnection); + connect(m_pIntroStartSet, &ControlObject::valueChanged, + this, &CueControl::introStartSet, + Qt::DirectConnection); m_pIntroStartClear = new ControlPushButton(ConfigKey(group, "intro_start_clear")); - connect(m_pIntroStartClear, &ControlObject::valueChanged, this, &CueControl::introStartClear, Qt::DirectConnection); + connect(m_pIntroStartClear, &ControlObject::valueChanged, + this, &CueControl::introStartClear, + Qt::DirectConnection); m_pIntroStartActivate = new ControlPushButton(ConfigKey(group, "intro_start_activate")); - connect(m_pIntroStartActivate, &ControlObject::valueChanged, this, &CueControl::introStartActivate, Qt::DirectConnection); + connect(m_pIntroStartActivate, &ControlObject::valueChanged, + this, &CueControl::introStartActivate, + Qt::DirectConnection); m_pIntroEndPosition = new ControlObject(ConfigKey(group, "intro_end_position")); m_pIntroEndPosition->set(-1.0); @@ -116,13 +144,19 @@ CueControl::CueControl(QString group, m_pIntroEndEnabled->setReadOnly(); m_pIntroEndSet = new ControlPushButton(ConfigKey(group, "intro_end_set")); - connect(m_pIntroEndSet, &ControlObject::valueChanged, this, &CueControl::introEndSet, Qt::DirectConnection); + connect(m_pIntroEndSet, &ControlObject::valueChanged, + this, &CueControl::introEndSet, + Qt::DirectConnection); m_pIntroEndClear = new ControlPushButton(ConfigKey(group, "intro_end_clear")); - connect(m_pIntroEndClear, &ControlObject::valueChanged, this, &CueControl::introEndClear, Qt::DirectConnection); + connect(m_pIntroEndClear, &ControlObject::valueChanged, + this, &CueControl::introEndClear, + Qt::DirectConnection); m_pIntroEndActivate = new ControlPushButton(ConfigKey(group, "intro_end_activate")); - connect(m_pIntroEndActivate, &ControlObject::valueChanged, this, &CueControl::introEndActivate, Qt::DirectConnection); + connect(m_pIntroEndActivate, &ControlObject::valueChanged, + this, &CueControl::introEndActivate, + Qt::DirectConnection); m_pOutroStartPosition = new ControlObject(ConfigKey(group, "outro_start_position")); m_pOutroStartPosition->set(-1.0); @@ -131,13 +165,19 @@ CueControl::CueControl(QString group, m_pOutroStartEnabled->setReadOnly(); m_pOutroStartSet = new ControlPushButton(ConfigKey(group, "outro_start_set")); - connect(m_pOutroStartSet, &ControlObject::valueChanged, this, &CueControl::outroStartSet, Qt::DirectConnection); + connect(m_pOutroStartSet, &ControlObject::valueChanged, + this, &CueControl::outroStartSet, + Qt::DirectConnection); m_pOutroStartClear = new ControlPushButton(ConfigKey(group, "outro_start_clear")); - connect(m_pOutroStartClear, &ControlObject::valueChanged, this, &CueControl::outroStartClear, Qt::DirectConnection); + connect(m_pOutroStartClear, &ControlObject::valueChanged, + this, &CueControl::outroStartClear, + Qt::DirectConnection); m_pOutroStartActivate = new ControlPushButton(ConfigKey(group, "outro_start_activate")); - connect(m_pOutroStartActivate, &ControlObject::valueChanged, this, &CueControl::outroStartActivate, Qt::DirectConnection); + connect(m_pOutroStartActivate, &ControlObject::valueChanged, + this, &CueControl::outroStartActivate, + Qt::DirectConnection); m_pOutroEndPosition = new ControlObject(ConfigKey(group, "outro_end_position")); m_pOutroEndPosition->set(-1.0); @@ -146,13 +186,19 @@ CueControl::CueControl(QString group, m_pOutroEndEnabled->setReadOnly(); m_pOutroEndSet = new ControlPushButton(ConfigKey(group, "outro_end_set")); - connect(m_pOutroEndSet, &ControlObject::valueChanged, this, &CueControl::outroEndSet, Qt::DirectConnection); + connect(m_pOutroEndSet, &ControlObject::valueChanged, + this, &CueControl::outroEndSet, + Qt::DirectConnection); m_pOutroEndClear = new ControlPushButton(ConfigKey(group, "outro_end_clear")); - connect(m_pOutroEndClear, &ControlObject::valueChanged, this, &CueControl::outroEndClear, Qt::DirectConnection); + connect(m_pOutroEndClear, &ControlObject::valueChanged, + this, &CueControl::outroEndClear, + Qt::DirectConnection); m_pOutroEndActivate = new ControlPushButton(ConfigKey(group, "outro_end_activate")); - connect(m_pOutroEndActivate, &ControlObject::valueChanged, this, &CueControl::outroEndActivate, Qt::DirectConnection); + connect(m_pOutroEndActivate, &ControlObject::valueChanged, + this, &CueControl::outroEndActivate, + Qt::DirectConnection); m_pVinylControlEnabled = new ControlProxy(group, "vinylcontrol_enabled"); m_pVinylControlMode = new ControlProxy(group, "vinylcontrol_mode"); @@ -203,14 +249,30 @@ void CueControl::createControls() { for (int i = 0; i < m_iNumHotCues; ++i) { HotcueControl* pControl = new HotcueControl(getGroup(), i); - connect(pControl, &HotcueControl::hotcuePositionChanged, this, &CueControl::hotcuePositionChanged, Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueSet, this, &CueControl::hotcueSet, Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueGoto, this, &CueControl::hotcueGoto, Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueGotoAndPlay, this, &CueControl::hotcueGotoAndPlay, Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueGotoAndStop, this, &CueControl::hotcueGotoAndStop, Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueActivate, this, &CueControl::hotcueActivate, Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueActivatePreview, this, &CueControl::hotcueActivatePreview, Qt::DirectConnection); - connect(pControl, &HotcueControl::hotcueClear, this, &CueControl::hotcueClear, Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcuePositionChanged, + this, &CueControl::hotcuePositionChanged, + Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueSet, + this, &CueControl::hotcueSet, + Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueGoto, + this, &CueControl::hotcueGoto, + Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueGotoAndPlay, + this, &CueControl::hotcueGotoAndPlay, + Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueGotoAndStop, + this, &CueControl::hotcueGotoAndStop, + Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueActivate, + this, &CueControl::hotcueActivate, + Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueActivatePreview, + this, &CueControl::hotcueActivatePreview, + Qt::DirectConnection); + connect(pControl, &HotcueControl::hotcueClear, + this, &CueControl::hotcueClear, + Qt::DirectConnection); m_hotcueControls.append(pControl); } @@ -221,7 +283,9 @@ void CueControl::attachCue(CuePointer pCue, HotcueControl* pControl) { return; } detachCue(pControl); - connect(pCue.get(), &Cue::updated, this, &CueControl::cueUpdated, Qt::DirectConnection); + connect(pCue.get(), &Cue::updated, + this, &CueControl::cueUpdated, + Qt::DirectConnection); pControl->setCue(pCue); } @@ -242,7 +306,7 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { QMutexLocker lock(&m_mutex); if (m_pLoadedTrack) { disconnect(m_pLoadedTrack.get(), 0, this, 0); - for (const auto& pControl : m_hotcueControls) { + for (const auto& pControl: m_hotcueControls) { detachCue(pControl); } @@ -264,12 +328,16 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { } m_pLoadedTrack = pNewTrack; - connect(m_pLoadedTrack.get(), &Track::cuesUpdated, this, &CueControl::trackCuesUpdated, Qt::DirectConnection); + connect(m_pLoadedTrack.get(), &Track::cuesUpdated, + this, &CueControl::trackCuesUpdated, + Qt::DirectConnection); - connect(m_pLoadedTrack.get(), &Track::beatsUpdated, this, &CueControl::trackBeatsUpdated, Qt::DirectConnection); + connect(m_pLoadedTrack.get(), &Track::beatsUpdated, + this, &CueControl::trackBeatsUpdated, + Qt::DirectConnection); CuePointer pLoadCue; - for (const CuePointer& pCue : m_pLoadedTrack->getCuePoints()) { + for (const CuePointer& pCue: m_pLoadedTrack->getCuePoints()) { if (pCue->getType() == Cue::LOAD) { DEBUG_ASSERT(!pLoadCue); pLoadCue = pCue; @@ -281,15 +349,13 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { // Use pNewTrack from now, because m_pLoadedTrack might have been reset // immediately after leaving the locking scope! + // Because of legacy, we store the (load) cue point twice and need to // sync both values. // The Cue::LOAD from getCuePoints() has the priority CuePosition cuePoint; if (pLoadCue) { cuePoint.setPosition(pLoadCue->getPosition()); - // Update load cue source to ensure AnalyzerSilence does not - // overwrite it - cuePoint.setSource(pLoadCue->getSource()); // adjust the track cue accordingly pNewTrack->setCuePoint(cuePoint); } else { @@ -329,7 +395,7 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { // If cue recall is ON and main cue point is set, seek to it. seekExact(m_pCuePoint->get()); } else if (!(m_pVinylControlEnabled->get() && - m_pVinylControlMode->get() == MIXXX_VCMODE_ABSOLUTE)) { + m_pVinylControlMode->get() == MIXXX_VCMODE_ABSOLUTE)) { // Otherwise, seek to zero unless vinylcontrol is on and // set to absolute. This allows users to load tracks and // have the needle-drop be maintained. @@ -352,14 +418,14 @@ void CueControl::loadCuesFromTrack() { if (!m_pLoadedTrack) return; - for (const CuePointer& pCue : m_pLoadedTrack->getCuePoints()) { + for (const CuePointer& pCue: m_pLoadedTrack->getCuePoints()) { if (!pLoadCue && pCue->getType() == Cue::LOAD) { pLoadCue = pCue; } else if (pCue->getType() == Cue::INTRO) { - DEBUG_ASSERT(!pIntroCue); // There should be only one INTRO cue + DEBUG_ASSERT(!pIntroCue); // There should be only one INTRO cue pIntroCue = pCue; } else if (pCue->getType() == Cue::OUTRO) { - DEBUG_ASSERT(!pOutroCue); // There should be only one OUTRO cue + DEBUG_ASSERT(!pOutroCue); // There should be only one OUTRO cue pOutroCue = pCue; } else if (pCue->getType() == Cue::CUE && pCue->getHotCue() != -1) { int hotcue = pCue->getHotCue(); @@ -503,7 +569,8 @@ void CueControl::hotcueSet(HotcueControl* pControl, double v) { CuePointer pCue(m_pLoadedTrack->createAndAddCue()); double closestBeat = m_pClosestBeat->get(); double cuePosition = - (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? closestBeat : getSampleOfTrack().current; + (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? + closestBeat : getSampleOfTrack().current; pCue->setPosition(cuePosition); pCue->setHotCue(hotcue); pCue->setLabel(""); @@ -522,7 +589,7 @@ void CueControl::hotcueSet(HotcueControl* pControl, double v) { // potentially invalid for vinyl control? bool playing = m_pPlay->toBool(); if (!playing && m_pQuantizeEnabled->toBool()) { - lock.unlock(); // prevent deadlock. + lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(cuePosition); } @@ -734,7 +801,7 @@ void CueControl::hintReader(HintVector* pHintList) { // this is called from the engine thread // it is no locking required, because m_hotcueControl is filled during the // constructor and getPosition()->get() is a ControlObject - for (const auto& pControl : m_hotcueControls) { + for (const auto& pControl: m_hotcueControls) { double position = pControl->getPosition(); if (position != -1) { cue_hint.frame = SampleUtil::floorPlayPosToFrame(position); @@ -753,7 +820,8 @@ void CueControl::cueSet(double v) { QMutexLocker lock(&m_mutex); double closestBeat = m_pClosestBeat->get(); - double cue = (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? closestBeat : getSampleOfTrack().current; + double cue = (m_pQuantizeEnabled->toBool() && closestBeat != -1) ? + closestBeat : getSampleOfTrack().current; m_pCuePoint->set(cue); TrackPointer pLoadedTrack = m_pLoadedTrack; lock.unlock(); @@ -779,7 +847,8 @@ void CueControl::cueClear(double v) { } } -void CueControl::cueGoto(double v) { +void CueControl::cueGoto(double v) +{ if (!v) return; @@ -793,9 +862,9 @@ void CueControl::cueGoto(double v) { seekAbs(cuePoint); } -void CueControl::cueGotoAndPlay(double v) { - if (!v) - return; +void CueControl::cueGotoAndPlay(double v) +{ + if (!v) return; cueGoto(v); QMutexLocker lock(&m_mutex); // Start playing if not already @@ -809,7 +878,8 @@ void CueControl::cueGotoAndPlay(double v) { } } -void CueControl::cueGotoAndStop(double v) { +void CueControl::cueGotoAndStop(double v) +{ if (!v) return; @@ -823,7 +893,8 @@ void CueControl::cueGotoAndStop(double v) { seekExact(cuePoint); } -void CueControl::cuePreview(double v) { +void CueControl::cuePreview(double v) +{ QMutexLocker lock(&m_mutex); if (v) { @@ -890,7 +961,7 @@ void CueControl::cueCDJ(double v) { // If quantize is enabled, jump to the cue point since it's not // necessarily where we currently are if (m_pQuantizeEnabled->toBool()) { - lock.unlock(); // prevent deadlock. + lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(m_pCuePoint->get()); } @@ -965,6 +1036,7 @@ void CueControl::cuePlay(double v) { // If not freely playing (i.e. stopped or platter IS being touched), press to go to cue and stop. // On release, start playing from cue point. + QMutexLocker lock(&m_mutex); const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching(); TrackAt trackAt = getTrackAt(); @@ -988,12 +1060,12 @@ void CueControl::cuePlay(double v) { // If quantize is enabled, jump to the cue point since it's not // necessarily where we currently are if (m_pQuantizeEnabled->toBool()) { - lock.unlock(); // prevent deadlock. + lock.unlock(); // prevent deadlock. // Enginebuffer will quantize more exactly than we can. seekAbs(m_pCuePoint->get()); } } - } else if (trackAt == TrackAt::Cue) { + } else if (trackAt == TrackAt::Cue){ m_bPreviewing = false; m_pPlay->set(1.0); lock.unlock(); @@ -1367,9 +1439,9 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) QMutexLocker lock(&m_mutex); double cueMode = m_pCueMode->get(); if ((cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) && - newPlay && playPossible && - !m_pPlay->toBool() && - !m_bypassCueSetByPlay) { + newPlay && playPossible && + !m_pPlay->toBool() && + !m_bypassCueSetByPlay) { // in Denon mode each play from pause moves the cue point // if not previewing cueSet(1.0); @@ -1412,7 +1484,7 @@ bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible) m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS); } } else if (cueMode == CUE_MODE_MIXXX || cueMode == CUE_MODE_MIXXX_NO_BLINK || - cueMode == CUE_MODE_NUMARK) { + cueMode == CUE_MODE_NUMARK) { m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF); } else { // Flashing indicates that play is possible in Pioneer mode @@ -1604,11 +1676,12 @@ SeekOnLoadMode CueControl::getSeekOnLoadMode() { return seekOnLoadModeFromDouble(m_pSeekOnLoadMode->get()); } + ConfigKey HotcueControl::keyForControl(int hotcue, const char* name) { ConfigKey key; key.group = m_group; // Add one to hotcue so that we don't have a hotcue_0 - key.item = QLatin1String("hotcue_") % QString::number(hotcue + 1) % "_" % name; + key.item = QLatin1String("hotcue_") % QString::number(hotcue+1) % "_" % name; return key; } @@ -1619,7 +1692,9 @@ HotcueControl::HotcueControl(QString group, int i) m_bPreviewing(false), m_previewingPosition(-1) { m_hotcuePosition = new ControlObject(keyForControl(i, "position")); - connect(m_hotcuePosition, &ControlObject::valueChanged, this, &HotcueControl::slotHotcuePositionChanged, Qt::DirectConnection); + connect(m_hotcuePosition, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcuePositionChanged, + Qt::DirectConnection); m_hotcuePosition->set(-1); m_hotcueEnabled = new ControlObject(keyForControl(i, "enabled")); @@ -1634,25 +1709,39 @@ HotcueControl::HotcueControl(QString group, int i) Qt::DirectConnection); m_hotcueSet = new ControlPushButton(keyForControl(i, "set")); - connect(m_hotcueSet, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueSet, Qt::DirectConnection); + connect(m_hotcueSet, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcueSet, + Qt::DirectConnection); m_hotcueGoto = new ControlPushButton(keyForControl(i, "goto")); - connect(m_hotcueGoto, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueGoto, Qt::DirectConnection); + connect(m_hotcueGoto, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcueGoto, + Qt::DirectConnection); m_hotcueGotoAndPlay = new ControlPushButton(keyForControl(i, "gotoandplay")); - connect(m_hotcueGotoAndPlay, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueGotoAndPlay, Qt::DirectConnection); + connect(m_hotcueGotoAndPlay, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcueGotoAndPlay, + Qt::DirectConnection); m_hotcueGotoAndStop = new ControlPushButton(keyForControl(i, "gotoandstop")); - connect(m_hotcueGotoAndStop, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueGotoAndStop, Qt::DirectConnection); + connect(m_hotcueGotoAndStop, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcueGotoAndStop, + Qt::DirectConnection); m_hotcueActivate = new ControlPushButton(keyForControl(i, "activate")); - connect(m_hotcueActivate, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueActivate, Qt::DirectConnection); + connect(m_hotcueActivate, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcueActivate, + Qt::DirectConnection); m_hotcueActivatePreview = new ControlPushButton(keyForControl(i, "activate_preview")); - connect(m_hotcueActivatePreview, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueActivatePreview, Qt::DirectConnection); + connect(m_hotcueActivatePreview, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcueActivatePreview, + Qt::DirectConnection); m_hotcueClear = new ControlPushButton(keyForControl(i, "clear")); - connect(m_hotcueClear, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueClear, Qt::DirectConnection); + connect(m_hotcueClear, &ControlObject::valueChanged, + this, &HotcueControl::slotHotcueClear, + Qt::DirectConnection); } HotcueControl::~HotcueControl() { From 4e3c457f5900777e1f187ab06389da28cd0bb482 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Thu, 3 Oct 2019 10:27:20 +1000 Subject: [PATCH 27/36] Update CoreAudio offsets --- src/library/rekordbox/rekordboxfeature.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index f2e481cb10e..94e3a5c7216 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -825,10 +825,10 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { switch (timingShiftCase) { #ifdef __COREAUDIO__ case EXIT_CODE_CASE_A: - timingOffset = 13; + timingOffset = 12; break; case EXIT_CODE_CASE_B: - timingOffset = 11; + timingOffset = 13; break; case EXIT_CODE_CASE_C: timingOffset = 26; From 4084bfd270ebbb1d97fff713060f89c1afc6eb8d Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Thu, 3 Oct 2019 16:32:09 +1000 Subject: [PATCH 28/36] Fix mp3guessenc for Windows builds --- lib/mp3guessenc-0.27.4/mp3g_io_config.h | 3 ++- lib/mp3guessenc-0.27.4/mp3guessenc.c | 24 +++++++++++++++--------- lib/mp3guessenc-0.27.4/tags.c | 8 ++++---- lib/mp3guessenc-0.27.4/tags.h | 4 ++++ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/mp3guessenc-0.27.4/mp3g_io_config.h b/lib/mp3guessenc-0.27.4/mp3g_io_config.h index 63d8a3668a9..32d79d4536c 100644 --- a/lib/mp3guessenc-0.27.4/mp3g_io_config.h +++ b/lib/mp3guessenc-0.27.4/mp3g_io_config.h @@ -16,6 +16,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +/* Modifed by Evan Dekker 2019-09-26 */ #ifndef MP3G_IO_CONFIG_H_ #define MP3G_IO_CONFIG_H_ @@ -40,7 +41,7 @@ #define ftello ftello64 #endif /* __MINGW32__ */ -#if defined(OS2) || defined(_OS2) || defined(__OS2__) || defined(AMIGA) || defined(__amigaos__) +#if defined(OS2) || defined(_OS2) || defined(__OS2__) || defined(AMIGA) || defined(__amigaos__) || defined(__WINDOWS__) #define fseeko fseek #define ftello ftell #if defined(OS2) || defined(_OS2) || defined(__OS2__) diff --git a/lib/mp3guessenc-0.27.4/mp3guessenc.c b/lib/mp3guessenc-0.27.4/mp3guessenc.c index a3bd467b8bf..f0a1c34c647 100644 --- a/lib/mp3guessenc-0.27.4/mp3guessenc.c +++ b/lib/mp3guessenc-0.27.4/mp3guessenc.c @@ -66,7 +66,11 @@ #include #include #include +#if defined(__WINDOWS__) +#include +#else #include +#endif #include #include "mp3guessenc.h" @@ -206,7 +210,7 @@ char malformed_part1(unsigned char *, currentFrame *); /* Mpeg header utilities */ -char head_check(unsigned int head, char relaxed) +char mp3guessenc_head_check(unsigned int head, char relaxed) /* * Integrity checks for an mpeg header (4 bytes) * Return values: @@ -350,7 +354,7 @@ char layerIII(unsigned int head) /* unit for begin_ptr and length is bit */ /* begin_ptr should have values in 1..7 - fix: it works with any values */ /* begin_ptr may be NULL, when not NULL it is increased of 'length' amount */ -unsigned short crc_update(unsigned short old_crc, unsigned char *data, unsigned int *begin_ptr, unsigned int length) +unsigned short mp3guessenc_crc_update(unsigned short old_crc, unsigned char *data, unsigned int *begin_ptr, unsigned int length) { #define CRC16_POLYNOMIAL 0x8005 @@ -428,11 +432,11 @@ char verify_crc(unsigned int header, unsigned char *chk_buffer, int p1len, unsig /* the crc16 is first calculated on the last two bytes of the header */ dummy = (unsigned char)((header>>8)&0xFF); - curr_crc = crc_update(curr_crc, &dummy, NULL, 8); + curr_crc = mp3guessenc_crc_update(curr_crc, &dummy, NULL, 8); dummy = (unsigned char)(header&0xFF); - curr_crc = crc_update(curr_crc, &dummy, NULL, 8); + curr_crc = mp3guessenc_crc_update(curr_crc, &dummy, NULL, 8); - curr_crc = crc_update(curr_crc, chk_buffer, NULL, p1len- + curr_crc = mp3guessenc_crc_update(curr_crc, chk_buffer, NULL, p1len- 8*(sizeof(unsigned int)+sizeof(unsigned short))); return (curr_crc==target); @@ -464,7 +468,7 @@ unsigned short crc_reflected_update(unsigned short old_crc, unsigned char *data, for (idx=0; idx +#endif + #define LAMETAGSIZE 36 #define LAME_STRING_LENGTH 48 From f9684182b103031390881b126d702adc7ceb9efe Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Thu, 3 Oct 2019 16:40:49 +1000 Subject: [PATCH 29/36] Update mp3guessenc tags.c --- lib/mp3guessenc-0.27.4/tags.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/mp3guessenc-0.27.4/tags.c b/lib/mp3guessenc-0.27.4/tags.c index deff7ed57f7..95c9158a6f6 100644 --- a/lib/mp3guessenc-0.27.4/tags.c +++ b/lib/mp3guessenc-0.27.4/tags.c @@ -146,12 +146,12 @@ typedef struct mmtag_tail_infos_t { unsigned char footer[MMTAG_FOOTER_SIZE]; } mmtag_tail_infos_t; -//#if defined(__WATCOMC__) +#if defined(__WATCOMC__) || defined(__WINDOWS__) /* Watcom compiler defaults alignment to 1 byte */ typedef struct mmtag_partial_infos_t { -//#else -//typedef struct __attribute__((packed)) mmtag_partial_infos_t { -//#endif +#else +typedef struct __attribute__((packed)) mmtag_partial_infos_t { +#endif unsigned char empty[12]; unsigned char sync[MMTAG_VERSION_BLOCK_SUBSECTION_LENGTH]; unsigned char xing[MMTAG_VERSION_BLOCK_SUBSECTION_LENGTH]; From cbe208034d36a062a4a9bafadc60521ced277af7 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Fri, 11 Oct 2019 16:46:51 +1100 Subject: [PATCH 30/36] Read hotcue colors and comments --- src/library/rekordbox/rekordbox_anlz.h | 363 ++++++++++++++++++++- src/library/rekordbox/rekordboxfeature.cpp | 124 ++++++- 2 files changed, 467 insertions(+), 20 deletions(-) diff --git a/src/library/rekordbox/rekordbox_anlz.h b/src/library/rekordbox/rekordbox_anlz.h index a86194a63fb..31efc57173d 100644 --- a/src/library/rekordbox/rekordbox_anlz.h +++ b/src/library/rekordbox/rekordbox_anlz.h @@ -32,24 +32,51 @@ class rekordbox_anlz_t : public kaitai::kstruct { public: + class phrase_up_down_t; class path_tag_t; class wave_preview_tag_t; class beat_grid_tag_t; class wave_color_preview_tag_t; class wave_scroll_tag_t; + class phrase_verse_bridge_t; + class song_structure_tag_t; + class cue_extended_entry_t; class vbr_tag_t; + class song_structure_entry_t; class cue_entry_t; class beat_grid_beat_t; + class cue_extended_tag_t; class unknown_tag_t; class tagged_section_t; class wave_color_scroll_tag_t; class cue_tag_t; + enum cue_entry_status_t { + CUE_ENTRY_STATUS_DISABLED = 0, + CUE_ENTRY_STATUS_ENABLED = 1 + }; + + enum cue_list_type_t { + CUE_LIST_TYPE_MEMORY_CUES = 0, + CUE_LIST_TYPE_HOT_CUES = 1 + }; + + enum phrase_style_t { + PHRASE_STYLE_UP_DOWN = 1, + PHRASE_STYLE_VERSE_BRIDGE = 2 + }; + + enum cue_entry_type_t { + CUE_ENTRY_TYPE_MEMORY_CUE = 1, + CUE_ENTRY_TYPE_LOOP = 2 + }; + enum section_tags_t { SECTION_TAGS_CUES_2 = 1346588466, SECTION_TAGS_CUES = 1346588482, SECTION_TAGS_PATH = 1347441736, SECTION_TAGS_BEAT_GRID = 1347507290, + SECTION_TAGS_SONG_STRUCTURE = 1347638089, SECTION_TAGS_VBR = 1347830354, SECTION_TAGS_WAVE_PREVIEW = 1347895638, SECTION_TAGS_WAVE_TINY = 1347900978, @@ -58,19 +85,25 @@ class rekordbox_anlz_t : public kaitai::kstruct { SECTION_TAGS_WAVE_COLOR_SCROLL = 1347900981 }; - enum cue_list_type_t { - CUE_LIST_TYPE_MEMORY_CUES = 0, - CUE_LIST_TYPE_HOT_CUES = 1 - }; - - enum cue_entry_type_t { - CUE_ENTRY_TYPE_MEMORY_CUE = 1, - CUE_ENTRY_TYPE_LOOP = 2 + enum phrase_verse_bridge_id_t { + PHRASE_VERSE_BRIDGE_ID_INTRO = 1, + PHRASE_VERSE_BRIDGE_ID_VERSE1 = 2, + PHRASE_VERSE_BRIDGE_ID_VERSE2 = 3, + PHRASE_VERSE_BRIDGE_ID_VERSE3 = 4, + PHRASE_VERSE_BRIDGE_ID_VERSE4 = 5, + PHRASE_VERSE_BRIDGE_ID_VERSE5 = 6, + PHRASE_VERSE_BRIDGE_ID_VERSE6 = 7, + PHRASE_VERSE_BRIDGE_ID_BRIDGE = 8, + PHRASE_VERSE_BRIDGE_ID_CHORUS = 9, + PHRASE_VERSE_BRIDGE_ID_OUTRO = 10 }; - enum cue_entry_status_t { - CUE_ENTRY_STATUS_DISABLED = 0, - CUE_ENTRY_STATUS_ENABLED = 1 + enum phrase_up_down_id_t { + PHRASE_UP_DOWN_ID_INTRO = 1, + PHRASE_UP_DOWN_ID_UP = 2, + PHRASE_UP_DOWN_ID_DOWN = 3, + PHRASE_UP_DOWN_ID_CHORUS = 5, + PHRASE_UP_DOWN_ID_OUTRO = 6 }; rekordbox_anlz_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, rekordbox_anlz_t* p__root = 0); @@ -81,6 +114,29 @@ class rekordbox_anlz_t : public kaitai::kstruct { public: ~rekordbox_anlz_t(); + class phrase_up_down_t : public kaitai::kstruct { + + public: + + phrase_up_down_t(kaitai::kstream* p__io, rekordbox_anlz_t::song_structure_entry_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~phrase_up_down_t(); + + private: + phrase_up_down_id_t m_id; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::song_structure_entry_t* m__parent; + + public: + phrase_up_down_id_t id() const { return m_id; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::song_structure_entry_t* _parent() const { return m__parent; } + }; + /** * Stores the file path of the audio file to which this analysis * applies. @@ -289,6 +345,190 @@ class rekordbox_anlz_t : public kaitai::kstruct { rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } }; + class phrase_verse_bridge_t : public kaitai::kstruct { + + public: + + phrase_verse_bridge_t(kaitai::kstream* p__io, rekordbox_anlz_t::song_structure_entry_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~phrase_verse_bridge_t(); + + private: + phrase_verse_bridge_id_t m_id; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::song_structure_entry_t* m__parent; + + public: + phrase_verse_bridge_id_t id() const { return m_id; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::song_structure_entry_t* _parent() const { return m__parent; } + }; + + /** + * Stores the song structure, also known as phrases (intro, verse, + * bridge, chorus, up, down, outro). + */ + + class song_structure_tag_t : public kaitai::kstruct { + + public: + + song_structure_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~song_structure_tag_t(); + + private: + uint32_t m_len_entry_bytes; + uint16_t m_len_entries; + phrase_style_t m_style; + std::string m__unnamed3; + uint16_t m_end_beat; + std::string m__unnamed5; + std::vector* m_entries; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + + /** + * The size of each entry, in bytes. Seems to always be 24. + */ + uint32_t len_entry_bytes() const { return m_len_entry_bytes; } + + /** + * The number of phrases. + */ + uint16_t len_entries() const { return m_len_entries; } + + /** + * The phrase style. 1 is the up-down style + * (white label text in rekordbox) where the main phrases consist + * of up, down, and chorus. 2 is the bridge-verse style + * (black label text in rekordbox) where the main phrases consist + * of verse, chorus, and bridge. Style 3 is mostly identical to + * bridge-verse style except verses 1-3 are labeled VERSE1 and verses + * 4-6 are labeled VERSE2 in rekordbox. + */ + phrase_style_t style() const { return m_style; } + std::string _unnamed3() const { return m__unnamed3; } + + /** + * The beat number at which the last phrase ends. The track may + * continue after the last phrase ends. If this is the case, it will + * mostly be silence. + */ + uint16_t end_beat() const { return m_end_beat; } + std::string _unnamed5() const { return m__unnamed5; } + std::vector* entries() const { return m_entries; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + + /** + * A cue extended list entry. Can either describe a memory cue or a + * loop. + */ + + class cue_extended_entry_t : public kaitai::kstruct { + + public: + + cue_extended_entry_t(kaitai::kstream* p__io, rekordbox_anlz_t::cue_extended_tag_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~cue_extended_entry_t(); + + private: + std::string m__unnamed0; + uint32_t m_len_header; + uint32_t m_len_entry; + uint32_t m_hot_cue; + cue_entry_type_t m_type; + std::string m__unnamed5; + uint32_t m_time; + uint32_t m_loop_time; + std::string m__unnamed8; + uint32_t m_len_comment; + std::string m_comment; + uint8_t m_color_code; + uint8_t m_color_red; + uint8_t m_color_green; + uint8_t m_color_blue; + std::string m__unnamed15; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::cue_extended_tag_t* m__parent; + + public: + std::string _unnamed0() const { return m__unnamed0; } + uint32_t len_header() const { return m_len_header; } + uint32_t len_entry() const { return m_len_entry; } + + /** + * If zero, this is an ordinary memory cue, otherwise this a + * hot cue with the specified number. + */ + uint32_t hot_cue() const { return m_hot_cue; } + + /** + * Indicates whether this is a memory cue or a loop. + */ + cue_entry_type_t type() const { return m_type; } + std::string _unnamed5() const { return m__unnamed5; } + + /** + * The position, in milliseconds, at which the cue point lies + * in the track. + */ + uint32_t time() const { return m_time; } + + /** + * The position, in milliseconds, at which the player loops + * back to the cue time if this is a loop. + */ + uint32_t loop_time() const { return m_loop_time; } + std::string _unnamed8() const { return m__unnamed8; } + uint32_t len_comment() const { return m_len_comment; } + + /** + * The comment assigned to this cue by the DJ, if any, with a trailing NUL. + */ + std::string comment() const { return m_comment; } + + /** + * A lookup value for a color table? We use this to index to the colors shown in rekordbox. + */ + uint8_t color_code() const { return m_color_code; } + + /** + * The red component of the color to be displayed. + */ + uint8_t color_red() const { return m_color_red; } + + /** + * The green component of the color to be displayed. + */ + uint8_t color_green() const { return m_color_green; } + + /** + * The blue component of the color to be displayed. + */ + uint8_t color_blue() const { return m_color_blue; } + std::string _unnamed15() const { return m__unnamed15; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::cue_extended_tag_t* _parent() const { return m__parent; } + }; + /** * Stores an index allowing rapid seeking to particular times * within a variable-bitrate audio file. @@ -319,6 +559,63 @@ class rekordbox_anlz_t : public kaitai::kstruct { rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } }; + /** + * A song structure entry, represents a single phrase. + */ + + class song_structure_entry_t : public kaitai::kstruct { + + public: + + song_structure_entry_t(kaitai::kstream* p__io, rekordbox_anlz_t::song_structure_tag_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~song_structure_entry_t(); + + private: + uint16_t m_phrase_number; + uint16_t m_beat_number; + kaitai::kstruct* m_phrase_id; + std::string m__unnamed3; + uint8_t m_fill_in; + uint16_t m_fill_in_beat_number; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::song_structure_tag_t* m__parent; + + public: + + /** + * The absolute number of the phrase, starting at one. + */ + uint16_t phrase_number() const { return m_phrase_number; } + + /** + * The beat number at which the phrase starts. + */ + uint16_t beat_number() const { return m_beat_number; } + + /** + * Identifier of the phrase label. + */ + kaitai::kstruct* phrase_id() const { return m_phrase_id; } + std::string _unnamed3() const { return m__unnamed3; } + + /** + * If nonzero, fill-in is present. + */ + uint8_t fill_in() const { return m_fill_in; } + + /** + * The beat number at which fill-in starts. + */ + uint16_t fill_in_beat_number() const { return m_fill_in_beat_number; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::song_structure_tag_t* _parent() const { return m__parent; } + }; + /** * A cue list entry. Can either represent a memory cue or a loop. */ @@ -447,6 +744,50 @@ class rekordbox_anlz_t : public kaitai::kstruct { rekordbox_anlz_t::beat_grid_tag_t* _parent() const { return m__parent; } }; + /** + * A variation of cue_tag which was introduced with the nxs2 line, + * and adds descriptive names. (Still comes in two forms, either + * holding memory cues and loop points, or holding hot cues and + * loop points.) Also includes hot cues D through H and color assignment. + */ + + class cue_extended_tag_t : public kaitai::kstruct { + + public: + + cue_extended_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent = 0, rekordbox_anlz_t* p__root = 0); + + private: + void _read(); + + public: + ~cue_extended_tag_t(); + + private: + cue_list_type_t m_type; + uint16_t m_len_cues; + std::string m__unnamed2; + std::vector* m_cues; + rekordbox_anlz_t* m__root; + rekordbox_anlz_t::tagged_section_t* m__parent; + + public: + + /** + * Identifies whether this tag stores ordinary or hot cues. + */ + cue_list_type_t type() const { return m_type; } + + /** + * The length of the cue comment list. + */ + uint16_t len_cues() const { return m_len_cues; } + std::string _unnamed2() const { return m__unnamed2; } + std::vector* cues() const { return m_cues; } + rekordbox_anlz_t* _root() const { return m__root; } + rekordbox_anlz_t::tagged_section_t* _parent() const { return m__parent; } + }; + class unknown_tag_t : public kaitai::kstruct { public: diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 94e3a5c7216..a222bf39e4d 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -26,6 +26,7 @@ #include "util/db/dbconnectionpooler.h" #include "util/file.h" #include "util/sandbox.h" +#include "util/color/color.h" #include "waveform/waveform.h" #include "widget/wlibrary.h" @@ -660,7 +661,67 @@ void clearDeviceTables(QSqlDatabase& database, TreeItem* child) { transaction.commit(); } -void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, QString anlzPath) { +void setHotCue(TrackPointer track, double position, int id, QString label, int colorCode) { + CuePointer pCue; + bool hotCueFound = false; + + for (const CuePointer& trackCue : track->getCuePoints()) { + if (trackCue->getHotCue() == id) { + pCue = trackCue; + hotCueFound = true; + break; + } + } + + if (!hotCueFound) { + pCue = CuePointer(track->createAndAddCue()); + } + + pCue->setPosition(position); + pCue->setHotCue(id); + pCue->setLabel(label); + pCue->setType(Cue::CUE); + + // Map 17 possible Rekordbox hotcue colors to closest Mixxx hotcue colors + switch (colorCode) { + case 38: + case 42: + pCue->setColor(Color::kPredefinedColorsSet.red); + break; + case 0: + case 14: + case 18: + case 22: + case 26: + pCue->setColor(Color::kPredefinedColorsSet.green); + break; + case 30: + case 32: + pCue->setColor(Color::kPredefinedColorsSet.yellow); + break; + case 1: + case 5: + case 62: + pCue->setColor(Color::kPredefinedColorsSet.blue); + break; + case 9: + pCue->setColor(Color::kPredefinedColorsSet.cyan); + break; + case 56: + case 60: + pCue->setColor(Color::kPredefinedColorsSet.magenta); + break; + case 45: + case 49: + pCue->setColor(Color::kPredefinedColorsSet.pink); + break; + default: + pCue->setColor(Color::kPredefinedColorsSet.noColor); + break; + } +} + +void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool ignoreBeatsAndLegacyCues, QString anlzPath) { if (!QFile(anlzPath).exists()) { return; } @@ -672,13 +733,20 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, QStrin rekordbox_anlz_t anlz = rekordbox_anlz_t(&ks); + double sampleRateFrames = sampleRate * 2.0; + double cueLoadPosition = kLongestPosition; + QString cueLoadComment; double cueLoopPosition = kLongestPosition; double loopLength = -1.0; for (std::vector::iterator section = anlz.sections()->begin(); section != anlz.sections()->end(); ++section) { switch ((*section)->fourcc()) { case rekordbox_anlz_t::SECTION_TAGS_BEAT_GRID: { + if (ignoreBeatsAndLegacyCues) { + break; + } + rekordbox_anlz_t::beat_grid_tag_t* beatGridTag = static_cast((*section)->body()); QVector beats; @@ -698,9 +766,12 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, QStrin *track, beats, extraVersionInfo, false, false, sampleRate, 0, 0, 0); track->setBeats(pBeats); - } break; + } break; case rekordbox_anlz_t::SECTION_TAGS_CUES: { - double sampleRateFrames = sampleRate * 2.0; + if (ignoreBeatsAndLegacyCues) { + break; + } + rekordbox_anlz_t::cue_tag_t* cuesTag = static_cast((*section)->body()); for (std::vector::iterator cueEntry = cuesTag->cues()->begin(); cueEntry != cuesTag->cues()->end(); ++cueEntry) { @@ -730,10 +801,43 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, QStrin } } break; case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { - CuePointer pCue(track->createAndAddCue()); - pCue->setPosition(position); - pCue->setHotCue(static_cast((*cueEntry)->hot_cue() - 1)); - pCue->setType(Cue::CUE); + setHotCue(track, position, static_cast((*cueEntry)->hot_cue() - 1), QString(), -1); + } break; + } + } + } break; + case rekordbox_anlz_t::SECTION_TAGS_CUES_2: { + rekordbox_anlz_t::cue_extended_tag_t* cuesExtendedTag = static_cast((*section)->body()); + + for (std::vector::iterator cueExtendedEntry = cuesExtendedTag->cues()->begin(); cueExtendedEntry != cuesExtendedTag->cues()->end(); ++cueExtendedEntry) { + int time = static_cast((*cueExtendedEntry)->time()) - timingOffset; + // Ensure no offset times are less than 1 + if (time < 1) { + time = 1; + } + double position = sampleRateFrames * 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: { + // As Mixxx can only have 1 saved cue point, use the first occurance of a memory cue relative to the start of the track + if (position < cueLoadPosition) { + cueLoadPosition = position; + cueLoadComment = QString::fromStdString((*cueExtendedEntry)->comment()); + } + } break; + case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { + // As Mixxx can only have 1 saved loop, use the first occurance of a memory loop relative to the start of the track + if (position < cueLoopPosition) { + cueLoopPosition = position; + loopLength = sampleRateFrames * static_cast((*cueExtendedEntry)->loop_time() - (*cueExtendedEntry)->time()); + } + } break; + } + } break; + case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { + setHotCue(track, position, static_cast((*cueExtendedEntry)->hot_cue() - 1), QString::fromStdString((*cueExtendedEntry)->comment()), static_cast((*cueExtendedEntry)->color_code())); } break; } } @@ -745,6 +849,8 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, QStrin if (cueLoadPosition < kLongestPosition) { track->setCuePoint(CuePosition(cueLoadPosition, Cue::MANUAL)); + CuePointer pLoadCue = track->findCueByType(Cue::LOAD); + pLoadCue->setLabel(cueLoadComment); } if (cueLoopPosition < kLongestPosition) { CuePointer pCue(track->createAndAddCue()); @@ -852,9 +958,9 @@ TrackPointer RekordboxPlaylistModel::getTrack(const QModelIndex& index) const { double sampleRate = static_cast(track->getSampleRate()) / 1000.0; QString anlzPath = index.sibling(index.row(), fieldIndex("analyze_path")).data().toString(); - readAnalyze(track, sampleRate, timingOffset, anlzPath); + readAnalyze(track, sampleRate, timingOffset, false, anlzPath); QString anlzPathExt = anlzPath.left(anlzPath.length() - 3) + "EXT"; - readAnalyze(track, sampleRate, timingOffset, anlzPathExt); + readAnalyze(track, sampleRate, timingOffset, true, anlzPathExt); // Assume that the key of the file the has been analyzed in Recordbox is correct // and prevent the AnalyzerKey from re-analyzing. From 8676e4fbae00149e6dd270e28a5ee243bdefcee2 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Fri, 11 Oct 2019 16:53:33 +1100 Subject: [PATCH 31/36] Add Anlz cpp file --- src/library/rekordbox/rekordbox_anlz.cpp | 151 +++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/src/library/rekordbox/rekordbox_anlz.cpp b/src/library/rekordbox/rekordbox_anlz.cpp index af90ebbcace..7e3af0ca844 100644 --- a/src/library/rekordbox/rekordbox_anlz.cpp +++ b/src/library/rekordbox/rekordbox_anlz.cpp @@ -32,6 +32,19 @@ rekordbox_anlz_t::~rekordbox_anlz_t() { delete m_sections; } +rekordbox_anlz_t::phrase_up_down_t::phrase_up_down_t(kaitai::kstream* p__io, rekordbox_anlz_t::song_structure_entry_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::phrase_up_down_t::_read() { + m_id = static_cast(m__io->read_u2be()); +} + +rekordbox_anlz_t::phrase_up_down_t::~phrase_up_down_t() { +} + rekordbox_anlz_t::path_tag_t::path_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; @@ -124,6 +137,75 @@ void rekordbox_anlz_t::wave_scroll_tag_t::_read() { rekordbox_anlz_t::wave_scroll_tag_t::~wave_scroll_tag_t() { } +rekordbox_anlz_t::phrase_verse_bridge_t::phrase_verse_bridge_t(kaitai::kstream* p__io, rekordbox_anlz_t::song_structure_entry_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::phrase_verse_bridge_t::_read() { + m_id = static_cast(m__io->read_u2be()); +} + +rekordbox_anlz_t::phrase_verse_bridge_t::~phrase_verse_bridge_t() { +} + +rekordbox_anlz_t::song_structure_tag_t::song_structure_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::song_structure_tag_t::_read() { + m_len_entry_bytes = m__io->read_u4be(); + m_len_entries = m__io->read_u2be(); + m_style = static_cast(m__io->read_u2be()); + m__unnamed3 = m__io->read_bytes(6); + m_end_beat = m__io->read_u2be(); + m__unnamed5 = m__io->read_bytes(4); + int l_entries = len_entries(); + m_entries = new std::vector(); + m_entries->reserve(l_entries); + for (int i = 0; i < l_entries; i++) { + m_entries->push_back(new song_structure_entry_t(m__io, this, m__root)); + } +} + +rekordbox_anlz_t::song_structure_tag_t::~song_structure_tag_t() { + for (std::vector::iterator it = m_entries->begin(); it != m_entries->end(); ++it) { + delete *it; + } + delete m_entries; +} + +rekordbox_anlz_t::cue_extended_entry_t::cue_extended_entry_t(kaitai::kstream* p__io, rekordbox_anlz_t::cue_extended_tag_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::cue_extended_entry_t::_read() { + m__unnamed0 = m__io->ensure_fixed_contents(std::string("\x50\x43\x50\x32", 4)); + m_len_header = m__io->read_u4be(); + m_len_entry = m__io->read_u4be(); + m_hot_cue = m__io->read_u4be(); + m_type = static_cast(m__io->read_u1()); + m__unnamed5 = m__io->read_bytes(3); + m_time = m__io->read_u4be(); + m_loop_time = m__io->read_u4be(); + m__unnamed8 = m__io->read_bytes(12); + m_len_comment = m__io->read_u4be(); + m_comment = kaitai::kstream::bytes_to_str(m__io->read_bytes(len_comment()), std::string("utf-16be")); + m_color_code = m__io->read_u1(); + m_color_red = m__io->read_u1(); + m_color_green = m__io->read_u1(); + m_color_blue = m__io->read_u1(); + m__unnamed15 = m__io->read_bytes(((len_entry() - 48) - len_comment())); +} + +rekordbox_anlz_t::cue_extended_entry_t::~cue_extended_entry_t() { +} + rekordbox_anlz_t::vbr_tag_t::vbr_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; @@ -144,6 +226,38 @@ rekordbox_anlz_t::vbr_tag_t::~vbr_tag_t() { delete m_index; } +rekordbox_anlz_t::song_structure_entry_t::song_structure_entry_t(kaitai::kstream* p__io, rekordbox_anlz_t::song_structure_tag_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::song_structure_entry_t::_read() { + m_phrase_number = m__io->read_u2be(); + m_beat_number = m__io->read_u2be(); + switch (_parent()->style()) { + case PHRASE_STYLE_UP_DOWN: { + m_phrase_id = new phrase_up_down_t(m__io, this, m__root); + break; + } + case PHRASE_STYLE_VERSE_BRIDGE: { + m_phrase_id = new phrase_verse_bridge_t(m__io, this, m__root); + break; + } + default: { + m_phrase_id = new phrase_verse_bridge_t(m__io, this, m__root); + break; + } + } + m__unnamed3 = m__io->read_bytes((_parent()->len_entry_bytes() - 9)); + m_fill_in = m__io->read_u1(); + m_fill_in_beat_number = m__io->read_u2be(); +} + +rekordbox_anlz_t::song_structure_entry_t::~song_structure_entry_t() { + delete m_phrase_id; +} + rekordbox_anlz_t::cue_entry_t::cue_entry_t(kaitai::kstream* p__io, rekordbox_anlz_t::cue_tag_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; @@ -184,6 +298,31 @@ void rekordbox_anlz_t::beat_grid_beat_t::_read() { rekordbox_anlz_t::beat_grid_beat_t::~beat_grid_beat_t() { } +rekordbox_anlz_t::cue_extended_tag_t::cue_extended_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + _read(); +} + +void rekordbox_anlz_t::cue_extended_tag_t::_read() { + m_type = static_cast(m__io->read_u4be()); + m_len_cues = m__io->read_u2be(); + m__unnamed2 = m__io->read_bytes(2); + int l_cues = len_cues(); + m_cues = new std::vector(); + m_cues->reserve(l_cues); + for (int i = 0; i < l_cues; i++) { + m_cues->push_back(new cue_extended_entry_t(m__io, this, m__root)); + } +} + +rekordbox_anlz_t::cue_extended_tag_t::~cue_extended_tag_t() { + for (std::vector::iterator it = m_cues->begin(); it != m_cues->end(); ++it) { + delete *it; + } + delete m_cues; +} + rekordbox_anlz_t::unknown_tag_t::unknown_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; @@ -237,6 +376,12 @@ void rekordbox_anlz_t::tagged_section_t::_read() { m_body = new wave_scroll_tag_t(m__io__raw_body, this, m__root); break; } + case 1347638089: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new song_structure_tag_t(m__io__raw_body, this, m__root); + break; + } case 1347507290: { m__raw_body = m__io->read_bytes((len_tag() - 12)); m__io__raw_body = new kaitai::kstream(m__raw_body); @@ -255,6 +400,12 @@ void rekordbox_anlz_t::tagged_section_t::_read() { m_body = new wave_color_scroll_tag_t(m__io__raw_body, this, m__root); break; } + case 1346588466: { + m__raw_body = m__io->read_bytes((len_tag() - 12)); + m__io__raw_body = new kaitai::kstream(m__raw_body); + m_body = new cue_extended_tag_t(m__io__raw_body, this, m__root); + break; + } case 1347441736: { m__raw_body = m__io->read_bytes((len_tag() - 12)); m__io__raw_body = new kaitai::kstream(m__raw_body); From 7228e2e3932e806568c9ee0c5fdb0d1acd6bc180 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Mon, 14 Oct 2019 10:48:02 +1100 Subject: [PATCH 32/36] Fix cue label asserts --- src/library/rekordbox/rekordboxfeature.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index a222bf39e4d..d957437b849 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -677,10 +677,13 @@ void setHotCue(TrackPointer track, double position, int id, QString label, int c pCue = CuePointer(track->createAndAddCue()); } + pCue->setType(Cue::CUE); pCue->setPosition(position); pCue->setHotCue(id); - pCue->setLabel(label); - pCue->setType(Cue::CUE); + + if (!label.isNull()) { + pCue->setLabel(label); + } // Map 17 possible Rekordbox hotcue colors to closest Mixxx hotcue colors switch (colorCode) { @@ -850,7 +853,9 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i if (cueLoadPosition < kLongestPosition) { track->setCuePoint(CuePosition(cueLoadPosition, Cue::MANUAL)); CuePointer pLoadCue = track->findCueByType(Cue::LOAD); - pLoadCue->setLabel(cueLoadComment); + if (!cueLoadComment.isNull()) { + pLoadCue->setLabel(cueLoadComment); + } } if (cueLoopPosition < kLongestPosition) { CuePointer pCue(track->createAndAddCue()); From 4c45692665196d71c7ecdbb2c2276701caf87d8f Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Tue, 15 Oct 2019 10:45:23 +1100 Subject: [PATCH 33/36] ANLZ file update to fix crash when cues missing color bytes --- src/library/rekordbox/rekordbox_anlz.cpp | 40 +++++++++++++++++++++--- src/library/rekordbox/rekordbox_anlz.h | 30 ++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/library/rekordbox/rekordbox_anlz.cpp b/src/library/rekordbox/rekordbox_anlz.cpp index 7e3af0ca844..bb31bff98bc 100644 --- a/src/library/rekordbox/rekordbox_anlz.cpp +++ b/src/library/rekordbox/rekordbox_anlz.cpp @@ -196,14 +196,44 @@ void rekordbox_anlz_t::cue_extended_entry_t::_read() { m__unnamed8 = m__io->read_bytes(12); m_len_comment = m__io->read_u4be(); m_comment = kaitai::kstream::bytes_to_str(m__io->read_bytes(len_comment()), std::string("utf-16be")); - m_color_code = m__io->read_u1(); - m_color_red = m__io->read_u1(); - m_color_green = m__io->read_u1(); - m_color_blue = m__io->read_u1(); - m__unnamed15 = m__io->read_bytes(((len_entry() - 48) - len_comment())); + n_color_code = true; + if ((len_entry() - len_comment()) > 44) { + n_color_code = false; + m_color_code = m__io->read_u1(); + } + n_color_red = true; + if ((len_entry() - len_comment()) > 45) { + n_color_red = false; + m_color_red = m__io->read_u1(); + } + n_color_green = true; + if ((len_entry() - len_comment()) > 46) { + n_color_green = false; + m_color_green = m__io->read_u1(); + } + n_color_blue = true; + if ((len_entry() - len_comment()) > 47) { + n_color_blue = false; + m_color_blue = m__io->read_u1(); + } + n__unnamed15 = true; + if ((len_entry() - len_comment()) > 48) { + n__unnamed15 = false; + m__unnamed15 = m__io->read_bytes(((len_entry() - 48) - len_comment())); + } } rekordbox_anlz_t::cue_extended_entry_t::~cue_extended_entry_t() { + if (!n_color_code) { + } + if (!n_color_red) { + } + if (!n_color_green) { + } + if (!n_color_blue) { + } + if (!n__unnamed15) { + } } rekordbox_anlz_t::vbr_tag_t::vbr_tag_t(kaitai::kstream* p__io, rekordbox_anlz_t::tagged_section_t* p__parent, rekordbox_anlz_t* p__root) : kaitai::kstruct(p__io) { diff --git a/src/library/rekordbox/rekordbox_anlz.h b/src/library/rekordbox/rekordbox_anlz.h index 31efc57173d..19fbc6d9d38 100644 --- a/src/library/rekordbox/rekordbox_anlz.h +++ b/src/library/rekordbox/rekordbox_anlz.h @@ -462,10 +462,40 @@ class rekordbox_anlz_t : public kaitai::kstruct { uint32_t m_len_comment; std::string m_comment; uint8_t m_color_code; + bool n_color_code; + + public: + bool _is_null_color_code() { color_code(); return n_color_code; }; + + private: uint8_t m_color_red; + bool n_color_red; + + public: + bool _is_null_color_red() { color_red(); return n_color_red; }; + + private: uint8_t m_color_green; + bool n_color_green; + + public: + bool _is_null_color_green() { color_green(); return n_color_green; }; + + private: uint8_t m_color_blue; + bool n_color_blue; + + public: + bool _is_null_color_blue() { color_blue(); return n_color_blue; }; + + private: std::string m__unnamed15; + bool n__unnamed15; + + public: + bool _is_null__unnamed15() { _unnamed15(); return n__unnamed15; }; + + private: rekordbox_anlz_t* m__root; rekordbox_anlz_t::cue_extended_tag_t* m__parent; From 133d1bc6500a6256612925b6129a760e19b3fbba Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Tue, 15 Oct 2019 14:26:42 +1100 Subject: [PATCH 34/36] Unicode support for cue comments --- src/library/rekordbox/rekordboxfeature.cpp | 88 +++++++++++----------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index d957437b849..1c6bcb58f2d 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -22,11 +22,11 @@ #include "track/beatfactory.h" #include "track/cue.h" #include "track/keyfactory.h" +#include "util/color/color.h" #include "util/db/dbconnectionpooled.h" #include "util/db/dbconnectionpooler.h" #include "util/file.h" #include "util/sandbox.h" -#include "util/color/color.h" #include "waveform/waveform.h" #include "widget/wlibrary.h" @@ -164,6 +164,10 @@ inline bool instanceof (const T* ptr) { return dynamic_cast(ptr) != nullptr; } +QString toUnicode(std::string toConvert) { + return QTextCodec::codecForName("UTF-16BE")->toUnicode(QByteArray(toConvert.c_str(), toConvert.length())); +} + // Functions getText and parseDeviceDB are roughly based on the following Java file: // https://github.com/Deep-Symmetry/crate-digger/commit/f09fa9fc097a2a428c43245ddd542ac1370c1adc // getText is needed because the strings in the PDB file "have a variety of obscure representations". @@ -180,9 +184,7 @@ QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { } else if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_long_utf16be_t* longUtf16beString = static_cast(deviceString->body()); - QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); - std::string utf16be = longUtf16beString->text(); - return codec->toUnicode(QByteArray(utf16be.c_str(), utf16be.length())); + return toUnicode(longUtf16beString->text()); } return QString(); @@ -687,42 +689,42 @@ void setHotCue(TrackPointer track, double position, int id, QString label, int c // Map 17 possible Rekordbox hotcue colors to closest Mixxx hotcue colors switch (colorCode) { - case 38: - case 42: - pCue->setColor(Color::kPredefinedColorsSet.red); - break; - case 0: - case 14: - case 18: - case 22: - case 26: - pCue->setColor(Color::kPredefinedColorsSet.green); - break; - case 30: - case 32: - pCue->setColor(Color::kPredefinedColorsSet.yellow); - break; - case 1: - case 5: - case 62: - pCue->setColor(Color::kPredefinedColorsSet.blue); - break; - case 9: - pCue->setColor(Color::kPredefinedColorsSet.cyan); - break; - case 56: - case 60: - pCue->setColor(Color::kPredefinedColorsSet.magenta); - break; - case 45: - case 49: - pCue->setColor(Color::kPredefinedColorsSet.pink); - break; - default: - pCue->setColor(Color::kPredefinedColorsSet.noColor); - break; + case 38: + case 42: + pCue->setColor(Color::kPredefinedColorsSet.red); + break; + case 0: + case 14: + case 18: + case 22: + case 26: + pCue->setColor(Color::kPredefinedColorsSet.green); + break; + case 30: + case 32: + pCue->setColor(Color::kPredefinedColorsSet.yellow); + break; + case 1: + case 5: + case 62: + pCue->setColor(Color::kPredefinedColorsSet.blue); + break; + case 9: + pCue->setColor(Color::kPredefinedColorsSet.cyan); + break; + case 56: + case 60: + pCue->setColor(Color::kPredefinedColorsSet.magenta); + break; + case 45: + case 49: + pCue->setColor(Color::kPredefinedColorsSet.pink); + break; + default: + pCue->setColor(Color::kPredefinedColorsSet.noColor); + break; } -} +} void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool ignoreBeatsAndLegacyCues, QString anlzPath) { if (!QFile(anlzPath).exists()) { @@ -769,7 +771,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i *track, beats, extraVersionInfo, false, false, sampleRate, 0, 0, 0); track->setBeats(pBeats); - } break; + } break; case rekordbox_anlz_t::SECTION_TAGS_CUES: { if (ignoreBeatsAndLegacyCues) { break; @@ -808,7 +810,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } break; } } - } break; + } break; case rekordbox_anlz_t::SECTION_TAGS_CUES_2: { rekordbox_anlz_t::cue_extended_tag_t* cuesExtendedTag = static_cast((*section)->body()); @@ -827,7 +829,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i // As Mixxx can only have 1 saved cue point, use the first occurance of a memory cue relative to the start of the track if (position < cueLoadPosition) { cueLoadPosition = position; - cueLoadComment = QString::fromStdString((*cueExtendedEntry)->comment()); + cueLoadComment = toUnicode((*cueExtendedEntry)->comment()); } } break; case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { @@ -840,7 +842,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } } break; case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { - setHotCue(track, position, static_cast((*cueExtendedEntry)->hot_cue() - 1), QString::fromStdString((*cueExtendedEntry)->comment()), static_cast((*cueExtendedEntry)->color_code())); + setHotCue(track, position, static_cast((*cueExtendedEntry)->hot_cue() - 1), toUnicode((*cueExtendedEntry)->comment()), static_cast((*cueExtendedEntry)->color_code())); } break; } } From 2abac5c138a9c4846634cbd790eb566938592332 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Thu, 24 Oct 2019 09:32:21 +1100 Subject: [PATCH 35/36] Disable temporarily setting hotcue colors --- src/library/rekordbox/rekordboxfeature.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 1c6bcb58f2d..9d5eba56ea6 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -663,7 +663,7 @@ void clearDeviceTables(QSqlDatabase& database, TreeItem* child) { transaction.commit(); } -void setHotCue(TrackPointer track, double position, int id, QString label, int colorCode) { +void setHotCue(TrackPointer track, double position, int id, QString label, int colorCode, int colorRed, int colorGreen, int colorBlue) { CuePointer pCue; bool hotCueFound = false; @@ -687,6 +687,12 @@ void setHotCue(TrackPointer track, double position, int id, QString label, int c pCue->setLabel(label); } + /* +TODO(ehendrikd): +Update setting hotcue colors once proposed PR is merged +allowing custom hotcue colors/palette +See: https://github.com/mixxxdj/mixxx/pull/2119 + // Map 17 possible Rekordbox hotcue colors to closest Mixxx hotcue colors switch (colorCode) { case 38: @@ -724,6 +730,7 @@ void setHotCue(TrackPointer track, double position, int id, QString label, int c pCue->setColor(Color::kPredefinedColorsSet.noColor); break; } +*/ } void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool ignoreBeatsAndLegacyCues, QString anlzPath) { @@ -806,7 +813,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } } break; case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { - setHotCue(track, position, static_cast((*cueEntry)->hot_cue() - 1), QString(), -1); + setHotCue(track, position, static_cast((*cueEntry)->hot_cue() - 1), QString(), -1, -1, -1, -1); } break; } } @@ -842,7 +849,7 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } } break; case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { - setHotCue(track, position, static_cast((*cueExtendedEntry)->hot_cue() - 1), toUnicode((*cueExtendedEntry)->comment()), static_cast((*cueExtendedEntry)->color_code())); + setHotCue(track, position, static_cast((*cueExtendedEntry)->hot_cue() - 1), toUnicode((*cueExtendedEntry)->comment()), static_cast((*cueExtendedEntry)->color_code()), static_cast((*cueExtendedEntry)->color_red()), static_cast((*cueExtendedEntry)->color_green()), static_cast((*cueExtendedEntry)->color_blue())); } break; } } From 11a57292c4b2f53c7adaaf18bc4e053d7232b223 Mon Sep 17 00:00:00 2001 From: Evan Dekker Date: Tue, 12 Nov 2019 11:31:19 +1100 Subject: [PATCH 36/36] Updates to compile with #2103 --- src/library/rekordbox/rekordboxfeature.cpp | 55 +++++++++++++--------- src/library/rekordbox/rekordboxfeature.h | 2 +- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 9d5eba56ea6..7046edb63d5 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -679,8 +679,8 @@ void setHotCue(TrackPointer track, double position, int id, QString label, int c pCue = CuePointer(track->createAndAddCue()); } - pCue->setType(Cue::CUE); - pCue->setPosition(position); + pCue->setType(Cue::Type::HotCue); + pCue->setStartPosition(position); pCue->setHotCue(id); if (!label.isNull()) { @@ -688,10 +688,12 @@ void setHotCue(TrackPointer track, double position, int id, QString label, int c } /* -TODO(ehendrikd): -Update setting hotcue colors once proposed PR is merged -allowing custom hotcue colors/palette -See: https://github.com/mixxxdj/mixxx/pull/2119 + TODO(ehendrikd): + Update setting hotcue colors once proposed PR is merged + allowing custom hotcue colors/palette + See: + https://github.com/mixxxdj/mixxx/pull/2119 + https://github.com/mixxxdj/mixxx/pull/2345 // Map 17 possible Rekordbox hotcue colors to closest Mixxx hotcue colors switch (colorCode) { @@ -749,8 +751,8 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i double cueLoadPosition = kLongestPosition; QString cueLoadComment; - double cueLoopPosition = kLongestPosition; - double loopLength = -1.0; + double cueLoopStartPosition = kLongestPosition; + double cueLoopEndPosition = kLongestPosition; for (std::vector::iterator section = anlz.sections()->begin(); section != anlz.sections()->end(); ++section) { switch ((*section)->fourcc()) { @@ -805,9 +807,14 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } break; case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { // As Mixxx can only have 1 saved loop, use the first occurance of a memory loop relative to the start of the track - if (position < cueLoopPosition) { - cueLoopPosition = position; - loopLength = sampleRateFrames * static_cast((*cueEntry)->loop_time() - (*cueEntry)->time()); + if (position < cueLoopStartPosition) { + cueLoopStartPosition = position; + int endTime = static_cast((*cueEntry)->loop_time()) - timingOffset; + // Ensure no offset times are less than 1 + if (endTime < 1) { + endTime = 1; + } + cueLoopEndPosition = sampleRateFrames * static_cast(endTime); } } break; } @@ -841,9 +848,14 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } break; case rekordbox_anlz_t::CUE_ENTRY_TYPE_LOOP: { // As Mixxx can only have 1 saved loop, use the first occurance of a memory loop relative to the start of the track - if (position < cueLoopPosition) { - cueLoopPosition = position; - loopLength = sampleRateFrames * static_cast((*cueExtendedEntry)->loop_time() - (*cueExtendedEntry)->time()); + if (position < cueLoopStartPosition) { + cueLoopStartPosition = position; + int endTime = static_cast((*cueExtendedEntry)->loop_time()) - timingOffset; + // Ensure no offset times are less than 1 + if (endTime < 1) { + endTime = 1; + } + cueLoopEndPosition = sampleRateFrames * static_cast(endTime); } } break; } @@ -860,18 +872,17 @@ void readAnalyze(TrackPointer track, double sampleRate, int timingOffset, bool i } if (cueLoadPosition < kLongestPosition) { - track->setCuePoint(CuePosition(cueLoadPosition, Cue::MANUAL)); - CuePointer pLoadCue = track->findCueByType(Cue::LOAD); + track->setCuePoint(CuePosition(cueLoadPosition)); + CuePointer pLoadCue = track->findCueByType(Cue::Type::MainCue); if (!cueLoadComment.isNull()) { pLoadCue->setLabel(cueLoadComment); } } - if (cueLoopPosition < kLongestPosition) { + if (cueLoopStartPosition < kLongestPosition) { CuePointer pCue(track->createAndAddCue()); - pCue->setPosition(cueLoopPosition); - pCue->setLength(loopLength); - pCue->setSource(Cue::MANUAL); - pCue->setType(Cue::LOOP); + pCue->setStartPosition(cueLoopStartPosition); + pCue->setEndPosition(cueLoopEndPosition); + pCue->setType(Cue::Type::Loop); } } @@ -1056,7 +1067,7 @@ RekordboxFeature::~RekordboxFeature() { delete m_pRekordboxPlaylistModel; } -void RekordboxFeature::bindWidget(WLibrary* libraryWidget, +void RekordboxFeature::bindLibraryWidget(WLibrary* libraryWidget, KeyboardEventFilter* keyboard) { Q_UNUSED(keyboard); WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget); diff --git a/src/library/rekordbox/rekordboxfeature.h b/src/library/rekordbox/rekordboxfeature.h index 177d5cd4bd9..7add5f7a38f 100644 --- a/src/library/rekordbox/rekordboxfeature.h +++ b/src/library/rekordbox/rekordboxfeature.h @@ -64,7 +64,7 @@ class RekordboxFeature : public BaseExternalLibraryFeature { QVariant title(); QIcon getIcon(); static bool isSupported(); - void bindWidget(WLibrary* libraryWidget, + void bindLibraryWidget(WLibrary* libraryWidget, KeyboardEventFilter* keyboard) override; TreeItemModel* getChildModel();