|
|
|
#include <noatun/playlistsaver.h>
|
|
|
|
#include <tqdom.h>
|
|
|
|
#include <kio/netaccess.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tqtextstream.h>
|
|
|
|
#include <noatun/app.h>
|
|
|
|
#include "ksaver.h"
|
|
|
|
#include <ksimpleconfig.h>
|
|
|
|
#include <kmimetype.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqxml.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
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<TQString,TQString> 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("<!DOCTYPE XMLPlaylist><playlist version=\"1.0\" client=\"noatun\"/>"));
|
|
|
|
|
|
|
|
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<TQString,TQString> propMap;
|
|
|
|
|
|
|
|
for (int i=0; i<a.count(); i++)
|
|
|
|
{
|
|
|
|
propMap[a.qName(i)] = a.value(i);
|
|
|
|
}
|
|
|
|
saver->readItem(propMap);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class MSASXStructure : public TQXmlDefaultHandler
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
PlaylistSaver *saver;
|
|
|
|
bool fresh;
|
|
|
|
bool inEntry, inTitle;
|
|
|
|
TQMap<TQString,TQString> 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) << "<ENTRY> =====================" << endl;
|
|
|
|
inEntry=true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (inEntry) // inside entry block
|
|
|
|
{
|
|
|
|
// known stuff inside an <entry> ... </entry> block:
|
|
|
|
// <title>blah</title>
|
|
|
|
// <param album="blub" />
|
|
|
|
// <param artist="blah" />
|
|
|
|
// <ref HREF="file:/something" />
|
|
|
|
if(name.lower()=="ref")
|
|
|
|
{
|
|
|
|
for (int i=0; i<a.count(); i++)
|
|
|
|
{
|
|
|
|
if(a.qName(i).lower()=="href")
|
|
|
|
{
|
|
|
|
TQString filename=a.value(i);
|
|
|
|
if (filename.find(TQRegExp("^[a-zA-Z0-9]+:/"))==0)
|
|
|
|
{
|
|
|
|
KURL url(filename);
|
|
|
|
KMimeType::Ptr mimetype = KMimeType::findByURL(url);
|
|
|
|
TQString type=mimetype->name();
|
|
|
|
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<a.count(); i++)
|
|
|
|
{
|
|
|
|
if(a.value(i).lower()=="album")
|
|
|
|
keyName="album";
|
|
|
|
else if(a.value(i).lower()=="artist")
|
|
|
|
keyName="author";
|
|
|
|
else if(!keyName.isEmpty()) // successfully found a key, the next key=value pair has to be the value
|
|
|
|
{
|
|
|
|
// kdDebug(66666) << "keyName=" << keyName << ", next value is '" << a.value(i) << "'" << endl;
|
|
|
|
keyValue=a.value(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!keyName.isEmpty() && !keyValue.isEmpty())
|
|
|
|
{
|
|
|
|
// kdDebug(66666) << "adding property; key='" << keyName << "', value='" << keyValue << "'" << endl;
|
|
|
|
propMap[keyName]=keyValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(name.lower()=="title")
|
|
|
|
{
|
|
|
|
if(inTitle) // WHOOPS, we are already in an entry, this should NEVER happen
|
|
|
|
{
|
|
|
|
kdDebug(66666) << "STOP, TITLE INSIDE TITLE!" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// kdDebug(66666) << "<TITLE> ======" << endl;
|
|
|
|
inTitle=true;
|
|
|
|
}
|
|
|
|
/* else
|
|
|
|
{
|
|
|
|
kdDebug(66666) << "Unknown/unused element inside ENTRY block, NAME='" << name << "'" << endl;
|
|
|
|
for (int i=0; i<a.count(); i++)
|
|
|
|
kdDebug(66666) << " | " << a.qName(i) << " = '" << a.value(i) << "'" << endl;
|
|
|
|
}*/
|
|
|
|
}
|
|
|
|
/* else
|
|
|
|
{
|
|
|
|
kdDebug(66666) << "Uninteresting element, NAME='" << name << "'" << endl;
|
|
|
|
for (int i=0; i<a.count(); i++)
|
|
|
|
kdDebug(66666) << " | " << a.qName(i) << " = '" << a.value(i) << "'" << endl;
|
|
|
|
}*/
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool endElement(const TQString&,const TQString&, const TQString& name)
|
|
|
|
{
|
|
|
|
// kdDebug(66666) << k_funcinfo << "name='" << name << "'" << endl;
|
|
|
|
if (name.lower()=="entry")
|
|
|
|
{
|
|
|
|
if(inEntry)
|
|
|
|
{
|
|
|
|
/* kdDebug(66666) << "</ENTRY> =====================" << endl;
|
|
|
|
for (TQMap<TQString,TQString>::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 </entry> without a start
|
|
|
|
{
|
|
|
|
kdDebug(66666) << "STOP, </ENTRY> without a start" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (name.lower()=="title")
|
|
|
|
{
|
|
|
|
if(inTitle && inEntry)
|
|
|
|
{
|
|
|
|
// kdDebug(66666) << "</TITLE> ======" << endl;
|
|
|
|
inTitle=false;
|
|
|
|
}
|
|
|
|
else if (inTitle) // found </title> without a start or not inside an <entry> ... </entry>
|
|
|
|
{
|
|
|
|
kdDebug(66666) << "STOP, </TITLE> 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(KIO::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(!KIO::NetAccess::download(file, localFile, 0L))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// if it's a PLS, transfer control, again (KIO 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<TQString,TQString> 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()
|
|
|
|
|
|
|
|
KIO::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<int>(((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();
|
|
|
|
KIO::NetAccess::upload(local, file, 0L);
|
|
|
|
saver.remove();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static TQString findNoCase(const TQMap<TQString,TQString> &map, const TQString &key)
|
|
|
|
{
|
|
|
|
for (TQMap<TQString,TQString>::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(!KIO::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<TQString,TQString> 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<TQString,TQString> 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 &)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|