Initial commit.

This commit is contained in:
Jonas Kvinge
2018-02-27 18:06:05 +01:00
parent 85d9664df7
commit b2b1ba7abe
1393 changed files with 177311 additions and 1 deletions

View File

@@ -0,0 +1,69 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "asxiniparser.h"
#include "core/logging.h"
#include <QTextStream>
#include <QtDebug>
AsxIniParser::AsxIniParser(CollectionBackendInterface *collection, QObject *parent)
: ParserBase(collection, parent) {}
bool AsxIniParser::TryMagic(const QByteArray &data) const {
return data.toLower().contains("[reference]");
}
SongList AsxIniParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const {
SongList ret;
while (!device->atEnd()) {
QString line = QString::fromUtf8(device->readLine()).trimmed();
int equals = line.indexOf('=');
QString key = line.left(equals).toLower();
QString value = line.mid(equals + 1);
if (key.startsWith("ref")) {
Song song = LoadSong(value, 0, dir);
if (song.is_valid()) {
ret << song;
}
}
}
return ret;
}
void AsxIniParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const {
QTextStream s(device);
s << "[Reference]" << endl;
int n = 1;
for (const Song &song : songs) {
s << "Ref" << n << "=" << URLOrFilename(song.url(), dir, path_type) << endl;
++n;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ASXINIPARSER_H
#define ASXINIPARSER_H
#include "config.h"
#include "parserbase.h"
class AsxIniParser : public ParserBase {
Q_OBJECT
public:
AsxIniParser(CollectionBackendInterface *collection, QObject *parent = nullptr);
QString name() const { return "ASX/INI"; }
QStringList file_extensions() const { return QStringList() << "asxini"; }
bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const;
};
#endif // ASXINIPARSER_H

View File

@@ -0,0 +1,155 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "asxparser.h"
#include "core/utilities.h"
#include <QBuffer>
#include <QDomDocument>
#include <QFile>
#include <QIODevice>
#include <QRegExp>
#include <QUrl>
#include <QXmlStreamReader>
#include <QtDebug>
ASXParser::ASXParser(CollectionBackendInterface *collection, QObject *parent)
: XMLParser(collection, parent) {}
SongList ASXParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const {
// We have to load everything first so we can munge the "XML".
QByteArray data = device->readAll();
// (thanks Amarok...)
// ASX looks a lot like xml, but doesn't require tags to be case sensitive,
// meaning we have to accept things like: <Abstract>...</abstract>
// We use a dirty way to achieve this: we make all tags lower case
QRegExp ex("(<[/]?[^>]*[A-Z]+[^>]*>)");
ex.setCaseSensitivity(Qt::CaseInsensitive);
int index = 0;
while ((index = ex.indexIn(data, index)) != -1) {
data.replace(ex.cap(1).toLocal8Bit(), ex.cap(1).toLower().toLocal8Bit());
index += ex.matchedLength();
}
// Some playlists have unescaped & characters in URLs :(
ex.setPattern("(href\\s*=\\s*\")([^\"]+)\"");
index = 0;
while ((index = ex.indexIn(data, index)) != -1) {
QString url = ex.cap(2);
url.replace(QRegExp("&(?!amp;|quot;|apos;|lt;|gt;)"), "&amp;");
QByteArray replacement = QString(ex.cap(1) + url + "\"").toLocal8Bit();
data.replace(ex.cap(0).toLocal8Bit(), replacement);
index += replacement.length();
}
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
SongList ret;
QXmlStreamReader reader(&buffer);
if (!Utilities::ParseUntilElement(&reader, "asx")) {
return ret;
}
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, "entry")) {
Song song = ParseTrack(&reader, dir);
if (song.is_valid()) {
ret << song;
}
}
return ret;
}
Song ASXParser::ParseTrack(QXmlStreamReader *reader, const QDir &dir) const {
QString title, artist, album, ref;
while (!reader->atEnd()) {
QXmlStreamReader::TokenType type = reader->readNext();
switch (type) {
case QXmlStreamReader::StartElement: {
QStringRef name = reader->name();
if (name == "ref") {
ref = reader->attributes().value("href").toString();
} else if (name == "title") {
title = reader->readElementText();
} else if (name == "author") {
artist = reader->readElementText();
}
break;
}
case QXmlStreamReader::EndElement: {
if (reader->name() == "entry") {
goto return_song;
}
break;
}
default:
break;
}
}
return_song:
Song song = LoadSong(ref, 0, dir);
// Override metadata with what was in the playlist
song.set_title(title);
song.set_artist(artist);
song.set_album(album);
return song;
}
void ASXParser::Save(const SongList &songs, QIODevice *device, const QDir&, Playlist::Path path_type) const {
QXmlStreamWriter writer(device);
writer.setAutoFormatting(true);
writer.setAutoFormattingIndent(2);
writer.writeStartDocument();
{
StreamElement asx("asx", &writer);
writer.writeAttribute("version", "3.0");
for (const Song &song : songs) {
StreamElement entry("entry", &writer);
writer.writeTextElement("title", song.title());
{
StreamElement ref("ref", &writer);
writer.writeAttribute("href", song.url().toString());
}
if (!song.artist().isEmpty()) {
writer.writeTextElement("author", song.artist());
}
}
}
writer.writeEndDocument();
}
bool ASXParser::TryMagic(const QByteArray &data) const {
return data.toLower().contains("<asx");
}

View File

@@ -0,0 +1,46 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ASXPARSER_H
#define ASXPARSER_H
#include "config.h"
#include "xmlparser.h"
class ASXParser : public XMLParser {
Q_OBJECT
public:
ASXParser(CollectionBackendInterface *collection, QObject *parent = nullptr);
QString name() const { return "ASX"; }
QStringList file_extensions() const { return QStringList() << "asx"; }
bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const;
private:
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir) const;
};
#endif

View File

@@ -0,0 +1,374 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "cueparser.h"
#include "core/logging.h"
#include "core/timeconstants.h"
#include <QBuffer>
#include <QDateTime>
#include <QFileInfo>
#include <QStringBuilder>
#include <QRegExp>
#include <QTextCodec>
#include <QTextStream>
#include <QtDebug>
const char *CueParser::kFileLineRegExp = "(\\S+)\\s+(?:\"([^\"]+)\"|(\\S+))\\s*(?:\"([^\"]+)\"|(\\S+))?";
const char *CueParser::kIndexRegExp = "(\\d{2,3}):(\\d{2}):(\\d{2})";
const char *CueParser::kPerformer = "performer";
const char *CueParser::kTitle = "title";
const char *CueParser::kSongWriter = "songwriter";
const char *CueParser::kFile = "file";
const char *CueParser::kTrack = "track";
const char *CueParser::kIndex = "index";
const char *CueParser::kAudioTrackType = "audio";
const char *CueParser::kRem = "rem";
const char *CueParser::kGenre = "genre";
const char *CueParser::kDate = "date";
const char *CueParser::kDisc = "discnumber";
CueParser::CueParser(CollectionBackendInterface *collection, QObject *parent)
: ParserBase(collection, parent) {}
SongList CueParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const {
SongList ret;
QTextStream text_stream(device);
text_stream.setCodec(QTextCodec::codecForUtfText(device->peek(1024), QTextCodec::codecForName("UTF-8")));
QString dir_path = dir.absolutePath();
// read the first line already
QString line = text_stream.readLine();
QList<CueEntry> entries;
int files = 0;
// -- whole file
while (!text_stream.atEnd()) {
QString album_artist;
QString album;
QString album_composer;
QString file;
QString file_type;
QString genre;
QString date;
QString disc;
// -- FILE section
do {
QStringList splitted = SplitCueLine(line);
// uninteresting or incorrect line
if (splitted.size() < 2) {
continue;
}
QString line_name = splitted[0].toLower();
QString line_value = splitted[1];
// PERFORMER
if (line_name == kPerformer) {
album_artist = line_value;
// TITLE
}
else if (line_name == kTitle) {
album = line_value;
// SONGWRITER
}
else if (line_name == kSongWriter) {
album_composer = line_value;
// FILE
}
else if (line_name == kFile) {
file = QDir::isAbsolutePath(line_value) ? line_value : dir.absoluteFilePath(line_value);
if (splitted.size() > 2) {
file_type = splitted[2];
}
// REM
}
else if (line_name == kRem) {
if (splitted.size() < 3) {
break;
}
// REM GENRE
if (line_value.toLower() == kGenre) {
genre = splitted[2];
// REM DATE
} else if (line_value.toLower() == kDate) {
date = splitted[2];
// REM DISC
} else if (line_value.toLower() == kDisc) {
disc = splitted[2];
}
// end of the header -> go into the track mode
} else if (line_name == kTrack) {
files++;
break;
}
// just ignore the rest of possible field types for now...
} while (!(line = text_stream.readLine()).isNull());
if(line.isNull()) {
qLog(Warning) << "the .cue file from " << dir_path << " defines no tracks!";
return ret;
}
// if this is a data file, all of it's tracks will be ignored
bool valid_file = file_type.compare("BINARY", Qt::CaseInsensitive) && file_type.compare("MOTOROLA", Qt::CaseInsensitive);
QString track_type;
QString index;
QString artist;
QString composer;
QString title;
// TRACK section
do {
QStringList splitted = SplitCueLine(line);
// uninteresting or incorrect line
if (splitted.size() < 2) {
continue;
}
QString line_name = splitted[0].toLower();
QString line_value = splitted[1];
QString line_additional = splitted.size() > 2 ? splitted[2].toLower() : "";
if (line_name == kTrack) {
// the beginning of another track's definition - we're saving the current one
// for later (if it's valid of course)
// please note that the same code is repeated just after this 'do-while' loop
if(valid_file && !index.isEmpty() && (track_type.isEmpty() || track_type == kAudioTrackType)) {
entries.append(CueEntry(file, index, title, artist, album_artist, album, composer, album_composer, genre, date, disc));
}
// clear the state
track_type = index = artist = title = "";
if (!line_additional.isEmpty()) {
track_type = line_additional;
}
}
else if (line_name == kIndex) {
// we need the index's position field
if (!line_additional.isEmpty()) {
// if there's none "01" index, we'll just take the first one
// also, we'll take the "01" index even if it's the last one
if (line_value == "01" || index.isEmpty()) {
index = line_additional;
}
}
}
else if (line_name == kPerformer) {
artist = line_value;
}
else if (line_name == kTitle) {
title = line_value;
}
else if (line_name == kSongWriter) {
composer = line_value;
// end of track's for the current file -> parse next one
}
else if (line_name == kFile) {
break;
}
// just ignore the rest of possible field types for now...
} while (!(line = text_stream.readLine()).isNull());
// we didn't add the last song yet...
if(valid_file && !index.isEmpty() && (track_type.isEmpty() || track_type == kAudioTrackType)) {
entries.append(CueEntry(file, index, title, artist, album_artist, album, composer, album_composer, genre, date, disc));
}
}
QDateTime cue_mtime = QFileInfo(playlist_path).lastModified();
// finalize parsing songs
for (int i = 0; i < entries.length(); i++) {
CueEntry entry = entries.at(i);
Song song = LoadSong(entry.file, IndexToMarker(entry.index), dir);
// cue song has mtime equal to qMax(media_file_mtime, cue_sheet_mtime)
if (cue_mtime.isValid()) {
song.set_mtime(qMax(cue_mtime.toTime_t(), song.mtime()));
}
song.set_cue_path(playlist_path);
// overwrite the stuff, we may have read from the file or collection, using
// the current .cue metadata
// set track number only in single-file mode
if (files == 1) {
song.set_track(i + 1);
}
// the last TRACK for every FILE gets it's 'end' marker from the media file's
// length
if(i + 1 < entries.size() && entries.at(i).file == entries.at(i + 1).file) {
// incorrect indices?
if (!UpdateSong(entry, entries.at(i + 1).index, &song)) {
continue;
}
} else {
// incorrect index?
if (!UpdateLastSong(entry, &song)) {
continue;
}
}
ret << song;
}
return ret;
}
// This and the kFileLineRegExp do most of the "dirty" work, namely: splitting the raw .cue
// line into logical parts and getting rid of all the unnecessary whitespaces and quoting.
QStringList CueParser::SplitCueLine(const QString &line) const {
QRegExp line_regexp(kFileLineRegExp);
if (!line_regexp.exactMatch(line.trimmed())) {
return QStringList();
}
// let's remove the empty entries while we're at it
return line_regexp.capturedTexts().filter(QRegExp(".+")).mid(1, -1);
}
// Updates the song with data from the .cue entry. This one mustn't be used for the
// last song in the .cue file.
bool CueParser::UpdateSong(const CueEntry &entry, const QString &next_index, Song *song) const {
qint64 beginning = IndexToMarker(entry.index);
qint64 end = IndexToMarker(next_index);
// incorrect indices (we won't be able to calculate beginning or end)
if (beginning == -1 || end == -1) {
return false;
}
// believe the CUE: Init() forces validity
song->Init(entry.title, entry.PrettyArtist(), entry.album, beginning, end);
song->set_albumartist(entry.album_artist);
song->set_composer(entry.PrettyComposer());
song->set_genre(entry.genre);
song->set_year(entry.date.toInt());
song->set_disc(entry.disc.toInt());
return true;
}
// Updates the song with data from the .cue entry. This one must be used only for the
// last song in the .cue file.
bool CueParser::UpdateLastSong(const CueEntry &entry, Song *song) const {
qint64 beginning = IndexToMarker(entry.index);
// incorrect index (we won't be able to calculate beginning)
if (beginning == -1) {
return false;
}
// believe the CUE and force validity (like UpdateSong() does)
song->set_valid(true);
song->set_title(entry.title);
song->set_artist(entry.PrettyArtist());
song->set_album(entry.album);
song->set_albumartist(entry.album_artist);
song->set_genre(entry.genre);
song->set_year(entry.date.toInt());
song->set_composer(entry.PrettyComposer());
song->set_disc(entry.disc.toInt());
// we don't do anything with the end here because it's already set to
// the end of the media file (if it exists)
song->set_beginning_nanosec(beginning);
return true;
}
qint64 CueParser::IndexToMarker(const QString &index) const {
QRegExp index_regexp(kIndexRegExp);
if (!index_regexp.exactMatch(index)) {
return -1;
}
QStringList splitted = index_regexp.capturedTexts().mid(1, -1);
qlonglong frames = splitted.at(0).toLongLong() * 60 * 75 + splitted.at(1).toLongLong() * 75 + splitted.at(2).toLongLong();
return (frames * kNsecPerSec) / 75;
}
void CueParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const {
// TODO
}
// Looks for a track starting with one of the .cue's keywords.
bool CueParser::TryMagic(const QByteArray &data) const {
QStringList splitted = QString::fromUtf8(data.constData()).split('\n');
for (int i = 0; i < splitted.length(); i++) {
QString line = splitted.at(i).trimmed();
if (line.startsWith(kPerformer, Qt::CaseInsensitive) ||
line.startsWith(kTitle, Qt::CaseInsensitive) ||
line.startsWith(kFile, Qt::CaseInsensitive) ||
line.startsWith(kTrack, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,107 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CUEPARSER_H
#define CUEPARSER_H
#include "config.h"
#include "parserbase.h"
#include <QRegExp>
// This parser will try to detect the real encoding of a .cue file but there's
// a great chance it will fail so it's probably best to assume that the parser
// is UTF compatible only.
class CueParser : public ParserBase {
Q_OBJECT
public:
static const char *kFileLineRegExp;
static const char *kIndexRegExp;
static const char *kPerformer;
static const char *kTitle;
static const char *kSongWriter;
static const char *kFile;
static const char *kTrack;
static const char *kIndex;
static const char *kAudioTrackType;
static const char *kRem;
static const char *kGenre;
static const char *kDate;
static const char *kDisc;
CueParser(CollectionBackendInterface *library, QObject *parent = nullptr);
QString name() const { return "CUE"; }
QStringList file_extensions() const { return QStringList() << "cue"; }
QString mime_type() const { return "application/x-cue"; }
bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const;
private:
// A single TRACK entry in .cue file.
struct CueEntry {
QString file;
QString index;
QString title;
QString artist;
QString album_artist;
QString album;
QString composer;
QString album_composer;
QString genre;
QString date;
QString disc;
QString PrettyArtist() const { return artist.isEmpty() ? album_artist : artist; }
QString PrettyComposer() const { return composer.isEmpty() ? album_composer : composer; }
CueEntry(QString &file, QString &index, QString &title, QString &artist, QString &album_artist, QString &album, QString &composer, QString &album_composer, QString &genre, QString &date, QString &disc) {
this->file = file;
this->index = index;
this->title = title;
this->artist = artist;
this->album_artist = album_artist;
this->album = album;
this->composer = composer;
this->album_composer = album_composer;
this->genre = genre;
this->date = date;
this->disc = disc;
}
};
bool UpdateSong(const CueEntry &entry, const QString &next_index, Song *song) const;
bool UpdateLastSong(const CueEntry &entry, Song *song) const;
QStringList SplitCueLine(const QString &line) const;
qint64 IndexToMarker(const QString &index) const;
};
#endif // CUEPARSER_H

View File

@@ -0,0 +1,141 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "m3uparser.h"
#include "core/logging.h"
#include "core/timeconstants.h"
#include "playlist/playlist.h"
#include <QBuffer>
#include <QtDebug>
M3UParser::M3UParser(CollectionBackendInterface *collection, QObject *parent)
: ParserBase(collection, parent) {}
SongList M3UParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const {
SongList ret;
M3UType type = STANDARD;
Metadata current_metadata;
QString data = QString::fromUtf8(device->readAll());
data.replace('\r', '\n');
data.replace("\n\n", "\n");
QByteArray bytes = data.toUtf8();
QBuffer buffer(&bytes);
buffer.open(QIODevice::ReadOnly);
QString line = QString::fromUtf8(buffer.readLine()).trimmed();
if (line.startsWith("#EXTM3U")) {
// This is in extended M3U format.
type = EXTENDED;
line = QString::fromUtf8(buffer.readLine()).trimmed();
}
forever {
if (line.startsWith('#')) {
// Extended info or comment.
if (type == EXTENDED && line.startsWith("#EXT")) {
if (!ParseMetadata(line, &current_metadata)) {
qLog(Warning) << "Failed to parse metadata: " << line;
}
}
}
else if (!line.isEmpty()) {
Song song = LoadSong(line, 0, dir);
if (!current_metadata.title.isEmpty()) {
song.set_title(current_metadata.title);
}
if (!current_metadata.artist.isEmpty()) {
song.set_artist(current_metadata.artist);
}
if (current_metadata.length > 0) {
song.set_length_nanosec(current_metadata.length);
}
ret << song;
current_metadata = Metadata();
}
if (buffer.atEnd()) {
break;
}
line = QString::fromUtf8(buffer.readLine()).trimmed();
}
return ret;
}
bool M3UParser::ParseMetadata(const QString &line, M3UParser::Metadata *metadata) const {
// Extended info, eg.
// #EXTINF:123,Sample Artist - Sample title
QString info = line.section(':', 1);
QString l = info.section(',', 0, 0);
bool ok = false;
int length = l.toInt(&ok);
if (!ok) {
return false;
}
metadata->length = length * kNsecPerSec;
QString track_info = info.section(',', 1);
QStringList list = track_info.split(" - ");
if (list.size() <= 1) {
metadata->title = track_info;
return true;
}
metadata->artist = list[0].trimmed();
metadata->title = list[1].trimmed();
return true;
}
void M3UParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const {
device->write("#EXTM3U\n");
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
bool writeMetadata = s.value(Playlist::kWriteMetadata, true).toBool();
s.endGroup();
for (const Song &song : songs) {
if (song.url().isEmpty()) {
continue;
}
if (writeMetadata) {
QString meta = QString("#EXTINF:%1,%2 - %3\n")
.arg(song.length_nanosec() / kNsecPerSec)
.arg(song.artist())
.arg(song.title());
device->write(meta.toUtf8());
}
device->write(URLOrFilename(song.url(), dir, path_type).toUtf8());
device->write("\n");
}
}
bool M3UParser::TryMagic(const QByteArray &data) const {
return data.contains("#EXTM3U") || data.contains("#EXTINF");
}

View File

@@ -0,0 +1,72 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef M3UPARSER_H
#define M3UPARSER_H
#include "config.h"
#include <QUrl>
#include "gtest/gtest_prod.h"
#include "parserbase.h"
class M3UParser : public ParserBase {
Q_OBJECT
public:
M3UParser(CollectionBackendInterface *collection, QObject *parent = nullptr);
QString name() const { return "M3U"; }
QStringList file_extensions() const { return QStringList() << "m3u" << "m3u8"; }
QString mime_type() const { return "text/uri-list"; }
bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const;
private:
enum M3UType {
STANDARD = 0,
EXTENDED, // Includes extended info (track, artist, etc.)
LINK, // Points to a directory.
};
struct Metadata {
Metadata() : length(-1) {}
QString artist;
QString title;
qint64 length;
};
bool ParseMetadata(const QString &line, Metadata *metadata) const;
FRIEND_TEST(M3UParserTest, ParsesMetadata);
FRIEND_TEST(M3UParserTest, ParsesTrackLocation);
FRIEND_TEST(M3UParserTest, ParsesTrackLocationRelative);
FRIEND_TEST(M3UParserTest, ParsesTrackLocationHttp);
#ifdef Q_OS_WIN32
FRIEND_TEST(M3UParserTest, ParsesTrackLocationAbsoluteWindows);
#endif // Q_OS_WIN32
};
#endif // M3UPARSER_H

View File

@@ -0,0 +1,112 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "parserbase.h"
#include "core/tagreaderclient.h"
#include "collection/collectionbackend.h"
#include "collection/collectionquery.h"
#include "collection/sqlrow.h"
#include "playlist/playlist.h"
#include <QUrl>
ParserBase::ParserBase(CollectionBackendInterface *collection, QObject *parent)
: QObject(parent), collection_(collection) {}
void ParserBase::LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir, Song *song) const {
if (filename_or_url.isEmpty()) {
return;
}
QString filename = filename_or_url;
if (filename_or_url.contains(QRegExp("^[a-z]{2,}:"))) {
QUrl url(filename_or_url);
if (url.scheme() == "file") {
filename = url.toLocalFile();
}
//else {
// song->set_url(QUrl::fromUserInput(filename_or_url));
// song->set_filetype(Song::Type_Stream);
// song->set_valid(true);
// return;
// }
}
// Strawberry always wants / separators internally. Using
// QDir::fromNativeSeparators() only works on the same platform the playlist
// was created on/for, using replace() lets playlists work on any platform.
filename = filename.replace('\\', '/');
// Make the path absolute
if (!QDir::isAbsolutePath(filename)) {
filename = dir.absoluteFilePath(filename);
}
// Use the canonical path
if (QFile::exists(filename)) {
filename = QFileInfo(filename).canonicalFilePath();
}
const QUrl url = QUrl::fromLocalFile(filename);
// Search in the collection
Song collection_song;
if (collection_) {
collection_song = collection_->GetSongByUrl(url, beginning);
}
// If it was found in the collection then use it, otherwise load metadata from
// disk.
if (collection_song.is_valid()) {
*song = collection_song;
}
else {
TagReaderClient::Instance()->ReadFileBlocking(filename, song);
}
}
Song ParserBase::LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir) const {
Song song;
LoadSong(filename_or_url, beginning, dir, &song);
return song;
}
QString ParserBase::URLOrFilename(const QUrl &url, const QDir &dir, Playlist::Path path_type) const {
if (url.scheme() != "file") return url.toString();
const QString filename = url.toLocalFile();
if (path_type != Playlist::Path_Absolute && QDir::isAbsolutePath(filename)) {
const QString relative = dir.relativeFilePath(filename);
if (!relative.startsWith("../") || path_type == Playlist::Path_Relative)
return relative;
}
return filename;
}

View File

@@ -0,0 +1,76 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PARSERBASE_H
#define PARSERBASE_H
#include "config.h"
#include <QObject>
#include <QDir>
#include "core/song.h"
#include "playlist/playlist.h"
class CollectionBackendInterface;
class ParserBase : public QObject {
Q_OBJECT
public:
ParserBase(CollectionBackendInterface *collection, QObject *parent = nullptr);
virtual QString name() const = 0;
virtual QStringList file_extensions() const = 0;
virtual QString mime_type() const { return QString(); }
virtual bool TryMagic(const QByteArray &data) const = 0;
// Loads all songs from playlist found at path 'playlist_path' in directory 'dir'.
// The 'device' argument is an opened and ready to read from represantation of
// this playlist.
// This method might not return all of the songs found in the playlist. Any playlist
// parser may decide to leave out some entries if it finds them incomplete or invalid.
// This means that the final resulting SongList should be considered valid (at least
// from the parser's point of view).
virtual SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const = 0;
virtual void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const = 0;
protected:
// Loads a song. If filename_or_url is a URL (with a scheme other than
// "file") then it is set on the song and the song marked as a stream.
// If it is a filename or a file:// URL then it is made absolute and canonical
// and set as a file:// url on the song. Also sets the song's metadata by
// searching in the Collection, or loading from the file as a fallback.
// This function should always be used when loading a playlist.
Song LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir) const;
void LoadSong(const QString &filename_or_url, qint64 beginning, const QDir &dir, Song *song) const;
// If the URL is a file:// URL then returns its path, absolute or relative to
// the directory depending on the path_type option.
// Otherwise returns the URL as is.
// This function should always be used when saving a playlist.
QString URLOrFilename(const QUrl &url, const QDir &dir, Playlist::Path path_type) const;
private:
CollectionBackendInterface *collection_;
};
#endif // PARSERBASE_H

View File

@@ -0,0 +1,190 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "asxparser.h"
#include "asxiniparser.h"
#include "cueparser.h"
#include "m3uparser.h"
#include "playlistparser.h"
#include "plsparser.h"
#include "wplparser.h"
#include "xspfparser.h"
#include "core/logging.h"
#include <QtDebug>
const int PlaylistParser::kMagicSize = 512;
PlaylistParser::PlaylistParser(CollectionBackendInterface *collection, QObject *parent)
: QObject(parent)
{
default_parser_ = new XSPFParser(collection, this);
parsers_ << new M3UParser(collection, this);
parsers_ << default_parser_;
parsers_ << new PLSParser(collection, this);
parsers_ << new ASXParser(collection, this);
parsers_ << new AsxIniParser(collection, this);
parsers_ << new CueParser(collection, this);
parsers_ << new WplParser(collection, this);
}
QStringList PlaylistParser::file_extensions() const {
QStringList ret;
for (ParserBase *parser : parsers_) {
ret << parser->file_extensions();
}
qStableSort(ret);
return ret;
}
QStringList PlaylistParser::mime_types() const {
QStringList ret;
for (ParserBase *parser : parsers_) {
if (!parser->mime_type().isEmpty()) ret << parser->mime_type();
}
qStableSort(ret);
return ret;
}
QString PlaylistParser::filters() const {
QStringList filters;
QStringList all_extensions;
for (ParserBase *parser : parsers_) {
filters << FilterForParser(parser, &all_extensions);
}
filters.prepend(tr("All playlists (%1)").arg(all_extensions.join(" ")));
return filters.join(";;");
}
QString PlaylistParser::FilterForParser(const ParserBase *parser, QStringList *all_extensions) const {
QStringList extensions;
for (const QString &extension : parser->file_extensions())
extensions << "*." + extension;
if (all_extensions) *all_extensions << extensions;
return tr("%1 playlists (%2)").arg(parser->name(), extensions.join(" "));
}
QString PlaylistParser::default_extension() const {
return default_parser_->file_extensions()[0];
}
QString PlaylistParser::default_filter() const {
return FilterForParser(default_parser_);
}
ParserBase *PlaylistParser::ParserForExtension(const QString &suffix) const {
for (ParserBase *p : parsers_) {
if (p->file_extensions().contains(suffix)) return p;
}
return nullptr;
}
ParserBase *PlaylistParser::ParserForMimeType(const QString &mime_type) const {
for (ParserBase *p : parsers_) {
if (!p->mime_type().isEmpty() &&
(QString::compare(p->mime_type(), mime_type, Qt::CaseInsensitive) == 0))
return p;
}
return nullptr;
}
ParserBase *PlaylistParser::ParserForMagic(const QByteArray &data, const QString &mime_type) const {
for (ParserBase *p : parsers_) {
if ((!mime_type.isEmpty() && mime_type == p->mime_type()) || p->TryMagic(data))
return p;
}
return nullptr;
}
SongList PlaylistParser::LoadFromFile(const QString &filename) const {
QFileInfo info(filename);
// Find a parser that supports this file extension
ParserBase *parser = ParserForExtension(info.suffix());
if (!parser) {
qLog(Warning) << "Unknown filetype:" << filename;
return SongList();
}
// Open the file
QFile file(filename);
file.open(QIODevice::ReadOnly);
return parser->Load(&file, filename, info.absolutePath());
}
SongList PlaylistParser::LoadFromDevice(QIODevice *device, const QString &path_hint, const QDir &dir_hint) const {
// Find a parser that supports this data
ParserBase *parser = ParserForMagic(device->peek(kMagicSize));
if (!parser) {
return SongList();
}
return parser->Load(device, path_hint, dir_hint);
}
void PlaylistParser::Save(const SongList &songs, const QString &filename, Playlist::Path path_type) const {
QFileInfo info(filename);
// Find a parser that supports this file extension
ParserBase *parser = ParserForExtension(info.suffix());
if (!parser) {
qLog(Warning) << "Unknown filetype:" << filename;
return;
}
// Open the file
QFile file(filename);
file.open(QIODevice::WriteOnly);
return parser->Save(songs, &file, info.absolutePath(), path_type);
}

View File

@@ -0,0 +1,67 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYLISTPARSER_H
#define PLAYLISTPARSER_H
#include "config.h"
#include <QDir>
#include <QObject>
#include "core/song.h"
#include "playlist/playlist.h"
class ParserBase;
class CollectionBackendInterface;
class PlaylistParser : public QObject {
Q_OBJECT
public:
PlaylistParser(CollectionBackendInterface *collection, QObject *parent = nullptr);
static const int kMagicSize;
QStringList file_extensions() const;
QString filters() const;
QStringList mime_types() const;
QString default_extension() const;
QString default_filter() const;
ParserBase *ParserForMagic(const QByteArray &data, const QString &mime_type = QString()) const;
ParserBase *ParserForExtension(const QString &suffix) const;
ParserBase *ParserForMimeType(const QString &mime) const;
SongList LoadFromFile(const QString &filename) const;
SongList LoadFromDevice(QIODevice *device, const QString &path_hint = QString(), const QDir &dir_hint = QDir()) const;
void Save(const SongList &songs, const QString &filename, Playlist::Path) const;
private:
QString FilterForParser(const ParserBase *parser, QStringList *all_extensions = nullptr) const;
private:
QList<ParserBase*> parsers_;
ParserBase *default_parser_;
};
#endif // PLAYLISTPARSER_H

View File

@@ -0,0 +1,91 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "plsparser.h"
#include "core/logging.h"
#include "core/timeconstants.h"
#include <QTextStream>
#include <QtDebug>
PLSParser::PLSParser(CollectionBackendInterface *collection, QObject *parent)
: ParserBase(collection, parent) {}
SongList PLSParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const {
QMap<int, Song> songs;
QRegExp n_re("\\d+$");
while (!device->atEnd()) {
QString line = QString::fromUtf8(device->readLine()).trimmed();
int equals = line.indexOf('=');
QString key = line.left(equals).toLower();
QString value = line.mid(equals + 1);
n_re.indexIn(key);
int n = n_re.cap(0).toInt();
if (key.startsWith("file")) {
Song song = LoadSong(value, 0, dir);
// Use the title and length we've already loaded if any
if (!songs[n].title().isEmpty()) song.set_title(songs[n].title());
if (songs[n].length_nanosec() != -1)
song.set_length_nanosec(songs[n].length_nanosec());
songs[n] = song;
}
else if (key.startsWith("title")) {
songs[n].set_title(value);
}
else if (key.startsWith("length")) {
qint64 seconds = value.toLongLong();
if (seconds > 0) {
songs[n].set_length_nanosec(seconds * kNsecPerSec);
}
}
}
return songs.values();
}
void PLSParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const {
QTextStream s(device);
s << "[playlist]" << endl;
s << "Version=2" << endl;
s << "NumberOfEntries=" << songs.count() << endl;
int n = 1;
for (const Song &song : songs) {
s << "File" << n << "=" << URLOrFilename(song.url(), dir, path_type) << endl;
s << "Title" << n << "=" << song.title() << endl;
s << "Length" << n << "=" << song.length_nanosec() / kNsecPerSec << endl;
++n;
}
}
bool PLSParser::TryMagic(const QByteArray &data) const {
return data.toLower().contains("[playlist]");
}

View File

@@ -0,0 +1,44 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLSPARSER_H
#define PLSPARSER_H
#include "config.h"
#include "parserbase.h"
class PLSParser : public ParserBase {
Q_OBJECT
public:
PLSParser(CollectionBackendInterface *collection, QObject *parent = nullptr);
QString name() const { return "PLS"; }
QStringList file_extensions() const { return QStringList() << "pls"; }
QString mime_type() const { return "audio/x-scpls"; }
bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const;
};
#endif // PLSPARSER_H

View File

@@ -0,0 +1,120 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2013, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "wplparser.h"
#include "core/utilities.h"
#include "version.h"
#include <QXmlStreamReader>
WplParser::WplParser(CollectionBackendInterface *collection, QObject *parent)
: XMLParser(collection, parent) {}
bool WplParser::TryMagic(const QByteArray &data) const {
return data.contains("<?wpl") || data.contains("<smil>");
}
SongList WplParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const {
SongList ret;
QXmlStreamReader reader(device);
if (!Utilities::ParseUntilElement(&reader, "smil") || !Utilities::ParseUntilElement(&reader, "body")) {
return ret;
}
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, "seq")) {
ParseSeq(dir, &reader, &ret);
}
return ret;
}
void WplParser::ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *songs) const {
while (!reader->atEnd()) {
QXmlStreamReader::TokenType type = reader->readNext();
switch (type) {
case QXmlStreamReader::StartElement: {
QStringRef name = reader->name();
if (name == "media") {
QStringRef src = reader->attributes().value("src");
if (!src.isEmpty()) {
Song song = LoadSong(src.toString(), 0, dir);
if (song.is_valid()) {
songs->append(song);
}
}
} else {
Utilities::ConsumeCurrentElement(reader);
}
break;
}
case QXmlStreamReader::EndElement: {
if (reader->name() == "seq") {
return;
}
break;
}
default:
break;
}
}
}
void WplParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const {
QXmlStreamWriter writer(device);
writer.setAutoFormatting(true);
writer.setAutoFormattingIndent(2);
writer.writeProcessingInstruction("wpl", "version=\"1.0\"");
StreamElement smil("smil", &writer);
{
StreamElement head("head", &writer);
WriteMeta("Generator", "Strawberry -- " STRAWBERRY_VERSION_DISPLAY, &writer);
WriteMeta("ItemCount", QString::number(songs.count()), &writer);
}
{
StreamElement body("body", &writer);
{
StreamElement seq("seq", &writer);
for (const Song &song : songs) {
writer.writeStartElement("media");
writer.writeAttribute("src", URLOrFilename(song.url(), dir, path_type));
writer.writeEndElement();
}
}
}
}
void WplParser::WriteMeta(const QString &name, const QString &content, QXmlStreamWriter *writer) const {
writer->writeStartElement("meta");
writer->writeAttribute("name", name);
writer->writeAttribute("content", content);
writer->writeEndElement();
}

