feedgenerator: work on podcast gen started
This commit is contained in:
parent
8ad2e87985
commit
38feddbe2d
4 changed files with 214 additions and 10 deletions
|
@ -10,13 +10,16 @@
|
|||
'''
|
||||
|
||||
from feedgenerator.feed import FeedGenerator
|
||||
from feedgenerator.podcast import PodcastGenerator
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 2 or not (
|
||||
sys.argv[1].endswith('rss') or sys.argv[1].endswith('atom') ):
|
||||
sys.argv[1].endswith('rss') \
|
||||
or sys.argv[1].endswith('atom') \
|
||||
or sys.argv[1].endswith('podcast') ):
|
||||
print 'Usage: %s ( <file>.atom | atom | <file>.rss | rss )' % \
|
||||
'pythom -m feedgenerator'
|
||||
print ''
|
||||
|
@ -29,7 +32,7 @@ if __name__ == '__main__':
|
|||
|
||||
arg = sys.argv[1]
|
||||
|
||||
fg = FeedGenerator()
|
||||
fg = PodcastGenerator() if arg.endswith('podcast') else FeedGenerator()
|
||||
fg.id('http://lernfunk.de/_MEDIAID_123')
|
||||
fg.title('Testfeed')
|
||||
fg.author( {'name':'Lars Kiesow','email':'lkiesow@uos.de'} )
|
||||
|
@ -60,6 +63,10 @@ if __name__ == '__main__':
|
|||
print fg.atom_str(pretty=True)
|
||||
elif arg == 'rss':
|
||||
print fg.rss_str(pretty=True)
|
||||
elif arg == 'podcast':
|
||||
fg.itunes_author('Lars Kiesow')
|
||||
fg.itunes_category('Technology', 'Podcasting')
|
||||
print fg.podcast_str(pretty=True)
|
||||
elif arg.endswith('atom'):
|
||||
fg.atom_file(arg)
|
||||
elif arg.endswith('rss'):
|
||||
|
|
|
@ -16,7 +16,7 @@ import dateutil.tz
|
|||
from feedgenerator.util import ensure_format
|
||||
|
||||
|
||||
class FeedEntry:
|
||||
class FeedEntry(object):
|
||||
|
||||
# ATOM
|
||||
# required
|
||||
|
|
|
@ -17,7 +17,7 @@ from feedgenerator.entry import FeedEntry
|
|||
from feedgenerator.util import ensure_format
|
||||
|
||||
|
||||
class FeedGenerator:
|
||||
class FeedGenerator(object):
|
||||
|
||||
__feed_entries = []
|
||||
|
||||
|
@ -69,7 +69,7 @@ class FeedGenerator:
|
|||
|
||||
|
||||
|
||||
def __create_atom(self):
|
||||
def _create_atom(self):
|
||||
'''Create a ATOM feed xml structure containing all previously set fields.
|
||||
|
||||
:returns: Tuple containing the feed root element and the element tree.
|
||||
|
@ -176,7 +176,7 @@ class FeedGenerator:
|
|||
properly indented.
|
||||
:returns: String representation of the ATOM feed.
|
||||
'''
|
||||
feed, doc = self.__create_atom()
|
||||
feed, doc = self._create_atom()
|
||||
return etree.tostring(feed, pretty_print=pretty)
|
||||
|
||||
|
||||
|
@ -185,12 +185,12 @@ class FeedGenerator:
|
|||
|
||||
:param filename: Name of file to write.
|
||||
'''
|
||||
feed, doc = self.__create_atom()
|
||||
feed, doc = self._create_atom()
|
||||
with open(filename, 'w') as f:
|
||||
doc.write(f)
|
||||
|
||||
|
||||
def __create_rss(self):
|
||||
def _create_rss(self):
|
||||
'''Create an RSS feed xml structure containing all previously set fields.
|
||||
|
||||
:returns: Tuple containing the feed root element and the element tree.
|
||||
|
@ -317,7 +317,7 @@ class FeedGenerator:
|
|||
properly indented.
|
||||
:returns: String representation of the RSS feed.
|
||||
'''
|
||||
feed, doc = self.__create_rss()
|
||||
feed, doc = self._create_rss()
|
||||
return etree.tostring(feed, pretty_print=pretty)
|
||||
|
||||
|
||||
|
@ -326,7 +326,7 @@ class FeedGenerator:
|
|||
|
||||
:param filename: Name of file to write.
|
||||
'''
|
||||
feed, doc = self.__create_rss()
|
||||
feed, doc = self._create_rss()
|
||||
with open(filename, 'w') as f:
|
||||
doc.write(f)
|
||||
|
||||
|
|
197
feedgenerator/podcast.py
Normal file
197
feedgenerator/podcast.py
Normal file
|
@ -0,0 +1,197 @@
|
|||
#!/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
feedgenerator.podcast
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Extends the feedgenerator to produce podcasts.
|
||||
|
||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
||||
|
||||
:license: FreeBSD and LGPL, see LICENSE for more details.
|
||||
'''
|
||||
|
||||
from lxml import etree
|
||||
from datetime import datetime
|
||||
import dateutil.parser
|
||||
import dateutil.tz
|
||||
from feedgenerator.feed import FeedGenerator
|
||||
from feedgenerator.util import ensure_format
|
||||
|
||||
|
||||
class PodcastGenerator(FeedGenerator):
|
||||
|
||||
|
||||
## ITunes tags
|
||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||
__itunes_author = None
|
||||
__itunes_block = None
|
||||
__itunes_category = None
|
||||
|
||||
|
||||
|
||||
|
||||
def __create_podcast(self):
|
||||
'''Create an RSS feed xml structure containing all previously set fields.
|
||||
|
||||
:returns: Tuple containing the feed root element and the element tree.
|
||||
'''
|
||||
rss_feed, _ = super(PodcastGenerator,self)._create_rss()
|
||||
# Replace the root element to add the itunes namespace
|
||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||
feed = etree.Element('rss', version='2.0',
|
||||
nsmap={
|
||||
'atom' :'http://www.w3.org/2005/Atom',
|
||||
'itunes':ITUNES_NS} )
|
||||
feed[:] = rss_feed[:]
|
||||
channel = feed[0]
|
||||
doc = etree.ElementTree(feed)
|
||||
|
||||
if self.__itunes_author:
|
||||
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
||||
author.text = self.__itunes_author
|
||||
|
||||
if not self.__itunes_block is None:
|
||||
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
||||
block.text = 'yes' if self.__itunes_block else 'no'
|
||||
|
||||
if self.__itunes_category:
|
||||
category = etree.SubElement(channel, '{%s}category' % ITUNES_NS)
|
||||
category.attrib['text'] = self.__itunes_category['cat']
|
||||
if self.__itunes_category.get('sub'):
|
||||
subcategory = etree.SubElement(category, '{%s}category' % ITUNES_NS)
|
||||
subcategory.attrib['text'] = self.__itunes_category['sub']
|
||||
|
||||
return feed, doc
|
||||
|
||||
|
||||
def podcast_str(self, pretty=False):
|
||||
'''Generates an RSS feed and returns the feed XML as string.
|
||||
|
||||
:param pretty: If the feed should be split into multiple lines and
|
||||
properly indented.
|
||||
:returns: String representation of the RSS feed.
|
||||
'''
|
||||
feed, doc = self.__create_podcast()
|
||||
return etree.tostring(feed, pretty_print=pretty)
|
||||
|
||||
|
||||
def podcast_file(self, filename):
|
||||
'''Generates an RSS feed and write the resulting XML to a file.
|
||||
|
||||
:param filename: Name of file to write.
|
||||
'''
|
||||
feed, doc = self.__create_podcast()
|
||||
with open(filename, 'w') as f:
|
||||
doc.write(f)
|
||||
|
||||
|
||||
def itunes_author(self, itunes_author=None):
|
||||
'''Get or set the itunes:author. The content of this tag is shown in the
|
||||
Artist column in iTunes. If the tag is not present, iTunes uses the
|
||||
contents of the <author> tag. If <itunes:author> is not present at the
|
||||
feed level, iTunes will use the contents of <managingEditor>.
|
||||
|
||||
:param itunes_author: The author of the podcast.
|
||||
:returns: The author of the podcast.
|
||||
'''
|
||||
if not itunes_author is None:
|
||||
self.__itunes_author = itunes_author
|
||||
return self.__itunes_author
|
||||
|
||||
|
||||
def itunes_block(self, itunes_block=None):
|
||||
'''Get or set the ITunes block attribute. Use this to prevent the entire
|
||||
podcast from appearing in the iTunes podcast directory.
|
||||
|
||||
:param itunes_block: Block the podcast.
|
||||
:returns: If the podcast is blocked.
|
||||
'''
|
||||
if not itunes_block is None:
|
||||
self.__itunes_block = itunes_block
|
||||
return self.__itunes_block
|
||||
|
||||
|
||||
def itunes_category(self, itunes_category=None, itunes_subcategory=None):
|
||||
'''Get or set the ITunes category which appears in the category column
|
||||
and in iTunes Store Browser.
|
||||
|
||||
The (sub-)category has to be one from the values defined at
|
||||
http://www.apple.com/itunes/podcasts/specs.html#categories
|
||||
|
||||
:param itunes_category: Category of the podcast.
|
||||
:param itunes_subcategory: Subcategory of the podcast.
|
||||
:returns: Category data of the podcast.
|
||||
'''
|
||||
if not itunes_category is None:
|
||||
if not itunes_category in self._itunes_categories.keys():
|
||||
raise ValueError('Invalid category')
|
||||
cat = {'cat':itunes_category}
|
||||
if not itunes_subcategory is None:
|
||||
if not itunes_subcategory in self._itunes_categories[itunes_category]:
|
||||
raise ValueError('Invalid subcategory')
|
||||
cat['sub'] = itunes_subcategory
|
||||
self.__itunes_category = cat
|
||||
return self.__itunes_category
|
||||
|
||||
|
||||
def itunes_image(self, itunes_image=None):
|
||||
'''Get or set the image for the podcast. This tag specifies the artwork
|
||||
for your podcast. Put the URL to the image in the href attribute. iTunes
|
||||
prefers square .jpg images that are at least 1400x1400 pixels, which is
|
||||
different from what is specified for the standard RSS image tag. In order
|
||||
for a podcast to be eligible for an iTunes Store feature, the
|
||||
accompanying image must be at least 1400x1400 pixels.
|
||||
|
||||
iTunes supports images in JPEG and PNG formats with an RGB color space
|
||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||
<itunes:image> tag is not present, iTunes will use the contents of the
|
||||
RSS image tag.
|
||||
|
||||
If you change your podcast’s image, also change the file’s name. iTunes
|
||||
may not change the image if it checks your feed and the image URL is the
|
||||
same. The server hosting your cover art image must allow HTTP head
|
||||
requests for iTS to be able to automatically update your cover art.
|
||||
|
||||
:param itunes_image: Image of the podcast.
|
||||
:returns: Image of the podcast.
|
||||
'''
|
||||
if not itunes_image is None:
|
||||
if not ( itunes_image.endswith('.jpg') or itunes_image.endswith('.png') ):
|
||||
ValueError('Image file must be png or jpg')
|
||||
self.__itunes_image = itunes_image
|
||||
return self.__itunes_image
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_itunes_categories = {
|
||||
'Arts': [ 'Design', 'Fashion & Beauty', 'Food', 'Literature',
|
||||
'Performing Arts', 'Visual Arts' ],
|
||||
'Business' : [ 'Business News', 'Careers', 'Investing',
|
||||
'Management & Marketing', 'Shopping' ],
|
||||
'Comedy' : [],
|
||||
'Education' : [ 'Education', 'Education Technology',
|
||||
'Higher Education', 'K-12', 'Language Courses', 'Training' ],
|
||||
'Games & Hobbies' : [ 'Automotive', 'Aviation', 'Hobbies',
|
||||
'Other Games', 'Video Games' ],
|
||||
'Government & Organizations' : [ 'Local', 'National', 'Non-Profit',
|
||||
'Regional' ],
|
||||
'Health' : [ 'Alternative Health', 'Fitness & Nutrition', 'Self-Help',
|
||||
'Sexuality' ],
|
||||
'Kids & Family' : [],
|
||||
'Music' : [],
|
||||
'News & Politics' : [],
|
||||
'Religion & Spirituality' : [ 'Buddhism', 'Christianity', 'Hinduism',
|
||||
'Islam', 'Judaism', 'Other', 'Spirituality' ],
|
||||
'Science & Medicine' : [ 'Medicine', 'Natural Sciences',
|
||||
'Social Sciences' ],
|
||||
'Society & Culture' : [ 'History', 'Personal Journals', 'Philosophy',
|
||||
'Places & Travel' ],
|
||||
'Sports & Recreation' : [ 'Amateur', 'College & High School',
|
||||
'Outdoor', 'Professional' ],
|
||||
'Technology' : [ 'Gadgets', 'Tech News', 'Podcasting',
|
||||
'Software How-To' ],
|
||||
'TV & Film' : []
|
||||
}
|
Loading…
Reference in a new issue