54 Commits

Author SHA1 Message Date
Matt
975c953d78 Version bump to 0.3.1 2020-12-23 21:33:39 -05:00
mcarlton00
9de1af4204 Merge pull request #40 from mcarlton00/external-sub-names
Show proper language names for external subs
2020-12-23 21:32:49 -05:00
Matt
7b7502fa2f Show proper language names for external subs 2020-12-19 17:07:16 -05:00
mcarlton00
b565005219 Merge pull request #39 from mcarlton00/clone-skin-fix
Fix clone skin function
2020-12-19 11:38:16 -05:00
Matt
68008c675e Fix errors when cloning default skin 2020-12-18 19:53:14 -05:00
mcarlton00
2cf86eb6ae Merge pull request #37 from mcarlton00/urlencoding-auth-is-dumb
Don't urlencode auth json payload
2020-12-18 17:18:00 -05:00
Matt
b0a1f9a680 Don't urlencode auth json payload 2020-12-17 23:29:26 -05:00
Matt
3f7816762e Version bump to 0.3.0 2020-12-11 20:33:51 -05:00
mcarlton00
8bade51eb5 Merge pull request #33 from mcarlton00/10.7-fixes
10.7 fixes
2020-12-11 20:30:49 -05:00
Matt
7c4398bfb5 When playback stops, only try to delete a transcode if we're transcoding 2020-12-05 18:30:40 -05:00
Matt
8f736e8bd3 We need the brackets for later 2020-12-05 18:28:20 -05:00
Matt
65a9b11dc5 Include URL when there's been an http error 2020-12-05 18:27:36 -05:00
Matt
7ffd16df4b Remove manually specifying return payload 2020-12-05 18:26:53 -05:00
mcarlton00
e2628d27dc Don't error on empty user list 2020-11-28 15:00:29 -05:00
mcarlton00
8799c2bb5e Use correct lookup URL 2020-11-28 14:59:55 -05:00
Matt
4ba0b64d2c Update auth for 10.7 2020-11-23 17:58:45 -05:00
mcarlton00
4b2f43e8a2 Merge pull request #32 from mcarlton00/json-payloads
Proper API json parsing
2020-11-15 14:44:11 -05:00
Matt
df774ca3c5 Simplify logic checks 2020-11-15 14:13:45 -05:00
Matt
084fab576e Remove debug statement 2020-11-15 11:04:13 -05:00
mcarlton00
1733e64403 Parse json payloads in centralized place 2020-11-11 22:50:26 -05:00
mcarlton00
1d0360c0c3 Merge pull request #29 from mcarlton00/words-r-hard
Connect to servers with special characters in the name
2020-10-06 22:22:34 -04:00
Matt
45823ccd96 Connect to servers with special characters in the name 2020-10-06 21:43:36 -04:00
mcarlton00
ef3b64cf51 Merge pull request #25 from mcarlton00/castaway
Fix casting from web UI
2020-09-10 17:41:24 -04:00
mcarlton00
a424fb8793 Merge pull request #24 from mcarlton00/when-is-a-string-not-a-string
Use future strings to fix unicode errors
2020-09-10 17:40:51 -04:00
mcarlton00
b6ae819d32 Merge pull request #23 from mcarlton00/i-know-my-abcs
Fix browsing libraries by letter
2020-09-10 17:39:41 -04:00
Matt
8711ae2452 Fix casting from web UI 2020-09-05 17:49:18 -04:00
Matt
a90c2c2fa8 Use future strings to fix unicode errors 2020-09-05 17:29:38 -04:00
Matt
03a89d4f43 Remove unnecessary log line 2020-09-05 16:15:54 -04:00
Matt
d48b2bdf2a Fix browsing libraries by letter 2020-09-05 16:11:15 -04:00
mcarlton00
6a6ca8c642 Merge pull request #22 from mcarlton00/noisy-logs-are-noisy
Fix log levels
2020-09-03 10:35:57 -04:00
Matt
d3ffecb866 Fix log levels 2020-09-02 23:04:13 -04:00
mcarlton00
083f91611a Merge pull request #16 from Shadowghost/websocket-url-fix
Fix websocket_url (fixes playing transcoded streams)
2020-08-18 08:29:05 -04:00
Shadowghost
01e9c45df6 Fix websocket_url (fixes playing transcoded streams) 2020-08-18 11:22:27 +02:00
Matt
b327ebc5bd Update authors field 2020-08-16 22:00:55 -04:00
Matt
ec1a5add73 version bump 2020-08-16 21:19:12 -04:00
mcarlton00
b7110a7222 Merge pull request #15 from ltGuillaume/patch-1
Fix annoying typo "defalt"
2020-08-16 19:13:45 -04:00
Guillaume
2e19d2eac1 Fix annoying typo "defalt" 2020-08-16 23:51:56 +02:00
mcarlton00
ad7f388d68 Merge pull request #11 from mcarlton00/improve-logging
Improve logging
2020-07-25 16:01:58 -04:00
Matt
f28c1e7fae Cleanup less helpful logging 2020-07-25 13:36:21 -04:00
Matt
6ce342c0b3 Remove debug statement 2020-07-25 13:36:03 -04:00
Matt
622bdf613c Sanitize server url from logs 2020-07-25 13:35:29 -04:00
Matt
a0efd1087f Copy log handler from JF for Kodi, modify for strings 2020-07-25 01:01:30 -04:00
mcarlton00
9813295fd3 Merge pull request #8 from TrueTechy/server_address_storing_fix
Store full URL instead of component parts. Fixes #1 and Fixes #2
2020-07-19 18:27:32 -04:00
Abby Gourlay
303fdfc9ad Merge branch 'master' of github.com:mcarlton00/jellycon into server_address_storing_fix 2020-07-19 23:19:28 +01:00
mcarlton00
90d88a998c Merge pull request #9 from TrueTechy/issue-template
Create issue templates
2020-07-19 17:59:13 -04:00
Abby
1999e1daf2 Update issue templates 2020-07-19 17:17:45 +01:00
mcarlton00
433f39dd38 Merge pull request #7 from TrueTechy/httplib_replacement
Replace httplib with requests
2020-07-18 23:24:46 -04:00
Abby
110746e859 Update resources/lib/image_server.py spelling error
Co-authored-by: mcarlton00 <mcarlton00@gmail.com>
2020-07-19 04:21:37 +01:00
Abby Gourlay
640860a3af Store full URL instead of component parts. Fixes #1 and Fixes #2 2020-07-19 03:54:15 +01:00
Abby Gourlay
b21fa807db Fixed auth bug 2020-07-19 02:05:34 +01:00
Abby Gourlay
446dd921bf Replace httplib with requests 2020-07-19 01:31:42 +01:00
mcarlton00
594fccd602 Merge pull request #6 from TrueTechy/fix_connection_test
Lowered the maximum connection test size to comply with API limits
2020-07-18 11:31:33 -04:00
Abby Gourlay
47993b612f Removed debugging code 2020-07-18 15:55:24 +01:00
Abby Gourlay
2302f2dbef Lowered the maximum connection test size to comply with API limits 2020-07-18 15:53:21 +01:00
38 changed files with 979 additions and 1114 deletions

36
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,36 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Logs**
<!-- Please paste any log errors. -->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
**System (please complete the following information):**
- OS: [e.g. Android, Debian, Windows]
- Jellyfin Version: [e.g. 10.0.1]
- Kodi Version: [e.g. 18.3]
- Addon Version: [e.g. 0.1.1]
**Additional context**
<!-- Add any other context about the problem here. -->

View File

@@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.jellycon"
name="JellyCon"
version="0.1.1"
provider-name="Team B">
version="0.3.1"
provider-name="Jellyfin Contributors">
<requires>
<import addon="xbmc.python" version="2.25.0"/>
<import addon="script.module.pil" version="1.1.7"/>
<import addon="script.module.requests" version="2.22.0"/>
<import addon="script.module.six" version="1.13.0"/>
<import addon="script.module.kodi-six" />
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video audio</provides>
@@ -20,7 +23,7 @@
<website>https://github.com/jellyfin/jellycon/wiki</website>
<source>https://github.com/jellyfin/jellycon</source>
<summary lang="en_GB">Browse and play your Jellyfin server media library.</summary>
<description lang="en_GB">An addon to allow you to browse and playback your Jellyfin (www.jellyfin.org) Movie, TV Show and Music collections.</description>
<description lang="en_GB">An addon to allow you to browse and playback your Jellyfin (https://jellyfin.org) Movie, TV Show and Music collections.</description>
<assets>
<icon>icon.png</icon>
<fanart>fanart.jpg</fanart>

View File

@@ -2,11 +2,11 @@
import xbmcaddon
from resources.lib.simple_logging import SimpleLogging
from resources.lib.loghandler import LazyLogger
from resources.lib.functions import main_entry_point
from resources.lib.tracking import set_timing_enabled
log = SimpleLogging('default')
log = LazyLogger('default')
settings = xbmcaddon.Addon()
log_timing_data = settings.getSetting('log_timing') == "true"

View File

@@ -1,4 +1,5 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import time
import threading
@@ -6,9 +7,9 @@ import threading
import xbmc
import xbmcgui
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class ActionAutoClose(threading.Thread):
@@ -27,7 +28,7 @@ class ActionAutoClose(threading.Thread):
log.debug("ActionAutoClose Running")
while not xbmc.abortRequested and not self.stop_thread:
time_since_last = time.time() - self.last_interaction
log.debug("ActionAutoClose time_since_last : {0}", time_since_last)
log.debug("ActionAutoClose time_since_last : {0}".format(time_since_last))
if time_since_last > 20:
log.debug("ActionAutoClose Closing Parent")
@@ -40,7 +41,7 @@ class ActionAutoClose(threading.Thread):
def set_last(self):
self.last_interaction = time.time()
log.debug("ActionAutoClose set_last : {0}", self.last_interaction)
log.debug("ActionAutoClose set_last : {0}".format(self.last_interaction))
def stop(self):
log.debug("ActionAutoClose stop_thread called")
@@ -79,7 +80,7 @@ class ActionMenu(xbmcgui.WindowXMLDialog):
pass
def onMessage(self, message):
log.debug("ActionMenu: onMessage: {0}", message)
log.debug("ActionMenu: onMessage: {0}".format(message))
def onAction(self, action):
@@ -91,12 +92,12 @@ class ActionMenu(xbmcgui.WindowXMLDialog):
self.close()
else:
self.auto_close_thread.set_last()
log.debug("ActionMenu: onAction: {0}", action.getId())
log.debug("ActionMenu: onAction: {0}".format(action.getId()))
def onClick(self, control_id):
if control_id == 3000:
self.selected_action = self.listControl.getSelectedItem()
log.debug("ActionMenu: Selected Item: {0}", self.selected_action)
log.debug("ActionMenu: Selected Item: {0}".format(self.selected_action))
self.auto_close_thread.stop()
self.close()

View File

@@ -1,9 +1,11 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmc
import xbmcgui
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class BitrateDialog(xbmcgui.WindowXMLDialog):
@@ -35,7 +37,7 @@ class BitrateDialog(xbmcgui.WindowXMLDialog):
pass
def onMessage(self, message):
log.debug("ActionMenu: onMessage: {0}", message)
log.debug("ActionMenu: onMessage: {0}".format(message))
def onAction(self, action):
@@ -54,5 +56,5 @@ class BitrateDialog(xbmcgui.WindowXMLDialog):
def onClick(self, control_id):
if control_id == 3000:
log.debug("ActionMenu: Selected Item: {0}", control_id)
#self.close()
log.debug("ActionMenu: Selected Item: {0}".format(control_id))
#self.close()

View File

@@ -1,8 +1,9 @@
# coding=utf-8
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import urllib
import httplib
import requests
import base64
import sys
import threading
@@ -14,15 +15,15 @@ import xbmc
import xbmcaddon
from .downloadutils import DownloadUtils
from .simple_logging import SimpleLogging
from .jsonrpc import JsonRpc
from .loghandler import LazyLogger
from .jsonrpc import JsonRpc, get_value
from .translation import string_load
from .datamanager import DataManager
from .utils import get_art, double_urlencode
from .kodi_utils import HomeWindow
downloadUtils = DownloadUtils()
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class CacheArtwork(threading.Thread):
@@ -62,7 +63,7 @@ class CacheArtwork(threading.Thread):
monitor.waitForAbort(5)
log.debug("CacheArtwork background thread exited : stop_all_activity : {0}", self.stop_all_activity)
log.debug("CacheArtwork background thread exited : stop_all_activity : {0}".format(self.stop_all_activity))
@staticmethod
def delete_cached_images(item_id):
@@ -74,7 +75,7 @@ class CacheArtwork(threading.Thread):
item_image_url_part = "Items/%s/Images/" % item_id
item_image_url_part = item_image_url_part.replace("/", "%2f")
log.debug("texture ids: {0}", item_image_url_part)
log.debug("texture ids: {0}".format(item_image_url_part))
# is the web server enabled
web_query = {"setting": "services.webserver"}
@@ -87,7 +88,7 @@ class CacheArtwork(threading.Thread):
params = {"properties": ["url"]}
json_result = JsonRpc('Textures.GetTextures').execute(params)
textures = json_result.get("result", {}).get("textures", [])
log.debug("texture ids: {0}", textures)
log.debug("texture ids: {0}".format(textures))
progress.update(70, string_load(30346))
@@ -97,7 +98,7 @@ class CacheArtwork(threading.Thread):
texture_url = texture["url"]
if item_image_url_part in texture_url:
delete_count += 1
log.debug("removing texture id: {0}", texture_id)
log.debug("removing texture id: {0}".format(texture_id))
params = {"textureid": int(texture_id)}
JsonRpc('Textures.RemoveTexture').execute(params)
@@ -141,8 +142,8 @@ class CacheArtwork(threading.Thread):
jellyfin_texture_urls = self.get_jellyfin_artwork(delete_pdialog)
log.debug("kodi textures: {0}", textures)
log.debug("jellyfin texture urls: {0}", jellyfin_texture_urls)
log.debug("kodi textures: {0}".format(textures))
log.debug("jellyfin texture urls: {0}".format(jellyfin_texture_urls))
if jellyfin_texture_urls is not None:
@@ -157,7 +158,7 @@ class CacheArtwork(threading.Thread):
unused_texture_ids.add(texture["textureid"])
total = len(unused_texture_ids)
log.debug("unused texture ids: {0}", unused_texture_ids)
log.debug("unused texture ids: {0}".format(unused_texture_ids))
for texture_id in unused_texture_ids:
params = {"textureid": int(texture_id)}
@@ -206,11 +207,11 @@ class CacheArtwork(threading.Thread):
try:
result_text = self.cache_artwork(dp)
except Exception as err:
log.error("Cache Images Failed : {0}", err)
log.error("Cache Images Failed : {0}".format(err))
dp.close()
del dp
if result_text is not None:
log.debug("Cache Images reuslt : {0}", " - ".join(result_text))
log.debug("Cache Images reuslt : {0}".format(" - ".join(result_text)))
def get_jellyfin_artwork(self, progress):
log.debug("get_jellyfin_artwork")
@@ -233,7 +234,7 @@ class CacheArtwork(threading.Thread):
results = results.get("Items")
server = downloadUtils.get_server()
log.debug("Jellyfin Item Count Count: {0}", len(results))
log.debug("Jellyfin Item Count Count: {0}".format(len(results)))
if self.stop_all_activity:
return None
@@ -254,36 +255,27 @@ class CacheArtwork(threading.Thread):
log.debug("cache_artwork")
# is the web server enabled
web_query = {"setting": "services.webserver"}
result = JsonRpc('Settings.GetSettingValue').execute(web_query)
xbmc_webserver_enabled = result['result']['value']
if not xbmc_webserver_enabled:
if not get_value("services.webserver"):
log.error("Kodi web server not enabled, can not cache images")
return
# get the port
web_port = {"setting": "services.webserverport"}
result = JsonRpc('Settings.GetSettingValue').execute(web_port)
xbmc_port = result['result']['value']
log.debug("xbmc_port: {0}", xbmc_port)
xbmc_port = get_value("services.webserverport")
log.debug("xbmc_port: {0}".format(xbmc_port))
# get the user
web_user = {"setting": "services.webserverusername"}
result = JsonRpc('Settings.GetSettingValue').execute(web_user)
xbmc_username = result['result']['value']
log.debug("xbmc_username: {0}", xbmc_username)
xbmc_username = get_value("services.webserverusername")
log.debug("xbmc_username: {0}".format(xbmc_username))
# get the password
web_pass = {"setting": "services.webserverpassword"}
result = JsonRpc('Settings.GetSettingValue').execute(web_pass)
xbmc_password = result['result']['value']
xbmc_password = get_value("services.webserverpassword")
progress.update(0, string_load(30356))
params = {"properties": ["url"]}
json_result = JsonRpc('Textures.GetTextures').execute(params)
textures = json_result.get("result", {}).get("textures", [])
log.debug("Textures.GetTextures Count: {0}", len(textures))
log.debug("Textures.GetTextures Count: {0}".format(len(textures)))
if self.stop_all_activity:
return
@@ -301,7 +293,7 @@ class CacheArtwork(threading.Thread):
del textures
del json_result
log.debug("texture_urls Count: {0}", len(texture_urls))
log.debug("texture_urls Count: {0}".format(len(texture_urls)))
if self.stop_all_activity:
return
@@ -313,6 +305,7 @@ class CacheArtwork(threading.Thread):
return
missing_texture_urls = set()
# image_types = ["thumb", "poster", "banner", "clearlogo", "tvshow.poster", "tvshow.banner", "tvshow.landscape"]
for image_url in jellyfin_texture_urls:
if image_url not in texture_urls and not image_url.endswith("&Tag=") and len(image_url) > 0:
@@ -321,10 +314,10 @@ class CacheArtwork(threading.Thread):
if self.stop_all_activity:
return
log.debug("texture_urls: {0}", texture_urls)
log.debug("missing_texture_urls: {0}", missing_texture_urls)
log.debug("Number of existing textures: {0}", len(texture_urls))
log.debug("Number of missing textures: {0}", len(missing_texture_urls))
log.debug("texture_urls: {0}".format(texture_urls))
log.debug("missing_texture_urls: {0}".format(missing_texture_urls))
log.debug("Number of existing textures: {0}".format(len(texture_urls)))
log.debug("Number of missing textures: {0}".format(len(missing_texture_urls)))
kodi_http_server = "localhost:" + str(xbmc_port)
headers = {}
@@ -333,27 +326,25 @@ class CacheArtwork(threading.Thread):
headers = {'Authorization': 'Basic %s' % base64.b64encode(auth)}
total = len(missing_texture_urls)
index = 1
count_done = 0
for get_url in missing_texture_urls:
for index, get_url in enumerate(missing_texture_urls, 1):
# log.debug("texture_url: {0}", get_url)
url = double_urlencode(get_url)
kodi_texture_url = ("/image/image://%s" % url)
log.debug("kodi_texture_url: {0}", kodi_texture_url)
log.debug("kodi_texture_url: {0}".format(kodi_texture_url))
percentage = int((float(index) / float(total)) * 100)
message = "%s of %s" % (index, total)
progress.update(percentage, message)
conn = httplib.HTTPConnection(kodi_http_server, timeout=20)
conn.request(method="GET", url=kodi_texture_url, headers=headers)
data = conn.getresponse()
if data.status == 200:
count_done += 1
log.debug("Get Image Result: {0}", data.status)
cache_url = "http://%s%s" % (kodi_http_server, kodi_texture_url)
data = requests.get(cache_url, timeout=20, headers=headers)
if data.status_code == 200:
count_done += 1
log.debug("Get Image Result: {0}".format(data.status_code))
index += 1
# if progress.iscanceled():
# if "iscanceled" in dir(progress) and progress.iscanceled():
if isinstance(progress, xbmcgui.DialogProgress) and progress.iscanceled():

View File

@@ -1,4 +1,5 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
from uuid import uuid4 as uuid4
import xbmcaddon
@@ -6,9 +7,9 @@ import xbmc
import xbmcvfs
from .kodi_utils import HomeWindow
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class ClientInformation:
@@ -23,20 +24,20 @@ class ClientInformation:
return client_id
jellyfin_guid_path = xbmc.translatePath("special://temp/jellycon_guid").decode('utf-8')
log.debug("jellyfin_guid_path: {0}", jellyfin_guid_path)
log.debug("jellyfin_guid_path: {0}".format(jellyfin_guid_path))
guid = xbmcvfs.File(jellyfin_guid_path)
client_id = guid.read()
guid.close()
if not client_id:
client_id = str("%012X" % uuid4())
log.debug("Generating a new guid: {0}", client_id)
log.debug("Generating a new guid: {0}".format(client_id))
guid = xbmcvfs.File(jellyfin_guid_path, 'w')
guid.write(client_id)
guid.close()
log.debug("jellyfin_client_id (NEW): {0}", client_id)
log.debug("jellyfin_client_id (NEW): {0}".format(client_id))
else:
log.debug("jellyfin_client_id: {0}", client_id)
log.debug("jellyfin_client_id: {0}".format(client_id))
window.set_property("client_id", client_id)
return client_id

View File

@@ -1,10 +1,10 @@
import threading
import xbmc
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from resources.lib.functions import show_menu
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class ContextMonitor(threading.Thread):
@@ -33,41 +33,6 @@ class ContextMonitor(threading.Thread):
xbmc.sleep(100)
'''
context_up = False
is_jellycon_item = False
while not xbmc.abortRequested and not self.stop_thread:
if xbmc.getCondVisibility("Window.IsActive(fullscreenvideo) | Window.IsActive(visualisation)"):
xbmc.sleep(1000)
else:
if xbmc.getCondVisibility("Window.IsVisible(contextmenu)"):
context_up = True
if is_jellycon_item:
xbmc.executebuiltin("Dialog.Close(contextmenu,true)")
else:
if context_up: # context now down, do something
context_up = False
container_id = xbmc.getInfoLabel("System.CurrentControlID")
log.debug("ContextMonitor Container ID: {0}", container_id)
item_id = xbmc.getInfoLabel("Container(" + str(container_id) + ").ListItem.Property(id)")
log.debug("ContextMonitor Item ID: {0}", item_id)
if item_id:
params = {}
params["item_id"] = item_id
show_menu(params)
container_id = xbmc.getInfoLabel("System.CurrentControlID")
condition = ("String.StartsWith(Container(" + str(container_id) +
").ListItem.Path,plugin://plugin.video.jellycon) + !String.IsEmpty(Container(" +
str(container_id) + ").ListItem.Property(id))")
is_jellycon_item = xbmc.getCondVisibility(condition)
xbmc.sleep(200)
'''
log.debug("ContextMonitor Thread Exited")
def stop_monitor(self):

View File

@@ -1,6 +1,6 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import json
from collections import defaultdict
import threading
import hashlib
@@ -9,7 +9,7 @@ import cPickle
import time
from .downloadutils import DownloadUtils
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .item_functions import extract_item_info
from .kodi_utils import HomeWindow
from .translation import string_load
@@ -21,7 +21,7 @@ import xbmcaddon
import xbmcvfs
import xbmcgui
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class CacheItem:
@@ -46,21 +46,15 @@ class DataManager:
# log.debug("DataManager __init__")
pass
@staticmethod
def load_json_data(json_data):
return json.loads(json_data, object_hook=lambda d: defaultdict(lambda: None, d))
@timer
def get_content(self, url):
json_data = DownloadUtils().download_url(url)
result = self.load_json_data(json_data)
return result
return DownloadUtils().download_url(url)
@timer
def get_items(self, url, gui_options, use_cache=False):
home_window = HomeWindow()
log.debug("last_content_url : use_cache={0} url={1}", use_cache, url)
log.debug("last_content_url : use_cache={0} url={1}".format(use_cache, url))
home_window.set_property("last_content_url", url)
download_utils = DownloadUtils()
@@ -102,7 +96,7 @@ class DataManager:
item_list = cache_item.item_list
total_records = cache_item.total_records
except Exception as err:
log.error("Pickle Data Load Failed : {0}", err)
log.error("Pickle Data Load Failed : {0}".format(err))
item_list = None
# we need to load the list item data form the server
@@ -206,7 +200,7 @@ class CacheManagerThread(threading.Thread):
else:
log.debug("CacheManagerThread : Reloading to recheck data hashes")
cached_hash = self.cached_item.item_list_hash
log.debug("CacheManagerThread : Cache Hash : {0}", cached_hash)
log.debug("CacheManagerThread : Cache Hash : {0}".format(cached_hash))
data_manager = DataManager()
results = data_manager.get_content(self.cached_item.items_url)
@@ -232,7 +226,7 @@ class CacheManagerThread(threading.Thread):
return
loaded_hash = self.get_data_hash(loaded_items)
log.debug("CacheManagerThread : Loaded Hash : {0}", loaded_hash)
log.debug("CacheManagerThread : Loaded Hash : {0}".format(loaded_hash))
# if they dont match then save the data and trigger a content reload
if cached_hash != loaded_hash:
@@ -252,7 +246,7 @@ class CacheManagerThread(threading.Thread):
# TODO: probably should only set this in simple check mode
current_time_stamp = str(time.time())
home_window.set_property("jellycon_widget_reload", current_time_stamp)
log.debug("Setting New Widget Hash: {0}", current_time_stamp)
log.debug("Setting New Widget Hash: {0}".format(current_time_stamp))
log.debug("CacheManagerThread : Sending container refresh")
xbmc.executebuiltin("Container.Refresh")
@@ -276,7 +270,7 @@ def clear_cached_server_data():
del_count = 0
for filename in files:
if filename.startswith("cache_") and filename.endswith(".pickle"):
log.debug("Deleteing CacheFile: {0}", filename)
log.debug("Deleteing CacheFile: {0}".format(filename))
xbmcvfs.delete(os.path.join(addon_dir, filename))
del_count += 1
@@ -293,7 +287,7 @@ def clear_old_cache_data():
del_count = 0
for filename in files:
if filename.startswith("cache_") and filename.endswith(".pickle"):
log.debug("clear_old_cache_data() : Checking CacheFile : {0}", filename)
log.debug("clear_old_cache_data() : Checking CacheFile : {0}".format(filename))
cache_item = None
for x in range(0, 5):
@@ -304,7 +298,7 @@ def clear_old_cache_data():
cache_item = cPickle.load(handle)
break
except Exception as error:
log.debug("clear_old_cache_data() : Pickle load error : {0}", error)
log.debug("clear_old_cache_data() : Pickle load error : {0}".format(error))
cache_item = None
xbmc.sleep(1000)
@@ -313,9 +307,9 @@ def clear_old_cache_data():
if cache_item.date_last_used is not None:
item_last_used = time.time() - cache_item.date_last_used
log.debug("clear_old_cache_data() : Cache item last used : {0} sec ago", item_last_used)
log.debug("clear_old_cache_data() : Cache item last used : {0} sec ago".format(item_last_used))
if item_last_used == -1 or item_last_used > (3600 * 24 * 7):
log.debug("clear_old_cache_data() : Deleting cache item age : {0}", item_last_used)
log.debug("clear_old_cache_data() : Deleting cache item age : {0}".format(item_last_used))
data_file = os.path.join(addon_dir, filename)
with FileLock(data_file + ".locked", timeout=5):
xbmcvfs.delete(data_file)
@@ -326,4 +320,4 @@ def clear_old_cache_data():
with FileLock(data_file + ".locked", timeout=5):
xbmcvfs.delete(data_file)
log.debug("clear_old_cache_data() : Cache items deleted : {0}", del_count)
log.debug("clear_old_cache_data() : Cache items deleted : {0}".format(del_count))

View File

@@ -1,4 +1,5 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmcaddon
import xbmcplugin
@@ -12,12 +13,12 @@ from .datamanager import DataManager
from .kodi_utils import HomeWindow
from .downloadutils import DownloadUtils
from .translation import string_load
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .item_functions import add_gui_item, ItemDetails
from .utils import send_event_notification
from .tracking import timer
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
@timer
@@ -29,8 +30,8 @@ def get_content(url, params):
if not media_type:
xbmcgui.Dialog().ok(string_load(30135), string_load(30139))
log.debug("URL: {0}", url)
log.debug("MediaType: {0}", media_type)
log.debug("URL: {0}".format(url))
log.debug("MediaType: {0}".format(media_type))
pluginhandle = int(sys.argv[1])
settings = xbmcaddon.Addon()
@@ -71,7 +72,7 @@ def get_content(url, params):
elif media_type == "playlists":
view_type = "Playlists"
log.debug("media_type:{0} content_type:{1} view_type:{2} ", media_type, content_type, view_type)
log.debug("media_type:{0} content_type:{1} view_type:{2} ".format(media_type, content_type, view_type))
# show a progress indicator if needed
progress = None
@@ -88,22 +89,22 @@ def get_content(url, params):
if page_limit > 0 and media_type.startswith("movie"):
m = re.search('StartIndex=([0-9]{1,4})', url)
if m and m.group(1):
log.debug("UPDATING NEXT URL: {0}", url)
log.debug("UPDATING NEXT URL: {0}".format(url))
start_index = int(m.group(1))
log.debug("current_start : {0}", start_index)
log.debug("current_start : {0}".format(start_index))
if start_index > 0:
prev_index = start_index - page_limit
if prev_index < 0:
prev_index = 0
url_prev = re.sub('StartIndex=([0-9]{1,4})', 'StartIndex=' + str(prev_index), url)
url_next = re.sub('StartIndex=([0-9]{1,4})', 'StartIndex=' + str(start_index + page_limit), url)
log.debug("UPDATING NEXT URL: {0}", url_next)
log.debug("UPDATING NEXT URL: {0}".format(url_next))
else:
log.debug("ADDING NEXT URL: {0}", url)
log.debug("ADDING NEXT URL: {0}".format(url))
url_next = url + "&StartIndex=" + str(start_index + page_limit) + "&Limit=" + str(page_limit)
url = url + "&StartIndex=" + str(start_index) + "&Limit=" + str(page_limit)
log.debug("ADDING NEXT URL: {0}", url_next)
log.debug("ADDING NEXT URL: {0}".format(url_next))
# use the data manager to get the data
# result = dataManager.GetContent(url)
@@ -118,7 +119,7 @@ def get_content(url, params):
if dir_items is None:
return
log.debug("total_records: {0}", total_records)
log.debug("total_records: {0}".format(total_records))
# add paging items
if page_limit > 0 and media_type.startswith("movie"):
@@ -126,7 +127,7 @@ def get_content(url, params):
list_item = xbmcgui.ListItem("Prev Page (" + str(start_index - page_limit + 1) + "-" + str(start_index) +
" of " + str(total_records) + ")")
u = sys.argv[0] + "?url=" + urllib.quote(url_prev) + "&mode=GET_CONTENT&media_type=movies"
log.debug("ADDING PREV ListItem: {0} - {1}", u, list_item)
log.debug("ADDING PREV ListItem: {0} - {1}".format(u, list_item))
dir_items.insert(0, (u, list_item, True))
if start_index + page_limit < total_records:
@@ -136,7 +137,7 @@ def get_content(url, params):
list_item = xbmcgui.ListItem("Next Page (" + str(start_index + page_limit + 1) + "-" +
str(upper_count) + " of " + str(total_records) + ")")
u = sys.argv[0] + "?url=" + urllib.quote(url_next) + "&mode=GET_CONTENT&media_type=movies"
log.debug("ADDING NEXT ListItem: {0} - {1}", u, list_item)
log.debug("ADDING NEXT ListItem: {0} - {1}".format(u, list_item))
dir_items.append((u, list_item, True))
# set the Kodi content type
@@ -144,7 +145,7 @@ def get_content(url, params):
xbmcplugin.setContent(pluginhandle, content_type)
elif detected_type is not None:
# if the media type is not set then try to use the detected type
log.debug("Detected content type: {0}", detected_type)
log.debug("Detected content type: {0}".format(detected_type))
if detected_type == "Movie":
view_type = "Movies"
content_type = 'movies'
@@ -166,11 +167,11 @@ def get_content(url, params):
view_key = "view-" + content_type
view_id = settings.getSetting(view_key)
if view_id:
log.debug("Setting view for type:{0} to id:{1}", view_key, view_id)
log.debug("Setting view for type:{0} to id:{1}".format(view_key, view_id))
display_items_notification = {"view_id": view_id}
send_event_notification("set_view", display_items_notification)
else:
log.debug("No view id for view type:{0}", view_key)
log.debug("No view id for view type:{0}".format(view_key))
# send display items event
# display_items_notification = {"view_type": view_type}
@@ -185,7 +186,7 @@ def get_content(url, params):
def set_sort(pluginhandle, view_type, default_sort):
log.debug("SETTING_SORT for media type: {0}", view_type)
log.debug("SETTING_SORT for media type: {0}".format(view_type))
if default_sort == "none":
xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_UNSORTED)
@@ -202,7 +203,7 @@ def set_sort(pluginhandle, view_type, default_sort):
settings = xbmcaddon.Addon()
preset_sort_order = settings.getSetting("sort-" + view_type)
log.debug("SETTING_SORT preset_sort_order: {0}", preset_sort_order)
log.debug("SETTING_SORT preset_sort_order: {0}".format(preset_sort_order))
if preset_sort_order in sorting_order_mapping:
xbmcplugin.addSortMethod(pluginhandle, sorting_order_mapping[preset_sort_order])
@@ -311,7 +312,7 @@ def process_directory(url, progress, params, use_cache_data=False):
detected_type = item_details.item_type
if item_details.item_type == "Season" and first_season_item is None:
log.debug("Setting First Season to : {0}", item_details.__dict__)
log.debug("Setting First Season to : {0}".format(item_details.__dict__))
first_season_item = item_details
total_unwatched += item_details.unwatched_episodes
@@ -357,7 +358,7 @@ def process_directory(url, progress, params, use_cache_data=False):
if gui_item:
dir_items.append(gui_item)
else:
log.debug("Dropping empty folder item : {0}", item_details.__dict__)
log.debug("Dropping empty folder item : {0}".format(item_details.__dict__))
elif item_details.item_type == "MusicArtist":
u = ('{server}/Users/{userid}/items' +

View File

@@ -1,26 +1,28 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmcgui
import xbmcaddon
import httplib
import requests
import hashlib
import ssl
import StringIO
import gzip
import json
from urlparse import urlparse
import urllib
from base64 import b64encode
from collections import defaultdict
from traceback import format_exc
from kodi_six.utils import py2_decode
from .kodi_utils import HomeWindow
from .clientinfo import ClientInformation
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .translation import string_load
from .tracking import timer
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
def save_user_details(settings, user_name, user_password):
@@ -104,16 +106,15 @@ class DownloadUtils:
self.use_https = False
if settings.getSetting('protocol') == "1":
self.use_https = True
log.debug("use_https: {0}", self.use_https)
log.debug("use_https: {0}".format(self.use_https))
self.verify_cert = settings.getSetting('verify_cert') == 'true'
log.debug("verify_cert: {0}", self.verify_cert)
log.debug("verify_cert: {0}".format(self.verify_cert))
def post_capabilities(self):
url = "{server}/Sessions/Capabilities/Full?format=json"
data = {
'IconUrl': "https://raw.githubusercontent.com/faush01/plugin.video.jellycon/develop/kodi.png",
'SupportsMediaControl': True,
'PlayableMediaTypes': ["Video", "Audio"],
'SupportedCommands': ["MoveUp",
@@ -151,7 +152,7 @@ class DownloadUtils:
}
self.download_url(url, post_body=data, method="POST")
log.debug("Posted Capabilities: {0}", data)
log.debug("Posted Capabilities: {0}".format(data))
def get_item_playback_info(self, item_id, force_transcode):
@@ -332,60 +333,24 @@ class DownloadUtils:
else:
url = "{server}/Items/%s/PlaybackInfo?MaxStreamingBitrate=%s" % (item_id, bitrate)
log.debug("PlaybackInfo : {0}", url)
log.debug("PlaybackInfo : {0}", profile)
log.debug("PlaybackInfo : {0}".format(url))
log.debug("PlaybackInfo : {0}".format(profile))
play_info_result = self.download_url(url, post_body=playback_info, method="POST")
play_info_result = json.loads(play_info_result)
log.debug("PlaybackInfo : {0}", play_info_result)
log.debug("PlaybackInfo : {0}".format(play_info_result))
return play_info_result
def get_server(self):
settings = xbmcaddon.Addon()
host = settings.getSetting('ipaddress')
if len(host) == 0 or host == "<none>":
return None
#For migration from storing URL parts to just one URL
if settings.getSetting('ipaddress') != "" and settings.getSetting('ipaddress') != "&lt;none&gt;":
log.info("Migrating to new server url storage")
url = ("http://" if settings.getSetting('protocol') == "0" else "https://") + settings.getSetting('ipaddress') + ":" + settings.getSetting('port')
settings.setSetting('server_address', url)
settings.setSetting('ipaddress', "")
port = settings.getSetting('port')
if not port and self.use_https:
port = "443"
settings.setSetting("port", port)
elif not port:
port = "80"
settings.setSetting("port", port)
# if user entered a full path i.e. http://some_host:port
if host.lower().strip().startswith("http://") or host.lower().strip().startswith("https://"):
log.debug("Extracting host info from url: {0}", host)
url_bits = urlparse(host.strip())
if host.lower().strip().startswith("http://"):
settings.setSetting('protocol', '0')
self.use_https = False
elif host.lower().strip().startswith("https://"):
settings.setSetting('protocol', '1')
self.use_https = True
if url_bits.hostname is not None and len(url_bits.hostname) > 0:
host = url_bits.hostname
if url_bits.username and url_bits.password:
host = "%s:%s@" % (url_bits.username, url_bits.password) + host
settings.setSetting("ipaddress", host)
if url_bits.port is not None and url_bits.port > 0:
port = str(url_bits.port)
settings.setSetting("port", port)
if self.use_https:
server = "https://" + host + ":" + port
else:
server = "http://" + host + ":" + port
return server
return settings.getSetting('server_address')
@staticmethod
def get_all_artwork(item, server):
@@ -426,25 +391,25 @@ class DownloadUtils:
# for episodes always use the parent BG
if item_type == "Episode" and art_type == "Backdrop":
item_id = data["ParentBackdropItemId"]
bg_item_tags = data["ParentBackdropImageTags"]
if bg_item_tags is not None and len(bg_item_tags) > 0:
item_id = data.get("ParentBackdropItemId")
bg_item_tags = data.get("ParentBackdropImageTags", [])
if bg_item_tags:
image_tag = bg_item_tags[0]
elif art_type == "Backdrop" and parent is True:
item_id = data["ParentBackdropItemId"]
bg_item_tags = data["ParentBackdropImageTags"]
if bg_item_tags is not None and len(bg_item_tags) > 0:
item_id = data.get("ParentBackdropItemId")
bg_item_tags = data.get("ParentBackdropImageTags", [])
if bg_item_tags:
image_tag = bg_item_tags[0]
elif art_type == "Backdrop":
bg_tags = data["BackdropImageTags"]
if bg_tags is not None and len(bg_tags) > index:
bg_tags = data.get("BackdropImageTags", [])
if bg_tags:
image_tag = bg_tags[index]
# log.debug("Background Image Tag: {0}", imageTag)
elif parent is False:
image_tags = data["ImageTags"]
if image_tags is not None:
image_tag_type = image_tags[art_type]
if image_tag_type is not None:
image_tags = data.get("ImageTags", [])
if image_tags:
image_tag_type = image_tags.get(art_type)
if image_tag_type:
image_tag = image_tag_type
# log.debug("Image Tag: {0}", imageTag)
elif parent is True:
@@ -454,8 +419,8 @@ class DownloadUtils:
else:
tag_name = 'Parent%sImageTag' % art_type
id_name = 'Parent%sItemId' % art_type
parent_image_id = data[id_name]
parent_image_tag = data[tag_name]
parent_image_id = data.get(id_name)
parent_image_tag = data.get(tag_name)
if parent_image_id is not None and parent_image_tag is not None:
item_id = parent_image_id
image_tag = parent_image_tag
@@ -519,8 +484,8 @@ class DownloadUtils:
userid = window.get_property("userid")
user_image = window.get_property("userimage")
if userid and user_image:
log.debug("JellyCon DownloadUtils -> Returning saved UserID: {0}", userid)
if userid:
log.debug("JellyCon DownloadUtils -> Returning saved UserID: {0}".format(userid))
return userid
settings = xbmcaddon.Addon()
@@ -529,33 +494,27 @@ class DownloadUtils:
if not user_name:
return ""
log.debug("Looking for user name: {0}", user_name)
log.debug("Looking for user name: {0}".format(user_name))
try:
json_data = self.download_url("{server}/Users/Public?format=json", suppress=True, authenticate=False)
result = self.download_url("{server}/Users/Public?format=json", suppress=True, authenticate=False)
except Exception as msg:
log.error("Get User unable to connect: {0}", msg)
log.error("Get User unable to connect: {0}".format(msg))
return ""
log.debug("GETUSER_JSONDATA_01: {0}", json_data)
log.debug("GETUSER_JSONDATA_01: {0}".format(py2_decode(result)))
try:
result = json.loads(json_data)
except Exception as e:
log.debug("Could not load user data: {0}", e)
if not result:
return ""
if result is None:
return ""
log.debug("GETUSER_JSONDATA_02: {0}", result)
log.debug("GETUSER_JSONDATA_02: {0}".format(result))
secure = False
for user in result:
if user.get("Name") == unicode(user_name, "utf-8"):
userid = user.get("Id")
user_image = self.get_user_artwork(user, 'Primary')
log.debug("Username Found: {0}", user.get("Name"))
log.debug("Username Found: {0}".format(user.get("Name")))
if user.get("HasPassword", False):
secure = True
log.debug("Username Is Secure (HasPassword=True)")
@@ -579,7 +538,7 @@ class DownloadUtils:
string_load(30045),
icon="special://home/addons/plugin.video.jellycon/icon.png")
log.debug("userid: {0}", userid)
log.debug("userid: {0}".format(userid))
window.set_property("userid", userid)
window.set_property("userimage", user_image)
@@ -592,39 +551,31 @@ class DownloadUtils:
token = window.get_property("AccessToken")
if token is not None and token != "":
log.debug("JellyCon DownloadUtils -> Returning saved AccessToken: {0}", token)
log.debug("JellyCon DownloadUtils -> Returning saved AccessToken: {0}".format(token))
return token
settings = xbmcaddon.Addon()
port = settings.getSetting("port")
host = settings.getSetting("ipaddress")
if host is None or host == "" or port is None or port == "":
return ""
server_address = settings.getSetting("server_address")
url = "{server}/Users/AuthenticateByName?format=json"
user_details = load_user_details(settings)
user_name = urllib.quote(user_details.get("username", ""))
pwd_text = urllib.quote(user_details.get("password", ""))
user_name = user_details.get("username", "")
pwd_text = user_details.get("password", "")
message_data = "username=" + user_name + "&pw=" + pwd_text
message_data = {'username': user_name, 'pw': pwd_text}
resp = self.download_url(url, post_body=message_data, method="POST", suppress=True, authenticate=False)
log.debug("AuthenticateByName: {0}", resp)
result = self.download_url(url, post_body=message_data, method="POST", suppress=True, authenticate=False)
log.debug("AuthenticateByName: {0}".format(result))
access_token = None
userid = None
try:
result = json.loads(resp)
access_token = result.get("AccessToken")
# userid = result["SessionInfo"].get("UserId")
userid = result["User"].get("Id")
except:
pass
access_token = result.get("AccessToken")
userid = result["User"].get("Id")
if access_token is not None:
log.debug("User Authenticated: {0}", access_token)
log.debug("User Id: {0}", userid)
log.debug("User Authenticated: {0}".format(access_token))
log.debug("User Id: {0}".format(userid))
window.set_property("AccessToken", access_token)
window.set_property("userid", userid)
# WINDOW.setProperty("userimage", "")
@@ -660,27 +611,24 @@ class DownloadUtils:
if authenticate is False:
auth_string = "MediaBrowser Client=\"" + client + "\",Device=\"" + device_name + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
# headers["Authorization"] = authString
headers['X-Emby-Authorization'] = auth_string
return headers
else:
userid = self.get_user_id()
auth_string = "MediaBrowser UserId=\"" + userid + "\",Client=\"" + client + "\",Device=\"" + device_name + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
# headers["Authorization"] = authString
headers['X-Emby-Authorization'] = auth_string
auth_token = self.authenticate()
if auth_token != "":
headers["X-MediaBrowser-Token"] = auth_token
log.debug("JellyCon Authentication Header: {0}", headers)
log.debug("JellyCon Authentication Header: {0}".format(headers))
return headers
@timer
def download_url(self, url, suppress=False, post_body=None, method="GET", authenticate=True, headers=None):
log.debug("downloadUrl")
return_data = "null"
settings = xbmcaddon.Addon()
user_details = load_user_details(settings)
username = user_details.get("username", "")
@@ -689,23 +637,23 @@ class DownloadUtils:
http_timeout = int(settings.getSetting("http_timeout"))
if authenticate and username == "":
return return_data
return {}
if settings.getSetting("suppressErrors") == "true":
suppress = True
log.debug("Before: {0}", url)
log.debug("Before: {0}".format(url))
if url.find("{server}") != -1:
server = self.get_server()
if server is None:
return return_data
return {}
url = url.replace("{server}", server)
if url.find("{userid}") != -1:
userid = self.get_user_id()
if not userid:
return return_data
return {}
url = url.replace("{userid}", userid)
if url.find("{ItemLimit}") != -1:
@@ -720,120 +668,75 @@ class DownloadUtils:
home_window = HomeWindow()
random_movies = home_window.get_property("random-movies")
if not random_movies:
return return_data
return {}
url = url.replace("{random_movies}", random_movies)
log.debug("After: {0}", url)
conn = None
log.debug("After: {0}".format(url))
try:
url_bits = urlparse(url.strip())
protocol = url_bits.scheme
host_name = url_bits.hostname
port = url_bits.port
user_name = url_bits.username
user_password = url_bits.password
url_path = url_bits.path
url_puery = url_bits.query
if not host_name or host_name == "<none>":
return return_data
local_use_https = False
if protocol.lower() == "https":
local_use_https = True
server = "%s:%s" % (host_name, port)
url_path = url_path + "?" + url_puery
if local_use_https and self.verify_cert:
log.debug("Connection: HTTPS, Cert checked")
conn = httplib.HTTPSConnection(server, timeout=http_timeout)
elif local_use_https and not self.verify_cert:
log.debug("Connection: HTTPS, Cert NOT checked")
conn = httplib.HTTPSConnection(server, timeout=http_timeout, context=ssl._create_unverified_context())
else:
log.debug("Connection: HTTP")
conn = httplib.HTTPConnection(server, timeout=http_timeout)
head = self.get_auth_header(authenticate)
if user_name and user_password:
log.info("Replacing username & Password info")
# add basic auth headers
user_and_pass = b64encode(b"%s:%s" % (user_name, user_password)).decode("ascii")
head["Authorization"] = 'Basic %s' % user_and_pass
head["User-Agent"] = "JellyCon-" + ClientInformation().get_version()
log.debug("HEADERS: {0}", head)
if post_body is not None:
http_request = getattr(requests, method.lower())
if post_body:
if isinstance(post_body, dict):
content_type = "application/json"
head["Content-Type"] = "application/json"
post_body = json.dumps(post_body)
else:
content_type = "application/x-www-form-urlencoded"
head["Content-Type"] = "application/x-www-form-urlencoded"
head["Content-Type"] = content_type
log.debug("Content-Type: {0}", content_type)
log.debug("Content-Type: {0}".format(head["Content-Type"]))
log.debug("POST DATA: {0}".format(post_body))
log.debug("POST DATA: {0}", post_body)
conn.request(method=method, url=url_path, body=post_body, headers=head)
data = http_request(url, data=post_body, headers=head)
else:
conn.request(method=method, url=url_path, headers=head)
data = http_request(url, headers=head)
data = conn.getresponse()
log.debug("HTTP response: {0} {1}", data.status, data.reason)
log.debug("GET URL HEADERS: {0}", data.getheaders())
if int(data.status) == 200:
ret_data = data.read()
content_type = data.getheader('content-encoding')
log.debug("Data Len Before: {0}", len(ret_data))
if content_type == "gzip":
ret_data = StringIO.StringIO(ret_data)
gzipper = gzip.GzipFile(fileobj=ret_data)
return_data = gzipper.read()
else:
return_data = ret_data
if data.status_code == 200:
if headers is not None and isinstance(headers, dict):
headers.update(data.getheaders())
log.debug("Data Len After: {0}", len(return_data))
log.debug("====== 200 returned =======")
log.debug("Content-Type: {0}", content_type)
log.debug("{0}", return_data)
log.debug("====== 200 finished ======")
headers.update(data.headers)
log.debug("{0}".format(data.json()))
elif int(data.status) >= 400:
elif data.status_code >= 400:
if int(data.status) == 401:
if data.status_code == 401:
# remove any saved password
m = hashlib.md5()
m.update(username)
hashed_username = m.hexdigest()
log.error("HTTP response error 401 auth error, removing any saved passwords for user: {0}", hashed_username)
log.error("HTTP response error 401 auth error, removing any saved passwords for user: {0}".format(hashed_username))
settings.setSetting("saved_user_password_" + hashed_username, "")
save_user_details(settings, "", "")
log.error("HTTP response error: {0} {1}", data.status, data.reason)
log.error("HTTP response error for {0}: {1} {2}".format(url, data.status_code, data.content))
if suppress is False:
xbmcgui.Dialog().notification(string_load(30316),
string_load(30200) % str(data.reason),
string_load(30200) % str(data.content),
icon="special://home/addons/plugin.video.jellycon/icon.png")
try:
result = data.json()
except:
result = {}
return result
except Exception as msg:
log.error("Unable to connect to {0} : {1}", server, msg)
if suppress is False:
log.error("{0}".format(format_exc()))
log.error("Unable to connect to {0} : {1}".format(server, msg))
if not suppress:
xbmcgui.Dialog().notification(string_load(30316),
str(msg),
icon="special://home/addons/plugin.video.jellycon/icon.png")
finally:
try:
log.debug("Closing HTTP connection: {0}", conn)
conn.close()
except:
pass
return return_data

View File

@@ -90,10 +90,6 @@ import sys
import time
import errno
# from .simple_logging import SimpleLogging
# log = SimpleLogging(__name__)
class FileLock(object):
""" A file locking mechanism that has context-manager support so
you can use it in a ``with`` statement. This should be relatively cross
@@ -201,7 +197,7 @@ if __name__ == "__main__":
import threading
import tempfile
from builtins import range
temp_dir = tempfile.mkdtemp()
protected_filepath = os.path.join(temp_dir, "somefile.txt")
@@ -236,4 +232,4 @@ if __name__ == "__main__":
# Please manually inspect the output. Does it look like the operations were atomic?
with open(protected_filepath, "r") as f:
sys.stdout.write(f.read())
"""
"""

View File

@@ -1,4 +1,5 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import urllib
import sys
@@ -20,7 +21,7 @@ from .kodi_utils import HomeWindow
from .clientinfo import ClientInformation
from .datamanager import DataManager, clear_cached_server_data
from .server_detect import check_server, check_connection_speed
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .menu_functions import display_main_menu, display_menu, show_movie_alpha_list, show_tvshow_alpha_list, show_genre_list, show_search, show_movie_pages
from .translation import string_load
from .server_sessions import show_server_sessions
@@ -39,7 +40,7 @@ __addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile'))
__cwd__ = __addon__.getAddonInfo('path')
PLUGINPATH = xbmc.translatePath(os.path.join(__cwd__))
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
@@ -60,14 +61,14 @@ def main_entry_point():
pr = cProfile.Profile()
pr.enable()
log.debug("Running Python: {0}", sys.version_info)
log.debug("Running JellyCon: {0}", ClientInformation().get_version())
log.debug("Kodi BuildVersion: {0}", xbmc.getInfoLabel("System.BuildVersion"))
log.debug("Kodi Version: {0}", kodi_version)
log.debug("Script argument data: {0}", sys.argv)
log.debug("Running Python: {0}".format(sys.version_info))
log.debug("Running JellyCon: {0}".format(ClientInformation().get_version()))
log.debug("Kodi BuildVersion: {0}".format(xbmc.getInfoLabel("System.BuildVersion")))
log.debug("Kodi Version: {0}".format(kodi_version))
log.debug("Script argument data: {0}".format(sys.argv))
params = get_params()
log.debug("Script params: {0}", params)
log.debug("Script params: {0}".format(params))
request_path = params.get("request_path", None)
param_url = params.get('url', None)
@@ -148,8 +149,8 @@ def main_entry_point():
else:
log.info("Unable to find TV show parent ID.")
else:
log.debug("JellyCon -> Mode: {0}", mode)
log.debug("JellyCon -> URL: {0}", param_url)
log.debug("JellyCon -> Mode: {0}".format(mode))
log.debug("JellyCon -> URL: {0}".format(param_url))
if mode == "GET_CONTENT":
get_content(param_url, params)
@@ -189,7 +190,7 @@ def __get_parent_id_from(params):
result = None
show_provider_ids = params.get("show_ids")
if show_provider_ids is not None:
log.debug("TV show providers IDs: {}", show_provider_ids)
log.debug("TV show providers IDs: {}".format(show_provider_ids))
get_show_url = "{server}/Users/{userid}/Items?fields=MediaStreams&Recursive=true" \
"&IncludeItemTypes=series&IncludeMedia=true&ImageTypeLimit=1&Limit=16" \
"&AnyProviderIdEquals=" + show_provider_ids
@@ -198,21 +199,21 @@ def __get_parent_id_from(params):
if len(show) == 1:
result = content.get("Items")[0].get("Id")
else:
log.debug("TV show not found for ids: {}", show_provider_ids)
log.debug("TV show not found for ids: {}".format(show_provider_ids))
else:
log.error("TV show parameter not found in request.")
return result
def toggle_watched(params):
log.debug("toggle_watched: {0}", params)
log.debug("toggle_watched: {0}".format(params))
item_id = params.get("item_id", None)
if item_id is None:
return
url = "{server}/Users/{userid}/Items/" + item_id + "?format=json"
data_manager = DataManager()
result = data_manager.get_content(url)
log.debug("toggle_watched item info: {0}", result)
log.debug("toggle_watched item info: {0}".format(result))
user_data = result.get("UserData", None)
if user_data is None:
@@ -225,35 +226,35 @@ def toggle_watched(params):
def mark_item_watched(item_id):
log.debug("Mark Item Watched: {0}", item_id)
log.debug("Mark Item Watched: {0}".format(item_id))
url = "{server}/Users/{userid}/PlayedItems/" + item_id
downloadUtils.download_url(url, post_body="", method="POST")
check_for_new_content()
home_window = HomeWindow()
last_url = home_window.get_property("last_content_url")
if last_url:
log.debug("markWatched_lastUrl: {0}", last_url)
log.debug("markWatched_lastUrl: {0}".format(last_url))
home_window.set_property("skip_cache_for_" + last_url, "true")
xbmc.executebuiltin("Container.Refresh")
def mark_item_unwatched(item_id):
log.debug("Mark Item UnWatched: {0}", item_id)
log.debug("Mark Item UnWatched: {0}".format(item_id))
url = "{server}/Users/{userid}/PlayedItems/" + item_id
downloadUtils.download_url(url, method="DELETE")
check_for_new_content()
home_window = HomeWindow()
last_url = home_window.get_property("last_content_url")
if last_url:
log.debug("markUnwatched_lastUrl: {0}", last_url)
log.debug("markUnwatched_lastUrl: {0}".format(last_url))
home_window.set_property("skip_cache_for_" + last_url, "true")
xbmc.executebuiltin("Container.Refresh")
def mark_item_favorite(item_id):
log.debug("Add item to favourites: {0}", item_id)
log.debug("Add item to favourites: {0}".format(item_id))
url = "{server}/Users/{userid}/FavoriteItems/" + item_id
downloadUtils.download_url(url, post_body="", method="POST")
check_for_new_content()
@@ -266,7 +267,7 @@ def mark_item_favorite(item_id):
def unmark_item_favorite(item_id):
log.debug("Remove item from favourites: {0}", item_id)
log.debug("Remove item from favourites: {0}".format(item_id))
url = "{server}/Users/{userid}/FavoriteItems/" + item_id
downloadUtils.download_url(url, method="DELETE")
check_for_new_content()
@@ -280,8 +281,7 @@ def unmark_item_favorite(item_id):
def delete(item_id):
json_data = downloadUtils.download_url("{server}/Users/{userid}/Items/" + item_id + "?format=json")
item = json.loads(json_data)
item = downloadUtils.download_url("{server}/Users/{userid}/Items/" + item_id + "?format=json")
item_id = item.get("Id")
item_name = item.get("Name", "")
@@ -304,7 +304,7 @@ def delete(item_id):
return_value = xbmcgui.Dialog().yesno(string_load(30091), final_name, string_load(30092))
if return_value:
log.debug('Deleting Item: {0}', item_id)
log.debug('Deleting Item: {0}'.format(item_id))
url = '{server}/Items/' + item_id
progress = xbmcgui.DialogProgress()
progress.create(string_load(30052), string_load(30053))
@@ -324,8 +324,8 @@ def get_params():
plugin_path = sys.argv[0]
paramstring = sys.argv[2]
log.debug("Parameter string: {0}", paramstring)
log.debug("Plugin Path string: {0}", plugin_path)
log.debug("Parameter string: {0}".format(paramstring))
log.debug("Plugin Path string: {0}".format(plugin_path))
param = {}
@@ -348,12 +348,12 @@ def get_params():
elif (len(splitparams)) == 3:
param[splitparams[0]] = splitparams[1] + "=" + splitparams[2]
log.debug("JellyCon -> Detected parameters: {0}", param)
log.debug("JellyCon -> Detected parameters: {0}".format(param))
return param
def show_menu(params):
log.debug("showMenu(): {0}", params)
log.debug("showMenu(): {0}".format(params))
home_window = HomeWindow()
settings = xbmcaddon.Addon()
@@ -362,7 +362,7 @@ def show_menu(params):
url = "{server}/Users/{userid}/Items/" + item_id + "?format=json"
data_manager = DataManager()
result = data_manager.get_content(url)
log.debug("Menu item info: {0}", result)
log.debug("Menu item info: {0}".format(result))
if result is None:
return
@@ -466,15 +466,15 @@ def show_menu(params):
view_key = "view-" + container_content_type
current_default_view = settings.getSetting(view_key)
view_match = container_view_id == current_default_view
log.debug("View ID:{0} Content type:{1}", container_view_id, container_content_type)
log.debug("View ID:{0} Content type:{1}".format(container_view_id, container_content_type))
if container_content_type in ["movies", "tvshows", "seasons", "episodes", "sets"]:
if view_match:
li = xbmcgui.ListItem("Unset as defalt view")
li = xbmcgui.ListItem("Unset as default view")
li.setProperty('menu_id', 'unset_view')
action_items.append(li)
else:
li = xbmcgui.ListItem("Set as defalt view")
li = xbmcgui.ListItem("Set as default view")
li.setProperty('menu_id', 'set_view')
action_items.append(li)
@@ -487,7 +487,7 @@ def show_menu(params):
selected_action = ""
if selected_action_item is not None:
selected_action = selected_action_item.getProperty('menu_id')
log.debug("Menu Action Selected: {0}", selected_action)
log.debug("Menu Action Selected: {0}".format(selected_action))
del action_menu
if selected_action == "play":
@@ -498,11 +498,11 @@ def show_menu(params):
play_action(params)
elif selected_action == "set_view":
log.debug("Settign view type for {0} to {1}", view_key, container_view_id)
log.debug("Settign view type for {0} to {1}".format(view_key, container_view_id))
settings.setSetting(view_key, container_view_id)
elif selected_action == "unset_view":
log.debug("Un-Settign view type for {0} to {1}", view_key, container_view_id)
log.debug("Un-Settign view type for {0} to {1}".format(view_key, container_view_id))
settings.setSetting(view_key, "")
elif selected_action == "refresh_server":
@@ -513,7 +513,7 @@ def show_menu(params):
"&ReplaceAllImages=true" +
"&ReplaceAllMetadata=true")
res = downloadUtils.download_url(url, post_body="", method="POST")
log.debug("Refresh Server Responce: {0}", res)
log.debug("Refresh Server Responce: {0}".format(res))
elif selected_action == "hide":
user_details = load_user_details(settings)
@@ -522,13 +522,13 @@ def show_menu(params):
url = "{server}/Items/" + item_id + "/Tags/Add"
post_tag_data = {"Tags": [{"Name": hide_tag_string}]}
res = downloadUtils.download_url(url, post_body=post_tag_data, method="POST")
log.debug("Add Tag Responce: {0}", res)
log.debug("Add Tag Responce: {0}".format(res))
check_for_new_content()
last_url = home_window.get_property("last_content_url")
if last_url:
log.debug("markUnwatched_lastUrl: {0}", last_url)
log.debug("markUnwatched_lastUrl: {0}".format(last_url))
home_window.set_property("skip_cache_for_" + last_url, "true")
xbmc.executebuiltin("Container.Refresh")
@@ -549,7 +549,7 @@ def show_menu(params):
bitrate_dialog.doModal()
selected_transcode_value = bitrate_dialog.selected_transcode_value
del bitrate_dialog
log.debug("selected_transcode_value: {0}", selected_transcode_value)
log.debug("selected_transcode_value: {0}".format(selected_transcode_value))
if selected_transcode_value > 0:
settings.setSetting("force_max_stream_bitrate", str(selected_transcode_value))
@@ -577,11 +577,10 @@ def show_menu(params):
elif selected_action == "safe_delete":
url = "{server}/jellyfin_safe_delete/delete_item/" + item_id
delete_action = downloadUtils.download_url(url)
result = json.loads(delete_action)
result = downloadUtils.download_url(url)
dialog = xbmcgui.Dialog()
if result:
log.debug("Safe_Delete_Action: {0}", result)
log.debug("Safe_Delete_Action: {0}".format(result))
action_token = result["action_token"]
message = "You are about to delete the following item[CR][CR]"
@@ -611,7 +610,7 @@ def show_menu(params):
confirm_dialog.message = message
confirm_dialog.heading = "Confirm delete files?"
confirm_dialog.doModal()
log.debug("safe_delete_confirm_dialog: {0}", confirm_dialog.confirm)
log.debug("safe_delete_confirm_dialog: {0}".format(confirm_dialog.confirm))
if confirm_dialog.confirm:
url = "{server}/jellyfin_safe_delete/delete_item_action"
@@ -620,12 +619,11 @@ def show_menu(params):
'action_token': action_token
}
delete_action = downloadUtils.download_url(url, method="POST", post_body=playback_info)
log.debug("Delete result action: {0}", delete_action)
delete_action_result = json.loads(delete_action)
if not delete_action_result:
dialog.ok("Error", "Error deleteing files", "Error in responce from server")
elif not delete_action_result["result"]:
dialog.ok("Error", "Error deleteing files", delete_action_result["message"])
log.debug("Delete result action: {0}".format(delete_action))
if not delete_action:
dialog.ok("Error", "Error deleting files", "Error in responce from server")
elif not delete_action.get("result"):
dialog.ok("Error", "Error deleting files", delete_action["message"])
else:
dialog.ok("Deleted", "Files deleted")
else:
@@ -686,29 +684,11 @@ def show_menu(params):
def populate_listitem(item_id):
log.debug("populate_listitem: {0}", item_id)
log.debug("populate_listitem: {0}".format(item_id))
url = "{server}/Users/{userid}/Items/" + item_id + "?format=json"
json_data = downloadUtils.download_url(url)
result = json.loads(json_data)
log.debug("populate_listitem item info: {0}", result)
'''
server = downloadUtils.getServer()
gui_options = {}
gui_options["server"] = server
gui_options["name_format"] = None
gui_options["name_format_type"] = None
details, extraData = extract_item_info(result,gui_options )
u, list_item, folder = add_gui_item(result["Id"], details, extraData, {}, folder=False)
log.debug("list_item path: {0}", u)
#list_item.setProperty('IsPlayable', 'false')
#list_item.setPath(u)
'''
result = downloadUtils.download_url(url)
log.debug("populate_listitem item info: {0}".format(result))
item_title = result.get("Name", string_load(30280))
@@ -738,7 +718,7 @@ def populate_listitem(item_id):
def show_content(params):
log.debug("showContent Called: {0}", params)
log.debug("showContent Called: {0}".format(params))
item_type = params.get("item_type")
settings = xbmcaddon.Addon()
@@ -760,7 +740,7 @@ def show_content(params):
"&IsVirtualUnaired=false" +
"&IncludeItemTypes=" + item_type)
log.debug("showContent Content Url: {0}", content_url)
log.debug("showContent Content Url: {0}".format(content_url))
get_content(content_url, params)
@@ -776,28 +756,16 @@ def search_results_person(params):
'&Fields={field_filters}' +
'&format=json')
'''
details_result = dataManager.GetContent(details_url)
log.debug("Search Results Details: {0}", details_result)
if details_result:
items = details_result.get("Items")
found_types = set()
for item in items:
found_types.add(item.get("Type"))
log.debug("search_results_person found_types: {0}", found_types)
'''
params["name_format"] = "Episode|episode_name_format"
dir_items, detected_type, total_records = process_directory(details_url, None, params)
log.debug('search_results_person results: {0}', dir_items)
log.debug('search_results_person detect_type: {0}', detected_type)
log.debug('search_results_person results: {0}'.format(dir_items))
log.debug('search_results_person detect_type: {0}'.format(detected_type))
if detected_type is not None:
# if the media type is not set then try to use the detected type
log.debug("Detected content type: {0}", detected_type)
log.debug("Detected content type: {0}".format(detected_type))
content_type = None
if detected_type == "Movie":
@@ -825,9 +793,9 @@ def search_results(params):
item_type = params.get('item_type')
query_string = params.get('query')
if query_string:
log.debug("query_string : {0}", query_string)
log.debug("query_string : {0}".format(query_string))
query_string = urllib.unquote(query_string)
log.debug("query_string : {0}", query_string)
log.debug("query_string : {0}".format(query_string))
item_type = item_type.lower()
@@ -867,14 +835,14 @@ def search_results(params):
return
home_window.set_property("last_search", user_input)
log.debug('searchResults Called: {0}', params)
log.debug('searchResults Called: {0}'.format(params))
query = user_input
else:
query = query_string
query = urllib.quote(query)
log.debug("query : {0}", query)
log.debug("query : {0}".format(query))
if (not item_type) or (not query):
return
@@ -904,7 +872,7 @@ def search_results(params):
"&userId={userid}")
person_search_results = dataManager.get_content(search_url)
log.debug("Person Search Result : {0}", person_search_results)
log.debug("Person Search Result : {0}".format(person_search_results))
if person_search_results is None:
return
@@ -967,23 +935,30 @@ def search_results(params):
def play_action(params):
log.debug("== ENTER: PLAY ==")
log.debug("PLAY ACTION PARAMS: {0}", params)
log.debug("PLAY ACTION PARAMS: {0}".format(params))
item_id = params.get("item_id")
auto_resume = int(params.get("auto_resume", "-1"))
log.debug("AUTO_RESUME: {0}", auto_resume)
auto_resume = params.get("auto_resume", "-1")
if auto_resume == 'None':
auto_resume = '-1'
if auto_resume:
auto_resume = int(auto_resume)
else:
auto_resume = -1
log.debug("AUTO_RESUME: {0}".format(auto_resume))
force_transcode = params.get("force_transcode", None) is not None
log.debug("FORCE_TRANSCODE: {0}", force_transcode)
log.debug("FORCE_TRANSCODE: {0}".format(force_transcode))
media_source_id = params.get("media_source_id", "")
log.debug("media_source_id: {0}", media_source_id)
log.debug("media_source_id: {0}".format(media_source_id))
subtitle_stream_index = params.get("subtitle_stream_index")
log.debug("subtitle_stream_index: {0}", subtitle_stream_index)
log.debug("subtitle_stream_index: {0}".format(subtitle_stream_index))
audio_stream_index = params.get("audio_stream_index")
log.debug("audio_stream_index: {0}", audio_stream_index)
log.debug("audio_stream_index: {0}".format(audio_stream_index))
action = params.get("action", "play")
@@ -1001,7 +976,7 @@ def play_action(params):
play_info["media_source_id"] = media_source_id
play_info["subtitle_stream_index"] = subtitle_stream_index
play_info["audio_stream_index"] = audio_stream_index
log.info("Sending jellycon_play_action : {0}", play_info)
log.info("Sending jellycon_play_action : {0}".format(play_info))
send_event_notification("jellycon_play_action", play_info)
@@ -1010,13 +985,12 @@ def play_item_trailer(item_id):
url = ("{server}/Users/{userid}/Items/%s/LocalTrailers?format=json" % item_id)
json_data = downloadUtils.download_url(url)
result = json.loads(json_data)
result = downloadUtils.download_url(url)
if result is None:
return
log.debug("LocalTrailers {0}", result)
log.debug("LocalTrailers {0}".format(result))
count = 1
trailer_names = []
@@ -1035,9 +1009,8 @@ def play_item_trailer(item_id):
trailer_list.append(info)
url = ("{server}/Users/{userid}/Items/%s?format=json&Fields=RemoteTrailers" % item_id)
json_data = downloadUtils.download_url(url)
result = json.loads(json_data)
log.debug("RemoteTrailers: {0}", result)
result = downloadUtils.download_url(url)
log.debug("RemoteTrailers: {0}".format(result))
count = 1
if result is None:
@@ -1058,7 +1031,7 @@ def play_item_trailer(item_id):
trailer_names.append(name)
trailer_list.append(info)
log.debug("TrailerList: {0}", trailer_list)
log.debug("TrailerList: {0}".format(trailer_list))
trailer_text = []
for trailer in trailer_list:
@@ -1069,7 +1042,7 @@ def play_item_trailer(item_id):
resp = dialog.select(string_load(30308), trailer_text)
if resp > -1:
trailer = trailer_list[resp]
log.debug("SelectedTrailer: {0}", trailer)
log.debug("SelectedTrailer: {0}".format(trailer))
if trailer.get("type") == "local":
params = {}
@@ -1079,7 +1052,7 @@ def play_item_trailer(item_id):
elif trailer.get("type") == "remote":
youtube_id = trailer.get("url").rsplit('=', 1)[1]
youtube_plugin = "RunPlugin(plugin://plugin.video.youtube/play/?video_id=%s)" % youtube_id
log.debug("youtube_plugin: {0}", youtube_plugin)
log.debug("youtube_plugin: {0}".format(youtube_plugin))
# play_info = {}
# play_info["url"] = youtube_plugin

View File

@@ -1,3 +1,4 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmcvfs
import xbmc
@@ -7,11 +8,11 @@ from urlparse import urlparse
from random import shuffle
import threading
import httplib
import requests
import io
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .datamanager import DataManager
from .downloadutils import DownloadUtils
from .utils import get_art
@@ -24,7 +25,7 @@ except Exception as err:
pil_loaded = False
PORT_NUMBER = 24276
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
def get_image_links(url):
@@ -78,7 +79,7 @@ def get_image_links(url):
def build_image(path):
log.debug("build_image()")
log.debug("Request Path : {0}", path)
log.debug("Request Path : {0}".format(path))
request_path = path[1:]
@@ -86,7 +87,7 @@ def build_image(path):
return []
decoded_url = base64.b64decode(request_path)
log.debug("decoded_url : {0}", decoded_url)
log.debug("decoded_url : {0}".format(decoded_url))
image_urls = get_image_links(decoded_url)
@@ -116,13 +117,11 @@ def build_image(path):
server = "%s:%s" % (host_name, port)
url_full_path = url_path + "?" + url_query
log.debug("Loading image from : {0} {1} {2}", image_count, server, url_full_path)
log.debug("Loading image from : {0} {1} {2}".format(image_count, server, url_full_path))
try:
conn = httplib.HTTPConnection(server)
conn.request("GET", url_full_path)
image_responce = conn.getresponse()
image_data = image_responce.read()
image_response = requests.get(thumb_url)
image_data = image_response.content
loaded_image = Image.open(io.BytesIO(image_data))
image = ImageOps.fit(loaded_image, size, method=Image.ANTIALIAS, bleed=0.0, centering=(0.5, 0.5))
@@ -136,7 +135,7 @@ def build_image(path):
del image_data
except Exception as con_err:
log.debug("Error loading image : {0}", str(con_err))
log.debug("Error loading image : {0}".format(con_err))
image_count += 1
@@ -170,12 +169,6 @@ class HttpImageHandler(BaseHTTPRequestHandler):
self.end_headers()
return
def do_QUIT(self):
log.debug("HttpImageHandler:do_QUIT()")
self.send_response(200)
self.end_headers()
return
def serve_image(self):
if pil_loaded:
@@ -211,14 +204,8 @@ class HttpImageServerThread(threading.Thread):
threading.Thread.__init__(self)
def stop(self):
self.keep_running = False
log.debug("HttpImageServerThread:stop called")
try:
conn = httplib.HTTPConnection("localhost:%d" % PORT_NUMBER)
conn.request("QUIT", "/")
conn.getresponse()
except:
pass
self.keep_running = False
def run(self):
log.debug("HttpImageServerThread:started")

View File

@@ -1,3 +1,4 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import sys
import os
@@ -12,11 +13,11 @@ import xbmcaddon
import xbmcgui
from .utils import get_art, datetime_from_string
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .downloadutils import DownloadUtils
from .kodi_utils import HomeWindow
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
addon_instance = xbmcaddon.Addon()
@@ -100,27 +101,27 @@ def extract_item_info(item, gui_options):
item_details = ItemDetails()
item_details.id = item["Id"]
item_details.etag = item["Etag"]
item_details.is_folder = item["IsFolder"]
item_details.item_type = item["Type"]
item_details.location_type = item["LocationType"]
item_details.name = item["Name"]
item_details.sort_name = item["SortName"]
item_details.id = item.get("Id")
item_details.etag = item.get("Etag")
item_details.is_folder = item.get("IsFolder")
item_details.item_type = item.get("Type")
item_details.location_type = item.get("LocationType")
item_details.name = item.get("Name")
item_details.sort_name = item.get("SortName")
item_details.original_title = item_details.name
if item_details.item_type == "Episode":
item_details.episode_number = item["IndexNumber"]
item_details.season_number = item["ParentIndexNumber"]
item_details.series_id = item["SeriesId"]
item_details.episode_number = item.get("IndexNumber")
item_details.season_number = item.get("ParentIndexNumber")
item_details.series_id = item.get("SeriesId")
if item_details.season_number != 0:
item_details.season_sort_number = item_details.season_number
item_details.episode_sort_number = item_details.episode_number
else:
special_after_season = item["AirsAfterSeasonNumber"]
special_before_season = item["AirsBeforeSeasonNumber"]
special_before_episode = item["AirsBeforeEpisodeNumber"]
special_after_season = item.get("AirsAfterSeasonNumber")
special_before_season = item.get("AirsBeforeSeasonNumber")
special_before_episode = item.get("AirsBeforeEpisodeNumber")
if special_after_season:
item_details.season_sort_number = special_after_season + 1
@@ -131,21 +132,21 @@ def extract_item_info(item, gui_options):
item_details.episode_sort_number = special_before_episode - 1
elif item_details.item_type == "Season":
item_details.season_number = item["IndexNumber"]
item_details.series_id = item["SeriesId"]
item_details.season_number = item.get("IndexNumber")
item_details.series_id = item.get("SeriesId")
elif item_details.item_type == "Series":
item_details.status = item["Status"]
item_details.status = item.get("Status")
elif item_details.item_type == "Audio":
item_details.track_number = item["IndexNumber"]
item_details.album_name = item["Album"]
artists = item["Artists"]
if artists is not None and len(artists) > 0:
item_details.track_number = item.get("IndexNumber")
item_details.album_name = item.get("Album")
artists = item.get("Artists", [])
if artists:
item_details.song_artist = artists[0] # get first artist
elif item_details.item_type == "MusicAlbum":
item_details.album_artist = item["AlbumArtist"]
item_details.album_artist = item.get("AlbumArtist")
item_details.album_name = item_details.name
if item_details.season_number is None:
@@ -153,34 +154,34 @@ def extract_item_info(item, gui_options):
if item_details.episode_number is None:
item_details.episode_number = 0
if item["Taglines"] is not None and len(item["Taglines"]) > 0:
item_details.tagline = item["Taglines"][0]
if item.get("Taglines", []):
item_details.tagline = item.get("Taglines")[0]
item_details.tags = []
if item["TagItems"] is not None and len(item["TagItems"]) > 0:
for tag_info in item["TagItems"]:
item_details.tags.append(tag_info["Name"])
if item.get("TagItems", []):
for tag_info in item.get("TagItems"):
item_details.tags.append(tag_info.get("Name"))
# set the item name
# override with name format string from request
name_format = gui_options["name_format"]
name_format_type = gui_options["name_format_type"]
name_format = gui_options.get("name_format")
name_format_type = gui_options.get("name_format_type")
if name_format is not None and item_details.item_type == name_format_type:
name_info = {}
name_info["ItemName"] = item["Name"]
season_name = item["SeriesName"]
name_info["ItemName"] = item.get("Name")
season_name = item.get("SeriesName")
if season_name:
name_info["SeriesName"] = season_name
else:
name_info["SeriesName"] = ""
name_info["SeasonIndex"] = u"%02d" % item_details.season_number
name_info["EpisodeIndex"] = u"%02d" % item_details.episode_number
log.debug("FormatName: {0} | {1}", name_format, name_info)
log.debug("FormatName: {0} | {1}".format(name_format, name_info))
item_details.name = unicode(name_format).format(**name_info).strip()
year = item["ProductionYear"]
prem_date = item["PremiereDate"]
year = item.get("ProductionYear")
prem_date = item.get("PremiereDate")
if year is not None:
item_details.year = year
@@ -191,35 +192,35 @@ def extract_item_info(item, gui_options):
tokens = prem_date.split("T")
item_details.premiere_date = tokens[0]
create_date = item["DateCreated"]
if create_date is not None:
create_date = item.get("DateCreated")
if create_date:
item_details.date_added = create_date.split('.')[0].replace('T', " ")
# add the premiered date for Upcoming TV
if item_details.location_type == "Virtual":
airtime = item["AirTime"]
airtime = item.get("AirTime")
item_details.name = item_details.name + ' - ' + item_details.premiere_date + ' - ' + str(airtime)
if item_details.item_type == "Program":
item_details.program_channel_name = item["ChannelName"]
item_details.program_start_date = item["StartDate"]
item_details.program_end_date = item["EndDate"]
item_details.program_channel_name = item.get("ChannelName")
item_details.program_start_date = item.get("StartDate")
item_details.program_end_date = item.get("EndDate")
# Process MediaStreams
media_streams = item["MediaStreams"]
if media_streams is not None:
media_streams = item.get("MediaStreams", [])
if media_streams:
media_info_list = []
for mediaStream in media_streams:
stream_type = mediaStream["Type"]
stream_type = mediaStream.get("Type")
if stream_type == "Video":
media_info = {}
media_info["type"] = "video"
media_info["codec"] = mediaStream["Codec"]
media_info["height"] = mediaStream["Height"]
media_info["width"] = mediaStream["Width"]
aspect_ratio = mediaStream["AspectRatio"]
media_info["codec"] = mediaStream.get("Codec")
media_info["height"] = mediaStream.get("Height")
media_info["width"] = mediaStream.get("Width")
aspect_ratio = mediaStream.get("AspectRatio")
media_info["apect"] = aspect_ratio
if aspect_ratio is not None and len(aspect_ratio) >= 3:
if aspect_ratio and len(aspect_ratio) >= 3:
try:
aspect_width, aspect_height = aspect_ratio.split(':')
media_info["apect_ratio"] = float(aspect_width) / float(aspect_height)
@@ -231,36 +232,36 @@ def extract_item_info(item, gui_options):
if stream_type == "Audio":
media_info = {}
media_info["type"] = "audio"
media_info["codec"] = mediaStream["Codec"]
media_info["channels"] = mediaStream["Channels"]
media_info["language"] = mediaStream["Language"]
media_info["codec"] = mediaStream.get("Codec")
media_info["channels"] = mediaStream.get("Channels")
media_info["language"] = mediaStream.get("Language")
media_info_list.append(media_info)
if stream_type == "Subtitle":
item_details.subtitle_available = True
media_info = {}
media_info["type"] = "sub"
media_info["language"] = mediaStream["Language"]
media_info["language"] = mediaStream.get("Language", '')
media_info_list.append(media_info)
item_details.media_streams = media_info_list
# Process People
people = item["People"]
people = item.get("People", [])
if people is not None:
cast = []
for person in people:
person_type = person["Type"]
person_type = person.get("Type")
if person_type == "Director":
item_details.director = item_details.director + person["Name"] + ' '
item_details.director = item_details.director + person.get("Name") + ' '
elif person_type == "Writing":
item_details.writer = person["Name"]
elif person_type == "Actor":
# log.debug("Person: {0}", person)
person_name = person["Name"]
person_role = person["Role"]
person_id = person["Id"]
person_tag = person["PrimaryImageTag"]
if person_tag is not None:
person_name = person.get("Name")
person_role = person.get("Role")
person_id = person.get("Id")
person_tag = person.get("PrimaryImageTag")
if person_tag:
person_thumbnail = download_utils.image_url(person_id,
"Primary", 0, 400, 400,
person_tag,
@@ -272,64 +273,62 @@ def extract_item_info(item, gui_options):
item_details.cast = cast
# Process Studios
studios = item["Studios"]
studios = item.get("Studios", [])
if studios is not None:
for studio in studios:
if item_details.studio is None: # Just take the first one
studio_name = studio["Name"]
studio_name = studio.get("Name")
item_details.studio = studio_name
break
# production location
prod_location = item["ProductionLocations"]
prod_location = item.get("ProductionLocations", [])
# log.debug("ProductionLocations : {0}", prod_location)
if prod_location and len(prod_location) > 0:
if prod_location:
item_details.production_location = prod_location[0]
# Process Genres
genres = item["Genres"]
if genres is not None and len(genres) > 0:
genres = item.get("Genres", [])
if genres:
item_details.genres = genres
# Process UserData
user_data = item["UserData"]
if user_data is None:
user_data = defaultdict(lambda: None, {})
user_data = item.get("UserData", {})
if user_data["Played"] is True:
if user_data.get("Played"):
item_details.overlay = "6"
item_details.play_count = 1
else:
item_details.overlay = "7"
item_details.play_count = 0
if user_data["IsFavorite"] is True:
if user_data.get("IsFavorite"):
item_details.overlay = "5"
item_details.favorite = "true"
else:
item_details.favorite = "false"
reasonable_ticks = user_data["PlaybackPositionTicks"]
if reasonable_ticks is not None:
reasonable_ticks = user_data.get("PlaybackPositionTicks", 0)
if reasonable_ticks:
reasonable_ticks = int(reasonable_ticks) / 1000
item_details.resume_time = int(reasonable_ticks / 10000)
item_details.series_name = item["SeriesName"]
item_details.plot = item["Overview"]
item_details.series_name = item.get("SeriesName", '')
item_details.plot = item.get("Overview", '')
runtime = item["RunTimeTicks"]
if item_details.is_folder is False and runtime is not None:
runtime = item.get("RunTimeTicks")
if item_details.is_folder is False and runtime:
item_details.duration = long(runtime) / 10000000
child_count = item["ChildCount"]
if child_count is not None:
child_count = item.get("ChildCount")
if child_count:
item_details.total_seasons = child_count
recursive_item_count = item["RecursiveItemCount"]
if recursive_item_count is not None:
recursive_item_count = item.get("RecursiveItemCount")
if recursive_item_count:
item_details.total_episodes = recursive_item_count
unplayed_item_count = user_data["UnplayedItemCount"]
unplayed_item_count = user_data.get("UnplayedItemCount")
if unplayed_item_count is not None:
item_details.unwatched_episodes = unplayed_item_count
item_details.watched_episodes = item_details.total_episodes - unplayed_item_count
@@ -337,20 +336,20 @@ def extract_item_info(item, gui_options):
item_details.number_episodes = item_details.total_episodes
item_details.art = get_art(item, gui_options["server"])
item_details.rating = item["OfficialRating"]
item_details.mpaa = item["OfficialRating"]
item_details.rating = item.get("OfficialRating")
item_details.mpaa = item.get("OfficialRating")
item_details.community_rating = item["CommunityRating"]
if item_details.community_rating is None:
item_details.community_rating = item.get("CommunityRating")
if not item_details.community_rating:
item_details.community_rating = 0.0
item_details.critic_rating = item["CriticRating"]
if item_details.critic_rating is None:
item_details.critic_rating = item.get("CriticRating")
if not item_details.critic_rating:
item_details.critic_rating = 0.0
item_details.location_type = item["LocationType"]
item_details.recursive_item_count = item["RecursiveItemCount"]
item_details.recursive_unplayed_items_count = user_data["UnplayedItemCount"]
item_details.location_type = item.get("LocationType")
item_details.recursive_item_count = item.get("RecursiveItemCount")
item_details.recursive_unplayed_items_count = user_data.get("UnplayedItemCount")
item_details.mode = "GET_CONTENT"
@@ -497,7 +496,7 @@ def add_gui_item(url, item_details, display_options, folder=True, default_sort=F
info_labels["rating"] = item_details.rating
info_labels["year"] = item_details.year
if item_details.genres is not None and len(item_details.genres) > 0:
if item_details.genres:
genres_list = []
for genre in item_details.genres:
genres_list.append(urllib.quote(genre.encode('utf8')))

View File

@@ -1,3 +1,5 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import json
import xbmc
@@ -9,7 +11,7 @@ class JsonRpc(object):
params = None
def __init__(self, method, **kwargs):
self.method = method
for arg in kwargs: # id_(int), jsonrpc(str)
@@ -18,7 +20,7 @@ class JsonRpc(object):
def _query(self):
query = {
'jsonrpc': self.jsonrpc,
'id': self.id_,
'method': self.method,

View File

@@ -1,3 +1,5 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmc
import xbmcgui
import xbmcplugin
@@ -6,9 +8,9 @@ import xbmcaddon
import sys
import json
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
addon = xbmcaddon.Addon()
@@ -59,9 +61,9 @@ def get_kodi_version():
result = result.get("result")
version_data = result.get("version")
version = float(str(version_data.get("major")) + "." + str(version_data.get("minor")))
log.debug("Version: {0} - {1}", version, version_data)
log.debug("Version: {0} - {1}".format(version, version_data))
except:
version = 0.0
log.error("Version Error : RAW Version Data: {0}", result)
log.error("Version Error : RAW Version Data: {0}".format(result))
return version

View File

@@ -1,13 +1,15 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import threading
import time
import xbmc
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .widgets import check_for_new_content
from .tracking import timer
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class LibraryChangeMonitor(threading.Thread):

141
resources/lib/loghandler.py Normal file
View File

@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
##################################################################################################
import os
import logging
import sys
import traceback
from six import ensure_text
from kodi_six import xbmc, xbmcaddon
from urlparse import urlparse
##################################################################################################
__addon__ = xbmcaddon.Addon(id='plugin.video.jellycon')
__pluginpath__ = xbmc.translatePath(__addon__.getAddonInfo('path'))
##################################################################################################
def getLogger(name=None):
if name is None:
return __LOGGER
return __LOGGER.getChild(name)
class LogHandler(logging.StreamHandler):
def __init__(self):
logging.StreamHandler.__init__(self)
self.setFormatter(MyFormatter())
self.sensitive = {'Token': [], 'Server': []}
settings = xbmcaddon.Addon()
self.server = settings.getSetting('server_address')
self.debug = settings.getSetting('log_debug')
def emit(self, record):
if self._get_log_level(record.levelno):
string = self.format(record)
# Hide server URL in logs
string = string.replace(self.server or "{server}", "{jellyfin-server}")
xbmc.log(string, level=xbmc.LOGNOTICE)
def _get_log_level(self, level):
levels = {
logging.ERROR: 0,
logging.WARNING: 0,
logging.INFO: 1,
logging.DEBUG: 2
}
if self.debug == 'true':
log_level = 2
else:
log_level = 1
return log_level >= levels[level]
class MyFormatter(logging.Formatter):
def __init__(self, fmt='%(name)s -> %(levelname)s::%(relpath)s:%(lineno)s %(message)s'):
logging.Formatter.__init__(self, fmt)
def format(self, record):
if record.pathname:
record.pathname = ensure_text(record.pathname, get_filesystem_encoding())
self._gen_rel_path(record)
# Call the original formatter class to do the grunt work
result = logging.Formatter.format(self, record)
return result
def formatException(self, exc_info):
_pluginpath_real = os.path.realpath(__pluginpath__)
res = []
for o in traceback.format_exception(*exc_info):
o = ensure_text(o, get_filesystem_encoding())
if o.startswith(' File "'):
# If this split can't handle your file names, you should seriously consider renaming your files.
fn = o.split(' File "', 2)[1].split('", line ', 1)[0]
rfn = os.path.realpath(fn)
if rfn.startswith(_pluginpath_real):
o = o.replace(fn, os.path.relpath(rfn, _pluginpath_real))
res.append(o)
return ''.join(res)
def _gen_rel_path(self, record):
if record.pathname:
record.relpath = os.path.relpath(record.pathname, __pluginpath__)
class LazyLogger(object):
"""`helper.loghandler.getLogger()` is used everywhere.
This class helps avoiding import errors.
"""
__logger = None
__logger_name = None
def __init__(self, logger_name=None):
self.__logger_name = logger_name
def __getattr__(self, name):
if self.__logger is None:
self.__logger = getLogger(self.__logger_name)
return getattr(self.__logger, name)
def get_filesystem_encoding():
enc = sys.getfilesystemencoding()
if not enc:
enc = sys.getdefaultencoding()
if not enc or enc == 'ascii':
enc = 'utf-8'
return enc
__LOGGER = logging.getLogger('JELLYFIN')
for handler in __LOGGER.handlers:
__LOGGER.removeHandler(handler)
__LOGGER.addHandler(LogHandler())
__LOGGER.setLevel(logging.DEBUG)

View File

@@ -1,29 +1,30 @@
# coding=utf-8
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import sys
import json
import urllib
import base64
import string
import xbmcplugin
import xbmcaddon
from .downloadutils import DownloadUtils
from .kodi_utils import add_menu_directory_item, HomeWindow
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .translation import string_load
from .datamanager import DataManager
from .utils import get_art, get_jellyfin_url
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
downloadUtils = DownloadUtils()
__addon__ = xbmcaddon.Addon()
def show_movie_tags(menu_params):
log.debug("show_movie_tags: {0}", menu_params)
log.debug("show_movie_tags: {0}".format(menu_params))
parent_id = menu_params.get("parent_id")
url_params = {}
@@ -50,7 +51,7 @@ def show_movie_tags(menu_params):
tags = result.get("Items")
log.debug("Tags : {0}", result)
log.debug("Tags : {0}".format(result))
for tag in tags:
name = tag["Name"]
@@ -80,14 +81,14 @@ def show_movie_tags(menu_params):
content_url +
"&mode=GET_CONTENT" +
"&media_type=movies")
log.debug("addMenuDirectoryItem: {0} - {1}", name, url)
log.debug("addMenuDirectoryItem: {0} - {1}".format(name, url))
add_menu_directory_item(name, url, art=art)
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def show_movie_years(menu_params):
log.debug("show_movie_years: {0}", menu_params)
log.debug("show_movie_years: {0}".format(menu_params))
parent_id = menu_params.get("parent_id")
group_into_decades = menu_params.get("group") == "true"
@@ -166,14 +167,14 @@ def show_movie_years(menu_params):
content_url +
"&mode=GET_CONTENT" +
"&media_type=movies")
log.debug("addMenuDirectoryItem: {0} - {1}", name, url)
log.debug("addMenuDirectoryItem: {0} - {1}".format(name, url))
add_menu_directory_item(name, url, art=art)
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def show_movie_pages(menu_params):
log.debug("showMoviePages: {0}", menu_params)
log.debug("showMoviePages: {0}".format(menu_params))
parent_id = menu_params.get("parent_id")
settings = xbmcaddon.Addon()
@@ -199,7 +200,7 @@ def show_movie_pages(menu_params):
return
total_results = result.get("TotalRecordCount", 0)
log.debug("showMoviePages TotalRecordCount {0}", total_results)
log.debug("showMoviePages TotalRecordCount {0}".format(total_results))
if result == 0:
return
@@ -250,14 +251,14 @@ def show_movie_pages(menu_params):
url = sys.argv[0] + ("?url=" + content_url +
"&mode=GET_CONTENT" +
"&media_type=" + collection["media_type"])
log.debug("addMenuDirectoryItem: {0} - {1} - {2}", collection.get('title'), url, collection.get("art"))
log.debug("addMenuDirectoryItem: {0} - {1} - {2}".format(collection.get('title'), url, collection.get("art")))
add_menu_directory_item(collection.get('title', string_load(30250)), url, art=collection.get("art"))
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def show_genre_list(menu_params):
log.debug("showGenreList: {0}", menu_params)
log.debug("showGenreList: {0}".format(menu_params))
server = downloadUtils.get_server()
if server is None:
@@ -331,7 +332,7 @@ def show_genre_list(menu_params):
url = sys.argv[0] + ("?url=" + urllib.quote(collection['path']) +
"&mode=GET_CONTENT" +
"&media_type=" + collection["media_type"])
log.debug("addMenuDirectoryItem: {0} - {1} - {2}", collection.get('title'), url, collection.get("art"))
log.debug("addMenuDirectoryItem: {0} - {1} - {2}".format(collection.get('title'), url, collection.get("art")))
add_menu_directory_item(collection.get('title', string_load(30250)), url, art=collection.get("art"))
xbmcplugin.endOfDirectory(int(sys.argv[1]))
@@ -360,22 +361,12 @@ def show_movie_alpha_list(menu_params):
if parent_id is not None:
url_params["ParentId"] = parent_id
prefix_url = get_jellyfin_url("{server}/Items/Prefixes", url_params)
data_manager = DataManager()
result = data_manager.get_content(prefix_url)
if not result:
return
alpha_list = []
for prefix in result:
alpha_list.append(prefix.get("Name"))
prefixes = '#' + string.ascii_uppercase
collections = []
for alphaName in alpha_list:
for alpha_name in prefixes:
item_data = {}
item_data['title'] = alphaName
item_data['title'] = alpha_name
item_data['media_type'] = "Movies"
params = {}
@@ -391,10 +382,10 @@ def show_movie_alpha_list(menu_params):
if parent_id is not None:
params["ParentId"] = parent_id
if alphaName == "#":
if alpha_name == "#":
params["NameLessThan"] = "A"
else:
params["NameStartsWith"] = alphaName
params["NameStartsWith"] = alpha_name
url = get_jellyfin_url("{server}/Users/{userid}/Items", params)
item_data['path'] = url
@@ -407,7 +398,7 @@ def show_movie_alpha_list(menu_params):
for collection in collections:
url = (sys.argv[0] + "?url=" + urllib.quote(collection['path']) +
"&mode=GET_CONTENT&media_type=" + collection["media_type"])
log.debug("addMenuDirectoryItem: {0} ({1})", collection.get('title'), url)
log.debug("addMenuDirectoryItem: {0} ({1})".format(collection.get('title'), url))
add_menu_directory_item(collection.get('title', string_load(30250)), url, art=collection.get("art"))
xbmcplugin.endOfDirectory(int(sys.argv[1]))
@@ -430,20 +421,11 @@ def show_tvshow_alpha_list(menu_params):
url_params["SortOrder"] = "Ascending"
if parent_id is not None:
menu_params["ParentId"] = parent_id
prefix_url = get_jellyfin_url("{server}/Items/Prefixes", url_params)
data_manager = DataManager()
result = data_manager.get_content(prefix_url)
if not result:
return
alpha_list = []
for prefix in result:
alpha_list.append(prefix.get("Name"))
prefixes = '#' + string.ascii_uppercase
collections = []
for alpha_name in alpha_list:
for alpha_name in prefixes:
item_data = {}
item_data['title'] = alpha_name
item_data['media_type'] = "tvshows"
@@ -477,7 +459,7 @@ def show_tvshow_alpha_list(menu_params):
for collection in collections:
url = (sys.argv[0] + "?url=" + urllib.quote(collection['path']) +
"&mode=GET_CONTENT&media_type=" + collection["media_type"])
log.debug("addMenuDirectoryItem: {0} ({1})", collection.get('title'), url)
log.debug("addMenuDirectoryItem: {0} ({1})".format(collection.get('title'), url))
add_menu_directory_item(collection.get('title', string_load(30250)), url, art=collection.get("art"))
xbmcplugin.endOfDirectory(int(sys.argv[1]))
@@ -1053,7 +1035,7 @@ def display_library_view(params):
data_manager = DataManager()
view_info = data_manager.get_content(view_info_url)
log.debug("VIEW_INFO : {0}", view_info)
log.debug("VIEW_INFO : {0}".format(view_info))
collection_type = view_info.get("CollectionType", None)
@@ -1111,7 +1093,7 @@ def show_search():
def set_library_window_values(force=False):
log.debug("set_library_window_values Called forced={0}", force)
log.debug("set_library_window_values Called forced={0}".format(force))
home_window = HomeWindow()
already_set = home_window.get_property("view_item.0.name")
@@ -1145,19 +1127,19 @@ def set_library_window_values(force=False):
# plugin.video.jellycon-
prop_name = "view_item.%i.name" % index
home_window.set_property(prop_name, name)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}", prop_name, name)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}".format(prop_name, name))
prop_name = "view_item.%i.id" % index
home_window.set_property(prop_name, item_id)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}", prop_name, item_id)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}".format(prop_name, item_id))
prop_name = "view_item.%i.type" % index
home_window.set_property(prop_name, collection_type)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}", prop_name, collection_type)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}".format(prop_name, collection_type))
thumb = downloadUtils.get_artwork(item, "Primary", server=server)
prop_name = "view_item.%i.thumb" % index
home_window.set_property(prop_name, thumb)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}", prop_name, thumb)
log.debug("set_library_window_values: plugin.video.jellycon-{0}={1}".format(prop_name, thumb))
index += 1

View File

@@ -1,10 +1,12 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmc
import xbmcaddon
import xbmcgui
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class PictureViewer(xbmcgui.WindowXMLDialog):
picture_url = None

View File

@@ -9,7 +9,7 @@ import json
import os
import base64
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .downloadutils import DownloadUtils
from .resume_dialog import ResumeDialog
from .utils import PlayUtils, get_art, send_event_notification, convert_size
@@ -24,7 +24,7 @@ from .picture_viewer import PictureViewer
from .tracking import timer
from .playnext import PlayNextDialog
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
download_utils = DownloadUtils()
@@ -60,7 +60,7 @@ def play_all_files(items, monitor, play_items=True):
source_id = selected_media_source.get("Id")
playurl, playback_type, listitem_props = PlayUtils().get_play_url(selected_media_source, play_session_id)
log.info("Play URL: {0} PlaybackType: {1} ListItem Properties: {2}", playurl, playback_type, listitem_props)
log.info("Play URL: {0} PlaybackType: {1} ListItem Properties: {2}".format(playurl, playback_type, listitem_props))
if playurl is None:
return
@@ -89,7 +89,7 @@ def play_all_files(items, monitor, play_items=True):
data["play_session_id"] = play_session_id
data["play_action_type"] = "play_all"
monitor.played_information[playurl] = data
log.debug("Add to played_information: {0}", monitor.played_information)
log.debug("Add to played_information: {0}".format(monitor.played_information))
list_item.setPath(playurl)
list_item = set_list_item_props(item_id, list_item, item, server, listitem_props, item_title)
@@ -121,7 +121,7 @@ def play_list_of_items(id_list, monitor):
def add_to_playlist(play_info, monitor):
log.debug("Adding item to playlist : {0}", play_info)
log.debug("Adding item to playlist : {0}".format(play_info))
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
server = download_utils.get_server()
@@ -159,7 +159,7 @@ def add_to_playlist(play_info, monitor):
source_id = selected_media_source.get("Id")
playurl, playback_type, listitem_props = PlayUtils().get_play_url(selected_media_source, play_session_id)
log.info("Play URL: {0} PlaybackType: {1} ListItem Properties: {2}", playurl, playback_type, listitem_props)
log.info("Play URL: {0} PlaybackType: {1} ListItem Properties: {2}".format(playurl, playback_type, listitem_props))
if playurl is None:
return
@@ -188,7 +188,7 @@ def add_to_playlist(play_info, monitor):
data["play_session_id"] = play_session_id
data["play_action_type"] = "play_all"
monitor.played_information[playurl] = data
log.debug("Add to played_information: {0}", monitor.played_information)
log.debug("Add to played_information: {0}".format(monitor.played_information))
list_item.setPath(playurl)
list_item = set_list_item_props(item_id, list_item, item, server, listitem_props, item_title)
@@ -238,7 +238,7 @@ def play_file(play_info, monitor):
subtitle_stream_index = play_info.get("subtitle_stream_index", None)
audio_stream_index = play_info.get("audio_stream_index", None)
log.debug("playFile id({0}) resume({1}) force_transcode({2})", item_id, auto_resume, force_transcode)
log.debug("playFile id({0}) resume({1}) force_transcode({2})".format(item_id, auto_resume, force_transcode))
settings = xbmcaddon.Addon()
addon_path = settings.getAddonInfo('path')
@@ -251,7 +251,7 @@ def play_file(play_info, monitor):
url = "{server}/Users/{userid}/Items/%s?format=json" % (item_id,)
data_manager = DataManager()
result = data_manager.get_content(url)
log.debug("Playfile item: {0}", result)
log.debug("Playfile item: {0}".format(result))
if result is None:
log.debug("Playfile item was None, so can not play!")
@@ -259,14 +259,14 @@ def play_file(play_info, monitor):
# if this is a season, playlist or album then play all items in that parent
if result.get("Type") in ["Season", "MusicAlbum", "Playlist"]:
log.debug("PlayAllFiles for parent item id: {0}", item_id)
log.debug("PlayAllFiles for parent item id: {0}".format(item_id))
url = ('{server}/Users/{userid}/items' +
'?ParentId=%s' +
'&Fields=MediaSources' +
'&format=json')
url = url % (item_id,)
result = data_manager.get_content(url)
log.debug("PlayAllFiles items: {0}", result)
log.debug("PlayAllFiles items: {0}".format(result))
# process each item
items = result["Items"]
@@ -366,7 +366,7 @@ def play_file(play_info, monitor):
resume_dialog.doModal()
resume_result = resume_dialog.getResumeAction()
del resume_dialog
log.debug("Resume Dialog Result: {0}", resume_result)
log.debug("Resume Dialog Result: {0}".format(resume_result))
# check system settings for play action
# if prompt is set ask to set it to auto resume
@@ -389,9 +389,9 @@ def play_file(play_info, monitor):
elif resume_result == -1:
return
log.debug("play_session_id: {0}", play_session_id)
log.debug("play_session_id: {0}".format(play_session_id))
playurl, playback_type, listitem_props = PlayUtils().get_play_url(selected_media_source, play_session_id)
log.info("Play URL: {0} Playback Type: {1} ListItem Properties: {2}", playurl, playback_type, listitem_props)
log.info("Play URL: {0} Playback Type: {1} ListItem Properties: {2}".format(playurl, playback_type, listitem_props))
if playurl is None:
return
@@ -431,7 +431,7 @@ def play_file(play_info, monitor):
if playback_type == "2": # if transcoding then prompt for audio and subtitle
playurl = audio_subs_pref(playurl, list_item, selected_media_source, item_id, audio_stream_index,
subtitle_stream_index)
log.debug("New playurl for transcoding: {0}", playurl)
log.debug("New playurl for transcoding: {0}".format(playurl))
elif playback_type == "1": # for direct stream add any streamable subtitles
external_subs(selected_media_source, list_item, item_id)
@@ -446,7 +446,7 @@ def play_file(play_info, monitor):
data["item_type"] = result.get("Type", None)
data["can_delete"] = result.get("CanDelete", False)
monitor.played_information[playurl] = data
log.debug("Add to played_information: {0}", monitor.played_information)
log.debug("Add to played_information: {0}".format(monitor.played_information))
list_item.setPath(playurl)
list_item = set_list_item_props(item_id, list_item, result, server, listitem_props, item_title)
@@ -488,12 +488,12 @@ def play_file(play_info, monitor):
count = 0
max_loops = 2 * 120
while not monitor.abortRequested() and player.isPlaying() and count < max_loops:
log.info("PlaybackResumrAction : Seeking to : {0}", seek_to_time)
log.info("PlaybackResumrAction : Seeking to : {0}".format(seek_to_time))
player.seekTime(seek_to_time)
current_position = player.getTime()
if current_position >= target_seek:
break
log.info("PlaybackResumrAction : target:{0} current:{1}", target_seek, current_position)
log.info("PlaybackResumrAction : target:{0} current:{1}".format(target_seek, current_position))
count = count + 1
xbmc.sleep(500)
@@ -571,7 +571,7 @@ def get_next_episode(item):
data_manager = DataManager()
items_result = data_manager.get_content(url)
log.debug("get_next_episode, sibling list: {0}", items_result)
log.debug("get_next_episode, sibling list: {0}".format(items_result))
if items_result is None:
log.debug("get_next_episode no results")
@@ -583,7 +583,7 @@ def get_next_episode(item):
index = item.get("IndexNumber", -1)
# find the very next episode in the season
if index == item_index + 1:
log.debug("get_next_episode, found next episode: {0}", item)
log.debug("get_next_episode, found next episode: {0}".format(item))
return item
return None
@@ -795,7 +795,7 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
if select_subs_index in downloadable_streams:
subtitle_url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
subtitle_url = subtitle_url % (download_utils.get_server(), item_id, source_id, select_subs_index)
log.debug("Streaming subtitles url: {0} {1}", select_subs_index, subtitle_url)
log.debug("Streaming subtitles url: {0} {1}".format(select_subs_index, subtitle_url))
list_item.setSubtitles([subtitle_url])
else:
# Burn subtitles
@@ -815,7 +815,7 @@ def audio_subs_pref(url, list_item, media_source, item_id, audio_stream_index, s
if select_subs_index in downloadable_streams:
subtitle_url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
subtitle_url = subtitle_url % (download_utils.get_server(), item_id, source_id, select_subs_index)
log.debug("Streaming subtitles url: {0} {1}", select_subs_index, subtitle_url)
log.debug("Streaming subtitles url: {0} {1}".format(select_subs_index, subtitle_url))
list_item.setSubtitles([subtitle_url])
else:
# Burn subtitles
@@ -853,12 +853,15 @@ def external_subs(media_source, list_item, item_id):
source_id = media_source['Id']
server = download_utils.get_server()
token = download_utils.authenticate()
language = stream.get('Language', '')
codec = stream.get('Codec', '')
if stream.get('DeliveryUrl', '').lower().startswith('/videos'):
url = "%s%s" % (server, stream.get('DeliveryUrl'))
url_root = '{}/Videos/{}/{}/Subtitles/{}'.format(server, item_id, source_id, index)
if language:
url = '{}/Stream.{}.{}?api_key={}'.format(
url_root, language, codec, token)
else:
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s"
% (server, item_id, source_id, index, stream['Codec'], token))
url = '{}/Stream.{}?api_key={}'.format(url_root, codec, token)
default = ""
if stream['IsDefault']:
@@ -867,7 +870,7 @@ def external_subs(media_source, list_item, item_id):
if stream['IsForced']:
forced = "forced"
sub_name = stream.get('Language', "n/a") + " (" + stream.get('Codec', "n/a") + ") " + default + " " + forced
sub_name = '{} ( {} ) {} {}'.format(language, codec, default, forced)
sub_names.append(sub_name)
externalsubs.append(url)
@@ -884,7 +887,7 @@ def external_subs(media_source, list_item, item_id):
resp = xbmcgui.Dialog().select(string_load(30292), sub_names)
if resp > -1:
selected_sub = externalsubs[resp]
log.debug("External Subtitle Selected: {0}", selected_sub)
log.debug("External Subtitle Selected: {0}".format(selected_sub))
list_item.setSubtitles([selected_sub])
@@ -937,7 +940,7 @@ def send_progress(monitor):
'VolumeLevel': volume
}
log.debug("Sending POST progress started: {0}", postdata)
log.debug("Sending POST progress started: {0}".format(postdata))
url = "{server}/Sessions/Playing/Progress"
download_utils.download_url(url, post_body=postdata, method="POST")
@@ -955,7 +958,7 @@ def get_volume():
def prompt_for_stop_actions(item_id, data):
log.debug("prompt_for_stop_actions Called : {0}", data)
log.debug("prompt_for_stop_actions Called : {0}".format(data))
settings = xbmcaddon.Addon()
current_position = data.get("currentPossition", 0)
@@ -986,7 +989,7 @@ def prompt_for_stop_actions(item_id, data):
# item percentage complete
# percenatge_complete = int(((current_position * 10000000) / runtime) * 100)
percenatge_complete = int((current_position / duration) * 100)
log.debug("Episode Percentage Complete: {0}", percenatge_complete)
log.debug("Episode Percentage Complete: {0}".format(percenatge_complete))
if (can_delete and
prompt_delete_episode_percentage < 100 and
@@ -1047,12 +1050,12 @@ def prompt_for_stop_actions(item_id, data):
def stop_all_playback(played_information):
log.debug("stop_all_playback : {0}", played_information)
log.debug("stop_all_playback : {0}".format(played_information))
if len(played_information) == 0:
return
log.debug("played_information: {0}", played_information)
log.debug("played_information: {0}".format(played_information))
home_screen = HomeWindow()
home_screen.clear_property("currently_playing_id")
@@ -1060,8 +1063,8 @@ def stop_all_playback(played_information):
for item_url in played_information:
data = played_information.get(item_url)
if data.get("currently_playing", False) is True:
log.debug("item_url: {0}", item_url)
log.debug("item_data: {0}", data)
log.debug("item_url: {0}".format(item_url))
log.debug("item_data: {0}".format(data))
current_position = data.get("currentPossition", 0)
duration = data.get("duration", 0)
@@ -1070,7 +1073,7 @@ def stop_all_playback(played_information):
play_session_id = data.get("play_session_id")
if jellyfin_item_id is not None and current_position >= 0:
log.debug("Playback Stopped at: {0}", current_position)
log.debug("Playback Stopped at: {0}".format(current_position))
url = "{server}/Sessions/Playing/Stopped"
postdata = {
@@ -1086,21 +1089,22 @@ def stop_all_playback(played_information):
if data.get("play_action_type", "") == "play":
prompt_for_stop_actions(jellyfin_item_id, data)
device_id = ClientInformation().get_device_id()
url = "{server}/Videos/ActiveEncodings?DeviceId=%s" % device_id
download_utils.download_url(url, method="DELETE")
if data.get('playback_type') == 'Transcode':
device_id = ClientInformation().get_device_id()
url = "{server}/Videos/ActiveEncodings?DeviceId=%s" % device_id
download_utils.download_url(url, method="DELETE")
def get_playing_data(play_data_map):
try:
playing_file = xbmc.Player().getPlayingFile()
except Exception as e:
log.error("get_playing_data : getPlayingFile() : {0}", e)
log.error("get_playing_data : getPlayingFile() : {0}".format(e))
return None
log.debug("get_playing_data : getPlayingFile() : {0}", playing_file)
log.debug("get_playing_data : getPlayingFile() : {0}".format(playing_file))
if playing_file not in play_data_map:
infolabel_path_and_file = xbmc.getInfoLabel("Player.Filenameandpath")
log.debug("get_playing_data : Filenameandpath : {0}", infolabel_path_and_file)
log.debug("get_playing_data : Filenameandpath : {0}".format(infolabel_path_and_file))
if infolabel_path_and_file not in play_data_map:
log.debug("get_playing_data : play data not found")
return None
@@ -1113,7 +1117,7 @@ def get_playing_data(play_data_map):
class Service(xbmc.Player):
def __init__(self, *args):
log.debug("Starting monitor service: {0}", args)
log.debug("Starting monitor service: {0}".format(args))
self.played_information = {}
def onPlayBackStarted(self):
@@ -1151,7 +1155,7 @@ class Service(xbmc.Player):
'PlaySessionId': play_session_id
}
log.debug("Sending POST play started: {0}", postdata)
log.debug("Sending POST play started: {0}".format(postdata))
url = "{server}/Sessions/Playing"
download_utils.download_url(url, post_body=postdata, method="POST")
@@ -1202,8 +1206,6 @@ class PlaybackService(xbmc.Monitor):
self.monitor = monitor
def onNotification(self, sender, method, data):
log.debug("PlaybackService:onNotification:{0}:{1}:{2}", sender, method, data)
if method == 'GUI.OnScreensaverActivated':
self.screensaver_activated()
return
@@ -1221,20 +1223,20 @@ class PlaybackService(xbmc.Monitor):
data_json = json.loads(data)
message_data = data_json[0]
log.debug("PlaybackService:onNotification:{0}", message_data)
log.debug("PlaybackService:onNotification:{0}".format(message_data))
decoded_data = base64.b64decode(message_data)
play_info = json.loads(decoded_data)
if signal == "jellycon_play_action":
log.info("Received jellycon_play_action : {0}", play_info)
log.info("Received jellycon_play_action : {0}".format(play_info))
play_file(play_info, self.monitor)
elif signal == "jellycon_play_youtube_trailer_action":
log.info("Received jellycon_play_trailer_action : {0}", play_info)
log.info("Received jellycon_play_trailer_action : {0}".format(play_info))
trailer_link = play_info["url"]
xbmc.executebuiltin(trailer_link)
elif signal == "set_view":
view_id = play_info["view_id"]
log.debug("Setting view id: {0}", view_id)
log.debug("Setting view id: {0}".format(view_id))
xbmc.executebuiltin("Container.SetViewMode(%s)" % int(view_id))
def screensaver_activated(self):

View File

@@ -1,3 +1,5 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import os
import threading
@@ -5,10 +7,10 @@ import xbmc
import xbmcgui
import xbmcaddon
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .play_utils import send_event_notification
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class PlayNextService(threading.Thread):
@@ -38,7 +40,7 @@ class PlayNextService(threading.Thread):
if not is_playing:
settings = xbmcaddon.Addon()
play_next_trigger_time = int(settings.getSetting('play_next_trigger_time'))
log.debug("New play_next_trigger_time value: {0}", play_next_trigger_time)
log.debug("New play_next_trigger_time value: {0}".format(play_next_trigger_time))
duration = player.getTotalTime()
position = player.getTime()
@@ -47,10 +49,10 @@ class PlayNextService(threading.Thread):
if not play_next_triggered and (trigger_time > time_to_end) and play_next_dialog is None:
play_next_triggered = True
log.debug("play_next_triggered hit at {0} seconds from end", time_to_end)
log.debug("play_next_triggered hit at {0} seconds from end".format(time_to_end))
play_data = get_playing_data(self.monitor.played_information)
log.debug("play_next_triggered play_data : {0}", play_data)
log.debug("play_next_triggered play_data : {0}".format(play_data))
next_episode = play_data.get("next_episode")
item_type = play_data.get("item_type")
@@ -116,7 +118,7 @@ class PlayNextDialog(xbmcgui.WindowXMLDialog):
pass
def onMessage(self, message):
log.debug("PlayNextDialog: onMessage: {0}", message)
log.debug("PlayNextDialog: onMessage: {0}".format(message))
def onAction(self, action):
@@ -125,7 +127,7 @@ class PlayNextDialog(xbmcgui.WindowXMLDialog):
elif action.getId() == 92: # ACTION_NAV_BACK
self.close()
else:
log.debug("PlayNextDialog: onAction: {0}", action.getId())
log.debug("PlayNextDialog: onAction: {0}".format(action.getId()))
def onClick(self, control_id):
if control_id == 3013:
@@ -133,7 +135,7 @@ class PlayNextDialog(xbmcgui.WindowXMLDialog):
self.play_called
self.close()
next_item_id = self.episode_info.get("Id")
log.debug("Playing Next Episode: {0}", next_item_id)
log.debug("Playing Next Episode: {0}".format(next_item_id))
play_info = {}
play_info["item_id"] = next_item_id
play_info["auto_resume"] = "-1"

View File

@@ -1,11 +1,12 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmcgui
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .translation import string_load
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class ResumeDialog(xbmcgui.WindowXMLDialog):

View File

@@ -1,11 +1,12 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmc
import xbmcgui
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class SafeDeleteDialog(xbmcgui.WindowXMLDialog):
@@ -36,7 +37,7 @@ class SafeDeleteDialog(xbmcgui.WindowXMLDialog):
pass
def onMessage(self, message):
log.debug("SafeDeleteDialog: onMessage: {0}", message)
log.debug("SafeDeleteDialog: onMessage: {0}".format(message))
def onAction(self, action):
@@ -45,7 +46,7 @@ class SafeDeleteDialog(xbmcgui.WindowXMLDialog):
elif action.getId() == 92: # ACTION_NAV_BACK
self.close()
else:
log.debug("SafeDeleteDialog: onAction: {0}", action.getId())
log.debug("SafeDeleteDialog: onAction: {0}".format(action.getId()))
def onClick(self, controlID):
if controlID == 1:

View File

@@ -1,9 +1,10 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import socket
import json
from urlparse import urlparse
import httplib
import requests
import ssl
import time
import hashlib
@@ -12,15 +13,16 @@ from datetime import datetime
import xbmcaddon
import xbmcgui
import xbmc
from kodi_six.utils import py2_decode
from .kodi_utils import HomeWindow
from .downloadutils import DownloadUtils, save_user_details, load_user_details
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .translation import string_load
from .utils import datetime_from_string
from .clientinfo import ClientInformation
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
__addon__ = xbmcaddon.Addon()
__addon_name__ = __addon__.getAddonInfo('name')
@@ -40,66 +42,45 @@ def check_connection_speed():
url = server + "/playback/bitratetest?size=%s" % test_data_size
url_bits = urlparse(url.strip())
protocol = url_bits.scheme
host_name = url_bits.hostname
port = url_bits.port
# user_name = url_bits.username
# user_password = url_bits.password
url_path = url_bits.path
url_puery = url_bits.query
server = "%s:%s" % (host_name, port)
url_path = url_path + "?" + url_puery
local_use_https = False
if protocol.lower() == "https":
local_use_https = True
if local_use_https and verify_cert:
log.debug("Connection: HTTPS, Cert checked")
conn = httplib.HTTPSConnection(server, timeout=http_timeout)
elif local_use_https and not verify_cert:
log.debug("Connection: HTTPS, Cert NOT checked")
conn = httplib.HTTPSConnection(server, timeout=http_timeout, context=ssl._create_unverified_context())
else:
log.debug("Connection: HTTP")
conn = httplib.HTTPConnection(server, timeout=http_timeout)
head = du.get_auth_header(True)
head["User-Agent"] = "JellyCon-" + ClientInformation().get_version()
conn.request(method="GET", url=url_path, headers=head)
request_details = {
"stream": True,
"headers": head
}
if not verify_cert:
request_details["verify"] = False
progress_dialog = xbmcgui.DialogProgress()
message = 'Testing with {0} MB of data'.format(speed_test_data_size)
progress_dialog.create("JellyCon connection speed test", message)
total_data_read = 0
total_time = time.time()
start_time = time.time()
log.debug("Starting Connection Speed Test")
response = conn.getresponse()
response = requests.get(url, **request_details)
last_percentage_done = 0
if int(response.status) == 200:
data = response.read(10240)
while len(data) > 0:
total_data_read = 0
if response.status_code == 200:
for data in response.iter_content(chunk_size=10240):
total_data_read += len(data)
percentage_done = int(float(total_data_read) / float(test_data_size) * 100.0)
if last_percentage_done != percentage_done:
progress_dialog.update(percentage_done)
last_percentage_done = percentage_done
data = response.read(10240)
else:
log.error("HTTP response error: {0} {1}", response.status, response.reason)
error_message = "HTTP response error: %s\n%s" % (response.status, response.reason)
log.error("HTTP response error: {0} {1}".format(response.status_code, response.content))
error_message = "HTTP response error: %s\n%s" % (response.status_code, response.content)
xbmcgui.Dialog().ok("Speed Test Error", error_message)
return -1
total_data_read_kbits = (total_data_read * 8) / 1000
total_time = time.time() - total_time
total_time = time.time() - start_time
speed = int(total_data_read_kbits / total_time)
log.debug("Finished Connection Speed Test, speed: {0} total_data: {1}, total_time: {2}", speed, total_data_read, total_time)
log.debug("Finished Connection Speed Test, speed: {0} total_data: {1}, total_time: {2}".format(speed, total_data_read, total_time))
progress_dialog.close()
del progress_dialog
@@ -119,10 +100,9 @@ def check_safe_delete_available():
log.debug("check_safe_delete_available")
du = DownloadUtils()
json_data = du.download_url("{server}/Plugins")
result = json.loads(json_data)
if result is not None:
log.debug("Server Plugin List: {0}", result)
result = du.download_url("{server}/Plugins")
if result:
log.debug("Server Plugin List: {0}".format(result))
safe_delete_found = False
for plugin in result:
@@ -130,7 +110,7 @@ def check_safe_delete_available():
safe_delete_found = True
break
log.debug("Safe Delete Plugin Available: {0}", safe_delete_found)
log.debug("Safe Delete Plugin Available: {0}".format(safe_delete_found))
home_window = HomeWindow()
if safe_delete_found:
home_window.set_property("safe_delete_plugin_available", "true")
@@ -157,8 +137,8 @@ def get_server_details():
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
log.debug("MutliGroup: {0}", multi_group)
log.debug("Sending UDP Data: {0}", message)
log.debug("MutliGroup: {0}".format(multi_group))
log.debug("Sending UDP Data: {0}".format(message))
progress = xbmcgui.DialogProgress()
progress.create(__addon_name__ + " : " + string_load(30373))
@@ -179,11 +159,11 @@ def get_server_details():
except:
break
except Exception as e:
log.error("UPD Discovery Error: {0}", e)
log.error("UPD Discovery Error: {0}".format(e))
progress.close()
log.debug("Found Servers: {0}", servers)
log.debug("Found Servers: {0}".format(servers))
return servers
@@ -247,29 +227,18 @@ def check_server(force=False, change_user=False, notify=False):
xbmc.executebuiltin("ActivateWindow(Home)")
return
url_bits = urlparse(server_url)
server_address = url_bits.hostname
server_port = str(url_bits.port)
server_protocol = url_bits.scheme
user_name = url_bits.username
user_password = url_bits.password
public_lookup_url = "%s/System/Info/Public?format=json" % (server_url)
if user_name and user_password:
temp_url = "%s://%s:%s@%s:%s/Users/Public?format=json" % (server_protocol, user_name, user_password, server_address, server_port)
else:
temp_url = "%s://%s:%s/Users/Public?format=json" % (server_protocol, server_address, server_port)
log.debug("Testing_Url: {0}", temp_url)
log.debug("Testing_Url: {0}".format(public_lookup_url))
progress = xbmcgui.DialogProgress()
progress.create(__addon_name__ + " : " + string_load(30376))
progress.update(0, string_load(30377))
json_data = du.download_url(temp_url, authenticate=False)
result = du.download_url(public_lookup_url, authenticate=False)
progress.close()
result = json.loads(json_data)
if result is not None:
if result:
xbmcgui.Dialog().ok(__addon_name__ + " : " + string_load(30167),
"%s://%s:%s/" % (server_protocol, server_address, server_port))
server_url)
break
else:
return_index = xbmcgui.Dialog().yesno(__addon_name__ + " : " + string_load(30135),
@@ -279,30 +248,8 @@ def check_server(force=False, change_user=False, notify=False):
xbmc.executebuiltin("ActivateWindow(Home)")
return
log.debug("Selected server: {0}", server_url)
# parse the url
url_bits = urlparse(server_url)
server_address = url_bits.hostname
server_port = str(url_bits.port)
server_protocol = url_bits.scheme
user_name = url_bits.username
user_password = url_bits.password
log.debug("Detected server info {0} - {1} - {2}", server_protocol, server_address, server_port)
# save the server info
settings.setSetting("port", server_port)
if user_name and user_password:
server_address = "%s:%s@%s" % (url_bits.username, url_bits.password, server_address)
settings.setSetting("ipaddress", server_address)
if server_protocol == "https":
settings.setSetting("protocol", "1")
else:
settings.setSetting("protocol", "0")
log.debug("Selected server: {0}".format(server_url))
settings.setSetting("server_address", server_url)
something_changed = True
# do we need to change the user
@@ -319,168 +266,158 @@ def check_server(force=False, change_user=False, notify=False):
# get a list of users
log.debug("Getting user list")
json_data = du.download_url(server_url + "/Users/Public?format=json", authenticate=False)
result = du.download_url(server_url + "/Users/Public?format=json", authenticate=False)
log.debug("jsonData: {0}", json_data)
try:
result = json.loads(json_data)
except:
result = None
log.debug("jsonData: {0}".format(py2_decode(result)))
if result is None:
xbmcgui.Dialog().ok(string_load(30135),
string_load(30201),
string_load(30169) + server_url)
selected_id = -1
users = []
for user in result:
config = user.get("Configuration")
if config is not None:
if config.get("IsHidden", False) is False:
name = user.get("Name")
admin = user.get("Policy", {}).get("IsAdministrator", False)
time_ago = ""
last_active = user.get("LastActivityDate")
if last_active:
last_active_date = datetime_from_string(last_active)
log.debug("LastActivityDate: {0}".format(last_active_date))
ago = datetime.now() - last_active_date
log.debug("LastActivityDate: {0}".format(ago))
days = divmod(ago.seconds, 86400)
hours = divmod(days[1], 3600)
minutes = divmod(hours[1], 60)
log.debug("LastActivityDate: {0} {1} {2}".format(days[0], hours[0], minutes[0]))
if days[0]:
time_ago += " %sd" % days[0]
if hours[0]:
time_ago += " %sh" % hours[0]
if minutes[0]:
time_ago += " %sm" % minutes[0]
time_ago = time_ago.strip()
if not time_ago:
time_ago = "Active: now"
else:
time_ago = "Active: %s ago" % time_ago
log.debug("LastActivityDate: {0}".format(time_ago))
user_item = xbmcgui.ListItem(name)
user_image = du.get_user_artwork(user, 'Primary')
if not user_image:
user_image = "DefaultUser.png"
art = {"Thumb": user_image}
user_item.setArt(art)
user_item.setLabel2("TEST")
sub_line = time_ago
if user.get("HasPassword", False) is True:
sub_line += ", Password"
user_item.setProperty("secure", "true")
m = hashlib.md5()
m.update(name)
hashed_username = m.hexdigest()
saved_password = settings.getSetting("saved_user_password_" + hashed_username)
if saved_password:
sub_line += ": Saved"
else:
user_item.setProperty("secure", "false")
if admin:
sub_line += ", Admin"
else:
sub_line += ", User"
user_item.setProperty("manual", "false")
user_item.setLabel2(sub_line)
users.append(user_item)
if current_username == name:
selected_id = len(users) - 1
if current_username:
selection_title = string_load(30180) + " (" + current_username + ")"
else:
selected_id = -1
users = []
for user in result:
config = user.get("Configuration")
if config is not None:
if config.get("IsHidden", False) is False:
name = user.get("Name")
admin = user.get("Policy", {}).get("IsAdministrator", False)
selection_title = string_load(30180)
time_ago = ""
last_active = user.get("LastActivityDate")
if last_active:
last_active_date = datetime_from_string(last_active)
log.debug("LastActivityDate: {0}", last_active_date)
ago = datetime.now() - last_active_date
log.debug("LastActivityDate: {0}", ago)
days = divmod(ago.seconds, 86400)
hours = divmod(days[1], 3600)
minutes = divmod(hours[1], 60)
log.debug("LastActivityDate: {0} {1} {2}", days[0], hours[0], minutes[0])
if days[0]:
time_ago += " %sd" % days[0]
if hours[0]:
time_ago += " %sh" % hours[0]
if minutes[0]:
time_ago += " %sm" % minutes[0]
time_ago = time_ago.strip()
if not time_ago:
time_ago = "Active: now"
else:
time_ago = "Active: %s ago" % time_ago
log.debug("LastActivityDate: {0}", time_ago)
# add manual login
user_item = xbmcgui.ListItem(string_load(30365))
art = {"Thumb": "DefaultUser.png"}
user_item.setArt(art)
user_item.setLabel2(string_load(30366))
user_item.setProperty("secure", "true")
user_item.setProperty("manual", "true")
users.append(user_item)
user_item = xbmcgui.ListItem(name)
user_image = du.get_user_artwork(user, 'Primary')
if not user_image:
user_image = "DefaultUser.png"
art = {"Thumb": user_image}
user_item.setArt(art)
user_item.setLabel2("TEST")
return_value = xbmcgui.Dialog().select(selection_title,
users,
preselect=selected_id,
autoclose=20000,
useDetails=True)
sub_line = time_ago
if return_value > -1 and return_value != selected_id:
if user.get("HasPassword", False) is True:
sub_line += ", Password"
user_item.setProperty("secure", "true")
something_changed = True
selected_user = users[return_value]
secured = selected_user.getProperty("secure") == "true"
manual = selected_user.getProperty("manual") == "true"
selected_user_name = selected_user.getLabel()
m = hashlib.md5()
m.update(name)
hashed_username = m.hexdigest()
saved_password = settings.getSetting("saved_user_password_" + hashed_username)
if saved_password:
sub_line += ": Saved"
log.debug("Selected User Name: {0} : {1}".format(return_value, selected_user_name))
else:
user_item.setProperty("secure", "false")
if manual:
kb = xbmc.Keyboard()
kb.setHeading(string_load(30005))
if current_username:
kb.setDefault(current_username)
kb.doModal()
if kb.isConfirmed():
selected_user_name = kb.getText()
log.debug("Manual entered username: {0}".format(selected_user_name))
else:
return
if admin:
sub_line += ", Admin"
else:
sub_line += ", User"
if secured:
# we need a password, check the settings first
m = hashlib.md5()
m.update(selected_user_name)
hashed_username = m.hexdigest()
saved_password = settings.getSetting("saved_user_password_" + hashed_username)
allow_password_saving = settings.getSetting("allow_password_saving") == "true"
user_item.setProperty("manual", "false")
user_item.setLabel2(sub_line)
users.append(user_item)
# if not saving passwords but have a saved ask to clear it
if not allow_password_saving and saved_password:
clear_password = xbmcgui.Dialog().yesno(string_load(30368), string_load(30369))
if clear_password:
settings.setSetting("saved_user_password_" + hashed_username, "")
if current_username == name:
selected_id = len(users) - 1
if saved_password:
log.debug("Saving username and password: {0}".format(selected_user_name))
log.debug("Using stored password for user: {0}".format(hashed_username))
save_user_details(settings, selected_user_name, saved_password)
if current_username:
selection_title = string_load(30180) + " (" + current_username + ")"
else:
selection_title = string_load(30180)
# add manual login
user_item = xbmcgui.ListItem(string_load(30365))
art = {"Thumb": "DefaultUser.png"}
user_item.setArt(art)
user_item.setLabel2(string_load(30366))
user_item.setProperty("secure", "true")
user_item.setProperty("manual", "true")
users.append(user_item)
return_value = xbmcgui.Dialog().select(selection_title,
users,
preselect=selected_id,
autoclose=20000,
useDetails=True)
if return_value > -1 and return_value != selected_id:
something_changed = True
selected_user = users[return_value]
secured = selected_user.getProperty("secure") == "true"
manual = selected_user.getProperty("manual") == "true"
selected_user_name = selected_user.getLabel()
log.debug("Selected User Name: {0} : {1}", return_value, selected_user_name)
if manual:
else:
kb = xbmc.Keyboard()
kb.setHeading(string_load(30005))
if current_username:
kb.setDefault(current_username)
kb.setHeading(string_load(30006))
kb.setHiddenInput(True)
kb.doModal()
if kb.isConfirmed():
selected_user_name = kb.getText()
log.debug("Manual entered username: {0}", selected_user_name)
else:
return
log.debug("Saving username and password: {0}".format(selected_user_name))
save_user_details(settings, selected_user_name, kb.getText())
if secured:
# we need a password, check the settings first
m = hashlib.md5()
m.update(selected_user_name)
hashed_username = m.hexdigest()
saved_password = settings.getSetting("saved_user_password_" + hashed_username)
allow_password_saving = settings.getSetting("allow_password_saving") == "true"
# if not saving passwords but have a saved ask to clear it
if not allow_password_saving and saved_password:
clear_password = xbmcgui.Dialog().yesno(string_load(30368), string_load(30369))
if clear_password:
settings.setSetting("saved_user_password_" + hashed_username, "")
if saved_password:
log.debug("Saving username and password: {0}", selected_user_name)
log.debug("Using stored password for user: {0}", hashed_username)
save_user_details(settings, selected_user_name, saved_password)
else:
kb = xbmc.Keyboard()
kb.setHeading(string_load(30006))
kb.setHiddenInput(True)
kb.doModal()
if kb.isConfirmed():
log.debug("Saving username and password: {0}", selected_user_name)
save_user_details(settings, selected_user_name, kb.getText())
# should we save the password
if allow_password_saving:
save_password = xbmcgui.Dialog().yesno(string_load(30363), string_load(30364))
if save_password:
log.debug("Saving password for fast user switching: {0}", hashed_username)
settings.setSetting("saved_user_password_" + hashed_username, kb.getText())
else:
log.debug("Saving username with no password: {0}", selected_user_name)
save_user_details(settings, selected_user_name, "")
# should we save the password
if allow_password_saving:
save_password = xbmcgui.Dialog().yesno(string_load(30363), string_load(30364))
if save_password:
log.debug("Saving password for fast user switching: {0}".format(hashed_username))
settings.setSetting("saved_user_password_" + hashed_username, kb.getText())
else:
log.debug("Saving username with no password: {0}".format(selected_user_name))
save_user_details(settings, selected_user_name, "")
if something_changed:
home_window = HomeWindow()

View File

@@ -1,15 +1,15 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import json
import sys
import xbmcgui
import xbmcplugin
from .downloadutils import DownloadUtils
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .utils import get_art
from .datamanager import DataManager
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
def show_server_sessions():
@@ -29,7 +29,7 @@ def show_server_sessions():
url = "{server}/Sessions"
results = data_manager.get_content(url)
log.debug("session_info: {0}", results)
log.debug("session_info: {0}".format(results))
if results is None:
return

View File

@@ -1,51 +0,0 @@
# Gnu General Public License - see LICENSE.TXT
import xbmc
import xbmcaddon
from .jsonrpc import JsonRpc
class SimpleLogging:
name = ""
enable_logging = False
def __init__(self, name):
settings = xbmcaddon.Addon()
prefix = settings.getAddonInfo('name')
self.name = prefix + '.' + name
self.enable_logging = settings.getSetting('log_debug') == "true"
# params = {"setting": "debug.showloginfo"}
# setting_result = json_rpc('Settings.getSettingValue').execute(params)
# current_value = setting_result.get("result", None)
# if current_value is not None:
# self.enable_logging = current_value.get("value", False)
# xbmc.log("LOGGING_ENABLED %s : %s" % (self.name, str(self.enable_logging)), level=xbmc.LOGDEBUG)
def __str__(self):
return "LoggingEnabled: " + str(self.enable_logging)
def info(self, fmt, *args, **kwargs):
log_line = self.name + "|INFO|" + self.log_line(fmt, *args)
xbmc.log(log_line, level=xbmc.LOGNOTICE)
def error(self, fmt, *args, **kwargs):
log_line = self.name + "|ERROR|" + self.log_line(fmt, *args)
xbmc.log(log_line, level=xbmc.LOGERROR)
def debug(self, fmt, *args, **kwargs):
if self.enable_logging:
log_line = self.name + "|DEBUG|" + self.log_line(fmt, *args)
xbmc.log(log_line, level=xbmc.LOGNOTICE)
@staticmethod
def log_line(fmt, *args):
new_args = []
# convert any unicode to utf-8 strings
for arg in args:
if isinstance(arg, unicode):
new_args.append(arg.encode("utf-8"))
else:
new_args.append(arg)
log_line = fmt.format(*new_args)
return log_line

View File

@@ -1,15 +1,17 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import os
import xml.etree.ElementTree as ET
import xbmc
import xbmcgui
import xbmcvfs
from .jsonrpc import JsonRpc, get_value, set_value
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
ver = xbmc.getInfoLabel('System.BuildVersion')[:2]
@@ -27,9 +29,6 @@ def clone_default_skin():
set_skin_settings()
update_kodi_settings()
# xbmc.executebuiltin("ReloadSkin()")
# xbmc.executebuiltin("ActivateWindow(Home)")
def walk_path(root_path, relative_path, all_files):
files = xbmcvfs.listdir(root_path)
@@ -52,7 +51,7 @@ def clone_skin():
kodi_path = xbmc.translatePath("special://xbmc")
kodi_skin_source = os.path.join(kodi_path, "addons", "skin.estuary")
log.debug("Kodi Skin Source: {0}", kodi_skin_source)
log.debug("Kodi Skin Source: {0}".format(kodi_skin_source))
pdialog = xbmcgui.DialogProgress()
pdialog.create("JellyCon Skin Cloner", "")
@@ -60,11 +59,11 @@ def clone_skin():
all_files = []
walk_path(kodi_skin_source, "", all_files)
for found in all_files:
log.debug("Found Path: {0}", found)
log.debug("Found Path: {0}".format(found))
kodi_home_path = xbmc.translatePath("special://home")
kodi_skin_destination = os.path.join(kodi_home_path, "addons", "skin.estuary_jellycon")
log.debug("Kodi Skin Destination: {0}", kodi_skin_destination)
log.debug("Kodi Skin Destination: {0}".format(kodi_skin_destination))
# copy all skin files (clone)
count = 0
@@ -81,22 +80,18 @@ def clone_skin():
# alter skin addon.xml
addon_xml_path = os.path.join(kodi_skin_destination, "addon.xml")
with open(addon_xml_path, "r") as addon_file:
addon_xml_data = addon_file.read()
addon_tree = ET.parse(addon_xml_path)
addon_root = addon_tree.getroot()
addon_xml_data = addon_xml_data.replace("id=\"skin.estuary\"", "id=\"skin.estuary_jellycon\"")
addon_xml_data = addon_xml_data.replace("name=\"Estuary\"", "name=\"Estuary JellyCon\"")
addon_root.attrib['id'] = 'skin.estuary_jellycon'
addon_root.attrib['name'] = 'Estuary JellyCon'
# log.debug("{0}", addon_xml_data)
# update the addon.xml
with open(addon_xml_path, "w") as addon_file:
addon_file.write(addon_xml_data)
addon_tree.write(addon_xml_path)
# get jellycon path
jellycon_path = os.path.join(kodi_home_path, "addons", "plugin.video.jellycon")
log.debug("Major Version: {0}", ver)
log.debug("Major Version: {0}".format(ver))
file_list = ["Home.xml",
"Includes_Home.xml",
@@ -104,6 +99,7 @@ def clone_skin():
"DialogSeekBar.xml",
"VideoOSD.xml"]
# Copy customized skin files from our addon into cloned skin
for file_name in file_list:
source = os.path.join(jellycon_path, "resources", "skins", "skin.estuary", ver, "xml", file_name)
destination = os.path.join(kodi_skin_destination, "xml", file_name)
@@ -123,11 +119,11 @@ def clone_skin():
'enabled': True
}
result = JsonRpc('Addons.SetAddonEnabled').execute(params)
log.debug("Addons.SetAddonEnabled : {0}", result)
log.debug("Addons.SetAddonEnabled : {0}".format(result))
log.debug("SkinCloner : Current Skin : " + get_value("lookandfeel.skin"))
set_result = set_value("lookandfeel.skin", "skin.estuary_jellycon")
log.debug("Save Setting : lookandfeel.skin : {0}", set_result)
log.debug("Save Setting : lookandfeel.skin : {0}".format(set_result))
log.debug("SkinCloner : Current Skin : " + get_value("lookandfeel.skin"))

View File

@@ -1,11 +1,12 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import sys
import functools
import time
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
enabled = False
@@ -27,6 +28,6 @@ def timer(func):
data = args[1]
elif func.__name__ == "main_entry_point" and len(sys.argv) > 2:
data = sys.argv[2]
log.info("timing_data|{0}|{1}|{2}|{3}", func.__name__, started, ended, data)
log.info("timing_data|{0}|{1}|{2}|{3}".format(func.__name__, started, ended, data))
return value
return wrapper

View File

@@ -1,4 +1,5 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import urllib
import encodings
@@ -6,12 +7,12 @@ import encodings
import xbmc
import xbmcgui
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .datamanager import DataManager
from .translation import string_load
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
dataManager = DataManager()
details_string = 'EpisodeCount,SeasonCount,Path,Etag,MediaStreams'
@@ -111,7 +112,7 @@ def get_match(item_type, title, year, imdb_id):
results = results.get('SearchHints')
if results is None:
results = []
log.debug('SearchHints jsonData: {0}', results)
log.debug('SearchHints jsonData: {0}'.format(results))
potential_matches = []
@@ -121,12 +122,12 @@ def get_match(item_type, title, year, imdb_id):
if (name == title and int(year) == production_year) or (int(year) == production_year):
potential_matches.append(item)
log.debug('Potential matches: {0}', potential_matches)
log.debug('Potential matches: {0}'.format(potential_matches))
for item in potential_matches:
item_imdb_id = get_imdb_id(item.get('ItemId'))
if item_imdb_id == imdb_id:
log.debug('Found match: {0}', item)
log.debug('Found match: {0}'.format(item))
return item
return None

View File

@@ -1,7 +1,9 @@
import xbmcaddon
from .simple_logging import SimpleLogging
from __future__ import division, absolute_import, print_function, unicode_literals
log = SimpleLogging(__name__)
import xbmcaddon
from .loghandler import LazyLogger
log = LazyLogger(__name__)
addon = xbmcaddon.Addon()

View File

@@ -1,4 +1,6 @@
# Gnu General Public License - see LICENSE.TXT
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmcaddon
import xbmc
import xbmcvfs
@@ -13,9 +15,10 @@ import math
from datetime import datetime
import calendar
import re
from urllib import urlencode
from .downloadutils import DownloadUtils
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .clientinfo import ClientInformation
# hack to get datetime strptime loaded
@@ -23,22 +26,16 @@ throwaway = time.strptime('20110101', '%Y%m%d')
# define our global download utils
downloadUtils = DownloadUtils()
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
def get_jellyfin_url(base_url, params):
params["format"] = "json"
param_list = []
for key in params:
if params[key] is not None:
value = params[key]
if isinstance(value, unicode):
value = value.encode("utf8")
else:
value = str(value)
param_list.append(key + "=" + urllib.quote_plus(value, safe="{}"))
param_string = "&".join(param_list)
return base_url + "?" + param_string
url_params = urlencode(params)
# Filthy hack until I get around to reworking the network flow
# It relies on {thing} strings in downloadutils.py
url_params = url_params.replace('%7B', '{').replace('%7D', '}')
return base_url + "?" + url_params
###########################################################################
@@ -88,7 +85,7 @@ class PlayUtils:
if direct_path.startswith("//"):
direct_path = "smb://" + direct_path[2:]
log.debug("playback_direct_path: {0}", direct_path)
log.debug("playback_direct_path: {0}".format(direct_path))
if xbmcvfs.exists(direct_path):
playurl = direct_path
@@ -164,13 +161,13 @@ class PlayUtils:
lines = contents.split(line_break)
for line in lines:
line = line.strip()
log.debug("STRM Line: {0}", line)
log.debug("STRM Line: {0}".format(line))
if line.startswith('#KODIPROP:'):
match = re.search('#KODIPROP:(?P<item_property>[^=]+?)=(?P<property_value>.+)', line)
if match:
item_property = match.group('item_property')
property_value = match.group('property_value')
log.debug("STRM property found: {0} value: {1}", item_property, property_value)
log.debug("STRM property found: {0} value: {1}".format(item_property, property_value))
listitem_props.append((item_property, property_value))
else:
log.debug("STRM #KODIPROP incorrect format")
@@ -181,7 +178,7 @@ class PlayUtils:
playurl = line
log.debug("STRM playback url found")
log.debug("Playback URL: {0} ListItem Properties: {1}", playurl, listitem_props)
log.debug("Playback URL: {0} ListItem Properties: {1}".format(playurl, listitem_props))
return playurl, listitem_props
@@ -218,8 +215,8 @@ def get_art(item, server):
'tvshow.landscape': ''
}
image_tags = item["ImageTags"]
if image_tags is not None and image_tags["Primary"] is not None:
image_tags = item.get("ImageTags", {})
if image_tags and image_tags.get("Primary"):
# image_tag = image_tags["Primary"]
art['thumb'] = downloadUtils.get_artwork(item, "Primary", server=server)
@@ -307,7 +304,7 @@ def send_event_notification(method, data):
base64_data = base64.b64encode(message_data)
escaped_data = '\\"[\\"{0}\\"]\\"'.format(base64_data)
command = 'XBMC.NotifyAll({0}.SIGNAL,{1},{2})'.format(source_id, method, escaped_data)
log.debug("Sending notification event data: {0}", command)
log.debug("Sending notification event data: {0}".format(command))
xbmc.executebuiltin(command)
@@ -317,7 +314,7 @@ def datetime_from_string(time_string):
time_string = re.sub("[0-9]{1}Z", " UTC", time_string)
elif time_string[-6:] == "+00:00":
time_string = re.sub("[0-9]{1}\+00:00", " UTC", time_string)
log.debug("New Time String : {0}", time_string)
log.debug("New Time String : {0}".format(time_string))
start_time = time.strptime(time_string, "%Y-%m-%dT%H:%M:%S.%f %Z")
dt = datetime(*(start_time[0:6]))

View File

@@ -73,7 +73,7 @@ STATUS_INVALID_EXTENSION = 1010
STATUS_UNEXPECTED_CONDITION = 1011
STATUS_TLS_HANDSHAKE_ERROR = 1015
logger = logging.getLogger()
#logger = logging.getLogger()
class WebSocketException(Exception):
@@ -108,10 +108,10 @@ def enableTrace(tracable):
"""
global traceEnabled
traceEnabled = tracable
if tracable:
if not logger.handlers:
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
#if tracable:
# if not logger.handlers:
# logger.addHandler(logging.StreamHandler())
# logger.setLevel(logging.DEBUG)
def setdefaulttimeout(timeout):
@@ -512,10 +512,10 @@ class WebSocket(object):
header_str = "\r\n".join(headers)
self._send(header_str)
if traceEnabled:
logger.debug("--- request header ---")
logger.debug(header_str)
logger.debug("-----------------------")
#if traceEnabled:
# logger.debug("--- request header ---")
# logger.debug(header_str)
# logger.debug("-----------------------")
status, resp_headers = self._read_headers()
if status != 101:
@@ -550,16 +550,16 @@ class WebSocket(object):
def _read_headers(self):
status = None
headers = {}
if traceEnabled:
logger.debug("--- response header ---")
#if traceEnabled:
# logger.debug("--- response header ---")
while True:
line = self._recv_line()
if line == "\r\n":
break
line = line.strip()
if traceEnabled:
logger.debug(line)
#if traceEnabled:
# logger.debug(line)
if not status:
status_info = line.split(" ", 2)
status = int(status_info[1])
@@ -571,8 +571,8 @@ class WebSocket(object):
else:
raise WebSocketException("Invalid header")
if traceEnabled:
logger.debug("-----------------------")
#if traceEnabled:
# logger.debug("-----------------------")
return status, headers
@@ -591,8 +591,8 @@ class WebSocket(object):
frame.get_mask_key = self.get_mask_key
data = frame.format()
length = len(data)
if traceEnabled:
logger.debug("send: " + repr(data))
#if traceEnabled:
# logger.debug("send: " + repr(data))
while data:
l = self._send(data)
data = data[l:]
@@ -645,7 +645,7 @@ class WebSocket(object):
self._cont_data[1] += frame.data
else:
self._cont_data = [frame.opcode, frame.data]
if frame.fin:
data = self._cont_data
self._cont_data = None
@@ -718,12 +718,12 @@ class WebSocket(object):
reason: the reason to close. This must be string.
"""
try:
self.sock.shutdown(socket.SHUT_RDWR)
except:
pass
'''
if self.connected:
if status < 0 or status >= ABNF.LENGTH_16:
@@ -735,10 +735,10 @@ class WebSocket(object):
self.sock.settimeout(3)
try:
frame = self.recv_frame()
if logger.isEnabledFor(logging.ERROR):
if #logger.isEnabledFor(logging.ERROR):
recv_status = struct.unpack("!H", frame.data)[0]
if recv_status != STATUS_NORMAL:
logger.error("close status: " + repr(recv_status))
#logger.error("close status: " + repr(recv_status))
except:
pass
self.sock.settimeout(timeout)
@@ -856,7 +856,7 @@ class WebSocketApp(object):
"""
self.keep_running = False
if(self.sock != None):
self.sock.close()
self.sock.close()
def _send_ping(self, interval):
while True:
@@ -897,14 +897,14 @@ class WebSocketApp(object):
thread.start()
while self.keep_running:
try:
data = self.sock.recv()
if data is None or self.keep_running == False:
break
self._callback(self.on_message, data)
self._callback(self.on_message, data)
except Exception as e:
found_timeout = False
for arg in e.args:
@@ -928,7 +928,7 @@ class WebSocketApp(object):
try:
callback(self, *args)
except Exception as e:
logger.error(e)
#logger.error(e)
if True:#logger.isEnabledFor(logging.DEBUG):
_, _, tb = sys.exc_info()
traceback.print_tb(tb)

View File

@@ -10,13 +10,13 @@ import xbmc
import xbmcgui
from .functions import play_action
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from . import clientinfo
from . import downloadutils
from .jsonrpc import JsonRpc
from .kodi_utils import HomeWindow
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
class WebSocketClient(threading.Thread):
@@ -65,10 +65,10 @@ class WebSocketClient(threading.Thread):
self._general_commands(data)
else:
log.debug("WebSocket Message Type: {0}", message)
log.debug("WebSocket Message Type: {0}".format(message))
def _library_changed(self, data):
log.debug("Library_Changed: {0}", data)
log.debug("Library_Changed: {0}".format(data))
self._library_monitor.check_for_updates()
def _play(self, data):
@@ -81,7 +81,7 @@ class WebSocketClient(threading.Thread):
home_screen.set_property("skip_select_user", "true")
startat = data.get('StartPositionTicks', -1)
log.debug("WebSocket Message PlayNow: {0}", data)
log.debug("WebSocket Message PlayNow: {0}".format(data))
media_source_id = data.get("MediaSourceId", "")
subtitle_stream_index = data.get("SubtitleStreamIndex", None)
@@ -124,14 +124,14 @@ class WebSocketClient(threading.Thread):
seek_to = data['SeekPositionTicks']
seek_time = seek_to / 10000000.0
player.seekTime(seek_time)
log.debug("Seek to {0}", seek_time)
log.debug("Seek to {0}".format(seek_time))
elif command in actions:
actions[command]()
log.debug("Command: {0} completed", command)
log.debug("Command: {0} completed".format(command))
else:
log.debug("Unknown command: {0}", command)
log.debug("Unknown command: {0}".format(command))
return
def _general_commands(self, data):
@@ -175,7 +175,7 @@ class WebSocketClient(threading.Thread):
# header = arguments['Header']
text = arguments['Text']
# show notification here
log.debug("WebSocket DisplayMessage: {0}", text)
log.debug("WebSocket DisplayMessage: {0}".format(text))
xbmcgui.Dialog().notification("JellyCon", text)
elif command == 'SendString':
@@ -234,7 +234,7 @@ class WebSocketClient(threading.Thread):
self.post_capabilities()
def on_error(self, ws, error):
log.debug("Error: {0}", error)
log.debug("Error: {0}".format(error))
def run(self):
@@ -254,8 +254,8 @@ class WebSocketClient(threading.Thread):
else:
server = server.replace('http', "ws")
websocket_url = "%s/websocket?api_key=%s&deviceId=%s" % (server, token, self.device_id)
log.debug("websocket url: {0}", websocket_url)
websocket_url = "%s/socket?api_key=%s&deviceId=%s" % (server, token, self.device_id)
log.debug("websocket url: {0}".format(websocket_url))
self._client = websocket.WebSocketApp(websocket_url,
on_open=self.on_open,

View File

@@ -1,8 +1,9 @@
from __future__ import division, absolute_import, print_function, unicode_literals
import xbmcaddon
import xbmcplugin
import xbmcgui
import xbmc
import json
import hashlib
import random
import time
@@ -10,12 +11,12 @@ import time
from .downloadutils import DownloadUtils
from .utils import get_jellyfin_url
from .datamanager import DataManager
from .simple_logging import SimpleLogging
from .loghandler import LazyLogger
from .kodi_utils import HomeWindow
from .dir_functions import process_directory
from .tracking import timer
log = SimpleLogging(__name__)
log = LazyLogger(__name__)
downloadUtils = DownloadUtils()
dataManager = DataManager()
kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
@@ -43,7 +44,6 @@ def set_random_movies():
url = get_jellyfin_url("{server}/Users/{userid}/Items", url_params)
results = downloadUtils.download_url(url, suppress=True)
results = json.loads(results)
randon_movies_list = []
if results is not None:
@@ -58,14 +58,14 @@ def set_random_movies():
m.update(movies_list_string)
new_widget_hash = m.hexdigest()
log.debug("set_random_movies : {0}", movies_list_string)
log.debug("set_random_movies : {0}", new_widget_hash)
log.debug("set_random_movies : {0}".format(movies_list_string))
log.debug("set_random_movies : {0}".format(new_widget_hash))
home_window.set_property("random-movies", movies_list_string)
home_window.set_property("random-movies-changed", new_widget_hash)
def set_background_image(force=False):
log.debug("set_background_image Called forced={0}", force)
log.debug("set_background_image Called forced={0}".format(force))
global background_current_item
global background_items
@@ -76,12 +76,12 @@ def set_background_image(force=False):
background_items = []
if len(background_items) == 0:
log.debug("set_background_image: Need to load more backgrounds {0} - {1}",
len(background_items), background_current_item)
log.debug("set_background_image: Need to load more backgrounds {0} - {1}".format(
len(background_items), background_current_item))
url_params = {}
url_params["Recursive"] = True
# url_params["limit"] = 60
url_params["limit"] = 100
url_params["SortBy"] = "Random"
url_params["IncludeItemTypes"] = "Movie,Series"
url_params["ImageTypeLimit"] = 1
@@ -90,7 +90,6 @@ def set_background_image(force=False):
server = downloadUtils.get_server()
results = downloadUtils.download_url(url, suppress=True)
results = json.loads(results)
if results is not None:
items = results.get("Items", [])
@@ -105,12 +104,12 @@ def set_background_image(force=False):
item_background["name"] = label
background_items.append(item_background)
log.debug("set_background_image: Loaded {0} more backgrounds", len(background_items))
log.debug("set_background_image: Loaded {0} more backgrounds".format(len(background_items)))
if len(background_items) > 0:
bg_image = background_items[background_current_item].get("image")
label = background_items[background_current_item].get("name")
log.debug("set_background_image: {0} - {1} - {2}", background_current_item, label, bg_image)
log.debug("set_background_image: {0} - {1} - {2}".format(background_current_item, label, bg_image))
background_current_item += 1
if background_current_item >= len(background_items):
@@ -133,7 +132,7 @@ def check_for_new_content():
log.debug("Using simple new content check")
current_time_stamp = str(time.time())
home_window.set_property("jellycon_widget_reload", current_time_stamp)
log.debug("Setting New Widget Hash: {0}", current_time_stamp)
log.debug("Setting New Widget Hash: {0}".format(current_time_stamp))
return
url_params = {}
@@ -144,13 +143,11 @@ def check_for_new_content():
url_params["SortOrder"] = "Descending"
url_params["IncludeItemTypes"] = "Movie,Episode"
url_params["ImageTypeLimit"] = 0
url_params["format"] = "json"
added_url = get_jellyfin_url('{server}/Users/{userid}/Items', url_params)
added_result = downloadUtils.download_url(added_url, suppress=True)
result = json.loads(added_result)
log.debug("LATEST_ADDED_ITEM: {0}", result)
result = downloadUtils.download_url(added_url, suppress=True)
log.debug("LATEST_ADDED_ITEM: {0}".format(result))
last_added_date = ""
if result is not None:
@@ -158,7 +155,7 @@ def check_for_new_content():
if len(items) > 0:
item = items[0]
last_added_date = item.get("Etag", "")
log.debug("last_added_date: {0}", last_added_date)
log.debug("last_added_date: {0}".format(last_added_date))
url_params = {}
url_params["Recursive"] = True
@@ -168,13 +165,11 @@ def check_for_new_content():
url_params["SortOrder"] = "Descending"
url_params["IncludeItemTypes"] = "Movie,Episode"
url_params["ImageTypeLimit"] = 0
url_params["format"] = "json"
played_url = get_jellyfin_url('{server}/Users/{userid}/Items', url_params)
played_result = downloadUtils.download_url(played_url, suppress=True)
result = json.loads(played_result)
log.debug("LATEST_PLAYED_ITEM: {0}", result)
result = downloadUtils.download_url(played_url, suppress=True)
log.debug("LATEST_PLAYED_ITEM: {0}".format(result))
last_played_date = ""
if result is not None:
@@ -186,30 +181,30 @@ def check_for_new_content():
if user_data is not None:
last_played_date = user_data.get("LastPlayedDate", "")
log.debug("last_played_date: {0}", last_played_date)
log.debug("last_played_date: {0}".format(last_played_date))
current_widget_hash = home_window.get_property("jellycon_widget_reload")
log.debug("Current Widget Hash: {0}", current_widget_hash)
log.debug("Current Widget Hash: {0}".format(current_widget_hash))
m = hashlib.md5()
m.update(last_played_date + last_added_date)
new_widget_hash = m.hexdigest()
log.debug("New Widget Hash: {0}", new_widget_hash)
log.debug("New Widget Hash: {0}".format(new_widget_hash))
if current_widget_hash != new_widget_hash:
home_window.set_property("jellycon_widget_reload", new_widget_hash)
log.debug("Setting New Widget Hash: {0}", new_widget_hash)
log.debug("Setting New Widget Hash: {0}".format(new_widget_hash))
@timer
def get_widget_content_cast(handle, params):
log.debug("getWigetContentCast Called: {0}", params)
log.debug("getWigetContentCast Called: {0}".format(params))
server = downloadUtils.get_server()
item_id = params["id"]
data_manager = DataManager()
result = data_manager.get_content("{server}/Users/{userid}/Items/" + item_id + "?format=json")
log.debug("ItemInfo: {0}", result)
result = data_manager.get_content("{server}/Users/{userid}/Items/" + item_id)
log.debug("ItemInfo: {0}".format(result))
if not result:
return
@@ -272,7 +267,7 @@ def get_widget_content_cast(handle, params):
@timer
def get_widget_content(handle, params):
log.debug("getWigetContent Called: {0}", params)
log.debug("getWigetContent Called: {0}".format(params))
settings = xbmcaddon.Addon()
hide_watched = settings.getSetting("hide_watched") == "true"
@@ -283,12 +278,11 @@ def get_widget_content(handle, params):
log.error("getWigetContent type not set")
return
log.debug("widget_type: {0}", widget_type)
log.debug("widget_type: {0}".format(widget_type))
url_verb = "{server}/Users/{userid}/Items"
url_params = {}
url_params["Limit"] = "{ItemLimit}"
url_params["format"] = "json"
url_params["Fields"] = "{field_filters}"
url_params["ImageTypeLimit"] = 1
url_params["IsMissing"] = False
@@ -331,7 +325,6 @@ def get_widget_content(handle, params):
url_params["IsVirtualUnaired"] = False
url_params["IncludeItemTypes"] = "Episode"
url_params["ImageTypeLimit"] = 1
url_params["format"] = "json"
elif widget_type == "recent_episodes":
xbmcplugin.setContent(handle, 'episodes')
@@ -360,7 +353,6 @@ def get_widget_content(handle, params):
url_params["userid"] = "{userid}"
url_params["Recursive"] = True
url_params["Fields"] = "{field_filters}"
url_params["format"] = "json"
url_params["ImageTypeLimit"] = 1
elif widget_type == "movie_recommendations":
@@ -377,7 +369,7 @@ def get_widget_content(handle, params):
set_id = 0
while len(ids) < 20 and suggested_items:
items = suggested_items[set_id]
log.debug("BaselineItemName : {0} - {1}", set_id, items.get("BaselineItemName"))
log.debug("BaselineItemName : {0} - {1}".format(set_id, items.get("BaselineItemName")))
items = items["Items"]
rand = random.randint(0, len(items) - 1)
# log.debug("random suggestions index : {0} {1}", rand, set_id)
@@ -397,7 +389,7 @@ def get_widget_content(handle, params):
set_id = 0
id_list = ",".join(ids)
log.debug("Recommended Items : {0}", len(ids), id_list)
log.debug("Recommended Items : {0}".format(len(ids), id_list))
url_params["Ids"] = id_list
items_url = get_jellyfin_url(url_verb, url_params)
@@ -417,7 +409,7 @@ def get_widget_content(handle, params):
if detected_type is not None:
# if the media type is not set then try to use the detected type
log.debug("Detected content type: {0}", detected_type)
log.debug("Detected content type: {0}".format(detected_type))
content_type = None
if detected_type == "Movie":

View File

@@ -4,9 +4,10 @@
<setting label="30388" type="lsep"/>
<setting label="30011" type="action" action="RunScript(plugin.video.jellycon,0,?mode=DETECT_SERVER_USER)" option="close"/>
<setting id="protocol" type="select" label="30390" lvalues="30391|30392" default="0" />
<setting id="ipaddress" type="text" label="30000" default="&lt;none&gt;" visible="true" enable="true" />
<setting id="port" type="text" label="30001" default="8096" visible="true" enable="true" />
<setting id="ipaddress" type="text" label="30000" default="" visible="false" enable="false" />
<setting id="protocol" type="select" label="30390" lvalues="30391|30392" default="0" visible="false"/>
<setting id="port" type="text" label="30001" default="8096" visible="false" enable="false" />
<setting id="server_address" type="text" label="30000" default="" visible="true" enable="true" />
<setting id="verify_cert" type="bool" label="30003" default="false" visible="true" enable="true" />
<setting label="30389" type="lsep"/>
@@ -133,7 +134,7 @@
<setting id="use_cached_widget_data" type="bool" label="30441" default="false" visible="true" enable="true" />
<setting id="showLoadProgress" type="bool" label="30120" default="false" visible="true" enable="true" />
<setting id="suppressErrors" type="bool" label="30315" default="false" visible="true" enable="true" />
<setting id="speed_test_data_size" type="slider" label="30436" default="15" range="5,1,100" option="int" visible="true"/>
<setting id="speed_test_data_size" type="slider" label="30436" default="10" range="1,1,10" option="int" visible="true"/>
</category>
<category label="30421">

View File

@@ -9,7 +9,7 @@ import xbmcaddon
import xbmcgui
from resources.lib.downloadutils import DownloadUtils, save_user_details
from resources.lib.simple_logging import SimpleLogging
from resources.lib.loghandler import LazyLogger
from resources.lib.play_utils import Service, PlaybackService, send_progress
from resources.lib.kodi_utils import HomeWindow
from resources.lib.widgets import set_background_image, set_random_movies
@@ -35,13 +35,13 @@ home_window.clear_property("userid")
home_window.clear_property("AccessToken")
home_window.clear_property("Params")
log = SimpleLogging('service')
log = LazyLogger('service')
monitor = xbmc.Monitor()
try:
clear_old_cache_data()
except Exception as error:
log.error("Error in clear_old_cache_data() : {0}", error)
log.error("Error in clear_old_cache_data() : {0}".format(error))
# wait for 10 seconds for the Kodi splash screen to close
i = 0
@@ -60,7 +60,7 @@ try:
download_utils.authenticate()
download_utils.get_user_id()
except Exception as error:
log.error("Error with initial service auth: {0}", error)
log.error("Error with initial service auth: {0}".format(error))
image_server = HttpImageServerThread()
@@ -181,8 +181,8 @@ while not xbmc.abortRequested:
set_background_image(False)
except Exception as error:
log.error("Exception in Playback Monitor: {0}", error)
log.error("{0}", traceback.format_exc())
log.error("Exception in Playback Monitor: {0}".format(error))
log.error("{0}".format(traceback.format_exc()))
first_run = False
xbmc.sleep(1000)