View File

@@ -0,0 +1,46 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2013, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WPLPARSER_H
#define WPLPARSER_H
#include "config.h"
#include "xmlparser.h"
class WplParser : public XMLParser {
public:
WplParser(CollectionBackendInterface *collection, QObject *parent = nullptr);
QString name() const { return "WPL"; }
QStringList file_extensions() const { return QStringList() << "wpl"; }
QString mime_type() const { return "application/vnd.ms-wpl"; }
bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type = Playlist::Path_Automatic) const;
private:
void ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *songs) const;
void WriteMeta(const QString &name, const QString &content, QXmlStreamWriter *writer) const;
};
#endif // WPLPARSER_H

View File

@@ -0,0 +1,33 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "xmlparser.h"
#include <QDomDocument>
#include <QFile>
#include <QIODevice>
#include <QRegExp>
#include <QUrl>
#include <QXmlStreamReader>
XMLParser::XMLParser(CollectionBackendInterface *collection, QObject *parent)
: ParserBase(collection, parent) {}

View File

@@ -0,0 +1,52 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef XMLPARSER_H
#define XMLPARSER_H
#include "config.h"
#include "parserbase.h"
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
class QDomDocument;
class QDomNode;
class XMLParser : public ParserBase {
protected:
XMLParser(CollectionBackendInterface *collection, QObject *parent);
class StreamElement {
public:
StreamElement(const QString& name, QXmlStreamWriter *stream) : stream_(stream) {
stream->writeStartElement(name);
}
~StreamElement() { stream_->writeEndElement(); }
private:
QXmlStreamWriter *stream_;
Q_DISABLE_COPY(StreamElement);
};
};
#endif

View File

@@ -0,0 +1,198 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "xspfparser.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include "playlist/playlist.h"
#include <QDomDocument>
#include <QFile>
#include <QIODevice>
#include <QRegExp>
#include <QUrl>
#include <QXmlStreamReader>
XSPFParser::XSPFParser(CollectionBackendInterface *collection, QObject *parent)
: XMLParser(collection, parent) {}
SongList XSPFParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir) const {
SongList ret;
QXmlStreamReader reader(device);
if (!Utilities::ParseUntilElement(&reader, "playlist") || !Utilities::ParseUntilElement(&reader, "trackList")) {
return ret;
}
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, "track")) {
Song song = ParseTrack(&reader, dir);
if (song.is_valid()) {
ret << song;
}
}
return ret;
}
Song XSPFParser::ParseTrack(QXmlStreamReader *reader, const QDir &dir) const {
QString title, artist, album, location;
qint64 nanosec = -1;
int track_num = -1;
while (!reader->atEnd()) {
QXmlStreamReader::TokenType type = reader->readNext();
switch (type) {
case QXmlStreamReader::StartElement: {
QStringRef name = reader->name();
if (name == "location") {
location = reader->readElementText();
}
else if (name == "title") {
title = reader->readElementText();
}
else if (name == "creator") {
artist = reader->readElementText();
}
else if (name == "album") {
album = reader->readElementText();
}
else if (name == "duration") { // in milliseconds.
const QString duration = reader->readElementText();
bool ok = false;
nanosec = duration.toInt(&ok) * kNsecPerMsec;
if (!ok) {
nanosec = -1;
}
}
else if (name == "trackNum") {
const QString track_num_str = reader->readElementText();
bool ok = false;
track_num = track_num_str.toInt(&ok);
if (!ok || track_num < 1) {
track_num = -1;
}
}
else if (name == "image") {
// TODO: Fetch album covers.
}
else if (name == "info") {
// TODO: Do something with extra info?
}
break;
}
case QXmlStreamReader::EndElement: {
if (reader->name() == "track") {
goto return_song;
}
}
default:
break;
}
}
return_song:
Song song = LoadSong(location, 0, dir);
// Override metadata with what was in the playlist
song.set_title(title);
song.set_artist(artist);
song.set_album(album);
song.set_length_nanosec(nanosec);
song.set_track(track_num);
return song;
}
void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, Playlist::Path path_type) const {
QFileInfo file;
QXmlStreamWriter writer(device);
writer.setAutoFormatting(true);
writer.setAutoFormattingIndent(2);
writer.writeStartDocument();
StreamElement playlist("playlist", &writer);
writer.writeAttribute("version", "1");
writer.writeDefaultNamespace("http://xspf.org/ns/0/");
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
bool writeMetadata = s.value(Playlist::kWriteMetadata, true).toBool();
s.endGroup();
StreamElement tracklist("trackList", &writer);
for (const Song &song : songs) {
QString filename_or_url = URLOrFilename(song.url(), dir, path_type).toUtf8();
StreamElement track("track", &writer);
writer.writeTextElement("location", filename_or_url);
if (writeMetadata) {
writer.writeTextElement("title", song.title());
if (!song.artist().isEmpty()) {
writer.writeTextElement("creator", song.artist());
}
if (!song.album().isEmpty()) {
writer.writeTextElement("album", song.album());
}
if (song.length_nanosec() != -1) {
writer.writeTextElement(
"duration", QString::number(song.length_nanosec() / kNsecPerMsec));
}
if (song.track() > 0) {
writer.writeTextElement("trackNum", QString::number(song.track()));
}
QString art = song.art_manual().isEmpty() ? song.art_automatic()
: song.art_manual();
// Ignore images that are in our resource bundle.
if (!art.startsWith(":") && !art.isEmpty()) {
QString art_filename;
if (!art.contains("://")) {
art_filename = art;
} else if (QUrl(art).scheme() == "file") {
art_filename = QUrl(art).toLocalFile();
}
if (!art_filename.isEmpty() && !(art_filename == "(embedded)")) {
// Make this filename relative to the directory we're saving the
// playlist.
QUrl url = QUrl(art_filename);
url.setScheme("file"); // Need to explicitly set this.
art_filename = URLOrFilename(url, dir, path_type).toUtf8();
} else {
// Just use whatever URL was in the Song.
art_filename = art;
}
writer.writeTextElement("image", art_filename);
}
}
}
writer.writeEndDocument();
}
bool XSPFParser::TryMagic(const QByteArray &data) const {
return data.contains("<playlist") && data.contains("<trackList");
}

View File

@@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef XSPFPARSER_H
#define XSPFPARSER_H
#include "config.h"
#include "xmlparser.h"
#include <QXmlStreamReader>
class QDomDocument;
class QDomNode;
class XSPFParser : public XMLParser {
Q_OBJECT
public:
XSPFParser(CollectionBackendInterface *collection, QObject *parent = nullptr);
QString name() const { return "XSPF"; }
QStringList file_extensions() const { return QStringList() << "xspf"; }
bool TryMagic(const QByteArray &data) const;
SongList Load(QIODevice *device, const QString &playlist_path = "", const QDir &dir = QDir()) const;
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), Playlist::Path path_type = Playlist::Path_Automatic) const;
private:
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir) const;
};
#endif