The branch, frodo has been updated
via 3c5227edd5fde1d69f75d3d4f5b085e8835f7224 (commit)
from 9c818868318ddb186eae59d2b4b8e0d5152ad305 (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=3c5227edd5fde1d69f75d3d4f5b085e8835f7224
commit 3c5227edd5fde1d69f75d3d4f5b085e8835f7224
Author: beenje <[email protected]>
Date: Mon May 6 22:29:22 2013 +0200
[plugin.video.animeftw] updated to version 1.8.0
diff --git a/plugin.video.animeftw/addon.xml b/plugin.video.animeftw/addon.xml
index 34960f4..3effd21 100644
--- a/plugin.video.animeftw/addon.xml
+++ b/plugin.video.animeftw/addon.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="plugin.video.animeftw" name="AnimeFTW.tv" version="1.7.2"
provider-name="robotman321">
+<addon id="plugin.video.animeftw" name="AnimeFTW.tv" version="1.8.0"
provider-name="robotman321">
<requires>
- <import addon="xbmc.python" version="2.0"/>
- <import addon="script.module.beautifulsoup" version="3.0.8"/>
+ <import addon="xbmc.python" version="2.1.0"/>
+ <import addon="script.module.elementtree" version="1.2.7"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
@@ -10,10 +10,14 @@
<extension point="xbmc.addon.metadata">
<language>en jp</language>
<platform>all</platform>
- <minversion>29000</minversion>
+ <website>http://www.animeftw.tv</website>
+
<source>https://ftw-entertainment-llc-apps.googlecode.com/svn/trunk/plugin.video.animeftw/</source>
+ <forum>http://forum.xbmc.org/showthread.php?tid=80010</forum>
+ <email>[email protected]</email>
+ <license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
<summary>Watch high-quality, streaming anime from AnimeFTW.tv!</summary>
- <description>AnimeFTW.tv provides high-quality, streaming anime using both
DivX and H264 video codecs. With over 650 series and 1.5TB of video available,
AnimeFTW aims to provide the best collection of anime series, OVAs, and movies
to its users.
+ <description>AnimeFTW.tv provides high-quality, streaming anime using both
DivX and H264 video codecs. With over 750 series and 2TB of video available,
AnimeFTW aims to provide the best collection of anime series, OVAs, and movies
to its users.
</description>
- <disclaimer>You must have an account at www.animeftw.tv and be logged in
for this addon to function! Only the first two episodes are available to free
members!</disclaimer>
+ <disclaimer>You must have an account at www.animeftw.tv and be logged in
for this addon to function! Only the first two episodes are available to free
members!</disclaimer>
</extension>
</addon>
\ No newline at end of file
diff --git a/plugin.video.animeftw/changelog.txt
b/plugin.video.animeftw/changelog.txt
index 9d9a47b..3fcc4e6 100644
--- a/plugin.video.animeftw/changelog.txt
+++ b/plugin.video.animeftw/changelog.txt
@@ -1,3 +1,6 @@
+[B]Version 1.7.3[/B]
+- Replacing BeautifulSoup with ElementTree, courtesy of don @ AFTW
+
[B]Version 1.7.2[/B]
- Updated Python Version
- Added Language Tag
diff --git a/plugin.video.animeftw/default.py b/plugin.video.animeftw/default.py
index 7e01622..0101238 100644
--- a/plugin.video.animeftw/default.py
+++ b/plugin.video.animeftw/default.py
@@ -1,20 +1,20 @@
-"""
- AnimeFTW
- maruchan
-"""
-import sys
-import xbmcaddon
-#import xbmcaddon
-
-#plugin constants
-__plugin__ = "AnimeFTW"
-__author__ = "maruchan"
-__settings__ = xbmcaddon.Addon(id='plugin.video.animeftw')
-
-print "[PLUGIN] '%s: version initialized!" % (__plugin__)
-
-if __name__ == "__main__":
- from resources.lib import main_ftw2
- main_ftw2.Main()
-
+"""
+ AnimeFTW
+ maruchan
+"""
+import sys
+import xbmcaddon
+#import xbmcaddon
+
+#plugin constants
+__plugin__ = "AnimeFTW"
+__author__ = "maruchan"
+__settings__ = xbmcaddon.Addon(id='plugin.video.animeftw')
+
+print "[PLUGIN] '%s: version initialized!" % (__plugin__)
+
+if __name__ == "__main__":
+ from resources.lib import main_ftw2
+ main_ftw2.Main()
+
sys.modules.clear()
\ No newline at end of file
diff --git a/plugin.video.animeftw/difference.patch
b/plugin.video.animeftw/difference.patch
index 738408c..848c142 100644
--- a/plugin.video.animeftw/difference.patch
+++ b/plugin.video.animeftw/difference.patch
@@ -3,67 +3,67 @@ Index: resources/lib/main_ftw2.py
--- resources/lib/main_ftw2.py (revision 43)
+++ resources/lib/main_ftw2.py (working copy)
@@ -136,33 +135,34 @@
- htmlSource = self.getHTML(url)
- soup = BeautifulSoup.BeautifulSoup(htmlSource,
convertEntities=BeautifulSoup.BeautifulSoup.HTML_ENTITIES)
- episodes = soup.findAll(category)
-+ if len(episodes) > 1:
-+ for i, episode in enumerate(episodes):
-+ if category == 'episode':
-+ epname =
unicode(episode.find('epnumber').string + ".) " +
episode.find('name').string.replace('`', '\'')).encode('utf-8')
-+ else:
-+ epname =
unicode(episode.find('name').string.replace('`', '\'')).encode('utf-8')
-+ UI().addItem({'Title': epname,
'mode':'playEpisode', 'url': episode.find('videolink').string, 'Thumb':
seriesimage, 'Seriesname': seriesname}, None, True, len(episodes))
-+ del episodes
-+ UI().endofdirectory('none')
-+ else:
-+ info = {}
-+ info['name'] =
unicode(episodes[0].find('name').string.replace('`', '\'')).encode('utf-8')
-+ info['epname'] = ''
-+ info['thumb'] = seriesimage
-+ self.playVid(episodes[0].find('videolink').string,
info['name'], seriesimage)
-
-- for i, episode in enumerate(episodes):
-- if category == 'episode':
-- epname =
unicode(episode.find('epnumber').string + ".) " +
episode.find('name').string.replace('`', '\'')).encode('utf-8')
-- else:
-- epname =
unicode(episode.find('name').string.replace('`', '\'')).encode('utf-8')
--
-- url = episode.find('videolink').string
-- thumbnail = episode.find('image').string
-- if thumbnail ==
"http://static.ftw-cdn.com/site-images/video-images/noimage.png":
-- thumbnail = seriesimage
--
-- li = xbmcgui.ListItem(epname, path = url,
thumbnailImage = thumbnail)
-- li.setInfo(type="Video", infoLabels={ "Title": epname })
-- li.setProperty("IsPlayable","true");
-- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=url, listitem=li, isFolder=False)
-- del episodes
-- UI().endofdirectory()
--
- def playVid(self, url, name, thumb):
-- stream_url = url.replace(' ', '')
-+ stream_url = url.replace(' ', '%20')
- stream_url += '?Referrer=www.animeftw.tv'
- if thumb == None:
- thumb = ''
-- item = xbmcgui.ListItem( label = name, label2 = name, iconImage
= thumb, thumbnailImage = thumb)
-- item.setInfo("video", infoLabels={ "Title": name })
-- xbmc.Player(xbmc.PLAYER_CORE_DVDPLAYER).play(stream_url, item)
-+ print 'url = ' + stream_url
-+ #item = xbmcgui.ListItem( label = name, label2 = name,
iconImage = thumb, thumbnailImage = thumb, path = stream_url)
-+ item = xbmcgui.ListItem( label = name, label2 = name, iconImage
= "DefaultVideo.png", thumbnailImage = thumb)
-+ item.setInfo(type="video", infoLabels={ "Title": name })
-+
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=stream_url,listitem=item)
-+ #xbmcplugin.setResolvedUrl(handle = int(sys.argv[1]), succeeded
= True, listitem = item)
-+ #xbmc.Player(xbmc.PLAYER_CORE_DVDPLAYER).play(stream_url, item)
-
- class UI:
-
+ htmlSource = self.getHTML(url)
+ soup = BeautifulSoup.BeautifulSoup(htmlSource,
convertEntities=BeautifulSoup.BeautifulSoup.HTML_ENTITIES)
+ episodes = soup.findAll(category)
++ if len(episodes) > 1:
++ for i, episode in enumerate(episodes):
++ if category == 'episode':
++ epname =
unicode(episode.find('epnumber').string + ".) " +
episode.find('name').string.replace('`', '\'')).encode('utf-8')
++ else:
++ epname =
unicode(episode.find('name').string.replace('`', '\'')).encode('utf-8')
++ UI().addItem({'Title': epname,
'mode':'playEpisode', 'url': episode.find('videolink').string, 'Thumb':
seriesimage, 'Seriesname': seriesname}, None, True, len(episodes))
++ del episodes
++ UI().endofdirectory('none')
++ else:
++ info = {}
++ info['name'] =
unicode(episodes[0].find('name').string.replace('`', '\'')).encode('utf-8')
++ info['epname'] = ''
++ info['thumb'] = seriesimage
++ self.playVid(episodes[0].find('videolink').string,
info['name'], seriesimage)
+
+- for i, episode in enumerate(episodes):
+- if category == 'episode':
+- epname =
unicode(episode.find('epnumber').string + ".) " +
episode.find('name').string.replace('`', '\'')).encode('utf-8')
+- else:
+- epname =
unicode(episode.find('name').string.replace('`', '\'')).encode('utf-8')
+-
+- url = episode.find('videolink').string
+- thumbnail = episode.find('image').string
+- if thumbnail ==
"http://static.ftw-cdn.com/site-images/video-images/noimage.png":
+- thumbnail = seriesimage
+-
+- li = xbmcgui.ListItem(epname, path = url,
thumbnailImage = thumbnail)
+- li.setInfo(type="Video", infoLabels={ "Title": epname })
+- li.setProperty("IsPlayable","true");
+- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=url, listitem=li, isFolder=False)
+- del episodes
+- UI().endofdirectory()
+-
+ def playVid(self, url, name, thumb):
+- stream_url = url.replace(' ', '')
++ stream_url = url.replace(' ', '%20')
+ stream_url += '?Referrer=www.animeftw.tv'
+ if thumb == None:
+ thumb = ''
+- item = xbmcgui.ListItem( label = name, label2 = name, iconImage
= thumb, thumbnailImage = thumb)
+- item.setInfo("video", infoLabels={ "Title": name })
+- xbmc.Player(xbmc.PLAYER_CORE_DVDPLAYER).play(stream_url, item)
++ print 'url = ' + stream_url
++ #item = xbmcgui.ListItem( label = name, label2 = name,
iconImage = thumb, thumbnailImage = thumb, path = stream_url)
++ item = xbmcgui.ListItem( label = name, label2 = name, iconImage
= "DefaultVideo.png", thumbnailImage = thumb)
++ item.setInfo(type="video", infoLabels={ "Title": name })
++
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=stream_url,listitem=item)
++ #xbmcplugin.setResolvedUrl(handle = int(sys.argv[1]), succeeded
= True, listitem = item)
++ #xbmc.Player(xbmc.PLAYER_CORE_DVDPLAYER).play(stream_url, item)
+
+ class UI:
+
@@ -250,6 +250,7 @@
- grabFTW().getEpisodes(self.main.args.url, self.main.args.name,
self.main.args.icon, self.main.args.mode)
-
- def startVideo(self):
-+ print "startVideo: '" + self.main.args.url + "' title: " +
self.main.args.name
- grabFTW().playVid(self.main.args.url, self.main.args.name,
self.main.args.icon)
-
- class Main:
+ grabFTW().getEpisodes(self.main.args.url, self.main.args.name,
self.main.args.icon, self.main.args.mode)
+
+ def startVideo(self):
++ print "startVideo: '" + self.main.args.url + "' title: " +
self.main.args.name
+ grabFTW().playVid(self.main.args.url, self.main.args.name,
self.main.args.icon)
+
+ class Main:
diff --git a/plugin.video.animeftw/icon.png b/plugin.video.animeftw/icon.png
index 373e81d..b62330d 100644
Binary files a/plugin.video.animeftw/icon.png and
b/plugin.video.animeftw/icon.png differ
diff --git a/plugin.video.animeftw/resources/__init__.py
b/plugin.video.animeftw/resources/__init__.py
index ee074ac..b93054b 100644
--- a/plugin.video.animeftw/resources/__init__.py
+++ b/plugin.video.animeftw/resources/__init__.py
@@ -1 +1 @@
-# Dummy file to make this directory a package.
+# Dummy file to make this directory a package.
diff --git a/plugin.video.animeftw/resources/language/English/strings.xml
b/plugin.video.animeftw/resources/language/English/strings.xml
index 4244db7..474c0f2 100644
--- a/plugin.video.animeftw/resources/language/English/strings.xml
+++ b/plugin.video.animeftw/resources/language/English/strings.xml
@@ -1,77 +1,77 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<strings>
- <!-- Crunchyroll Login Info -->
- <string id="20002">Username</string>
- <string id="20003">Password</string>
- <string id="20004">Fetch plot/poster from MyAnimeList.net</string>
-
- <!-- Interface Strings -->
- <string id="50003">Latest Additions</string>
- <string id="50000">Anime</string>
- <string id="50001">OVAs</string>
- <string id="50002">Movies</string>
-
- <string id="51000">All Series</string>
- <string id="51001">Currently Airing</string>
- <string id="51002">Completed Series</string>
- <string id="51003">Most Popular</string>
- <string id="51004">Browse by Genre</string>
-
- <string id="50005">Action</string>
- <string id="50006">Adventure</string>
- <string id="50007">Aliens</string>
- <string id="50008">Angst</string>
- <string id="50009">Bishounen</string>
- <string id="50010">Bounty Hunters</string>
- <string id="50011">Clubs</string>
- <string id="50012">Comedy</string>
- <string id="50013">Coming-of-Age</string>
- <string id="50014">Conspiracy</string>
- <string id="50015">Contemporary Fantasy</string>
- <string id="50016">Cyberpunk</string>
- <string id="50017">Daily Life</string>
- <string id="50018">Demons</string>
- <string id="50019">Detective</string>
- <string id="50020">Dystopia</string>
- <string id="50021">Ecchi</string>
- <string id="50022">Elementary School</string>
- <string id="50023">Elves</string>
- <string id="50024">Fantasy</string>
- <string id="50025">Female Students</string>
- <string id="50026">Gunfights</string>
- <string id="50027">Harem</string>
- <string id="50028">High School</string>
- <string id="50029">Historical</string>
- <string id="50030">Horror</string>
- <string id="50031">Humanoid</string>
- <string id="50032">Idol</string>
- <string id="50033">Love Polygon</string>
- <string id="50034">Magical</string>
- <string id="50035">Martial Arts</string>
- <string id="50036">Mecha</string>
- <string id="50037">Military</string>
- <string id="50038">Music</string>
- <string id="50039">Novel</string>
- <string id="50040">Nudity</string>
- <string id="50041">Parallel Universe</string>
- <string id="50042">Parody</string>
- <string id="50043">Piloted Robots</string>
- <string id="50044">Post-apocalyptic</string>
- <string id="50045">School Life</string>
- <string id="50046">Sci-Fi</string>
- <string id="50047">Seinen</string>
- <string id="50048">Shoujo</string>
- <string id="50049">Shounen</string>
- <string id="50050">Slapstick</string>
- <string id="50051">Space Travel</string>
- <string id="50052">Special Squads</string>
- <string id="50053">Sports</string>
- <string id="50054">Sudden Girlfriend Appearance</string>
- <string id="50055">Swordplay</string>
- <string id="50056">Thriller</string>
- <string id="50057">Tragedy</string>
- <string id="50058">Underworld</string>
- <string id="50059">Vampires</string>
- <string id="50060">Virtual Reality</string>
- <string id="50061">World War II</string>
-</strings>
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<strings>
+ <!-- Crunchyroll Login Info -->
+ <string id="20002">Username</string>
+ <string id="20003">Password</string>
+ <string id="20004">Fetch plot/poster from MyAnimeList.net</string>
+
+ <!-- Interface Strings -->
+ <string id="50003">Latest Additions</string>
+ <string id="50000">Anime</string>
+ <string id="50001">OVAs</string>
+ <string id="50002">Movies</string>
+
+ <string id="51000">All Series</string>
+ <string id="51001">Currently Airing</string>
+ <string id="51002">Completed Series</string>
+ <string id="51003">Most Popular</string>
+ <string id="51004">Browse by Genre</string>
+
+ <string id="50005">Action</string>
+ <string id="50006">Adventure</string>
+ <string id="50007">Aliens</string>
+ <string id="50008">Angst</string>
+ <string id="50009">Bishounen</string>
+ <string id="50010">Bounty Hunters</string>
+ <string id="50011">Clubs</string>
+ <string id="50012">Comedy</string>
+ <string id="50013">Coming-of-Age</string>
+ <string id="50014">Conspiracy</string>
+ <string id="50015">Contemporary Fantasy</string>
+ <string id="50016">Cyberpunk</string>
+ <string id="50017">Daily Life</string>
+ <string id="50018">Demons</string>
+ <string id="50019">Detective</string>
+ <string id="50020">Dystopia</string>
+ <string id="50021">Ecchi</string>
+ <string id="50022">Elementary School</string>
+ <string id="50023">Elves</string>
+ <string id="50024">Fantasy</string>
+ <string id="50025">Female Students</string>
+ <string id="50026">Gunfights</string>
+ <string id="50027">Harem</string>
+ <string id="50028">High School</string>
+ <string id="50029">Historical</string>
+ <string id="50030">Horror</string>
+ <string id="50031">Humanoid</string>
+ <string id="50032">Idol</string>
+ <string id="50033">Love Polygon</string>
+ <string id="50034">Magical</string>
+ <string id="50035">Martial Arts</string>
+ <string id="50036">Mecha</string>
+ <string id="50037">Military</string>
+ <string id="50038">Music</string>
+ <string id="50039">Novel</string>
+ <string id="50040">Nudity</string>
+ <string id="50041">Parallel Universe</string>
+ <string id="50042">Parody</string>
+ <string id="50043">Piloted Robots</string>
+ <string id="50044">Post-apocalyptic</string>
+ <string id="50045">School Life</string>
+ <string id="50046">Sci-Fi</string>
+ <string id="50047">Seinen</string>
+ <string id="50048">Shoujo</string>
+ <string id="50049">Shounen</string>
+ <string id="50050">Slapstick</string>
+ <string id="50051">Space Travel</string>
+ <string id="50052">Special Squads</string>
+ <string id="50053">Sports</string>
+ <string id="50054">Sudden Girlfriend Appearance</string>
+ <string id="50055">Swordplay</string>
+ <string id="50056">Thriller</string>
+ <string id="50057">Tragedy</string>
+ <string id="50058">Underworld</string>
+ <string id="50059">Vampires</string>
+ <string id="50060">Virtual Reality</string>
+ <string id="50061">World War II</string>
+</strings>
diff --git a/plugin.video.animeftw/resources/lib/__init__.py
b/plugin.video.animeftw/resources/lib/__init__.py
index ee074ac..b93054b 100644
--- a/plugin.video.animeftw/resources/lib/__init__.py
+++ b/plugin.video.animeftw/resources/lib/__init__.py
@@ -1 +1 @@
-# Dummy file to make this directory a package.
+# Dummy file to make this directory a package.
diff --git a/plugin.video.animeftw/resources/lib/main_ftw2.py
b/plugin.video.animeftw/resources/lib/main_ftw2.py
index 7db2380..d4442f0 100644
--- a/plugin.video.animeftw/resources/lib/main_ftw2.py
+++ b/plugin.video.animeftw/resources/lib/main_ftw2.py
@@ -1,285 +1,288 @@
-import os
-import sys
-import md5
-import urllib
-import xbmc
-import xbmcgui
-import xbmcaddon
-import xbmcplugin
-import BeautifulSoup
-
-SETTINGS = sys.modules[ "__main__" ].__settings__
-
-class updateArgs:
-
- def __init__(self, *args, **kwargs):
- for key, value in kwargs.iteritems():
- if value == 'None':
- kwargs[key] = None
- else:
- kwargs[key] = urllib.unquote_plus(kwargs[key])
- self.__dict__.update(kwargs)
-
-class LoginFTW:
-
- def __init__(self, *args, **kwargs):
- self.status = 0
- self.settings = {}
- self.settings['username'] = SETTINGS.getSetting("username_ftw")
- self.settings['password'] = SETTINGS.getSetting("password_ftw")
-
- def checkLogin(self):
- if self.settings['username'] == '' or self.settings['password']
== '':
- self.resp = xbmcgui.Dialog().yesno("No
username/password set!","AnimeFTW.tv requires you to be logged in to view", \
- "videos. Would you like to log-in now?")
- if self.resp:
- self.respLogin = SETTINGS.openSettings()
- if self.respLogin:
- self.settings['username'] =
SETTINGS.getSetting("username_ftw")
- self.settings['password'] =
SETTINGS.getSetting("password_ftw")
- return self.settings['username'],
self.settings['password']
- else:
-
xbmc.executebuiltin('XBMC.Notification("Please Login:","An advanced user
account is required to view content.", 3000)')
- return '', ''
- else:
- xbmc.executebuiltin('XBMC.Notification("Please
Login:","An advanced user account is required to view content.", 3000)')
- return '', ''
- else:
- return self.settings['username'],
self.settings['password']
-
- def hashPassword(self, password):
- return md5.new(password).hexdigest()
-
-class grabFTW:
-
- def __init__(self, *args, **kwargs):
- self.settings = {}
- self.settings['username'], self.settings['password'] =
LoginFTW().checkLogin()
- self.settings['passHash'] =
LoginFTW().hashPassword(self.settings['password'])
- self.urlString = 'did=hVhS-672s-sKhK-yUn0&username=' +
self.settings['username'] + '&password=' + self.settings['passHash']
-
- def getHTML(self, url):
- self.currenturl = url
- htmlSource = None
- print "[FTW] Finding URL: "+self.currenturl
- htmlSource = urllib.urlopen(url).read()
- print "[FTW] Got URL."
- return htmlSource
-
- def getLatest(self, count = 25):
- htmlSource =
self.getHTML("https://www.animeftw.tv/api/v1/show?" + self.urlString +
"&show=latest&start=0&count=" + str(count))
- soup = BeautifulSoup.BeautifulSoup(htmlSource,
convertEntities=BeautifulSoup.BeautifulSoup.HTML_ENTITIES)
- latest_list = soup.findAll('episode')
- for episode in latest_list:
- UI().addItem({'Seriesname':
unicode(episode.find('series').string.replace('`', '\'')).encode('utf-8'),
'Title': unicode(episode.find('series').string.replace('`', '\'') + " - " +
episode.find('epnumber').string + " - " +
episode.find('name').string.replace('`', '\'')).encode('utf-8'), 'mode':
'playEpisode', 'url': episode.find('videolink').string })
- del latest_list
- UI().endofdirectory('title')
-
- def getGenres(self):
- htmlSource =
self.getHTML("https://www.animeftw.tv/api/v1/show?" + self.urlString +
"&show=tagcloud")
- soup = BeautifulSoup.BeautifulSoup(htmlSource,
convertEntities=BeautifulSoup.BeautifulSoup.HTML_ENTITIES)
- tag_list = soup.findAll('tag')
- for tag in tag_list:
- genreFilter = tag['href'].split('filter=')[1]
- UI().addItem({'Title':
unicode(tag.string).encode('utf-8').title(), 'mode': 'anime_all', 'url':
tag['href'], 'category': str(genreFilter)})
- del tag_list
- UI().endofdirectory('title')
-
- def getListing(self, category = 0, showType = 'anime', count = 2000,
filter = None):
- print "[FTW] FILTER is set to: " + str(filter)
- url = "https://www.animeftw.tv/api/v1/show?" + self.urlString +
"&show=" + showType + "&start=0&count=" + str(count)
- if filter:
- url += "&filter=" + str(filter)
- htmlSource = self.getHTML(url)
- soup = BeautifulSoup.BeautifulSoup(htmlSource,
convertEntities=BeautifulSoup.BeautifulSoup.HTML_ENTITIES)
- cat_list = ['episode', 'episode', 'episode', 'episode',
'episode', 'movie']
- videoType = cat_list[category]
- print "[FTW] Current video type: " + str(videoType)
- series_list = soup.findAll('series')
- for series in series_list:
- numberOfMovies = int(series.find('movies').string)
- numberOfEpisodes = int(series.find('episodes').string)
- isOVA = series.find('ova').string
- isAiring = series.find('airing').string
- if numberOfMovies == 1 and numberOfEpisodes == 1 and
category != 5:
- continue
- elif numberOfMovies < 1 and category == 5:
- continue
- elif isOVA == 'yes' and category != 0:
- continue
- elif isOVA == 'no' and category == 0:
- continue
- elif isAiring == 'no' and category == 2:
- continue
- elif isAiring == 'yes' and category == 3:
- continue
- else:
- seriesname = series.find('seriesname').string
- seriesname = unicode(seriesname.replace('`',
'\'')).encode('utf-8')
-
- seriesdict = {'name': seriesname, \
- 'nameorig':
unicode(series.find('romaji').string).encode('utf-8'), \
- 'url':
series['href'], \
- 'thumb':
series.find('image').string, \
-
'plot':unicode(series.find('description').string).encode('utf-8'), \
- 'rating': 0.0, \
- 'episodes':
numberOfEpisodes, \
- 'genre':
unicode(series.find('category').string).encode('utf-8') }
-
- UI().addItem({'Title':seriesdict['name'],
'mode': videoType, 'url':seriesdict['url'], 'Thumb':seriesdict['thumb']},
seriesdict, True, len(series_list))
-
- del series_list
- del soup
- UI().endofdirectory('title')
-
- def getEpisodes(self, url, seriesname = None, seriesimage = None,
category = None):
- htmlSource = self.getHTML(url)
- soup = BeautifulSoup.BeautifulSoup(htmlSource,
convertEntities=BeautifulSoup.BeautifulSoup.HTML_ENTITIES)
- episodes = soup.findAll(category)
-
- for i, episode in enumerate(episodes):
- if category == 'episode':
- epname =
unicode(episode.find('epnumber').string + ".) " +
episode.find('name').string.replace('`', '\'')).encode('utf-8')
- else:
- epname =
unicode(episode.find('name').string.replace('`', '\'')).encode('utf-8')
-
- url = episode.find('videolink').string
- thumbnail = episode.find('image').string
- if thumbnail ==
"http://static.ftw-cdn.com/site-images/video-images/noimage.png":
- thumbnail = seriesimage
-
- li = xbmcgui.ListItem(epname, path = url,
thumbnailImage = thumbnail)
- li.setInfo(type="Video", infoLabels={ "Title": epname })
- li.setProperty("IsPlayable","true");
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=url, listitem=li, isFolder=False)
- del episodes
- UI().endofdirectory()
-
- def playVid(self, url, name, thumb):
- stream_url = url.replace(' ', '')
- stream_url += '?Referrer=www.animeftw.tv'
- if thumb == None:
- thumb = ''
- item = xbmcgui.ListItem( label = name, label2 = name, iconImage
= thumb, thumbnailImage = thumb)
- item.setInfo("video", infoLabels={ "Title": name })
- xbmc.Player(xbmc.PLAYER_CORE_DVDPLAYER).play(stream_url, item)
-
-class UI:
-
- def __init__(self):
- self.main = Main(checkMode = False)
- xbmcplugin.setContent(int(sys.argv[1]), 'videos')
-
- def endofdirectory(self, sortMethod = 'none'):
- # set sortmethod to something xbmc can use
- if sortMethod == 'title':
- xbmcplugin.addSortMethod(int(sys.argv[1]),
xbmcplugin.SORT_METHOD_LABEL)
- xbmcplugin.addSortMethod(int(sys.argv[1]),
xbmcplugin.SORT_METHOD_VIDEO_RATING)
- elif sortMethod == 'none':
- xbmcplugin.addSortMethod(int(sys.argv[1]),
xbmcplugin.SORT_METHOD_NONE)
-
- dontAddToHierarchy = False
- xbmcplugin.endOfDirectory(handle = int(sys.argv[1]),
updateListing = dontAddToHierarchy)
-
- def addItem(self, info, extrainfo = None, isFolder=True, total_items =
0):
- #Defaults in dict. Use 'None' instead of None so it is
compatible for quote_plus in parseArgs
- info.setdefault('url', 'None')
- info.setdefault('Thumb', 'None')
- info.setdefault('id','None')
- info.setdefault('category','None')
- info.setdefault('Seriesname','None')
- info.setdefault('Icon', info['Thumb'])
-
- #create params for xbmcplugin module
- u = sys.argv[0]+\
- '?url='+urllib.quote_plus(info['url'])+\
- '&mode='+urllib.quote_plus(info['mode'])+\
- '&name='+urllib.quote_plus(info['Title'])+\
- '&seriesname='+urllib.quote_plus(info['Seriesname'])+\
- '&id='+urllib.quote_plus(info['id'])+\
- '&category='+urllib.quote_plus(info['category'])+\
- '&icon='+urllib.quote_plus(info['Thumb'])
- #create list item
- if extrainfo != None:
- li=xbmcgui.ListItem(label = extrainfo['name'],
iconImage = info['Icon'], thumbnailImage = info['Thumb'])
- li.setInfo("video", infoLabels={
"Title":extrainfo['name'], "OriginalTitle": extrainfo['nameorig'], "episode":
extrainfo['episodes'], "Plot":extrainfo['plot'], "Genre":extrainfo['genre'],
'Rating':extrainfo['rating']})
- else:
- li=xbmcgui.ListItem(label = info['Title'], iconImage =
info['Icon'], thumbnailImage = info['Thumb'])
- #for videos, replace context menu with queue and add to
favorites
- if not isFolder:
- li.setProperty("IsPlayable", "true")
- #let xbmc know this can be played, unlike a folder.
- #add context menu items to non-folder items.
- contextmenu = [('Queue Video', 'Action(Queue)')]
- #for folders, completely remove contextmenu, as it is totally
useless.
- else:
- li.setProperty("IsPlayable", "false")
- #add item to list
- ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u,
listitem=li, isFolder=isFolder,totalItems=total_items)
-
- def showCategories(self):
- self.addItem({'Title':SETTINGS.getLocalizedString(50003),
'mode':'latest'})
- self.addItem({'Title':SETTINGS.getLocalizedString(50000),
'mode':'series'})
- self.addItem({'Title':SETTINGS.getLocalizedString(50001),
'mode':'ovas'})
- self.addItem({'Title':SETTINGS.getLocalizedString(50002),
'mode':'movies'})
- self.endofdirectory()
-
- def showAnimeSeries(self):
- self.addItem({'Title':SETTINGS.getLocalizedString(51000),
'mode':'anime_all'})
- self.addItem({'Title':SETTINGS.getLocalizedString(51001),
'mode':'anime_airing'})
- self.addItem({'Title':SETTINGS.getLocalizedString(51002),
'mode':'anime_completed'})
- self.addItem({'Title':SETTINGS.getLocalizedString(51004),
'mode':'anime_genres'})
- self.endofdirectory()
-
- def animeGenre(self):
- grabFTW().getGenres()
- self.endofdirectory()
-
- def latest(self):
- grabFTW().getLatest(25)
-
- def series(self):
- cat_dict = {'ovas': 0, 'anime_all': 1, 'anime_airing': 2,
'anime_completed': 3, 'anime_genres': 4, 'movies': 5}
- if(self.main.args.category != None):
- print "[FTW] Looks like there's a filter..."
-
grabFTW().getListing(category=cat_dict[self.main.args.mode],
filter=self.main.args.category)
- else:
- grabFTW().getListing(cat_dict[self.main.args.mode])
-
- def episodes(self):
- grabFTW().getEpisodes(self.main.args.url, self.main.args.name,
self.main.args.icon, self.main.args.mode)
-
- def startVideo(self):
- grabFTW().playVid(self.main.args.url, self.main.args.name,
self.main.args.icon)
-
-class Main:
-
- def __init__(self, checkMode = True):
- #self.user = None
- self.parseArgs()
- if checkMode:
- self.checkMode()
-
- def parseArgs(self):
- if (sys.argv[2]):
- exec "self.args = updateArgs(%s')" %
(sys.argv[2][1:].replace('&', "',").replace('=', "='"))
- else:
- self.args = updateArgs(mode = 'None', url = 'None',
name = 'None')
-
- def checkMode(self):
- mode = self.args.mode
- print "[FTW] Current mode is: " + str(self.args.mode)
- if mode is None:
- UI().showCategories()
- elif mode == 'series':
- UI().showAnimeSeries()
- elif mode == 'episode' or mode == 'movie':
- UI().episodes()
- elif mode == 'playEpisode':
- UI().startVideo()
- elif mode == 'latest':
- UI().latest()
- elif mode == 'anime_genres':
- UI().animeGenre()
- elif mode == 'anime_all' or mode == 'ovas' or mode ==
'anime_airing' or mode == 'anime_completed' or mode == 'movies':
- UI().series()
\ No newline at end of file
+import os
+import sys
+import md5
+import urllib
+import xbmc
+import xbmcgui
+import xbmcaddon
+import xbmcplugin
+from xml.etree import ElementTree
+
+SETTINGS = sys.modules[ "__main__" ].__settings__
+
+class updateArgs:
+
+ def __init__(self, *args, **kwargs):
+ for key, value in kwargs.iteritems():
+ if value == 'None':
+ kwargs[key] = None
+ else:
+ kwargs[key] = urllib.unquote_plus(kwargs[key])
+ self.__dict__.update(kwargs)
+
+class LoginFTW:
+
+ def __init__(self, *args, **kwargs):
+ self.status = 0
+ self.settings = {}
+ self.settings['username'] = SETTINGS.getSetting("username_ftw")
+ self.settings['password'] = SETTINGS.getSetting("password_ftw")
+
+ def checkLogin(self):
+ if self.settings['username'] == '' or self.settings['password']
== '':
+ self.resp = xbmcgui.Dialog().yesno("No
username/password set!","AnimeFTW.tv requires you to be logged in to view", \
+ "videos. Would you like to log-in now?")
+ if self.resp:
+ self.respLogin = SETTINGS.openSettings()
+ if self.respLogin:
+ self.settings['username'] =
SETTINGS.getSetting("username_ftw")
+ self.settings['password'] =
SETTINGS.getSetting("password_ftw")
+ return self.settings['username'],
self.settings['password']
+ else:
+
xbmc.executebuiltin('XBMC.Notification("Please Login:","An advanced user
account is required to view content.", 3000)')
+ return '', ''
+ else:
+ xbmc.executebuiltin('XBMC.Notification("Please
Login:","An advanced user account is required to view content.", 3000)')
+ return '', ''
+ else:
+ return self.settings['username'],
self.settings['password']
+
+ def hashPassword(self, password):
+ return md5.new(password).hexdigest()
+
+class grabFTW:
+
+ def __init__(self, *args, **kwargs):
+ self.settings = {}
+ self.settings['username'], self.settings['password'] =
LoginFTW().checkLogin()
+ self.settings['passHash'] =
LoginFTW().hashPassword(self.settings['password'])
+ self.urlString = 'did=hVhS-672s-sKhK-yUn0&username=' +
self.settings['username'] + '&password=' + self.settings['passHash']
+
+ def getHTML(self, url):
+ self.currenturl = url
+ htmlSource = None
+ print "[FTW] Finding URL: "+self.currenturl
+ htmlSource = urllib.urlopen(url).read()
+ print "[FTW] Got URL."
+ return htmlSource
+
+ def getLatest(self, count = 25):
+ htmlSource =
self.getHTML("https://www.animeftw.tv/api/v1/show?" + self.urlString +
"&show=latest&start=0&count=" + str(count))
+ root = ElementTree.fromstring(htmlSource)
+ latest_list = root.findall('episode')
+ for episode in latest_list:
+ UI().addItem({'Seriesname':
unicode(episode.find('series').text.replace('`', '\'')).encode('utf-8'),
'Title': unicode(episode.find('series').text.replace('`', '\'') + " - " +
episode.find('epnumber').text + " - " + episode.find('name').text.replace('`',
'\'')).encode('utf-8'), 'mode': 'playEpisode', 'url':
episode.find('videolink').text })
+ del latest_list
+ del root
+ UI().endofdirectory('title')
+
+ def getGenres(self):
+ htmlSource =
self.getHTML("https://www.animeftw.tv/api/v1/show?" + self.urlString +
"&show=tagcloud")
+ root = ElementTree.fromstring(htmlSource)
+ tag_list = root.findall('tag')
+ for tag in tag_list:
+ genreFilter = tag.attrib['href'].split('filter=')[1]
+ UI().addItem({'Title':
unicode(tag.text).encode('utf-8').title(), 'mode': 'anime_all', 'url':
tag.attrib['href'], 'category': str(genreFilter)})
+ del tag_list
+ del root
+ UI().endofdirectory('title')
+
+ def getListing(self, category = 0, showType = 'anime', count = 2000,
filter = None):
+ print "[FTW] FILTER is set to: " + str(filter)
+ url = "https://www.animeftw.tv/api/v1/show?" + self.urlString +
"&show=" + showType + "&start=0&count=" + str(count)
+ if filter:
+ url += "&filter=" + str(filter)
+ htmlSource = self.getHTML(url)
+ root = ElementTree.fromstring(htmlSource)
+ cat_list = ['episode', 'episode', 'episode', 'episode',
'episode', 'movie']
+ videoType = cat_list[category]
+ print "[FTW] Current video type: " + str(videoType)
+ series_list = root.findall('series')
+ for series in series_list:
+ numberOfMovies = int(series.find('movies').text)
+ numberOfEpisodes = int(series.find('episodes').text)
+ isOVA = series.find('ova').text
+ isAiring = series.find('airing').text
+ if numberOfMovies == 1 and numberOfEpisodes == 1 and
category != 5:
+ continue
+ elif numberOfMovies < 1 and category == 5:
+ continue
+ elif isOVA == 'yes' and category != 0:
+ continue
+ elif isOVA == 'no' and category == 0:
+ continue
+ elif isAiring == 'no' and category == 2:
+ continue
+ elif isAiring == 'yes' and category == 3:
+ continue
+ else:
+ seriesname = series.find('seriesName').text
+ seriesname = unicode(seriesname.replace('`',
'\'')).encode('utf-8')
+
+ seriesdict = {'name': seriesname, \
+ 'nameorig':
unicode(series.find('romaji').text).encode('utf-8'), \
+ 'url':
series.attrib['href'], \
+ 'thumb':
series.find('image').text, \
+
'plot':unicode(series.find('description').text).encode('utf-8'), \
+ 'rating': 0.0, \
+ 'episodes':
numberOfEpisodes, \
+ 'genre':
unicode(series.find('category').text).encode('utf-8') }
+
+ UI().addItem({'Title':seriesdict['name'],
'mode': videoType, 'url':seriesdict['url'], 'Thumb':seriesdict['thumb']},
seriesdict, True, len(series_list))
+
+ del series_list
+ del root
+ UI().endofdirectory('title')
+
+ def getEpisodes(self, url, seriesname = None, seriesimage = None,
category = None):
+ htmlSource = self.getHTML(url)
+ root = ElementTree.fromstring(htmlSource)
+ episodes = root.findall('.//' + category)
+
+ for i, episode in enumerate(episodes):
+ if category == 'episode':
+ epname = unicode(episode.find('epnumber').text
+ ".) " + episode.find('name').text.replace('`', '\'')).encode('utf-8')
+ else:
+ epname =
unicode(episode.find('name').text.replace('`', '\'')).encode('utf-8')
+
+ url = episode.find('videolink').text
+ thumbnail = episode.find('image').text
+ if thumbnail ==
"http://static.ftw-cdn.com/site-images/video-images/noimage.png":
+ thumbnail = seriesimage
+
+ li = xbmcgui.ListItem(epname, path = url,
thumbnailImage = thumbnail)
+ li.setInfo(type="Video", infoLabels={ "Title": epname })
+ li.setProperty("IsPlayable","true");
+ xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
url=url, listitem=li, isFolder=False)
+ del episodes
+ del root
+ UI().endofdirectory()
+
+ def playVid(self, url, name, thumb):
+ stream_url = url.replace(' ', '')
+ stream_url += '?Referrer=www.animeftw.tv'
+ if thumb == None:
+ thumb = ''
+ item = xbmcgui.ListItem( label = name, label2 = name, iconImage
= thumb, thumbnailImage = thumb)
+ item.setInfo("video", infoLabels={ "Title": name })
+ xbmc.Player(xbmc.PLAYER_CORE_DVDPLAYER).play(stream_url, item)
+
+class UI:
+
+ def __init__(self):
+ self.main = Main(checkMode = False)
+ xbmcplugin.setContent(int(sys.argv[1]), 'videos')
+
+ def endofdirectory(self, sortMethod = 'none'):
+ # set sortmethod to something xbmc can use
+ if sortMethod == 'title':
+ xbmcplugin.addSortMethod(int(sys.argv[1]),
xbmcplugin.SORT_METHOD_LABEL)
+ xbmcplugin.addSortMethod(int(sys.argv[1]),
xbmcplugin.SORT_METHOD_VIDEO_RATING)
+ elif sortMethod == 'none':
+ xbmcplugin.addSortMethod(int(sys.argv[1]),
xbmcplugin.SORT_METHOD_NONE)
+
+ dontAddToHierarchy = False
+ xbmcplugin.endOfDirectory(handle = int(sys.argv[1]),
updateListing = dontAddToHierarchy)
+
+ def addItem(self, info, extrainfo = None, isFolder=True, total_items =
0):
+ #Defaults in dict. Use 'None' instead of None so it is
compatible for quote_plus in parseArgs
+ info.setdefault('url', 'None')
+ info.setdefault('Thumb', 'None')
+ info.setdefault('id','None')
+ info.setdefault('category','None')
+ info.setdefault('Seriesname','None')
+ info.setdefault('Icon', info['Thumb'])
+
+ #create params for xbmcplugin module
+ u = sys.argv[0]+\
+ '?url='+urllib.quote_plus(info['url'])+\
+ '&mode='+urllib.quote_plus(info['mode'])+\
+ '&name='+urllib.quote_plus(info['Title'])+\
+ '&seriesname='+urllib.quote_plus(info['Seriesname'])+\
+ '&id='+urllib.quote_plus(info['id'])+\
+ '&category='+urllib.quote_plus(info['category'])+\
+ '&icon='+urllib.quote_plus(info['Thumb'])
+ #create list item
+ if extrainfo != None:
+ li=xbmcgui.ListItem(label = extrainfo['name'],
iconImage = info['Icon'], thumbnailImage = info['Thumb'])
+ li.setInfo("video", infoLabels={
"Title":extrainfo['name'], "OriginalTitle": extrainfo['nameorig'], "episode":
extrainfo['episodes'], "Plot":extrainfo['plot'], "Genre":extrainfo['genre'],
'Rating':extrainfo['rating']})
+ else:
+ li=xbmcgui.ListItem(label = info['Title'], iconImage =
info['Icon'], thumbnailImage = info['Thumb'])
+ #for videos, replace context menu with queue and add to
favorites
+ if not isFolder:
+ li.setProperty("IsPlayable", "true")
+ #let xbmc know this can be played, unlike a folder.
+ #add context menu items to non-folder items.
+ contextmenu = [('Queue Video', 'Action(Queue)')]
+ #for folders, completely remove contextmenu, as it is totally
useless.
+ else:
+ li.setProperty("IsPlayable", "false")
+ #add item to list
+ ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u,
listitem=li, isFolder=isFolder,totalItems=total_items)
+
+ def showCategories(self):
+ self.addItem({'Title':SETTINGS.getLocalizedString(50003),
'mode':'latest'})
+ self.addItem({'Title':SETTINGS.getLocalizedString(50000),
'mode':'series'})
+ self.addItem({'Title':SETTINGS.getLocalizedString(50001),
'mode':'ovas'})
+ self.addItem({'Title':SETTINGS.getLocalizedString(50002),
'mode':'movies'})
+ self.endofdirectory()
+
+ def showAnimeSeries(self):
+ self.addItem({'Title':SETTINGS.getLocalizedString(51000),
'mode':'anime_all'})
+ self.addItem({'Title':SETTINGS.getLocalizedString(51001),
'mode':'anime_airing'})
+ self.addItem({'Title':SETTINGS.getLocalizedString(51002),
'mode':'anime_completed'})
+ self.addItem({'Title':SETTINGS.getLocalizedString(51004),
'mode':'anime_genres'})
+ self.endofdirectory()
+
+ def animeGenre(self):
+ grabFTW().getGenres()
+ self.endofdirectory()
+
+ def latest(self):
+ grabFTW().getLatest(25)
+
+ def series(self):
+ cat_dict = {'ovas': 0, 'anime_all': 1, 'anime_airing': 2,
'anime_completed': 3, 'anime_genres': 4, 'movies': 5}
+ if(self.main.args.category != None):
+ print "[FTW] Looks like there's a filter..."
+
grabFTW().getListing(category=cat_dict[self.main.args.mode],
filter=self.main.args.category)
+ else:
+ grabFTW().getListing(cat_dict[self.main.args.mode])
+
+ def episodes(self):
+ grabFTW().getEpisodes(self.main.args.url, self.main.args.name,
self.main.args.icon, self.main.args.mode)
+
+ def startVideo(self):
+ grabFTW().playVid(self.main.args.url, self.main.args.name,
self.main.args.icon)
+
+class Main:
+
+ def __init__(self, checkMode = True):
+ #self.user = None
+ self.parseArgs()
+ if checkMode:
+ self.checkMode()
+
+ def parseArgs(self):
+ if (sys.argv[2]):
+ exec "self.args = updateArgs(%s')" %
(sys.argv[2][1:].replace('&', "',").replace('=', "='"))
+ else:
+ self.args = updateArgs(mode = 'None', url = 'None',
name = 'None')
+
+ def checkMode(self):
+ mode = self.args.mode
+ print "[FTW] Current mode is: " + str(self.args.mode)
+ if mode is None:
+ UI().showCategories()
+ elif mode == 'series':
+ UI().showAnimeSeries()
+ elif mode == 'episode' or mode == 'movie':
+ UI().episodes()
+ elif mode == 'playEpisode':
+ UI().startVideo()
+ elif mode == 'latest':
+ UI().latest()
+ elif mode == 'anime_genres':
+ UI().animeGenre()
+ elif mode == 'anime_all' or mode == 'ovas' or mode ==
'anime_airing' or mode == 'anime_completed' or mode == 'movies':
+ UI().series()
diff --git a/plugin.video.animeftw/resources/settings.xml
b/plugin.video.animeftw/resources/settings.xml
index e91050a..efa9a54 100644
--- a/plugin.video.animeftw/resources/settings.xml
+++ b/plugin.video.animeftw/resources/settings.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<settings>
- <setting id="username_ftw" type="text" label="20002" default=""/>
- <setting id="password_ftw" type="text" label="20003" option="hidden"
default=""/>
-</settings>
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<settings>
+ <setting id="username_ftw" type="text" label="20002" default=""/>
+ <setting id="password_ftw" type="text" label="20003" option="hidden"
default=""/>
+</settings>
-----------------------------------------------------------------------
Summary of changes:
plugin.video.animeftw/addon.xml | 16 +-
plugin.video.animeftw/changelog.txt | 3 +
plugin.video.animeftw/default.py | 38 +-
plugin.video.animeftw/difference.patch | 126 +++---
plugin.video.animeftw/icon.png | Bin 7794 -> 85762 bytes
plugin.video.animeftw/resources/__init__.py | 2 +-
.../resources/language/English/strings.xml | 154 +++---
plugin.video.animeftw/resources/lib/__init__.py | 2 +-
plugin.video.animeftw/resources/lib/main_ftw2.py | 573 ++++++++++----------
plugin.video.animeftw/resources/settings.xml | 10 +-
10 files changed, 467 insertions(+), 457 deletions(-)
hooks/post-receive
--
Plugins
------------------------------------------------------------------------------
Learn Graph Databases - Download FREE O'Reilly Book
"Graph Databases" is the definitive new guide to graph databases and
their applications. This 200-page book is written by three acclaimed
leaders in the field. The early access version is available now.
Download your free book today! http://p.sf.net/sfu/neotech_d2d_may
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons