Summary: | amaroK Should be Able to Import and Export Podcast Subscription | ||
---|---|---|---|
Product: | [Applications] amarok | Reporter: | Pratik Patel <pkpatel88> |
Component: | Podcast | Assignee: | Amarok Developers <amarok-bugs-dist> |
Status: | RESOLVED FIXED | ||
Severity: | wishlist | CC: | andreaswuest, bart.cerneels, bjoernv, dev+kde, kde, kuba.serafinowski, tatum.rashad |
Priority: | NOR | ||
Version: | 2.3-GIT | ||
Target Milestone: | 2.4.0 | ||
Platform: | Ubuntu | ||
OS: | Linux | ||
Latest Commit: | Version Fixed In: | 2.4 | |
Sentry Crash Report: | |||
Attachments: |
screenshot of location export to OPML action.
No OPML export in Amarok 2.8 found |
Description
Pratik Patel
2006-04-23 17:55:09 UTC
This sounds like a very reasonable wish to me. Additionally, using a list of podcast subscriptions stored as OPML (just as akregator does for rss feeds) instead of storing the subscriptions only into the database (as is done in 1.4, starting with beta3), would have the advantage of the subscriptions being persistent despite database backends being changed. This would also make amaroK make a little bit more consistent with itself, as I view my podcast subscriptions as configuration data, and all other configuration data is stored in plain files, not in the database. 100% agreed, would be cool to have. Bart wrote an OPML importer, lets use it! Yeah, but as OPML is getting more stronger, amarok should also have an OPML import /export function built in I also would love to see an option to export (or import for that matter) podcasts from other apps like google reader, Rhythmbox,etc. Along the same lines, it would be nice if there were an easy way to export your songs metadata (ratings,etc), without having to manually copy the database tables. Yes, exporting the feed list would be very useful. Surprised it's not already possible. This feature is missing in Amarok 1.4.6. Do we know if it is scheduled to be included in any near term release? On 13 Jul 2007 18:42:38 -0000, Sujee Sivasubramaniyam <kde@sujee.net> wrote: > This feature is missing in Amarok 1.4.6. > Do we know if it is scheduled to be included in any near term release? Unfortunately it won't happen very soon. 1.4 is feature frozen (only bugfixes allowed), and all current development happens in 2.0, which is due October 2007 earliest. Realistically, it will be later. Would be cool to have it in 2.0, but currently we are *very* busy already, so any help is welcome. This feature would have another advantage: Some people intend to reinstall Linux on their computer completely. How should they otherwise save their personal content - like all (gr)own playlists ? Import and export features are the sign of truly open software. A two year wait for this feature to be added is too long. Come on Amarok team - increase the priority of this task, please. @Kuhn: by copying their database and settings from .kde/share/apps/Amarok and .kde/share/config for example @Robert: This is not going to be done in Amarok 1.4.x as it is in feature freeze. I therefore up the version to 2.0-svn. Podcast support has been worked on in Amarok 2 already and looks promising. No idea if you are already able to export them though. @Robert Brown To be able to grant you that request I will need: a) 35.000 euro per year to work on Open Source projects full time b) a project manager for my house-in-planning or c) patience from our users. Thank you for either one of those 3. @Robert Brown Banshee and Rhythmbox does not have this feature. Miro has this feature. I recently started developing for Amarok (specifically on podcasts). I will attempt to implement this feature. I recently started developing for Amarok (specifically on podcasts). I will attempt to implement this feature. Please delete the duplicate of my comment. Importing of OPML is implemented in v2.2.1-236-g247a935 and will be part of Amarok 2.2.2. Still working on the exporting. It’s good to hear that progress is being made in that area. But sadly it doesn’t help me and thousands (millions? *g*) of other users who still cling on to Amarok 1, because we can’t migrate our collection of podcast files (1,7 GB in my case). Can you tell me please if I should still uphold my hopes or is it perhaps already decided that there will definitely be no solution for this of any kind? In the latter case I could try and put my (somewhat limited) SQL knowledge to use and write some kind of converter. Though I would need some time to get aqcuainted with the DB structures of both versions. Cheers (In reply to comment #18) > It’s good to hear that progress is being made in that area. But sadly it > doesn’t help me and thousands (millions? *g*) of other users who still cling on > to Amarok 1, because we can’t migrate our collection of podcast files (1,7 GB > in my case). Can you tell me please if I should still uphold my hopes or is it > perhaps already decided that there will definitely be no solution for this of > any kind? > > In the latter case I could try and put my (somewhat limited) SQL knowledge to > use and write some kind of converter. Though I would need some time to get > aqcuainted with the DB structures of both versions. > > Cheers Hey Frank, Could you please open a new bug report for this. I wanted to do a 1.4-podcast importer included in the general database importer. But unfortunately there were more urgent tasks for me personally and no one else seemed interested. I figured Amarok 2 has been out for to long for anyone to still care. You are the first one that offers to implement it. You can mail kde-devel@kde.org if you need help, please send a mail anyway that you are working on it. I'll give you pointers in a reply mail. The deadline for 2.2.3 inclusion is next Tuesday. Talk to us in #amarok on freenode as well. My nick is Stecchino. Good luck Bart *** Bug 231513 has been marked as a duplicate of this bug. *** A src/OpmlWriter.h [License: UNKNOWN] A src/OpmlWriter.cpp [License: UNKNOWN] A src/OpmlOutline.h [License: UNKNOWN] A src/OpmlOutline.cpp [License: UNKNOWN] commit 2018f55d2df9dbbf53339c9594ab23743fefb3d7 Author: Bart Cerneels <bart.cerneels@kde.org> Date: Fri Apr 23 16:52:14 2010 +0200 Add OPML export for local podcast subscription. BUG:126120 diff --git a/ChangeLog b/ChangeLog index d204a3c..67d8742 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ Amarok ChangeLog VERSION NEXT FEATURES: + * OPML export for podcast subscriptions. (BR 126120) * New "Playlist Length" constraint for the APG, which allows you to specify the number of tracks in the playlist. What was the "Playlist Length" constraint is now called "Playlist Duration". diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d114881..d8325cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -667,7 +667,9 @@ set(amaroklib_LIB_SRCS browsers/filebrowser/FileView.cpp firstruntutorial/FirstRunTutorial.cpp firstruntutorial/FirstRunTutorialPage.cpp + OpmlOutline.cpp OpmlParser.cpp + OpmlWriter.cpp PaletteHandler.cpp PopupDropperFactory.cpp statemanagement/ApplicationController.cpp diff --git a/src/OpmlOutline.cpp b/src/OpmlOutline.cpp new file mode 100644 index 0000000..5a3d653 --- /dev/null +++ b/src/OpmlOutline.cpp @@ -0,0 +1,24 @@ +/**************************************************************************************** + * Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> * + * Copyright (c) 2009-2010 Bart Cerneels <bart.cerneels@kde.org> * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see <http://www.gnu.org/licenses/>. * + ****************************************************************************************/ + +#include "OpmlOutline.h" + +OpmlOutline::OpmlOutline( OpmlOutline *parent ) + : m_parent( parent ) + , m_hasChildren( false ) +{ +} diff --git a/src/OpmlOutline.h b/src/OpmlOutline.h new file mode 100644 index 0000000..8b64dc1 --- /dev/null +++ b/src/OpmlOutline.h @@ -0,0 +1,54 @@ +/**************************************************************************************** + * Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> * + * Copyright (c) 2009-2010 Bart Cerneels <bart.cerneels@kde.org> * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see <http://www.gnu.org/licenses/>. * + ****************************************************************************************/ + +#ifndef OPMLOUTLINE_H +#define OPMLOUTLINE_H + +#include "amarok_export.h" + +#include <QMap> +#include <QString> + +class AMAROK_EXPORT OpmlOutline +{ + public: + OpmlOutline( OpmlOutline *parent = 0 ); + ~OpmlOutline() {} + + OpmlOutline *parent() const { return m_parent; } + bool isRootItem() const { return m_parent == 0; } + + QMap<QString,QString> attributes() const { return m_attributes; } + void addAttribute( const QString &key, const QString &value ) + { m_attributes.insert( key, value ); } + + QList<OpmlOutline *> children() const { return m_children; } + void setHasChildren( bool hasChildren ) { m_hasChildren = hasChildren; } + bool hasChildren() const { return m_hasChildren; } + void addChild( OpmlOutline *outline ) { m_children << outline; } + void addChildren( QList<OpmlOutline *> outlineList ) + { m_children << outlineList; } + + private: + OpmlOutline *m_parent; + QMap<QString,QString> m_attributes; + + bool m_hasChildren; + QList<OpmlOutline *> m_children; +}; + +#endif // OPMLOUTLINE_H diff --git a/src/OpmlParser.cpp b/src/OpmlParser.cpp index 0656adb..017c6c2 100644 --- a/src/OpmlParser.cpp +++ b/src/OpmlParser.cpp @@ -26,13 +26,6 @@ #include <KLocale> #include <threadweaver/Job.h> - -OpmlOutline::OpmlOutline( OpmlOutline *parent ) - : m_parent( parent ) - , m_hasChildren( false ) -{ -} - const QString OpmlParser::OPML_MIME = "text/x-opml+xml"; OpmlParser::OpmlParser( const QString &filename ) diff --git a/src/OpmlParser.h b/src/OpmlParser.h index 338bea7..28c6c0c 100644 --- a/src/OpmlParser.h +++ b/src/OpmlParser.h @@ -19,6 +19,7 @@ #define OPMLPARSER_H #include "amarok_export.h" +#include "OpmlOutline.h" #include <threadweaver/Job.h> @@ -27,34 +28,6 @@ #include <QString> #include <QStringList> -class AMAROK_EXPORT OpmlOutline -{ - public: - OpmlOutline( OpmlOutline *parent = 0 ); - ~OpmlOutline(); - - OpmlOutline *parent() const { return m_parent; } - bool isRootItem() const { return m_parent == 0; } - - QMap<QString,QString> attributes() const { return m_attributes; } - void addAttribute( const QString &key, const QString &value ) - { m_attributes.insert( key, value ); } - - QList<OpmlOutline *> children() const { return m_children; } - void setHasChildren( bool hasChildren ) { m_hasChildren = hasChildren; } - bool hasChildren() const { return m_hasChildren; } - void addChild( OpmlOutline *outline ) { m_children << outline; } - void addChildren( QList<OpmlOutline *> outlineList ) - { m_children << outlineList; } - - private: - OpmlOutline *m_parent; - QMap<QString,QString> m_attributes; - - bool m_hasChildren; - QList<OpmlOutline *> m_children; -}; - /** * Parser for OPML files. */ diff --git a/src/OpmlWriter.cpp b/src/OpmlWriter.cpp new file mode 100644 index 0000000..ed78de7 --- /dev/null +++ b/src/OpmlWriter.cpp @@ -0,0 +1,74 @@ +/**************************************************************************************** + * Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org> * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see <http://www.gnu.org/licenses/>. * + ****************************************************************************************/ + +#include "OpmlWriter.h" +#include "core/support/Debug.h" + +#include <KUrl> + +OpmlWriter::OpmlWriter( const OpmlOutline *rootOutline, QIODevice *device ) + : ThreadWeaver::Job() + , m_rootOutline( rootOutline ) +{ + m_xmlWriter = new QXmlStreamWriter( device ); +} + +void +OpmlWriter::run() +{ +#define _x m_xmlWriter + _x->setAutoFormatting( true ); + _x->writeStartDocument(); + _x->writeStartElement( "opml" ); + _x->writeAttribute( "version", "1.0" ); + _x->writeStartElement( "head" ); + //root outline is threated special, it's attributes will be the elements of <head> + QMapIterator<QString, QString> ai( m_rootOutline->attributes() ); //attributesIterator + while( ai.hasNext() ) + { + ai.next(); + _x->writeTextElement( ai.key(), ai.value() ); + } + _x->writeEndElement(); // head + _x->writeStartElement( "body" ); + foreach( const OpmlOutline *childOutline, m_rootOutline->children() ) + writeOutline( childOutline ); + _x->writeEndDocument(); //implicitly closes all open tags (opml & body) + emit result( 0 ); +} + +void +OpmlWriter::writeOutline( const OpmlOutline *outline ) +{ + bool hasChildren = outline->children().count() != 0; + if( hasChildren ) + _x->writeStartElement( "outline" ); + else + _x->writeEmptyElement( "outline" ); + QMapIterator<QString, QString> ai( outline->attributes() ); // attributesIterator + while( ai.hasNext() ) + { + ai.next(); + _x->writeAttribute( ai.key(), ai.value() ); + } + + if( hasChildren ) + { + foreach( const OpmlOutline *childOutline, outline->children() ) + writeOutline( childOutline ); + _x->writeEndElement(); // outline + } +} diff --git a/src/OpmlWriter.h b/src/OpmlWriter.h new file mode 100644 index 0000000..c28e76d --- /dev/null +++ b/src/OpmlWriter.h @@ -0,0 +1,55 @@ +/**************************************************************************************** + * Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org> * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see <http://www.gnu.org/licenses/>. * + ****************************************************************************************/ + +#ifndef OPMLWRITER_H +#define OPMLWRITER_H + +#include "OpmlOutline.h" + +#include <threadweaver/Job.h> +#include <KUrl> + +#include <QXmlStreamWriter> + +class OpmlWriter : public ThreadWeaver::Job +{ + Q_OBJECT + public: + OpmlWriter( const OpmlOutline *rootOutline, QIODevice *device ); + + /** + * The function that starts the actual work. Inherited from ThreadWeaver::Job + * Note the work is performed in a separate thread + * @return Returns true on success and false on failure + */ + void run(); + + QIODevice *device() { return m_xmlWriter->device(); } + + signals: + /** + * Signal emmited when writing is complete. + */ + void result( int error ); + + private: + void writeOutline( const OpmlOutline *outline ); + const OpmlOutline *m_rootOutline; + KUrl m_fileUrl; + QXmlStreamWriter *m_xmlWriter; +}; + +#endif // OPMLWRITER_H diff --git a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp index 7f4ff18..23a392b 100644 --- a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp +++ b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp @@ -31,10 +31,12 @@ #include "core/collections/support/SqlStorage.h" #include "statusbar/StatusBar.h" #include "SvgHandler.h" +#include "OpmlWriter.h" #include "ui_SqlPodcastProviderSettingsWidget.h" #include <KLocale> +#include <KFileDialog> #include <KIO/CopyJob> #include <KIO/DeleteJob> #include <KIO/Job> @@ -280,6 +282,13 @@ SqlPodcastProvider::providerActions() configureAction->setProperty( "popupdropper_svg_id", "configure" ); connect( configureAction, SIGNAL( triggered() ), this, SLOT( slotConfigureProvider() ) ); m_providerActions << configureAction; + + QAction *exportOpmlAction = new QAction( KIcon( "document-export" ), + i18n( "&Export to subscriptions to OPML file" ), + this + ); + connect( exportOpmlAction, SIGNAL(triggered()), SLOT(slotExportOpml()) ); + m_providerActions << exportOpmlAction; } return m_providerActions; @@ -294,6 +303,7 @@ SqlPodcastProvider::playlistActions( Playlists::PlaylistPtr playlist ) if( sqlChannel.isNull() ) return actions; + //TODO: add export OPML action for selected playlists only. Use the QAction::data() trick. if( m_configureChannelAction == 0 ) { m_configureChannelAction = new QAction( @@ -618,6 +628,52 @@ SqlPodcastProvider::slotConfigChanged() } void +SqlPodcastProvider::slotExportOpml() +{ + OpmlOutline *rootOutline = new OpmlOutline(); + //TODO: root OPML outline head + + //TODO: folder outline support + foreach( SqlPodcastChannelPtr channel, m_channels ) + { + OpmlOutline *channelOutline = new OpmlOutline( rootOutline ); + #define addAttr( k, v ) channelOutline->addAttribute( k, v ) + addAttr( "text", channel->title() ); + addAttr( "xmlUrl", channel->url().url() ); + rootOutline->addChild( channelOutline ); + } + + //TODO: add checkbox as widget to filedialog to include podcast settings. + KFileDialog fileDialog( KUrl( "kfiledialog:///podcast/amarok_podcasts.opml"), "*.opml", + The::mainWindow() ); + fileDialog.setMode( KFile::File ); + fileDialog.setCaption( i18n( "Select file for OPML export") ); + if( fileDialog.exec() != KDialog::Accepted ) + return; + + KUrl filePath = fileDialog.selectedUrl(); + + QFile *opmlFile = new QFile( filePath.toLocalFile(), this ); + if( !opmlFile->open( QIODevice::WriteOnly | QIODevice::Truncate ) ) + { + error() << "could not open OPML file " << filePath.url(); + return; + } + OpmlWriter *opmlWriter = new OpmlWriter( rootOutline, opmlFile ); + connect( opmlWriter, SIGNAL(result(int)), SLOT(slotOpmlWriterDone(int)) ); + opmlWriter->run(); +} + +void +SqlPodcastProvider::slotOpmlWriterDone( int result ) +{ + OpmlWriter *writer = qobject_cast<OpmlWriter *>( QObject::sender() ); + Q_ASSERT( writer ); + writer->device()->close(); + delete writer; +} + +void SqlPodcastProvider::configureChannel( Podcasts::SqlPodcastChannelPtr sqlChannel ) { if( !sqlChannel ) diff --git a/src/core-impl/podcasts/sql/SqlPodcastProvider.h b/src/core-impl/podcasts/sql/SqlPodcastProvider.h index 09e9e2a..b650151 100644 --- a/src/core-impl/podcasts/sql/SqlPodcastProvider.h +++ b/src/core-impl/podcasts/sql/SqlPodcastProvider.h @@ -100,6 +100,7 @@ class SqlPodcastProvider : public Podcasts::PodcastProvider void slotDownloadProgress( KJob *job, unsigned long percent ); void slotWriteTagsToFiles(); void slotConfigChanged(); + void slotExportOpml(); signals: void totalPodcastDownloadProgress( int progress ); @@ -113,6 +114,7 @@ class SqlPodcastProvider : public Podcasts::PodcastProvider const QString &description, Podcasts::PodcastReader* reader ); void slotStatusBarSorryMessage( const QString &message ); + void slotOpmlWriterDone( int result ); private: void configureProvider(); i have updated to version 2.4 of amarok (ubuntu packages) but i cannot find the export function! where is it supposed to be? i can only see the button for opml import. thanks in advance. Created attachment 56726 [details]
screenshot of location export to OPML action.
The export action is specific to the SQL podcasts and so you'll find it as an action there. These actions show on the playlist-root items representing the sources of the content when you hover over them. Disable "Merged View" or you won't see them.
I'll attach a screenshot so you know what to look for.
hi Bart, thanks for the screenshot. i finally found the export button :-) Created attachment 89502 [details]
No OPML export in Amarok 2.8 found
The OPML export option (shown in screenshot from 2011-02-01) can not be found in Amarok 2.8 (screenshot from 2014-11-08) anymore. If there is no other location, please re-open this bug. (In reply to bjoernv from comment #26) > The OPML export option (shown in screenshot from 2011-02-01) can not be > found in Amarok 2.8 (screenshot from 2014-11-08) anymore. If there is no > other location, please re-open this bug. No, please do not reopen such an old bug, which was actually a feature request, but file a new bug FWIW: the OPML Import button is right there, on top of the podcast panel on the right, so nothing to report anway. No, Myriam. The OPML _import_ button is there, but there is not OPML export button. Then please open a new report. Currently you can export to m3u, pls and xspf, just right-click on the subscription. As I found no such report I opened one here: https://bugs.kde.org/show_bug.cgi?id=371192 |