#include #include #include #include #include #include #include "ksaver.h" #include #include #include #include #include #include PlaylistSaver::PlaylistSaver() { } PlaylistSaver::~PlaylistSaver() { } bool PlaylistSaver::save(const KURL &file, int opt) { // kdDebug(66666) << k_funcinfo << "opt=" << opt << endl; if(file.isEmpty() || !file.isValid()) return false; switch (opt) { default: case 0: case XMLPlaylist: return saveXML(file, opt); case M3U: case EXTM3U: return saveM3U(file, opt); case PLS: return savePLS(file, opt); case ASX: return false; // No, I won't code that! [mETz] } } bool PlaylistSaver::load(const KURL &file, int opt) { // kdDebug(66666) << k_funcinfo << "opt=" << opt << endl; switch (opt) { default: case 0: case XMLPlaylist: case ASX: return loadXML(file, opt); case M3U: case EXTM3U: return loadM3U(file, opt); case PLS: return loadPLS(file, opt); } } bool PlaylistSaver::metalist(const KURL &url) { kdDebug(66666) << k_funcinfo << "url=" << url.url() << endl; TQString end=url.filename().right(3).lower(); /* if (end=="mp3" || end=="ogg") // we want to download streams only { kdDebug(66666) << k_funcinfo << "I can only load playlists" << endl; return false; } */ /* .wax audio/x-ms-wax Metafiles that reference Windows Media files with the .asf, .wma or .wax file extensions. .wvx video/x-ms-wvx Metafiles that reference Windows Media files with the .wma, .wmv, .wvx or .wax file extensions. .asx video/x-ms-asf Metafiles that reference Windows Media files with the .wma, .wax, .wmv, .wvx, .asf, or .asx file extensions. */ // it's actually a stream! if (end!="pls" && end!="m3u" && end!="wax" && // windows mediaplayer metafile end!="wvx" && // windows mediaplayer metafile end!="asx" && // windows mediaplayer metafile url.protocol().lower()=="http") { KMimeType::Ptr mimetype = KMimeType::findByURL(url); TQString type=mimetype->name(); if (type!="application/octet-stream") return false; TQMap map; map["playObject"]="Arts::StreamPlayObject"; map["title"] = i18n("Stream from %1").arg(url.host()); KURL u(url); if (!u.hasPath()) u.setPath("/"); map["stream_"] = map["url"] = u.url(); reset(); readItem(map); return true; } // it is a pls, m3u or ms-media-player file by now if(loadXML(url, XMLPlaylist)) return true; if(loadXML(url,ASX)) return true; if(loadPLS(url)) return true; if(loadM3U(url)) return true; return false; } bool PlaylistSaver::saveXML(const KURL &file, int ) { TQString localFile; if (file.isLocalFile()) localFile = TQFile::encodeName(file.path()); else localFile = napp->tempSaveName(file.path()); // TQDom is a pain :) TQDomDocument doc("playlist"); doc.setContent(TQString("")); TQDomElement docElem=doc.documentElement(); reset(); PlaylistItem i; TQStringList props; while ((i=writeItem())) { // write all properties props=i.properties(); TQDomElement elem=doc.createElement("item"); for (TQStringList::Iterator pi(props.begin()); pi!=props.end(); ++pi) { TQString val=i.property(*pi); elem.setAttribute(*pi, val); if ((*pi)=="url") { KURL u(val); if (u.isLocalFile()) { elem.setAttribute("local", u.path()); } } } docElem.appendChild(elem); props.clear(); } Noatun::KSaver saver(localFile); if (!saver.open()) return false; saver.textStream().setEncoding(TQTextStream::UnicodeUTF8); saver.textStream() << doc.toString(); saver.close(); return true; } class NoatunXMLStructure : public TQXmlDefaultHandler { public: PlaylistSaver *saver; bool fresh; NoatunXMLStructure(PlaylistSaver *s) : saver(s), fresh(true) { } bool startElement( const TQString&, const TQString &, const TQString &name, const TQXmlAttributes &a ) { if (fresh) { if (name=="playlist") { fresh=false; return true; } else { return false; } } if (name != "item") return true; TQMap propMap; for (int i=0; ireadItem(propMap); return true; } }; class MSASXStructure : public TQXmlDefaultHandler { public: PlaylistSaver *saver; bool fresh; bool inEntry, inTitle; TQMap propMap; TQString mAbsPath; MSASXStructure(PlaylistSaver *s, const TQString &absPath) : saver(s), fresh(true), inEntry(false), inTitle(false), mAbsPath(absPath) { //kdDebug(66666) << k_funcinfo << endl; } bool startElement(const TQString&, const TQString &, const TQString &name, const TQXmlAttributes &a) { if (fresh) { if (name.lower()=="asx") { //kdDebug(66666) << "found ASX format" << endl; fresh=false; return true; } else { kdDebug(66666) << "This is NOT an ASX style playlist!" << endl; return false; } } if (name.lower()=="entry") { if(inEntry) // WHOOPS, we are already in an entry, this should NEVER happen { kdDebug(66666) << "STOP, ENTRY INSIDE ENTRY!" << endl; return false; } // kdDebug(66666) << " =====================" << endl; inEntry=true; } else { if (inEntry) // inside entry block { // known stuff inside an ... block: // blah // // // if(name.lower()=="ref") { for (int i=0; iname(); if (type != "application/octet-stream") { propMap["url"]=filename; } else { propMap["playObject"]="SplayPlayObject"; propMap["title"] = i18n("Stream from %1").arg(url.host()); if (!url.hasPath()) url.setPath("/"); propMap["url"] = url.url(); propMap["stream_"]=propMap["url"]; // readItem(propMap); // continue; } } else { KURL u1; // we have to deal with a relative path if (filename.find('/')) { u1.setPath(mAbsPath); //FIXME: how to get the path in this place? u1.setFileName(filename); } else { u1.setPath(filename); } propMap["url"]=u1.url(); } // kdDebug(66666) << "adding property url, value='" << propMap["url"] << "'" << endl; } } } else if(name.lower()=="param") { TQString keyName="", keyValue=""; for (int i=0; i ======" << endl; inTitle=true; } /* else { kdDebug(66666) << "Unknown/unused element inside ENTRY block, NAME='" << name << "'" << endl; for (int i=0; i =====================" << endl; for (TQMap::ConstIterator it=propMap.begin(); it!=propMap.end(); ++it ) kdDebug(66666) << "key='" << it.key() << "', val='" << it.data() << "'" << endl; */ saver->readItem(propMap); propMap.clear(); inEntry=false; } else // found without a start { kdDebug(66666) << "STOP, without a start" << endl; return false; } } else if (name.lower()=="title") { if(inTitle && inEntry) { // kdDebug(66666) << " ======" << endl; inTitle=false; } else if (inTitle) // found without a start or not inside an ... { kdDebug(66666) << "STOP, without a start" << endl; return false; } } return true; } bool characters(const TQString &ch) { if(inTitle) { if (!ch.isEmpty()) { propMap["title"]=ch; // kdDebug(66666) << "adding property; key='title', value='" << ch << "'" << endl; } } return true; } }; bool PlaylistSaver::loadXML(const KURL &url, int opt) { kdDebug(66666) << k_funcinfo << "file='" << url.url() << "', opt=" << opt << endl; TQString dest; if(TDEIO::NetAccess::download(url, dest, 0L)) { TQFile file(dest); if (!file.open(IO_ReadOnly)) return false; reset(); // TQXml is horribly documented TQXmlInputSource source(TQT_TQIODEVICE(&file)); TQXmlSimpleReader reader; if (opt == ASX || url.path().right(4).lower()==".wax" || url.path().right(4).lower()==".asx" || url.path().right(4).lower()==".wvx") { MSASXStructure ASXparser(this, url.path(0)); reader.setContentHandler(&ASXparser); reader.parse(source); return !ASXparser.fresh; } else { NoatunXMLStructure parser(this); reader.setContentHandler(&parser); reader.parse(source); return !parser.fresh; } } // END download() return false; } bool PlaylistSaver::loadM3U(const KURL &file, int /*opt*/) { kdDebug(66666) << k_funcinfo << "file='" << file.path() << endl; TQString localFile; if(!TDEIO::NetAccess::download(file, localFile, 0L)) return false; // if it's a PLS, transfer control, again (TDEIO bug?) #if 0 { KSimpleConfig list(local, true); list.setGroup("playlist"); // some stupid Windows lusers like to be case insensitive TQStringList groups=list.groupList().grep(TQRegExp("^playlist$", false)); if (groups.count()) { KURL l; l.setPath(local); return loadPLS(l); } } #endif TQFile saver(localFile); saver.open(IO_ReadOnly); TQTextStream t(&saver); bool isExt = false; // flag telling if we load an EXTM3U file TQString filename; TQString extinf; TQMap prop; reset(); while (!t.eof()) { if (isExt) { extinf = t.readLine(); if (!extinf.startsWith("#EXTINF:")) { // kdDebug(66666) << "EXTM3U extinf line != extinf, assuming it's a filename." << endl; filename = extinf; extinf=""; } else { filename = t.readLine(); // read in second line containing the filename } //kdDebug(66666) << "EXTM3U filename = '" << filename << "'" << endl; } else // old style m3u { filename = t.readLine(); } if (filename == "#EXTM3U") // on first line { // kdDebug(66666) << "FOUND '#EXTM3U' @ " << saver.at() << "." << endl; isExt=true; continue; // skip parsing the first (i.e. this) line } if (filename.isEmpty()) continue; if (filename.find(TQRegExp("^[a-zA-Z0-9]+:/"))==0) { //kdDebug(66666) << k_funcinfo << "url filename = " << filename << endl; KURL protourl(filename); KMimeType::Ptr mimetype = KMimeType::findByURL(protourl); if (mimetype->name() != "application/octet-stream") { prop["url"] = filename; } else { prop["playObject"]="SplayPlayObject"; // Default title, might be overwritten by #EXTINF later prop["title"] = i18n("Stream from %1").arg(protourl.host()); if (!protourl.hasPath()) protourl.setPath("/"); prop["url"] = protourl.url(); prop["stream_"] = prop["url"]; } } else // filename that is not of URL style (i.e. NOT "proto:/path/somefile") { KURL u1; // we have to deal with a relative path if (filename.find('/')) { u1.setPath(file.path(0)); u1.setFileName(filename); } else { u1.setPath(filename); } prop["url"] = u1.url(); } // parse line of the following format: //#EXTINF:length,displayed_title if (isExt) { extinf.remove(0,8); // remove "#EXTINF:" //kdDebug(66666) << "EXTM3U extinf = '" << extinf << "'" << endl; int timeTitleSep = extinf.find(',', 0); int length = (extinf.left(timeTitleSep)).toInt(); if (length>0) prop["length"]=TQString::number(length*1000); TQString displayTitle=extinf.mid(timeTitleSep+1); if (!displayTitle.isEmpty()) { int artistTitleSep = displayTitle.find(" - ",0); if (artistTitleSep == -1) // no "artist - title" like format, just set it as title { prop["title"] = displayTitle; } else { prop["author"] = displayTitle.left(artistTitleSep); prop["title"] = displayTitle.mid(artistTitleSep+3); } /*kdDebug(66666) << "EXTM3U author/artist='" << prop["author"] << "', title='" << prop["title"] << "'" << endl;*/ } // END !displayTitle.isEmpty() } // END if(isExt) // kdDebug(66666) << k_funcinfo << "adding file '" << prop["url"] << "' to playlist" << endl; readItem(prop); prop.clear(); } // END while() TDEIO::NetAccess::removeTempFile(localFile); // kdDebug(66666) << k_funcinfo << "END" << endl; return true; } bool PlaylistSaver::saveM3U(const KURL &file, int opt) { // kdDebug(66666) << k_funcinfo << "file='" << file.path() << "', opt=" << opt << endl; bool isExt=(opt==EXTM3U); // easier ;) TQString local(napp->tempSaveName(file.path())); TQFile saver(local); saver.open(IO_ReadWrite | IO_Truncate); TQTextStream t(&saver); reset(); PlaylistItem i; TQStringList props; // this is more code but otoh faster than checking for isExt inside the loop if(isExt) { t << "#EXTM3U" << '\n'; while ((i=writeItem())) { int length = static_cast(((i.property("length")).toInt())/1000); if(length==0) length=-1; // special value in an EXTM3U file, means "unknown" KURL u(i.property("url")); TQString title; // if a playlistitem is without a tag or ONLY title is set if((i.property("author").isEmpty() && i.property("title").isEmpty()) || (i.property("author").isEmpty() && !i.property("title").isEmpty()) ) title = u.filename().left(u.filename().length()-4); else title = i.property("author") + " - " + i.property("title"); // kdDebug(66666) << "#EXTINF:"<< TQString::number(length) << "," << title << endl; t << "#EXTINF:"<< TQString::number(length) << "," << title << '\n'; if (u.isLocalFile()) t << u.path() << '\n'; else t << u.url() << '\n'; } } else { while ((i=writeItem())) { KURL u(i.property("url")); if (u.isLocalFile()) t << u.path() << '\n'; else t << u.url() << '\n'; } } saver.close(); TDEIO::NetAccess::upload(local, file, 0L); saver.remove(); return true; } static TQString findNoCase(const TQMap &map, const TQString &key) { for (TQMap::ConstIterator i=map.begin(); i!=map.end(); ++i) { if (i.key().lower() == key.lower()) return i.data(); } return 0; } bool PlaylistSaver::loadPLS(const KURL &file, int /*opt*/) { kdDebug(66666) << k_funcinfo << "file='" << file.path() << endl; TQString localFile; if(!TDEIO::NetAccess::download(file, localFile, 0L)) return false; TQFile checkFile(localFile); checkFile.open(IO_ReadOnly); TQTextStream t(&checkFile); TQString firstLine = t.readLine(); if(firstLine.lower() != "[playlist]") { kdDebug(66666) << k_funcinfo << "PLS didn't start with '[playlist]', aborting" << endl; return false; } KSimpleConfig list(localFile, true); //list.setGroup("playlist"); // some stupid Windows lusers like to be case insensitive TQStringList groups = list.groupList().grep(TQRegExp("^playlist$", false)); /* if (!groups.count()) // didn't find "[playlist]", it's not a .pls file return false; */ TQMap group = list.entryMap(groups[0]); TQString numOfEntries = findNoCase(group, "numberofentries"); if(numOfEntries.isEmpty()) return false; reset(); unsigned int nEntries = numOfEntries.toInt(); for(unsigned int entry = 1; entry <= nEntries; ++entry ) { TQString str; str.sprintf("file%d", entry); TQString cast = findNoCase(group, str.utf8()); str.sprintf("title%d", entry); TQString title = findNoCase(group, str.utf8()); // assume that everything in a pls is a streamable file TQMap map; KURL url(cast); if (!url.hasPath()) url.setPath("/"); map["playObject"]="SplayPlayObject"; if (title.isEmpty()) map["title"] = i18n("Stream from %1 (port: %2)").arg( url.host() ).arg( url.port() ); else map["title"] = i18n("Stream from %1, (ip: %2, port: %3)").arg( title ).arg( url.host() ).arg(url.port() ); map["url"] = map["stream_"]= url.url(); readItem(map); } return true; } bool PlaylistSaver::savePLS(const KURL &, int) { return false; } void PlaylistSaver::setGroup(const TQString &) { }