Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 163 additions & 89 deletions Tested.bundle/Contents/Code/__init__.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,75 @@
API_PATH = 'http://api.tested.com'
API_KEY = '060b7347a9f8e317a1a9efceea0f8b70bf7018f6'

ART = 'art-default.png'
ICON = 'icon-default.png'

def ValidatePrefs():
link_code = Prefs['link_code'].upper()
if link_code and len(link_code) == 6:
response = JSON.ObjectFromURL(API_PATH + '/validate?link_code=' + link_code + '&format=json')
if 'api_key' in response:
Dict['api_key'] = response['api_key']
Dict.Save()

# Category Icons
LIVE_ICON = 'live.png'
LATEST_ICON = 'latest.png'
SCIENCE_ICON = 'science.png'
REVIEW_ICON = 'review.png'
APP_OF_THE_DAY_ICON = 'app-of-the-day.png'
DIY_ICON = 'diy.png'
COFFEE_ICON = 'coffee.png'
HOWTO_ICON = 'howto.png'
MAKERBOT_ICON = 'makerbot.png'
SEARCH_ICON = 'search.png'

# Creates the main menu (the first menu that pops up when opening the app)
@handler('/video/tested', 'Tested')
def MainMenu():
if 'api_key' in Dict:
global API_KEY
API_KEY = Dict['api_key']

oc = ObjectContainer()

# Live stream
response = JSON.ObjectFromURL(API_PATH + '/chats/?api_key=' + API_KEY + '&format=json')

if response['status_code'] == 100:
# Revert to the default key
Dict.Reset()
Dict.Save()

chats = response['results']
for chat in chats:
url = 'http://www.justin.tv/widgets/live_embed_player.swf?channel=' + chat['channel_name'] + '&auto_play=true&start_volume=25'
if chat['password']:
url += '&publisherGuard=' + chat['password']

# Checks to see if a livestream is going on, since the Tested API is no longer around it checks the Justin TV API
# Assumes live streams are on the "tested" channel
LIVE_CHANNEL = 'tested'
response = JSON.ObjectFromURL('http://api.justin.tv/api/stream/list.json?channel=' + LIVE_CHANNEL)

if( len(response) > 0):

stream = response[0] #This should be fine as there can only ever be one live stream going per channel

url = 'http://www.justin.tv/widgets/live_embed_player.swf?channel=' + LIVE_CHANNEL + '&auto_play=true&start_volume=25'
title = stream['title']

oc.add(
VideoClipObject(
key=WebVideoURL(url),
title='LIVE: ' + chat['title'],
summary=chat['deck'],
title='LIVE: ' + title,
source_title='Justin.tv',
thumb=chat['image']['super_url'],
thumb=R(LIVE_ICON),
art=R(ART),
rating_key=chat['channel_name']
rating_key=LIVE_CHANNEL
)
)


oc.add(
DirectoryObject(
key='/video/tested/videos',
title='Latest',
summary='Watch the newest stuff.',
thumb=R(ICON),
summary='',
thumb=R(LATEST_ICON),
art=R(ART)
)
)

categories = JSON.ObjectFromURL(API_PATH + '/video_types/?api_key=' + API_KEY + '&format=json')['results']


# The categories were chosen using a script to see what the most popular tags were on tested youtube videos
# Script can be found here: https://gist.github.com/2940575
categories = [
('Science & Technology', 'Tech', '', R(SCIENCE_ICON)),
('Reviews', 'review', '', R(REVIEW_ICON)),
('App Of The Day', 'app,of,the,day', '', R(APP_OF_THE_DAY_ICON)),
('DIY', 'diy', '', R(DIY_ICON)),
('Coffee', 'coffee', '', R(COFFEE_ICON)),
('Howtos', 'howto', '', R(HOWTO_ICON)),
('Makerbot', 'makerbot', '', R(MAKERBOT_ICON))
]
for cat in categories:
oc.add(
DirectoryObject(
key='/video/tested/videos/?cat_id=' + str(cat['id']),
title=cat['name'],
summary=cat['deck'],
thumb=R(ICON),
key='/video/tested/videos/?cat_id=' + str(cat[1]),
title=cat[0],
summary='',
thumb=cat[3],
art=R(ART)
)
)
Expand All @@ -74,64 +79,133 @@ def MainMenu():
key=Callback(Videos),
title='Search',
prompt='Search',
thumb=R(ICON),
art=R(ART)
)
)

oc.add(
PrefsObject(
title='Preferences',
thumb=R(ICON),
thumb=R(SEARCH_ICON),
art=R(ART)
)
)

return oc


# Handles when a category is selected from the main menu
@route('/video/tested/videos')
def Videos(cat_id=None, query=None):
if 'api_key' in Dict:
global API_KEY
API_KEY = Dict['api_key']
def Videos(cat_id=None, query=None, page = 1):

# Apparently plex *always* passes parameters to these functions as strings
# So have to 'unconvert' the parameters
page = int(page)

if query == '':
query = None

if cat_id == '':
cat_id = None

# Change this to change how many results are displayed per page, maximum is 50 according to youtube API
MAXRESULTS = 20

oc = ObjectContainer()

Log(str(page))
url_suffix = '&start-index=' + str((page - 1) * MAXRESULTS + 1)
url_suffix += '&max-results=' + str(MAXRESULTS)

