/* * Copyright (C) 2003-2015 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/TextInputStream.hxx" #include "DetachedSong.hxx" #include "tag/TagBuilder.hxx" #include "util/ASCII.hxx" #include "util/StringUtil.hxx" #include "util/DivideString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include #include static constexpr Domain pls_domain("pls"); static bool FindPlaylistSection(TextInputStream &is) { char *line; while ((line = is.ReadLine()) != nullptr) { line = Strip(line); if (StringEqualsCaseASCII(line, "[playlist]")) return true; } return false; } static bool ParsePls(TextInputStream &is, std::forward_list &songs) { assert(songs.empty()); if (!FindPlaylistSection(is)) return false; unsigned n_entries = 0; struct Entry { std::string file, title; int length; Entry():length(-1) {} }; static constexpr unsigned MAX_ENTRIES = 65536; std::vector entries; char *line; while ((line = is.ReadLine()) != nullptr) { line = Strip(line); if (*line == 0 || *line == ';') continue; if (*line == '[') /* another section starts; we only want [Playlist], so stop here */ break; const DivideString ds(line, '=', true); if (!ds.IsDefined()) continue; const char *const name = ds.GetFirst(); const char *const value = ds.GetSecond(); if (StringEqualsCaseASCII(name, "NumberOfEntries")) { n_entries = strtoul(value, nullptr, 10); if (n_entries == 0) /* empty file - nothing remains to be done */ return true; if (n_entries > MAX_ENTRIES) n_entries = MAX_ENTRIES; entries.resize(n_entries); } else if (StringEqualsCaseASCII(name, "File", 4)) { unsigned i = strtoul(name + 4, nullptr, 10); if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) { if (entries.size() < i) entries.resize(i); entries[i - 1].file = value; } } else if (StringEqualsCaseASCII(name, "Title", 5)) { unsigned i = strtoul(name + 5, nullptr, 10); if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) { if (entries.size() < i) entries.resize(i); entries[i - 1].title = value; } } else if (StringEqualsCaseASCII(name, "Length", 6)) { unsigned i = strtoul(name + 6, nullptr, 10); if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) { if (entries.size() < i) entries.resize(i); entries[i - 1].length = atoi(value); } } } if (n_entries == 0) /* no "NumberOfEntries" found */ return false; auto i = songs.before_begin(); for (const auto &entry : entries) { const char *uri = entry.file.c_str(); TagBuilder tag; if (!entry.title.empty()) tag.AddItem(TAG_TITLE, entry.title.c_str()); if (entry.length > 0) tag.SetDuration(SignedSongTime::FromS(entry.length)); i = songs.emplace_after(i, uri, tag.Commit()); } return true; } static bool ParsePls(InputStream &is, std::forward_list &songs) { TextInputStream tis(is); return ParsePls(tis, songs); } static SongEnumerator * pls_open_stream(InputStream &is) { std::forward_list songs; if (!ParsePls(is, songs)) return nullptr; 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, };