aboutsummaryrefslogtreecommitdiffstats
path: root/src/playlist/plugins
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.cxx186
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.cxx88
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx184
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.cxx153
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.cxx83
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.cxx164
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.cxx185
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.cxx401
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.cxx234
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.hxx25
18 files changed, 1903 insertions, 0 deletions
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx
new file mode 100644
index 000000000..3185a8144
--- /dev/null
+++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AsxPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct AsxParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<DetachedSong> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ENTRY,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "ref" element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ AsxParser()
+ :state(ROOT) {}
+
+};
+
+static void XMLCALL
+asx_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ if (StringEqualsCaseASCII(element_name, "entry")) {
+ parser->state = AsxParser::ENTRY;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case AsxParser::ENTRY:
+ if (StringEqualsCaseASCII(element_name, "ref")) {
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "href");
+ if (href != nullptr)
+ parser->location = href;
+ } else if (StringEqualsCaseASCII(element_name, "author"))
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag_type = TAG_ARTIST;
+ else if (StringEqualsCaseASCII(element_name, "title"))
+ parser->tag_type = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void XMLCALL
+asx_end_element(void *user_data, const XML_Char *element_name)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (StringEqualsCaseASCII(element_name, "entry")) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = AsxParser::ROOT;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void XMLCALL
+asx_char_data(void *user_data, const XML_Char *s, int len)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+asx_open_stream(InputStream &is)
+{
+ AsxParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(asx_start_element, asx_end_element);
+ expat.SetCharacterDataHandler(asx_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ nullptr
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ nullptr
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ "asx",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ asx_open_stream,
+
+ nullptr,
+ asx_suffixes,
+ asx_mime_types,
+};
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.hxx b/src/playlist/plugins/AsxPlaylistPlugin.hxx
new file mode 100644
index 000000000..63371be0f
--- /dev/null
+++ b/src/playlist/plugins/AsxPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX
+#define MPD_ASX_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx
new file mode 100644
index 000000000..b907d34d0
--- /dev/null
+++ b/src/playlist/plugins/CuePlaylistPlugin.cxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CuePlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
+#include "input/TextInputStream.hxx"
+
+#include <string>
+
+class CuePlaylist final : public SongEnumerator {
+ InputStream &is;
+ TextInputStream tis;
+ CueParser parser;
+
+ public:
+ CuePlaylist(InputStream &_is)
+ :is(_is), tis(is) {
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+cue_playlist_open_stream(InputStream &is)
+{
+ return new CuePlaylist(is);
+}
+
+DetachedSong *
+CuePlaylist::NextSong()
+{
+ DetachedSong *song = parser.Get();
+ if (song != nullptr)
+ return song;
+
+ const char *line;
+ while ((line = tis.ReadLine()) != nullptr) {
+ parser.Feed(line);
+ song = parser.Get();
+ if (song != nullptr)
+ return song;
+ }
+
+ parser.Finish();
+ return parser.Get();
+}
+
+static const char *const cue_playlist_suffixes[] = {
+ "cue",
+ nullptr
+};
+
+static const char *const cue_playlist_mime_types[] = {
+ "application/x-cue",
+ nullptr
+};
+
+const struct playlist_plugin cue_playlist_plugin = {
+ "cue",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ cue_playlist_open_stream,
+
+ nullptr,
+ cue_playlist_suffixes,
+ cue_playlist_mime_types,
+};
diff --git a/src/playlist/plugins/CuePlaylistPlugin.hxx b/src/playlist/plugins/CuePlaylistPlugin.hxx
new file mode 100644
index 000000000..4d833bfc2
--- /dev/null
+++ b/src/playlist/plugins/CuePlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX
+#define MPD_CUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin cue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
new file mode 100644
index 000000000..8baa11c03
--- /dev/null
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Playlist plugin that reads embedded cue sheets from the "CUESHEET"
+ * tag of a music file.
+ */
+
+#include "config.h"
+#include "EmbeddedCuePlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagId3.hxx"
+#include "tag/ApeTag.hxx"
+#include "DetachedSong.hxx"
+#include "TagFile.hxx"
+#include "fs/Traits.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "util/ASCII.hxx"
+
+#include <string.h>
+
+class EmbeddedCuePlaylist final : public SongEnumerator {
+public:
+ /**
+ * This is an override for the CUE's "FILE". An embedded CUE
+ * sheet must always point to the song file it is contained
+ * in.
+ */
+ std::string filename;
+
+ /**
+ * The value of the file's "CUESHEET" tag.
+ */
+ std::string cuesheet;
+
+ /**
+ * The offset of the next line within "cuesheet".
+ */
+ char *next;
+
+ CueParser *parser;
+
+public:
+ EmbeddedCuePlaylist()
+ :parser(nullptr) {
+ }
+
+ virtual ~EmbeddedCuePlaylist() {
+ delete parser;
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static void
+embcue_tag_pair(const char *name, const char *value, void *ctx)
+{
+ EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx;
+
+ if (playlist->cuesheet.empty() &&
+ StringEqualsCaseASCII(name, "cuesheet"))
+ playlist->cuesheet = value;
+}
+
+static const struct tag_handler embcue_tag_handler = {
+ nullptr,
+ nullptr,
+ embcue_tag_pair,
+};
+
+static SongEnumerator *
+embcue_playlist_open_uri(const char *uri,
+ gcc_unused Mutex &mutex,
+ gcc_unused Cond &cond)
+{
+ if (!PathTraitsUTF8::IsAbsolute(uri))
+ /* only local files supported */
+ return nullptr;
+
+ const auto path_fs = AllocatedPath::FromUTF8(uri);
+ if (path_fs.IsNull())
+ return nullptr;
+
+ const auto playlist = new EmbeddedCuePlaylist();
+
+ tag_file_scan(path_fs, embcue_tag_handler, playlist);
+ if (playlist->cuesheet.empty()) {
+ tag_ape_scan2(path_fs, &embcue_tag_handler, playlist);
+ if (playlist->cuesheet.empty())
+ tag_id3_scan(path_fs, &embcue_tag_handler, playlist);
+ }
+
+ if (playlist->cuesheet.empty()) {
+ /* no "CUESHEET" tag found */
+ delete playlist;
+ return nullptr;
+ }
+
+ playlist->filename = PathTraitsUTF8::GetBase(uri);
+
+ playlist->next = &playlist->cuesheet[0];
+ playlist->parser = new CueParser();
+
+ return playlist;
+}
+
+DetachedSong *
+EmbeddedCuePlaylist::NextSong()
+{
+ DetachedSong *song = parser->Get();
+ if (song != nullptr)
+ return song;
+
+ while (*next != 0) {
+ const char *line = next;
+ char *eol = strpbrk(next, "\r\n");
+ if (eol != nullptr) {
+ /* null-terminate the line */
+ *eol = 0;
+ next = eol + 1;
+ } else
+ /* last line; put the "next" pointer to the
+ end of the buffer */
+ next += strlen(line);
+
+ parser->Feed(line);
+ song = parser->Get();
+ if (song != nullptr) {
+ song->SetURI(filename);
+ return song;
+ }
+ }
+
+ parser->Finish();
+ song = parser->Get();
+ if (song != nullptr)
+ song->SetURI(filename);
+ return song;
+}
+
+static const char *const embcue_playlist_suffixes[] = {
+ /* a few codecs that are known to be supported; there are
+ probably many more */
+ "flac",
+ "mp3", "mp2",
+ "mp4", "mp4a", "m4b",
+ "ape",
+ "wv",
+ "ogg", "oga",
+ nullptr
+};
+
+const struct playlist_plugin embcue_playlist_plugin = {
+ "embcue",
+
+ nullptr,
+ nullptr,
+ embcue_playlist_open_uri,
+ nullptr,
+
+ nullptr,
+ embcue_playlist_suffixes,
+ nullptr,
+};
diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
new file mode 100644
index 000000000..5eedf3f13
--- /dev/null
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
+#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin embcue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..93316ca6c
--- /dev/null
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ExtM3uPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
+#include "input/TextInputStream.hxx"
+
+#include <string.h>
+#include <stdlib.h>
+
+class ExtM3uPlaylist final : public SongEnumerator {
+ TextInputStream tis;
+
+public:
+ ExtM3uPlaylist(InputStream &is)
+ :tis(is) {
+ }
+
+ bool CheckFirstLine() {
+ char *line = tis.ReadLine();
+ if (line == nullptr)
+ return false;
+
+ StripRight(line);
+ return strcmp(line, "#EXTM3U") == 0;
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+extm3u_open_stream(InputStream &is)
+{
+ ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is);
+
+ if (!playlist->CheckFirstLine()) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ delete playlist;
+ return nullptr;
+ }
+
+ return playlist;
+}
+
+/**
+ * Parse a EXTINF line.
+ *
+ * @param line the rest of the input line after the colon
+ */
+static Tag
+extm3u_parse_tag(const char *line)
+{
+ long duration;
+ char *endptr;
+ const char *name;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return Tag();
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = StripLeft(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return Tag();
+
+ TagBuilder tag;
+ tag.SetDuration(SignedSongTime::FromS(unsigned(duration)));
+
+ /* unfortunately, there is no real specification for the
+ EXTM3U format, so we must assume that the string after the
+ comma is opaque, and is just the song name*/
+ if (*name != 0)
+ tag.AddItem(TAG_NAME, name);
+
+ return tag.Commit();
+}
+
+DetachedSong *
+ExtM3uPlaylist::NextSong()
+{
+ Tag tag;
+ char *line_s;
+
+ do {
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
+ return nullptr;
+
+ StripRight(line_s);
+
+ if (StringStartsWith(line_s, "#EXTINF:")) {
+ tag = extm3u_parse_tag(line_s + 8);
+ continue;
+ }
+
+ line_s = StripLeft(line_s);
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return new DetachedSong(line_s, std::move(tag));
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ "m3u8",
+ nullptr
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ nullptr
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ "extm3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ extm3u_open_stream,
+
+ nullptr,
+ extm3u_suffixes,
+ extm3u_mime_types,
+};
diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..5743ded43
--- /dev/null
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
+#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..0428d291a
--- /dev/null
+++ b/src/playlist/plugins/M3uPlaylistPlugin.cxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "M3uPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "util/StringUtil.hxx"
+#include "input/TextInputStream.hxx"
+
+class M3uPlaylist final : public SongEnumerator {
+ TextInputStream tis;
+
+public:
+ M3uPlaylist(InputStream &is)
+ :tis(is) {
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+m3u_open_stream(InputStream &is)
+{
+ return new M3uPlaylist(is);
+}
+
+DetachedSong *
+M3uPlaylist::NextSong()
+{
+ char *line_s;
+
+ do {
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
+ return nullptr;
+
+ line_s = Strip(line_s);
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return new DetachedSong(line_s);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ "m3u8",
+ nullptr
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ nullptr
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ "m3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ m3u_open_stream,
+
+ nullptr,
+ m3u_suffixes,
+ m3u_mime_types,
+};
diff --git a/src/playlist/plugins/M3uPlaylistPlugin.hxx b/src/playlist/plugins/M3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..f1ad14069
--- /dev/null
+++ b/src/playlist/plugins/M3uPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX
+#define MPD_M3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx
new file mode 100644
index 000000000..f7724f522
--- /dev/null
+++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlsPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "input/InputStream.hxx"
+#include "DetachedSong.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+
+#include <string>
+#include <stdio.h>
+
+#include <stdio.h>
+
+static constexpr Domain pls_domain("pls");
+
+static void
+pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
+{
+ gchar *value;
+ GError *error = nullptr;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ FormatError(pls_domain,
+ "Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = nullptr;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = nullptr;
+ }
+ }
+
+ for (; num_entries > 0; --num_entries) {
+ char key[64];
+ sprintf(key, "File%u", num_entries);
+ char *uri = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
+ key, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ TagBuilder tag;
+
+ sprintf(key, "Title%u", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ nullptr);
+ if (value != nullptr)
+ tag.AddItem(TAG_TITLE, value);
+
+ g_free(value);
+
+ sprintf(key, "Length%u", num_entries);
+ int length = g_key_file_get_integer(keyfile, "playlist", key,
+ nullptr);
+ if (length > 0)
+ tag.SetDuration(SignedSongTime::FromS(length));
+
+ songs.emplace_front(uri, tag.Commit());
+ g_free(uri);
+ }
+
+}
+
+static SongEnumerator *
+pls_open_stream(InputStream &is)
+{
+ GError *error = nullptr;
+ Error error2;
+
+ std::string kf_data;
+
+ do {
+ char buffer[1024];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
+ if (nbytes == 0) {
+ if (error2.IsDefined()) {
+ LogError(error2);
+ return nullptr;
+ }
+
+ break;
+ }
+
+ kf_data.append(buffer, nbytes);
+ /* Limit to 64k */
+ } while (kf_data.length() < 65536);
+
+ if (kf_data.empty()) {
+ LogWarning(pls_domain, "KeyFile parser failed: No Data");
+ return nullptr;
+ }
+
+ GKeyFile *keyfile = g_key_file_new();
+ if (!g_key_file_load_from_data(keyfile,
+ kf_data.data(), kf_data.length(),
+ G_KEY_FILE_NONE, &error)) {
+ FormatError(pls_domain,
+ "KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return nullptr;
+ }
+
+ std::forward_list<DetachedSong> songs;
+ pls_parser(keyfile, songs);
+ g_key_file_free(keyfile);
+
+ return new MemorySongEnumerator(std::move(songs));
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ nullptr
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ nullptr
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ "pls",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ pls_open_stream,
+
+ nullptr,
+ pls_suffixes,
+ pls_mime_types,
+};
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.hxx b/src/playlist/plugins/PlsPlaylistPlugin.hxx
new file mode 100644
index 000000000..1a3f33873
--- /dev/null
+++ b/src/playlist/plugins/PlsPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX
+#define MPD_PLS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
new file mode 100644
index 000000000..6f9aad54b
--- /dev/null
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RssPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct RssParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<DetachedSong> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ITEM,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "enclosure"
+ * element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ RssParser()
+ :state(ROOT) {}
+};
+
+static void XMLCALL
+rss_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ parser->state = RssParser::ITEM;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "enclosure")) {
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "url");
+ if (href != nullptr)
+ parser->location = href;
+ } else if (StringEqualsCaseASCII(element_name, "title"))
+ parser->tag_type = TAG_TITLE;
+ else if (StringEqualsCaseASCII(element_name, "itunes:author"))
+ parser->tag_type = TAG_ARTIST;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_end_element(void *user_data, const XML_Char *element_name)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = RssParser::ROOT;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_char_data(void *user_data, const XML_Char *s, int len)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+rss_open_stream(InputStream &is)
+{
+ RssParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(rss_start_element, rss_end_element);
+ expat.SetCharacterDataHandler(rss_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const rss_suffixes[] = {
+ "rss",
+ nullptr
+};
+
+static const char *const rss_mime_types[] = {
+ "application/rss+xml",
+ "text/xml",
+ nullptr
+};
+
+const struct playlist_plugin rss_playlist_plugin = {
+ "rss",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ rss_open_stream,
+
+ nullptr,
+ rss_suffixes,
+ rss_mime_types,
+};
diff --git a/src/playlist/plugins/RssPlaylistPlugin.hxx b/src/playlist/plugins/RssPlaylistPlugin.hxx
new file mode 100644
index 000000000..a00a5a898
--- /dev/null
+++ b/src/playlist/plugins/RssPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX
+#define MPD_RSS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin rss_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
new file mode 100644
index 000000000..ec4d240a5
--- /dev/null
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SoundCloudPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "config/ConfigData.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+#include <yajl/yajl_parse.h>
+
+#include <string>
+
+#include <string.h>
+
+static struct {
+ std::string apikey;
+} soundcloud_config;
+
+static constexpr Domain soundcloud_domain("soundcloud");
+
+static bool
+soundcloud_init(const config_param &param)
+{
+ // APIKEY for MPD application, registered under DarkFox' account.
+ soundcloud_config.apikey = param.GetBlockValue("apikey", "a25e51780f7f86af0afa91f241d091f8");
+ if (soundcloud_config.apikey.empty()) {
+ LogDebug(soundcloud_domain,
+ "disabling the soundcloud playlist plugin "
+ "because API key is not set");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Construct a full soundcloud resolver URL from the given fragment.
+ * @param uri uri of a soundcloud page (or just the path)
+ * @return Constructed URL. Must be freed with g_free.
+ */
+static char *
+soundcloud_resolve(const char* uri)
+{
+ char *u, *ru;
+
+ if (StringStartsWith(uri, "https://")) {
+ u = g_strdup(uri);
+ } else if (StringStartsWith(uri, "soundcloud.com")) {
+ u = g_strconcat("https://", uri, nullptr);
+ } else {
+ /* assume it's just a path on soundcloud.com */
+ u = g_strconcat("https://soundcloud.com/", uri, nullptr);
+ }
+
+ ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=",
+ u, "&client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ g_free(u);
+
+ return ru;
+}
+
+/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */
+
+enum key {
+ Duration,
+ Title,
+ Stream_URL,
+ Other,
+};
+
+const char* key_str[] = {
+ "duration",
+ "title",
+ "stream_url",
+ nullptr,
+};
+
+struct parse_data {
+ int key;
+ char* stream_url;
+ long duration;
+ char* title;
+ int got_url; /* nesting level of last stream_url */
+
+ std::forward_list<DetachedSong> songs;
+};
+
+static int
+handle_integer(void *ctx,
+ long
+#ifndef HAVE_YAJL1
+ long
+#endif
+ intval)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ switch (data->key) {
+ case Duration:
+ data->duration = intval;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int
+handle_string(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+ const char *s = (const char *) stringval;
+
+ switch (data->key) {
+ case Title:
+ g_free(data->title);
+ data->title = g_strndup(s, stringlen);
+ break;
+ case Stream_URL:
+ g_free(data->stream_url);
+ data->stream_url = g_strndup(s, stringlen);
+ data->got_url = 1;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int
+handle_mapkey(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ int i;
+ data->key = Other;
+
+ for (i = 0; i < Other; ++i) {
+ if (memcmp((const char *)stringval, key_str[i], stringlen) == 0) {
+ data->key = i;
+ break;
+ }
+ }
+
+ return 1;
+}
+
+static int
+handle_start_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 0)
+ data->got_url++;
+
+ return 1;
+}
+
+static int
+handle_end_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 1) {
+ data->got_url--;
+ return 1;
+ }
+
+ if (data->got_url == 0)
+ return 1;
+
+ /* got_url == 1, track finished, make it into a song */
+ data->got_url = 0;
+
+ char *u = g_strconcat(data->stream_url, "?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+
+ TagBuilder tag;
+ tag.SetDuration(SignedSongTime::FromMS(data->duration));
+ if (data->title != nullptr)
+ tag.AddItem(TAG_NAME, data->title);
+
+ data->songs.emplace_front(u, tag.Commit());
+ g_free(u);
+
+ return 1;
+}
+
+static yajl_callbacks parse_callbacks = {
+ nullptr,
+ nullptr,
+ handle_integer,
+ nullptr,
+ nullptr,
+ handle_string,
+ handle_start_map,
+ handle_mapkey,
+ handle_end_map,
+ nullptr,
+ nullptr,
+};
+
+/**
+ * Read JSON data and parse it using the given YAJL parser.
+ * @param url URL of the JSON data.
+ * @param hand YAJL parser handle.
+ * @return -1 on error, 0 on success.
+ */
+static int
+soundcloud_parse_json(const char *url, yajl_handle hand,
+ Mutex &mutex, Cond &cond)
+{
+ Error error;
+ InputStream *input_stream = InputStream::OpenReady(url, mutex, cond,
+ error);
+ if (input_stream == nullptr) {
+ if (error.IsDefined())
+ LogError(error);
+ return -1;
+ }
+
+ mutex.lock();
+
+ yajl_status stat;
+ int done = 0;
+
+ while (!done) {
+ char buffer[4096];
+ unsigned char *ubuffer = (unsigned char *)buffer;
+ const size_t nbytes =
+ input_stream->Read(buffer, sizeof(buffer), error);
+ if (nbytes == 0) {
+ if (error.IsDefined())
+ LogError(error);
+
+ if (input_stream->IsEOF()) {
+ done = true;
+ } else {
+ mutex.unlock();
+ delete input_stream;
+ return -1;
+ }
+ }
+
+ if (done) {
+#ifdef HAVE_YAJL1
+ stat = yajl_parse_complete(hand);
+#else
+ stat = yajl_complete_parse(hand);
+#endif
+ } else
+ stat = yajl_parse(hand, ubuffer, nbytes);
+
+ if (stat != yajl_status_ok
+#ifdef HAVE_YAJL1
+ && stat != yajl_status_insufficient_data
+#endif
+ )
+ {
+ unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
+ LogError(soundcloud_domain, (const char *)str);
+ yajl_free_error(hand, str);
+ break;
+ }
+ }
+
+ mutex.unlock();
+ delete input_stream;
+
+ return 0;
+}
+
+/**
+ * Parse a soundcloud:// URL and create a playlist.
+ * @param uri A soundcloud URL. Accepted forms:
+ * soundcloud://track/<track-id>
+ * soundcloud://playlist/<playlist-id>
+ * soundcloud://url/<url or path of soundcloud page>
+ */
+static SongEnumerator *
+soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
+{
+ assert(memcmp(uri, "soundcloud://", 13) == 0);
+ uri += 13;
+
+ char *u = nullptr;
+ if (memcmp(uri, "track/", 6) == 0) {
+ const char *rest = uri + 6;
+ u = g_strconcat("https://api.soundcloud.com/tracks/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "playlist/", 9) == 0) {
+ const char *rest = uri + 9;
+ u = g_strconcat("https://api.soundcloud.com/playlists/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "user/", 5) == 0) {
+ const char *rest = uri + 5;
+ u = g_strconcat("https://api.soundcloud.com/users/",
+ rest, "/tracks.json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "search/", 7) == 0) {
+ const char *rest = uri + 7;
+ u = g_strconcat("https://api.soundcloud.com/tracks.json?q=",
+ rest, "&client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "url/", 4) == 0) {
+ const char *rest = uri + 4;
+ /* Translate to soundcloud resolver call. libcurl will automatically
+ follow the redirect to the right resource. */
+ u = soundcloud_resolve(rest);
+ }
+
+ if (u == nullptr) {
+ LogWarning(soundcloud_domain, "unknown soundcloud URI");
+ return nullptr;
+ }
+
+ struct parse_data data;
+ data.got_url = 0;
+ data.title = nullptr;
+ data.stream_url = nullptr;
+#ifdef HAVE_YAJL1
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr,
+ &data);
+#else
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
+#endif
+
+ int ret = soundcloud_parse_json(u, hand, mutex, cond);
+
+ g_free(u);
+ yajl_free(hand);
+ g_free(data.title);
+ g_free(data.stream_url);
+
+ if (ret == -1)
+ return nullptr;
+
+ data.songs.reverse();
+ return new MemorySongEnumerator(std::move(data.songs));
+}
+
+static const char *const soundcloud_schemes[] = {
+ "soundcloud",
+ nullptr
+};
+
+const struct playlist_plugin soundcloud_playlist_plugin = {
+ "soundcloud",
+
+ soundcloud_init,
+ nullptr,
+ soundcloud_open_uri,
+ nullptr,
+
+ soundcloud_schemes,
+ nullptr,
+ nullptr,
+};
+
+
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
new file mode 100644
index 000000000..b355b477a
--- /dev/null
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin soundcloud_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx
new file mode 100644
index 000000000..5b6010b53
--- /dev/null
+++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "XspfPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+static constexpr Domain xspf_domain("xspf");
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct XspfParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<DetachedSong> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, PLAYLIST, TRACKLIST, TRACK,
+ LOCATION,
+ } state;
+
+ /**
+ * The current tag within the "track" element. This is only
+ * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "location" element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ XspfParser()
+ :state(ROOT) {}
+};
+
+static void XMLCALL
+xspf_start_element(void *user_data, const XML_Char *element_name,
+ gcc_unused const XML_Char **atts)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = XspfParser::PLAYLIST;
+
+ break;
+
+ case XspfParser::PLAYLIST:
+ if (strcmp(element_name, "trackList") == 0)
+ parser->state = XspfParser::TRACKLIST;
+
+ break;
+
+ case XspfParser::TRACKLIST:
+ if (strcmp(element_name, "track") == 0) {
+ parser->state = XspfParser::TRACK;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case XspfParser::TRACK:
+ if (strcmp(element_name, "location") == 0)
+ parser->state = XspfParser::LOCATION;
+ else if (strcmp(element_name, "title") == 0)
+ parser->tag_type = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag_type = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag_type = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag_type = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag_type = TAG_TRACK;
+
+ break;
+
+ case XspfParser::LOCATION:
+ break;
+ }
+}
+
+static void XMLCALL
+xspf_end_element(void *user_data, const XML_Char *element_name)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ break;
+
+ case XspfParser::PLAYLIST:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = XspfParser::ROOT;
+
+ break;
+
+ case XspfParser::TRACKLIST:
+ if (strcmp(element_name, "tracklist") == 0)
+ parser->state = XspfParser::PLAYLIST;
+
+ break;
+
+ case XspfParser::TRACK:
+ if (strcmp(element_name, "track") == 0) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = XspfParser::TRACKLIST;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case XspfParser::LOCATION:
+ parser->state = XspfParser::TRACK;
+ break;
+ }
+}
+
+static void XMLCALL
+xspf_char_data(void *user_data, const XML_Char *s, int len)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ case XspfParser::PLAYLIST:
+ case XspfParser::TRACKLIST:
+ break;
+
+ case XspfParser::TRACK:
+ if (!parser->location.empty() &&
+ parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+
+ case XspfParser::LOCATION:
+ parser->location.assign(s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+xspf_open_stream(InputStream &is)
+{
+ XspfParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(xspf_start_element, xspf_end_element);
+ expat.SetCharacterDataHandler(xspf_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ nullptr
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ nullptr
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ "xspf",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ xspf_open_stream,
+
+ nullptr,
+ xspf_suffixes,
+ xspf_mime_types,
+};
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.hxx b/src/playlist/plugins/XspfPlaylistPlugin.hxx
new file mode 100644
index 000000000..6b08a6be6
--- /dev/null
+++ b/src/playlist/plugins/XspfPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX
+#define MPD_XSPF_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif