The branch, eden has been updated
via bea2237630b203c94c271bd76ad94c1f27292b18 (commit)
from 87fc7b027d8ad999a513bd8e07fbb0ab424524d7 (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=bea2237630b203c94c271bd76ad94c1f27292b18
commit bea2237630b203c94c271bd76ad94c1f27292b18
Author: beenje <[email protected]>
Date: Tue Jan 15 22:59:45 2013 +0100
[plugin.video.academicearth] updated to version 1.3.3
diff --git a/plugin.video.academicearth/README.md
b/plugin.video.academicearth/README.md
index ca0fef4..da1699c 100644
--- a/plugin.video.academicearth/README.md
+++ b/plugin.video.academicearth/README.md
@@ -1,6 +1,7 @@
Academic Earth for XBMC
=======================
-version 1.2.0
+
+[](http://travis-ci.org/jbeluch/xbmc-academic-earth)
### Summary
diff --git a/plugin.video.academicearth/addon.py
b/plugin.video.academicearth/addon.py
index cf4104d..e08f191 100755
--- a/plugin.video.academicearth/addon.py
+++ b/plugin.video.academicearth/addon.py
@@ -15,8 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from operator import itemgetter
from xbmcswift2 import Plugin
-from resources.lib.academicearth.api import (AcademicEarth, Subject, Course,
- Lecture)
+from resources.lib.academicearth import api
PLUGIN_NAME = 'Academic Earth'
PLUGIN_ID = 'plugin.video.academicearth'
@@ -28,14 +27,35 @@ def show_index():
items = [
{'label': plugin.get_string(30200),
'path': plugin.url_for('show_subjects')},
+
+ {'label': plugin.get_string(30201),
+ 'path': plugin.url_for('show_universities')},
+
+ {'label': plugin.get_string(30202),
+ 'path': plugin.url_for('show_speakers')},
]
return items
[email protected]('/universities/')
+def show_universities():
+ ae = api.AcademicEarth()
+ unis = ae.get_universities()
+
+ items = [{
+ 'label': uni.name,
+ 'path': plugin.url_for('show_university_info', url=uni.url),
+ 'icon': uni.icon,
+ } for uni in unis]
+
+ sorted_items = sorted(items, key=lambda item: item['label'])
+ return sorted_items
+
+
@plugin.route('/subjects/')
def show_subjects():
- api = AcademicEarth()
- subjects = api.get_subjects()
+ ae = api.AcademicEarth()
+ subjects = ae.get_subjects()
items = [{
'label': subject.name,
@@ -46,20 +66,37 @@ def show_subjects():
return sorted_items
[email protected]('/subjects/<url>/')
-def show_subject_info(url):
- subject = Subject.from_url(url)
[email protected]('/speakers/')
+def show_speakers():
+ ae = api.AcademicEarth()
+ speakers = ae.get_speakers()
+
+ items = [{
+ 'label': speaker.name,
+ 'path': plugin.url_for('show_speaker_info', url=speaker.url),
+ } for speaker in speakers]
+
+ sorted_items = sorted(items, key=lambda item: item['label'])
+ return sorted_items
+
+
[email protected]('/subjects/<url>/', 'show_subject_info', {'cls': api.Subject})
[email protected]('/universities/<url>/', 'show_university_info',
+ {'cls': api.University})
[email protected]('/speakers/<url>/', 'show_speaker_info', {'cls': api.Speaker})
+def show_info(url, cls):
+ uni = cls.from_url(url)
courses = [{
'label': course.name,
'path': plugin.url_for('show_course_info', url=course.url),
- } for course in subject.courses]
+ } for course in uni.courses]
lectures = [{
'label': 'Lecture: %s' % lecture.name,
'path': plugin.url_for('play_lecture', url=lecture.url),
'is_playable': True,
- } for lecture in subject.lectures]
+ } for lecture in uni.lectures]
by_label = itemgetter('label')
items = sorted(courses, key=by_label) + sorted(lectures, key=by_label)
@@ -68,7 +105,7 @@ def show_subject_info(url):
@plugin.route('/courses/<url>/')
def show_course_info(url):
- course = Course.from_url(url)
+ course = api.Course.from_url(url)
lectures = [{
'label': 'Lecture: %s' % lecture.name,
'path': plugin.url_for('play_lecture', url=lecture.url),
@@ -80,8 +117,9 @@ def show_course_info(url):
@plugin.route('/lectures/<url>/')
def play_lecture(url):
- lecture = Lecture.from_url(url)
- url = 'plugin://plugin.video.youtube/?action=play_video&videoid=%s' %
lecture.youtube_id
+ lecture = api.Lecture.from_url(url)
+ ptn = 'plugin://plugin.video.youtube/?action=play_video&videoid=%s'
+ url = ptn % lecture.youtube_id
plugin.log.info('Playing url: %s' % url)
plugin.set_resolved_url(url)
diff --git a/plugin.video.academicearth/addon.xml
b/plugin.video.academicearth/addon.xml
index d3c2ee7..572651a 100644
--- a/plugin.video.academicearth/addon.xml
+++ b/plugin.video.academicearth/addon.xml
@@ -1,15 +1,16 @@
-<addon id="plugin.video.academicearth" name="Academic Earth"
provider-name="Jonathan Beluch (jbel)" version="1.3.1">
+<addon id="plugin.video.academicearth" name="Academic Earth"
provider-name="Jonathan Beluch (jbel)" version="1.3.3">
<requires>
<import addon="xbmc.python" version="2.0" />
- <import addon="script.module.beautifulsoup" version="3.0.8" />
- <import addon="script.module.xbmcswift2" version="1.1.1" />
- <import addon="plugin.video.youtube" version="3.1.0" />
+ <import addon="script.module.beautifulsoup" version="3.2.0" />
+ <import addon="script.module.xbmcswift2" version="1.3.1" />
+ <import addon="plugin.video.youtube" version="3.4.1" />
</requires>
<extension library="addon.py" point="xbmc.python.pluginsource">
<provides>video</provides>
</extension>
<extension point="xbmc.addon.metadata">
<platform>all</platform>
+ <language />
<summary>Watch lectures from Academic Earth
(http://academicearth.org)</summary>
<description>Browse online courses and lectures from the world's top
scholars.</description>
</extension>
diff --git a/plugin.video.academicearth/changelog.txt
b/plugin.video.academicearth/changelog.txt
index 3a689e6..8f86539 100644
--- a/plugin.video.academicearth/changelog.txt
+++ b/plugin.video.academicearth/changelog.txt
@@ -1,3 +1,9 @@
+Version 1.3.3
+* Add missing language tag to addon.xml.
+
+Version 1.3.2
+* Update addon to reflect website changes.
+
Version 1.3.1
* New version number to force updates
diff --git a/plugin.video.academicearth/resources/lib/academicearth/api.py
b/plugin.video.academicearth/resources/lib/academicearth/api.py
index dc17d6c..df8b346 100644
--- a/plugin.video.academicearth/resources/lib/academicearth/api.py
+++ b/plugin.video.academicearth/resources/lib/academicearth/api.py
@@ -1,14 +1,11 @@
'''
-
academicearth.api
~~~~~~~~~~~~~~~~~
This module contains the API classes and method to parse information from
the Academic Earth website.
-
'''
-from scraper import (get_subjects, get_courses, get_subject_metadata,
- get_course_metadata, get_lecture_metadata)
+import scraper
class AcademicEarth(object):
@@ -21,121 +18,88 @@ class AcademicEarth(object):
def get_subjects(self):
'''Returns a list of subjects available on the website.'''
- return [Subject(**info) for info in get_subjects()]
+ return [Subject(info, partial=True) for info
+ in scraper.Subject.get_subjects_partial()]
+ def get_universities(self):
+ '''Returns a list of universities available on the website.'''
+ return [University(info, partial=True) for info
+ in scraper.University.get_universities_partial()]
-class Subject(object):
- '''Object representing an Academic Earth subject.'''
+ def get_speakers(self):
+ '''Returns a list of speakers available on the website.'''
+ return [Speaker(info, partial=True) for info
+ in scraper.Speaker.get_speakers_partial()]
- def __init__(self, url, name=None):
- self.url = url
- self._name = name
- self._courses = None
- self._lectures = None
- self._loaded = False
- @classmethod
- def from_url(cls, url):
- return cls(url=url)
+class _BaseAPIObject(object):
+
+ scraper_cls = scraper.University
+
+ def __init__(self, info, partial=False):
+ self._setattrs(info)
+ self.partial = partial
+
+ def __getattr__(self, name):
+ if self.partial:
+ self.load()
+ return getattr(self, name)
+ raise AttributeError, '%s instance has no attribute %s' %
(self.__class__.__name__, name)
def __repr__(self):
- return u"<Subject '%s'>" % self.name
-
- def _load_metadata(self):
- resp = get_subject_metadata(self.url)
- if not self._name:
- self._name = resp['name']
- self._courses = [Course(**info) for info in resp['courses']]
- self._lectures = [Lecture(**info) for info in resp['lectures']]
- self._description = resp['description']
- self._loaded = True
-
- @property
- def name(self):
- '''Subject name'''
- if not self._name:
- self._load_metadata()
- return self._name
-
- @property
- def courses(self):
- '''List of courses available for this subject'''
- if not self._loaded:
- self._load_metadata()
- return self._courses
-
- @property
- def lectures(self):
- '''List of lectures available for this subject'''
- if not self._loaded:
- self._load_metadata()
- return self._lectures
-
-
-class Course(object):
-
- def __init__(self, url, name=None, **kwargs):
- self.url = url
- self._name = name
- self._loaded = False
- self._lectures = None
+ return u"<%s '%s'>" % (self.__class__.__name__, self.name)
+
+ def _setattrs(self, info):
+ for attr_name, attr_value in info.items():
+ setattr(self, attr_name, attr_value)
+
+ def load(self):
+ full_info = self.scraper_cls.from_url(url)
+ self._setattrs(info)
+ self.partial = False
@classmethod
def from_url(cls, url):
- return cls(url=url)
+ info = cls.scraper_cls.from_url(url)
+ return cls(info)
- def __repr__(self):
- return u"<Course '%s'>" % self.name
+class _ObjectWithCoursesLectures(_BaseAPIObject):
- def _load_metadata(self):
- resp = get_course_metadata(self.url)
- if not self._name:
- self._name = resp['name']
- self._lectures = [Lecture(**info) for info in resp['lectures']]
- self._loaded = True
+ def _setattrs(self, info):
+ for attr_name, attr_value in info.items():
+ if attr_name == 'courses':
+ setattr(self, attr_name, [Course(info) for info in attr_value])
+ elif attr_name == 'lectures':
+ setattr(self, attr_name, [Lecture(info) for info in
attr_value])
+ else:
+ setattr(self, attr_name, attr_value)
- @property
- def name(self):
- if not self._name:
- self._load_metadata()
- return self._name
+class Subject(_ObjectWithCoursesLectures):
- @property
- def lectures(self):
- if not self._loaded:
- self._load_metadata()
- return self._lectures
+ scraper_cls = scraper.Subject
-class Lecture(object):
+class University(_ObjectWithCoursesLectures):
- def __init__(self, url, name=None, **kwargs):
- self.url = url
- self._name = name
- self._loaded = False
+ scraper_cls = scraper.University
- @classmethod
- def from_url(cls, url):
- return cls(url=url)
- def __repr__(self):
- return u"<Lecture '%s'>" % self.name
-
- def _load_metadata(self):
- resp = get_lecture_metadata(self.url)
- if not self._name:
- self._name = resp['name']
- self._youtube_id = resp['youtube_id']
- self._loaded = True
-
- @property
- def name(self):
- if not self._name:
- self._load_metadata()
- return self._name
-
- @property
- def youtube_id(self):
- if not self._loaded:
- self._load_metadata()
- return self._youtube_id
+class Speaker(_ObjectWithCoursesLectures):
+
+ scraper_cls = scraper.Speaker
+
+
+class Course(_BaseAPIObject):
+
+ scraper_cls = scraper.Course
+
+ def _setattrs(self, info):
+ for attr_name, attr_value in info.items():
+ if attr_name == 'lectures':
+ setattr(self, attr_name, [Lecture(info) for info in
attr_value])
+ else:
+ setattr(self, attr_name, attr_value)
+
+
+class Lecture(_BaseAPIObject):
+ scraper_cls = scraper.Lecture
diff --git a/plugin.video.academicearth/resources/lib/academicearth/scraper.py
b/plugin.video.academicearth/resources/lib/academicearth/scraper.py
index 1e14ec7..334423c 100644
--- a/plugin.video.academicearth/resources/lib/academicearth/scraper.py
+++ b/plugin.video.academicearth/resources/lib/academicearth/scraper.py
@@ -4,22 +4,27 @@
This module contains some functions which do the website scraping for the
API module. You shouldn't have to use this module directly.
+
+ This module is meant to emulate responses from a "virtual" API server. All
+ website scraping is handled in this module, and clean dict responses are
+ returned. The `api` module, acts as a python client library for this
+ module's API.
'''
import re
-from urllib2 import urlopen
-from urlparse import urljoin
+import urllib
+import urlparse
from BeautifulSoup import BeautifulSoup as BS
BASE_URL = 'http://www.academicearth.org'
def _url(path):
- '''Returns a full url for the given path'''
- return urljoin(BASE_URL, path)
+ '''Returns a full url for the given path'''
+ return urlparse.urljoin(BASE_URL, path)
def get(url):
'''Performs a GET request for the given url and returns the response'''
- conn = urlopen(url)
+ conn = urllib.urlopen(url)
resp = conn.read()
conn.close()
return resp
@@ -36,68 +41,208 @@ def make_showall_url(url):
'''
if not url.endswith('/'):
url += '/'
- return url + 'page:1/show:500'
-
-
-def get_subjects():
- '''Returns a list of subjects for the website. Each subject is a dict with
- keys of 'name' and 'url'.
- '''
- url = _url('subjects')
- html = _html(url)
- subjs = html.findAll('a',
- {'href': lambda attr_value: attr_value.startswith('/subjects/')
- and len(attr_value) > len('/subjects/')})
-
- # subjs will contain some duplicates so we will key on url
- items = []
- urls = set()
- for subj in subjs:
- url = _url(subj['href'])
- if url not in urls:
- urls.add(url)
- items.append({
- 'name': subj.string,
- 'url': url,
- })
-
- # filter out any items that didn't parse correctly
- return [item for item in items if item['name'] and item['url']]
-
-
-def get_subject_metadata(subject_url):
- '''Returns metadata for a subject parsed from the given url'''
- html = _html(make_showall_url(subject_url))
- name = get_subject_name(html)
- courses = get_courses(html)
- lectures = get_lectures(html)
- desc = get_subject_description(html)
-
- return {
- 'name': name,
- 'courses': courses,
- 'lectures': lectures,
- 'description': desc,
- }
-
-
-def get_subject_name(html):
- return html.find('article').h1.text
-
-
-def get_course_name(html):
- return html.find('section', {'class': 'pagenav'}).span.text
+ return url + 'page:1/show:1000'
+
+
+class University(object):
+
+ listing_url = make_showall_url(_url('universities'))
+
+ @classmethod
+ def get_universities_partial(cls):
+ '''Returns a list of universities available on the website.'''
+ html = _html(cls.listing_url)
+
+ parent = html.find('div', {'class': 'lectureVideosIndex'}).div
+ unis = parent.findAll('div')
+
+ return [{
+ 'name': uni.findAll('a')[-1].string.strip(),
+ 'url': _url(uni.a['href']),
+ 'icon': uni.img['src'],
+ } for uni in unis]
+
+ @classmethod
+ def from_url(cls, url):
+ '''Returns metadata for a university parsed from the given url'''
+ html = _html(make_showall_url(url))
+ name = cls.get_name(html)
+ desc = cls.get_description(html)
+ courses = cls.get_courses(html)
+ lectures = cls.get_lectures(html)
+
+ return {
+ 'name': name,
+ 'courses': courses,
+ 'lectures': lectures,
+ 'description': desc,
+ }
+
+ @staticmethod
+ def get_name(html):
+ return html.find('div', {'class': 'title'}).h1.text
+
+ @staticmethod
+ def get_description(html):
+ desc_nodes = html.find('div', {'class':
'courseDetails'}).findAll('span')
+ return '\n'.join(node.text.strip() for node in desc_nodes)
+
+ @staticmethod
+ def get_courses(html):
+ return _get_courses_or_lectures('course', html)
+
+ @staticmethod
+ def get_lectures(html):
+ return _get_courses_or_lectures('lecture', html)
+
+
+class Subject(object):
+
+ listing_url = _url('subjects')
+
+ @classmethod
+ def get_subjects_partial(cls):
+ '''Returns a list of subjects for the website. Each subject is a dict
with
+ keys of 'name' and 'url'.
+ '''
+ html = _html(cls.listing_url)
+ subjs = html.findAll('a',
+ {'href': lambda attr_value: attr_value.startswith('/subjects/')
+ and len(attr_value) >
len('/subjects/')})
+
+ # subjs will contain some duplicates so we will key on url
+ items = []
+ urls = set()
+ for subj in subjs:
+ url = _url(subj['href'])
+ if url not in urls:
+ urls.add(url)
+ items.append({
+ 'name': subj.string,
+ 'url': url,
+ })
+
+ # filter out any items that didn't parse correctly
+ return [item for item in items if item['name'] and item['url']]
+
+ @classmethod
+ def from_url(cls, url):
+ '''Returns metadata for a subject parsed from the given url'''
+ html = _html(make_showall_url(url))
+ name = cls.get_name(html)
+ desc = cls.get_description(html)
+ courses = cls.get_courses(html)
+ lectures = cls.get_lectures(html)
+
+ return {
+ 'name': name,
+ 'courses': courses,
+ 'lectures': lectures,
+ 'description': desc,
+ }
+
+ @staticmethod
+ def get_name(html):
+ return html.find('article').h1.text
+
+ @staticmethod
+ def get_description(html):
+ return html.find('article').p.text
+
+ @staticmethod
+ def get_courses(html):
+ return _get_courses_or_lectures('course', html)
+
+ @staticmethod
+ def get_lectures(html):
+ return _get_courses_or_lectures('lecture', html)
+
+
+class Speaker(object):
+
+ listing_url = make_showall_url(_url('speakers'))
+
+ @classmethod
+ def get_speakers_partial(cls):
+ '''Returns a list of speakers available on the website.'''
+ html = _html(cls.listing_url)
+ speakers = html.findAll('div', {'class': 'blue-hover'})
+ return [{
+ 'name': spkr.div.string.strip(),
+ 'url': _url(spkr.a['href']),
+ } for spkr in speakers]
+
+ @classmethod
+ def from_url(cls, url):
+ '''Returns metadata for a speaker parsed from the given url'''
+ html = _html(make_showall_url(url))
+ name = cls.get_name(html)
+ courses = cls.get_courses(html)
+ lectures = cls.get_lectures(html)
+
+ return {
+ 'name': name,
+ 'courses': courses,
+ 'lectures': lectures,
+ }
+
+ @staticmethod
+ def get_name(html):
+ return html.find('div', {'class': 'title'}).h1.text
+
+ @staticmethod
+ def get_courses(html):
+ return _get_courses_or_lectures('course', html)
+
+ @staticmethod
+ def get_lectures(html):
+ return _get_courses_or_lectures('lecture', html)
+
+
+class Course(object):
+
+ @classmethod
+ def from_url(cls, url):
+ html = _html(make_showall_url(url))
+ lectures = cls.get_lectures(html)
+ name = cls.get_name(html)
+ return {
+ 'lectures': lectures,
+ 'name': name,
+ }
+
+ @staticmethod
+ def get_name(html):
+ return html.find('section', {'class': 'pagenav'}).span.text
+
+ @staticmethod
+ def get_lectures(html):
+ return _get_courses_or_lectures('lecture', html)
+
+
+class Lecture(object):
+
+ @classmethod
+ def from_url(cls, url):
+ html = _html(url)
+ name = cls.get_name(html)
+ youtube_id = cls.parse_youtube_id(html)
+ return {
+ 'name': name,
+ 'youtube_id': youtube_id
+ }
+
+ @staticmethod
+ def get_name(html):
+ return html.find('section', {'class': 'pagenav'}).span.text
+
+ @staticmethod
+ def parse_youtube_id(html):
+ url = html.find('a',
+ href=lambda h: h.startswith('http://www.youtube.com'))
+ return url['href'].split('=')[1]
-def get_lecture_name(html):
- return html.find('section', {'class': 'pagenav'}).span.text
-
-
-def get_subject_description(html):
- desc_nodes = html.find('article').findAll('span')
- return '\n'.join(node.text.strip() for node in desc_nodes)
-
-
def _get_courses_or_lectures(class_type, html):
'''class_type can be 'course' or 'lecture'.'''
nodes = html.findAll('div', {'class': class_type})
@@ -111,41 +256,3 @@ def _get_courses_or_lectures(class_type, html):
} for node in nodes]
return items
-
-
-def get_lectures(html):
- return _get_courses_or_lectures('lecture', html)
-
-
-def get_courses(html):
- return _get_courses_or_lectures('course', html)
-
-
-def get_course_metadata(course_url):
- html = _html(make_showall_url(course_url))
- lectures = get_lectures(html)
- name = get_course_name(html)
- return {
- 'lectures': lectures,
- 'name': name,
- }
-
-
-def get_lecture_metadata(lecture_url):
- html = _html(lecture_url)
- name = get_lecture_name(html)
- youtube_id = parse_youtube_id(html)
- return {
- 'name': name,
- 'youtube_id': youtube_id
- }
-
-
-
-def parse_youtube_id(html):
- embed = html.find('embed')
- yt_ptn = re.compile(r'http://www.youtube.com/v/(.+?)\?')
- match = yt_ptn.search(embed['src'])
- if match:
- return match.group(1)
- return None
-----------------------------------------------------------------------
Summary of changes:
.../.travis.yml | 0
plugin.video.academicearth/README.md | 3 +-
plugin.video.academicearth/addon.py | 62 +++-
plugin.video.academicearth/addon.xml | 9 +-
plugin.video.academicearth/changelog.txt | 6 +
.../requirements.txt | 3 +-
.../resources/lib/academicearth/api.py | 172 +++++-------
.../resources/lib/academicearth/scraper.py | 313 +++++++++++++-------
.../resources/lib/academicearth/test_scraper.py | 55 ++++
.../resources/tests}/__init__.py | 0
.../resources/tests/test_addon.py | 91 ++++++
.../resources/tests/test_api.py | 18 ++
12 files changed, 506 insertions(+), 226 deletions(-)
copy {plugin.video.vimcasts => plugin.video.academicearth}/.travis.yml (100%)
copy {plugin.video.classiccinema =>
plugin.video.academicearth}/requirements.txt (73%)
create mode 100644
plugin.video.academicearth/resources/lib/academicearth/test_scraper.py
copy {plugin.audio.radio_de/resources =>
plugin.video.academicearth/resources/tests}/__init__.py (100%)
create mode 100644 plugin.video.academicearth/resources/tests/test_addon.py
create mode 100644 plugin.video.academicearth/resources/tests/test_api.py
hooks/post-receive
--
Plugins
------------------------------------------------------------------------------
Master SQL Server Development, Administration, T-SQL, SSAS, SSIS, SSRS
and more. Get SQL Server skills now (including 2012) with LearnDevNow -
200+ hours of step-by-step video tutorials by Microsoft MVPs and experts.
SALE $99.99 this month only - learn more at:
http://p.sf.net/sfu/learnmore_122512
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons