From c01b7bc4307131f3aa7889e94b24b2c965a7e8fc Mon Sep 17 00:00:00 2001 From: ftiede Date: Wed, 23 May 2018 15:23:53 +0200 Subject: [PATCH 01/16] Add option to verify subsonic server certificate. (#6060) * Add option to verify subsonic server certificate. Defaults to true, as it is safer to have a server certificate verified, even more so, if the server is used over an insecure WAN link. During subsonic configuration the checkbox can be deactivated, so that no certificate verification will occur when talking to a subsonic server, allowing for self-signed certificates. With the proliferation of let's encrypt certificates there's probably less need for this option but it has been requested and hard-coding verify-off is IMHO bad security practice. If a valid certificate has been installed, the configuration file can be modified manually and after a restart Clementine will perform a proper server certificate verification. The patch might need some UI polishing and asks for string translations but is operational so far. * Satisfy CLang format checker. * Use QSettings' default value support. * Consistently use QSettings' default value method. --- src/internet/subsonic/subsonicservice.cpp | 8 ++++++-- src/internet/subsonic/subsonicservice.h | 4 +++- src/internet/subsonic/subsonicsettingspage.cpp | 6 +++++- src/internet/subsonic/subsonicsettingspage.ui | 18 ++++++++++++++---- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/internet/subsonic/subsonicservice.cpp b/src/internet/subsonic/subsonicservice.cpp index edb7f74d6..1970d5931 100644 --- a/src/internet/subsonic/subsonicservice.cpp +++ b/src/internet/subsonic/subsonicservice.cpp @@ -195,6 +195,7 @@ void SubsonicService::ReloadSettings() { username_ = s.value("username").toString(); password_ = s.value("password").toString(); usesslv3_ = s.value("usesslv3").toBool(); + verifycert_ = s.value("verifycert", true).toBool(); Login(); } @@ -225,11 +226,13 @@ void SubsonicService::Login() { } void SubsonicService::Login(const QString& server, const QString& username, - const QString& password, const bool& usesslv3) { + const QString& password, const bool& usesslv3, + const bool& verifycert) { UpdateServer(server); username_ = username; password_ = password; usesslv3_ = usesslv3; + verifycert_ = verifycert; Login(); } @@ -263,7 +266,8 @@ QNetworkReply* SubsonicService::Send(const QUrl& url) { // Don't try and check the authenticity of the SSL certificate - it'll almost // certainly be self-signed. QSslConfiguration sslconfig = QSslConfiguration::defaultConfiguration(); - sslconfig.setPeerVerifyMode(QSslSocket::VerifyNone); + sslconfig.setPeerVerifyMode(verifycert_ ? QSslSocket::VerifyPeer + : QSslSocket::VerifyNone); if (usesslv3_) { sslconfig.setProtocol(QSsl::SslV3); } diff --git a/src/internet/subsonic/subsonicservice.h b/src/internet/subsonic/subsonicservice.h index 3a358741f..22ff1bb14 100644 --- a/src/internet/subsonic/subsonicservice.h +++ b/src/internet/subsonic/subsonicservice.h @@ -97,7 +97,8 @@ class SubsonicService : public InternetService { void Login(); void Login(const QString& server, const QString& username, - const QString& password, const bool& usesslv3); + const QString& password, const bool& usesslv3, + const bool& verifycert); LoginState login_state() const { return login_state_; } @@ -154,6 +155,7 @@ signals: QString username_; QString password_; bool usesslv3_; + bool verifycert_; LoginState login_state_; QString working_server_; // The actual server, possibly post-redirect diff --git a/src/internet/subsonic/subsonicsettingspage.cpp b/src/internet/subsonic/subsonicsettingspage.cpp index a48509f3b..9c5fb41d5 100644 --- a/src/internet/subsonic/subsonicsettingspage.cpp +++ b/src/internet/subsonic/subsonicsettingspage.cpp @@ -45,6 +45,7 @@ SubsonicSettingsPage::SubsonicSettingsPage(SettingsDialog* dialog) ui_->login_state->AddCredentialField(ui_->username); ui_->login_state->AddCredentialField(ui_->password); ui_->login_state->AddCredentialField(ui_->usesslv3); + ui_->login_state->AddCredentialField(ui_->verifycert); ui_->login_state->AddCredentialGroup(ui_->server_group); ui_->login_state->SetAccountTypeText( @@ -63,6 +64,7 @@ void SubsonicSettingsPage::Load() { ui_->username->setText(s.value("username").toString()); ui_->password->setText(s.value("password").toString()); ui_->usesslv3->setChecked(s.value("usesslv3").toBool()); + ui_->verifycert->setChecked(s.value("verifycert", true).toBool()); // If the settings are complete, SubsonicService will have used them already // and @@ -80,6 +82,7 @@ void SubsonicSettingsPage::Save() { s.setValue("username", ui_->username->text()); s.setValue("password", ui_->password->text()); s.setValue("usesslv3", ui_->usesslv3->isChecked()); + s.setValue("verifycert", ui_->verifycert->isChecked()); } void SubsonicSettingsPage::LoginStateChanged( @@ -190,7 +193,8 @@ void SubsonicSettingsPage::ServerEditingFinished() { void SubsonicSettingsPage::Login() { ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress); service_->Login(ui_->server->text(), ui_->username->text(), - ui_->password->text(), ui_->usesslv3->isChecked()); + ui_->password->text(), ui_->usesslv3->isChecked(), + ui_->verifycert->isChecked()); } void SubsonicSettingsPage::Logout() { diff --git a/src/internet/subsonic/subsonicsettingspage.ui b/src/internet/subsonic/subsonicsettingspage.ui index 4b7fd08a6..e2aa61a77 100644 --- a/src/internet/subsonic/subsonicsettingspage.ui +++ b/src/internet/subsonic/subsonicsettingspage.ui @@ -44,17 +44,17 @@ - + QLineEdit::Password - + - + @@ -64,7 +64,17 @@ - + + + + Verify Server certificate + + + true + + + + Login From 6f3032a1ec5bf6fb09e6f9e595ef4a1c63c8ac5a Mon Sep 17 00:00:00 2001 From: aqua36 Date: Mon, 4 Jun 2018 11:55:09 +0300 Subject: [PATCH 02/16] add genius.com as lyrics provider (#6073) --- data/lyrics/ultimate_providers.xml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/data/lyrics/ultimate_providers.xml b/data/lyrics/ultimate_providers.xml index 7eb2ad702..563fc11ca 100644 --- a/data/lyrics/ultimate_providers.xml +++ b/data/lyrics/ultimate_providers.xml @@ -60,6 +60,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + From ef5a23a3fef5e4b62928a6da4ca1279a3c33f7e6 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Wed, 6 Jun 2018 22:47:08 +0200 Subject: [PATCH 03/16] Bump taglib --- 3rdparty/taglib/CMakeLists.txt | 25 +- 3rdparty/taglib/ape/apefile.cpp | 12 + 3rdparty/taglib/ape/apefile.h | 9 + 3rdparty/taglib/ape/apetag.cpp | 29 +- 3rdparty/taglib/asf/asfattribute.cpp | 52 ++- 3rdparty/taglib/asf/asfattribute.h | 2 +- 3rdparty/taglib/asf/asffile.cpp | 54 ++- 3rdparty/taglib/asf/asffile.h | 9 + 3rdparty/taglib/asf/asftag.cpp | 6 +- 3rdparty/taglib/audioproperties.cpp | 123 ++----- 3rdparty/taglib/fileref.cpp | 279 +++++++++++---- 3rdparty/taglib/fileref.h | 4 +- 3rdparty/taglib/flac/flacfile.cpp | 27 +- 3rdparty/taglib/flac/flacfile.h | 9 + 3rdparty/taglib/flac/flacpicture.cpp | 8 +- .../taglib/flac/flacunknownmetadatablock.cpp | 5 +- 3rdparty/taglib/it/itproperties.cpp | 2 +- 3rdparty/taglib/mod/modproperties.cpp | 2 +- 3rdparty/taglib/mod/modtag.cpp | 5 +- 3rdparty/taglib/mp4/mp4atom.cpp | 25 +- 3rdparty/taglib/mp4/mp4file.cpp | 18 + 3rdparty/taglib/mp4/mp4file.h | 9 + 3rdparty/taglib/mp4/mp4tag.cpp | 38 +- 3rdparty/taglib/mpc/mpcfile.cpp | 13 + 3rdparty/taglib/mpc/mpcfile.h | 9 + .../id3v2/frames/attachedpictureframe.cpp | 15 +- .../taglib/mpeg/id3v2/frames/chapterframe.cpp | 21 +- .../mpeg/id3v2/frames/commentsframe.cpp | 15 +- .../id3v2/frames/eventtimingcodesframe.cpp | 14 +- .../frames/generalencapsulatedobjectframe.cpp | 15 +- .../mpeg/id3v2/frames/ownershipframe.cpp | 15 +- .../taglib/mpeg/id3v2/frames/podcastframe.cpp | 10 +- .../mpeg/id3v2/frames/popularimeterframe.cpp | 15 +- .../taglib/mpeg/id3v2/frames/privateframe.cpp | 15 +- .../mpeg/id3v2/frames/relativevolumeframe.cpp | 15 +- .../id3v2/frames/synchronizedlyricsframe.cpp | 16 +- .../id3v2/frames/tableofcontentsframe.cpp | 22 +- .../mpeg/id3v2/frames/tableofcontentsframe.h | 2 + .../id3v2/frames/textidentificationframe.cpp | 13 +- .../frames/uniquefileidentifierframe.cpp | 12 +- .../taglib/mpeg/id3v2/frames/unknownframe.cpp | 10 +- .../frames/unsynchronizedlyricsframe.cpp | 16 +- .../taglib/mpeg/id3v2/frames/urllinkframe.cpp | 39 +- .../taglib/mpeg/id3v2/id3v2extendedheader.cpp | 4 +- 3rdparty/taglib/mpeg/id3v2/id3v2frame.cpp | 26 +- .../taglib/mpeg/id3v2/id3v2framefactory.cpp | 11 +- 3rdparty/taglib/mpeg/id3v2/id3v2tag.cpp | 8 +- 3rdparty/taglib/mpeg/mpegfile.cpp | 156 +++++--- 3rdparty/taglib/mpeg/mpegfile.h | 9 + 3rdparty/taglib/mpeg/mpegheader.cpp | 23 +- 3rdparty/taglib/mpeg/mpegproperties.cpp | 29 +- 3rdparty/taglib/mpeg/mpegutils.h | 16 +- 3rdparty/taglib/ogg/flac/oggflacfile.cpp | 25 +- 3rdparty/taglib/ogg/flac/oggflacfile.h | 11 +- 3rdparty/taglib/ogg/oggfile.cpp | 2 +- 3rdparty/taglib/ogg/oggpage.cpp | 8 +- 3rdparty/taglib/ogg/opus/opusfile.cpp | 15 +- 3rdparty/taglib/ogg/opus/opusfile.h | 12 +- 3rdparty/taglib/ogg/speex/speexfile.cpp | 15 +- 3rdparty/taglib/ogg/speex/speexfile.h | 12 +- 3rdparty/taglib/ogg/vorbis/vorbisfile.cpp | 16 +- 3rdparty/taglib/ogg/vorbis/vorbisfile.h | 11 +- 3rdparty/taglib/ogg/xiphcomment.cpp | 143 ++++---- 3rdparty/taglib/riff/aiff/aifffile.cpp | 13 + 3rdparty/taglib/riff/aiff/aifffile.h | 8 + 3rdparty/taglib/riff/rifffile.cpp | 37 +- 3rdparty/taglib/riff/wav/wavfile.cpp | 21 +- 3rdparty/taglib/riff/wav/wavfile.h | 9 + 3rdparty/taglib/s3m/s3mproperties.cpp | 2 +- 3rdparty/taglib/taglib_config.h.cmake | 5 + 3rdparty/taglib/tagunion.cpp | 5 +- 3rdparty/taglib/tagutils.cpp | 26 ++ 3rdparty/taglib/tagutils.h | 6 + 3rdparty/taglib/toolkit/taglib.h | 9 +- 3rdparty/taglib/toolkit/tbytevector.cpp | 93 ++--- 3rdparty/taglib/toolkit/tbytevectorstream.cpp | 6 +- 3rdparty/taglib/toolkit/tdebug.cpp | 21 +- 3rdparty/taglib/toolkit/tdebug.h | 14 +- 3rdparty/taglib/toolkit/tfile.cpp | 29 +- 3rdparty/taglib/toolkit/tfile.h | 2 +- 3rdparty/taglib/toolkit/tfilestream.cpp | 59 ++-- 3rdparty/taglib/toolkit/tiostream.cpp | 84 ++--- 3rdparty/taglib/toolkit/tlist.h | 5 + 3rdparty/taglib/toolkit/tlist.tcc | 22 +- 3rdparty/taglib/toolkit/tmap.h | 5 + 3rdparty/taglib/toolkit/tmap.tcc | 22 +- 3rdparty/taglib/toolkit/trefcounter.cpp | 11 +- 3rdparty/taglib/toolkit/trefcounter.h | 1 + 3rdparty/taglib/toolkit/tstring.cpp | 255 +++++--------- 3rdparty/taglib/toolkit/tstring.h | 7 + 3rdparty/taglib/toolkit/tutils.h | 61 +--- 3rdparty/taglib/toolkit/tzlib.cpp | 46 +-- 3rdparty/taglib/toolkit/unicode.cpp | 303 ---------------- 3rdparty/taglib/toolkit/unicode.h | 149 -------- 3rdparty/taglib/trueaudio/trueaudiofile.cpp | 12 + 3rdparty/taglib/trueaudio/trueaudiofile.h | 9 + 3rdparty/taglib/wavpack/wavpackfile.cpp | 12 + 3rdparty/taglib/wavpack/wavpackfile.h | 8 + 3rdparty/taglib/xm/xmfile.cpp | 2 +- 3rdparty/taglib/xm/xmproperties.cpp | 2 +- 3rdparty/utf8-cpp/CMakeLists.txt | 2 + 3rdparty/utf8-cpp/checked.h | 327 +++++++++++++++++ 3rdparty/utf8-cpp/core.h | 332 ++++++++++++++++++ CMakeLists.txt | 3 +- 104 files changed, 2076 insertions(+), 1575 deletions(-) delete mode 100644 3rdparty/taglib/toolkit/unicode.cpp delete mode 100644 3rdparty/taglib/toolkit/unicode.h create mode 100644 3rdparty/utf8-cpp/CMakeLists.txt create mode 100644 3rdparty/utf8-cpp/checked.h create mode 100644 3rdparty/utf8-cpp/core.h diff --git a/3rdparty/taglib/CMakeLists.txt b/3rdparty/taglib/CMakeLists.txt index 6aac92bdb..1ee3e8576 100644 --- a/3rdparty/taglib/CMakeLists.txt +++ b/3rdparty/taglib/CMakeLists.txt @@ -1,3 +1,5 @@ +cmake_minimum_required(VERSION 2.8.11) +set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-delete-non-virtual-dtor") set(TAGLIB_SOVERSION_CURRENT 17) @@ -45,6 +47,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/s3m ${CMAKE_CURRENT_SOURCE_DIR}/it ${CMAKE_CURRENT_SOURCE_DIR}/xm + ${CMAKE_SOURCE_DIR}/3rdparty ) if(ZLIB_FOUND) @@ -53,10 +56,6 @@ elseif(HAVE_ZLIB_SOURCE) include_directories(${ZLIB_SOURCE}) endif() -if(HAVE_BOOST_BYTESWAP OR HAVE_BOOST_ATOMIC OR HAVE_BOOST_ZLIB) - include_directories(${Boost_INCLUDE_DIR}) -endif() - set(tag_HDRS tag.h fileref.h @@ -333,12 +332,6 @@ set(toolkit_SRCS toolkit/tzlib.cpp ) -if(NOT WIN32) - set(unicode_SRCS - toolkit/unicode.cpp - ) -endif() - if(HAVE_ZLIB_SOURCE) set(zlib_SRCS ${ZLIB_SOURCE}/adler32.c @@ -355,7 +348,7 @@ set(tag_LIB_SRCS ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} - ${unicode_SRCS} ${zlib_SRCS} + ${zlib_SRCS} tag.cpp tagunion.cpp fileref.cpp @@ -365,18 +358,10 @@ set(tag_LIB_SRCS add_library(tag STATIC ${tag_LIB_SRCS} ${tag_HDRS}) -if(ZLIB_FOUND) +if(HAVE_ZLIB AND NOT HAVE_ZLIB_SOURCE) target_link_libraries(tag ${ZLIB_LIBRARIES}) endif() -if(HAVE_BOOST_ATOMIC) - target_link_libraries(tag ${Boost_ATOMIC_LIBRARY}) -endif() - -if(HAVE_BOOST_ZLIB) - target_link_libraries(tag ${Boost_IOSTREAMS_LIBRARY} ${Boost_ZLIB_LIBRARY}) -endif() - set_target_properties(tag PROPERTIES VERSION ${TAGLIB_SOVERSION_MAJOR}.${TAGLIB_SOVERSION_MINOR}.${TAGLIB_SOVERSION_PATCH} SOVERSION ${TAGLIB_SOVERSION_MAJOR} diff --git a/3rdparty/taglib/ape/apefile.cpp b/3rdparty/taglib/ape/apefile.cpp index 9f298aaf4..a10c1f646 100644 --- a/3rdparty/taglib/ape/apefile.cpp +++ b/3rdparty/taglib/ape/apefile.cpp @@ -83,6 +83,18 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool APE::File::isSupported(IOStream *stream) +{ + // An APE file has an ID "MAC " somewhere. An ID3v2 tag may precede. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true); + return (buffer.find("MAC ") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/ape/apefile.h b/3rdparty/taglib/ape/apefile.h index cfb19ff73..267778bae 100644 --- a/3rdparty/taglib/ape/apefile.h +++ b/3rdparty/taglib/ape/apefile.h @@ -211,6 +211,15 @@ namespace TagLib { */ bool hasID3v1Tag() const; + /*! + * Returns whether or not the given \a stream can be opened as an APE + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/ape/apetag.cpp b/3rdparty/taglib/ape/apetag.cpp index 89ef8ff41..79e1d5cc7 100644 --- a/3rdparty/taglib/ape/apetag.cpp +++ b/3rdparty/taglib/ape/apetag.cpp @@ -47,23 +47,24 @@ using namespace APE; namespace { - bool isKeyValid(const char *key, size_t length) + const unsigned int MinKeyLength = 2; + const unsigned int MaxKeyLength = 255; + + bool isKeyValid(const ByteVector &key) { const char *invalidKeys[] = { "ID3", "TAG", "OGGS", "MP+", 0 }; - if(length < 2 || length > 255) - return false; - // only allow printable ASCII including space (32..126) - for(const char *p = key; p < key + length; ++p) { - const int c = static_cast(*p); + for(ByteVector::ConstIterator it = key.begin(); it != key.end(); ++it) { + const int c = static_cast(*it); if(c < 32 || c > 126) return false; } + const String upperKey = String(key).upper(); for(size_t i = 0; invalidKeys[i] != 0; ++i) { - if(Utils::equalsIgnoreCase(key, invalidKeys[i])) + if(upperKey == invalidKeys[i]) return false; } @@ -191,7 +192,7 @@ void APE::Tag::setGenre(const String &s) void APE::Tag::setYear(unsigned int i) { - if(i <= 0) + if(i == 0) removeItem("YEAR"); else addValue("YEAR", String::number(i), true); @@ -199,7 +200,7 @@ void APE::Tag::setYear(unsigned int i) void APE::Tag::setTrack(unsigned int i) { - if(i <= 0) + if(i == 0) removeItem("TRACK"); else addValue("TRACK", String::number(i), true); @@ -296,11 +297,10 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps) bool APE::Tag::checkKey(const String &key) { - if(!key.isLatin1()) + if(key.size() < MinKeyLength || key.size() > MaxKeyLength) return false; - const std::string data = key.to8Bit(false); - return isKeyValid(data.c_str(), data.size()); + return isKeyValid(key.data(String::UTF8)); } APE::Footer *APE::Tag::footer() const @@ -419,7 +419,10 @@ void APE::Tag::parse(const ByteVector &data) const unsigned int keyLength = nullPos - pos - 8; const unsigned int valLegnth = data.toUInt(pos, false); - if(isKeyValid(&data[pos + 8], keyLength)){ + if(keyLength >= MinKeyLength + && keyLength <= MaxKeyLength + && isKeyValid(data.mid(pos + 8, keyLength))) + { APE::Item item; item.parse(data.mid(pos)); diff --git a/3rdparty/taglib/asf/asfattribute.cpp b/3rdparty/taglib/asf/asfattribute.cpp index 1e6ed7051..6faf7973b 100644 --- a/3rdparty/taglib/asf/asfattribute.cpp +++ b/3rdparty/taglib/asf/asfattribute.cpp @@ -36,20 +36,16 @@ using namespace TagLib; class ASF::Attribute::AttributePrivate : public RefCounter { public: - AttributePrivate() - : pictureValue(ASF::Picture::fromInvalid()), - stream(0), - language(0) {} + AttributePrivate() : + pictureValue(ASF::Picture::fromInvalid()), + numericValue(0), + stream(0), + language(0) {} AttributeTypes type; String stringValue; ByteVector byteVectorValue; ASF::Picture pictureValue; - union { - unsigned int intValue; - unsigned short shortValue; - unsigned long long longLongValue; - bool boolValue; - }; + unsigned long long numericValue; int stream; int language; }; @@ -95,28 +91,28 @@ ASF::Attribute::Attribute(unsigned int value) : d(new AttributePrivate()) { d->type = DWordType; - d->intValue = value; + d->numericValue = value; } ASF::Attribute::Attribute(unsigned long long value) : d(new AttributePrivate()) { d->type = QWordType; - d->longLongValue = value; + d->numericValue = value; } ASF::Attribute::Attribute(unsigned short value) : d(new AttributePrivate()) { d->type = WordType; - d->shortValue = value; + d->numericValue = value; } ASF::Attribute::Attribute(bool value) : d(new AttributePrivate()) { d->type = BoolType; - d->boolValue = value; + d->numericValue = value; } ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other) @@ -157,22 +153,22 @@ ByteVector ASF::Attribute::toByteVector() const unsigned short ASF::Attribute::toBool() const { - return d->shortValue; + return d->numericValue ? 1 : 0; } unsigned short ASF::Attribute::toUShort() const { - return d->shortValue; + return static_cast(d->numericValue); } unsigned int ASF::Attribute::toUInt() const { - return d->intValue; + return static_cast(d->numericValue); } unsigned long long ASF::Attribute::toULongLong() const { - return d->longLongValue; + return static_cast(d->numericValue); } ASF::Picture ASF::Attribute::toPicture() const @@ -212,24 +208,24 @@ String ASF::Attribute::parse(ASF::File &f, int kind) switch(d->type) { case WordType: - d->shortValue = readWORD(&f); + d->numericValue = readWORD(&f); break; case BoolType: if(kind == 0) { - d->boolValue = (readDWORD(&f) == 1); + d->numericValue = (readDWORD(&f) != 0); } else { - d->boolValue = (readWORD(&f) == 1); + d->numericValue = (readWORD(&f) != 0); } break; case DWordType: - d->intValue = readDWORD(&f); + d->numericValue = readDWORD(&f); break; case QWordType: - d->longLongValue = readQWORD(&f); + d->numericValue = readQWORD(&f); break; case UnicodeType: @@ -280,24 +276,24 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const switch (d->type) { case WordType: - data.append(ByteVector::fromShort(d->shortValue, false)); + data.append(ByteVector::fromShort(toUShort(), false)); break; case BoolType: if(kind == 0) { - data.append(ByteVector::fromUInt(d->boolValue ? 1 : 0, false)); + data.append(ByteVector::fromUInt(toBool(), false)); } else { - data.append(ByteVector::fromShort(d->boolValue ? 1 : 0, false)); + data.append(ByteVector::fromShort(toBool(), false)); } break; case DWordType: - data.append(ByteVector::fromUInt(d->intValue, false)); + data.append(ByteVector::fromUInt(toUInt(), false)); break; case QWordType: - data.append(ByteVector::fromLongLong(d->longLongValue, false)); + data.append(ByteVector::fromLongLong(toULongLong(), false)); break; case UnicodeType: diff --git a/3rdparty/taglib/asf/asfattribute.h b/3rdparty/taglib/asf/asfattribute.h index 64979216f..1738eb459 100644 --- a/3rdparty/taglib/asf/asfattribute.h +++ b/3rdparty/taglib/asf/asfattribute.h @@ -113,7 +113,7 @@ namespace TagLib /*! * Copies the contents of \a other into this item. */ - ASF::Attribute &operator=(const Attribute &other); + Attribute &operator=(const Attribute &other); /*! * Exchanges the content of the Attribute by the content of \a other. diff --git a/3rdparty/taglib/asf/asffile.cpp b/3rdparty/taglib/asf/asffile.cpp index 8f3952658..fd754803d 100644 --- a/3rdparty/taglib/asf/asffile.cpp +++ b/3rdparty/taglib/asf/asffile.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "asffile.h" #include "asftag.h" @@ -258,7 +259,6 @@ ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->contentDescriptionObject = this; const int titleLength = readWORD(file); const int artistLength = readWORD(file); const int copyrightLength = readWORD(file); @@ -299,7 +299,6 @@ ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() cons void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->extendedContentDescriptionObject = this; int count = readWORD(file); while(count--) { ASF::Attribute attribute; @@ -323,7 +322,6 @@ ByteVector ASF::File::FilePrivate::MetadataObject::guid() const void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->metadataObject = this; int count = readWORD(file); while(count--) { ASF::Attribute attribute; @@ -347,7 +345,6 @@ ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->metadataLibraryObject = this; int count = readWORD(file); while(count--) { ASF::Attribute attribute; @@ -376,7 +373,6 @@ ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->headerExtensionObject = this; file->seek(18, File::Current); long long dataSize = readDWORD(file); long long dataPos = 0; @@ -394,10 +390,12 @@ void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, unsig } BaseObject *obj; if(guid == metadataGuid) { - obj = new MetadataObject(); + file->d->metadataObject = new MetadataObject(); + obj = file->d->metadataObject; } else if(guid == metadataLibraryGuid) { - obj = new MetadataLibraryObject(); + file->d->metadataLibraryObject = new MetadataLibraryObject(); + obj = file->d->metadataLibraryObject; } else { obj = new UnknownObject(guid); @@ -473,6 +471,18 @@ void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, unsigned in } } +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool ASF::File::isSupported(IOStream *stream) +{ + // An ASF file has to start with the designated GUID. + + const ByteVector id = Utils::readHeader(stream, 16, false); + return (id == headerGuid); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -616,9 +626,8 @@ void ASF::File::read() if(!isValid()) return; - ByteVector guid = readBlock(16); - if(guid != headerGuid) { - debug("ASF: Not an ASF file."); + if(readBlock(16) != headerGuid) { + debug("ASF::File::read(): Not an ASF file."); setValid(false); return; } @@ -639,8 +648,10 @@ void ASF::File::read() } seek(2, Current); + FilePrivate::FilePropertiesObject *filePropertiesObject = 0; + FilePrivate::StreamPropertiesObject *streamPropertiesObject = 0; for(int i = 0; i < numObjects; i++) { - guid = readBlock(16); + const ByteVector guid = readBlock(16); if(guid.size() != 16) { setValid(false); break; @@ -652,19 +663,24 @@ void ASF::File::read() } FilePrivate::BaseObject *obj; if(guid == filePropertiesGuid) { - obj = new FilePrivate::FilePropertiesObject(); + filePropertiesObject = new FilePrivate::FilePropertiesObject(); + obj = filePropertiesObject; } else if(guid == streamPropertiesGuid) { - obj = new FilePrivate::StreamPropertiesObject(); + streamPropertiesObject = new FilePrivate::StreamPropertiesObject(); + obj = streamPropertiesObject; } else if(guid == contentDescriptionGuid) { - obj = new FilePrivate::ContentDescriptionObject(); + d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject(); + obj = d->contentDescriptionObject; } else if(guid == extendedContentDescriptionGuid) { - obj = new FilePrivate::ExtendedContentDescriptionObject(); + d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject(); + obj = d->extendedContentDescriptionObject; } else if(guid == headerExtensionGuid) { - obj = new FilePrivate::HeaderExtensionObject(); + d->headerExtensionObject = new FilePrivate::HeaderExtensionObject(); + obj = d->headerExtensionObject; } else if(guid == codecListGuid) { obj = new FilePrivate::CodecListObject(); @@ -680,4 +696,10 @@ void ASF::File::read() obj->parse(this, size); d->objects.append(obj); } + + if(!filePropertiesObject || !streamPropertiesObject) { + debug("ASF::File::read(): Missing mandatory header objects."); + setValid(false); + return; + } } diff --git a/3rdparty/taglib/asf/asffile.h b/3rdparty/taglib/asf/asffile.h index b674da79c..05cf4ee2a 100644 --- a/3rdparty/taglib/asf/asffile.h +++ b/3rdparty/taglib/asf/asffile.h @@ -115,6 +115,15 @@ namespace TagLib { */ virtual bool save(); + /*! + * Returns whether or not the given \a stream can be opened as an ASF + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: void read(); diff --git a/3rdparty/taglib/asf/asftag.cpp b/3rdparty/taglib/asf/asftag.cpp index ed2dba522..20a946f0a 100644 --- a/3rdparty/taglib/asf/asftag.cpp +++ b/3rdparty/taglib/asf/asftag.cpp @@ -39,10 +39,10 @@ public: AttributeListMap attributeListMap; }; -ASF::Tag::Tag() -: TagLib::Tag() +ASF::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; } ASF::Tag::~Tag() diff --git a/3rdparty/taglib/audioproperties.cpp b/3rdparty/taglib/audioproperties.cpp index 217d92870..c29051a64 100644 --- a/3rdparty/taglib/audioproperties.cpp +++ b/3rdparty/taglib/audioproperties.cpp @@ -43,6 +43,39 @@ using namespace TagLib; +// This macro is a workaround for the fact that we can't add virtual functions. +// Should be true virtual functions in taglib2. + +#define VIRTUAL_FUNCTION_WORKAROUND(function_name, default_value) \ + if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else if(dynamic_cast(this)) \ + return dynamic_cast(this)->function_name(); \ + else \ + return (default_value); + class AudioProperties::AudioPropertiesPrivate { @@ -59,98 +92,12 @@ AudioProperties::~AudioProperties() int AudioProperties::lengthInSeconds() const { - // This is an ugly workaround but we can't add a virtual function. - // Should be virtual in taglib2. - - if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if (dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInSeconds(); - - else - return 0; + VIRTUAL_FUNCTION_WORKAROUND(lengthInSeconds, 0) } int AudioProperties::lengthInMilliseconds() const { - // This is an ugly workaround but we can't add a virtual function. - // Should be virtual in taglib2. - - if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else if(dynamic_cast(this)) - return dynamic_cast(this)->lengthInMilliseconds(); - - else - return 0; + VIRTUAL_FUNCTION_WORKAROUND(lengthInMilliseconds, 0) } //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/fileref.cpp b/3rdparty/taglib/fileref.cpp index 3a7f2c65e..935c371bd 100644 --- a/3rdparty/taglib/fileref.cpp +++ b/3rdparty/taglib/fileref.cpp @@ -28,6 +28,7 @@ ***************************************************************************/ #include +#include #include #include #include @@ -59,49 +60,14 @@ namespace typedef List ResolverList; ResolverList fileTypeResolvers; - // Templatized internal functions. T should be String or IOStream*. + // Detect the file type by user-defined resolvers. - template - FileName toFileName(T arg) - { - debug("FileRef::toFileName(): This version should never be called."); - return FileName(L""); - } - - template <> - FileName toFileName(IOStream *arg) - { - return arg->name(); - } - - template <> - FileName toFileName(FileName arg) - { - return arg; - } - - template - File *resolveFileType(T arg, bool readProperties, - AudioProperties::ReadStyle style) - { - debug("FileRef::resolveFileType(): This version should never be called."); - return 0; - } - - template <> - File *resolveFileType(IOStream *arg, bool readProperties, - AudioProperties::ReadStyle style) - { - return 0; - } - - template <> - File *resolveFileType(FileName arg, bool readProperties, - AudioProperties::ReadStyle style) + File *detectByResolvers(FileName fileName, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) { ResolverList::ConstIterator it = fileTypeResolvers.begin(); for(; it != fileTypeResolvers.end(); ++it) { - File *file = (*it)->createFile(arg, readProperties, style); + File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle); if(file) return file; } @@ -109,18 +75,15 @@ namespace return 0; } - template - File* createInternal(T arg, bool readAudioProperties, - AudioProperties::ReadStyle audioPropertiesStyle) - { - File *file = resolveFileType(arg, readAudioProperties, audioPropertiesStyle); - if(file) - return file; + // Detect the file type based on the file extension. + File* detectByExtension(IOStream *stream, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { #ifdef _WIN32 - const String s = toFileName(arg).toString(); + const String s = stream->name().toString(); #else - const String s(toFileName(arg)); + const String s(stream->name()); #endif String ext; @@ -135,49 +98,163 @@ namespace if(ext.isEmpty()) return 0; + // .oga can be any audio in the Ogg container. So leave it to content-based detection. + if(ext == "MP3") - return new MPEG::File(arg, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + return new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); if(ext == "OGG") - return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle); + return new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "FLAC") + return new FLAC::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + if(ext == "MPC") + return new MPC::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "WV") + return new WavPack::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "SPX") + return new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "OPUS") + return new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "TTA") + return new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V") + return new MP4::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "WMA" || ext == "ASF") + return new ASF::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC") + return new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "WAV") + return new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "APE") + return new APE::File(stream, readAudioProperties, audioPropertiesStyle); + // module, nst and wow are possible but uncommon extensions + if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW") + return new Mod::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "S3M") + return new S3M::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "IT") + return new IT::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "XM") + return new XM::File(stream, readAudioProperties, audioPropertiesStyle); + + return 0; + } + + // Detect the file type based on the actual content of the stream. + + File *detectByContent(IOStream *stream, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { + File *file = 0; + + if(MPEG::File::isSupported(stream)) + file = new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + else if(Ogg::Vorbis::File::isSupported(stream)) + file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle); + else if(Ogg::FLAC::File::isSupported(stream)) + file = new Ogg::FLAC::File(stream, readAudioProperties, audioPropertiesStyle); + else if(FLAC::File::isSupported(stream)) + file = new FLAC::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + else if(MPC::File::isSupported(stream)) + file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle); + else if(WavPack::File::isSupported(stream)) + file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle); + else if(Ogg::Speex::File::isSupported(stream)) + file = new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle); + else if(Ogg::Opus::File::isSupported(stream)) + file = new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle); + else if(TrueAudio::File::isSupported(stream)) + file = new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle); + else if(MP4::File::isSupported(stream)) + file = new MP4::File(stream, readAudioProperties, audioPropertiesStyle); + else if(ASF::File::isSupported(stream)) + file = new ASF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(RIFF::AIFF::File::isSupported(stream)) + file = new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(RIFF::WAV::File::isSupported(stream)) + file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle); + else if(APE::File::isSupported(stream)) + file = new APE::File(stream, readAudioProperties, audioPropertiesStyle); + + // isSupported() only does a quick check, so double check the file here. + + if(file) { + if(file->isValid()) + return file; + else + delete file; + } + + return 0; + } + + // Internal function that supports FileRef::create(). + // This looks redundant, but necessary in order not to change the previous + // behavior of FileRef::create(). + + File* createInternal(FileName fileName, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { + File *file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle); + if(file) + return file; + +#ifdef _WIN32 + const String s = fileName.toString(); +#else + const String s(fileName); +#endif + + String ext; + const int pos = s.rfind("."); + if(pos != -1) + ext = s.substr(pos + 1).upper(); + + if(ext.isEmpty()) + return 0; + + if(ext == "MP3") + return new MPEG::File(fileName, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + if(ext == "OGG") + return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "OGA") { /* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */ - File *file = new Ogg::FLAC::File(arg, readAudioProperties, audioPropertiesStyle); + File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle); if(file->isValid()) return file; delete file; - return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle); + return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); } if(ext == "FLAC") - return new FLAC::File(arg, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + return new FLAC::File(fileName, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); if(ext == "MPC") - return new MPC::File(arg, readAudioProperties, audioPropertiesStyle); + return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "WV") - return new WavPack::File(arg, readAudioProperties, audioPropertiesStyle); + return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "SPX") - return new Ogg::Speex::File(arg, readAudioProperties, audioPropertiesStyle); + return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "OPUS") - return new Ogg::Opus::File(arg, readAudioProperties, audioPropertiesStyle); + return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "TTA") - return new TrueAudio::File(arg, readAudioProperties, audioPropertiesStyle); + return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V") - return new MP4::File(arg, readAudioProperties, audioPropertiesStyle); + return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "WMA" || ext == "ASF") - return new ASF::File(arg, readAudioProperties, audioPropertiesStyle); + return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC") - return new RIFF::AIFF::File(arg, readAudioProperties, audioPropertiesStyle); + return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "WAV") - return new RIFF::WAV::File(arg, readAudioProperties, audioPropertiesStyle); + return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "APE") - return new APE::File(arg, readAudioProperties, audioPropertiesStyle); + return new APE::File(fileName, readAudioProperties, audioPropertiesStyle); // module, nst and wow are possible but uncommon extensions if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW") - return new Mod::File(arg, readAudioProperties, audioPropertiesStyle); + return new Mod::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "S3M") - return new S3M::File(arg, readAudioProperties, audioPropertiesStyle); + return new S3M::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "IT") - return new IT::File(arg, readAudioProperties, audioPropertiesStyle); + return new IT::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "XM") - return new XM::File(arg, readAudioProperties, audioPropertiesStyle); + return new XM::File(fileName, readAudioProperties, audioPropertiesStyle); return 0; } @@ -186,15 +263,18 @@ namespace class FileRef::FileRefPrivate : public RefCounter { public: - FileRefPrivate(File *f) : + FileRefPrivate() : RefCounter(), - file(f) {} + file(0), + stream(0) {} ~FileRefPrivate() { delete file; + delete stream; } - File *file; + File *file; + IOStream *stream; }; //////////////////////////////////////////////////////////////////////////////// @@ -202,24 +282,27 @@ public: //////////////////////////////////////////////////////////////////////////////// FileRef::FileRef() : - d(new FileRefPrivate(0)) + d(new FileRefPrivate()) { } FileRef::FileRef(FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) : - d(new FileRefPrivate(createInternal(fileName, readAudioProperties, audioPropertiesStyle))) + d(new FileRefPrivate()) { + parse(fileName, readAudioProperties, audioPropertiesStyle); } FileRef::FileRef(IOStream* stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) : - d(new FileRefPrivate(createInternal(stream, readAudioProperties, audioPropertiesStyle))) + d(new FileRefPrivate()) { + parse(stream, readAudioProperties, audioPropertiesStyle); } FileRef::FileRef(File *file) : - d(new FileRefPrivate(file)) + d(new FileRefPrivate()) { + d->file = file; } FileRef::FileRef(const FileRef &ref) : @@ -341,3 +424,51 @@ File *FileRef::create(FileName fileName, bool readAudioProperties, { return createInternal(fileName, readAudioProperties, audioPropertiesStyle); } + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void FileRef::parse(FileName fileName, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) +{ + // Try user-defined resolvers. + + d->file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // Try to resolve file types based on the file extension. + + d->stream = new FileStream(fileName); + d->file = detectByExtension(d->stream, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // At last, try to resolve file types based on the actual content. + + d->file = detectByContent(d->stream, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // Stream have to be closed here if failed to resolve file types. + + delete d->stream; + d->stream = 0; +} + +void FileRef::parse(IOStream *stream, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) +{ + // User-defined resolvers won't work with a stream. + + // Try to resolve file types based on the file extension. + + d->file = detectByExtension(stream, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // At last, try to resolve file types based on the actual content of the file. + + d->file = detectByContent(stream, readAudioProperties, audioPropertiesStyle); +} diff --git a/3rdparty/taglib/fileref.h b/3rdparty/taglib/fileref.h index a12b1a9b1..c36f54cbd 100644 --- a/3rdparty/taglib/fileref.h +++ b/3rdparty/taglib/fileref.h @@ -274,8 +274,10 @@ namespace TagLib { bool readAudioProperties = true, AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average); - private: + void parse(FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle); + void parse(IOStream *stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle); + class FileRefPrivate; FileRefPrivate *d; }; diff --git a/3rdparty/taglib/flac/flacfile.cpp b/3rdparty/taglib/flac/flacfile.cpp index b31cc65e0..7f4371943 100644 --- a/3rdparty/taglib/flac/flacfile.cpp +++ b/3rdparty/taglib/flac/flacfile.cpp @@ -95,6 +95,18 @@ public: bool scanned; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool FLAC::File::isSupported(IOStream *stream) +{ + // A FLAC file has an ID "fLaC" somewhere. An ID3v2 tag may precede. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true); + return (buffer.find("fLaC") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -168,8 +180,8 @@ bool FLAC::File::save() } // Create new vorbis comments - - Tag::duplicate(&d->tag, xiphComment(true), false); + if(!hasXiphComment()) + Tag::duplicate(&d->tag, xiphComment(true), false); d->xiphCommentData = xiphComment()->render(false); @@ -198,7 +210,6 @@ bool FLAC::File::save() } // Compute the amount of padding, and append that to data. - // TODO: Should be calculated in offset_t in taglib2. long originalLength = d->streamStart - d->flacStart; long paddingLength = originalLength - data.size() - 4; @@ -298,11 +309,7 @@ bool FLAC::File::save() ID3v2::Tag *FLAC::File::ID3v2Tag(bool create) { - if(!create || d->tag[FlacID3v2Index]) - return static_cast(d->tag[FlacID3v2Index]); - - d->tag.set(FlacID3v2Index, new ID3v2::Tag); - return static_cast(d->tag[FlacID3v2Index]); + return d->tag.access(FlacID3v2Index, create); } ID3v1::Tag *FLAC::File::ID3v1Tag(bool create) @@ -507,7 +514,9 @@ void FLAC::File::scan() return; } - if(blockLength == 0 && blockType != MetadataBlock::Padding) { + if(blockLength == 0 + && blockType != MetadataBlock::Padding && blockType != MetadataBlock::SeekTable) + { debug("FLAC::File::scan() -- Zero-sized metadata block found"); setValid(false); return; diff --git a/3rdparty/taglib/flac/flacfile.h b/3rdparty/taglib/flac/flacfile.h index 65d856792..645090e0e 100644 --- a/3rdparty/taglib/flac/flacfile.h +++ b/3rdparty/taglib/flac/flacfile.h @@ -318,6 +318,15 @@ namespace TagLib { */ bool hasID3v2Tag() const; + /*! + * Returns whether or not the given \a stream can be opened as a FLAC + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/flac/flacpicture.cpp b/3rdparty/taglib/flac/flacpicture.cpp index 72c972477..ec07ad140 100644 --- a/3rdparty/taglib/flac/flacpicture.cpp +++ b/3rdparty/taglib/flac/flacpicture.cpp @@ -50,14 +50,14 @@ public: ByteVector data; }; -FLAC::Picture::Picture() +FLAC::Picture::Picture() : + d(new PicturePrivate()) { - d = new PicturePrivate; } -FLAC::Picture::Picture(const ByteVector &data) +FLAC::Picture::Picture(const ByteVector &data) : + d(new PicturePrivate()) { - d = new PicturePrivate; parse(data); } diff --git a/3rdparty/taglib/flac/flacunknownmetadatablock.cpp b/3rdparty/taglib/flac/flacunknownmetadatablock.cpp index dcd5d6515..f9cf6e658 100644 --- a/3rdparty/taglib/flac/flacunknownmetadatablock.cpp +++ b/3rdparty/taglib/flac/flacunknownmetadatablock.cpp @@ -39,11 +39,10 @@ public: ByteVector data; }; -FLAC::UnknownMetadataBlock::UnknownMetadataBlock(int code, const ByteVector &data) +FLAC::UnknownMetadataBlock::UnknownMetadataBlock(int code, const ByteVector &data) : + d(new UnknownMetadataBlockPrivate()) { - d = new UnknownMetadataBlockPrivate; d->code = code; - //debug(String(data.toHex())); d->data = data; } diff --git a/3rdparty/taglib/it/itproperties.cpp b/3rdparty/taglib/it/itproperties.cpp index 8f686dc7b..c317b660e 100644 --- a/3rdparty/taglib/it/itproperties.cpp +++ b/3rdparty/taglib/it/itproperties.cpp @@ -70,7 +70,7 @@ public: IT::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : AudioProperties(propertiesStyle), - d(new PropertiesPrivate) + d(new PropertiesPrivate()) { } diff --git a/3rdparty/taglib/mod/modproperties.cpp b/3rdparty/taglib/mod/modproperties.cpp index ed3df94d4..c6bf39475 100644 --- a/3rdparty/taglib/mod/modproperties.cpp +++ b/3rdparty/taglib/mod/modproperties.cpp @@ -46,7 +46,7 @@ public: Mod::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : AudioProperties(propertiesStyle), - d(new PropertiesPrivate) + d(new PropertiesPrivate()) { } diff --git a/3rdparty/taglib/mod/modtag.cpp b/3rdparty/taglib/mod/modtag.cpp index 616d8c1b1..0e8d03719 100644 --- a/3rdparty/taglib/mod/modtag.cpp +++ b/3rdparty/taglib/mod/modtag.cpp @@ -43,9 +43,10 @@ public: String trackerName; }; -Mod::Tag::Tag() : TagLib::Tag() +Mod::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; } Mod::Tag::~Tag() diff --git a/3rdparty/taglib/mp4/mp4atom.cpp b/3rdparty/taglib/mp4/mp4atom.cpp index 6ea0cb62c..20709212e 100644 --- a/3rdparty/taglib/mp4/mp4atom.cpp +++ b/3rdparty/taglib/mp4/mp4atom.cpp @@ -54,24 +54,25 @@ MP4::Atom::Atom(File *file) length = header.toUInt(); - if(length == 1) { + if(length == 0) { + // The last atom which extends to the end of the file. + length = file->length() - offset; + } + else if(length == 1) { + // The atom has a 64-bit length. const long long longLength = file->readBlock(8).toLongLong(); - if(sizeof(long) == sizeof(long long)) { + if(longLength <= LONG_MAX) { + // The actual length fits in long. That's always the case if long is 64-bit. length = static_cast(longLength); } else { - if(longLength <= LONG_MAX) { - // The atom has a 64-bit length, but it's actually a 31-bit value - length = static_cast(longLength); - } - else { - debug("MP4: 64-bit atoms are not supported"); - length = 0; - file->seek(0, File::End); - return; - } + debug("MP4: 64-bit atoms are not supported"); + length = 0; + file->seek(0, File::End); + return; } } + if(length < 8) { debug("MP4: Invalid atom size"); length = 0; diff --git a/3rdparty/taglib/mp4/mp4file.cpp b/3rdparty/taglib/mp4/mp4file.cpp index 3733fb40d..5ad8396de 100644 --- a/3rdparty/taglib/mp4/mp4file.cpp +++ b/3rdparty/taglib/mp4/mp4file.cpp @@ -26,6 +26,8 @@ #include #include #include +#include + #include "mp4atom.h" #include "mp4tag.h" #include "mp4file.h" @@ -69,6 +71,22 @@ public: MP4::Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool MP4::File::isSupported(IOStream *stream) +{ + // An MP4 file has to have an "ftyp" box first. + + const ByteVector id = Utils::readHeader(stream, 8, false); + return id.containsAt("ftyp", 4); +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : TagLib::File(file), d(new FilePrivate()) diff --git a/3rdparty/taglib/mp4/mp4file.h b/3rdparty/taglib/mp4/mp4file.h index 3840bd022..8a46d17df 100644 --- a/3rdparty/taglib/mp4/mp4file.h +++ b/3rdparty/taglib/mp4/mp4file.h @@ -120,6 +120,15 @@ namespace TagLib { */ bool hasMP4Tag() const; + /*! + * Returns whether or not the given \a stream can be opened as an ASF + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: void read(bool readProperties); diff --git a/3rdparty/taglib/mp4/mp4tag.cpp b/3rdparty/taglib/mp4/mp4tag.cpp index a8e2e7d3e..a3636a9d6 100644 --- a/3rdparty/taglib/mp4/mp4tag.cpp +++ b/3rdparty/taglib/mp4/mp4tag.cpp @@ -71,14 +71,15 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) : parseIntPair(atom); } else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" || - atom->name == "hdvd") { + atom->name == "hdvd" || atom->name == "shwm") { parseBool(atom); } - else if(atom->name == "tmpo") { + else if(atom->name == "tmpo" || atom->name == "rate" || atom->name == "\251mvi" || atom->name == "\251mvc") { parseInt(atom); } else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" || - atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") { + atom->name == "sfID" || atom->name == "atID" || atom->name == "geID" || + atom->name == "cmID") { parseUInt(atom); } else if(atom->name == "plID") { @@ -93,6 +94,9 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) : else if(atom->name == "covr") { parseCovr(atom); } + else if(atom->name == "purl" || atom->name == "egid") { + parseText(atom, -1); + } else { parseText(atom); } @@ -472,14 +476,16 @@ MP4::Tag::save() else if(name == "disk") { data.append(renderIntPairNoTrailing(name.data(String::Latin1), it->second)); } - else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd") { + else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd" || + name == "shwm") { data.append(renderBool(name.data(String::Latin1), it->second)); } - else if(name == "tmpo") { + else if(name == "tmpo" || name == "rate" || name == "\251mvi" || name == "\251mvc") { data.append(renderInt(name.data(String::Latin1), it->second)); } else if(name == "tvsn" || name == "tves" || name == "cnID" || - name == "sfID" || name == "atID" || name == "geID") { + name == "sfID" || name == "atID" || name == "geID" || + name == "cmID") { data.append(renderUInt(name.data(String::Latin1), it->second)); } else if(name == "plID") { @@ -491,6 +497,9 @@ MP4::Tag::save() else if(name == "covr") { data.append(renderCovr(name.data(String::Latin1), it->second)); } + else if(name == "purl" || name == "egid") { + data.append(renderText(name.data(String::Latin1), it->second, TypeImplicit)); + } else if(name.size() == 4){ data.append(renderText(name.data(String::Latin1), it->second)); } @@ -844,6 +853,11 @@ namespace { "sonm", "TITLESORT" }, { "soco", "COMPOSERSORT" }, { "sosn", "SHOWSORT" }, + { "shwm", "SHOWWORKMOVEMENT" }, + { "\251wrk", "WORK" }, + { "\251mvn", "MOVEMENTNAME" }, + { "\251mvi", "MOVEMENTNUMBER" }, + { "\251mvc", "MOVEMENTCOUNT" }, { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, @@ -897,10 +911,10 @@ PropertyMap MP4::Tag::properties() const } props[key] = value; } - else if(key == "BPM") { + else if(key == "BPM" || key == "MOVEMENTNUMBER" || key == "MOVEMENTCOUNT") { props[key] = String::number(it->second.toInt()); } - else if(key == "COMPILATION") { + else if(key == "COMPILATION" || key == "SHOWWORKMOVEMENT") { props[key] = String::number(it->second.toBool()); } else { @@ -942,21 +956,21 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props) if(reverseKeyMap.contains(it->first)) { String name = reverseKeyMap[it->first]; if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) { - int first = 0, second = 0; StringList parts = StringList::split(it->second.front(), "/"); if(!parts.isEmpty()) { - first = parts[0].toInt(); + int first = parts[0].toInt(); + int second = 0; if(parts.size() > 1) { second = parts[1].toInt(); } d->items[name] = MP4::Item(first, second); } } - else if(it->first == "BPM" && !it->second.isEmpty()) { + else if((it->first == "BPM" || it->first == "MOVEMENTNUMBER" || it->first == "MOVEMENTCOUNT") && !it->second.isEmpty()) { int value = it->second.front().toInt(); d->items[name] = MP4::Item(value); } - else if(it->first == "COMPILATION" && !it->second.isEmpty()) { + else if((it->first == "COMPILATION" || it->first == "SHOWWORKMOVEMENT") && !it->second.isEmpty()) { bool value = (it->second.front().toInt() != 0); d->items[name] = MP4::Item(value); } diff --git a/3rdparty/taglib/mpc/mpcfile.cpp b/3rdparty/taglib/mpc/mpcfile.cpp index daf24c8fb..0ffaf8933 100644 --- a/3rdparty/taglib/mpc/mpcfile.cpp +++ b/3rdparty/taglib/mpc/mpcfile.cpp @@ -75,6 +75,19 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool MPC::File::isSupported(IOStream *stream) +{ + // A newer MPC file has to start with "MPCK" or "MP+", but older files don't + // have keys to do a quick check. + + const ByteVector id = Utils::readHeader(stream, 4, false); + return (id == "MPCK" || id.startsWith("MP+")); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/mpc/mpcfile.h b/3rdparty/taglib/mpc/mpcfile.h index 541724dc2..89a866e3b 100644 --- a/3rdparty/taglib/mpc/mpcfile.h +++ b/3rdparty/taglib/mpc/mpcfile.h @@ -214,6 +214,15 @@ namespace TagLib { */ bool hasAPETag() const; + /*! + * Returns whether or not the given \a stream can be opened as an MPC + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp index 849ee9ff2..8e2630cef 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp @@ -48,14 +48,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -AttachedPictureFrame::AttachedPictureFrame() : Frame("APIC") +AttachedPictureFrame::AttachedPictureFrame() : + Frame("APIC"), + d(new AttachedPictureFramePrivate()) { - d = new AttachedPictureFramePrivate; } -AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data) : Frame(data) +AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data) : + Frame(data), + d(new AttachedPictureFramePrivate()) { - d = new AttachedPictureFramePrivate; setData(data); } @@ -169,9 +171,10 @@ ByteVector AttachedPictureFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data, Header *h) : Frame(h) +AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new AttachedPictureFramePrivate()) { - d = new AttachedPictureFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/chapterframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/chapterframe.cpp index 44cdf5e77..69fe1df0a 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -37,7 +37,11 @@ class ChapterFrame::ChapterFramePrivate { public: ChapterFramePrivate() : - tagHeader(0) + tagHeader(0), + startTime(0), + endTime(0), + startOffset(0), + endOffset(0) { embeddedFrameList.setAutoDelete(true); } @@ -57,9 +61,9 @@ public: //////////////////////////////////////////////////////////////////////////////// ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data) : - ID3v2::Frame(data) + ID3v2::Frame(data), + d(new ChapterFramePrivate()) { - d = new ChapterFramePrivate; d->tagHeader = tagHeader; setData(data); } @@ -68,10 +72,9 @@ ChapterFrame::ChapterFrame(const ByteVector &elementID, unsigned int startTime, unsigned int endTime, unsigned int startOffset, unsigned int endOffset, const FrameList &embeddedFrames) : - ID3v2::Frame("CHAP") + ID3v2::Frame("CHAP"), + d(new ChapterFramePrivate()) { - d = new ChapterFramePrivate; - // setElementID has a workaround for a previously silly API where you had to // specifically include the null byte. @@ -198,7 +201,7 @@ String ChapterFrame::toString() const s += ", start offset: " + String::number(d->startOffset); if(d->endOffset != 0xFFFFFFFF) - s += ", start offset: " + String::number(d->endOffset); + s += ", end offset: " + String::number(d->endOffset); if(!d->embeddedFrameList.isEmpty()) { StringList frameIDs; @@ -298,9 +301,9 @@ ByteVector ChapterFrame::renderFields() const } ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h) : - Frame(h) + Frame(h), + d(new ChapterFramePrivate()) { - d = new ChapterFramePrivate; d->tagHeader = tagHeader; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/commentsframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/commentsframe.cpp index b56183226..815e5e1a1 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/commentsframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/commentsframe.cpp @@ -48,15 +48,17 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -CommentsFrame::CommentsFrame(String::Type encoding) : Frame("COMM") +CommentsFrame::CommentsFrame(String::Type encoding) : + Frame("COMM"), + d(new CommentsFramePrivate()) { - d = new CommentsFramePrivate; d->textEncoding = encoding; } -CommentsFrame::CommentsFrame(const ByteVector &data) : Frame(data) +CommentsFrame::CommentsFrame(const ByteVector &data) : + Frame(data), + d(new CommentsFramePrivate()) { - d = new CommentsFramePrivate; setData(data); } @@ -188,8 +190,9 @@ ByteVector CommentsFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -CommentsFrame::CommentsFrame(const ByteVector &data, Header *h) : Frame(h) +CommentsFrame::CommentsFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new CommentsFramePrivate()) { - d = new CommentsFramePrivate(); parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp index 7af797f08..930318123 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp @@ -46,15 +46,15 @@ public: //////////////////////////////////////////////////////////////////////////////// EventTimingCodesFrame::EventTimingCodesFrame() : - Frame("ETCO") + Frame("ETCO"), + d(new EventTimingCodesFramePrivate()) { - d = new EventTimingCodesFramePrivate; } EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new EventTimingCodesFramePrivate()) { - d = new EventTimingCodesFramePrivate; setData(data); } @@ -136,9 +136,9 @@ ByteVector EventTimingCodesFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data, Header *h) - : Frame(h) +EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new EventTimingCodesFramePrivate()) { - d = new EventTimingCodesFramePrivate(); parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp index 1c773837f..c9b2f6dc4 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp @@ -50,14 +50,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame() : Frame("GEOB") +GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame() : + Frame("GEOB"), + d(new GeneralEncapsulatedObjectFramePrivate()) { - d = new GeneralEncapsulatedObjectFramePrivate; } -GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data) : Frame(data) +GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data) : + Frame(data), + d(new GeneralEncapsulatedObjectFramePrivate()) { - d = new GeneralEncapsulatedObjectFramePrivate; setData(data); } @@ -177,8 +179,9 @@ ByteVector GeneralEncapsulatedObjectFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data, Header *h) : Frame(h) +GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new GeneralEncapsulatedObjectFramePrivate()) { - d = new GeneralEncapsulatedObjectFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/ownershipframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/ownershipframe.cpp index 83a598245..0b180dbf6 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/ownershipframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/ownershipframe.cpp @@ -45,15 +45,17 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -OwnershipFrame::OwnershipFrame(String::Type encoding) : Frame("OWNE") +OwnershipFrame::OwnershipFrame(String::Type encoding) : + Frame("OWNE"), + d(new OwnershipFramePrivate()) { - d = new OwnershipFramePrivate; d->textEncoding = encoding; } -OwnershipFrame::OwnershipFrame(const ByteVector &data) : Frame(data) +OwnershipFrame::OwnershipFrame(const ByteVector &data) : + Frame(data), + d(new OwnershipFramePrivate()) { - d = new OwnershipFramePrivate; setData(data); } @@ -161,8 +163,9 @@ ByteVector OwnershipFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -OwnershipFrame::OwnershipFrame(const ByteVector &data, Header *h) : Frame(h) +OwnershipFrame::OwnershipFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new OwnershipFramePrivate()) { - d = new OwnershipFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/podcastframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/podcastframe.cpp index 5115a7dd1..7285b9689 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/podcastframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/podcastframe.cpp @@ -38,9 +38,10 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -PodcastFrame::PodcastFrame() : Frame("PCST") +PodcastFrame::PodcastFrame() : + Frame("PCST"), + d(new PodcastFramePrivate()) { - d = new PodcastFramePrivate; d->fieldData = ByteVector(4, '\0'); } @@ -72,8 +73,9 @@ ByteVector PodcastFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -PodcastFrame::PodcastFrame(const ByteVector &data, Header *h) : Frame(h) +PodcastFrame::PodcastFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new PodcastFramePrivate()) { - d = new PodcastFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/popularimeterframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/popularimeterframe.cpp index dac8589f3..9106fabd5 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/popularimeterframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/popularimeterframe.cpp @@ -43,14 +43,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -PopularimeterFrame::PopularimeterFrame() : Frame("POPM") +PopularimeterFrame::PopularimeterFrame() : + Frame("POPM"), + d(new PopularimeterFramePrivate()) { - d = new PopularimeterFramePrivate; } -PopularimeterFrame::PopularimeterFrame(const ByteVector &data) : Frame(data) +PopularimeterFrame::PopularimeterFrame(const ByteVector &data) : + Frame(data), + d(new PopularimeterFramePrivate()) { - d = new PopularimeterFramePrivate; setData(data); } @@ -130,8 +132,9 @@ ByteVector PopularimeterFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -PopularimeterFrame::PopularimeterFrame(const ByteVector &data, Header *h) : Frame(h) +PopularimeterFrame::PopularimeterFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new PopularimeterFramePrivate()) { - d = new PopularimeterFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/privateframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/privateframe.cpp index 24ee0f350..be80c4df8 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/privateframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/privateframe.cpp @@ -45,14 +45,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -PrivateFrame::PrivateFrame() : Frame("PRIV") +PrivateFrame::PrivateFrame() : + Frame("PRIV"), + d(new PrivateFramePrivate()) { - d = new PrivateFramePrivate; } -PrivateFrame::PrivateFrame(const ByteVector &data) : Frame(data) +PrivateFrame::PrivateFrame(const ByteVector &data) : + Frame(data), + d(new PrivateFramePrivate()) { - d = new PrivateFramePrivate; setData(data); } @@ -121,8 +123,9 @@ ByteVector PrivateFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -PrivateFrame::PrivateFrame(const ByteVector &data, Header *h) : Frame(h) +PrivateFrame::PrivateFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new PrivateFramePrivate()) { - d = new PrivateFramePrivate(); parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp index a907f6b97..3398ada82 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp @@ -51,14 +51,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RelativeVolumeFrame::RelativeVolumeFrame() : Frame("RVA2") +RelativeVolumeFrame::RelativeVolumeFrame() : + Frame("RVA2"), + d(new RelativeVolumeFramePrivate()) { - d = new RelativeVolumeFramePrivate; } -RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data) : Frame(data) +RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data) : + Frame(data), + d(new RelativeVolumeFramePrivate()) { - d = new RelativeVolumeFramePrivate; setData(data); } @@ -223,8 +225,9 @@ ByteVector RelativeVolumeFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data, Header *h) : Frame(h) +RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new RelativeVolumeFramePrivate()) { - d = new RelativeVolumeFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp index c3b50c7ec..6a62bb495 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp @@ -52,16 +52,16 @@ public: //////////////////////////////////////////////////////////////////////////////// SynchronizedLyricsFrame::SynchronizedLyricsFrame(String::Type encoding) : - Frame("SYLT") + Frame("SYLT"), + d(new SynchronizedLyricsFramePrivate()) { - d = new SynchronizedLyricsFramePrivate; d->textEncoding = encoding; } SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new SynchronizedLyricsFramePrivate()) { - d = new SynchronizedLyricsFramePrivate; setData(data); } @@ -189,7 +189,7 @@ void SynchronizedLyricsFrame::parseFields(const ByteVector &data) } } String text = readStringField(data, enc, &pos); - if(text.isEmpty() || pos + 4 > end) + if(pos + 4 > end) return; unsigned int time = data.toUInt(pos, true); @@ -234,9 +234,9 @@ ByteVector SynchronizedLyricsFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data, Header *h) - : Frame(h) +SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new SynchronizedLyricsFramePrivate()) { - d = new SynchronizedLyricsFramePrivate(); parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 507203590..ddd3b88c6 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -36,7 +36,9 @@ class TableOfContentsFrame::TableOfContentsFramePrivate { public: TableOfContentsFramePrivate() : - tagHeader(0) + tagHeader(0), + isTopLevel(false), + isOrdered(false) { embeddedFrameList.setAutoDelete(true); } @@ -80,9 +82,9 @@ namespace { //////////////////////////////////////////////////////////////////////////////// TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data) : - ID3v2::Frame(data) + ID3v2::Frame(data), + d(new TableOfContentsFramePrivate()) { - d = new TableOfContentsFramePrivate; d->tagHeader = tagHeader; setData(data); } @@ -90,9 +92,9 @@ TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID, const ByteVectorList &children, const FrameList &embeddedFrames) : - ID3v2::Frame("CTOC") + ID3v2::Frame("CTOC"), + d(new TableOfContentsFramePrivate()) { - d = new TableOfContentsFramePrivate; d->elementID = elementID; strip(d->elementID); d->childElements = children; @@ -272,9 +274,9 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) int pos = 0; unsigned int embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); - d->isTopLevel = (data.at(pos) & 2) > 0; - d->isOrdered = (data.at(pos++) & 1) > 0; - unsigned int entryCount = data.at(pos++); + d->isTopLevel = (data.at(pos) & 2) != 0; + d->isOrdered = (data.at(pos++) & 1) != 0; + unsigned int entryCount = static_cast(data.at(pos++)); for(unsigned int i = 0; i < entryCount; i++) { ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->childElements.append(childElementID); @@ -330,9 +332,9 @@ ByteVector TableOfContentsFrame::renderFields() const TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h) : - Frame(h) + Frame(h), + d(new TableOfContentsFramePrivate()) { - d = new TableOfContentsFramePrivate; d->tagHeader = tagHeader; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 31196b2e3..2e5401e8a 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/3rdparty/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -29,6 +29,8 @@ #include "id3v2tag.h" #include "id3v2frame.h" +#include "tbytevectorlist.h" + namespace TagLib { namespace ID3v2 { diff --git a/3rdparty/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index d9d3b29bc..db9a177e5 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -45,16 +45,16 @@ public: //////////////////////////////////////////////////////////////////////////////// TextIdentificationFrame::TextIdentificationFrame(const ByteVector &type, String::Type encoding) : - Frame(type) + Frame(type), + d(new TextIdentificationFramePrivate()) { - d = new TextIdentificationFramePrivate; d->textEncoding = encoding; } TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new TextIdentificationFramePrivate()) { - d = new TextIdentificationFramePrivate; setData(data); } @@ -252,9 +252,10 @@ ByteVector TextIdentificationFrame::renderFields() const // TextIdentificationFrame private members //////////////////////////////////////////////////////////////////////////////// -TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data, Header *h) : Frame(h) +TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new TextIdentificationFramePrivate()) { - d = new TextIdentificationFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp index a986e4db4..fcb855b2e 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp @@ -45,16 +45,16 @@ public: //////////////////////////////////////////////////////////////////////////////// UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : - ID3v2::Frame(data) + ID3v2::Frame(data), + d(new UniqueFileIdentifierFramePrivate()) { - d = new UniqueFileIdentifierFramePrivate; setData(data); } UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : - ID3v2::Frame("UFID") + ID3v2::Frame("UFID"), + d(new UniqueFileIdentifierFramePrivate()) { - d = new UniqueFileIdentifierFramePrivate; d->owner = owner; d->identifier = id; } @@ -141,8 +141,8 @@ ByteVector UniqueFileIdentifierFrame::renderFields() const } UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : - Frame(h) + Frame(h), + d(new UniqueFileIdentifierFramePrivate()) { - d = new UniqueFileIdentifierFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/unknownframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/unknownframe.cpp index 9d059193b..edb30084a 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/unknownframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/unknownframe.cpp @@ -38,9 +38,10 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -UnknownFrame::UnknownFrame(const ByteVector &data) : Frame(data) +UnknownFrame::UnknownFrame(const ByteVector &data) : + Frame(data), + d(new UnknownFramePrivate()) { - d = new UnknownFramePrivate; setData(data); } @@ -77,8 +78,9 @@ ByteVector UnknownFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -UnknownFrame::UnknownFrame(const ByteVector &data, Header *h) : Frame(h) +UnknownFrame::UnknownFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new UnknownFramePrivate()) { - d = new UnknownFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index 3d610c9a7..eafae171c 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -50,16 +50,16 @@ public: //////////////////////////////////////////////////////////////////////////////// UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(String::Type encoding) : - Frame("USLT") + Frame("USLT"), + d(new UnsynchronizedLyricsFramePrivate()) { - d = new UnsynchronizedLyricsFramePrivate; d->textEncoding = encoding; } UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new UnsynchronizedLyricsFramePrivate()) { - d = new UnsynchronizedLyricsFramePrivate; setData(data); } @@ -118,7 +118,7 @@ PropertyMap UnsynchronizedLyricsFrame::asProperties() const { PropertyMap map; String key = description().upper(); - if(key.isEmpty() || key.upper() == "LYRICS") + if(key.isEmpty() || key == "LYRICS") map.insert("LYRICS", text()); else map.insert("LYRICS:" + key, text()); @@ -190,9 +190,9 @@ ByteVector UnsynchronizedLyricsFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(const ByteVector &data, Header *h) - : Frame(h) +UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new UnsynchronizedLyricsFramePrivate()) { - d = new UnsynchronizedLyricsFramePrivate(); parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/3rdparty/taglib/mpeg/id3v2/frames/urllinkframe.cpp index 42d807122..543b5184d 100644 --- a/3rdparty/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/3rdparty/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -49,10 +49,14 @@ public: String description; }; +//////////////////////////////////////////////////////////////////////////////// +// UrlLinkFrame public members +//////////////////////////////////////////////////////////////////////////////// + UrlLinkFrame::UrlLinkFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new UrlLinkFramePrivate()) { - d = new UrlLinkFramePrivate; setData(data); } @@ -93,6 +97,10 @@ PropertyMap UrlLinkFrame::asProperties() const return map; } +//////////////////////////////////////////////////////////////////////////////// +// UrlLinkFrame protected members +//////////////////////////////////////////////////////////////////////////////// + void UrlLinkFrame::parseFields(const ByteVector &data) { d->url = String(data); @@ -103,24 +111,28 @@ ByteVector UrlLinkFrame::renderFields() const return d->url.data(String::Latin1); } -UrlLinkFrame::UrlLinkFrame(const ByteVector &data, Header *h) : Frame(h) +UrlLinkFrame::UrlLinkFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new UrlLinkFramePrivate()) { - d = new UrlLinkFramePrivate; parseFields(fieldData(data)); } +//////////////////////////////////////////////////////////////////////////////// +// UserUrlLinkFrame public members +//////////////////////////////////////////////////////////////////////////////// UserUrlLinkFrame::UserUrlLinkFrame(String::Type encoding) : - UrlLinkFrame("WXXX") + UrlLinkFrame("WXXX"), + d(new UserUrlLinkFramePrivate()) { - d = new UserUrlLinkFramePrivate; d->textEncoding = encoding; } UserUrlLinkFrame::UserUrlLinkFrame(const ByteVector &data) : - UrlLinkFrame(data) + UrlLinkFrame(data), + d(new UserUrlLinkFramePrivate()) { - d = new UserUrlLinkFramePrivate; setData(data); } @@ -158,7 +170,7 @@ PropertyMap UserUrlLinkFrame::asProperties() const { PropertyMap map; String key = description().upper(); - if(key.isEmpty() || key.upper() == "URL") + if(key.isEmpty() || key == "URL") map.insert("URL", url()); else map.insert("URL:" + key, url()); @@ -176,6 +188,10 @@ UserUrlLinkFrame *UserUrlLinkFrame::find(ID3v2::Tag *tag, const String &descript return 0; } +//////////////////////////////////////////////////////////////////////////////// +// UserUrlLinkFrame protected members +//////////////////////////////////////////////////////////////////////////////// + void UserUrlLinkFrame::parseFields(const ByteVector &data) { if(data.size() < 2) { @@ -222,8 +238,9 @@ ByteVector UserUrlLinkFrame::renderFields() const return v; } -UserUrlLinkFrame::UserUrlLinkFrame(const ByteVector &data, Header *h) : UrlLinkFrame(data, h) +UserUrlLinkFrame::UserUrlLinkFrame(const ByteVector &data, Header *h) : + UrlLinkFrame(data, h), + d(new UserUrlLinkFramePrivate()) { - d = new UserUrlLinkFramePrivate; parseFields(fieldData(data)); } diff --git a/3rdparty/taglib/mpeg/id3v2/id3v2extendedheader.cpp b/3rdparty/taglib/mpeg/id3v2/id3v2extendedheader.cpp index 667e3e94a..86eeee280 100644 --- a/3rdparty/taglib/mpeg/id3v2/id3v2extendedheader.cpp +++ b/3rdparty/taglib/mpeg/id3v2/id3v2extendedheader.cpp @@ -41,9 +41,9 @@ public: // public methods //////////////////////////////////////////////////////////////////////////////// -ExtendedHeader::ExtendedHeader() +ExtendedHeader::ExtendedHeader() : + d(new ExtendedHeaderPrivate()) { - d = new ExtendedHeaderPrivate(); } ExtendedHeader::~ExtendedHeader() diff --git a/3rdparty/taglib/mpeg/id3v2/id3v2frame.cpp b/3rdparty/taglib/mpeg/id3v2/id3v2frame.cpp index 1f896fa6e..4f88dec1f 100644 --- a/3rdparty/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/3rdparty/taglib/mpeg/id3v2/id3v2frame.cpp @@ -111,8 +111,8 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // // check if the key is contained in the key<=>frameID mapping ByteVector frameID = keyToFrameID(key); if(!frameID.isEmpty()) { - // Apple proprietary WFED (Podcast URL) is in fact a text frame. - if(frameID[0] == 'T' || frameID == "WFED"){ // text frame + // Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number) are in fact text frames. + if(frameID[0] == 'T' || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN"){ // text frame TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8); frame->setText(values); return frame; @@ -198,15 +198,15 @@ ByteVector Frame::render() const // protected members //////////////////////////////////////////////////////////////////////////////// -Frame::Frame(const ByteVector &data) +Frame::Frame(const ByteVector &data) : + d(new FramePrivate()) { - d = new FramePrivate; d->header = new Header(data); } -Frame::Frame(Header *h) +Frame::Frame(Header *h) : + d(new FramePrivate()) { - d = new FramePrivate; d->header = h; } @@ -392,6 +392,8 @@ namespace { "TDES", "PODCASTDESC" }, { "TGID", "PODCASTID" }, { "WFED", "PODCASTURL" }, + { "MVNM", "MOVEMENTNAME" }, + { "MVIN", "MOVEMENTNUMBER" }, }; const size_t frameTranslationSize = sizeof(frameTranslation) / sizeof(frameTranslation[0]); @@ -474,8 +476,8 @@ PropertyMap Frame::asProperties() const // workaround until this function is virtual if(id == "TXXX") return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties(); - // Apple proprietary WFED (Podcast URL) is in fact a text frame. - else if(id[0] == 'T' || id == "WFED") + // Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number) are in fact text frames. + else if(id[0] == 'T' || id == "WFED" || id == "MVNM" || id == "MVIN") return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties(); else if(id == "WXXX") return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties(); @@ -571,15 +573,15 @@ unsigned int Frame::Header::size(unsigned int version) // public members (Frame::Header) //////////////////////////////////////////////////////////////////////////////// -Frame::Header::Header(const ByteVector &data, bool synchSafeInts) +Frame::Header::Header(const ByteVector &data, bool synchSafeInts) : + d(new HeaderPrivate()) { - d = new HeaderPrivate; setData(data, synchSafeInts); } -Frame::Header::Header(const ByteVector &data, unsigned int version) +Frame::Header::Header(const ByteVector &data, unsigned int version) : + d(new HeaderPrivate()) { - d = new HeaderPrivate; setData(data, version); } diff --git a/3rdparty/taglib/mpeg/id3v2/id3v2framefactory.cpp b/3rdparty/taglib/mpeg/id3v2/id3v2framefactory.cpp index 0fbb87d02..9347ab869 100644 --- a/3rdparty/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/3rdparty/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -198,8 +198,8 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) // Text Identification (frames 4.2) - // Apple proprietary WFED (Podcast URL) is in fact a text frame. - if(frameID.startsWith("T") || frameID == "WFED") { + // Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number) are in fact text frames. + if(frameID.startsWith("T") || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN") { TextIdentificationFrame *f = frameID != "TXXX" ? new TextIdentificationFrame(data, header) @@ -334,10 +334,11 @@ void FrameFactory::rebuildAggregateFrames(ID3v2::Tag *tag) const tag->frameList("TDAT").size() == 1) { TextIdentificationFrame *tdrc = - static_cast(tag->frameList("TDRC").front()); + dynamic_cast(tag->frameList("TDRC").front()); UnknownFrame *tdat = static_cast(tag->frameList("TDAT").front()); - if(tdrc->fieldList().size() == 1 && + if(tdrc && + tdrc->fieldList().size() == 1 && tdrc->fieldList().front().size() == 4 && tdat->data().size() >= 5) { @@ -456,6 +457,8 @@ namespace { "TDS", "TDES" }, { "TID", "TGID" }, { "WFD", "WFED" }, + { "MVN", "MVNM" }, + { "MVI", "MVIN" }, }; const size_t frameConversion2Size = sizeof(frameConversion2) / sizeof(frameConversion2[0]); diff --git a/3rdparty/taglib/mpeg/id3v2/id3v2tag.cpp b/3rdparty/taglib/mpeg/id3v2/id3v2tag.cpp index 4c00ab6fb..525e88b63 100644 --- a/3rdparty/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/3rdparty/taglib/mpeg/id3v2/id3v2tag.cpp @@ -60,6 +60,7 @@ class ID3v2::Tag::TagPrivate { public: TagPrivate() : + factory(0), file(0), tagOffset(0), extendedHeader(0), @@ -286,7 +287,7 @@ void ID3v2::Tag::setGenre(const String &s) void ID3v2::Tag::setYear(unsigned int i) { - if(i <= 0) { + if(i == 0) { removeFrames("TDRC"); return; } @@ -295,7 +296,7 @@ void ID3v2::Tag::setYear(unsigned int i) void ID3v2::Tag::setTrack(unsigned int i) { - if(i <= 0) { + if(i == 0) { removeFrames("TRCK"); return; } @@ -618,7 +619,6 @@ ByteVector ID3v2::Tag::render(int version) const } // Compute the amount of padding, and append that to tagData. - // TODO: Should be calculated in long long in taglib2. long originalSize = d->header.tagSize(); long paddingSize = originalSize - (tagData.size() - Header::size()); @@ -723,7 +723,7 @@ void ID3v2::Tag::parse(const ByteVector &origData) if(d->header.extendedHeader()) { if(!d->extendedHeader) - d->extendedHeader = new ExtendedHeader; + d->extendedHeader = new ExtendedHeader(); d->extendedHeader->setData(data); if(d->extendedHeader->size() <= data.size()) { frameDataPosition += d->extendedHeader->size(); diff --git a/3rdparty/taglib/mpeg/mpegfile.cpp b/3rdparty/taglib/mpeg/mpegfile.cpp index af7253fa5..517aea569 100644 --- a/3rdparty/taglib/mpeg/mpegfile.cpp +++ b/3rdparty/taglib/mpeg/mpegfile.cpp @@ -76,6 +76,58 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +namespace +{ + // Dummy file class to make a stream work with MPEG::Header. + + class AdapterFile : public TagLib::File + { + public: + AdapterFile(IOStream *stream) : File(stream) {} + + Tag *tag() const { return 0; } + AudioProperties *audioProperties() const { return 0; } + bool save() { return false; } + }; +} + +bool MPEG::File::isSupported(IOStream *stream) +{ + if(!stream || !stream->isOpen()) + return false; + + // An MPEG file has MPEG frame headers. An ID3v2 tag may precede. + + // MPEG frame headers are really confusing with irrelevant binary data. + // So we check if a frame header is really valid. + + long headerOffset; + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset); + + if(buffer.isEmpty()) + return false; + + const long originalPosition = stream->tell(); + AdapterFile file(stream); + + for(unsigned int i = 0; i < buffer.size() - 1; ++i) { + if(isFrameSync(buffer, i)) { + const Header header(&file, headerOffset + i, true); + if(header.isValid()) { + stream->seek(originalPosition); + return true; + } + } + } + + stream->seek(originalPosition); + return false; +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -346,55 +398,50 @@ void MPEG::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) long MPEG::File::nextFrameOffset(long position) { - bool foundLastSyncPattern = false; - - ByteVector buffer; + ByteVector frameSyncBytes(2, '\0'); while(true) { seek(position); - buffer = readBlock(bufferSize()); - - if(buffer.size() <= 0) + const ByteVector buffer = readBlock(bufferSize()); + if(buffer.isEmpty()) return -1; - if(foundLastSyncPattern && secondSynchByte(buffer[0])) - return position - 1; - - for(unsigned int i = 0; i < buffer.size() - 1; i++) { - if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1])) - return position + i; + for(unsigned int i = 0; i < buffer.size(); ++i) { + frameSyncBytes[0] = frameSyncBytes[1]; + frameSyncBytes[1] = buffer[i]; + if(isFrameSync(frameSyncBytes)) { + const Header header(this, position + i - 1, true); + if(header.isValid()) + return position + i - 1; + } } - foundLastSyncPattern = firstSyncByte(buffer[buffer.size() - 1]); - position += buffer.size(); + position += bufferSize(); } } long MPEG::File::previousFrameOffset(long position) { - bool foundFirstSyncPattern = false; - ByteVector buffer; + ByteVector frameSyncBytes(2, '\0'); - while (position > 0) { - long size = std::min(position, bufferSize()); - position -= size; + while(position > 0) { + const long bufferLength = std::min(position, bufferSize()); + position -= bufferLength; seek(position); - buffer = readBlock(size); + const ByteVector buffer = readBlock(bufferLength); - if(buffer.size() <= 0) - break; - - if(foundFirstSyncPattern && firstSyncByte(buffer[buffer.size() - 1])) - return position + buffer.size() - 1; - - for(int i = buffer.size() - 2; i >= 0; i--) { - if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1])) - return position + i; + for(int i = buffer.size() - 1; i >= 0; --i) { + frameSyncBytes[1] = frameSyncBytes[0]; + frameSyncBytes[0] = buffer[i]; + if(isFrameSync(frameSyncBytes)) { + const Header header(this, position + i, true); + if(header.isValid()) + return position + i + header.frameLength(); + } } - - foundFirstSyncPattern = secondSynchByte(buffer[0]); } + return -1; } @@ -488,28 +535,41 @@ long MPEG::File::findID3v2() const ByteVector headerID = ID3v2::Header::fileIdentifier(); seek(0); - - const ByteVector data = readBlock(headerID.size()); - if(data.size() < headerID.size()) - return -1; - - if(data == headerID) + if(readBlock(headerID.size()) == headerID) return 0; - if(firstSyncByte(data[0]) && secondSynchByte(data[1])) + const Header firstHeader(this, 0, true); + if(firstHeader.isValid()) return -1; - // Look for the entire file, if neither an MEPG frame or ID3v2 tag was found - // at the beginning of the file. - // We don't care about the inefficiency of the code, since this is a seldom case. + // Look for an ID3v2 tag until reaching the first valid MPEG frame. - const long tagOffset = find(headerID); - if(tagOffset < 0) - return -1; + ByteVector frameSyncBytes(2, '\0'); + ByteVector tagHeaderBytes(3, '\0'); + long position = 0; - const long frameOffset = firstFrameOffset(); - if(frameOffset < tagOffset) - return -1; + while(true) { + seek(position); + const ByteVector buffer = readBlock(bufferSize()); + if(buffer.isEmpty()) + return -1; - return tagOffset; + for(unsigned int i = 0; i < buffer.size(); ++i) { + frameSyncBytes[0] = frameSyncBytes[1]; + frameSyncBytes[1] = buffer[i]; + if(isFrameSync(frameSyncBytes)) { + const Header header(this, position + i - 1, true); + if(header.isValid()) + return -1; + } + + tagHeaderBytes[0] = tagHeaderBytes[1]; + tagHeaderBytes[1] = tagHeaderBytes[2]; + tagHeaderBytes[2] = buffer[i]; + if(tagHeaderBytes == headerID) + return position + i - 2; + } + + position += bufferSize(); + } } diff --git a/3rdparty/taglib/mpeg/mpegfile.h b/3rdparty/taglib/mpeg/mpegfile.h index e9e973879..2d2dff002 100644 --- a/3rdparty/taglib/mpeg/mpegfile.h +++ b/3rdparty/taglib/mpeg/mpegfile.h @@ -370,6 +370,15 @@ namespace TagLib { */ bool hasAPETag() const; + /*! + * Returns whether or not the given \a stream can be opened as an MPEG + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/mpeg/mpegheader.cpp b/3rdparty/taglib/mpeg/mpegheader.cpp index e678f15bf..5a5015d61 100644 --- a/3rdparty/taglib/mpeg/mpegheader.cpp +++ b/3rdparty/taglib/mpeg/mpegheader.cpp @@ -182,7 +182,7 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength) // Check for the MPEG synch bytes. - if(!firstSyncByte(data[0]) || !secondSynchByte(data[1])) { + if(!isFrameSync(data)) { debug("MPEG::Header::parse() -- MPEG header did not match MPEG synch."); return; } @@ -197,10 +197,8 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength) d->version = Version2; else if(versionBits == 3) d->version = Version1; - else { - debug("MPEG::Header::parse() -- Invalid MPEG version bits."); + else return; - } // Set the MPEG layer @@ -212,10 +210,8 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength) d->layer = 2; else if(layerBits == 3) d->layer = 1; - else { - debug("MPEG::Header::parse() -- Invalid MPEG layer bits."); + else return; - } d->protectionEnabled = (static_cast(data[1] & 0x01) == 0); @@ -244,10 +240,8 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength) d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex]; - if(d->bitrate == 0) { - debug("MPEG::Header::parse() -- Invalid bit rate."); + if(d->bitrate == 0) return; - } // Set the sample rate @@ -264,7 +258,6 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength) d->sampleRate = sampleRates[d->version][samplerateIndex]; if(d->sampleRate == 0) { - debug("MPEG::Header::parse() -- Invalid sample rate."); return; } @@ -311,20 +304,16 @@ void MPEG::Header::parse(File *file, long offset, bool checkLength) file->seek(offset + d->frameLength); const ByteVector nextData = file->readBlock(4); - if(nextData.size() < 4) { - debug("MPEG::Header::parse() -- Could not read the next frame header."); + if(nextData.size() < 4) return; - } const unsigned int HeaderMask = 0xfffe0c00; const unsigned int header = data.toUInt(0, true) & HeaderMask; const unsigned int nextHeader = nextData.toUInt(0, true) & HeaderMask; - if(header != nextHeader) { - debug("MPEG::Header::parse() -- The next frame was not consistent with this frame."); + if(header != nextHeader) return; - } } // Now that we're done parsing, set this to be a valid frame. diff --git a/3rdparty/taglib/mpeg/mpegproperties.cpp b/3rdparty/taglib/mpeg/mpegproperties.cpp index 6e7bb8232..d8b7bd506 100644 --- a/3rdparty/taglib/mpeg/mpegproperties.cpp +++ b/3rdparty/taglib/mpeg/mpegproperties.cpp @@ -157,23 +157,13 @@ void MPEG::Properties::read(File *file) { // Only the first valid frame is required if we have a VBR header. - long firstFrameOffset = file->firstFrameOffset(); + const long firstFrameOffset = file->firstFrameOffset(); if(firstFrameOffset < 0) { debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream."); return; } - Header firstHeader(file, firstFrameOffset); - - while(!firstHeader.isValid()) { - firstFrameOffset = file->nextFrameOffset(firstFrameOffset + 1); - if(firstFrameOffset < 0) { - debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream."); - return; - } - - firstHeader = Header(file, firstFrameOffset); - } + const Header firstHeader(file, firstFrameOffset, false); // Check for a VBR header that will help us in gathering information about a // VBR stream. @@ -207,24 +197,13 @@ void MPEG::Properties::read(File *file) // Look for the last MPEG audio frame to calculate the stream length. - long lastFrameOffset = file->lastFrameOffset(); + const long lastFrameOffset = file->lastFrameOffset(); if(lastFrameOffset < 0) { debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream."); return; } - Header lastHeader(file, lastFrameOffset, false); - - while(!lastHeader.isValid()) { - lastFrameOffset = file->previousFrameOffset(lastFrameOffset); - if(lastFrameOffset < 0) { - debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream."); - return; - } - - lastHeader = Header(file, lastFrameOffset, false); - } - + const Header lastHeader(file, lastFrameOffset, false); const long streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength(); if(streamLength > 0) d->length = static_cast(streamLength * 8.0 / d->bitrate + 0.5); diff --git a/3rdparty/taglib/mpeg/mpegutils.h b/3rdparty/taglib/mpeg/mpegutils.h index e35f752fb..31b45a43b 100644 --- a/3rdparty/taglib/mpeg/mpegutils.h +++ b/3rdparty/taglib/mpeg/mpegutils.h @@ -41,17 +41,17 @@ namespace TagLib * MPEG frames can be recognized by the bit pattern 11111111 111, so the * first byte is easy to check for, however checking to see if the second byte * starts with \e 111 is a bit more tricky, hence these functions. + * + * \note This does not check the length of the vector, since this is an + * internal utility function. */ - inline bool firstSyncByte(unsigned char byte) + inline bool isFrameSync(const ByteVector &bytes, unsigned int offset = 0) { - return (byte == 0xFF); - } + // 0xFF in the second byte is possible in theory, but it's very unlikely. - inline bool secondSynchByte(unsigned char byte) - { - // 0xFF is possible in theory, but it's very unlikely be a header. - - return (byte != 0xFF && ((byte & 0xE0) == 0xE0)); + const unsigned char b1 = bytes[offset + 0]; + const unsigned char b2 = bytes[offset + 1]; + return (b1 == 0xFF && b2 != 0xFF && (b2 & 0xE0) == 0xE0); } } diff --git a/3rdparty/taglib/ogg/flac/oggflacfile.cpp b/3rdparty/taglib/ogg/flac/oggflacfile.cpp index 3a36ebe86..53d04508a 100644 --- a/3rdparty/taglib/ogg/flac/oggflacfile.cpp +++ b/3rdparty/taglib/ogg/flac/oggflacfile.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "oggflacfile.h" @@ -65,22 +66,36 @@ public: int commentPacket; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Ogg::FLAC::File::isSupported(IOStream *stream) +{ + // An Ogg FLAC file has IDs "OggS" and "fLaC" somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("fLaC") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// Ogg::FLAC::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(file) + Properties::ReadStyle propertiesStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) read(readProperties, propertiesStyle); } Ogg::FLAC::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(stream) + Properties::ReadStyle propertiesStyle) : + Ogg::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) read(readProperties, propertiesStyle); } @@ -172,7 +187,7 @@ void Ogg::FLAC::File::read(bool readProperties, Properties::ReadStyle properties if(d->hasXiphComment) d->comment = new Ogg::XiphComment(xiphCommentData()); else - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); if(readProperties) diff --git a/3rdparty/taglib/ogg/flac/oggflacfile.h b/3rdparty/taglib/ogg/flac/oggflacfile.h index 28b3f67f8..b2686e457 100644 --- a/3rdparty/taglib/ogg/flac/oggflacfile.h +++ b/3rdparty/taglib/ogg/flac/oggflacfile.h @@ -127,9 +127,6 @@ namespace TagLib { /*! * Save the file. This will primarily save and update the XiphComment. * Returns true if the save is successful. - * - * \warning In the current implementation, it's dangerous to call save() - * repeatedly. It leads to a segfault. */ virtual bool save(); @@ -146,6 +143,14 @@ namespace TagLib { */ bool hasXiphComment() const; + /*! + * Check if the given \a stream can be opened as an Ogg FLAC file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/ogg/oggfile.cpp b/3rdparty/taglib/ogg/oggfile.cpp index 86b0b0764..c36e4d46c 100644 --- a/3rdparty/taglib/ogg/oggfile.cpp +++ b/3rdparty/taglib/ogg/oggfile.cpp @@ -253,7 +253,7 @@ void Ogg::File::writePacket(unsigned int i, const ByteVector &packet) ByteVectorList packets = firstPage->packets(); packets[i - firstPage->firstPacketIndex()] = packet; - if(firstPage != lastPage && lastPage->packetCount() > 2) { + if(firstPage != lastPage && lastPage->packetCount() > 1) { ByteVectorList lastPagePackets = lastPage->packets(); lastPagePackets.erase(lastPagePackets.begin()); packets.append(lastPagePackets); diff --git a/3rdparty/taglib/ogg/oggpage.cpp b/3rdparty/taglib/ogg/oggpage.cpp index 75aea22a2..414d3d530 100644 --- a/3rdparty/taglib/ogg/oggpage.cpp +++ b/3rdparty/taglib/ogg/oggpage.cpp @@ -208,15 +208,15 @@ List Ogg::Page::paginate(const ByteVectorList &packets, static const unsigned int SplitSize = 32 * 255; - // Force repagination if the packets are too large for a page. + // Force repagination if the segment table will exceed the size limit. if(strategy != Repaginate) { - size_t totalSize = packets.size(); + size_t tableSize = 0; for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) - totalSize += it->size(); + tableSize += it->size() / 255 + 1; - if(totalSize > 255 * 255) + if(tableSize > 255) strategy = Repaginate; } diff --git a/3rdparty/taglib/ogg/opus/opusfile.cpp b/3rdparty/taglib/ogg/opus/opusfile.cpp index 43c102d07..d4f191ad1 100644 --- a/3rdparty/taglib/ogg/opus/opusfile.cpp +++ b/3rdparty/taglib/ogg/opus/opusfile.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "opusfile.h" @@ -53,6 +54,18 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Ogg::Opus::File::isSupported(IOStream *stream) +{ + // An Opus file has IDs "OggS" and "OpusHead" somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("OpusHead") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -101,7 +114,7 @@ Opus::Properties *Opus::File::audioProperties() const bool Opus::File::save() { if(!d->comment) - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); setPacket(1, ByteVector("OpusTags", 8) + d->comment->render(false)); diff --git a/3rdparty/taglib/ogg/opus/opusfile.h b/3rdparty/taglib/ogg/opus/opusfile.h index 0363b5846..0e094eae8 100644 --- a/3rdparty/taglib/ogg/opus/opusfile.h +++ b/3rdparty/taglib/ogg/opus/opusfile.h @@ -110,12 +110,18 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. - * - * \warning In the current implementation, it's dangerous to call save() - * repeatedly. It leads to a segfault. */ virtual bool save(); + /*! + * Returns whether or not the given \a stream can be opened as an Opus + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/ogg/speex/speexfile.cpp b/3rdparty/taglib/ogg/speex/speexfile.cpp index 2d374aedc..b3c8a6362 100644 --- a/3rdparty/taglib/ogg/speex/speexfile.cpp +++ b/3rdparty/taglib/ogg/speex/speexfile.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "speexfile.h" @@ -53,6 +54,18 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Ogg::Speex::File::isSupported(IOStream *stream) +{ + // A Speex file has IDs "OggS" and "Speex " somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("Speex ") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -101,7 +114,7 @@ Speex::Properties *Speex::File::audioProperties() const bool Speex::File::save() { if(!d->comment) - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); setPacket(1, d->comment->render()); diff --git a/3rdparty/taglib/ogg/speex/speexfile.h b/3rdparty/taglib/ogg/speex/speexfile.h index de38bfbfc..1be7113cb 100644 --- a/3rdparty/taglib/ogg/speex/speexfile.h +++ b/3rdparty/taglib/ogg/speex/speexfile.h @@ -110,12 +110,18 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. - * - * \warning In the current implementation, it's dangerous to call save() - * repeatedly. It leads to a segfault. */ virtual bool save(); + /*! + * Returns whether or not the given \a stream can be opened as a Speex + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/ogg/vorbis/vorbisfile.cpp b/3rdparty/taglib/ogg/vorbis/vorbisfile.cpp index 1e19e8629..b4f221ab1 100644 --- a/3rdparty/taglib/ogg/vorbis/vorbisfile.cpp +++ b/3rdparty/taglib/ogg/vorbis/vorbisfile.cpp @@ -28,10 +28,10 @@ #include #include #include +#include #include "vorbisfile.h" - using namespace TagLib; class Vorbis::File::FilePrivate @@ -59,6 +59,18 @@ namespace TagLib { static const char vorbisCommentHeaderID[] = { 0x03, 'v', 'o', 'r', 'b', 'i', 's', 0 }; } +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Vorbis::File::isSupported(IOStream *stream) +{ + // An Ogg Vorbis file has IDs "OggS" and "\x01vorbis" somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("\x01vorbis") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -109,7 +121,7 @@ bool Vorbis::File::save() ByteVector v(vorbisCommentHeaderID); if(!d->comment) - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); v.append(d->comment->render()); setPacket(1, v); diff --git a/3rdparty/taglib/ogg/vorbis/vorbisfile.h b/3rdparty/taglib/ogg/vorbis/vorbisfile.h index 48d9d7ca2..04c0c04ea 100644 --- a/3rdparty/taglib/ogg/vorbis/vorbisfile.h +++ b/3rdparty/taglib/ogg/vorbis/vorbisfile.h @@ -118,12 +118,17 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. - * - * \warning In the current implementation, it's dangerous to call save() - * repeatedly. It leads to a segfault. */ virtual bool save(); + /*! + * Check if the given \a stream can be opened as an Ogg Vorbis file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/ogg/xiphcomment.cpp b/3rdparty/taglib/ogg/xiphcomment.cpp index db46aecb7..f56bf810c 100644 --- a/3rdparty/taglib/ogg/xiphcomment.cpp +++ b/3rdparty/taglib/ogg/xiphcomment.cpp @@ -261,10 +261,14 @@ bool Ogg::XiphComment::checkKey(const String &key) { if(key.size() < 1) return false; - for(String::ConstIterator it = key.begin(); it != key.end(); it++) - // forbid non-printable, non-ascii, '=' (#61) and '~' (#126) - if (*it < 32 || *it >= 128 || *it == 61 || *it == 126) + + // A key may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. + + for(String::ConstIterator it = key.begin(); it != key.end(); it++) { + if(*it < 0x20 || *it > 0x7D || *it == 0x3D) return false; + } + return true; } @@ -275,11 +279,18 @@ String Ogg::XiphComment::vendorID() const void Ogg::XiphComment::addField(const String &key, const String &value, bool replace) { + if(!checkKey(key)) { + debug("Ogg::XiphComment::addField() - Invalid key. Field not added."); + return; + } + + const String upperKey = key.upper(); + if(replace) - removeFields(key.upper()); + removeFields(upperKey); if(!key.isEmpty() && !value.isEmpty()) - d->fieldListMap[key.upper()].append(value); + d->fieldListMap[upperKey].append(value); } void Ogg::XiphComment::removeField(const String &key, const String &value) @@ -436,85 +447,69 @@ void Ogg::XiphComment::parse(const ByteVector &data) const unsigned int commentLength = data.toUInt(pos, false); pos += 4; - ByteVector entry = data.mid(pos, commentLength); - + const ByteVector entry = data.mid(pos, commentLength); pos += commentLength; // Don't go past data end + if(pos > data.size()) break; - // Handle Pictures separately - if(entry.startsWith("METADATA_BLOCK_PICTURE=")) { - - // We need base64 encoded data including padding - if((entry.size() - 23) > 3 && ((entry.size() - 23) % 4) == 0) { - - // Decode base64 picture data - ByteVector picturedata = ByteVector::fromBase64(entry.mid(23)); - if(picturedata.size()) { - - // Decode Flac Picture - FLAC::Picture * picture = new FLAC::Picture(); - if(picture->parse(picturedata)) { - - d->pictureList.append(picture); - - // continue to next field - continue; - } - else { - delete picture; - debug("Failed to decode FlacPicture block"); - } - } - else { - debug("Failed to decode base64 encoded data"); - } - } - else { - debug("Invalid base64 encoded data"); - } - } - - // Handle old picture standard - if(entry.startsWith("COVERART=")) { - - if((entry.size() - 9) > 3 && ((entry.size() - 9) % 4) == 0) { - - // Decode base64 picture data - ByteVector picturedata = ByteVector::fromBase64(entry.mid(9)); - if (picturedata.size()) { - - // Assume it's some type of image file - FLAC::Picture * picture = new FLAC::Picture(); - picture->setData(picturedata); - picture->setMimeType("image/"); - picture->setType(FLAC::Picture::Other); - d->pictureList.append(picture); - - // continue to next field - continue; - } - else { - debug("Failed to decode base64 encoded data"); - } - } - else { - debug("Invalid base64 encoded data"); - } - } - // Check for field separator - int sep = entry.find('='); + + const int sep = entry.find('='); if(sep < 1) { - debug("Discarding invalid comment field."); + debug("Ogg::XiphComment::parse() - Discarding a field. Separator not found."); continue; } - // Parse key and value - String key = String(entry.mid(0, sep), String::UTF8); - String value = String(entry.mid(sep + 1), String::UTF8); - addField(key, value, false); + // Parse the key + + const String key = String(entry.mid(0, sep), String::UTF8).upper(); + if(!checkKey(key)) { + debug("Ogg::XiphComment::parse() - Discarding a field. Invalid key."); + continue; + } + + if(key == "METADATA_BLOCK_PICTURE" || key == "COVERART") { + + // Handle Pictures separately + + const ByteVector picturedata = ByteVector::fromBase64(entry.mid(sep + 1)); + if(picturedata.isEmpty()) { + debug("Ogg::XiphComment::parse() - Discarding a field. Invalid base64 data"); + continue; + } + + if(key[0] == L'M') { + + // Decode FLAC Picture + + FLAC::Picture * picture = new FLAC::Picture(); + if(picture->parse(picturedata)) { + d->pictureList.append(picture); + } + else { + delete picture; + debug("Ogg::XiphComment::parse() - Failed to decode FLAC Picture block"); + } + } + else { + + // Assume it's some type of image file + + FLAC::Picture * picture = new FLAC::Picture(); + picture->setData(picturedata); + picture->setMimeType("image/"); + picture->setType(FLAC::Picture::Other); + d->pictureList.append(picture); + } + } + else { + + // Parse the text + + addField(key, String(entry.mid(sep + 1), String::UTF8), false); + } } } diff --git a/3rdparty/taglib/riff/aiff/aifffile.cpp b/3rdparty/taglib/riff/aiff/aifffile.cpp index 1a29938ce..4f9c868e6 100644 --- a/3rdparty/taglib/riff/aiff/aifffile.cpp +++ b/3rdparty/taglib/riff/aiff/aifffile.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "aifffile.h" @@ -53,6 +54,18 @@ public: bool hasID3v2; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool RIFF::AIFF::File::isSupported(IOStream *stream) +{ + // An AIFF file has to start with "FORM????AIFF" or "FORM????AIFC". + + const ByteVector id = Utils::readHeader(stream, 12, false); + return (id.startsWith("FORM") && (id.containsAt("AIFF", 8) || id.containsAt("AIFC", 8))); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/riff/aiff/aifffile.h b/3rdparty/taglib/riff/aiff/aifffile.h index a79d76b2b..5ba1a2791 100644 --- a/3rdparty/taglib/riff/aiff/aifffile.h +++ b/3rdparty/taglib/riff/aiff/aifffile.h @@ -126,6 +126,14 @@ namespace TagLib { */ bool hasID3v2Tag() const; + /*! + * Check if the given \a stream can be opened as an AIFF file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/riff/rifffile.cpp b/3rdparty/taglib/riff/rifffile.cpp index f9d33e10d..0af4d4b42 100644 --- a/3rdparty/taglib/riff/rifffile.cpp +++ b/3rdparty/taglib/riff/rifffile.cpp @@ -95,13 +95,13 @@ unsigned int RIFF::File::riffSize() const unsigned int RIFF::File::chunkCount() const { - return d->chunks.size(); + return static_cast(d->chunks.size()); } unsigned int RIFF::File::chunkDataSize(unsigned int i) const { if(i >= d->chunks.size()) { - debug("RIFF::File::chunkPadding() - Index out of range. Returning 0."); + debug("RIFF::File::chunkDataSize() - Index out of range. Returning 0."); return 0; } @@ -111,7 +111,7 @@ unsigned int RIFF::File::chunkDataSize(unsigned int i) const unsigned int RIFF::File::chunkOffset(unsigned int i) const { if(i >= d->chunks.size()) { - debug("RIFF::File::chunkPadding() - Index out of range. Returning 0."); + debug("RIFF::File::chunkOffset() - Index out of range. Returning 0."); return 0; } @@ -161,19 +161,19 @@ void RIFF::File::setChunkData(unsigned int i, const ByteVector &data) std::vector::iterator it = d->chunks.begin(); std::advance(it, i); - const int originalSize = it->size + it->padding; + const long long originalSize = static_cast(it->size) + it->padding; writeChunk(it->name, data, it->offset - 8, it->size + it->padding + 8); it->size = data.size(); - it->padding = data.size() % 1; + it->padding = data.size() % 2; - const int diff = it->size + it->padding - originalSize; + const long long diff = static_cast(it->size) + it->padding - originalSize; // Now update the internal offsets for(++it; it != d->chunks.end(); ++it) - it->offset += diff; + it->offset += static_cast(diff); // Update the global size. @@ -187,7 +187,7 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data) void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate) { - if(d->chunks.size() == 0) { + if(d->chunks.empty()) { debug("RIFF::File::setChunkData - No valid chunks found."); return; } @@ -269,7 +269,7 @@ void RIFF::File::removeChunk(unsigned int i) void RIFF::File::removeChunk(const ByteVector &name) { - for(int i = d->chunks.size() - 1; i >= 0; --i) { + for(int i = static_cast(d->chunks.size()) - 1; i >= 0; --i) { if(d->chunks[i].name == name) removeChunk(i); } @@ -292,7 +292,6 @@ void RIFF::File::read() d->size = readBlock(4).toUInt(bigEndian); offset += 8; - seek(offset); // + 8: chunk header at least, fix for additional junk bytes while(offset + 8 <= length()) { @@ -307,28 +306,24 @@ void RIFF::File::read() break; } - if(static_cast(tell()) + chunkSize > length()) { + if(static_cast(offset) + 8 + chunkSize > length()) { debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid size (larger than the file size)"); setValid(false); break; } - offset += 8; - Chunk chunk; - chunk.name = chunkName; - chunk.size = chunkSize; - chunk.offset = offset; + chunk.name = chunkName; + chunk.size = chunkSize; + chunk.offset = offset + 8; + chunk.padding = 0; - offset += chunk.size; - - seek(offset); + offset = chunk.offset + chunk.size; // Check padding - chunk.padding = 0; - if(offset & 1) { + seek(offset); const ByteVector iByte = readBlock(1); if(iByte.size() == 1 && iByte[0] == '\0') { chunk.padding = 1; diff --git a/3rdparty/taglib/riff/wav/wavfile.cpp b/3rdparty/taglib/riff/wav/wavfile.cpp index 79ff91675..0ebe21c32 100644 --- a/3rdparty/taglib/riff/wav/wavfile.cpp +++ b/3rdparty/taglib/riff/wav/wavfile.cpp @@ -23,10 +23,11 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "tbytevector.h" -#include "tdebug.h" -#include "tstringlist.h" -#include "tpropertymap.h" +#include +#include +#include +#include +#include #include "wavfile.h" #include "id3v2tag.h" @@ -60,6 +61,18 @@ public: bool hasInfo; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool RIFF::WAV::File::isSupported(IOStream *stream) +{ + // A WAV file has to start with "RIFF????WAVE". + + const ByteVector id = Utils::readHeader(stream, 12, false); + return (id.startsWith("RIFF") && id.containsAt("WAVE", 8)); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/riff/wav/wavfile.h b/3rdparty/taglib/riff/wav/wavfile.h index 80f17a852..f6c190ed6 100644 --- a/3rdparty/taglib/riff/wav/wavfile.h +++ b/3rdparty/taglib/riff/wav/wavfile.h @@ -175,6 +175,15 @@ namespace TagLib { */ bool hasInfoTag() const; + /*! + * Returns whether or not the given \a stream can be opened as a WAV + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/s3m/s3mproperties.cpp b/3rdparty/taglib/s3m/s3mproperties.cpp index 69654b1a4..e3e443cb8 100644 --- a/3rdparty/taglib/s3m/s3mproperties.cpp +++ b/3rdparty/taglib/s3m/s3mproperties.cpp @@ -64,7 +64,7 @@ public: S3M::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : AudioProperties(propertiesStyle), - d(new PropertiesPrivate) + d(new PropertiesPrivate()) { } diff --git a/3rdparty/taglib/taglib_config.h.cmake b/3rdparty/taglib/taglib_config.h.cmake index 5f0ee6cfd..915f130aa 100644 --- a/3rdparty/taglib/taglib_config.h.cmake +++ b/3rdparty/taglib/taglib_config.h.cmake @@ -1,6 +1,11 @@ /* taglib_config.h. Generated by cmake from taglib_config.h.cmake */ +#ifndef TAGLIB_TAGLIB_CONFIG_H +#define TAGLIB_TAGLIB_CONFIG_H + /* These values are no longer used. This file is present only for compatibility reasons. */ #define TAGLIB_WITH_ASF 1 #define TAGLIB_WITH_MP4 1 + +#endif diff --git a/3rdparty/taglib/tagunion.cpp b/3rdparty/taglib/tagunion.cpp index c10d72b29..64d786b9a 100644 --- a/3rdparty/taglib/tagunion.cpp +++ b/3rdparty/taglib/tagunion.cpp @@ -79,10 +79,9 @@ public: std::vector tags; }; -TagUnion::TagUnion(Tag *first, Tag *second, Tag *third) +TagUnion::TagUnion(Tag *first, Tag *second, Tag *third) : + d(new TagUnionPrivate()) { - d = new TagUnionPrivate; - d->tags[0] = first; d->tags[1] = second; d->tags[2] = third; diff --git a/3rdparty/taglib/tagutils.cpp b/3rdparty/taglib/tagutils.cpp index dc047040c..d6d924064 100644 --- a/3rdparty/taglib/tagutils.cpp +++ b/3rdparty/taglib/tagutils.cpp @@ -77,3 +77,29 @@ long Utils::findAPE(File *file, long id3v1Location) return -1; } + +ByteVector TagLib::Utils::readHeader(IOStream *stream, unsigned int length, + bool skipID3v2, long *headerOffset) +{ + if(!stream || !stream->isOpen()) + return ByteVector(); + + const long originalPosition = stream->tell(); + long bufferOffset = 0; + + if(skipID3v2) { + stream->seek(0); + const ByteVector data = stream->readBlock(ID3v2::Header::size()); + if(data.startsWith(ID3v2::Header::fileIdentifier())) + bufferOffset = ID3v2::Header(data).completeTagSize(); + } + + stream->seek(bufferOffset); + const ByteVector header = stream->readBlock(length); + stream->seek(originalPosition); + + if(headerOffset) + *headerOffset = bufferOffset; + + return header; +} diff --git a/3rdparty/taglib/tagutils.h b/3rdparty/taglib/tagutils.h index fb11d1e06..4488a32ba 100644 --- a/3rdparty/taglib/tagutils.h +++ b/3rdparty/taglib/tagutils.h @@ -30,9 +30,12 @@ #ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header +#include + namespace TagLib { class File; + class IOStream; namespace Utils { @@ -41,6 +44,9 @@ namespace TagLib { long findID3v2(File *file); long findAPE(File *file, long id3v1Location); + + ByteVector readHeader(IOStream *stream, unsigned int length, bool skipID3v2, + long *headerOffset = 0); } } diff --git a/3rdparty/taglib/toolkit/taglib.h b/3rdparty/taglib/toolkit/taglib.h index bd4886bd8..6b029974e 100644 --- a/3rdparty/taglib/toolkit/taglib.h +++ b/3rdparty/taglib/toolkit/taglib.h @@ -30,7 +30,7 @@ #define TAGLIB_MAJOR_VERSION 1 #define TAGLIB_MINOR_VERSION 11 -#define TAGLIB_PATCH_VERSION 0 +#define TAGLIB_PATCH_VERSION 1 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)) || defined(__clang__) #define TAGLIB_IGNORE_MISSING_DESTRUCTOR _Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") @@ -115,7 +115,7 @@ namespace TagLib { * * \section installing Installing TagLib * - * Please see the TagLib website for the latest + * Please see the TagLib website for the latest * downloads. * * TagLib can be built using the CMake build system. TagLib installs a taglib-config and pkg-config file to @@ -160,11 +160,10 @@ namespace TagLib { * * Questions about TagLib should be directed to the TagLib mailing list, not directly to the author. * - * - TagLib Homepage + * - TagLib Homepage * - TagLib Mailing List (taglib-devel@kde.org) * - * \author Scott Wheeler et al. - * + * \author TagLib authors. */ #endif diff --git a/3rdparty/taglib/toolkit/tbytevector.cpp b/3rdparty/taglib/toolkit/tbytevector.cpp index 6494a448b..d272057f6 100644 --- a/3rdparty/taglib/toolkit/tbytevector.cpp +++ b/3rdparty/taglib/toolkit/tbytevector.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -63,7 +62,7 @@ int findChar( for(TIterator it = dataBegin + offset; it < dataEnd; it += byteAlign) { if(*it == c) - return (it - dataBegin); + return static_cast(it - dataBegin); } return -1; @@ -105,7 +104,7 @@ int findVector( ++itPattern; if(itPattern == patternEnd) - return (it - dataBegin); + return static_cast(it - dataBegin); } } @@ -125,7 +124,7 @@ T toNumber(const ByteVector &v, size_t offset, size_t length, bool mostSignifica T sum = 0; for(size_t i = 0; i < length; i++) { const size_t shift = (mostSignificantByteFirst ? length - 1 - i : i) * 8; - sum |= static_cast(static_cast(v[offset + i])) << shift; + sum |= static_cast(static_cast(v[static_cast(offset + i)])) << shift; } return sum; @@ -176,7 +175,7 @@ TFloat toFloat(const ByteVector &v, size_t offset) } tmp; ::memcpy(&tmp, v.data() + offset, sizeof(TInt)); - if(ENDIAN != Utils::floatByteOrder()) + if(ENDIAN != Utils::systemByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return tmp.f; @@ -191,7 +190,7 @@ ByteVector fromFloat(TFloat value) } tmp; tmp.f = value; - if(ENDIAN != Utils::floatByteOrder()) + if(ENDIAN != Utils::systemByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return ByteVector(reinterpret_cast(&tmp), sizeof(TInt)); @@ -300,7 +299,7 @@ ByteVector ByteVector::null; ByteVector ByteVector::fromCString(const char *s, unsigned int length) { if(length == 0xffffffff) - return ByteVector(s, ::strlen(s)); + return ByteVector(s, static_cast(::strlen(s))); else return ByteVector(s, length); } @@ -375,7 +374,7 @@ ByteVector::ByteVector(const char *data, unsigned int length) : } ByteVector::ByteVector(const char *data) : - d(new ByteVectorPrivate(data, ::strlen(data))) + d(new ByteVectorPrivate(data, static_cast(::strlen(data)))) { } @@ -485,46 +484,60 @@ ByteVector &ByteVector::replace(char oldByte, char newByte) ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &with) { - // TODO: This takes O(n!) time in the worst case. Rewrite it to run in O(n) time. - - if(pattern.size() == 0 || pattern.size() > size()) - return *this; - if(pattern.size() == 1 && with.size() == 1) return replace(pattern[0], with[0]); - const size_t withSize = with.size(); - const size_t patternSize = pattern.size(); - const ptrdiff_t diff = withSize - patternSize; + // Check if there is at least one occurrence of the pattern. - size_t offset = 0; - while (true) { - offset = find(pattern, offset); - if(offset == static_cast(-1)) // Use npos in taglib2. - break; + int offset = find(pattern, 0); + if(offset == -1) + return *this; + + if(pattern.size() == with.size()) { + + // We think this case might be common enough to optimize it. detach(); + do + { + ::memcpy(data() + offset, with.data(), with.size()); + offset = find(pattern, offset + pattern.size()); + } while(offset != -1); + } + else { - if(diff < 0) { - ::memmove( - data() + offset + withSize, - data() + offset + patternSize, - size() - offset - patternSize); - resize(size() + diff); - } - else if(diff > 0) { - resize(size() + diff); - ::memmove( - data() + offset + withSize, - data() + offset + patternSize, - size() - diff - offset - patternSize); + // Loop once to calculate the result size. + + unsigned int dstSize = size(); + do + { + dstSize += with.size() - pattern.size(); + offset = find(pattern, offset + pattern.size()); + } while(offset != -1); + + // Loop again to copy modified data to the new vector. + + ByteVector dst(dstSize); + int dstOffset = 0; + + offset = 0; + while(true) { + const int next = find(pattern, offset); + if(next == -1) { + ::memcpy(dst.data() + dstOffset, data() + offset, size() - offset); + break; + } + + ::memcpy(dst.data() + dstOffset, data() + offset, next - offset); + dstOffset += next - offset; + + ::memcpy(dst.data() + dstOffset, with.data(), with.size()); + dstOffset += with.size(); + + offset = next + pattern.size(); } - ::memcpy(data() + offset, with.data(), with.size()); - - offset += withSize; - if(offset > size() - patternSize) - break; + swap(dst); } return *this; @@ -963,7 +976,7 @@ ByteVector ByteVector::fromBase64(const ByteVector & input) // Only return output if we processed all bytes if(len == 0) { - output.resize(dst - (unsigned char*) output.data()); + output.resize(static_cast(dst - (unsigned char*) output.data())); return output; } return ByteVector(); diff --git a/3rdparty/taglib/toolkit/tbytevectorstream.cpp b/3rdparty/taglib/toolkit/tbytevectorstream.cpp index 5e200b3d7..333f528c1 100644 --- a/3rdparty/taglib/toolkit/tbytevectorstream.cpp +++ b/3rdparty/taglib/toolkit/tbytevectorstream.cpp @@ -53,9 +53,9 @@ ByteVectorStream::ByteVectorStreamPrivate::ByteVectorStreamPrivate(const ByteVec // public members //////////////////////////////////////////////////////////////////////////////// -ByteVectorStream::ByteVectorStream(const ByteVector &data) +ByteVectorStream::ByteVectorStream(const ByteVector &data) : + d(new ByteVectorStreamPrivate(data)) { - d = new ByteVectorStreamPrivate(data); } ByteVectorStream::~ByteVectorStream() @@ -137,7 +137,7 @@ void ByteVectorStream::seek(long offset, Position p) d->position += offset; break; case End: - d->position = length() - offset; + d->position = length() + offset; // offset is expected to be negative break; } } diff --git a/3rdparty/taglib/toolkit/tdebug.cpp b/3rdparty/taglib/toolkit/tdebug.cpp index 557baed6e..b2efc4cb5 100644 --- a/3rdparty/taglib/toolkit/tdebug.cpp +++ b/3rdparty/taglib/toolkit/tdebug.cpp @@ -27,6 +27,8 @@ #include #endif +#if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) + #include "tdebug.h" #include "tstring.h" #include "tdebuglistener.h" @@ -43,27 +45,20 @@ namespace TagLib void debug(const String &s) { -#if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) - debugListener->printMessage("TagLib: " + s + "\n"); - -#endif } void debugData(const ByteVector &v) { -#if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) - - for(size_t i = 0; i < v.size(); ++i) - { - std::string bits = std::bitset<8>(v[i]).to_string(); - String msg = Utils::formatString( - "*** [%d] - char '%c' - int %d, 0x%02x, 0b%s\n", + for(unsigned int i = 0; i < v.size(); ++i) { + const std::string bits = std::bitset<8>(v[i]).to_string(); + const String msg = Utils::formatString( + "*** [%u] - char '%c' - int %d, 0x%02x, 0b%s\n", i, v[i], v[i], v[i], bits.c_str()); debugListener->printMessage(msg); } - -#endif } } + +#endif diff --git a/3rdparty/taglib/toolkit/tdebug.h b/3rdparty/taglib/toolkit/tdebug.h index bd94d159f..80d00d39e 100644 --- a/3rdparty/taglib/toolkit/tdebug.h +++ b/3rdparty/taglib/toolkit/tdebug.h @@ -32,10 +32,11 @@ namespace TagLib { class ByteVector; #ifndef DO_NOT_DOCUMENT +#if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) /*! - * A simple function that outputs the debug messages to the listener. - * The default listener redirects the messages to \a stderr when NDEBUG is + * A simple function that outputs the debug messages to the listener. + * The default listener redirects the messages to \a stderr when NDEBUG is * not defined. * * \warning Do not use this outside of TagLib, it could lead to undefined @@ -45,7 +46,7 @@ namespace TagLib { * \internal */ void debug(const String &s); - + /*! * For debugging binary data. * @@ -56,6 +57,13 @@ namespace TagLib { * \internal */ void debugData(const ByteVector &v); + +#else + + #define debug(x) ((void)0) + #define debugData(x) ((void)0) + +#endif } #endif diff --git a/3rdparty/taglib/toolkit/tfile.cpp b/3rdparty/taglib/toolkit/tfile.cpp index c634baa85..aff1684d9 100644 --- a/3rdparty/taglib/toolkit/tfile.cpp +++ b/3rdparty/taglib/toolkit/tfile.cpp @@ -69,39 +69,38 @@ using namespace TagLib; class File::FilePrivate { public: - FilePrivate(IOStream *stream, bool owner); + FilePrivate(IOStream *stream, bool owner) : + stream(stream), + streamOwner(owner), + valid(true) {} + + ~FilePrivate() + { + if(streamOwner) + delete stream; + } IOStream *stream; bool streamOwner; bool valid; }; -File::FilePrivate::FilePrivate(IOStream *stream, bool owner) : - stream(stream), - streamOwner(owner), - valid(true) -{ -} - //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -File::File(FileName fileName) +File::File(FileName fileName) : + d(new FilePrivate(new FileStream(fileName), true)) { - IOStream *stream = new FileStream(fileName); - d = new FilePrivate(stream, true); } -File::File(IOStream *stream) +File::File(IOStream *stream) : + d(new FilePrivate(stream, false)) { - d = new FilePrivate(stream, false); } File::~File() { - if(d->stream && d->streamOwner) - delete d->stream; delete d; } diff --git a/3rdparty/taglib/toolkit/tfile.h b/3rdparty/taglib/toolkit/tfile.h index a6dda7bad..55f53a521 100644 --- a/3rdparty/taglib/toolkit/tfile.h +++ b/3rdparty/taglib/toolkit/tfile.h @@ -86,7 +86,7 @@ namespace TagLib { * format, the returned map's unsupportedData() list will contain one entry identifying * that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties() * to remove (a subset of) them. - * For files that contain more than one tag (e.g. an MP3 with both an ID3v2 and an ID3v2 + * For files that contain more than one tag (e.g. an MP3 with both an ID3v1 and an ID3v2 * tag) only the most "modern" one will be exported (ID3v2 in this case). * BIC: Will be made virtual in future releases. */ diff --git a/3rdparty/taglib/toolkit/tfilestream.cpp b/3rdparty/taglib/toolkit/tfilestream.cpp index 5205bae0b..17a09f3da 100644 --- a/3rdparty/taglib/toolkit/tfilestream.cpp +++ b/3rdparty/taglib/toolkit/tfilestream.cpp @@ -51,12 +51,11 @@ namespace { const DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); - if(!path.wstr().empty()) - return CreateFileW(path.wstr().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - else if(!path.str().empty()) - return CreateFileA(path.str().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - else - return InvalidFileHandle; +#if defined (PLATFORM_WINRT) + return CreateFile2(path.wstr().c_str(), access, FILE_SHARE_READ, OPEN_EXISTING, NULL); +#else + return CreateFileW(path.wstr().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); +#endif } void closeFile(FileHandle file) @@ -262,7 +261,7 @@ void FileStream::insert(const ByteVector &data, unsigned long start, unsigned lo // to overwrite. Appropriately increment the readPosition. seek(readPosition); - const size_t bytesRead = readFile(d->file, aboutToOverwrite); + const unsigned int bytesRead = static_cast(readFile(d->file, aboutToOverwrite)); aboutToOverwrite.resize(bytesRead); readPosition += bufferLength; @@ -305,10 +304,10 @@ void FileStream::removeBlock(unsigned long start, unsigned long length) ByteVector buffer(static_cast(bufferLength)); - for(size_t bytesRead = -1; bytesRead != 0;) + for(unsigned int bytesRead = -1; bytesRead != 0;) { seek(readPosition); - bytesRead = readFile(d->file, buffer); + bytesRead = static_cast(readFile(d->file, buffer)); readPosition += bytesRead; // Check to see if we just read the last block. We need to call clear() @@ -347,28 +346,17 @@ void FileStream::seek(long offset, Position p) #ifdef _WIN32 - DWORD whence; - switch(p) { - case Beginning: - whence = FILE_BEGIN; - break; - case Current: - whence = FILE_CURRENT; - break; - case End: - whence = FILE_END; - break; - default: + if(p != Beginning && p != Current && p != End) { debug("FileStream::seek() -- Invalid Position value."); return; } - SetLastError(NO_ERROR); - SetFilePointer(d->file, offset, NULL, whence); + LARGE_INTEGER liOffset; + liOffset.QuadPart = offset; - const int lastError = GetLastError(); - if(lastError != NO_ERROR && lastError != ERROR_NEGATIVE_SEEK) + if(!SetFilePointerEx(d->file, liOffset, NULL, static_cast(p))) { debug("FileStream::seek() -- Failed to set the file pointer."); + } #else @@ -410,10 +398,11 @@ long FileStream::tell() const { #ifdef _WIN32 - SetLastError(NO_ERROR); - const DWORD position = SetFilePointer(d->file, 0, NULL, FILE_CURRENT); - if(GetLastError() == NO_ERROR) { - return static_cast(position); + const LARGE_INTEGER zero = {}; + LARGE_INTEGER position; + + if(SetFilePointerEx(d->file, zero, &position, FILE_CURRENT) && position.QuadPart <= LONG_MAX) { + return static_cast(position.QuadPart); } else { debug("FileStream::tell() -- Failed to get the file pointer."); @@ -436,10 +425,10 @@ long FileStream::length() #ifdef _WIN32 - SetLastError(NO_ERROR); - const DWORD fileSize = GetFileSize(d->file, NULL); - if(GetLastError() == NO_ERROR) { - return static_cast(fileSize); + LARGE_INTEGER fileSize; + + if(GetFileSizeEx(d->file, &fileSize) && fileSize.QuadPart <= LONG_MAX) { + return static_cast(fileSize.QuadPart); } else { debug("FileStream::length() -- Failed to get the file size."); @@ -472,9 +461,7 @@ void FileStream::truncate(long length) seek(length); - SetLastError(NO_ERROR); - SetEndOfFile(d->file); - if(GetLastError() != NO_ERROR) { + if(!SetEndOfFile(d->file)) { debug("FileStream::truncate() -- Failed to truncate the file."); } diff --git a/3rdparty/taglib/toolkit/tiostream.cpp b/3rdparty/taglib/toolkit/tiostream.cpp index 72fe32a61..de0bd5053 100644 --- a/3rdparty/taglib/toolkit/tiostream.cpp +++ b/3rdparty/taglib/toolkit/tiostream.cpp @@ -23,73 +23,49 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#ifdef _WIN32 +# include +# include +#endif + #include "tiostream.h" using namespace TagLib; #ifdef _WIN32 -# include "tstring.h" -# include "tdebug.h" -# include - namespace { - // Check if the running system has CreateFileW() function. - // Windows9x systems don't have CreateFileW() or can't accept Unicode file names. - - bool supportsUnicode() + std::wstring ansiToUnicode(const char *str) { -#ifdef UNICODE - return true; -#else - const FARPROC p = GetProcAddress(GetModuleHandleA("kernel32"), "CreateFileW"); - return (p != NULL); -#endif - } - - // Indicates whether the system supports Unicode file names. - - const bool SystemSupportsUnicode = supportsUnicode(); - - // Converts a UTF-16 string into a local encoding. - // This function should only be used in Windows9x systems which don't support - // Unicode file names. - - std::string unicodeToAnsi(const wchar_t *wstr) - { - if(SystemSupportsUnicode) { - debug("unicodeToAnsi() - Should not be used on WinNT systems."); - } - - const int len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL); + const int len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); if(len == 0) - return std::string(); + return std::wstring(); - std::string str(len, '\0'); - WideCharToMultiByte(CP_ACP, 0, wstr, -1, &str[0], len, NULL, NULL); + std::wstring wstr(len - 1, L'\0'); + MultiByteToWideChar(CP_ACP, 0, str, -1, &wstr[0], len); - return str; + return wstr; } } -// If WinNT, stores a Unicode string into m_wname directly. -// If Win9x, converts and stores it into m_name to avoid calling Unicode version functions. +// m_name is no longer used, but kept for backward compatibility. -FileName::FileName(const wchar_t *name) - : m_name (SystemSupportsUnicode ? "" : unicodeToAnsi(name)) - , m_wname(SystemSupportsUnicode ? name : L"") +FileName::FileName(const wchar_t *name) : + m_name(), + m_wname(name) { } -FileName::FileName(const char *name) - : m_name(name) +FileName::FileName(const char *name) : + m_name(), + m_wname(ansiToUnicode(name)) { } -FileName::FileName(const FileName &name) - : m_name (name.m_name) - , m_wname(name.m_wname) +FileName::FileName(const FileName &name) : + m_name(), + m_wname(name.m_wname) { } @@ -115,25 +91,9 @@ const std::string &FileName::str() const String FileName::toString() const { - if(!m_wname.empty()) { - return String(m_wname); - } - else if(!m_name.empty()) { - const int len = MultiByteToWideChar(CP_ACP, 0, m_name.c_str(), -1, NULL, 0); - if(len == 0) - return String(); - - std::vector buf(len); - MultiByteToWideChar(CP_ACP, 0, m_name.c_str(), -1, &buf[0], len); - - return String(&buf[0]); - } - else { - return String(); - } + return String(m_wname.c_str()); } - #endif // _WIN32 //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/toolkit/tlist.h b/3rdparty/taglib/toolkit/tlist.h index 6b4aa0a52..377b82481 100644 --- a/3rdparty/taglib/toolkit/tlist.h +++ b/3rdparty/taglib/toolkit/tlist.h @@ -229,6 +229,11 @@ namespace TagLib { */ List &operator=(const List &l); + /*! + * Exchanges the content of this list by the content of \a l. + */ + void swap(List &l); + /*! * Compares this list with \a l and returns true if all of the elements are * the same. diff --git a/3rdparty/taglib/toolkit/tlist.tcc b/3rdparty/taglib/toolkit/tlist.tcc index bf8b0007c..478f09834 100644 --- a/3rdparty/taglib/toolkit/tlist.tcc +++ b/3rdparty/taglib/toolkit/tlist.tcc @@ -89,9 +89,9 @@ public: //////////////////////////////////////////////////////////////////////////////// template -List::List() +List::List() : + d(new ListPrivate()) { - d = new ListPrivate; } template @@ -196,7 +196,7 @@ List &List::clear() template unsigned int List::size() const { - return d->list.size(); + return static_cast(d->list.size()); } template @@ -283,16 +283,18 @@ const T &List::operator[](unsigned int i) const template List &List::operator=(const List &l) { - if(&l == this) - return *this; - - if(d->deref()) - delete d; - d = l.d; - d->ref(); + List(l).swap(*this); return *this; } +template +void List::swap(List &l) +{ + using std::swap; + + swap(d, l.d); +} + template bool List::operator==(const List &l) const { diff --git a/3rdparty/taglib/toolkit/tmap.h b/3rdparty/taglib/toolkit/tmap.h index c24c76367..f54e5a2a9 100644 --- a/3rdparty/taglib/toolkit/tmap.h +++ b/3rdparty/taglib/toolkit/tmap.h @@ -174,6 +174,11 @@ namespace TagLib { */ Map &operator=(const Map &m); + /*! + * Exchanges the content of this map by the content of \a m. + */ + void swap(Map &m); + protected: /* * If this List is being shared via implicit sharing, do a deep copy of the diff --git a/3rdparty/taglib/toolkit/tmap.tcc b/3rdparty/taglib/toolkit/tmap.tcc index 68f0d311a..2e4ed5ebf 100644 --- a/3rdparty/taglib/toolkit/tmap.tcc +++ b/3rdparty/taglib/toolkit/tmap.tcc @@ -48,9 +48,9 @@ public: }; template -Map::Map() +Map::Map() : + d(new MapPrivate()) { - d = new MapPrivate; } template @@ -152,7 +152,7 @@ Map &Map::erase(const Key &key) template unsigned int Map::size() const { - return d->map.size(); + return static_cast(d->map.size()); } template @@ -171,16 +171,18 @@ T &Map::operator[](const Key &key) template Map &Map::operator=(const Map &m) { - if(&m == this) - return *this; - - if(d->deref()) - delete(d); - d = m.d; - d->ref(); + Map(m).swap(*this); return *this; } +template +void Map::swap(Map &m) +{ + using std::swap; + + swap(d, m.d); +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/toolkit/trefcounter.cpp b/3rdparty/taglib/toolkit/trefcounter.cpp index 27d17b834..6638fcaa5 100644 --- a/3rdparty/taglib/toolkit/trefcounter.cpp +++ b/3rdparty/taglib/toolkit/trefcounter.cpp @@ -31,14 +31,9 @@ #if defined(HAVE_STD_ATOMIC) # include -# define ATOMIC_INT std::atomic -# define ATOMIC_INC(x) x.fetch_add(1) -# define ATOMIC_DEC(x) (x.fetch_sub(1) - 1) -#elif defined(HAVE_BOOST_ATOMIC) -# include -# define ATOMIC_INT boost::atomic -# define ATOMIC_INC(x) x.fetch_add(1) -# define ATOMIC_DEC(x) (x.fetch_sub(1) - 1) +# define ATOMIC_INT std::atomic_int +# define ATOMIC_INC(x) (++x) +# define ATOMIC_DEC(x) (--x) #elif defined(HAVE_GCC_ATOMIC) # define ATOMIC_INT int # define ATOMIC_INC(x) __sync_add_and_fetch(&x, 1) diff --git a/3rdparty/taglib/toolkit/trefcounter.h b/3rdparty/taglib/toolkit/trefcounter.h index bc3cdd9be..db97c5385 100644 --- a/3rdparty/taglib/toolkit/trefcounter.h +++ b/3rdparty/taglib/toolkit/trefcounter.h @@ -30,6 +30,7 @@ #include "taglib.h" #ifdef __APPLE__ +# define OSATOMIC_DEPRECATED 0 # include # define TAGLIB_ATOMIC_MAC #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) diff --git a/3rdparty/taglib/toolkit/tstring.cpp b/3rdparty/taglib/toolkit/tstring.cpp index 01a69266b..c60a3e2ed 100644 --- a/3rdparty/taglib/toolkit/tstring.cpp +++ b/3rdparty/taglib/toolkit/tstring.cpp @@ -23,19 +23,10 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -// This class assumes that std::basic_string has a contiguous and null-terminated buffer. - -#include #include #include -#include -#include -#ifdef _WIN32 -# include -#else -# include "unicode.h" -#endif +#include #include #include @@ -48,70 +39,6 @@ namespace { using namespace TagLib; - size_t UTF16toUTF8(const wchar_t *src, size_t srcLength, char *dst, size_t dstLength) - { - size_t len = 0; - -#ifdef _WIN32 - - len = ::WideCharToMultiByte(CP_UTF8, 0, src, srcLength, dst, dstLength, NULL, NULL); - -#else - - using namespace Unicode; - - const UTF16 *srcBegin = src; - const UTF16 *srcEnd = srcBegin + srcLength; - - UTF8 *dstBegin = reinterpret_cast(dst); - UTF8 *dstEnd = dstBegin + dstLength; - - ConversionResult result = ConvertUTF16toUTF8( - &srcBegin, srcEnd, &dstBegin, dstEnd, lenientConversion); - - if(result == conversionOK) - len = dstBegin - reinterpret_cast(dst); - -#endif - - if(len == 0) - debug("String::UTF16toUTF8() - Unicode conversion error."); - - return len; - } - - size_t UTF8toUTF16(const char *src, size_t srcLength, wchar_t *dst, size_t dstLength) - { - size_t len = 0; - -#ifdef _WIN32 - - len = ::MultiByteToWideChar(CP_UTF8, 0, src, srcLength, dst, dstLength); - -#else - - using namespace Unicode; - - const UTF8 *srcBegin = reinterpret_cast(src); - const UTF8 *srcEnd = srcBegin + srcLength; - - UTF16 *dstBegin = dst; - UTF16 *dstEnd = dstBegin + dstLength; - - ConversionResult result = ConvertUTF8toUTF16( - &srcBegin, srcEnd, &dstBegin, dstEnd, lenientConversion); - - if(result == conversionOK) - len = dstBegin - dst; - -#endif - - if(len == 0) - debug("String::UTF8toUTF16() - Unicode conversion error."); - - return len; - } - // Returns the native format of std::wstring. String::Type wcharByteOrder() { @@ -137,28 +64,61 @@ namespace { data.resize(length); - if(length > 0) { - const size_t len = UTF8toUTF16(s, length, &data[0], data.size()); - data.resize(len); + try { + const std::wstring::iterator dstEnd = utf8::utf8to16(s, s + length, data.begin()); + data.resize(dstEnd - data.begin()); } + catch(const utf8::exception &e) { + const String message(e.what()); + debug("String::copyFromUTF8() - UTF8-CPP error: " + message); + data.clear(); + } + } + + // Helper functions to read a UTF-16 character from an array. + template + unsigned short nextUTF16(const T **p); + + template <> + unsigned short nextUTF16(const wchar_t **p) + { + return static_cast(*(*p)++); + } + + template <> + unsigned short nextUTF16(const char **p) + { + union { + unsigned short w; + char c[2]; + } u; + u.c[0] = *(*p)++; + u.c[1] = *(*p)++; + return u.w; } // Converts a UTF-16 (with BOM), UTF-16LE or UTF16-BE string into // UTF-16(without BOM/CPU byte order) and copies it to the internal buffer. - void copyFromUTF16(std::wstring &data, const wchar_t *s, size_t length, String::Type t) + template + void copyFromUTF16(std::wstring &data, const T *s, size_t length, String::Type t) { bool swap; if(t == String::UTF16) { - if(length >= 1 && s[0] == 0xfeff) - swap = false; // Same as CPU endian. No need to swap bytes. - else if(length >= 1 && s[0] == 0xfffe) - swap = true; // Not same as CPU endian. Need to swap bytes. - else { - debug("String::copyFromUTF16() - Invalid UTF16 string."); + if(length < 1) { + debug("String::copyFromUTF16() - Invalid UTF16 string. Too short to have a BOM."); + return; + } + + const unsigned short bom = nextUTF16(&s); + if(bom == 0xfeff) + swap = false; // Same as CPU endian. No need to swap bytes. + else if(bom == 0xfffe) + swap = true; // Not same as CPU endian. Need to swap bytes. + else { + debug("String::copyFromUTF16() - Invalid UTF16 string. BOM is broken."); return; } - s++; length--; } else { @@ -166,57 +126,12 @@ namespace } data.resize(length); - if(length > 0) { - if(swap) { - for(size_t i = 0; i < length; ++i) - data[i] = Utils::byteSwap(static_cast(s[i])); - } - else { - ::wmemcpy(&data[0], s, length); - } - } - } - - // Converts a UTF-16 (with BOM), UTF-16LE or UTF16-BE string into - // UTF-16(without BOM/CPU byte order) and copies it to the internal buffer. - void copyFromUTF16(std::wstring &data, const char *s, size_t length, String::Type t) - { - bool swap; - if(t == String::UTF16) { - if(length < 2) { - debug("String::copyFromUTF16() - Invalid UTF16 string."); - return; - } - - // Uses memcpy instead of reinterpret_cast to avoid an alignment exception. - unsigned short bom; - ::memcpy(&bom, s, 2); - - if(bom == 0xfeff) - swap = false; // Same as CPU endian. No need to swap bytes. - else if(bom == 0xfffe) - swap = true; // Not same as CPU endian. Need to swap bytes. - else { - debug("String::copyFromUTF16() - Invalid UTF16 string."); - return; - } - - s += 2; - length -= 2; - } - else { - swap = (t != wcharByteOrder()); - } - - data.resize(length / 2); - for(size_t i = 0; i < length / 2; ++i) { - unsigned short c; - ::memcpy(&c, s, 2); + for(size_t i = 0; i < length; ++i) { + const unsigned short c = nextUTF16(&s); if(swap) - c = Utils::byteSwap(c); - - data[i] = static_cast(c); - s += 2; + data[i] = Utils::byteSwap(c); + else + data[i] = c; } } } @@ -229,10 +144,6 @@ public: StringPrivate() : RefCounter() {} - StringPrivate(unsigned int n, wchar_t c) : - RefCounter(), - data(static_cast(n), c) {} - /*! * Stores string in UTF-16. The byte order depends on the CPU endian. */ @@ -332,9 +243,13 @@ String::String(wchar_t c, Type t) : } String::String(char c, Type t) : - d(new StringPrivate(1, static_cast(c))) + d(new StringPrivate()) { - if(t != Latin1 && t != UTF8) { + if(t == Latin1) + copyFromLatin1(d->data, &c, 1); + else if(t == String::UTF8) + copyFromUTF8(d->data, &c, 1); + else { debug("String::String() -- char should not contain UTF16."); } } @@ -350,7 +265,7 @@ String::String(const ByteVector &v, Type t) : else if(t == UTF8) copyFromUTF8(d->data, v.data(), v.size()); else - copyFromUTF16(d->data, v.data(), v.size(), t); + copyFromUTF16(d->data, v.data(), v.size() / 2, t); // If we hit a null in the ByteVector, shrink the string again. d->data.resize(::wcslen(d->data.c_str())); @@ -410,12 +325,12 @@ String::ConstIterator String::end() const int String::find(const String &s, int offset) const { - return d->data.find(s.d->data, offset); + return static_cast(d->data.find(s.d->data, offset)); } int String::rfind(const String &s, int offset) const { - return d->data.rfind(s.d->data, offset); + return static_cast(d->data.rfind(s.d->data, offset)); } StringList String::split(const String &separator) const @@ -445,7 +360,10 @@ bool String::startsWith(const String &s) const String String::substr(unsigned int position, unsigned int n) const { - return String(d->data.substr(position, n)); + if(position == 0 && n >= size()) + return *this; + else + return String(d->data.substr(position, n)); } String &String::append(const String &s) @@ -464,12 +382,11 @@ String & String::clear() String String::upper() const { String s; + s.d->data.reserve(size()); - static int shift = 'A' - 'a'; - - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); ++it) { + for(ConstIterator it = begin(); it != end(); ++it) { if(*it >= 'a' && *it <= 'z') - s.d->data.push_back(*it + shift); + s.d->data.push_back(*it + 'A' - 'a'); else s.d->data.push_back(*it); } @@ -479,7 +396,7 @@ String String::upper() const unsigned int String::size() const { - return d->data.size(); + return static_cast(d->data.size()); } unsigned int String::length() const @@ -506,25 +423,27 @@ ByteVector String::data(Type t) const ByteVector v(size(), 0); char *p = v.data(); - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) + for(ConstIterator it = begin(); it != end(); ++it) *p++ = static_cast(*it); return v; } case UTF8: - if(!d->data.empty()) { - ByteVector v(size() * 4 + 1, 0); + ByteVector v(size() * 4, 0); - const size_t len = UTF16toUTF8( - d->data.c_str(), d->data.size(), v.data(), v.size()); - v.resize(len); + try { + const ByteVector::Iterator dstEnd = utf8::utf16to8(begin(), end(), v.begin()); + v.resize(static_cast(dstEnd - v.begin())); + } + catch(const utf8::exception &e) { + const String message(e.what()); + debug("String::data() - UTF8-CPP error: " + message); + v.clear(); + } return v; } - else { - return ByteVector(); - } case UTF16: { ByteVector v(2 + size() * 2, 0); @@ -535,7 +454,7 @@ ByteVector String::data(Type t) const *p++ = '\xff'; *p++ = '\xfe'; - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + for(ConstIterator it = begin(); it != end(); ++it) { *p++ = static_cast(*it & 0xff); *p++ = static_cast(*it >> 8); } @@ -547,7 +466,7 @@ ByteVector String::data(Type t) const ByteVector v(size() * 2, 0); char *p = v.data(); - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + for(ConstIterator it = begin(); it != end(); ++it) { *p++ = static_cast(*it >> 8); *p++ = static_cast(*it & 0xff); } @@ -559,7 +478,7 @@ ByteVector String::data(Type t) const ByteVector v(size() * 2, 0); char *p = v.data(); - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + for(ConstIterator it = begin(); it != end(); ++it) { *p++ = static_cast(*it & 0xff); *p++ = static_cast(*it >> 8); } @@ -604,12 +523,12 @@ String String::stripWhiteSpace() const return String(); const size_t pos2 = d->data.find_last_not_of(WhiteSpaceChars); - return substr(pos1, pos2 - pos1 + 1); + return substr(static_cast(pos1), static_cast(pos2 - pos1 + 1)); } bool String::isLatin1() const { - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + for(ConstIterator it = begin(); it != end(); ++it) { if(*it >= 256) return false; } @@ -618,7 +537,7 @@ bool String::isLatin1() const bool String::isAscii() const { - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + for(ConstIterator it = begin(); it != end(); ++it) { if(*it >= 128) return false; } @@ -787,6 +706,12 @@ void String::detach() if(d->count() > 1) String(d->data.c_str()).swap(*this); } + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +const String::Type String::WCharByteOrder = wcharByteOrder(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/toolkit/tstring.h b/3rdparty/taglib/toolkit/tstring.h index 97e9ff66e..b1be04b8c 100644 --- a/3rdparty/taglib/toolkit/tstring.h +++ b/3rdparty/taglib/toolkit/tstring.h @@ -536,6 +536,13 @@ namespace TagLib { void detach(); private: + /*! + * \deprecated This variable is no longer used, but NEVER remove this. It + * may lead to a linkage error. + */ + // BIC: remove + static const Type WCharByteOrder; + class StringPrivate; StringPrivate *d; }; diff --git a/3rdparty/taglib/toolkit/tutils.h b/3rdparty/taglib/toolkit/tutils.h index e3e4f6c36..6d96cd12d 100644 --- a/3rdparty/taglib/toolkit/tutils.h +++ b/3rdparty/taglib/toolkit/tutils.h @@ -34,9 +34,7 @@ # include #endif -#if defined(HAVE_BOOST_BYTESWAP) -# include -#elif defined(HAVE_MSC_BYTESWAP) +#if defined(HAVE_MSC_BYTESWAP) # include #elif defined(HAVE_GLIBC_BYTESWAP) # include @@ -63,11 +61,7 @@ namespace TagLib */ inline unsigned short byteSwap(unsigned short x) { -#if defined(HAVE_BOOST_BYTESWAP) - - return boost::endian::endian_reverse(static_cast(x)); - -#elif defined(HAVE_GCC_BYTESWAP) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap16(x); @@ -99,11 +93,7 @@ namespace TagLib */ inline unsigned int byteSwap(unsigned int x) { -#if defined(HAVE_BOOST_BYTESWAP) - - return boost::endian::endian_reverse(static_cast(x)); - -#elif defined(HAVE_GCC_BYTESWAP) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap32(x); @@ -138,11 +128,7 @@ namespace TagLib */ inline unsigned long long byteSwap(unsigned long long x) { -#if defined(HAVE_BOOST_BYTESWAP) - - return boost::endian::endian_reverse(static_cast(x)); - -#elif defined(HAVE_GCC_BYTESWAP) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap64(x); @@ -221,23 +207,6 @@ namespace TagLib return String(); } - /*! - * Returns whether the two strings s1 and s2 are equal, ignoring the case of - * the characters. - * - * We took the trouble to define this one here, since there are some - * incompatible variations of case insensitive strcmp(). - */ - inline bool equalsIgnoreCase(const char *s1, const char *s2) - { - while(*s1 != '\0' && *s2 != '\0' && ::tolower(*s1) == ::tolower(*s2)) { - s1++; - s2++; - } - - return (*s1 == '\0' && *s2 == '\0'); - } - /*! * The types of byte order of the running system. */ @@ -250,7 +219,7 @@ namespace TagLib }; /*! - * Returns the integer byte order of the system. + * Returns the byte order of the system. */ inline ByteOrder systemByteOrder() { @@ -265,26 +234,6 @@ namespace TagLib else return BigEndian; } - - /*! - * Returns the IEEE754 byte order of the system. - */ - inline ByteOrder floatByteOrder() - { - union { - double d; - char c; - } u; - - // 1.0 is stored in memory like 0x3FF0000000000000 in canonical form. - // So the first byte is zero if little endian. - - u.d = 1.0; - if(u.c == 0) - return LittleEndian; - else - return BigEndian; - } } } } diff --git a/3rdparty/taglib/toolkit/tzlib.cpp b/3rdparty/taglib/toolkit/tzlib.cpp index 40158fd2e..6d07ba3d2 100644 --- a/3rdparty/taglib/toolkit/tzlib.cpp +++ b/3rdparty/taglib/toolkit/tzlib.cpp @@ -27,23 +27,19 @@ # include #endif -#if defined(HAVE_ZLIB) +#ifdef HAVE_ZLIB # include -#elif defined(HAVE_BOOST_ZLIB) -# include -# include +# include +# include #endif -#include -#include - #include "tzlib.h" using namespace TagLib; bool zlib::isAvailable() { -#if defined(HAVE_ZLIB) || defined(HAVE_BOOST_ZLIB) +#ifdef HAVE_ZLIB return true; @@ -56,7 +52,7 @@ bool zlib::isAvailable() ByteVector zlib::decompress(const ByteVector &data) { -#if defined(HAVE_ZLIB) +#ifdef HAVE_ZLIB z_stream stream = {}; @@ -102,38 +98,6 @@ ByteVector zlib::decompress(const ByteVector &data) return outData; -#elif defined(HAVE_BOOST_ZLIB) - - using namespace boost::iostreams; - - struct : public sink - { - ByteVector data; - - typedef char char_type; - typedef sink_tag category; - - std::streamsize write(char const* s, std::streamsize n) - { - const unsigned int originalSize = data.size(); - - data.resize(static_cast(originalSize + n)); - ::memcpy(data.data() + originalSize, s, static_cast(n)); - - return n; - } - } sink; - - try { - zlib_decompressor().write(sink, data.data(), data.size()); - } - catch(const zlib_error &) { - debug("zlib::decompress() - Error reading compressed stream."); - return ByteVector(); - } - - return sink.data; - #else return ByteVector(); diff --git a/3rdparty/taglib/toolkit/unicode.cpp b/3rdparty/taglib/toolkit/unicode.cpp deleted file mode 100644 index 1b26977e5..000000000 --- a/3rdparty/taglib/toolkit/unicode.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/******************************************************************************* - * * - * THIS FILE IS INCLUDED IN TAGLIB, BUT IS NOT COPYRIGHTED BY THE TAGLIB * - * AUTHORS, NOT PART OF THE TAGLIB API AND COULD GO AWAY AT ANY POINT IN TIME. * - * AS SUCH IT SHOULD BE CONSIERED FOR INTERNAL USE ONLY. * - * * - *******************************************************************************/ - -/* - * Copyright 2001 Unicode, Inc. - * - * Disclaimer - * - * This source code is provided as is by Unicode, Inc. No claims are - * made as to fitness for any particular purpose. No warranties of any - * kind are expressed or implied. The recipient agrees to determine - * applicability of information provided. If this file has been - * purchased on magnetic or optical media from Unicode, Inc., the - * sole remedy for any claim will be exchange of defective media - * within 90 days of receipt. - * - * Limitations on Rights to Redistribute This Code - * - * Unicode, Inc. hereby grants the right to freely use the information - * supplied in this file in the creation of products supporting the - * Unicode Standard, and to make copies of this file in any form - * for internal or external distribution as long as this notice - * remains attached. - */ - -/* - * This file has been modified by Scott Wheeler to remove - * the UTF32 conversion functions and to place the appropriate functions - * in their own C++ namespace. - */ - -/* --------------------------------------------------------------------- - - Conversions between UTF32, UTF-16, and UTF-8. Source code file. - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Sept 2001: fixed const & error conditions per - mods suggested by S. Parent & A. Lillich. - - See the header file "ConvertUTF.h" for complete documentation. - ------------------------------------------------------------------------- */ - - -#include "unicode.h" -#include - -#define UNI_SUR_HIGH_START (UTF32)0xD800 -#define UNI_SUR_HIGH_END (UTF32)0xDBFF -#define UNI_SUR_LOW_START (UTF32)0xDC00 -#define UNI_SUR_LOW_END (UTF32)0xDFFF -#define false 0 -#define true 1 - -namespace Unicode { - -static const int halfShift = 10; /* used for shifting by 10 bits */ - -static const UTF32 halfBase = 0x0010000UL; -static const UTF32 halfMask = 0x3FFUL; - -/* - * Index into the table below with the first byte of a UTF-8 sequence to - * get the number of trailing bytes that are supposed to follow it. - */ -static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 -}; - -/* - * Magic values subtracted from a buffer value during UTF8 conversion. - * This table contains as many values as there might be trailing bytes - * in a UTF-8 sequence. - */ -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; - -/* - * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed - * into the first byte, depending on how many bytes follow. There are - * as many entries in this table as there are UTF-8 sequence types. - * (I.e., one byte sequence, two byte... six byte sequence.) - */ -static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - -/* --------------------------------------------------------------------- */ - -/* The interface converts a whole buffer to avoid function-call overhead. - * Constants have been gathered. Loops & conditionals have been removed as - * much as possible for efficiency, in favor of drop-through switches. - * (See "Note A" at the bottom of the file for equivalent code.) - * If your compiler supports it, the "isLegalUTF8" call can be turned - * into an inline function. - */ - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END && source < sourceEnd) { - UTF32 ch2 = *source; - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else if ((flags == strictConversion) && (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END)) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - /* Figure out how many bytes the result will require */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch < (UTF32)0x200000) { bytesToWrite = 4; - } else { bytesToWrite = 2; - ch = UNI_REPLACEMENT_CHAR; - } - // printf("bytes to write = %i\n", bytesToWrite); - target += bytesToWrite; - if (target > targetEnd) { - source = oldSource; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (ch | byteMark) & byteMask; ch >>= 6; - case 3: *--target = (ch | byteMark) & byteMask; ch >>= 6; - case 2: *--target = (ch | byteMark) & byteMask; ch >>= 6; - case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); - } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- */ - -/* - * Utility routine to tell whether a sequence of bytes is legal UTF-8. - * This must be called with the length pre-determined by the first byte. - * If not calling this from ConvertUTF8to*, then the length can be set by: - * length = trailingBytesForUTF8[*source]+1; - * and the sequence is illegal right away if there aren't that many bytes - * available. - * If presented with a length > 4, this returns false. The Unicode - * definition of UTF-8 goes up to 4-byte sequences. - */ - -static Boolean isLegalUTF8(const UTF8 *source, int length) { - UTF8 a; - const UTF8 *srcptr = source+length; - switch (length) { - default: return false; - /* Everything else falls through when "true"... */ - case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 2: if ((a = (*--srcptr)) > 0xBF) return false; - switch (*source) { - /* no fall-through in this inner switch */ - case 0xE0: if (a < 0xA0) return false; break; - case 0xF0: if (a < 0x90) return false; break; - case 0xF4: if (a > 0x8F) return false; break; - default: if (a < 0x80) return false; - } - case 1: if (*source >= 0x80 && *source < 0xC2) return false; - if (*source > 0xF4) return false; - } - return true; -} - -/* --------------------------------------------------------------------- */ - -/* - * Exported function to return whether a UTF-8 sequence is legal or not. - * This is not used here; it's just exported. - */ -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { - int length = trailingBytesForUTF8[*source]+1; - if (source+length > sourceEnd) { - return false; - } - return isLegalUTF8(source, length); -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - if ((flags == strictConversion) && (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END)) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_UTF16) { - if (flags == strictConversion) { - result = sourceIllegal; - source -= (extraBytesToRead+1); /* return to the start */ - break; /* Bail out; shouldn't continue */ - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (ch & halfMask) + UNI_SUR_LOW_START; - } - } - *sourceStart = source; - *targetStart = target; - return result; -} - -} - -/* --------------------------------------------------------------------- - - Note A. - The fall-through switches in UTF-8 reading code save a - temp variable, some decrements & conditionals. The switches - are equivalent to the following loop: - { - int tmpBytesToRead = extraBytesToRead+1; - do { - ch += *source++; - --tmpBytesToRead; - if (tmpBytesToRead) ch <<= 6; - } while (tmpBytesToRead > 0); - } - In UTF-8 writing code, the switches on "bytesToWrite" are - similarly unrolled loops. - - --------------------------------------------------------------------- */ - - diff --git a/3rdparty/taglib/toolkit/unicode.h b/3rdparty/taglib/toolkit/unicode.h deleted file mode 100644 index d3a869f04..000000000 --- a/3rdparty/taglib/toolkit/unicode.h +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef TAGLIB_UNICODE_H -#define TAGLIB_UNICODE_H - -/******************************************************************************* - * * - * THIS FILE IS INCLUDED IN TAGLIB, BUT IS NOT COPYRIGHTED BY THE TAGLIB * - * AUTHORS, NOT PART OF THE TAGLIB API AND COULD GO AWAY AT ANY POINT IN TIME. * - * AS SUCH IT SHOULD BE CONSIERED FOR INTERNAL USE ONLY. * - * * - *******************************************************************************/ - -#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header - -/* - * Copyright 2001 Unicode, Inc. - * - * Disclaimer - * - * This source code is provided as is by Unicode, Inc. No claims are - * made as to fitness for any particular purpose. No warranties of any - * kind are expressed or implied. The recipient agrees to determine - * applicability of information provided. If this file has been - * purchased on magnetic or optical media from Unicode, Inc., the - * sole remedy for any claim will be exchange of defective media - * within 90 days of receipt. - * - * Limitations on Rights to Redistribute This Code - * - * Unicode, Inc. hereby grants the right to freely use the information - * supplied in this file in the creation of products supporting the - * Unicode Standard, and to make copies of this file in any form - * for internal or external distribution as long as this notice - * remains attached. - */ - -/* - * This file has been modified by Scott Wheeler to remove - * the UTF32 conversion functions and to place the appropriate functions - * in their own C++ namespace. - */ - -/* --------------------------------------------------------------------- - - Conversions between UTF32, UTF-16, and UTF-8. Header file. - - Several functions are included here, forming a complete set of - conversions between the three formats. UTF-7 is not included - here, but is handled in a separate source file. - - Each of these routines takes pointers to input buffers and output - buffers. The input buffers are const. - - Each routine converts the text between *sourceStart and sourceEnd, - putting the result into the buffer between *targetStart and - targetEnd. Note: the end pointers are *after* the last item: e.g. - *(sourceEnd - 1) is the last item. - - The return result indicates whether the conversion was successful, - and if not, whether the problem was in the source or target buffers. - (Only the first encountered problem is indicated.) - - After the conversion, *sourceStart and *targetStart are both - updated to point to the end of last text successfully converted in - the respective buffers. - - Input parameters: - sourceStart - pointer to a pointer to the source buffer. - The contents of this are modified on return so that - it points at the next thing to be converted. - targetStart - similarly, pointer to pointer to the target buffer. - sourceEnd, targetEnd - respectively pointers to the ends of the - two buffers, for overflow checking only. - - These conversion functions take a ConversionFlags argument. When this - flag is set to strict, both irregular sequences and isolated surrogates - will cause an error. When the flag is set to lenient, both irregular - sequences and isolated surrogates are converted. - - Whether the flag is strict or lenient, all illegal sequences will cause - an error return. This includes sequences such as: , , - or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code - must check for illegal sequences. - - When the flag is set to lenient, characters over 0x10FFFF are converted - to the replacement character; otherwise (when the flag is set to strict) - they constitute an error. - - Output parameters: - The value "sourceIllegal" is returned from some routines if the input - sequence is malformed. When "sourceIllegal" is returned, the source - value will point to the illegal value that caused the problem. E.g., - in UTF-8 when a sequence is malformed, it points to the start of the - malformed sequence. - - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Fixes & updates, Sept 2001. - ------------------------------------------------------------------------- */ - -/* --------------------------------------------------------------------- - The following 4 definitions are compiler-specific. - The C standard does not guarantee that wchar_t has at least - 16 bits, so wchar_t is no less portable than unsigned short! - All should be unsigned values to avoid sign extension during - bit mask & shift operations. ------------------------------------------------------------------------- */ - -/* Some fundamental constants */ -#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD -#define UNI_MAX_BMP (UTF32)0x0000FFFF -#define UNI_MAX_UTF16 (UTF32)0x0010FFFF -#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF - -namespace Unicode { - -typedef unsigned long UTF32; /* at least 32 bits */ -typedef wchar_t UTF16; /* TagLib assumes that wchar_t is sufficient for UTF-16. */ -typedef unsigned char UTF8; /* typically 8 bits */ -typedef unsigned char Boolean; /* 0 or 1 */ - -typedef enum { - conversionOK = 0, /* conversion successful */ - sourceExhausted = 1, /* partial character in source, but hit end */ - targetExhausted = 2, /* insuff. room in target for conversion */ - sourceIllegal = 3 /* source sequence is illegal/malformed */ -} ConversionResult; - -typedef enum { - strictConversion = 0, - lenientConversion -} ConversionFlags; - -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); - -} // namespace Unicode - -/* --------------------------------------------------------------------- */ - -#endif -#endif diff --git a/3rdparty/taglib/trueaudio/trueaudiofile.cpp b/3rdparty/taglib/trueaudio/trueaudiofile.cpp index fc123ba34..e4de436ed 100644 --- a/3rdparty/taglib/trueaudio/trueaudiofile.cpp +++ b/3rdparty/taglib/trueaudio/trueaudiofile.cpp @@ -73,6 +73,18 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool TrueAudio::File::isSupported(IOStream *stream) +{ + // A TrueAudio file has to start with "TTA". An ID3v2 tag may precede. + + const ByteVector id = Utils::readHeader(stream, 3, true); + return (id == "TTA"); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/trueaudio/trueaudiofile.h b/3rdparty/taglib/trueaudio/trueaudiofile.h index 4bcb722af..3737ac63f 100644 --- a/3rdparty/taglib/trueaudio/trueaudiofile.h +++ b/3rdparty/taglib/trueaudio/trueaudiofile.h @@ -235,6 +235,15 @@ namespace TagLib { */ bool hasID3v2Tag() const; + /*! + * Returns whether or not the given \a stream can be opened as a TrueAudio + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/wavpack/wavpackfile.cpp b/3rdparty/taglib/wavpack/wavpackfile.cpp index ef92f4bdf..01bdba36c 100644 --- a/3rdparty/taglib/wavpack/wavpackfile.cpp +++ b/3rdparty/taglib/wavpack/wavpackfile.cpp @@ -71,6 +71,18 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool WavPack::File::isSupported(IOStream *stream) +{ + // A WavPack file has to start with "wvpk". + + const ByteVector id = Utils::readHeader(stream, 4, false); + return (id == "wvpk"); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// diff --git a/3rdparty/taglib/wavpack/wavpackfile.h b/3rdparty/taglib/wavpack/wavpackfile.h index 7e0bd27a9..ccc4ef6e8 100644 --- a/3rdparty/taglib/wavpack/wavpackfile.h +++ b/3rdparty/taglib/wavpack/wavpackfile.h @@ -200,6 +200,14 @@ namespace TagLib { */ bool hasAPETag() const; + /*! + * Check if the given \a stream can be opened as a WavPack file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/3rdparty/taglib/xm/xmfile.cpp b/3rdparty/taglib/xm/xmfile.cpp index 9192e9bf2..216dfd8cd 100644 --- a/3rdparty/taglib/xm/xmfile.cpp +++ b/3rdparty/taglib/xm/xmfile.cpp @@ -586,9 +586,9 @@ void XM::File::read(bool) unsigned int count = 4 + instrument.read(*this, instrumentHeaderSize - 4U); READ_ASSERT(count == std::min(instrumentHeaderSize, (unsigned long)instrument.size() + 4)); - unsigned long sampleHeaderSize = 0; long offset = 0; if(sampleCount > 0) { + unsigned long sampleHeaderSize = 0; sumSampleCount += sampleCount; // wouldn't know which header size to assume otherwise: READ_ASSERT(instrumentHeaderSize >= count + 4 && readU32L(sampleHeaderSize)); diff --git a/3rdparty/taglib/xm/xmproperties.cpp b/3rdparty/taglib/xm/xmproperties.cpp index 39a9fa0a0..93d849868 100644 --- a/3rdparty/taglib/xm/xmproperties.cpp +++ b/3rdparty/taglib/xm/xmproperties.cpp @@ -60,7 +60,7 @@ public: XM::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : AudioProperties(propertiesStyle), - d(new PropertiesPrivate) + d(new PropertiesPrivate()) { } diff --git a/3rdparty/utf8-cpp/CMakeLists.txt b/3rdparty/utf8-cpp/CMakeLists.txt new file mode 100644 index 000000000..e438780c8 --- /dev/null +++ b/3rdparty/utf8-cpp/CMakeLists.txt @@ -0,0 +1,2 @@ +cmake_minimum_required(VERSION 2.8.11) +set(CMAKE_CXX_STANDARD 11) diff --git a/3rdparty/utf8-cpp/checked.h b/3rdparty/utf8-cpp/checked.h new file mode 100644 index 000000000..2aef5838d --- /dev/null +++ b/3rdparty/utf8-cpp/checked.h @@ -0,0 +1,327 @@ +// Copyright 2006-2016 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" +#include + +namespace utf8 +{ + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + uint32_t cp; + public: + invalid_code_point(uint32_t codepoint) : cp(codepoint) {} + virtual const char* what() const throw() { return "Invalid code point"; } + uint32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + uint8_t u8; + public: + invalid_utf8 (uint8_t u) : u8(u) {} + virtual const char* what() const throw() { return "Invalid UTF-8"; } + uint8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + uint16_t u16; + public: + invalid_utf16 (uint16_t u) : u16(u) {} + virtual const char* what() const throw() { return "Invalid UTF-16"; } + uint16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const throw() { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + throw not_enough_room(); + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it, octet_iterator end) + { + uint32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(*it); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + uint32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + uint32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + /// Deprecated in versions that include "prior" + template + uint32_t previous(octet_iterator& it, octet_iterator pass_start) + { + octet_iterator end = it; + while (utf8::internal::is_trail(*(--it))) + if (it == pass_start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + octet_iterator temp = it; + return utf8::next(temp, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + for (distance_type i = 0; i < n; ++i) + utf8::next(it, end); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& rangestart, + const octet_iterator& rangeend) : + it(octet_it), range_start(rangestart), range_end(rangeend) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#endif //header guard + + diff --git a/3rdparty/utf8-cpp/core.h b/3rdparty/utf8-cpp/core.h new file mode 100644 index 000000000..ae0f367db --- /dev/null +++ b/3rdparty/utf8-cpp/core.h @@ -0,0 +1,332 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include + +namespace utf8 +{ + // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers + // You may need to change them to match your system. + // These typedefs have the same names as ones from cstdint, or boost/cstdint + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + +// Helper code - not intended to be directly called by the library users. May be changed at any time +namespace internal +{ + // Unicode constants + // Leading (high) surrogates: 0xd800 - 0xdbff + // Trailing (low) surrogates: 0xdc00 - 0xdfff + const uint16_t LEAD_SURROGATE_MIN = 0xd800u; + const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; + const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; + const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; + const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); + const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; + + // Maximum valid value for a Unicode code point + const uint32_t CODE_POINT_MAX = 0x0010ffffu; + + template + inline uint8_t mask8(octet_type oc) + { + return static_cast(0xff & oc); + } + template + inline uint16_t mask16(u16_type oc) + { + return static_cast(0xffff & oc); + } + template + inline bool is_trail(octet_type oc) + { + return ((utf8::internal::mask8(oc) >> 6) == 0x2); + } + + template + inline bool is_lead_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + } + + template + inline bool is_trail_surrogate(u16 cp) + { + return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_code_point_valid(u32 cp) + { + return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); + } + + template + inline typename std::iterator_traits::difference_type + sequence_length(octet_iterator lead_it) + { + uint8_t lead = utf8::internal::mask8(*lead_it); + if (lead < 0x80) + return 1; + else if ((lead >> 5) == 0x6) + return 2; + else if ((lead >> 4) == 0xe) + return 3; + else if ((lead >> 3) == 0x1e) + return 4; + else + return 0; + } + + template + inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) + { + if (cp < 0x80) { + if (length != 1) + return true; + } + else if (cp < 0x800) { + if (length != 2) + return true; + } + else if (cp < 0x10000) { + if (length != 3) + return true; + } + + return false; + } + + enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; + + /// Helper for get_sequence_x + template + utf_error increase_safely(octet_iterator& it, octet_iterator end) + { + if (++it == end) + return NOT_ENOUGH_ROOM; + + if (!utf8::internal::is_trail(*it)) + return INCOMPLETE_SEQUENCE; + + return UTF8_OK; + } + + #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} + + /// get_sequence_x functions decode utf-8 sequences of the length x + template + utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + return UTF8_OK; + } + + template + utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); + + return UTF8_OK; + } + + template + utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + template + utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR + + template + utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + // Save the original value of it so we can go back in case of failure + // Of course, it does not make much sense with i.e. stream iterators + octet_iterator original_it = it; + + uint32_t cp = 0; + // Determine the sequence length based on the lead octet + typedef typename std::iterator_traits::difference_type octet_difference_type; + const octet_difference_type length = utf8::internal::sequence_length(it); + + // Get trail octets and calculate the code point + utf_error err = UTF8_OK; + switch (length) { + case 0: + return INVALID_LEAD; + case 1: + err = utf8::internal::get_sequence_1(it, end, cp); + break; + case 2: + err = utf8::internal::get_sequence_2(it, end, cp); + break; + case 3: + err = utf8::internal::get_sequence_3(it, end, cp); + break; + case 4: + err = utf8::internal::get_sequence_4(it, end, cp); + break; + } + + if (err == UTF8_OK) { + // Decoding succeeded. Now, security checks... + if (utf8::internal::is_code_point_valid(cp)) { + if (!utf8::internal::is_overlong_sequence(cp, length)){ + // Passed! Return here. + code_point = cp; + ++it; + return UTF8_OK; + } + else + err = OVERLONG_SEQUENCE; + } + else + err = INVALID_CODE_POINT; + } + + // Failure branch - restore the original value of the iterator + it = original_it; + return err; + } + + template + inline utf_error validate_next(octet_iterator& it, octet_iterator end) { + uint32_t ignored; + return utf8::internal::validate_next(it, end, ignored); + } + +} // namespace internal + + /// The library API - functions intended to be called by the users + + // Byte order mark + const uint8_t bom[] = {0xef, 0xbb, 0xbf}; + + template + octet_iterator find_invalid(octet_iterator start, octet_iterator end) + { + octet_iterator result = start; + while (result != end) { + utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); + if (err_code != internal::UTF8_OK) + return result; + } + return result; + } + + template + inline bool is_valid(octet_iterator start, octet_iterator end) + { + return (utf8::find_invalid(start, end) == end); + } + + template + inline bool starts_with_bom (octet_iterator it, octet_iterator end) + { + return ( + ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && + ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && + ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) + ); + } + + //Deprecated in release 2.3 + template + inline bool is_bom (octet_iterator it) + { + return ( + (utf8::internal::mask8(*it++)) == bom[0] && + (utf8::internal::mask8(*it++)) == bom[1] && + (utf8::internal::mask8(*it)) == bom[2] + ); + } +} // namespace utf8 + +#endif // header guard + + diff --git a/CMakeLists.txt b/CMakeLists.txt index bdbaab9a4..ec71934b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,11 +96,12 @@ find_path(SPARSEHASH_INCLUDE_DIRS google/sparsetable) option(USE_BUILTIN_TAGLIB "If the system's version of Taglib is too old, compile our builtin version instead" ON) if (USE_BUILTIN_TAGLIB AND TAGLIB_VERSION VERSION_LESS 1.8) message(STATUS "Using builtin taglib because your system's version is too old") - set(TAGLIB_VERSION 1.11.0) + set(TAGLIB_VERSION 1.11.1) set(TAGLIB_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/taglib/;${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/") set(TAGLIB_LIBRARY_DIRS "") set(TAGLIB_LIBRARIES tag) set(TAGLIB_HAS_OPUS ON) + add_subdirectory(3rdparty/utf8-cpp) add_subdirectory(3rdparty/taglib) else() set(CMAKE_REQUIRED_INCLUDES "${TAGLIB_INCLUDE_DIRS}") From e0d2d9b4246fcf9cac5b39d824708f181890e7c7 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Wed, 6 Jun 2018 22:54:48 +0200 Subject: [PATCH 04/16] Use taglib to check for valid audio file --- src/core/song.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/core/song.cpp b/src/core/song.cpp index 32b645dff..00e223161 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -51,6 +51,7 @@ #endif #endif +#include #include #ifdef HAVE_LIBGPOD @@ -666,22 +667,17 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) { void Song::InitFromFilePartial(const QString& filename) { set_url(QUrl::fromLocalFile(filename)); - // We currently rely on filename suffix to know if it's a music file or not. - // TODO(Arnaud Bienner): I know this is not satisfying, but currently, - // we rely on TagLib which seems to have the behavior (filename checks). - // Someday, it would be nice to perform some magic tests everywhere. QFileInfo info(filename); d->basefilename_ = info.fileName(); QString suffix = info.suffix().toLower(); - if (suffix == "mp3" || suffix == "ogg" || suffix == "flac" || - suffix == "mpc" || suffix == "m4a" || suffix == "aac" || - suffix == "wma" || suffix == "mp4" || suffix == "spx" || - suffix == "wav" || suffix == "opus" || suffix == "m4b" || - suffix == "wv") { - d->valid_ = true; - } else { + + TagLib::FileRef fileref(filename.toUtf8().constData()); + if (fileref.file()) d->valid_ = true; + else { d->valid_ = false; + qLog(Error) << "File" << filename << "is not recognized by TagLib as a valid audio file."; } + } void Song::InitArtManual() { From 03261f5b8de24b55c199d11248d0fa72a61740f0 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Wed, 6 Jun 2018 22:59:21 +0200 Subject: [PATCH 05/16] Use bultin taglib as default Only use systems taglib if it's newer than the current release because of audio file detection by content. --- CMakeLists.txt | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec71934b8..1b8a0f58e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ pkg_check_modules(LIBPULSE libpulse) pkg_check_modules(LIBXML libxml-2.0) pkg_check_modules(QJSON REQUIRED QJson) pkg_check_modules(SPOTIFY libspotify>=12.1.45) -pkg_check_modules(TAGLIB REQUIRED taglib>=1.6) +pkg_check_modules(TAGLIB taglib) if (WIN32) find_package(ZLIB REQUIRED) @@ -89,12 +89,22 @@ find_path(LASTFM1_INCLUDE_DIRS lastfm/Track.h) find_path(SPARSEHASH_INCLUDE_DIRS google/sparsetable) -# Google Drive support needs Taglib 1.8, but this version isn't in old Ubuntu -# distros. If the user seems to want Drive support (ie. they have sparsehash -# installed and haven't disabled drive), and has an old taglib, compile our -# internal one and use that instead. -option(USE_BUILTIN_TAGLIB "If the system's version of Taglib is too old, compile our builtin version instead" ON) -if (USE_BUILTIN_TAGLIB AND TAGLIB_VERSION VERSION_LESS 1.8) +# Only use system taglib if it's greater than 1.11.1 because of audio file detection by content. +if (TAGLIB_VERSION VERSION_GREATER 1.11.1 OR WIN32) + option(USE_SYSTEM_TAGLIB "Use system taglib" ON) +else() + option(USE_SYSTEM_TAGLIB "Use system taglib" OFF) +endif() + +if (TAGLIB_FOUND AND USE_SYSTEM_TAGLIB) + message(STATUS "Using system taglib library") + set(CMAKE_REQUIRED_INCLUDES "${TAGLIB_INCLUDE_DIRS}") + set(CMAKE_REQUIRED_LIBRARIES "${TAGLIB_LIBRARIES}") + check_cxx_source_compiles("#include + int main() { char *s; TagLib::Ogg::Opus::File opusfile(s); return 0;}" TAGLIB_HAS_OPUS) + set(CMAKE_REQUIRED_INCLUDES) + set(CMAKE_REQUIRED_LIBRARIES) +else() message(STATUS "Using builtin taglib because your system's version is too old") set(TAGLIB_VERSION 1.11.1) set(TAGLIB_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/taglib/;${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/") @@ -103,13 +113,6 @@ if (USE_BUILTIN_TAGLIB AND TAGLIB_VERSION VERSION_LESS 1.8) set(TAGLIB_HAS_OPUS ON) add_subdirectory(3rdparty/utf8-cpp) add_subdirectory(3rdparty/taglib) -else() - set(CMAKE_REQUIRED_INCLUDES "${TAGLIB_INCLUDE_DIRS}") - set(CMAKE_REQUIRED_LIBRARIES "${TAGLIB_LIBRARIES}") - check_cxx_source_compiles("#include - int main() { char *s; TagLib::Ogg::Opus::File opusfile(s); return 0;}" TAGLIB_HAS_OPUS) - set(CMAKE_REQUIRED_INCLUDES) - set(CMAKE_REQUIRED_LIBRARIES) endif() if(LASTFM_INCLUDE_DIRS AND LASTFM1_INCLUDE_DIRS) From 925e74f9095742bbcf1d21c7bb3391fcad4fae49 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Thu, 7 Jun 2018 00:53:25 +0200 Subject: [PATCH 06/16] Fix crash when uridecodebin fails. (#6077) Let's the user see the error message what failed instead of Clementine crashing. Also don't do gst_object_unref unless bin is set. This fixes GStreamer-CRITICAL gst_object_unref: assertion 'object != NULL' failed --- src/engines/gstengine.cpp | 2 +- src/engines/gstenginepipeline.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engines/gstengine.cpp b/src/engines/gstengine.cpp index 42e25864f..86b712fc5 100644 --- a/src/engines/gstengine.cpp +++ b/src/engines/gstengine.cpp @@ -759,7 +759,7 @@ GstElement* GstEngine::CreateElement(const QString& factoryName, "GStreamer could not create the element: %1. " "Please make sure that you have installed all necessary " "GStreamer plugins (e.g. OGG and MP3)").arg(factoryName)); - gst_object_unref(GST_OBJECT(bin)); + if (bin) gst_object_unref(GST_OBJECT(bin)); return nullptr; } diff --git a/src/engines/gstenginepipeline.cpp b/src/engines/gstenginepipeline.cpp index c352e044e..4416e78b8 100644 --- a/src/engines/gstenginepipeline.cpp +++ b/src/engines/gstenginepipeline.cpp @@ -157,8 +157,9 @@ bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) { // Create elements GstElement* src = engine_->CreateElement("tcpserversrc", new_bin); + if (!src) return false; GstElement* gdp = engine_->CreateElement("gdpdepay", new_bin); - if (!src || !gdp) return false; + if (!gdp) return false; // Pick a port number const int port = Utilities::PickUnusedPort(); @@ -182,6 +183,7 @@ bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) { Q_ARG(QString, url.toString()), Q_ARG(quint16, port)); } else { new_bin = engine_->CreateElement("uridecodebin"); + if (!new_bin) return false; g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(), nullptr); CHECKED_GCONNECT(G_OBJECT(new_bin), "drained", &SourceDrainedCallback, From da648fd08f62b870aed614810244115213cd498f Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Tue, 12 Jun 2018 10:27:10 +0200 Subject: [PATCH 07/16] Fix setting device on windows (#6081) --- src/engines/gstenginepipeline.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/engines/gstenginepipeline.cpp b/src/engines/gstenginepipeline.cpp index 4416e78b8..f5763eb95 100644 --- a/src/engines/gstenginepipeline.cpp +++ b/src/engines/gstenginepipeline.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include "bufferconsumer.h" #include "config.h" @@ -253,14 +252,11 @@ bool GstEnginePipeline::Init() { g_object_set(G_OBJECT(audiosink_), "device", device_.toString().toUtf8().constData(), nullptr); break; - -#ifdef Q_OS_WIN32 case QVariant::ByteArray: { - GUID guid = QUuid(device_.toByteArray()); - g_object_set(G_OBJECT(audiosink_), "device", &guid, nullptr); + g_object_set(G_OBJECT(audiosink_), "device", + device_.toByteArray().constData(), nullptr); break; } -#endif // Q_OS_WIN32 default: qLog(Warning) << "Unknown device type" << device_; From 41ed505277680ae930f3ee2c9c8d6408e9552cd7 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Wed, 13 Jun 2018 10:38:56 +0200 Subject: [PATCH 08/16] Fix git revision string (#6083) --- cmake/Version.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/Version.cmake b/cmake/Version.cmake index 194c6e9de..5a3750ca3 100644 --- a/cmake/Version.cmake +++ b/cmake/Version.cmake @@ -127,6 +127,7 @@ else(FORCE_GIT_REVISION) if(NOT GIT_EXECUTABLE-NOTFOUND) execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} RESULT_VARIABLE GIT_INFO_RESULT OUTPUT_VARIABLE GIT_REV ERROR_QUIET From f3aab34d3ab3fc0d6444067e60d71e9926a1717c Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Wed, 13 Jun 2018 10:56:42 +0200 Subject: [PATCH 09/16] Add ALSA Device Finder (#6079) --- CMakeLists.txt | 4 ++ src/CMakeLists.txt | 10 +++ src/config.h.in | 1 + src/engines/alsadevicefinder.cpp | 107 +++++++++++++++++++++++++++++++ src/engines/alsadevicefinder.h | 33 ++++++++++ src/engines/gstengine.cpp | 22 ++++++- 6 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 src/engines/alsadevicefinder.cpp create mode 100644 src/engines/alsadevicefinder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b8a0f58e..9804eed9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,10 @@ find_package(Gettext REQUIRED) find_package(PkgConfig REQUIRED) find_package(Protobuf REQUIRED) find_package(FFTW3) +find_package(ALSA) +if(ALSA_FOUND) + set(HAVE_ALSA ON) +endif() find_library(PROTOBUF_STATIC_LIBRARY libprotobuf.a libprotobuf) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f0a8a569b..8f258af17 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1187,6 +1187,12 @@ optional_source(HAVE_LIBPULSE engines/pulsedevicefinder.cpp ) +# ALSA integration +optional_source(HAVE_ALSA + SOURCES + engines/alsadevicefinder.cpp +) + # Hack to add Clementine to the Unity system tray whitelist optional_source(LINUX SOURCES core/ubuntuunityhack.cpp @@ -1305,6 +1311,10 @@ if(HAVE_LIBPULSE) target_link_libraries(clementine_lib ${LIBPULSE_LIBRARIES}) endif() +if(HAVE_ALSA) + target_link_libraries(clementine_lib ${ALSA_LIBRARIES}) +endif(HAVE_ALSA) + if (APPLE) target_link_libraries(clementine_lib "-framework AppKit" diff --git a/src/config.h.in b/src/config.h.in index 831d74a28..4475e28dc 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -36,6 +36,7 @@ #cmakedefine HAVE_LIBLASTFM1 #cmakedefine HAVE_LIBMTP #cmakedefine HAVE_LIBPULSE +#cmakedefine HAVE_ALSA #cmakedefine HAVE_MOODBAR #cmakedefine HAVE_SEAFILE #cmakedefine HAVE_SKYDRIVE diff --git a/src/engines/alsadevicefinder.cpp b/src/engines/alsadevicefinder.cpp new file mode 100644 index 000000000..929676d8f --- /dev/null +++ b/src/engines/alsadevicefinder.cpp @@ -0,0 +1,107 @@ +/* This file is part of Clementine. + Copyright 2017, Jonas Kvinge + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "alsadevicefinder.h" +#include "devicefinder.h" + +AlsaDeviceFinder::AlsaDeviceFinder() : DeviceFinder("alsasink") {} + +QList AlsaDeviceFinder::ListDevices() { + QList ret; + + snd_pcm_stream_name(SND_PCM_STREAM_PLAYBACK); + + int card = -1; + snd_ctl_card_info_t* cardinfo; + snd_ctl_card_info_alloca(&cardinfo); + while (true) { + int result = snd_card_next(&card); + if (result < 0) { + qLog(Error) << "Unable to get soundcard:" << snd_strerror(result); + break; + } + if (card < 0) break; + + char str[32]; + snprintf(str, sizeof(str) - 1, "hw:%d", card); + + snd_ctl_t* handle; + result = snd_ctl_open(&handle, str, 0); + if (result < 0) { + qLog(Error) << "Unable to open soundcard" << card << ":" + << snd_strerror(result); + continue; + } + BOOST_SCOPE_EXIT(&handle) { snd_ctl_close(handle); } + BOOST_SCOPE_EXIT_END + + result = snd_ctl_card_info(handle, cardinfo); + if (result < 0) { + qLog(Error) << "Control hardware failure for card" << card << ":" + << snd_strerror(result); + continue; + } + + int dev = -1; + snd_pcm_info_t* pcminfo; + snd_pcm_info_alloca(&pcminfo); + while (true) { + result = snd_ctl_pcm_next_device(handle, &dev); + if (result < 0) { + qLog(Error) << "Unable to get PCM for card" << card << ":" + << snd_strerror(result); + continue; + } + if (dev < 0) break; + + snd_pcm_info_set_device(pcminfo, dev); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); + + result = snd_ctl_pcm_info(handle, pcminfo); + if (result < 0) { + if (result != -ENOENT) + qLog(Error) << "Unable to get control digital audio info for card" + << card << ":" << snd_strerror(result); + continue; + } + + Device device; + device.description = QString("%1 %2") + .arg(snd_ctl_card_info_get_name(cardinfo)) + .arg(snd_pcm_info_get_name(pcminfo)); + device.device_property_value = QString("hw:%1,%2").arg(card).arg(dev); + device.icon_name = GuessIconName(device.description); + ret.append(device); + } + } + + snd_config_update_free_global(); + + return ret; +} diff --git a/src/engines/alsadevicefinder.h b/src/engines/alsadevicefinder.h new file mode 100644 index 000000000..3148ade31 --- /dev/null +++ b/src/engines/alsadevicefinder.h @@ -0,0 +1,33 @@ +/* This file is part of Clementine. + Copyright 2017, Jonas Kvinge + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#ifndef ALSADEVICEFINDER_H +#define ALSADEVICEFINDER_H + +#include + +#include "devicefinder.h" + +class AlsaDeviceFinder : public DeviceFinder { + public: + AlsaDeviceFinder(); + + virtual bool Initialise() { return true; } + virtual QList ListDevices(); +}; + +#endif // ALSADEVICEFINDER_H diff --git a/src/engines/gstengine.cpp b/src/engines/gstengine.cpp index 86b712fc5..222e95943 100644 --- a/src/engines/gstengine.cpp +++ b/src/engines/gstengine.cpp @@ -58,6 +58,10 @@ #include "engines/pulsedevicefinder.h" #endif +#ifdef HAVE_ALSA +#include "engines/alsadevicefinder.h" +#endif + #ifdef Q_OS_DARWIN #include "engines/osxdevicefinder.h" #endif @@ -76,6 +80,7 @@ #endif using std::shared_ptr; +using std::unique_ptr; using std::vector; const char* GstEngine::kSettingsGroup = "GstEngine"; @@ -158,10 +163,23 @@ void GstEngine::InitialiseGstreamer() { plugin_names.insert(plugin.name); } - QList device_finders; + bool pa(false); #ifdef HAVE_LIBPULSE - device_finders.append(new PulseDeviceFinder); + unique_ptr finder_pulse(new PulseDeviceFinder); + if (plugin_names.contains(finder_pulse->gstreamer_sink()) && + finder_pulse->Initialise()) { + pa = true; + device_finders_.append(finder_pulse.release()); + } #endif + + QList device_finders; + if (!pa) { // Only add alsa devices if pulseaudio is not enabled to avoid + // confusion. +#ifdef HAVE_ALSA + device_finders.append(new AlsaDeviceFinder); +#endif + } #ifdef Q_OS_DARWIN device_finders.append(new OsxDeviceFinder); #endif From 465fa2ce8711243f32ded24c4e0744034ebfae91 Mon Sep 17 00:00:00 2001 From: vaterlangen Date: Wed, 13 Jun 2018 22:03:23 +0200 Subject: [PATCH 10/16] Clicking on songs in playlists via remote control now respecting behavioural settings from GUI (either direct changes it or enques it) --- src/networkremote/incomingdataparser.cpp | 32 ++++++++++++++++++++++-- src/networkremote/incomingdataparser.h | 4 +++ src/playlist/playlistmanager.cpp | 12 ++++++++- src/playlist/playlistmanager.h | 4 +++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/networkremote/incomingdataparser.cpp b/src/networkremote/incomingdataparser.cpp index fbaa84d62..8a0c8a1ec 100644 --- a/src/networkremote/incomingdataparser.cpp +++ b/src/networkremote/incomingdataparser.cpp @@ -32,6 +32,11 @@ #endif IncomingDataParser::IncomingDataParser(Application* app) : app_(app) { + // load settings initaily and sign up for updates + ReloadSettings(); + connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings())); + + // Connect all the signals // due the player is in a different thread, we cannot access these functions // directly @@ -47,6 +52,7 @@ IncomingDataParser::IncomingDataParser(Application* app) : app_(app) { connect(this, SIGNAL(PlayAt(int, Engine::TrackChangeFlags, bool)), app_->player(), SLOT(PlayAt(int, Engine::TrackChangeFlags, bool))); connect(this, SIGNAL(SeekTo(int)), app_->player(), SLOT(SeekTo(int))); + connect(this, SIGNAL(Enque(int,int)), app_->playlist_manager(), SLOT(Enque(int,int))); connect(this, SIGNAL(SetActivePlaylist(int)), app_->playlist_manager(), SLOT(SetActivePlaylist(int))); @@ -80,6 +86,12 @@ IncomingDataParser::IncomingDataParser(Application* app) : app_(app) { IncomingDataParser::~IncomingDataParser() {} +void IncomingDataParser::ReloadSettings() { + QSettings s; + s.beginGroup(MainWindow::kSettingsGroup); + doubleclick_playlist_addmode_ = MainWindow::PlaylistAddBehaviour(s.value("doubleclick_playlist_addmode", MainWindow::PlaylistAddBehaviour_Enqueue).toInt()); +} + bool IncomingDataParser::close_connection() { return close_connection_; } void IncomingDataParser::Parse(const pb::remote::Message& msg) { @@ -194,8 +206,24 @@ void IncomingDataParser::ChangeSong(const pb::remote::Message& msg) { emit SetActivePlaylist(request.playlist_id()); } - // Play the selected song - emit PlayAt(request.song_index(), Engine::Manual, false); + switch (doubleclick_playlist_addmode_) + { + // Play the selected song + case MainWindow::PlaylistAddBehaviour_Play: + emit PlayAt(request.song_index(), Engine::Manual, false); + break; + + // Enque the selected song + case MainWindow::PlaylistAddBehaviour_Enqueue: + emit Enque(request.playlist_id(), request.song_index()); + if (app_->player()->GetState() != Engine::Playing) + { + emit PlayAt(request.song_index(), Engine::Manual, false); + } + + break; + } + } void IncomingDataParser::SetRepeatMode(const pb::remote::Repeat& repeat) { diff --git a/src/networkremote/incomingdataparser.h b/src/networkremote/incomingdataparser.h index 2f2bb0f7c..ba8cd4db1 100644 --- a/src/networkremote/incomingdataparser.h +++ b/src/networkremote/incomingdataparser.h @@ -5,6 +5,7 @@ #include "core/application.h" #include "remotecontrolmessages.pb.h" #include "remoteclient.h" +#include "ui/mainwindow.h" class IncomingDataParser : public QObject { Q_OBJECT @@ -16,6 +17,7 @@ class IncomingDataParser : public QObject { public slots: void Parse(const pb::remote::Message& msg); + void ReloadSettings(); signals: void SendClementineInfo(); @@ -38,6 +40,7 @@ signals: void Previous(); void SetVolume(int volume); void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle); + void Enque(int id, int i); void SetActivePlaylist(int id); void ShuffleCurrent(); void SetRepeatMode(PlaylistSequence::RepeatMode mode); @@ -56,6 +59,7 @@ signals: private: Application* app_; bool close_connection_; + MainWindow::PlaylistAddBehaviour doubleclick_playlist_addmode_; void GetPlaylistSongs(const pb::remote::Message& msg); void ChangeSong(const pb::remote::Message& msg); diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index efd3917d5..a447f19fa 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -29,6 +29,7 @@ #include "library/libraryplaylistitem.h" #include "playlistparsers/playlistparser.h" #include "smartplaylists/generator.h" +#include "queue.h" #include #include @@ -63,7 +64,7 @@ PlaylistManager::~PlaylistManager() { void PlaylistManager::Init(LibraryBackend* library_backend, PlaylistBackend* playlist_backend, PlaylistSequence* sequence, - PlaylistContainer* playlist_container) { + PlaylistContainer* playlist_container) { library_backend_ = library_backend; playlist_backend_ = playlist_backend; sequence_ = sequence; @@ -397,6 +398,15 @@ void PlaylistManager::ChangePlaylistOrder(const QList& ids) { playlist_backend_->SetPlaylistOrder(ids); } +void PlaylistManager::Enque(int id, int i) { + QModelIndexList dummyIndexList; + + Q_ASSERT(playlists_.contains(id)); + + dummyIndexList.append(playlist(id)->index(i, 0)); + playlist(id)->queue()->ToggleTracks(dummyIndexList); +} + void PlaylistManager::UpdateSummaryText() { int tracks = current()->rowCount(); quint64 nanoseconds = 0; diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h index 8c8f66d10..e674393d1 100644 --- a/src/playlist/playlistmanager.h +++ b/src/playlist/playlistmanager.h @@ -84,6 +84,8 @@ class PlaylistManagerInterface : public QObject { virtual void Open(int id) = 0; virtual void ChangePlaylistOrder(const QList& ids) = 0; + virtual void Enque(int id, int index) = 0; + virtual void SongChangeRequestProcessed(const QUrl& url, bool valid) = 0; virtual void SetCurrentPlaylist(int id) = 0; @@ -192,6 +194,8 @@ class PlaylistManager : public PlaylistManagerInterface { void Open(int id); void ChangePlaylistOrder(const QList& ids); + void Enque(int id, int index); + void SetCurrentPlaylist(int id); void SetActivePlaylist(int id); void SetActiveToCurrent(); From 083129f41c2e5acc710c9ceccacc7baaef256465 Mon Sep 17 00:00:00 2001 From: Clang Formatter Date: Wed, 13 Jun 2018 20:37:13 +0000 Subject: [PATCH 11/16] Automatically formatted --- src/networkremote/incomingdataparser.cpp | 22 +++++++++++----------- src/networkremote/incomingdataparser.h | 6 +++--- src/playlist/playlistmanager.cpp | 22 +++++++++++----------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/networkremote/incomingdataparser.cpp b/src/networkremote/incomingdataparser.cpp index 8a0c8a1ec..a4c8ebc12 100644 --- a/src/networkremote/incomingdataparser.cpp +++ b/src/networkremote/incomingdataparser.cpp @@ -36,7 +36,6 @@ IncomingDataParser::IncomingDataParser(Application* app) : app_(app) { ReloadSettings(); connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings())); - // Connect all the signals // due the player is in a different thread, we cannot access these functions // directly @@ -52,7 +51,8 @@ IncomingDataParser::IncomingDataParser(Application* app) : app_(app) { connect(this, SIGNAL(PlayAt(int, Engine::TrackChangeFlags, bool)), app_->player(), SLOT(PlayAt(int, Engine::TrackChangeFlags, bool))); connect(this, SIGNAL(SeekTo(int)), app_->player(), SLOT(SeekTo(int))); - connect(this, SIGNAL(Enque(int,int)), app_->playlist_manager(), SLOT(Enque(int,int))); + connect(this, SIGNAL(Enque(int, int)), app_->playlist_manager(), + SLOT(Enque(int, int))); connect(this, SIGNAL(SetActivePlaylist(int)), app_->playlist_manager(), SLOT(SetActivePlaylist(int))); @@ -87,9 +87,12 @@ IncomingDataParser::IncomingDataParser(Application* app) : app_(app) { IncomingDataParser::~IncomingDataParser() {} void IncomingDataParser::ReloadSettings() { - QSettings s; - s.beginGroup(MainWindow::kSettingsGroup); - doubleclick_playlist_addmode_ = MainWindow::PlaylistAddBehaviour(s.value("doubleclick_playlist_addmode", MainWindow::PlaylistAddBehaviour_Enqueue).toInt()); + QSettings s; + s.beginGroup(MainWindow::kSettingsGroup); + doubleclick_playlist_addmode_ = MainWindow::PlaylistAddBehaviour( + s.value("doubleclick_playlist_addmode", + MainWindow::PlaylistAddBehaviour_Enqueue) + .toInt()); } bool IncomingDataParser::close_connection() { return close_connection_; } @@ -206,8 +209,7 @@ void IncomingDataParser::ChangeSong(const pb::remote::Message& msg) { emit SetActivePlaylist(request.playlist_id()); } - switch (doubleclick_playlist_addmode_) - { + switch (doubleclick_playlist_addmode_) { // Play the selected song case MainWindow::PlaylistAddBehaviour_Play: emit PlayAt(request.song_index(), Engine::Manual, false); @@ -216,14 +218,12 @@ void IncomingDataParser::ChangeSong(const pb::remote::Message& msg) { // Enque the selected song case MainWindow::PlaylistAddBehaviour_Enqueue: emit Enque(request.playlist_id(), request.song_index()); - if (app_->player()->GetState() != Engine::Playing) - { - emit PlayAt(request.song_index(), Engine::Manual, false); + if (app_->player()->GetState() != Engine::Playing) { + emit PlayAt(request.song_index(), Engine::Manual, false); } break; } - } void IncomingDataParser::SetRepeatMode(const pb::remote::Repeat& repeat) { diff --git a/src/networkremote/incomingdataparser.h b/src/networkremote/incomingdataparser.h index ba8cd4db1..97b7d39b4 100644 --- a/src/networkremote/incomingdataparser.h +++ b/src/networkremote/incomingdataparser.h @@ -1,10 +1,10 @@ #ifndef INCOMINGDATAPARSER_H #define INCOMINGDATAPARSER_H -#include "core/player.h" #include "core/application.h" -#include "remotecontrolmessages.pb.h" +#include "core/player.h" #include "remoteclient.h" +#include "remotecontrolmessages.pb.h" #include "ui/mainwindow.h" class IncomingDataParser : public QObject { @@ -19,7 +19,7 @@ class IncomingDataParser : public QObject { void Parse(const pb::remote::Message& msg); void ReloadSettings(); -signals: + signals: void SendClementineInfo(); void SendFirstData(bool send_playlist_songs); void SendAllPlaylists(); diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index a447f19fa..cb2207372 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -15,11 +15,6 @@ along with Clementine. If not, see . */ -#include "playlistbackend.h" -#include "playlistcontainer.h" -#include "playlistmanager.h" -#include "playlistsaveoptionsdialog.h" -#include "playlistview.h" #include "core/application.h" #include "core/logging.h" #include "core/player.h" @@ -27,9 +22,14 @@ #include "core/utilities.h" #include "library/librarybackend.h" #include "library/libraryplaylistitem.h" +#include "playlistbackend.h" +#include "playlistcontainer.h" +#include "playlistmanager.h" #include "playlistparsers/playlistparser.h" -#include "smartplaylists/generator.h" +#include "playlistsaveoptionsdialog.h" +#include "playlistview.h" #include "queue.h" +#include "smartplaylists/generator.h" #include #include @@ -64,7 +64,7 @@ PlaylistManager::~PlaylistManager() { void PlaylistManager::Init(LibraryBackend* library_backend, PlaylistBackend* playlist_backend, PlaylistSequence* sequence, - PlaylistContainer* playlist_container) { + PlaylistContainer* playlist_container) { library_backend_ = library_backend; playlist_backend_ = playlist_backend; sequence_ = sequence; @@ -399,12 +399,12 @@ void PlaylistManager::ChangePlaylistOrder(const QList& ids) { } void PlaylistManager::Enque(int id, int i) { - QModelIndexList dummyIndexList; + QModelIndexList dummyIndexList; - Q_ASSERT(playlists_.contains(id)); + Q_ASSERT(playlists_.contains(id)); - dummyIndexList.append(playlist(id)->index(i, 0)); - playlist(id)->queue()->ToggleTracks(dummyIndexList); + dummyIndexList.append(playlist(id)->index(i, 0)); + playlist(id)->queue()->ToggleTracks(dummyIndexList); } void PlaylistManager::UpdateSummaryText() { From 5dbdcca54fdc055b29f3c1c6c29e4ae32d1a1640 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Thu, 14 Jun 2018 16:31:25 +0200 Subject: [PATCH 12/16] Set empty string for sinks with default devices and auto sink --- src/engines/gstengine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/engines/gstengine.cpp b/src/engines/gstengine.cpp index 222e95943..4d0260278 100644 --- a/src/engines/gstengine.cpp +++ b/src/engines/gstengine.cpp @@ -951,6 +951,7 @@ GstEngine::OutputDetailsList GstEngine::GetOutputsList() const { OutputDetails default_output; default_output.description = tr("Choose automatically"); default_output.gstreamer_plugin_name = kAutoSink; + default_output.device_property_value = QString(""); ret.append(default_output); for (DeviceFinder* finder : device_finders_) { @@ -976,6 +977,7 @@ GstEngine::OutputDetailsList GstEngine::GetOutputsList() const { OutputDetails output; output.description = tr("Default device on %1").arg(plugin.description); output.gstreamer_plugin_name = plugin.name; + output.device_property_value = QString(""); ret.append(output); } } From a3978711234e7c00d02676a9e165cb8c6da1a7c7 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Thu, 14 Jun 2018 16:33:23 +0200 Subject: [PATCH 13/16] Fix loading output/device setting --- src/ui/playbacksettingspage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/playbacksettingspage.cpp b/src/ui/playbacksettingspage.cpp index 957317377..e20e8e199 100644 --- a/src/ui/playbacksettingspage.cpp +++ b/src/ui/playbacksettingspage.cpp @@ -91,7 +91,7 @@ void PlaybackSettingsPage::Load() { s.beginGroup(GstEngine::kSettingsGroup); QString sink = s.value("sink", GstEngine::kAutoSink).toString(); - QString device = s.value("device").toString(); + QVariant device = s.value("device"); ui_->gst_output->setCurrentIndex(0); for (int i = 0; i < ui_->gst_output->count(); ++i) { From e2f63e3945bb3bcca2e3a4d996f72e9289491010 Mon Sep 17 00:00:00 2001 From: Eoin O'Neill Date: Fri, 8 Jun 2018 23:58:03 -0700 Subject: [PATCH 14/16] Implemented SPC playback functionality. Would like to add more playback support for additional GME supported file types. GME is already supported by the GStreamer backend. --- .../remotecontrolmessages.proto | 1 + ext/libclementine-tagreader/CMakeLists.txt | 1 + ext/libclementine-tagreader/gmereader.cpp | 151 ++++++++++++++++++ ext/libclementine-tagreader/gmereader.h | 51 ++++++ ext/libclementine-tagreader/tagreader.cpp | 25 +-- .../tagreadermessages.proto | 1 + src/core/song.cpp | 10 +- src/core/song.h | 1 + src/widgets/fileview.cpp | 8 +- 9 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 ext/libclementine-tagreader/gmereader.cpp create mode 100644 ext/libclementine-tagreader/gmereader.h diff --git a/ext/libclementine-remote/remotecontrolmessages.proto b/ext/libclementine-remote/remotecontrolmessages.proto index f1ebc3219..b7f197bbd 100644 --- a/ext/libclementine-remote/remotecontrolmessages.proto +++ b/ext/libclementine-remote/remotecontrolmessages.proto @@ -105,6 +105,7 @@ message SongMetadata { CDDA = 12; OGGOPUS = 13; WAVPACK = 14; + SPC = 15; STREAM = 99; } diff --git a/ext/libclementine-tagreader/CMakeLists.txt b/ext/libclementine-tagreader/CMakeLists.txt index 39b159929..02d54897b 100644 --- a/ext/libclementine-tagreader/CMakeLists.txt +++ b/ext/libclementine-tagreader/CMakeLists.txt @@ -14,6 +14,7 @@ set(MESSAGES set(SOURCES fmpsparser.cpp tagreader.cpp + gmereader.cpp ) set(HEADERS diff --git a/ext/libclementine-tagreader/gmereader.cpp b/ext/libclementine-tagreader/gmereader.cpp new file mode 100644 index 000000000..eb1e5d886 --- /dev/null +++ b/ext/libclementine-tagreader/gmereader.cpp @@ -0,0 +1,151 @@ +#include "core/logging.h" +#include "core/timeconstants.h" +#include "gmereader.h" +#include "tagreader.h" + +#include +#include +#include +#include +#include +#include +#include + +bool GME::IsSupportedFormat(const QFileInfo& file_info) { + return file_info.completeSuffix().endsWith("spc"); +} + +void GME::ReadFile(const QFileInfo& file_info, + pb::tagreader::SongMetadata* song_info) { + if (file_info.completeSuffix().endsWith("spc")) + GME::SPC::Read(file_info, song_info); +} + +void GME::SPC::Read(const QFileInfo& file_info, + pb::tagreader::SongMetadata* song_info) { + QFile file(file_info.filePath()); + if (!file.open(QIODevice::ReadOnly)) return; + + qLog(Debug) << "Reading SPC from file" << file_info.fileName(); + + // Check for header -- more reliable than file name alone. + if (!file.read(33).startsWith(QString("SNES-SPC700").toAscii())) return; + + /* + * First order of business -- get any tag values that exist within the core + * file information. These only allow for a certain number of bytes + * per field, so they will likely be overwritten either by the id666 standard + * or the APETAG format (as used by other players, such as foobar and winamp) + * + * Make sure to check id6 documentation before changing the read values! + */ + + file.seek(HAS_ID6_OFFSET); + bool has_id6 = (file.read(1)[0] == (char)xID6_STATUS::ON); + + file.seek(SONG_TITLE_OFFSET); + song_info->set_title(QString::fromAscii(file.read(32)).toStdString()); + + file.seek(GAME_TITLE_OFFSET); + song_info->set_album(QString::fromAscii(file.read(32)).toStdString()); + + file.seek(ARTIST_OFFSET); + song_info->set_artist(QString::fromAscii(file.read(32)).toStdString()); + + file.seek(INTRO_LENGTH_OFFSET); + QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE); + quint64 length_in_sec = 0; + if (length_bytes.size() >= INTRO_LENGTH_SIZE) { + length_in_sec = ConvertSPCStringToNum(length_bytes); + qLog(Debug) << length_in_sec << "------ LENGTH"; + + if (!length_in_sec || length_in_sec >= 0x1FFF) { + // This means that parsing the length as a string failed, so get value LE. + length_in_sec = + length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16); + } + + if (length_in_sec < 0x1FFF) { + song_info->set_length_nanosec(length_in_sec * kNsecPerSec); + } + } + + file.seek(FADE_LENGTH_OFFSET); + QByteArray fade_bytes = file.read(FADE_LENGTH_SIZE); + if (fade_bytes.size() >= FADE_LENGTH_SIZE) { + quint64 fade_length_in_ms = ConvertSPCStringToNum(fade_bytes); + qLog(Debug) << fade_length_in_ms << "------ Fade Length"; + + if (fade_length_in_ms > 0x7FFF) { + fade_length_in_ms = fade_bytes[0] | (fade_bytes[1] << 8) | + (fade_bytes[2] << 16) | (fade_bytes[3] << 24); + } + } + + /* Check for XID6 data -- this is infrequently used, but being able to fill + * in data from this is ideal before trying to rely on APETAG values. XID6 + * format follows EA's binary file format standard named "IFF" */ + file.seek(XID6_OFFSET); + if (has_id6 && file.read(4) == QString("xid6").toAscii()) { + QByteArray xid6_head_data = file.read(4); + if (xid6_head_data.size() >= 4) { + qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | + (xid6_head_data[2] << 16) | xid6_head_data[3]; + /* This should be the size remaining for entire ID6 block, but it + * seems that most files treat this as the size of the remaining header + * space... */ + + qLog(Debug) << file_info.fileName() << " has ID6 tag."; + + while ((file.pos()) + 4 < XID6_OFFSET + xid6_size) { + QByteArray arr = file.read(4); + if (arr.size() < 4) break; + + qint8 id = arr[0]; + qint8 type = arr[1]; + qint16 length = arr[2] | (arr[3] << 8); + + file.read(GetNextMemAddressAlign32bit(length)); + } + } + } + + /* Music Players that support SPC tend to support additional tagging data as + * an APETAG entry at the bottom of the file instead of writing into the xid6 + * tagging space. This is where a lot of the extra data for a file is stored, + * such as genre or replaygain data. + * + * This data is currently supported by TagLib, so we will simply use that for + * the remaining values. */ + TagLib::APE::File ape(file_info.filePath().toStdString().data()); + if (ape.hasAPETag()) { + TagLib::Tag* tag = ape.tag(); + if (!tag) return; + + song_info->set_year(tag->year()); + song_info->set_track(tag->track()); + TagReader::Decode(tag->artist(), nullptr, song_info->mutable_artist()); + TagReader::Decode(tag->title(), nullptr, song_info->mutable_title()); + TagReader::Decode(tag->album(), nullptr, song_info->mutable_album()); + TagReader::Decode(tag->genre(), nullptr, song_info->mutable_genre()); + song_info->set_valid(true); + } + + song_info->set_type(pb::tagreader::SongMetadata_Type_SPC); +} + +qint16 GME::SPC::GetNextMemAddressAlign32bit(qint16 input) { + return ((input + 0x3) & ~0x3); + // Plus 0x3 for rounding up (not down), AND NOT to flatten out on a 32 bit + // level. +} + +quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray& arr) { + quint64 result = 0; + for (auto it = arr.begin(); it != arr.end(); it++) { + unsigned int num = *it - '0'; + if (num > 9) break; + result = (result * 10) + num; // Shift Left and add. + } + return result; +} diff --git a/ext/libclementine-tagreader/gmereader.h b/ext/libclementine-tagreader/gmereader.h new file mode 100644 index 000000000..9369dfca3 --- /dev/null +++ b/ext/libclementine-tagreader/gmereader.h @@ -0,0 +1,51 @@ +#ifndef GMEREADER_H +#define GMEREADER_H + +#include +#include "tagreadermessages.pb.h" + +class QFileInfo; +class QByteArray; + +namespace GME { +bool IsSupportedFormat(const QFileInfo& file_info); +void ReadFile(const QFileInfo& file_info, + pb::tagreader::SongMetadata* song_info); + +namespace SPC { +/* SPC SPEC: + * http://vspcplay.raphnet.net/spc_file_format.txt + */ +const int HAS_ID6_OFFSET = 0x23; +const int SONG_TITLE_OFFSET = 0x2E; +const int GAME_TITLE_OFFSET = 0x4E; +const int DUMPER_OFFSET = 0x6E; +const int COMMENTS_OFFSET = 0x7E; +/*It seems that intro length and fade length are inconsistent from + *file to file. It should be looked into within the GME source code + *to see how GStreamer gets its values for playback length.*/ +const int INTRO_LENGTH_OFFSET = 0xA9; +const int INTRO_LENGTH_SIZE = 3; +const int FADE_LENGTH_OFFSET = 0xAC; +const int FADE_LENGTH_SIZE = 4; +const int ARTIST_OFFSET = 0xB1; +const int XID6_OFFSET = (0x101C0 + 64); + +const int NANO_PER_MS = 1000000; + +enum xID6_STATUS { + ON = 0x26, + OFF = 0x27, +}; + +enum xID6_ID { SongName = 0x01, GameName = 0x02, ArtistName = 0x03 }; + +enum xID6_TYPE { Length = 0x0, String = 0x1, Integer = 0x4 }; + +void Read(const QFileInfo& file_info, pb::tagreader::SongMetadata* song_info); +qint16 GetNextMemAddressAlign32bit(qint16 input); +quint64 ConvertSPCStringToNum(const QByteArray& arr); +} // namespace SPC +} // namespace GME + +#endif diff --git a/ext/libclementine-tagreader/tagreader.cpp b/ext/libclementine-tagreader/tagreader.cpp index 208c9758e..da93c2303 100644 --- a/ext/libclementine-tagreader/tagreader.cpp +++ b/ext/libclementine-tagreader/tagreader.cpp @@ -56,10 +56,11 @@ #include -#include "fmpsparser.h" #include "core/logging.h" #include "core/messagehandler.h" #include "core/timeconstants.h" +#include "fmpsparser.h" +#include "gmereader.h" // Taglib added support for FLAC pictures in 1.7.0 #if (TAGLIB_MAJOR_VERSION > 1) || \ @@ -100,7 +101,7 @@ TagLib::String StdStringToTaglibString(const std::string& s) { TagLib::String QStringToTaglibString(const QString& s) { return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8); } -} +} // namespace const char* TagReader::kMP4_FMPS_Rating_ID = "----:com.apple.iTunes:FMPS_Rating"; @@ -115,7 +116,7 @@ namespace { const char* kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR"; const char* kASF_OriginalDate_ID = "WM/OriginalReleaseTime"; const char* kASF_OriginalYear_ID = "WM/OriginalReleaseYear"; -} +} // namespace TagReader::TagReader() : factory_(new TagLibFileRefFactory), @@ -138,6 +139,9 @@ void TagReader::ReadFile(const QString& filename, std::unique_ptr fileref(factory_->GetFileRef(filename)); if (fileref->isNull()) { qLog(Info) << "TagLib hasn't been able to read " << filename << " file"; + + // Try fallback -- GME filetypes + GME::ReadFile(info, song); return; } @@ -301,8 +305,9 @@ void TagReader::ReadFile(const QString& filename, if (items.contains(kMP4_FMPS_Rating_ID)) { float rating = - TStringToQString(items[kMP4_FMPS_Rating_ID].toStringList().toString( - '\n')).toFloat(); + TStringToQString( + items[kMP4_FMPS_Rating_ID].toStringList().toString('\n')) + .toFloat(); if (song->rating() <= 0 && rating > 0) { song->set_rating(rating); } @@ -586,8 +591,9 @@ void TagReader::SetVorbisComments( vorbis_comments->addField("CONTENT GROUP", StdStringToTaglibString(song.grouping()), true); vorbis_comments->addField( - "BPM", QStringToTaglibString( - song.bpm() <= 0 - 1 ? QString() : QString::number(song.bpm())), + "BPM", + QStringToTaglibString(song.bpm() <= 0 - 1 ? QString() + : QString::number(song.bpm())), true); vorbis_comments->addField( "DISCNUMBER", @@ -604,10 +610,9 @@ void TagReader::SetVorbisComments( StdStringToTaglibString(song.albumartist()), true); vorbis_comments->removeField("ALBUM ARTIST"); - vorbis_comments->addField("LYRICS", - StdStringToTaglibString(song.lyrics()), true); + vorbis_comments->addField("LYRICS", StdStringToTaglibString(song.lyrics()), + true); vorbis_comments->removeField("UNSYNCEDLYRICS"); - } void TagReader::SetFMPSStatisticsVorbisComments( diff --git a/ext/libclementine-tagreader/tagreadermessages.proto b/ext/libclementine-tagreader/tagreadermessages.proto index 857dc4914..32fb0da2e 100644 --- a/ext/libclementine-tagreader/tagreadermessages.proto +++ b/ext/libclementine-tagreader/tagreadermessages.proto @@ -17,6 +17,7 @@ message SongMetadata { CDDA = 12; OGGOPUS = 13; WAVPACK = 14; + SPC = 15; STREAM = 99; } diff --git a/src/core/song.cpp b/src/core/song.cpp index 00e223161..e0dc53dae 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -70,6 +70,7 @@ #include "core/utilities.h" #include "covers/albumcoverloader.h" #include "engines/enginebase.h" +#include "gmereader.h" #include "library/sqlrow.h" #include "tagreadermessages.pb.h" #include "widgets/trackslider.h" @@ -434,6 +435,8 @@ QString Song::TextForFiletype(FileType type) { return QObject::tr("TrueAudio"); case Song::Type_Cdda: return QObject::tr("CDDA"); + case Song::Type_Spc: + return QObject::tr("SNES SPC700"); case Song::Type_Stream: return QObject::tr("Stream"); @@ -672,12 +675,13 @@ void Song::InitFromFilePartial(const QString& filename) { QString suffix = info.suffix().toLower(); TagLib::FileRef fileref(filename.toUtf8().constData()); - if (fileref.file()) d->valid_ = true; + if (fileref.file() || GME::IsSupportedFormat(info)) + d->valid_ = true; else { d->valid_ = false; - qLog(Error) << "File" << filename << "is not recognized by TagLib as a valid audio file."; + qLog(Error) << "File" << filename + << "is not recognized by TagLib as a valid audio file."; } - } void Song::InitArtManual() { diff --git a/src/core/song.h b/src/core/song.h index d3667e49f..82e1f8a06 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -102,6 +102,7 @@ class Song { Type_Cdda = 12, Type_OggOpus = 13, Type_WavPack = 14, + Type_Spc = 15, Type_Stream = 99, }; static QString TextForFiletype(FileType type); diff --git a/src/widgets/fileview.cpp b/src/widgets/fileview.cpp index a371540d7..d6bb3ce7f 100644 --- a/src/widgets/fileview.cpp +++ b/src/widgets/fileview.cpp @@ -15,17 +15,17 @@ along with Clementine. If not, see . */ -#include "fileview.h" -#include "ui_fileview.h" #include "core/deletefiles.h" #include "core/filesystemmusicstorage.h" #include "core/mimedata.h" +#include "fileview.h" #include "ui/iconloader.h" #include "ui/mainwindow.h" // for filter information #include "ui/organiseerrordialog.h" +#include "ui_fileview.h" -#include #include +#include #include #include @@ -33,7 +33,7 @@ const char* FileView::kFileFilter = "*.mp3 *.ogg *.flac *.mpc *.m4a *.m4b *.aac *.wma " "*.mp4 *.spx *.wav *.m3u *.m3u8 *.pls *.xspf " "*.asx *.asxini *.cue *.ape *.wv *.mka *.opus " - "*.oga *.mka *.mp2"; + "*.oga *.mka *.mp2 *.spc"; FileView::FileView(QWidget* parent) : QWidget(parent), From fcf96cb6ffee937568ae7042d1bbe74efb1c1baa Mon Sep 17 00:00:00 2001 From: vaterlangen Date: Mon, 18 Jun 2018 15:21:44 +0200 Subject: [PATCH 15/16] Queue size and duration is now displayed in queue manager && Album Cover Popup fixes (#6086) --- dist/format.py | 2 +- src/core/player.h | 2 +- src/playlist/queue.cpp | 47 ++++++++++++++++++++++++++- src/playlist/queue.h | 15 ++++++++- src/playlist/queuemanager.cpp | 6 ++++ src/playlist/queuemanager.ui | 29 ++++++++++++----- src/ui/albumcoverchoicecontroller.cpp | 29 ++++++++++++++++- src/ui/albumcoverchoicecontroller.h | 7 +++- src/widgets/nowplayingwidget.cpp | 2 +- 9 files changed, 123 insertions(+), 16 deletions(-) diff --git a/dist/format.py b/dist/format.py index c3c964cc0..616802b54 100755 --- a/dist/format.py +++ b/dist/format.py @@ -12,7 +12,7 @@ def main(): description='Reformats C++ source files that have changed from a given ' 'git ref.') parser.add_argument('--url', - default='http://clang.clementine-player.org/format', + default='https://clang.clementine-player.org/format', help='a URL of a Clang-in-the-cloud service') parser.add_argument('--ref', default='origin/master', help='the git-ref to compare against') diff --git a/src/core/player.h b/src/core/player.h index 19c2821b4..7dd141965 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -110,7 +110,7 @@ signals: // The toggle parameter is true when user requests to toggle visibility for // Pretty OSD - void ForceShowOSD(Song, bool toogle); + void ForceShowOSD(Song, bool toggle); }; class Player : public PlayerInterface { diff --git a/src/playlist/queue.cpp b/src/playlist/queue.cpp index d66e3e03f..898acd9d4 100644 --- a/src/playlist/queue.cpp +++ b/src/playlist/queue.cpp @@ -21,9 +21,17 @@ #include #include +#include "core/utilities.h" + const char* Queue::kRowsMimetype = "application/x-clementine-queue-rows"; -Queue::Queue(QObject* parent) : QAbstractProxyModel(parent) {} +Queue::Queue(Playlist* parent) + : QAbstractProxyModel(parent), playlist_(parent), total_length_ns_(0) { + connect(this, SIGNAL(ItemCountChanged(int)), SLOT(UpdateTotalLength())); + connect(this, SIGNAL(TotalLengthChanged(quint64)), SLOT(UpdateSummaryText())); + + UpdateSummaryText(); +} QModelIndex Queue::mapFromSource(const QModelIndex& source_index) const { if (!source_index.isValid()) return QModelIndex(); @@ -77,6 +85,7 @@ void Queue::SourceDataChanged(const QModelIndex& top_left, emit dataChanged(proxy_index, proxy_index); } + emit ItemCountChanged(this->ItemCount()); } void Queue::SourceLayoutChanged() { @@ -89,6 +98,7 @@ void Queue::SourceLayoutChanged() { --i; } } + emit ItemCountChanged(this->ItemCount()); } QModelIndex Queue::index(int row, int column, const QModelIndex& parent) const { @@ -180,6 +190,41 @@ int Queue::PositionOf(const QModelIndex& source_index) const { bool Queue::is_empty() const { return source_indexes_.isEmpty(); } +int Queue::ItemCount() const { return source_indexes_.length(); } + +quint64 Queue::GetTotalLength() const { return total_length_ns_; } + +void Queue::UpdateTotalLength() { + quint64 total = 0; + + for (QPersistentModelIndex row : source_indexes_) { + int id = row.row(); + + Q_ASSERT(playlist_->has_item_at(id)); + + quint64 length = playlist_->item_at(id)->Metadata().length_nanosec(); + if (length > 0) total += length; + } + + total_length_ns_ = total; + + emit TotalLengthChanged(total); +} + +void Queue::UpdateSummaryText() { + QString summary; + int tracks = this->ItemCount(); + quint64 nanoseconds = this->GetTotalLength(); + + // TODO: Make the plurals translatable + summary += tracks == 1 ? tr("1 track") : tr("%1 tracks").arg(tracks); + + if (nanoseconds) + summary += " - [ " + Utilities::WordyTimeNanosec(nanoseconds) + " ]"; + + emit SummaryTextChanged(summary); +} + void Queue::Clear() { if (source_indexes_.isEmpty()) return; diff --git a/src/playlist/queue.h b/src/playlist/queue.h index 77751f0f5..f840e733e 100644 --- a/src/playlist/queue.h +++ b/src/playlist/queue.h @@ -26,7 +26,7 @@ class Queue : public QAbstractProxyModel { Q_OBJECT public: - Queue(QObject* parent = nullptr); + Queue(Playlist* parent); static const char* kRowsMimetype; @@ -35,6 +35,8 @@ class Queue : public QAbstractProxyModel { int PositionOf(const QModelIndex& source_index) const; bool ContainsSourceRow(int source_row) const; int PeekNext() const; + int ItemCount() const; + quint64 GetTotalLength() const; // Modify the queue int TakeNext(); @@ -66,13 +68,24 @@ class Queue : public QAbstractProxyModel { int column, const QModelIndex& parent); Qt::ItemFlags flags(const QModelIndex& index) const; + public slots: + void UpdateSummaryText(); + + signals: + void TotalLengthChanged(const quint64 length); + void ItemCountChanged(const int count); + void SummaryTextChanged(const QString& message); + private slots: void SourceDataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right); void SourceLayoutChanged(); + void UpdateTotalLength(); private: QList source_indexes_; + const Playlist* playlist_; + quint64 total_length_ns_; }; #endif // QUEUE_H diff --git a/src/playlist/queuemanager.cpp b/src/playlist/queuemanager.cpp index 55520d64f..b44f4d4fc 100644 --- a/src/playlist/queuemanager.cpp +++ b/src/playlist/queuemanager.cpp @@ -73,6 +73,8 @@ void QueueManager::CurrentPlaylistChanged(Playlist* playlist) { SLOT(UpdateButtonState())); disconnect(current_playlist_->queue(), SIGNAL(layoutChanged()), this, SLOT(UpdateButtonState())); + disconnect(current_playlist_->queue(), SIGNAL(SummaryTextChanged(QString)), + ui_->queue_summary, SLOT(setText(QString))); disconnect(current_playlist_, SIGNAL(destroyed()), this, SLOT(PlaylistDestroyed())); } @@ -87,6 +89,8 @@ void QueueManager::CurrentPlaylistChanged(Playlist* playlist) { SLOT(UpdateButtonState())); connect(current_playlist_->queue(), SIGNAL(layoutChanged()), this, SLOT(UpdateButtonState())); + connect(current_playlist_->queue(), SIGNAL(SummaryTextChanged(QString)), + ui_->queue_summary, SLOT(setText(QString))); connect(current_playlist_, SIGNAL(destroyed()), this, SLOT(PlaylistDestroyed())); @@ -95,6 +99,8 @@ void QueueManager::CurrentPlaylistChanged(Playlist* playlist) { connect(ui_->list->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(UpdateButtonState())); + + QTimer::singleShot(0, current_playlist_->queue(), SLOT(UpdateSummaryText())); } void QueueManager::MoveUp() { diff --git a/src/playlist/queuemanager.ui b/src/playlist/queuemanager.ui index 3203f8027..89147c240 100644 --- a/src/playlist/queuemanager.ui +++ b/src/playlist/queuemanager.ui @@ -17,7 +17,7 @@ :/icon.png:/icon.png - + @@ -144,14 +144,25 @@ - - - Qt::Horizontal - - - QDialogButtonBox::Close - - + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + diff --git a/src/ui/albumcoverchoicecontroller.cpp b/src/ui/albumcoverchoicecontroller.cpp index ed78208c9..c00cd5919 100644 --- a/src/ui/albumcoverchoicecontroller.cpp +++ b/src/ui/albumcoverchoicecontroller.cpp @@ -55,7 +55,8 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget* parent) cover_searcher_(nullptr), cover_fetcher_(nullptr), save_file_dialog_(nullptr), - cover_from_url_dialog_(nullptr) { + cover_from_url_dialog_(nullptr), + album_cover_popup_(nullptr) { cover_from_file_ = new QAction(IconLoader::Load("document-open", IconLoader::Base), tr("Load cover from disk..."), this); @@ -203,7 +204,27 @@ QString AlbumCoverChoiceController::UnsetCover(Song* song) { return cover; } +bool AlbumCoverChoiceController::ToggleCover(const Song& song) { + if (album_cover_popup_ != nullptr) { + album_cover_popup_->accept(); + album_cover_popup_ = nullptr; + return false; + } + + album_cover_popup_ = ShowCoverPrivate(song); + + // keep track of our window to prevent endless stacking + connect(album_cover_popup_, SIGNAL(finished(int)), this, + SLOT(AlbumCoverPopupClosed())); + + return true; +} + void AlbumCoverChoiceController::ShowCover(const Song& song) { + ShowCoverPrivate(song); +} + +QDialog* AlbumCoverChoiceController::ShowCoverPrivate(const Song& song) { QDialog* dialog = new QDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose, true); @@ -245,6 +266,12 @@ void AlbumCoverChoiceController::ShowCover(const Song& song) { dialog->setWindowTitle(title_text); dialog->setFixedSize(label->pixmap()->size()); dialog->show(); + + return dialog; +} + +void AlbumCoverChoiceController::AlbumCoverPopupClosed() { + album_cover_popup_ = nullptr; } void AlbumCoverChoiceController::SearchCoverAutomatically(const Song& song) { diff --git a/src/ui/albumcoverchoicecontroller.h b/src/ui/albumcoverchoicecontroller.h index c71a4f5c7..529783fcf 100644 --- a/src/ui/albumcoverchoicecontroller.h +++ b/src/ui/albumcoverchoicecontroller.h @@ -96,6 +96,7 @@ class AlbumCoverChoiceController : public QWidget { // Shows the cover of given song in it's original size. void ShowCover(const Song& song); + bool ToggleCover(const Song& song); // Search for covers automatically void SearchCoverAutomatically(const Song& song); @@ -113,14 +114,16 @@ class AlbumCoverChoiceController : public QWidget { static bool CanAcceptDrag(const QDragEnterEvent* e); -signals: + signals: void AutomaticCoverSearchDone(); private slots: void AlbumCoverFetched(quint64 id, const QImage& image, const CoverSearchStatistics& statistics); + void AlbumCoverPopupClosed(); private: + QDialog* ShowCoverPrivate(const Song& song); QString GetInitialPathForFileDialog(const Song& song, const QString& filename); @@ -144,6 +147,8 @@ signals: QAction* search_cover_auto_; QMap cover_fetching_tasks_; + + QDialog* album_cover_popup_; }; #endif // ALBUMCOVERCHOICECONTROLLER_H diff --git a/src/widgets/nowplayingwidget.cpp b/src/widgets/nowplayingwidget.cpp index b62b5b671..502a7801d 100644 --- a/src/widgets/nowplayingwidget.cpp +++ b/src/widgets/nowplayingwidget.cpp @@ -641,7 +641,7 @@ void NowPlayingWidget::UnsetCover() { } void NowPlayingWidget::ShowCover() { - album_cover_choice_controller_->ShowCover(metadata_); + album_cover_choice_controller_->ToggleCover(metadata_); } void NowPlayingWidget::SearchCoverAutomatically() { From 4733185d08e78b8b1b43b154a5ef83251783e6c0 Mon Sep 17 00:00:00 2001 From: Eoin O'Neill Date: Mon, 18 Jun 2018 06:26:11 -0700 Subject: [PATCH 16/16] Added basic VGM format playback and tag reading. (#6089) Current VGM format implementation in GStreamer (gstgme) only supports Sega Geneses (Mega Drive) and Sega Master System emulation. GStreamer also cannot handle the VGZ format (a shorthand for vgm.gz, a gzipped archive that contains a song) which means that users will currently have to extract the contents of their VGZ files to individual vgm files. --- .../remotecontrolmessages.proto | 1 + ext/libclementine-tagreader/gmereader.cpp | 97 ++++++++++++++++++- ext/libclementine-tagreader/gmereader.h | 20 ++++ .../tagreadermessages.proto | 1 + src/core/song.cpp | 2 + src/core/song.h | 1 + src/widgets/fileview.cpp | 2 +- 7 files changed, 119 insertions(+), 5 deletions(-) diff --git a/ext/libclementine-remote/remotecontrolmessages.proto b/ext/libclementine-remote/remotecontrolmessages.proto index b7f197bbd..8745a94af 100644 --- a/ext/libclementine-remote/remotecontrolmessages.proto +++ b/ext/libclementine-remote/remotecontrolmessages.proto @@ -106,6 +106,7 @@ message SongMetadata { OGGOPUS = 13; WAVPACK = 14; SPC = 15; + VGM = 16; STREAM = 99; } diff --git a/ext/libclementine-tagreader/gmereader.cpp b/ext/libclementine-tagreader/gmereader.cpp index eb1e5d886..1e1c6d443 100644 --- a/ext/libclementine-tagreader/gmereader.cpp +++ b/ext/libclementine-tagreader/gmereader.cpp @@ -12,13 +12,16 @@ #include bool GME::IsSupportedFormat(const QFileInfo& file_info) { - return file_info.completeSuffix().endsWith("spc"); + return (file_info.completeSuffix().endsWith("spc") || + file_info.completeSuffix().endsWith("vgm")); } void GME::ReadFile(const QFileInfo& file_info, pb::tagreader::SongMetadata* song_info) { if (file_info.completeSuffix().endsWith("spc")) - GME::SPC::Read(file_info, song_info); + SPC::Read(file_info, song_info); + if (file_info.completeSuffix().endsWith("vgm")) + VGM::Read(file_info, song_info); } void GME::SPC::Read(const QFileInfo& file_info, @@ -26,7 +29,7 @@ void GME::SPC::Read(const QFileInfo& file_info, QFile file(file_info.filePath()); if (!file.open(QIODevice::ReadOnly)) return; - qLog(Debug) << "Reading SPC from file" << file_info.fileName(); + qLog(Debug) << "Reading tags from SPC file: " << file_info.fileName(); // Check for header -- more reliable than file name alone. if (!file.read(33).startsWith(QString("SNES-SPC700").toAscii())) return; @@ -128,9 +131,9 @@ void GME::SPC::Read(const QFileInfo& file_info, TagReader::Decode(tag->title(), nullptr, song_info->mutable_title()); TagReader::Decode(tag->album(), nullptr, song_info->mutable_album()); TagReader::Decode(tag->genre(), nullptr, song_info->mutable_genre()); - song_info->set_valid(true); } + song_info->set_valid(true); song_info->set_type(pb::tagreader::SongMetadata_Type_SPC); } @@ -149,3 +152,89 @@ quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray& arr) { } return result; } + +void GME::VGM::Read(const QFileInfo& file_info, + pb::tagreader::SongMetadata* song_info) { + QFile file(file_info.filePath()); + if (!file.open(QIODevice::ReadOnly)) return; + + qLog(Debug) << "Reading tags from VGM file: " << file_info.fileName(); + + if (!file.read(4).startsWith(QString("Vgm ").toAscii())) return; + + file.seek(GD3_TAG_PTR); + QByteArray gd3_head = file.read(4); + if (gd3_head.size() < 4) return; + + quint64 pt = (unsigned char)gd3_head[0] | ((unsigned char)gd3_head[1] << 8) | + ((unsigned char)gd3_head[2] << 16) | + ((unsigned)gd3_head[3] << 24); + + file.seek(SAMPLE_COUNT); + QByteArray sample_count_bytes = file.read(4); + file.seek(LOOP_SAMPLE_COUNT); + QByteArray loop_count_bytes = file.read(4); + quint64 length = 0; + + if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) return; + + file.seek(GD3_TAG_PTR + pt); + QByteArray gd3_version = file.read(4); + + file.seek(file.pos() + 4); + QByteArray gd3_length_bytes = file.read(4); + quint32 gd3_length = + (unsigned char)gd3_length_bytes[0] | ((unsigned char)gd3_head[1] << 8) | + ((unsigned char)gd3_head[2] << 16) | ((unsigned char)gd3_head[3] << 24); + + QByteArray gd3Data = file.read(gd3_length); + QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly); + // Stored as 16 bit UTF string, two bytes per letter. + fileTagStream.setCodec("UTF-16"); + QStringList strings = fileTagStream.readLine(0).split('\0'); + if (strings.count() < 10) return; + + /* VGM standard dictates string tag data exist in specific order. + * Order alternates between English and Japanese version of data. + * Read GD3 tag standard for more details. */ + song_info->set_title(strings[0].toStdString()); + song_info->set_album(strings[2].toStdString()); + song_info->set_artist(strings[6].toStdString()); + song_info->set_year(strings[8].left(4).toInt()); + song_info->set_length_nanosec(length * kNsecPerMsec); + song_info->set_valid(true); + song_info->set_type(pb::tagreader::SongMetadata_Type_VGM); +} + +bool GME::VGM::GetPlaybackLength(const QByteArray& sample_count_bytes, + const QByteArray& loop_count_bytes, + quint64& out_length) { + if (sample_count_bytes.size() != 4) return false; + if (loop_count_bytes.size() != 4) return false; + + quint64 sample_count = (unsigned char)sample_count_bytes[0] | + ((unsigned char)sample_count_bytes[1] << 8) | + ((unsigned char)sample_count_bytes[2] << 16) | + ((unsigned char)sample_count_bytes[3] << 24); + + qLog(Debug) << QString::number(sample_count, 16); + qLog(Debug) << sample_count_bytes.toHex(); + + if (sample_count <= 0) return false; + + quint64 loop_sample_count = (unsigned char)loop_count_bytes[0] | + ((unsigned char)loop_count_bytes[1] << 8) | + ((unsigned char)loop_count_bytes[2] << 16) | + ((unsigned char)loop_count_bytes[3] << 24); + + if (loop_sample_count <= 0) { + out_length = sample_count * 1000 / SAMPLE_TIMEBASE; + return true; + } + + quint64 intro_length_ms = + (sample_count - loop_sample_count) * 1000 / SAMPLE_TIMEBASE; + quint64 loop_length_ms = (loop_sample_count)*1000 / SAMPLE_TIMEBASE; + out_length = intro_length_ms + (loop_length_ms * 2) + GST_GME_LOOP_TIME_MS; + return true; +} diff --git a/ext/libclementine-tagreader/gmereader.h b/ext/libclementine-tagreader/gmereader.h index 9369dfca3..e3959be15 100644 --- a/ext/libclementine-tagreader/gmereader.h +++ b/ext/libclementine-tagreader/gmereader.h @@ -46,6 +46,26 @@ void Read(const QFileInfo& file_info, pb::tagreader::SongMetadata* song_info); qint16 GetNextMemAddressAlign32bit(qint16 input); quint64 ConvertSPCStringToNum(const QByteArray& arr); } // namespace SPC + +namespace VGM { +/* VGM SPEC: + * http://www.smspower.org/uploads/Music/vgmspec170.txt?sid=17c810c54633b6dd4982f92f718361c1 + * GD3 TAG SPEC: + * http://www.smspower.org/uploads/Music/gd3spec100.txt */ +const int GD3_TAG_PTR = 0x14; +const int SAMPLE_COUNT = 0x18; +const int LOOP_SAMPLE_COUNT = 0x20; +const int SAMPLE_TIMEBASE = 44100; +const int GST_GME_LOOP_TIME_MS = 8000; + +void Read(const QFileInfo& file_info, pb::tagreader::SongMetadata* song_info); +/* Takes in two QByteArrays, expected to be 4 bytes long. Desired length + * is returned via output parameter out_length. Returns false on error. */ +bool GetPlaybackLength(const QByteArray& sample_count_bytes, + const QByteArray& loop_count_bytes, quint64& out_length); + +} // namespace VGM + } // namespace GME #endif diff --git a/ext/libclementine-tagreader/tagreadermessages.proto b/ext/libclementine-tagreader/tagreadermessages.proto index 32fb0da2e..fa00749ce 100644 --- a/ext/libclementine-tagreader/tagreadermessages.proto +++ b/ext/libclementine-tagreader/tagreadermessages.proto @@ -18,6 +18,7 @@ message SongMetadata { OGGOPUS = 13; WAVPACK = 14; SPC = 15; + VGM = 16; STREAM = 99; } diff --git a/src/core/song.cpp b/src/core/song.cpp index e0dc53dae..c16dc7c52 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -437,6 +437,8 @@ QString Song::TextForFiletype(FileType type) { return QObject::tr("CDDA"); case Song::Type_Spc: return QObject::tr("SNES SPC700"); + case Song::Type_VGM: + return QObject::tr("VGM"); case Song::Type_Stream: return QObject::tr("Stream"); diff --git a/src/core/song.h b/src/core/song.h index 82e1f8a06..64e40d312 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -103,6 +103,7 @@ class Song { Type_OggOpus = 13, Type_WavPack = 14, Type_Spc = 15, + Type_VGM = 16, Type_Stream = 99, }; static QString TextForFiletype(FileType type); diff --git a/src/widgets/fileview.cpp b/src/widgets/fileview.cpp index d6bb3ce7f..31b61a03d 100644 --- a/src/widgets/fileview.cpp +++ b/src/widgets/fileview.cpp @@ -33,7 +33,7 @@ const char* FileView::kFileFilter = "*.mp3 *.ogg *.flac *.mpc *.m4a *.m4b *.aac *.wma " "*.mp4 *.spx *.wav *.m3u *.m3u8 *.pls *.xspf " "*.asx *.asxini *.cue *.ape *.wv *.mka *.opus " - "*.oga *.mka *.mp2 *.spc"; + "*.oga *.mka *.mp2 *.spc *.vgm"; FileView::FileView(QWidget* parent) : QWidget(parent),