The branch, eden has been updated
via 6663b002ab184813c9800884f290c6ec00bdd272 (commit)
from bb04b4155c1dfcfb034008ad938d2be6e61902be (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=6663b002ab184813c9800884f290c6ec00bdd272
commit 6663b002ab184813c9800884f290c6ec00bdd272
Author: beenje <[email protected]>
Date: Thu Mar 28 22:06:27 2013 +0100
[plugin.video.synopsi] updated to version 0.5.2
diff --git a/plugin.video.synopsi/_src/install_xbmc_version.sh
b/plugin.video.synopsi/_src/install_xbmc_version.sh
index dc6829b..113f309 100755
--- a/plugin.video.synopsi/_src/install_xbmc_version.sh
+++ b/plugin.video.synopsi/_src/install_xbmc_version.sh
@@ -1 +1,2 @@
-sudo aptitude install xbmc=2:11.0~git20120423.cd20772-1
+VERSION=$(aptitude versions "^xbmc$" | awk '{ print $2 }' | grep 2:11.0)
+sudo aptitude install xbmc=$VERSION
diff --git a/plugin.video.synopsi/_src/usefull_queries.sql
b/plugin.video.synopsi/_src/usefull_queries.sql
index 3db2ceb..66771bc 100644
--- a/plugin.video.synopsi/_src/usefull_queries.sql
+++ b/plugin.video.synopsi/_src/usefull_queries.sql
@@ -6,7 +6,9 @@ select user_id, email, min(inserted) from api_apilog left join
auth_user as AU o
select user_id, email, min(inserted), date_joined from api_apilog left join
auth_user as AU on AU.id=user_id group by user_id, date_joined, email order by
min(date_joined) desc;
/* api users with api request counts */
-select user_id, email, min(inserted), date_joined from api_apilog left join
auth_user as AU on AU.id=user_id group by user_id, date_joined, email order by
min(date_joined) desc;
/* checkins whith software info filled-in */
select * from actions_checkin where data like '%software_info":"{%' and
data!='{}' and data!='';
+
+/* fail responses percentil by method */
+select method, count(id), sum(case when response_status=0 then 1 else 0 end)
as cnt_ok, sum(case when response_status=0 then 0 else 1 end) as cnt_fail,
round(sum(case when response_status=0 then 0 else 1 end)::real/count(id)*100)
as fail_percentil from api_apilog group by method order by fail_percentil;
diff --git a/plugin.video.synopsi/addon.xml b/plugin.video.synopsi/addon.xml
index 38fadf6..335a0f3 100644
--- a/plugin.video.synopsi/addon.xml
+++ b/plugin.video.synopsi/addon.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.synopsi"
name="SynopsiTV"
- version="0.5.1"
+ version="0.5.2"
provider-name="Synopsi.TV">
<requires>
<import addon="xbmc.python" version="2.0"/>
diff --git a/plugin.video.synopsi/apiclient.py
b/plugin.video.synopsi/apiclient.py
index 7f92119..ac60356 100644
--- a/plugin.video.synopsi/apiclient.py
+++ b/plugin.video.synopsi/apiclient.py
@@ -29,7 +29,7 @@ defaultIdentifyProps = commonTitleProps + ['tvshow_id']
watchableTitleProps = commonTitleProps + ['watched']
defaultTVShowProps = commonTitleProps + ['seasons']
smallListProps = ['id', 'cover_medium', 'name', 'watched', 'type']
-defaultEpisodeProps = smallListProps + ['season_number', 'episode_number']
+defaultEpisodeProps = smallListProps + ['season_number', 'episode_number',
'cover_large', 'tvshow_id', 'tvshow_name']
allSeasonProps = ['id', 'cover_full', 'cover_large', 'cover_medium',
'cover_small', 'cover_thumbnail', 'season_number', 'episodes_count',
'watched_count']
defaultSeasonProps = ['id', 'cover_medium', 'season_number', 'episodes_count',
'watched_count']
defaultSeasonProps2 = ['id', 'episodes']
@@ -462,7 +462,7 @@ class ApiClient(loggable.Loggable):
return self.execute(req)
- def unwatchedEpisodes(self, props=watchableTitleProps):
+ def unwatchedEpisodes(self, props=defaultEpisodeProps):
req = {
'methodPath': 'profile/unwatched_episodes/',
'method': 'get',
diff --git a/plugin.video.synopsi/app_apiclient.py
b/plugin.video.synopsi/app_apiclient.py
index aef8e50..5a1712e 100644
--- a/plugin.video.synopsi/app_apiclient.py
+++ b/plugin.video.synopsi/app_apiclient.py
@@ -160,13 +160,7 @@ class AppApiClient(ApiClient):
def get_tvshow_season(self, season_id):
season = self.season(season_id)
- # fix names
- for i in season['episodes']:
- episident = 'S%sE%s' % (i['season_number'],
i['episode_number'])
- i['name'] = '%s - %s' % (episident, i['name'])
- top.stvList.updateTitle(i)
-
- return season['episodes']
+ return season
def get_title(self, stv_id, detailProps=defaultDetailProps,
castProps=defaultCastProps):
return self.title(stv_id, detailProps, castProps)
diff --git a/plugin.video.synopsi/cache.py b/plugin.video.synopsi/cache.py
index 81d56f8..ea8223f 100644
--- a/plugin.video.synopsi/cache.py
+++ b/plugin.video.synopsi/cache.py
@@ -1,5 +1,5 @@
# xbmc
-import xbmc
+import xbmc, xbmcvfs
# python standart lib
import base64
@@ -47,7 +47,6 @@ class OfflineStvList(object):
self.filePath = filePath or self.__class__.getDefaultFilePath()
self.clear()
self.uuid = uuid
- #~ self.list()
@classmethod
def getDefaultFilePath(cls):
@@ -78,7 +77,6 @@ class OfflineStvList(object):
def deserialize(self, _string):
self.items, self.byType, self.byTypeId, self.byFilename,
self.byStvId = pickle.loads(base64.b64decode(_string))
- self.dump()
def log(self, msg):
log('CACHE / ' + msg)
@@ -130,9 +128,14 @@ class OfflineStvList(object):
if title.has_key('id'):
movie['stvId'] = title['id']
+
+ # use synopsi runtime if possible
+ if title.get('runtime'):
+ movie['runtime'] = 60 * title['runtime']
+
self.log('identified: ' + title['name'])
else:
- self.log('File NOT identified %s' %
movie['file'])
+ self.log('NOT identified %s' % movie['file'])
# current block could raise ApiCallError, when there is
not a real problem
try:
@@ -161,7 +164,7 @@ class OfflineStvList(object):
self.put(stv_title)
def put(self, item):
- " Put a new record in the list "
+ """ Put a new record in the list """
self.log('PUT ' + dump(filtertitles(item)))
# check if an item with this stvId is not already there
if item.has_key('stvId') and self.hasStvId(item['stvId']):
@@ -311,10 +314,10 @@ class OfflineStvList(object):
def clear(self):
self.items = []
- self.byType = { 'movie': {}, 'tvshow': {}, 'episode': {},
'season': {}}
- self.byTypeId = {}
+ self.byType = { 'movie': {}, 'tvshow': {}, 'episode': {},
'season': {}} # ids here are xbmc_ids, except tvshow_ids!
+ self.byTypeId = {}
# ids
here are xbmc_ids
self.byFilename = {}
- self.byStvId = {}
+ self.byStvId = {}
# ids
here are stv_ids
def getItems(self):
return self.items
@@ -403,12 +406,12 @@ class OfflineStvList(object):
def save(self):
self.log('SAVING / ' + self.filePath)
- f = open(self.filePath, 'w')
+ f = xbmcvfs.File(self.filePath, 'w')
f.write(self.serialize())
f.close()
def load(self):
- f = open(self.filePath, 'r')
+ f = xbmcvfs.File(self.filePath, 'r')
self.deserialize(f.read())
f.close()
@@ -496,7 +499,7 @@ class OnlineStvList(OfflineStvList):
class AppStvList(OnlineStvList):
def get_local_tvshows(self):
local_tvshows = self.getAllByType('tvshow')
- log('local tvshows ' + dump(local_tvshows))
+
return local_tvshows.values()
def get_tvshow_local_seasons(self, stv_id):
diff --git a/plugin.video.synopsi/changelog.txt
b/plugin.video.synopsi/changelog.txt
index d547882..002af98 100644
--- a/plugin.video.synopsi/changelog.txt
+++ b/plugin.video.synopsi/changelog.txt
@@ -1,3 +1,13 @@
+[B]Version 0.5.2[/B]
+
+* Fixed: if xbmc.Player doesn't provide runtime, plugin failed to show rating
dialog
+* Fixed: api directors format change
++ Added: display year after movie name
++ Added: display episode identification in every episode listings
++ Added: breadcrumbs for listings
++ Added: display tvshow name in episode detail
+* Fixed: show correct titles on home screen (recently added)
+
[B]Version 0.5.1 [/B]
- Fixed: labels in movie details
diff --git a/plugin.video.synopsi/dialog.py b/plugin.video.synopsi/dialog.py
index 60959c1..1e49cc6 100644
--- a/plugin.video.synopsi/dialog.py
+++ b/plugin.video.synopsi/dialog.py
@@ -20,6 +20,9 @@ from cache import StvList, DuplicateStvIdException
import top
from threading import Thread
+# temporary
+import random
+
ACTIONS_CLICK = [7, 100]
LIST_ITEM_CONTROL_ID = 500
HACK_GO_BACK = -2
@@ -111,23 +114,37 @@ class ListDialog(MyDialog):
self.selectedMovie = None
self.listControl = None
- def onInit(self):
+
+ def onInit(self):
+ win = xbmcgui.Window(xbmcgui.getCurrentWindowDialogId())
+
self.listControl = self.getControl(LIST_ITEM_CONTROL_ID)
self.listControl.reset()
-
+
+ # asynchronous initialization
if self.__dict__.get('_async_init'):
result = {}
kwargs = self._async_init.get('kwargs', {})
kwargs['result'] = result
try:
- self._async_init['method'](**kwargs)
+ self._async_init['method'](**kwargs) #
method(result=result, +kwargs)
except (AuthenticationError, ListEmptyException) as e:
self.close()
return
- self.data = result
+ self.data.update(result['result'])
+
+ # exception of incoming data format
+ if self.data.has_key('episodes'):
+ self.data['items'] = self.data['episodes']
+ first_episode = self.data['items'][0]
+ self.data['tvshow_name'] = first_episode['tvshow_name']
+ self.data['_categoryName'] = self.data['tvshow_name'] +
' - Season ' + first_episode['season_number']
+ win.setProperty('ContainerCategory',
self.data.get('_categoryName', ''))
+
self.updateItems()
+
def updateItems(self):
items = []
@@ -156,9 +173,27 @@ class ListDialog(MyDialog):
def _getListItem(self, item):
#~ itemPath = 'mode=' + str(ActionCode.VideoDialogShowById) +
'&stv_id=' + str(item['id'])
- li = xbmcgui.ListItem(item['name'],
iconImage=item['cover_medium'])
+ itemName = item['name']
+
+ # add year after name
+ if item.has_key('year'):
+ itemName += ' (' + str(item['year']) + ')'
+
+ # for episodes, add epis-ident
+ if item['type'] == 'episode':
+ episident = get_episode_identifier(item)
+ itemName = '%s - %s' % (episident, itemName)
+
+ # create listitem with basic properties
+ li = xbmcgui.ListItem(itemName, iconImage=item['cover_medium'])
li.setProperty('id', str(item['id']))
li.setProperty('type', str(item['type']))
+
+ if item['type'] == 'episode':
+ li.setProperty('episode_number',
str(item['episode_number']))
+ li.setProperty('season_number',
str(item['season_number']))
+ li.setProperty('tvshow_name', str(item['tvshow_name']))
+
#~ li.setProperty('path', str(itemPath))
# prefer already set custom_overlay, if N/A set custom overlay
@@ -197,7 +232,14 @@ class ListDialog(MyDialog):
elif stv_id == HACK_SHOW_ALL_LOCAL_MOVIES:
show_submenu(ActionCode.LocalMovies)
else:
- show_video_dialog({'type':
item.getProperty('type'), 'id': stv_id}, close=False)
+ data = {'type': item.getProperty('type'), 'id':
stv_id}
+ if data['type'] == 'episode':
+ data['season_number'] =
item.getProperty('season_number')
+ data['episode_number'] =
item.getProperty('episode_number')
+ data['tvshow_name'] =
item.getProperty('tvshow_name')
+ #~ data['tvshow_name'] =
self.data['tvshow_name']
+
+ show_video_dialog(data, close=False)
@@ -208,11 +250,10 @@ def show_movie_list(item_list):
open_list_dialog({ 'items': item_list })
def show_tvshows_episodes(stv_id):
- def init_data(result, **kwargs):
- log('asyn handler show_tvshows_episodes: ' + str(kwargs))
- result['items'] = top.apiClient.get_tvshow_season(stv_id)
+ def init_data(result):
+ result['result'] = top.apiClient.get_tvshow_season(stv_id)
- tpl_data = { '_async_init': { 'method': init_data, 'kwargs': {} }}
+ tpl_data = { '_async_init': { 'method': init_data }}
open_list_dialog(tpl_data)
@@ -225,9 +266,10 @@ class VideoDialog(MyDialog):
super(VideoDialog, self).__init__()
self.data = kwargs['data']
self.controlId = None
-
+
def _init_data(self):
json_data = self.data
+
if json_data.get('type') == 'tvshow':
stv_details = top.apiClient.tvshow(json_data['id'],
cast_props=defaultCastProps)
else:
@@ -280,7 +322,12 @@ class VideoDialog(MyDialog):
# fill-in the form
win = xbmcgui.Window(xbmcgui.getCurrentWindowDialogId())
- win.setProperty("Movie.Title", self.data["name"] +
'[COLOR=gray] (' + unicode(self.data.get('year')) + ')[/COLOR]')
+ str_title = self.data['name'] + '[COLOR=gray] (' +
unicode(self.data.get('year')) + ')[/COLOR]'
+ if self.data['type'] == 'episode':
+ episident = get_episode_identifier(self.data)
+ str_title = self.data['tvshow_name'] + ' -
[COLOR=gray]' + episident + ' -[/COLOR] ' + str_title
+
+ win.setProperty("Movie.Title", str_title)
win.setProperty("Movie.Plot", self.data["plot"])
win.setProperty("Movie.Cover", self.data["cover_full"])
@@ -502,7 +549,7 @@ def video_dialog_template_fill(stv_details, json_data={}):
append_tuple('Genre', 'genres', tr_genre)
append_tuple('Cast', 'cast', tr_cast)
- append_tuple('Director', 'directors', tr_genre) # reuse
tr_genre here
+ append_tuple('Director', 'directors', tr_cast) # reuse tr_cast
here
append_tuple('Runtime', 'runtime', tr_runtime)
if tpl_data.get('date'):
@@ -549,9 +596,9 @@ def get_submenu_item_list(action_code, **kwargs):
def show_submenu(action_code, **kwargs):
def init_data(result, **kwargs):
- log('init_data kwargs: ' + str(kwargs))
- result['items'] = get_submenu_item_list(**kwargs)
+ result['result'] = {'items': get_submenu_item_list(**kwargs)}
- kwargs['action_code'] = action_code
- tpl_data = { '_async_init': { 'method': init_data, 'kwargs': kwargs }}
+ categoryName = submenu_categories_dict[action_code]
+ kwargs['action_code'] = action_code
+ tpl_data = { '_categoryName': categoryName, '_async_init': { 'method':
init_data, 'kwargs': kwargs }}
open_list_dialog(tpl_data)
diff --git a/plugin.video.synopsi/loggable.py b/plugin.video.synopsi/loggable.py
index a5ea521..17c9d7b 100644
--- a/plugin.video.synopsi/loggable.py
+++ b/plugin.video.synopsi/loggable.py
@@ -18,7 +18,7 @@ class Loggable(object):
# assure that log dir exists
logdir = self.get_log_dir()
- if not os.path.exists(logdir):
+ if not xbmcvfs.exists(logdir):
xbmcvfs.mkdir(logdir)
fh = logging.handlers.RotatingFileHandler(os.path.join(logdir,
self.name + '.log'), mode='w', backupCount=2)
diff --git a/plugin.video.synopsi/resources/const.py
b/plugin.video.synopsi/resources/const.py
index ceca21c..45e41d1 100644
--- a/plugin.video.synopsi/resources/const.py
+++ b/plugin.video.synopsi/resources/const.py
@@ -6,4 +6,6 @@ KEY="59c53964b1013defcff0155f6e4d54a4"
SECRET="487615d20b22cdce510fd3476ed84d924e2b0c45ce7c49dc621764e05fae0904"
# addon
-SERVICE_IFACE_VERSION=1
+# incrementing this will cause restarting plugin after update
+SERVICE_IFACE_VERSION=2
+
diff --git
a/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml
b/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml
index a89b681..98a4627 100644
--- a/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml
+++ b/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml
@@ -10,9 +10,34 @@
<include>ContentPanelBackgrounds</include>
<control type="group">
<include>Window_OpenClose_Animation</include>
-<!-- <include>CommonRootView</include> <!-- view id = 50 -->
-<!-- <include>FullWidthList</include> <!-- view id = 51 -->
-<!-- <include>ThumbnailView</include> <!-- view id = 500 -->
+
+ <control type="image">
+ <description>Section header image</description>
+ <posx>20</posx>
+ <posy>3</posy>
+ <width>35</width>
+ <height>35</height>
+ <aspectratio>keep</aspectratio>
+ <texture>icon_video.png</texture>
+ </control>
+ <control type="grouplist">
+ <posx>65</posx>
+ <posy>5</posy>
+ <width>1000</width>
+ <height>30</height>
+ <orientation>horizontal</orientation>
+ <align>left</align>
+ <itemgap>5</itemgap>
+ <control type="label">
+ <include>WindowTitleCommons</include>
+ <label>$LOCALIZE[3]</label>
+ </control>
+ <control type="label">
+ <include>WindowTitleCommons</include>
+ <label>[COLOR=blue] -
[/COLOR]$INFO[Window.Property(ContainerCategory)]</label>
+
<visible>!IsEmpty(Container.FolderName)</visible>
+ </control>
+ </control>
<control type="group">
<visible>Control.IsVisible(500)</visible>
<include>VisibleFadeEffect</include>
@@ -114,6 +139,7 @@
<align>center</align>
<aligny>center</aligny>
<info>ListItem.Label</info>
+ <scrollsuffix> ~
</scrollsuffix>
</control>
<control type="image">
<posx>180</posx>
diff --git a/plugin.video.synopsi/scrobbler.py
b/plugin.video.synopsi/scrobbler.py
index 62162f2..1cbdc39 100644
--- a/plugin.video.synopsi/scrobbler.py
+++ b/plugin.video.synopsi/scrobbler.py
@@ -90,10 +90,10 @@ class SynopsiPlayer(xbmc.Player):
else:
if xbmc.Player().isPlayingVideo():
self.playing = True
- self.total_time = xbmc.Player().getTotalTime()
+ self.media_file = self.getPlayingFile()
+ self.total_time = self.getTotalTime()
self.current_time = self.get_time()
self.started()
- self.media_file = self.getPlayingFile()
self.mediainfotag = self.getVideoInfoTag()
self.last_played_file = self.media_file
self.subtitle_file = self.getSubtitles()
@@ -133,6 +133,15 @@ class SynopsiPlayer(xbmc.Player):
return t
+ def getTotalTime(self):
+ tt = xbmc.Player().getTotalTime()
+
+ if not tt:
+ title = self.cache.getByFilename(self.media_file)
+ tt = title.get('runtime')
+
+ return tt
+
def get_media_info_tag(self):
try:
self.mediainfotag = self.getVideoInfoTag()
@@ -174,9 +183,14 @@ class SynopsiPlayerDecor(SynopsiPlayer):
def stopped(self):
self.playerEvent('stop')
+
+ # if we still dont have total runtime, use some default average
(this should now be very rare case)
+ if not self.total_time:
+ self.total_time = 5400
+
percent = self.current_time / self.total_time
self.log('percent:' + str(self.current_time / self.total_time))
-
+
# ask for rating only if more than 70% of movie passed
if percent > 0.7:
self.rate_file(self.last_played_file)
diff --git a/plugin.video.synopsi/tests/apiclient.unittest.py
b/plugin.video.synopsi/tests/apiclient.unittest.py
index 5ad2284..49f1795 100644
--- a/plugin.video.synopsi/tests/apiclient.unittest.py
+++ b/plugin.video.synopsi/tests/apiclient.unittest.py
@@ -4,6 +4,8 @@ from unittest import *
import logging
import json
from copy import copy
+import random
+import string
# test helper
from common import connection
@@ -92,10 +94,58 @@ class ApiTest(TestCase):
stv_title = client.titleIdentify(**ident2)
self.assertTrue(stv_title.has_key('type'))
+ ident3 = {
+ 'file_name':
'_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
+ }
+
+ stv_title = client.titleIdentify(**ident3)
+
+ self.assertTrue(stv_title.has_key('type'))
+
+ ident = {
+ 'file_name':
'_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
+ 'stv_title_hash': None,
+ 'os_title_hash': None
+ }
+
+ stv_title = client.titleIdentify(**ident)
+
+ self.assertTrue(stv_title.has_key('type'))
+
+ ident = {
+ 'file_name':
'_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
+ 'stv_title_hash': ''
+ }
+
+ stv_title = client.titleIdentify(**ident)
+
+ self.assertTrue(stv_title.has_key('type'))
+
+ ident = {
+ 'file_name': 'Avatar.avi',
+ 'stv_title_hash': ''
+ }
+
+ stv_title = client.titleIdentify(**ident)
+
+ self.assertTrue(stv_title.has_key('type'))
+
+ ident = {
+ 'file_name': 'Rambo.avi',
+ }
+
+ stv_title = client.titleIdentify(**ident)
+
+ self.assertTrue(stv_title.has_key('type'))
+
def test_library_add(self):
client.getAccessToken()
-
+
+ # change the device id, to use empty library
+ randstr = ''.join(random.choice(string.ascii_uppercase +
string.digits) for x in range(10))
+ client.device_id = 'testing_' + randstr
+
ident = {
'file_name':
'/Volumes/FLOAT/Film/_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
'stv_title_hash':
'8b05ff1ad4865480e4705a42b413115db2bf94db',
@@ -142,21 +192,55 @@ class ApiTest(TestCase):
self.assertTrue(data.has_key('titles'))
self.assertTrue(len(data['titles']) > 0)
- def test_profile_recco_local(self):
+ def test_profile_recco_local(self):
+ """
+ To test local recco, we have to prepare a scenario for
it:
+ - create new client with origin library
+ - get global recco
+ - add some random titles from global recco to library
+ - add titles not in global recco to library
+ - test that first title is in local recco, and second
not
+ """
+ props = [ 'id', 'name', 'year', 'cover_small' ]
+
+ device_id = ''.join([random.choice(string.hexdigits) for n in
xrange(32)])
+ new_client = ApiClient(c['base_url'], c['key'], c['secret'],
c['username'], c['password'], device_id, debugLvl = logging.DEBUG,
rel_api_url=c['rel_api_url'])
+
+ # get global recco
+ reco_global = new_client.profileRecco('movie', False, 50, props)
+ reco_global_ids = [i['id'] for i in reco_global['titles']]
+
+ # pick five titles
+ random_pick = list(set([random.choice(reco_global_ids) for i in
xrange(0,5)]))
+
+ # add them to library
+ for i in random_pick:
+ new_client.libraryTitleAdd(i)
+
+ # wait a little !
+ time.sleep(1)
+
+ # get local recco
+ reco_local = new_client.profileRecco('movie', True, 50, props)
- props = [ 'year', 'cover_small' ]
- data = client.profileRecco('movie', True, 5, props)
-
- self.assertTrue(data.has_key('recco_id'))
- self.assertTrue(data.has_key('titles'))
- self.assertTrue(len(data['titles']) > 0)
+ reco_local_ids = [i['id'] for i in reco_local['titles']]
+
+ # check local recco
+ self.assertTrue(reco_local.has_key('recco_id'))
+ self.assertTrue(reco_local.has_key('titles'))
+ self.assertTrue(len(reco_local['titles']) > 0)
+
+ # every picked movie should be in local recco
+ for i in random_pick:
+ self.assertTrue(i in reco_local_ids)
+
def test_profile_recco_watched(self):
props = [ 'id', 'year', 'cover_small' ]
data = client.profileRecco('movie', False, 5, props)
all_ids = [ i['id'] for i in data['titles'] ]
- print dump(all_ids)
+
self.assertTrue(data.has_key('recco_id'))
self.assertTrue(data.has_key('titles'))
self.assertTrue(len(data['titles']) > 0)
@@ -166,7 +250,7 @@ class ApiTest(TestCase):
new_data = client.profileRecco('movie', False, 5, props)
all_ids = [ i['id'] for i in new_data['titles'] ]
- print dump(all_ids)
+
self.assertFalse(check_id in all_ids)
@@ -196,12 +280,14 @@ class ApiTest(TestCase):
def test_season(self):
title = client.season(14376)
- # print dump(title)
+ self.assertTrue(title.has_key('episodes'))
+ es = title['episodes']
+ self.assertTrue(es[0].has_key('season_number'))
+ self.assertTrue(es[0].has_key('episode_number'))
+ self.assertTrue(es[0].has_key('watched'))
+ self.assertTrue(es[0].has_key('id'))
+
- self.assertTrue(title.has_key('cover_full'))
- self.assertTrue(title.get('type')=='tvshow')
- self.assertTrue(title.get('year')==2005)
- self.assertTrue(title['cast'][0]['name']=='Josh Radnor')
def test_unicode_input(self):
data = {
@@ -216,8 +302,12 @@ class ApiTest(TestCase):
def test_search(self):
result = client.search('Adams aebler', 13)
- # print dump(result)
+
self.assertTrue(result.has_key('search_result'))
+ self.assertTrue(result['search_result'][0]['id'] == 514461)
+
+
+ result = client.search('Love', 13)
self.assertTrue(len(result['search_result']) == 13)
def test_identify_correct(self):
@@ -225,6 +315,7 @@ class ApiTest(TestCase):
# print dump(result)
self.assertTrue(result['status']=='ok')
+ @skip('this needs deeper work')
def test_identify_correct_library(self):
TITLE_CORRECTION_TARGET = 1947362
CORRECTION_FILE_HASH =
'52b6f00222cdb3631d9914aee6b662961e924aa5' # hash of my "three times" file
@@ -298,7 +389,7 @@ class ApiTest(TestCase):
self.assertTrue(TITLE_CORRECTION_TARGET not in lib_ids)
self.assertTrue(SOME_ID_IN_LIBRARY in lib_ids)
-
+ @skip('this needs deeper work')
def test_correction_recco(self):
TITLE_CORRECTION_TARGET = 1947362
CORRECTION_FILE_HASH =
'52b6f00222cdb3631d9914aee6b662961e924aa5' # hash of my "three times" file
@@ -327,15 +418,25 @@ class ApiTest(TestCase):
print 'removing %d from library' %
TITLE_CORRECTION_TARGET
client.libraryTitleRemove(TITLE_CORRECTION_TARGET)
-
-
+
def test_library(self):
result = client.library(['date', 'genres', 'cover_small'])
- print dump(result)
+ self.assertTrue(result.get('created'))
+ self.assertTrue(result.get('device_id'))
+ self.assertTrue(result.get('name'))
+ self.assertTrue(result.get('titles'))
+ self.assertTrue(type(result['titles']) is list)
+
+ result2 = client.library(['id', 'cover_full', 'cover_large',
'cover_medium', 'cover_small', 'cover_thumbnail', 'date', 'genres', 'url',
'name', 'plot', 'released', 'trailer', 'type', 'year', 'runtime', 'directors',
'writers', 'cast', 'watched'])
+ self.assertTrue(result2.get('created'))
+ self.assertTrue(result2.get('device_id'))
+ self.assertTrue(result2.get('name'))
+ self.assertTrue(result2.get('titles'))
+ self.assertTrue(type(result2['titles']) is list)
if __name__ == '__main__':
c = connection
- client = ApiClient(c['base_url'], c['key'], c['secret'], c['username'],
c['password'], c['device_id'], debugLvl = logging.WARNING,
rel_api_url=c['rel_api_url'])
+ client = ApiClient(c['base_url'], c['key'], c['secret'], c['username'],
c['password'], c['device_id'], debugLvl = logging.DEBUG,
rel_api_url=c['rel_api_url'])
logger = logging.getLogger()
diff --git a/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py
b/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py
index 5724774..c9803ba 100644
--- a/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py
+++ b/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py
@@ -1,3 +1,7 @@
import os
mkdir = os.mkdir
+
+exists = os.path.exists
+
+File = open
diff --git a/plugin.video.synopsi/tests/utilities.unittest.py
b/plugin.video.synopsi/tests/utilities.unittest.py
index 5430fd0..025bc8c 100644
--- a/plugin.video.synopsi/tests/utilities.unittest.py
+++ b/plugin.video.synopsi/tests/utilities.unittest.py
@@ -22,13 +22,32 @@ class UtilitiesTest(TestCase):
movie['os_title_hash'] = hash_opensubtitle(path)
return movie
- print gethash(hash_path)
+ #~ print gethash(hash_path)
+
+ nonex_hash = gethash('x://non-existent')
+ self.assertEqual(nonex_hash['stv_title_hash'], None)
+ self.assertEqual(nonex_hash['os_title_hash'], None)
+
+
+ @skip('this is not supposed to work yet')
+ def test_samba_hash(self):
+ # test samba share hash (assumes local samba share)
+ # cannot run this test outside xbmc
+ local_path =
'~/Videos/Movies/Intouchables/Intouchables.2011.FRENCH.720p.BluRay.x264.mkv'
+ local_hash = gethash(local_path)
+
+ samba_path =
'smb://crux/movies/Intouchables/Intouchables.2011.FRENCH.720p.BluRay.x264.mkv'
+ samba_hash = gethash(local_path)
+
+ self.assertEqual(local_hash, samba_hash)
+
def test_xml_sources(self):
sources = get_movie_sources()
sources.sort(key=len, reverse=True)
print sources
-
+
+
def test_rel_path(self):
path1 =
'/home/smid/Videos/_testset/TVShows/xxx/the/movie/file.avi'
rel1 = rel_path(path1)
diff --git a/plugin.video.synopsi/utilities.py
b/plugin.video.synopsi/utilities.py
index b918ad2..0bc0bcd 100644
--- a/plugin.video.synopsi/utilities.py
+++ b/plugin.video.synopsi/utilities.py
@@ -1,5 +1,5 @@
# xbmc
-import xbmc, xbmcgui, xbmcaddon, xbmcplugin
+import xbmc, xbmcgui, xbmcaddon, xbmcplugin, xbmcvfs
import CommonFunctions
# python standart lib
@@ -45,7 +45,7 @@ SEARCH_RESULT_LIMIT = 15
# api request title properties
-reccoDefaultProps = ['id', 'cover_medium', 'name', 'type', 'watched']
+reccoDefaultProps = ['id', 'cover_medium', 'name', 'type', 'watched', 'year']
defaultDetailProps = ['id', 'cover_full', 'cover_large', 'cover_medium',
'cover_small', 'cover_thumbnail', 'date', 'genres', 'url', 'name', 'plot',
'released', 'trailer', 'type', 'year', 'directors', 'writers', 'runtime',
'cast']
tvshowdefaultDetailProps = defaultDetailProps + ['seasons']
defaultCastProps = ['name']
@@ -82,13 +82,28 @@ class ActionCode:
VideoDialogShow = 900
VideoDialogShowById = 910
+submenu_categories = [
+ (ActionCode.MovieRecco, "Movie Recommendations"),
+ (ActionCode.TVShows, "Popular TV Shows"),
+ (ActionCode.LocalMovieRecco, "Local Movie Recommendations"),
+ (ActionCode.LocalTVShows, "Local TV Shows"),
+ (ActionCode.UnwatchedEpisodes, "Unwatched TV Show Episodes"),
+ (ActionCode.UpcomingEpisodes, "Upcoming TV Episodes"),
+ (ActionCode.LoginAndSettings, "Login and Settings")
+]
+
+submenu_categories_dict = dict(submenu_categories)
+
+# we do not want the all local movies have listed in main menu, so this is an
easy fix
+submenu_categories_dict[ActionCode.LocalMovies] = 'All Your Local Movies'
+
# texts
t_noupcoming = 'There are no upcoming episodes from your tracked TV shows.'
t_nounwatched = 'There are no unwatched episodes in your TV Show tracking'
t_nolocalrecco = 'There are no items in this list. Either you have no movies
in your library or they have not been recognized by Synopsi'
t_nolocaltvshow = 'There are no items in this list. Either you have no
episodes in your library or they have not been recognized by Synopsi'
t_needrestart = 'To start the SynopsiTV service, please turn off your media
center then turn it back on again. Do this now?'
-t_needrestart_update = 'Addon service has been updated. For the plugin to work
correctly, turn off your media center then turn it back on again. Do this now?'
+t_needrestart_update = 'Addon has been recently updated. For the plugin to
work, it\'s neccessary to restart your media center. Restart now ?'
t_enter_title_to_search = 'Enter a title name to search for.'
t_correct_search_title = 'Search for the correct title'
@@ -219,7 +234,7 @@ def check_first_run():
xbmc.executebuiltin('ReloadSkin()')
raise Exception('Addon service is not running')
-def dialog_text(msg, max_line_length=20, max_lines=3):
+def dialog_text(msg, max_line_length=50, max_lines=3):
line_end = [0]
idx = -1
line_no = 0
@@ -259,19 +274,19 @@ def list_get(alist, index, default=''):
return default
def dialog_ok(msg):
- lines = dialog_text(msg, 45)
+ lines = dialog_text(msg)
return xbmcgui.Dialog().ok(t_stv, list_get(lines, 0), list_get(lines,
1), list_get(lines, 2))
def dialog_yesno(msg):
- lines = dialog_text(msg, 45)
+ lines = dialog_text(msg)
return xbmcgui.Dialog().yesno(t_stv, list_get(lines, 0),
list_get(lines, 1), list_get(lines, 2))
def clear_setting_cache():
"Clear cached addon setting. Useful after update"
settingsPath = os.path.join(__profile__, 'settings.xml')
- if os.path.exists(settingsPath):
- os.remove(settingsPath)
+ if xbmcvfs.exists(settingsPath):
+ xbmcvfs.delete(settingsPath)
def setting_cache_append_string(string):
settingsPath = os.path.join(__profile__, 'settings.xml')
@@ -307,7 +322,7 @@ class XMLRatingDialog(xbmcgui.WindowXMLDialog):
def onClick(self, controlId):
"""
- For controlID see: <control id="11" type="button"> in
SynopsiDialog.xml
+ For controlID see: <control id="11" type="button"> in Rating.xml
"""
if controlId == 11:
self.response = 1
@@ -345,7 +360,7 @@ class XMLLoginDialog(xbmcgui.WindowXMLDialog):
def onClick(self, controlId):
"""
- For controlID see: <control id="11" type="button"> in
SynopsiDialog.xml
+ For controlID see: <control id="11" type="button"> in Rating.xml
"""
# log(str('onClick:'+str(controlId)))
@@ -396,19 +411,40 @@ def is_protected(path):
return False
+def textfilter(bytestring):
+ import string,re
+
+ norm = string.maketrans('', '') #builds list of all characters
+ non_alnum = string.translate(norm, norm, string.letters+string.digits)
+
+ trans_nontext=string.maketrans(non_alnum,'?'*len(non_alnum))
+ cleaned=string.translate(bytestring, trans_nontext)
+
+ return cleaned
+
def stv_hash(filepath):
"""
New synopsi hash. Hashing the sedond 512 kB of a file using SHA1.
"""
+ chunk_offset = 524288
+ chunk_length = 524288
+
sha1 = hashlib.sha1()
try:
- with open(filepath, 'rb') as f:
- f.seek(524288, 0)
- sha1.update(f.read(524288))
+ f = xbmcvfs.File(filepath, 'r')
+ f.seek(chunk_offset, 0)
+ fcontent = f.read(chunk_length)
+ if len(fcontent) != chunk_length:
+ raise IOError()
+
+ sha1.update(fcontent)
+ f.close()
+
except (IOError) as e:
- raise HashError('Unable to hash file [%s]' % filepath)
+ log('Unable to hash file [%s]' % filepath)
+ return None
return sha1.hexdigest()
@@ -421,10 +457,11 @@ def old_stv_hash(filepath):
sha1 = hashlib.sha1()
try:
- with open(filepath, 'rb') as f:
- sha1.update(f.read(256))
- f.seek(-256, 2)
- sha1.update(f.read(256))
+ f = xbmcvfs.File(filepath, 'rb')
+ sha1.update(f.read(256))
+ f.seek(-256, 2)
+ sha1.update(f.read(256))
+ f.close()
except (IOError) as e:
return None
@@ -439,9 +476,9 @@ def hash_opensubtitle(name):
longlongformat = 'q' # long long
bytesize = struct.calcsize(longlongformat)
- _file = open(name, "rb")
+ _file = xbmcvfs.File(name, 'rb')
- filesize = os.path.getsize(name)
+ filesize = _file.size()
hash = filesize
if filesize < 65536 * 2:
@@ -467,7 +504,8 @@ def hash_opensubtitle(name):
return returnedhash
except(IOError):
- raise HashError('Unable to hash file [%s]' % name)
+ log('Unable to hash file [%s]' % name)
+ return None
def generate_deviceid():
@@ -522,14 +560,17 @@ def get_api_port():
If nothing is changed return default 9090.
"""
- path = os.path.join('special://profile', 'advancedsettings.xml')
- path = xbmc.translatePath(path)
-
try:
- tree = et.parse(path)
- root = tree.getroot()
+ path = os.path.join('special://profile', 'advancedsettings.xml')
+
+ f = xbmcvfs.File(path, 'r')
+ fcontent = f.read()
+ f.close()
+
+ root = et.fromstring(fcontent)
nodes = root.findall('.//tcpport')
value = int(nodes[0].text)
+
except:
value = 9090
@@ -579,15 +620,14 @@ def home_screen_fill(apiClient, cache):
# recco could return less than 5 items
if i < len(episode_recco):
- e = episode_recco[i]
- lib_item = cache.getByStvId(e['id'])
- log('episode %d %s' % (i, e['name']))
- log('lib_item %s' % (str(lib_item)))
-
WINDOW.setProperty("LatestEpisode.{0}.EpisodeTitle".format(i+1), e['name'])
-
WINDOW.setProperty("LatestEpisode.{0}.ShowTitle".format(i+1), e['name'])
-
WINDOW.setProperty("LatestEpisode.{0}.EpisodeNo".format(i+1), str(i))
- if lib_item:
-
WINDOW.setProperty("LatestEpisode.{0}.Path".format(i+1), e['cover_large'])
+ e = episode_recco[i]
+ c_episode = cache.getByStvId(e['id'])
+
+
+
WINDOW.setProperty("LatestEpisode.{0}.EpisodeTitle".format(i+1),
e['tvshow_name']) # tv show name
+
WINDOW.setProperty("LatestEpisode.{0}.ShowTitle".format(i+1), e['name'])
# episode name
+
WINDOW.setProperty("LatestEpisode.{0}.EpisodeNo".format(i+1),
get_episode_identifier(e)) # episode id string
+
WINDOW.setProperty("LatestEpisode.{0}.Path".format(i+1), c_episode['file'] if
c_episode else '')
WINDOW.setProperty("LatestEpisode.{0}.Thumb".format(i+1), e['cover_large'])
@@ -639,7 +679,7 @@ def get_rating():
Get rating from user:
1 = Amazing, 2 = OK, 3 = Terrible, 4 = Not rated
"""
- ui = XMLRatingDialog("SynopsiDialog.xml", __addonpath__, "Default")
+ ui = XMLRatingDialog("Rating.xml", __addonpath__, "Default")
ui.doModal()
_response = ui.response
del ui
@@ -691,26 +731,25 @@ def add_movie(movie, mode, iconimage):
new_li = (u, li, isFolder)
return new_li
-
-
+
+
def show_categories():
"""
Shows initial categories on home screen.
"""
xbmc.executebuiltin("Container.SetViewMode(503)")
- add_directory("Movie Recommendations", "url", ActionCode.MovieRecco,
"list.png")
- add_directory("Popular TV Shows", "url", ActionCode.TVShows, "list.png")
- add_directory("Local Movie Recommendations", "url",
ActionCode.LocalMovieRecco, "list.png")
- add_directory("Local TV Shows", "url", ActionCode.LocalTVShows,
"list.png")
- add_directory("Unwatched TV Show Episodes", "url",
ActionCode.UnwatchedEpisodes, "list.png")
- add_directory("Upcoming TV Episodes", "url",
ActionCode.UpcomingEpisodes, "list.png")
- add_directory("Login and Settings", "url", ActionCode.LoginAndSettings,
"list.png")
+ for categoryCode, categoryName in submenu_categories:
+ add_directory(categoryName, "url", categoryCode, "list.png")
def get_movie_sources():
userdata = xbmc.translatePath('special://userdata')
sourceFilePath = os.path.join(userdata, 'sources.xml')
- tree = et.parse(sourceFilePath)
- root = tree.getroot()
+
+ f = xbmcvfs.File(sourceFilePath, 'r')
+ fcontent = f.read()
+ f.close()
+
+ root = et.fromstring(fcontent)
el = root.findall('video/source/path')
return sorted([i.text for i in el], key=len, reverse=True)
@@ -722,4 +761,5 @@ def rel_path(realpath):
return realpath
-
+def get_episode_identifier(item):
+ return 'S%sE%s' % (item.get('season_number', '??'),
item.get('episode_number', '??'))
diff --git a/plugin.video.synopsi/xbmcrpc.py b/plugin.video.synopsi/xbmcrpc.py
index 19c4075..2b7fd81 100644
--- a/plugin.video.synopsi/xbmcrpc.py
+++ b/plugin.video.synopsi/xbmcrpc.py
@@ -8,7 +8,7 @@ import json
from utilities import *
from loggable import Loggable
-defaultProperties = ['file', 'imdbnumber', "lastplayed", "playcount"]
+defaultProperties = ['file', 'imdbnumber', "lastplayed", "playcount",
"runtime"]
class xbmcRPCclient(Loggable):
-----------------------------------------------------------------------
Summary of changes:
plugin.video.synopsi/_src/install_xbmc_version.sh | 3 +-
plugin.video.synopsi/_src/usefull_queries.sql | 4 +-
plugin.video.synopsi/addon.xml | 2 +-
plugin.video.synopsi/apiclient.py | 4 +-
plugin.video.synopsi/app_apiclient.py | 8 +-
plugin.video.synopsi/cache.py | 25 +-
plugin.video.synopsi/changelog.txt | 10 +
plugin.video.synopsi/dialog.py | 81 +-
plugin.video.synopsi/loggable.py | 2 +-
plugin.video.synopsi/resources/const.py | 4 +-
.../Default/720p/{SynopsiDialog.xml => Rating.xml} | 0
.../resources/skins/Default/720p/ViewsFileMode.xml | 613 --------
.../skins/Default/720p/ViewsVideoLibrary.xml | 1545 --------------------
.../skins/Default/720p/customLoginDialog.xml | 155 --
.../skins/Default/720p/custom_MyVideoNav.xml | 32 +-
.../resources/skins/Default/720p/includes.xml | 3 -
plugin.video.synopsi/scrobbler.py | 20 +-
plugin.video.synopsi/tests/apiclient.unittest.py | 143 ++-
plugin.video.synopsi/tests/fakeenv/xbmcvfs.py | 4 +
plugin.video.synopsi/tests/utilities.unittest.py | 23 +-
plugin.video.synopsi/utilities.py | 136 ++-
plugin.video.synopsi/xbmcrpc.py | 2 +-
22 files changed, 383 insertions(+), 2436 deletions(-)
rename plugin.video.synopsi/resources/skins/Default/720p/{SynopsiDialog.xml =>
Rating.xml} (100%)
delete mode 100644
plugin.video.synopsi/resources/skins/Default/720p/ViewsFileMode.xml
delete mode 100644
plugin.video.synopsi/resources/skins/Default/720p/ViewsVideoLibrary.xml
delete mode 100644
plugin.video.synopsi/resources/skins/Default/720p/customLoginDialog.xml
delete mode 100644
plugin.video.synopsi/resources/skins/Default/720p/includes.xml
hooks/post-receive
--
Plugins
------------------------------------------------------------------------------
Own the Future-Intel(R) Level Up Game Demo Contest 2013
Rise to greatness in Intel's independent game demo contest. Compete
for recognition, cash, and the chance to get your game on Steam.
$5K grand prize plus 10 genre and skill prizes. Submit your demo
by 6/6/13. http://altfarm.mediaplex.com/ad/ck/12124-176961-30367-2
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons