Skip to content

Commit

Permalink
use libmagic to deduce playlist's type
Browse files Browse the repository at this point in the history
This is usefull in the case when webserver don't uses
correct content-type header for a playlist.

Plus some .clang-formatting.

This commit closes #1
  • Loading branch information
thekvs committed Feb 4, 2017
1 parent fc5c17c commit 7f879a0
Show file tree
Hide file tree
Showing 21 changed files with 205 additions and 49 deletions.
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion cmake/CPackConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ set(CPACK_DEBIAN_PACKAGE_SECTION "sound")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Konstantin Sorokin <[email protected]>")
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/")
Expand Down
52 changes: 52 additions & 0 deletions cmake/FindLibMagic.cmake
Original file line number Diff line number Diff line change
@@ -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
)
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/about.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#ifndef __ABOUT_HPP_INCLUDED__
#define __ABOUT_HPP_INCLUDED__

#include <gtkmm.h>
#include "constants.hpp"
#include <gtkmm.h>

namespace radiotray
{
Expand Down
2 changes: 1 addition & 1 deletion src/asx_playlist_decoder.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#ifndef __ASX_PLAYLIST_DECODER_HPP_INCLUDED__
#define __ASX_PLAYLIST_DECODER_HPP_INCLUDED__

#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>

#include "playlist_decoder.hpp"
Expand Down
4 changes: 2 additions & 2 deletions src/config.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#ifndef __CONFIG_HPP_INCLUDED__
#define __CONFIG_HPP_INCLUDED__

#include <string>
#include <chrono>
#include <string>

#include <gtkmm.h>

#include "pugixml/pugixml.hpp"
#include "easyloggingpp/easylogging++.h"
#include "pugixml/pugixml.hpp"

#include "constants.hpp"

Expand Down
2 changes: 1 addition & 1 deletion src/event_manager.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#ifndef __EVENT_MANAGER_HPP_INCLUDED__
#define __EVENT_MANAGER_HPP_INCLUDED__

#include <string>
#include <memory>
#include <string>

#include <gtkmm.h>
#include <sigc++/sigc++.h>
Expand Down
5 changes: 3 additions & 2 deletions src/keyboard_control.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
#define __KEYBOARD_CONTROL_HPP_INCLUDED__

#include <memory>
#include <vector>
#include <string>
#include <vector>

#include <stdlib.h>
#include <termios.h>
Expand All @@ -27,7 +27,8 @@ class KeyboardControl
KeyboardControl() = delete;
KeyboardControl(const KeyboardControl&) = delete;

void operator()()
void
operator()()
{
std::cout << "Press <space> to stop/resume playing." << std::endl;
std::cout << "Press n/p to play next/previous station." << std::endl;
Expand Down
4 changes: 2 additions & 2 deletions src/m3u_playlist_decoder.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#ifndef __M3U_PLAYLIST_DECODER_HPP_INCLUDED__
#define __M3U_PLAYLIST_DECODER_HPP_INCLUDED__

#include <sstream>
#include <regex>
#include <iomanip>
#include <regex>
#include <sstream>

#include "playlist_decoder.hpp"

Expand Down
2 changes: 1 addition & 1 deletion src/notification.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#ifndef __NOTIFICATION_HPP_INCLUDED__
#define __NOTIFICATION_HPP_INCLUDED__

#include <string>
#include <sstream>
#include <string>

#include <gtkmm.h>
#include <libnotify/notify.h>
Expand Down
4 changes: 4 additions & 0 deletions src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Player::init(int argc, char** argv)
Glib::RefPtr<Gst::Bus> bus = playbin->get_bus();
bus->add_watch(sigc::mem_fun(*this, &Player::on_bus_message));

if (not playlist.init()) {
return false;
}

return true;
}

Expand Down
4 changes: 2 additions & 2 deletions src/player.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#ifndef __PLAYER_HPP_INCLUDED__
#define __PLAYER_HPP_INCLUDED__

#include <thread>
#include <memory>
#include <thread>

#include <glib.h>

Expand All @@ -13,8 +13,8 @@

#include <glibmm.h>

#include "playlist.hpp"
#include "event_manager.hpp"
#include "playlist.hpp"

namespace radiotray
{
Expand Down
102 changes: 89 additions & 13 deletions src/playlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<M3UPlaylistDecoder>());
decoders.push_back(std::make_shared<PLSPlaylistDecoder>());
decoders.push_back(std::make_shared<ASXPlaylistDecoder>());
decoders.push_back(std::make_shared<RAMPlaylistDecoder>());
decoders.push_back(std::make_shared<XSPFPlaylistDecoder>());
decoders.emplace(std::make_pair(PlaylistDecoderType::M3U_PLAYLIST_DECODER, std::make_shared<M3UPlaylistDecoder>()));
decoders.emplace(std::make_pair(PlaylistDecoderType::PLS_PLAYLIST_DECODER, std::make_shared<PLSPlaylistDecoder>()));
decoders.emplace(std::make_pair(PlaylistDecoderType::ASX_PLAYLIST_DECODER, std::make_shared<ASXPlaylistDecoder>()));
decoders.emplace(std::make_pair(PlaylistDecoderType::RAM_PLAYLIST_DECODER, std::make_shared<RAMPlaylistDecoder>()));
decoders.emplace(std::make_pair(PlaylistDecoderType::XSPF_PLAYLIST_DECODER, std::make_shared<XSPFPlaylistDecoder>()));
}

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<bool, MediaStreams>
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
}

Expand All @@ -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 = "<asx ";
if (strncasecmp(data.c_str(), kASXPlaylist.c_str(), kASXPlaylist.size()) == 0) {
return PlaylistDecoderType::ASX_PLAYLIST_DECODER;
}

return PlaylistDecoderType::UNKNOWN_PLAYLIST_DECODER;
}

size_t
Playlist::write_memory_cb(void* ptr, size_t size, size_t nmemb, void* data)
{
Expand Down
Loading

0 comments on commit 7f879a0

Please sign in to comment.