diff --git a/build/depends.py b/build/depends.py index 3cab15e57bb..3cafd41f9af 100644 --- a/build/depends.py +++ b/build/depends.py @@ -481,6 +481,30 @@ 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") + +# 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' @@ -1043,6 +1067,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", @@ -1545,7 +1573,7 @@ def depends(self, build): FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib, ProtoBuf, Chromaprint, RubberBand, SecurityFramework, CoreServices, IOKit, QtScriptByteArray, Reverb, FpClassify, PortAudioRingBuffer, LAME, - QueenMaryDsp] + QueenMaryDsp, Kaitai, MP3GuessEnc] def post_dependency_check_configure(self, build, conf): """Sets up additional things in the Environment that must happen diff --git a/lib/kaitai/LICENSE b/lib/kaitai/LICENSE new file mode 100644 index 00000000000..a1867624b1f --- /dev/null +++ b/lib/kaitai/LICENSE @@ -0,0 +1,7 @@ +Copyright 2016-2019 Kaitai Project: MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lib/kaitai/custom_decoder.h b/lib/kaitai/custom_decoder.h new file mode 100755 index 00000000000..6da7f5fd23c --- /dev/null +++ b/lib/kaitai/custom_decoder.h @@ -0,0 +1,16 @@ +#ifndef KAITAI_CUSTOM_DECODER_H +#define KAITAI_CUSTOM_DECODER_H + +#include + +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/lib/mp3guessenc-0.27.4/COPYING b/lib/mp3guessenc-0.27.4/COPYING new file mode 100644 index 00000000000..4362b49151d --- /dev/null +++ b/lib/mp3guessenc-0.27.4/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + 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..32d79d4536c --- /dev/null +++ b/lib/mp3guessenc-0.27.4/mp3g_io_config.h @@ -0,0 +1,55 @@ +/* + * 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 + */ +/* Modifed by Evan Dekker 2019-09-26 */ + +#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__) || defined(__WINDOWS__) +#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..f0a1c34c647 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/mp3guessenc.c @@ -0,0 +1,3389 @@ +/* + * 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 +#if defined(__WINDOWS__) +#include +#else +#include +#endif +#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 mp3guessenc_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 mp3guessenc_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 = mp3guessenc_crc_update(curr_crc, &dummy, NULL, 8); + dummy = (unsigned char)(header&0xFF); + curr_crc = mp3guessenc_crc_update(curr_crc, &dummy, NULL, 8); + + curr_crc = mp3guessenc_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 = mp3guessenc_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 = mp3guessenc_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 (mp3guessenc_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..95c9158a6f6 --- /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__) || 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 + 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..b2841877cc7 --- /dev/null +++ b/lib/mp3guessenc-0.27.4/tags.h @@ -0,0 +1,116 @@ +/* + * 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 + +#if defined(__WINDOWS__) +#include +#endif + +#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/res/images/library/ic_library_rekordbox.svg b/res/images/library/ic_library_rekordbox.svg new file mode 100644 index 00000000000..6e5e4bb676f --- /dev/null +++ b/res/images/library/ic_library_rekordbox.svg @@ -0,0 +1,46 @@ + +image/svg+xml \ No newline at end of file diff --git a/res/mixxx.qrc b/res/mixxx.qrc index c8c2ad59961..c4286344691 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 fc6cc4ca890..d5babba85aa 100644 --- a/res/schema.xml +++ b/res/schema.xml @@ -448,4 +448,37 @@ METADATA + + + Tables for Rekordbox library feature + + + 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 + ); + CREATE TABLE IF NOT EXISTS rekordbox_playlists ( + id INTEGER PRIMARY KEY, + name TEXT 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/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/library.cpp b/src/library/library.cpp index 9d4f6e3d82f..d152c0f79b9 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -29,6 +29,7 @@ #include "library/rhythmbox/rhythmboxfeature.h" #include "library/setlogfeature.h" #include "library/traktor/traktorfeature.h" +#include "library/rekordbox/rekordboxfeature.h" #include "util/db/dbconnectionpooled.h" #include "util/sandbox.h" @@ -191,7 +192,15 @@ Library::Library( pConfig->getValue(ConfigKey(kConfigGroup,"ShowTraktorLibrary"), true)) { addFeature(new TraktorFeature(this, m_pTrackCollection)); } - + + // 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. + if (pConfig->getValue(ConfigKey(kConfigGroup, "ShowRekordboxLibrary"), true)) { + addFeature(new RekordboxFeature(this, m_pTrackCollection)); + } + for (const auto& externalTrackCollection : m_externalTrackCollections) { auto feature = externalTrackCollection->newLibraryFeature(this); if (feature) { diff --git a/src/library/rekordbox/rekordbox_anlz.cpp b/src/library/rekordbox/rekordbox_anlz.cpp new file mode 100644 index 00000000000..bb31bff98bc --- /dev/null +++ b/src/library/rekordbox/rekordbox_anlz.cpp @@ -0,0 +1,498 @@ +// 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::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; + _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::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")); + 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) { + 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::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; + _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::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; + _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 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); + 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 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); + 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..19fbc6d9d38 --- /dev/null +++ b/src/library/rekordbox/rekordbox_anlz.h @@ -0,0 +1,1015 @@ +#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 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, + SECTION_TAGS_WAVE_SCROLL = 1347900979, + SECTION_TAGS_WAVE_COLOR_PREVIEW = 1347900980, + SECTION_TAGS_WAVE_COLOR_SCROLL = 1347900981 + }; + + 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 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); + +private: + void _read(); + +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. + */ + + 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; } + }; + + 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; + 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; + + 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. + */ + + 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 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. + */ + + 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; } + }; + + /** + * 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: + + 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_ diff --git a/src/library/rekordbox/rekordbox_pdb.cpp b/src/library/rekordbox/rekordbox_pdb.cpp new file mode 100644 index 00000000000..6ad2d48c064 --- /dev/null +++ b/src/library/rekordbox/rekordbox_pdb.cpp @@ -0,0 +1,1008 @@ +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "rekordbox_pdb.h" + + + +rekordbox_pdb_t::rekordbox_pdb_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, rekordbox_pdb_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = this; + _read(); +} + +void rekordbox_pdb_t::_read() { + m__unnamed0 = m__io->read_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 100644 index 00000000000..7046edb63d5 --- /dev/null +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -0,0 +1,1279 @@ +// rekordboxfeature.cpp +// Created 05/24/2019 by Evan Dekker + +#include +#include +#include +#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/color/color.h" +#include "util/db/dbconnectionpooled.h" +#include "util/db/dbconnectionpooler.h" +#include "util/file.h" +#include "util/sandbox.h" +#include "waveform/waveform.h" + +#include "widget/wlibrary.h" +#include "widget/wlibrarytextbrowser.h" + +#define IS_RECORDBOX_DEVICE "::isRecordboxDevice::" +#define IS_NOT_RECORDBOX_DEVICE "::isNotRecordboxDevice::" + +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); + 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."; + } + } +} + +// 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); + + 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() + kPDBPath); + + if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { + TreeItem* foundDevice = new TreeItem(rekordboxFeature); + 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 /media/$USER to devices. + QDir mediaUserDir("/media/" + qgetenv("USER")); + devices += mediaUserDir.entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); + + // Add folders under /run/media/$USER to devices. + QDir runMediaUserDir("/run/media/" + qgetenv("USER")); + devices += runMediaUserDir.entryInfoList( + QDir::AllDirs | QDir::NoDotAndDotDot); + + foreach (QFileInfo device, devices) { + QFileInfo rbDBFileInfo(device.filePath() + "/" + kPDBPath); + + if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { + TreeItem* foundDevice = new TreeItem(rekordboxFeature); + 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() + "/" + kPDBPath); + + if (rbDBFileInfo.exists() && rbDBFileInfo.isFile()) { + TreeItem* foundDevice = new TreeItem(rekordboxFeature); + QList data; + + data << device.filePath(); + data << IS_RECORDBOX_DEVICE; + + foundDevice->setLabel(device.fileName()); + foundDevice->setData(QVariant(data)); + + foundDevices << foundDevice; + } + } +#endif + + return foundDevices; +} + +template +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". + +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 toUnicode(longUtf16beString->text()); + } + + return QString(); +} + +int createDevicePlaylist(QSqlDatabase& database, QString devicePath) { + int playlistID = -1; + + 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 playlistID; + } + + 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 playlistID; + } + + while (idQuery.next()) { + playlistID = idQuery.value(idQuery.record().indexOf("id")).toInt(); + } + + return 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 = 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 = static_cast(track->tempo() / 100.0); + int bitrate = static_cast(track->bitrate()); + QString key = keysMap[track->key_id()]; + 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()); + + 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(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) + << "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(mixxx::DbConnectionPoolPtr dbConnectionPool, 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; + } + + // 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. + VERIFY_OR_DEBUG_ASSERT(database.isOpen()) { + 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); + + 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) { + if ((*table)->type() == tableOrder[tableOrderIndex]) { + uint16_t lastIndex = (*table)->last_page()->index(); + rekordbox_pdb_t::page_ref_t* currentRef = (*table)->first_page(); + + while (true) { + 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 = + 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()); + 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()); + 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()); + 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()); + 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); + + 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()); + + 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) { + break; + } else { + currentRef = page->next_page(); + } + } + } + } + } + + if (audioFilesCount > 0 || folderOrPlaylistFound) { + // If we have found anything, recursively build playlist/folder TreeItem children + // for the original device TreeItem + buildPlaylistTree(database, deviceItem, 0, playlistNameMap, playlistIsFolderMap, playlistTreeMap, playlistTrackMap, devicePath, device); + } + + qDebug() << "Found: " << audioFilesCount << " audio files in Rekordbox device " << device; + + transaction.commit(); + + return devicePath; +} + +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 = playlistNameMap[childID]; + + QString currentPath = playlistPath + kPLaylistPathDelimiter + 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(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(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(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 <= static_cast(playlistTrackMap[childID].size()); trackIndex++) { + uint32_t rbTrackID = playlistTrackMap[childID][trackIndex]; + + 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", 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", static_cast(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(database, child, childID, playlistNameMap, playlistIsFolderMap, playlistTreeMap, playlistTrackMap, currentPath, device); + } + } +} + +void clearDeviceTables(QSqlDatabase& database, TreeItem* child) { + ScopedTransaction transaction(database); + + int trackID = -1; + int playlistID = -1; + QSqlQuery tracksQuery(database); + tracksQuery.prepare("select id from rekordbox_library where device=:device"); + tracksQuery.bindValue(":device", child->getLabel()); + + 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(); +} + +void setHotCue(TrackPointer track, double position, int id, QString label, int colorCode, int colorRed, int colorGreen, int colorBlue) { + 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->setType(Cue::Type::HotCue); + pCue->setStartPosition(position); + pCue->setHotCue(id); + + if (!label.isNull()) { + 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 + https://github.com/mixxxdj/mixxx/pull/2345 + + // 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; + } + + 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 sampleRateFrames = sampleRate * 2.0; + + double cueLoadPosition = kLongestPosition; + QString cueLoadComment; + double cueLoopStartPosition = kLongestPosition; + double cueLoopEndPosition = kLongestPosition; + + 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; + + 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: { + 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) { + 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 < 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; + } + } break; + case rekordbox_anlz_t::CUE_LIST_TYPE_HOT_CUES: { + setHotCue(track, position, static_cast((*cueEntry)->hot_cue() - 1), QString(), -1, -1, -1, -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 = toUnicode((*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 < 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; + } + } 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()), static_cast((*cueExtendedEntry)->color_red()), static_cast((*cueExtendedEntry)->color_green()), static_cast((*cueExtendedEntry)->color_blue())); + } break; + } + } + } break; + default: + break; + } + } + + if (cueLoadPosition < kLongestPosition) { + track->setCuePoint(CuePosition(cueLoadPosition)); + CuePointer pLoadCue = track->findCueByType(Cue::Type::MainCue); + if (!cueLoadComment.isNull()) { + pLoadCue->setLabel(cueLoadComment); + } + } + if (cueLoopStartPosition < kLongestPosition) { + CuePointer pCue(track->createAndAddCue()); + pCue->setStartPosition(cueLoopStartPosition); + pCue->setEndPosition(cueLoopEndPosition); + pCue->setType(Cue::Type::Loop); + } +} + +} // anonymous namespace + +RekordboxPlaylistModel::RekordboxPlaylistModel(QObject* parent, + TrackCollection* trackCollection, + QSharedPointer trackSource) + : 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"; + + 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 = 12; + break; + case EXIT_CODE_CASE_B: + timingOffset = 13; + 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, false, anlzPath); + QString anlzPathExt = anlzPath.left(anlzPath.length() - 3) + "EXT"; + 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. + 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" + << "analyze_path"; + 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"); + + //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())); + // initialize the model + m_childModel.setRootItem(std::make_unique(this)); +} + +RekordboxFeature::~RekordboxFeature() { + m_devicesFuture.waitForFinished(); + m_tracksFuture.waitForFinished(); + delete m_pRekordboxPlaylistModel; +} + +void RekordboxFeature::bindLibraryWidget(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 the following from Rekordbox prepared removable devices:"); + 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("
    ")); + for (const auto& item : items) { + html.append(QString("
  • %1
  • ").arg(item)); + } + 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. + //https://bugs.launchpad.net/mixxx/+bug/1744816 + html.append(QString("%1") + .arg(refreshLink)); + return html; +} + +void RekordboxFeature::refreshLibraryModels() { +} + +void RekordboxFeature::activate() { + qDebug() << "RekordboxFeature::activate()"; + + // 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; + + // Let a worker thread do the XML parsing + 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 + // 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(); + TreeItem* root = m_childModel.getRootItem(); + + QSqlDatabase database = m_pTrackCollection->database(); + + if (foundDevices.size() == 0) { + // No Rekordbox devices found + ScopedTransaction transaction(database); + clearTable(database, "rekordbox_playlist_tracks"); + clearTable(database, "rekordbox_library"); + clearTable(database, "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 + clearDeviceTables(database, child); + + 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 100644 index 00000000000..7add5f7a38f --- /dev/null +++ b/src/library/rekordbox/rekordboxfeature.h @@ -0,0 +1,100 @@ +// 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 "library/baseexternallibraryfeature.h" +#include "library/baseexternalplaylistmodel.h" +#include "library/baseexternaltrackmodel.h" +#include "library/treeitemmodel.h" + +class TrackCollection; +class BaseExternalPlaylistModel; + +class RekordboxPlaylistModel : public BaseExternalPlaylistModel { + public: + RekordboxPlaylistModel(QObject* parent, + TrackCollection* pTrackCollection, + QSharedPointer trackSource); + TrackPointer getTrack(const QModelIndex& index) const override; + bool isColumnHiddenByDefault(int column) override; + + protected: + virtual void initSortColumnMapping(); +}; + +class RekordboxFeature : public BaseExternalLibraryFeature { + Q_OBJECT + public: + RekordboxFeature(QObject* parent, TrackCollection*); + ~RekordboxFeature() override; + + QVariant title(); + QIcon getIcon(); + static bool isSupported(); + void bindLibraryWidget(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; + BaseSqlTableModel* getPlaylistModelForPlaylist(QString playlist) override; + + TreeItemModel m_childModel; + TrackCollection* m_pTrackCollection; + 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 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