diff --git a/CMakeLists.txt b/CMakeLists.txt index 4702af2..fc3b3cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,11 @@ add_compile_options(-Wextra) set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s") +find_package(LibMagic REQUIRED) +if (NOT LIBMAGIC_FOUND) + message(FATAL_ERROR "Couldn't find libmagic") +endif(NOT LIBMAGIC_FOUND) + find_package(Threads REQUIRED) find_package(LibNotify REQUIRED) @@ -53,7 +58,7 @@ endif() set(TARGET_VERSION_MAJOR 0) set(TARGET_VERSION_MINOR 2) -set(TARGET_VERSION_PATCH 12) +set(TARGET_VERSION_PATCH 13) set(APP_VERSION "${TARGET_VERSION_MAJOR}.${TARGET_VERSION_MINOR}.${TARGET_VERSION_PATCH}") add_subdirectory(src) diff --git a/cmake/CPackConfig.cmake.in b/cmake/CPackConfig.cmake.in index 281e39b..235224e 100644 --- a/cmake/CPackConfig.cmake.in +++ b/cmake/CPackConfig.cmake.in @@ -13,7 +13,7 @@ set(CPACK_DEBIAN_PACKAGE_SECTION "sound") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Konstantin Sorokin ") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE @CPACK_DEBIAN_PACKAGE_ARCHITECTURE@) set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}_${APP_VERSION}_@CPACK_DEBIAN_PACKAGE_ARCHITECTURE@) -set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libgtkmm-3.0-1 | libgtkmm-3.0-1v5 (>= 3.10.1), libgstreamermm-0.10-2 (>= 0.10.11) | libgstreamermm-1.0-0v5 (>= 1.4.3), gstreamer0.10-plugins-base (>= 0.10.36) | gstreamer1.0-plugins-base (>= 1.2.4), gstreamer0.10-plugins-good (>= 0.10.31) | gstreamer1.0-plugins-good (>= 1.2.4), gstreamer0.10-plugins-bad (>= 0.10.23) | gstreamer1.0-plugins-bad (>= 1.2.4), libcurl3 (>= 7.35.0), libnotify4 (>= 0.7.6), libappindicator3-1") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libgtkmm-3.0-1 | libgtkmm-3.0-1v5 (>= 3.10.1), libgstreamermm-0.10-2 (>= 0.10.11) | libgstreamermm-1.0-0v5 (>= 1.4.3), gstreamer0.10-plugins-base (>= 0.10.36) | gstreamer1.0-plugins-base (>= 1.2.4), gstreamer0.10-plugins-good (>= 0.10.31) | gstreamer1.0-plugins-good (>= 1.2.4), gstreamer0.10-plugins-bad (>= 0.10.23) | gstreamer1.0-plugins-bad (>= 1.2.4), libcurl3 (>= 7.35.0), libnotify4 (>= 0.7.6), libappindicator3-1, libmagic1") set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/applications;/usr/share/pixmaps") set(CPACK_RPM_PACKAGE_URL "https://github.com/thekvs/radiotray-lite/") diff --git a/cmake/FindLibMagic.cmake b/cmake/FindLibMagic.cmake new file mode 100644 index 0000000..f1f4e54 --- /dev/null +++ b/cmake/FindLibMagic.cmake @@ -0,0 +1,52 @@ +# - Try to find libmagic header and library +# +# Usage of this module as follows: +# +# find_package(LibMagic) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# LibMagic_ROOT_DIR Set this variable to the root installation of +# libmagic if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# LIBMAGIC_FOUND System has libmagic and magic.h +# LIBMAGIC_LIBRARIES The libmagic library +# LIBMAGIC_INCLUDE_DIRS The location of magic.h + +find_path(LibMagic_ROOT_DIR + NAMES include/magic.h +) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # the static version of the library is preferred on OS X for the + # purposes of making packages (libmagic doesn't ship w/ OS X) + set(libmagic_names libmagic.a magic) +else () + set(libmagic_names magic) +endif () + +find_library(LIBMAGIC_LIBRARIES + NAMES ${libmagic_names} + HINTS ${LibMagic_ROOT_DIR}/lib +) + +find_path(LIBMAGIC_INCLUDE_DIRS + NAMES magic.h + HINTS ${LibMagic_ROOT_DIR}/include +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibMagic DEFAULT_MSG + LIBMAGIC_LIBRARIES + LIBMAGIC_INCLUDE_DIRS +) + +mark_as_advanced( + LibMagic_ROOT_DIR + LIBMAGIC_LIBRARIES + LIBMAGIC_INCLUDE_DIRS +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95ca827..97b8178 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ include_directories(${GSTREAMER_INCLUDE_DIRS}) include_directories(${GSTREAMERMM_INCLUDE_DIRS}) include_directories(${CURL_INCLUDE_DIRS}) include_directories(${LIBNOTIFY_INCLUDE_DIRS}) +include_directories(${LIBMAGIC_INCLUDE_DIRS}) include_directories(third_party) add_definitions(-Wno-deprecated-declarations) @@ -45,6 +46,7 @@ target_link_libraries( ${CURL_LIBRARIES} ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES} + ${LIBMAGIC_LIBRARIES} ) set_target_properties(radiotray-lite PROPERTIES COMPILE_FLAGS "-std=c++11") target_compile_definitions(radiotray-lite PRIVATE -DELPP_DISABLE_DEFAULT_CRASH_HANDLING) @@ -73,6 +75,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") ${GSTREAMERMM_LIBRARIES} ${CURL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + ${LIBMAGIC_LIBRARIES} ) set_target_properties(radiotray-lite-cli PROPERTIES COMPILE_FLAGS "-std=c++11") target_compile_definitions(radiotray-lite-cli PRIVATE -DELPP_DISABLE_DEFAULT_CRASH_HANDLING) diff --git a/src/about.hpp b/src/about.hpp index 96dc559..c90d9f0 100644 --- a/src/about.hpp +++ b/src/about.hpp @@ -1,8 +1,8 @@ #ifndef __ABOUT_HPP_INCLUDED__ #define __ABOUT_HPP_INCLUDED__ -#include #include "constants.hpp" +#include namespace radiotray { diff --git a/src/asx_playlist_decoder.hpp b/src/asx_playlist_decoder.hpp index 80417a2..58f0697 100644 --- a/src/asx_playlist_decoder.hpp +++ b/src/asx_playlist_decoder.hpp @@ -1,8 +1,8 @@ #ifndef __ASX_PLAYLIST_DECODER_HPP_INCLUDED__ #define __ASX_PLAYLIST_DECODER_HPP_INCLUDED__ -#include #include +#include #include #include "playlist_decoder.hpp" diff --git a/src/config.hpp b/src/config.hpp index 806b294..d151a70 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -1,13 +1,13 @@ #ifndef __CONFIG_HPP_INCLUDED__ #define __CONFIG_HPP_INCLUDED__ -#include #include +#include #include -#include "pugixml/pugixml.hpp" #include "easyloggingpp/easylogging++.h" +#include "pugixml/pugixml.hpp" #include "constants.hpp" diff --git a/src/event_manager.hpp b/src/event_manager.hpp index 9bdb0ee..130100a 100644 --- a/src/event_manager.hpp +++ b/src/event_manager.hpp @@ -1,8 +1,8 @@ #ifndef __EVENT_MANAGER_HPP_INCLUDED__ #define __EVENT_MANAGER_HPP_INCLUDED__ -#include #include +#include #include #include diff --git a/src/keyboard_control.hpp b/src/keyboard_control.hpp index cfc0b34..f30bfc4 100644 --- a/src/keyboard_control.hpp +++ b/src/keyboard_control.hpp @@ -2,8 +2,8 @@ #define __KEYBOARD_CONTROL_HPP_INCLUDED__ #include -#include #include +#include #include #include @@ -27,7 +27,8 @@ class KeyboardControl KeyboardControl() = delete; KeyboardControl(const KeyboardControl&) = delete; - void operator()() + void + operator()() { std::cout << "Press to stop/resume playing." << std::endl; std::cout << "Press n/p to play next/previous station." << std::endl; diff --git a/src/m3u_playlist_decoder.hpp b/src/m3u_playlist_decoder.hpp index c14fa12..3059a10 100644 --- a/src/m3u_playlist_decoder.hpp +++ b/src/m3u_playlist_decoder.hpp @@ -1,9 +1,9 @@ #ifndef __M3U_PLAYLIST_DECODER_HPP_INCLUDED__ #define __M3U_PLAYLIST_DECODER_HPP_INCLUDED__ -#include -#include #include +#include +#include #include "playlist_decoder.hpp" diff --git a/src/notification.hpp b/src/notification.hpp index 7c558d4..a7d796d 100644 --- a/src/notification.hpp +++ b/src/notification.hpp @@ -1,8 +1,8 @@ #ifndef __NOTIFICATION_HPP_INCLUDED__ #define __NOTIFICATION_HPP_INCLUDED__ -#include #include +#include #include #include diff --git a/src/player.cpp b/src/player.cpp index 4545b0b..737dce3 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -22,6 +22,10 @@ Player::init(int argc, char** argv) Glib::RefPtr bus = playbin->get_bus(); bus->add_watch(sigc::mem_fun(*this, &Player::on_bus_message)); + if (not playlist.init()) { + return false; + } + return true; } diff --git a/src/player.hpp b/src/player.hpp index 4c4d921..7cb8ad5 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -1,8 +1,8 @@ #ifndef __PLAYER_HPP_INCLUDED__ #define __PLAYER_HPP_INCLUDED__ -#include #include +#include #include @@ -13,8 +13,8 @@ #include -#include "playlist.hpp" #include "event_manager.hpp" +#include "playlist.hpp" namespace radiotray { diff --git a/src/playlist.cpp b/src/playlist.cpp index 422d494..bc5e31a 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -8,19 +8,41 @@ static const bool kOnlyHeaders = true; Playlist::Playlist() { - handle = curl_easy_init(); - curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errbuffer); - - decoders.push_back(std::make_shared()); - decoders.push_back(std::make_shared()); - decoders.push_back(std::make_shared()); - decoders.push_back(std::make_shared()); - decoders.push_back(std::make_shared()); + decoders.emplace(std::make_pair(PlaylistDecoderType::M3U_PLAYLIST_DECODER, std::make_shared())); + decoders.emplace(std::make_pair(PlaylistDecoderType::PLS_PLAYLIST_DECODER, std::make_shared())); + decoders.emplace(std::make_pair(PlaylistDecoderType::ASX_PLAYLIST_DECODER, std::make_shared())); + decoders.emplace(std::make_pair(PlaylistDecoderType::RAM_PLAYLIST_DECODER, std::make_shared())); + decoders.emplace(std::make_pair(PlaylistDecoderType::XSPF_PLAYLIST_DECODER, std::make_shared())); } Playlist::~Playlist() { curl_easy_cleanup(handle); + + if (mcookie) { + magic_close(mcookie); + } +} + +bool +Playlist::init() +{ + handle = curl_easy_init(); + curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errbuffer); + + mcookie = magic_open(MAGIC_NONE); + if (mcookie == nullptr) { + LOG(ERROR) << "Error opening libmagic database"; + return false; + } + + auto rc = magic_load(mcookie, NULL); + if (rc != 0) { + LOG(ERROR) << magic_error(mcookie); + return false; + } + + return true; } std::tuple @@ -129,18 +151,19 @@ Playlist::run_playlist_decoders(std::string url) MediaStreams streams; bool extracted = false; + // First try to detect playlist's type by Content-Type header auto content_type = get_content_type(); - if (!content_type.empty()) { + if (not content_type.empty()) { LOG(DEBUG) << "Content-Type: " << content_type; for (const auto& decoder : decoders) { - if (decoder->is_valid(content_type)) { - LOG(DEBUG) << "Matched " << decoder->desc(); + if (decoder.second->is_valid(content_type)) { + LOG(DEBUG) << "Matched " << decoder.second->desc(); prepare_playlist_request(url, not kOnlyHeaders); auto rc = curl_easy_perform(handle); if (rc == CURLE_OK) { LOG(DEBUG) << "Playlist: " << data; - streams = decoder->extract_media_streams(data); + streams = decoder.second->extract_media_streams(data); for (auto& s : streams) { LOG(DEBUG) << "Stream: " << s; } @@ -151,8 +174,23 @@ Playlist::run_playlist_decoders(std::string url) } } + // Try to infer playlist's type + abort_get_request = true; + prepare_playlist_request(url, not kOnlyHeaders); + auto rc = curl_easy_perform(handle); + abort_get_request = false; + + if (rc == CURLE_OK or rc == CURLE_WRITE_ERROR /* it's ok, we've aborted reading */) { + auto type = guess_playlist_decoder_type(); + if (type != PlaylistDecoderType::UNKNOWN_PLAYLIST_DECODER) { + const auto& decoder = decoders[type]; + streams = decoder->extract_media_streams(data); + extracted = true; + } + } + // No decoder found, consider url a media stream - if (streams.empty() && !extracted) { + if (streams.empty() and not extracted) { streams.push_back(url); } @@ -173,6 +211,44 @@ Playlist::has_prefix(const std::string& prefix, const std::string& str) return false; } +PlaylistDecoderType +Playlist::guess_playlist_decoder_type() +{ + auto desc = magic_buffer(mcookie, data.c_str(), data.size()); + if (desc == nullptr) { + return PlaylistDecoderType::UNKNOWN_PLAYLIST_DECODER; + } + + LOG(DEBUG) << "libmagic description: " << desc; + + static const std::string kM3UPlaylistDesc = "M3U"; + static const std::string kPLSPlaylistDesc = "PLS"; + static const std::string kXSPFPlaylistDesc = "XML"; + + if (strncasecmp(desc, kM3UPlaylistDesc.c_str(), kM3UPlaylistDesc.size()) == 0) { + return PlaylistDecoderType::M3U_PLAYLIST_DECODER; + } + + if (strncasecmp(desc, kPLSPlaylistDesc.c_str(), kPLSPlaylistDesc.size()) == 0) { + return PlaylistDecoderType::PLS_PLAYLIST_DECODER; + } + + if (strncasecmp(desc, kXSPFPlaylistDesc.c_str(), kXSPFPlaylistDesc.size()) == 0) { + std::transform(data.begin(), data.end(), data.begin(), tolower); + auto pos = data.find("/xspf.org/"); + if (pos != std::string::npos) { + return PlaylistDecoderType::XSPF_PLAYLIST_DECODER; + } + } + + static const std::string kASXPlaylist = " +#include +#include #include #include -#include +#include #include +#include -#include "constants.hpp" #include "config.hpp" +#include "constants.hpp" +#include "asx_playlist_decoder.hpp" #include "m3u_playlist_decoder.hpp" #include "pls_playlist_decoder.hpp" -#include "asx_playlist_decoder.hpp" #include "ram_playlist_decoder.hpp" #include "xspf_playlist_decoder.hpp" @@ -27,6 +29,7 @@ class Playlist ~Playlist(); + bool init(); std::tuple get_streams(std::string url); void set_config(std::shared_ptr cfg); @@ -34,18 +37,21 @@ class Playlist CURL* handle = nullptr; char errbuffer[CURL_ERROR_SIZE]; + magic_t mcookie = nullptr; + std::shared_ptr config; bool abort_get_request = false; std::string data; - std::vector> decoders; + std::map> decoders; void prepare_playlist_request(std::string url, bool only_headers); long get_http_status(); std::string get_content_type(); MediaStreams run_playlist_decoders(std::string url); bool has_prefix(const std::string& prefix, const std::string& str); + PlaylistDecoderType guess_playlist_decoder_type(); static size_t write_memory_cb(void* ptr, size_t size, size_t nmemb, void* data); }; diff --git a/src/playlist_decoder.hpp b/src/playlist_decoder.hpp index e18a25c..2b80ccb 100644 --- a/src/playlist_decoder.hpp +++ b/src/playlist_decoder.hpp @@ -1,14 +1,14 @@ #ifndef __PLAYLIST_DECODER_HPP_INCLUDED__ #define __PLAYLIST_DECODER_HPP_INCLUDED__ -#include -#include -#include #include -#include -#include #include +#include +#include #include +#include +#include +#include #include "easyloggingpp/easylogging++.h" @@ -16,6 +16,15 @@ namespace radiotray { using MediaStreams = std::vector; +enum class PlaylistDecoderType { + UNKNOWN_PLAYLIST_DECODER, + M3U_PLAYLIST_DECODER, + PLS_PLAYLIST_DECODER, + RAM_PLAYLIST_DECODER, + ASX_PLAYLIST_DECODER, + XSPF_PLAYLIST_DECODER +}; + class PlaylistDecoder { public: diff --git a/src/radiotray-lite-cli.cpp b/src/radiotray-lite-cli.cpp index 685aa28..2cb103c 100644 --- a/src/radiotray-lite-cli.cpp +++ b/src/radiotray-lite-cli.cpp @@ -1,15 +1,15 @@ -#include #include #include +#include -#include #include +#include #include #include -#include "playlist.hpp" #include "player.hpp" +#include "playlist.hpp" #include "keyboard_control.hpp" INITIALIZE_EASYLOGGINGPP diff --git a/src/radiotray-lite.cpp b/src/radiotray-lite.cpp index b7fbf24..6bb21a2 100644 --- a/src/radiotray-lite.cpp +++ b/src/radiotray-lite.cpp @@ -9,7 +9,7 @@ main(int argc, char* argv[]) { el::Configurations easylogging_config; easylogging_config.setToDefault(); - // Values are always std::string +// Values are always std::string #ifndef NDEBUG easylogging_config.set(el::Level::Info, el::ConfigurationType::Format, "%datetime %level %loc %msg"); diff --git a/src/ram_playlist_decoder.hpp b/src/ram_playlist_decoder.hpp index de193c9..e2e8e68 100644 --- a/src/ram_playlist_decoder.hpp +++ b/src/ram_playlist_decoder.hpp @@ -1,8 +1,8 @@ #ifndef __RAM_PLAYLIST_DECODER_HPP_INCLUDED__ #define __RAM_PLAYLIST_DECODER_HPP_INCLUDED__ -#include #include +#include #include "playlist_decoder.hpp" diff --git a/src/tray.hpp b/src/tray.hpp index 0c902f3..ae8c2fb 100644 --- a/src/tray.hpp +++ b/src/tray.hpp @@ -1,13 +1,13 @@ #ifndef __TRAY_HPP_INCLUDED__ #define __TRAY_HPP_INCLUDED__ -#include +#include #include #include -#include +#include -#include #include +#include #include #include @@ -15,11 +15,11 @@ #include "pugixml/pugixml.hpp" -#include "constants.hpp" -#include "player.hpp" #include "about.hpp" -#include "notification.hpp" #include "config.hpp" +#include "constants.hpp" +#include "notification.hpp" +#include "player.hpp" namespace radiotray { diff --git a/src/xspf_playlist_decoder.hpp b/src/xspf_playlist_decoder.hpp index 71771d3..0e5a77f 100644 --- a/src/xspf_playlist_decoder.hpp +++ b/src/xspf_playlist_decoder.hpp @@ -1,8 +1,8 @@ #ifndef __XSPF_PLAYLIST_DECODER_HPP_INCLUDED__ #define __XSPF_PLAYLIST_DECODER_HPP_INCLUDED__ -#include #include +#include #include "playlist_decoder.hpp" #include "pugixml/pugixml.hpp"