if query:
videos = JSON.ObjectFromURL(API_PATH + '/search/?api_key=' + API_KEY + '&resources=video&query=' + query + '&format=json')['results']
videos = JSON.ObjectFromURL('http://gdata.youtube.com/feeds/api/videos?alt=json&author=testedcom&prettyprint=true&v=2' + url_suffix + '&q=' + query.replace(' ', '%20'))
elif cat_id:
videos = JSON.ObjectFromURL(API_PATH + '/videos/?api_key=' + API_KEY + '&video_type=' + cat_id + '&sort=-publish_date&format=json')['results']
videos = JSON.ObjectFromURL('http://gdata.youtube.com/feeds/api/videos?alt=json&author=testedcom&prettyprint=true&v=2' + url_suffix + '&orderby=published&category=' + cat_id)
else:
videos = JSON.ObjectFromURL(API_PATH + '/videos/?api_key=' + API_KEY + '&sort=-publish_date&format=json')['results']

if Prefs['quality'] == 'Auto':
if 'hd_url' in videos[0]:
quality = 'hd_url'
else:
quality = 'high_url'
else:
quality = Prefs['quality'].lower() + '_url'

for vid in videos:
if 'wallpaper_image' not in vid or not vid['wallpaper_image']: # or whatever it gets called
vid_art = R(ART)
else:
vid_art = vid['wallpaper_image']

if quality == 'hd_url':
url = vid[quality] + '&api_key=' + API_KEY
else:
url = vid[quality]

oc.add(
VideoClipObject(
key=url,
title=vid['name'],
summary=vid['deck'],
thumb=vid['image']['super_url'],
art=vid_art,
rating_key=vid['id']
)
)
videos = JSON.ObjectFromURL('https://gdata.youtube.com/feeds/api/users/testedcom/uploads?alt=json&prettyprint=true&v=2' + url_suffix )


# The following tidbit was inspired by the youtube plugin that plex uses
if videos['feed'].has_key('entry'):

for video in videos['feed']['entry']:

if CheckRejectedEntry(video):
continue

video_url = None
for video_links in video['link']:
if video_links['type'] == 'text/html':
video_url = video_links['href']
break

if video_url is None:
Log('Found video that had no URL')
continue

video_title = video['media$group']['media$title']['$t']
thumb = video['media$group']['media$thumbnail'][2]['url']
duration = int(video['media$group']['yt$duration']['seconds']) * 1000

summary = None
try: summary = video['media$group']['media$description']['$t']
except: pass

rating = None
try: rating = float(video['gd$rating']['average']) * 2
except: pass

date = None
try: date = Datetime.ParseDate(video['published']['$t'].split('T')[0])
except:
try: date = Datetime.ParseDate(video['updated']['$t'].split('T')[0])
except: pass

oc.add(
VideoClipObject(
url=video_url + "&hd=1",
title=video_title,
summary=summary,
thumb=Callback(GetThumb, url = thumb),
art=R(ART),
rating = rating,
originally_available_at = date
)
)

# Handles pagination if there are more results that were not shown
if videos['feed'].has_key('openSearch$totalResults'):
total_results = int(videos['feed']['openSearch$totalResults']['$t'])
items_per_page = int(videos['feed']['openSearch$itemsPerPage']['$t'])
start_index = int(videos['feed']['openSearch$startIndex']['$t'])

if not query:
query = ''

if not cat_id:
cat_id = ''

if (start_index + items_per_page) < total_results:
oc.add(DirectoryObject(key = Callback(Videos, cat_id = cat_id, query = query, page = page + 1), title = 'Next'))

return oc

# Retrives the thumbnail of a video from the given url, caching it
def GetThumb(url):
try:
data = HTTP.Request(url, cacheTime = CACHE_1WEEK).content
return DataObject(data, 'image/jpeg')
except:
Log.Exception("Error when attempting to get the associated thumb")
return Redirect(R(ICON))

# Checks to see if the video passed is a valid video (not deleted, rejected, restricted, etc
def CheckRejectedEntry(entry):
try:
status_name = entry['app$control']['yt$state']['name']

if status_name in ['deleted', 'rejected', 'failed']:
return True

if status_name == 'restricted':
status_reason = entry['app$control']['yt$state']['reasonCode']

if status_reason in ['private', 'requesterRegion']:
return True

except:
pass

return False
13 changes: 0 additions & 13 deletions Tested.bundle/Contents/DefaultPrefs.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,2 @@
[
{
"id": "link_code",
"label": "Link Code",
"type": "text",
"default": ""
},
{
"id": "quality",
"type": "enum",
"label": "Quality",
"values": ["Auto", "Low", "High", "HD"],
"default": "Auto"
}
]
2 changes: 1 addition & 1 deletion Tested.bundle/Contents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<key>CFBundleIdentifier</key>
<string>com.plexapp.plugins.tested</string>
<key>PlexClientPlatforms</key>
<string>MacOSX,iOS,Android,LGTV</string>
<string>MacOSX,iOS,Android,LGTV,Windows</string>
<key>PlexFrameworkVersion</key>
<string>2</string>
<key>PlexPluginClass</key>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Tested.bundle/Contents/Resources/art-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/coffee.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/diy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/howto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Tested.bundle/Contents/Resources/icon-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/latest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/live.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/makerbot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/review.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/science.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tested.bundle/Contents/Resources/search.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.