/* * playlistimport.cpp * * Copyright (C) 2004-2005 Jürgen Kofler * * 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 #include #include #include #include #include "playlistimport.h" class MyXMLParser : public TQXmlDefaultHandler { public: TQValueList mrls; bool isKaffeinePlaylist; MyXMLParser() : isKaffeinePlaylist(false) {} bool startElement(const TQString&, const TQString&, const TQString &qname, const TQXmlAttributes &att) { if (qname == "playlist") if (att.value("client") == "kaffeine") { isKaffeinePlaylist = true; return true; } else { return false; } if (qname != "entry") return true; TQStringList subs = TQStringList(); int currentSub = -1; if ((!att.value("subs").isNull()) && (!att.value("subs").isEmpty())) subs = TQStringList::split("&",att.value("subs"),false); if ((!att.value("subs").isNull()) && (!att.value("subs").isEmpty())) { bool ok; currentSub = att.value("currentSub").toInt(&ok); if (!ok) currentSub = -1; } // kdDebug() << "PlaylistImport: kaffeine import url: " << att.value("url") << endl; mrls.append(MRL(att.value("url"), att.value("title"), PlaylistImport::stringToTime(att.value("length")), att.value("mime"), att.value("artist"), att.value("album"), att.value("track"), att.value("year"), att.value("genre"), TQString(), subs, currentSub)); return true; } }; bool PlaylistImport::kaffeine(const TQString& playlist, TQValueList& mrls) { kdDebug() << "PlaylistImport: kaffeine: " << playlist << endl; TQFile file(playlist); if (!file.open(IO_ReadOnly)) return false; TQXmlInputSource source(TQT_TQIODEVICE(&file)); TQXmlSimpleReader reader; MyXMLParser parser; reader.setContentHandler(&parser); reader.parse(source); file.close(); if (!parser.isKaffeinePlaylist) { return false; } else { TQValueList::ConstIterator end(parser.mrls.end()); for (TQValueList::ConstIterator it = parser.mrls.begin(); it != end; ++it) mrls.append(*it); return true; } } class NoatunXMLParser : public TQXmlDefaultHandler { public: TQValueList mrls; bool isNoatunPlaylist; NoatunXMLParser(): isNoatunPlaylist(false) {} bool startElement(const TQString&, const TQString &, const TQString &qname, const TQXmlAttributes &att) { if (qname == "playlist") if (att.value("client") == "noatun") { isNoatunPlaylist = true; return true; } else { return false; } if (qname != "item") return true; TQString title = att.value("title"); if (title.isNull()) title = att.value("url"); bool ok; TQTime length; int time = att.value("length").toInt(&ok); if ((ok) && (time > 0)) { length = TQTime().addMSecs(time); } kdDebug() << "PlaylistImport: noatun import url: " << att.value("url") << endl; mrls.append(MRL(att.value("url"), title, length, TQString(), att.value("author"), att.value("album"), att.value("track"))); return true; } }; bool PlaylistImport::noatun(const TQString& playlist, TQValueList& mrls) { kdDebug() << "PlaylistImport: noatun: " << playlist << endl; TQFile file(playlist); if (!file.open(IO_ReadOnly)) return false; TQXmlInputSource source(TQT_TQIODEVICE(&file)); TQXmlSimpleReader reader; NoatunXMLParser parser; reader.setContentHandler(&parser); reader.parse(source); file.close(); if (!parser.isNoatunPlaylist) { return false; } else { TQValueList::ConstIterator end(parser.mrls.end()); for (TQValueList::ConstIterator it = parser.mrls.begin(); it != end; ++it) mrls.append(*it); return true; } } bool PlaylistImport::m3u(const TQString& playlist , TQValueList& mrls) { kdDebug() << "PlaylistImport: m3u: " << playlist << endl; TQFile file(playlist); if (!file.open(IO_ReadOnly)) return false; TQTextStream stream(&file); // if (stream.readLine().upper() != "#EXTM3U") return false; TQString url; int time; bool ok; TQTime length; TQString title; KURL kUrl; bool foundAnyUrl = false; KURL plurl(playlist); plurl.setFileName (""); while (!stream.eof()) { url = stream.readLine(); time = 0; title = TQString(); length = TQTime(); if (url.left(1) == "#") { if (url.left(7).upper() == "#EXTINF") { url = url.remove(0,8); time = url.section(",", 0,0).toInt(&ok); if ((ok) && (time > 0)) { length = TQTime().addSecs(time); } title = url.section(",",1,1); url = stream.readLine(); } else { continue; } } url.replace ('\\', '/'); /* for windows styled urls */ kUrl = KURL (plurl, url); /* maybe a relative url */ if (kUrl.isValid()) { kdDebug() << "PlaylistImport: m3u import url: " << kUrl.prettyURL() << endl; MRL mrl; if (kUrl.isLocalFile()) mrl.setURL(kUrl.path()); else mrl.setURL(kUrl.prettyURL()); if (title.isNull()) title = kUrl.fileName(); mrl.setTitle(title); mrl.setLength(length); mrls.append(mrl); foundAnyUrl = true; } else kdDebug() << "PlaylistImport: M3U: Not valid: " << kUrl.prettyURL() << endl; } file.close(); if (foundAnyUrl) return true; else return false; } bool PlaylistImport::pls(const TQString& playlist, TQValueList& mrls) { kdDebug() << "PlaylistImport: pls: " << playlist << endl; TQFile file(playlist); if (!file.open(IO_ReadOnly)) return false; TQTextStream stream(&file); //if (stream.readLine().upper() != "[PLAYLIST]") return false; // Better Handling of pls playlists - Taken from amaroK - amarok.kde.org // Counted number of "File#=" lines. uint entryCnt = 0; // Value of the "NumberOfEntries=#" line. uint numberOfEntries = 0; bool havePlaylistSection = false; TQString tmp; TQStringList lines; // set Regexp keywords, Be case insensitive const TQRegExp regExp_NumberOfEntries("^NumberOfEntries\\s*=\\s*\\d+$", false); const TQRegExp regExp_File("^File\\d+\\s*=", false); const TQRegExp regExp_Title("^Title\\d+\\s*=", false); const TQRegExp regExp_Length("^Length\\d+\\s*=\\s*-?\\d+$", false); const TQRegExp regExp_Version("^Version\\s*=\\s*\\d+$", false); const TQString section_playlist("[playlist]"); /* Preprocess the input data. * Read the lines into a buffer; Cleanup the line strings; * Count the entries manually and read "NumberOfEntries". */ while (!stream.atEnd()) { tmp = stream.readLine(); tmp = tmp.stripWhiteSpace(); if (tmp.isEmpty()) continue; lines.append(tmp); if (tmp == section_playlist) { havePlaylistSection = true; continue; } if (tmp.contains(regExp_File)) { entryCnt++; continue; } if (tmp.contains(regExp_NumberOfEntries)) { numberOfEntries = TQString(tmp.section('=', -1)).stripWhiteSpace().toUInt(); continue; } } file.close(); if (numberOfEntries != entryCnt) { kdError() << "PlaylistImport: Invalid \"NumberOfEntries\" value in .pls playlist. " << "NumberOfEntries=" << numberOfEntries << " counted=" << entryCnt << endl; /* Corrupt file. The "NumberOfEntries" value is * not correct. Fix it by setting it to the manually * counted number and go on parsing. */ numberOfEntries = entryCnt; } // If we have no Entries, return if (!numberOfEntries) return true; int time; bool ok; uint index; bool inPlaylistSection = false; TQString* titles = new TQString[entryCnt]; TQString* files = new TQString[entryCnt]; TQTime* length = new TQTime[entryCnt]; TQStringList::const_iterator i = lines.begin(), end = lines.end(); for ( ; i != end; ++i) { if (!inPlaylistSection && havePlaylistSection) { /* The playlist begins with the "[playlist]" tag. * Skip everything before this. */ if ((*i) == section_playlist) inPlaylistSection = true; continue; } if ((*i).contains(regExp_File)) { // Have a "File#=XYZ" line. index = extractIndex(*i); if (index > numberOfEntries || index == 0) continue; files[index-1] = TQString((*i).section('=', 1)).stripWhiteSpace(); continue; } if ((*i).contains(regExp_Title)) { // Have a "Title#=XYZ" line. index = extractIndex(*i); if (index > numberOfEntries || index == 0) continue; titles[index-1] = TQString((*i).section('=', 1)).stripWhiteSpace(); continue; } if ((*i).contains(regExp_Length)) { // Have a "Length#=XYZ" line. index = extractIndex(*i); if (index > numberOfEntries || index == 0) continue; tmp = TQString((*i).section('=', 1)).stripWhiteSpace(); time = tmp.toInt(&ok); if ( !(ok) || !(time > 0) ) continue; length[index-1] = TQTime().addSecs(time); continue; } if ((*i).contains(regExp_NumberOfEntries)) { // Have the "NumberOfEntries=#" line. continue; } if ((*i).contains(regExp_Version)) { // Have the "Version=#" line. tmp = TQString((*i).section('=', 1)).stripWhiteSpace(); // We only support Version=2 if (tmp.toUInt(&ok) != 2) kdWarning() << "PlaylistImport: pls: Unsupported version." << endl; continue; } kdWarning() << "PlaylistImport: pls: Unrecognized line: \"" << *i << "\"" << endl; } for (uint i=0; i& mrls) { kdDebug() << "PlaylistImport: asx: " << playlist << endl; TQFile file(playlist); if (!file.open(IO_ReadOnly)) return false; TQDomDocument doc; TQString errorMsg; int errorLine, errorColumn; if (!doc.setContent(&file, &errorMsg, &errorLine, &errorColumn)) { kdError() << "PlaylistImport: XML parse error: " << errorMsg << " (line: " << errorLine << ", column: " << errorColumn << ")" << endl; return false; } TQDomElement root = doc.documentElement(); TQString url; TQString title; TQString author; TQTime length; TQString duration; if (root.nodeName().lower() != "asx") return false; TQDomNode node = root.firstChild(); TQDomNode subNode; TQDomElement element; while (!node.isNull()) { url = TQString(); title = TQString(); author = TQString(); length = TQTime(); if (node.nodeName().lower() == "entry") { subNode = node.firstChild(); while (!subNode.isNull()) { if ((subNode.nodeName().lower() == "ref") && (subNode.isElement()) && (url.isNull())) { element = subNode.toElement(); if (element.hasAttribute("href")) url = element.attribute("href"); if (element.hasAttribute("HREF")) url = element.attribute("HREF"); if (element.hasAttribute("Href")) url = element.attribute("Href"); if (element.hasAttribute("HRef")) url = element.attribute("HRef"); } if ((subNode.nodeName().lower() == "duration") && (subNode.isElement())) { duration = TQString(); element = subNode.toElement(); if (element.hasAttribute("value")) duration = element.attribute("value"); if (element.hasAttribute("Value")) duration = element.attribute("Value"); if (element.hasAttribute("VALUE")) duration = element.attribute("VALUE"); if (!duration.isNull()) length = PlaylistImport::stringToTime(duration); } if ((subNode.nodeName().lower() == "title") && (subNode.isElement())) { title = subNode.toElement().text(); } if ((subNode.nodeName().lower() == "author") && (subNode.isElement())) { author = subNode.toElement().text(); } /* possible nodes we ignore: ABSTRACT, BANNER, BASE, COPYRIGHT, ENDMARKER, MOREINFO, PARAM, * PREVIEWDURATION, STARTMARKER, STARTTIME */ subNode = subNode.nextSibling(); } if (!url.isNull()) { if (title.isNull()) title = url; kdDebug() << "PlaylistImport: asx import url: " << url << endl; mrls.append(MRL(url, title, length, TQString(), author)); } } node = node.nextSibling(); } file.close(); return true; } /**************************************************************** * Full SMIL support seems to be impossible at the moment... * * spec: http://www.w3.org/TR/REC-smil/ * ****************************************************************/ bool PlaylistImport::smil(const TQString& playlist, const MRL& baseMRL, TQValueList& mrls) { kdDebug() << "PlaylistImport: smil: " << playlist << endl; TQFile file(playlist); if (!file.open(IO_ReadOnly)) return false; TQDomDocument doc; doc.setContent(&file); TQDomElement root = doc.documentElement(); if (root.nodeName().lower() != "smil") return false; bool anyURL = false; KURL kurl; TQString url; TQDomNodeList nodeList; TQDomNode node; TQDomElement element; //video sources... nodeList = doc.elementsByTagName("video"); kdDebug() << "PlaylistImport: smil: " << nodeList.count() << " 'video' tags found" << endl; for (uint i = 0; i < nodeList.count(); i++) { node = nodeList.item(i); url = TQString(); if ((node.nodeName().lower() == "video") && (node.isElement())) { element = node.toElement(); if (element.hasAttribute("src")) url = element.attribute("src"); if (element.hasAttribute("Src")) url = element.attribute("Src"); if (element.hasAttribute("SRC")) url = element.attribute("SRC"); } if (!url.isNull()) { kurl = KURL(baseMRL.kurl(), url); kdDebug() << "PlaylistImport: smil: found video source: " << kurl.url() << endl; mrls.append(kurl); anyURL = true; } } //audio sources... nodeList = doc.elementsByTagName("audio"); kdDebug() << "PlaylistImport: smil: " << nodeList.count() << " 'audio' tags found" << endl; for (uint i = 0; i < nodeList.count(); i++) { node = nodeList.item(i); url = TQString(); if ((node.nodeName().lower() == "audio") && (node.isElement())) { element = node.toElement(); if (element.hasAttribute("src")) url = element.attribute("src"); if (element.hasAttribute("Src")) url = element.attribute("Src"); if (element.hasAttribute("SRC")) url = element.attribute("SRC"); } if (!url.isNull()) { kurl = KURL(baseMRL.kurl(), url); kdDebug() << "PlaylistImport: smil: found audio source: " << kurl.url() << endl; mrls.append(kurl); anyURL = true; } } file.close(); return anyURL; } TQTime PlaylistImport::stringToTime(const TQString& timeString) { int sec = 0; bool ok = false; TQStringList tokens = TQStringList::split(':',timeString); sec += tokens[0].toInt(&ok)*3600; //hours sec += tokens[1].toInt(&ok)*60; //minutes sec += tokens[2].toInt(&ok); //secs if (ok) return TQTime().addSecs(sec); else return TQTime(); }