Merge pull request #42 from MediaBrowser/Pre-testing

Pre testing
This commit is contained in:
angelblue05 2016-06-20 22:39:34 -05:00 committed by GitHub
commit b44dfd73c5
30 changed files with 1469 additions and 1592 deletions

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.emby"
name="Emby"
version="2.2.10"
version="2.2.11"
provider-name="Emby.media">
<requires>
<import addon="xbmc.python" version="2.1.0"/>

View file

@ -1,3 +1,8 @@
version 2.2.11
- Preparation for feature requests
- Add option to refresh Emby items via context menu
- Minor fixes
version 2.2.10
- Add keymap action for delete content: RunPlugin(plugin://plugin.video.emby?mode=delete)
- Fix various bugs

View file

@ -10,149 +10,159 @@ import xbmc
import xbmcaddon
import xbmcgui
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
addon_path = addon_.getAddonInfo('path').decode('utf-8')
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource)
#################################################################################################
_addon = xbmcaddon.Addon(id='plugin.video.emby')
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
_base_resource = xbmc.translatePath(os.path.join(_addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(_base_resource)
#################################################################################################
import api
import artwork
import utils
import clientinfo
import downloadutils
import librarysync
import read_embyserver as embyserver
import embydb_functions as embydb
import kodidb_functions as kodidb
import musicutils as musicutils
import api
from utils import Logging, settings, language as lang, kodiSQL
log = Logging('ContextMenu').log
def logMsg(msg, lvl=1):
utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl)
#################################################################################################
#Kodi contextmenu item to configure the emby settings
#for now used to set ratings but can later be used to sync individual items etc.
# Kodi contextmenu item to configure the emby settings
if __name__ == '__main__':
itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
kodiId = xbmc.getInfoLabel('ListItem.DBID').decode('utf-8')
itemType = xbmc.getInfoLabel('ListItem.DBTYPE').decode('utf-8')
itemId = ""
emby = embyserver.Read_EmbyServer()
if not itemType:
if xbmc.getCondVisibility("Container.Content(albums)"):
itemType = "album"
elif xbmc.getCondVisibility("Container.Content(artists)"):
itemType = "artist"
elif xbmc.getCondVisibility("Container.Content(songs)"):
itemType = "song"
elif xbmc.getCondVisibility("Container.Content(pictures)"):
itemType = "picture"
else:
log("ItemType is unknown.")
if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
itemId = xbmc.getInfoLabel("ListItem.Property(embyid)")
embyid = ""
if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
else:
embyconn = utils.kodiSQL('emby')
elif kodiId and itemType:
embyconn = kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
item = emby_db.getItem_byKodiId(itemid, itemtype)
item = emby_db.getItem_byKodiId(kodiId, itemType)
embycursor.close()
if item: embyid = item[0]
logMsg("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype))
try:
itemId = item[0]
except TypeError:
pass
if embyid:
item = emby.getItem(embyid)
log("Found ItemId: %s ItemType: %s" % (itemId, itemType), 1)
if itemId:
dialog = xbmcgui.Dialog()
emby = embyserver.Read_EmbyServer()
item = emby.getItem(itemId)
API = api.API(item)
userdata = API.getUserData()
likes = userdata['Likes']
favourite = userdata['Favorite']
options=[]
if likes == True:
#clear like for the item
options.append(utils.language(30402))
if likes == False or likes == None:
#Like the item
options.append(utils.language(30403))
if likes == True or likes == None:
#Dislike the item
options.append(utils.language(30404))
if favourite == False:
#Add to emby favourites
options.append(utils.language(30405))
if favourite == True:
#Remove from emby favourites
options.append(utils.language(30406))
if itemtype == "song":
#Set custom song rating
options.append(utils.language(30407))
#delete item
options.append(utils.language(30409))
#addon settings
options.append(utils.language(30408))
#display select dialog and process results
header = utils.language(30401)
ret = xbmcgui.Dialog().select(header, options)
if ret != -1:
if options[ret] == utils.language(30402):
emby.updateUserRating(embyid, deletelike=True)
if options[ret] == utils.language(30403):
emby.updateUserRating(embyid, like=True)
if options[ret] == utils.language(30404):
emby.updateUserRating(embyid, like=False)
if options[ret] == utils.language(30405):
emby.updateUserRating(embyid, favourite=True)
if options[ret] == utils.language(30406):
emby.updateUserRating(embyid, favourite=False)
if options[ret] == utils.language(30407):
kodiconn = utils.kodiSQL('music')
kodicursor = kodiconn.cursor()
query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" ))
kodicursor.execute(query, (itemid,))
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue))
if newvalue:
newvalue = int(newvalue)
if newvalue > 5: newvalue = "5"
if utils.settings('enableUpdateSongRating') == "true":
musicutils.updateRatingToFile(newvalue, API.getFilePath())
if utils.settings('enableExportSongRating') == "true":
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
emby.updateUserRating(embyid, like, favourite, deletelike)
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
kodicursor.execute(query, (newvalue,itemid,))
kodiconn.commit()
options = []
if options[ret] == utils.language(30408):
#Open addon settings
if favourite:
# Remove from emby favourites
options.append(lang(30406))
else:
# Add to emby favourites
options.append(lang(30405))
if itemType == "song":
# Set custom song rating
options.append(lang(30407))
# Refresh item
options.append(lang(30410))
# Delete item
options.append(lang(30409))
# Addon settings
options.append(lang(30408))
# Display select dialog and process results
resp = xbmcgui.Dialog().select(lang(30401), options)
if resp > -1:
selected = options[resp]
if selected == lang(30410):
# Refresh item
emby.refreshItem(itemId)
elif selected == lang(30405):
# Add favourite
emby.updateUserRating(itemId, favourite=True)
elif selected == lang(30406):
# Delete favourite
emby.updateUserRating(itemId, favourite=False)
elif selected == lang(30407):
# Update song rating
kodiconn = kodiSQL('music')
kodicursor = kodiconn.cursor()
query = "SELECT rating FROM song WHERE idSong = ?"
kodicursor.execute(query, (kodiId,))
try:
value = kodicursor.fetchone()[0]
current_value = int(round(float(value),0))
except TypeError:
pass
else:
new_value = dialog.numeric(0, lang(30411), str(current_value))
if new_value > -1:
new_value = int(new_value)
if new_value > 5:
new_value = 5
if settings('enableUpdateSongRating') == "true":
musicutils.updateRatingToFile(new_value, API.getFilePath())
query = "UPDATE song SET rating = ? WHERE idSong = ?"
kodicursor.execute(query, (new_value, kodiId,))
kodiconn.commit()
'''if settings('enableExportSongRating') == "true":
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(new_value)
emby.updateUserRating(itemId, like, favourite, deletelike)'''
finally:
kodicursor.close()
elif selected == lang(30408):
# Open addon settings
xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)")
if options[ret] == utils.language(30409):
#delete item from the server
elif selected == lang(30409):
# delete item from the server
delete = True
if utils.settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file from Emby Server? This will "
"also delete the file(s) from disk!"))
if settings('skipContextMenu') != "true":
resp = dialog.yesno(
heading=lang(29999),
line1=lang(33041))
if not resp:
logMsg("User skipped deletion for: %s." % embyid, 1)
log("User skipped deletion for: %s." % itemId, 1)
delete = False
if delete:
import downloadutils
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
logMsg("Deleting request: %s" % embyid, 0)
doUtils.downloadUrl(url, action_type="DELETE")
'''if utils.settings('skipContextMenu') != "true":
if xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file on Emby Server? This will "
"also delete the file(s) from disk!")):
import downloadutils
doUtils = downloadutils.DownloadUtils()
doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % embyid, action_type="DELETE")'''
log("Deleting request: %s" % itemId, 0)
emby.deleteItem(itemId)
xbmc.sleep(500)
xbmc.executebuiltin("Container.Update")
xbmc.executebuiltin('Container.Refresh')

View file

@ -12,30 +12,32 @@ import xbmcgui
#################################################################################################
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
addon_path = addon_.getAddonInfo('path').decode('utf-8')
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource)
_addon = xbmcaddon.Addon(id='plugin.video.emby')
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
_base_resource = xbmc.translatePath(os.path.join(_addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(_base_resource)
#################################################################################################
import entrypoint
import utils
from utils import Logging, window, language as lang
log = Logging('Default').log
#################################################################################################
enableProfiling = False
class Main:
class Main():
# MAIN ENTRY POINT
#@utils.profiling()
def __init__(self):
# Parse parameters
base_url = sys.argv[0]
params = urlparse.parse_qs(sys.argv[2][1:])
xbmc.log("Parameter string: %s" % sys.argv[2])
log("Parameter string: %s" % sys.argv[2], 0)
try:
mode = params['mode'][0]
itemid = params.get('id')
@ -70,11 +72,13 @@ class Main:
embypath = sys.argv[2][1:]
embyid = params.get('id',[""])[0]
entrypoint.getExtraFanArt(embyid,embypath)
return
if "/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0]:
embypath = sys.argv[2][1:]
embyid = params.get('id',[""])[0]
entrypoint.getVideoFiles(embyid,embypath)
entrypoint.getVideoFiles(embyid, embypath)
return
if modes.get(mode):
# Simple functions
@ -86,11 +90,11 @@ class Main:
limit = int(params['limit'][0])
modes[mode](itemid, limit)
elif mode in ["channels","getsubfolders"]:
elif mode in ("channels","getsubfolders"):
modes[mode](itemid)
elif mode == "browsecontent":
modes[mode]( itemid, params.get('type',[""])[0], params.get('folderid',[""])[0] )
modes[mode](itemid, params.get('type',[""])[0], params.get('folderid',[""])[0])
elif mode == "channelsfolder":
folderid = params['folderid'][0]
@ -102,16 +106,17 @@ class Main:
# Other functions
if mode == "settings":
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
elif mode in ("manualsync", "fastsync", "repair"):
if utils.window('emby_online') != "true":
if window('emby_online') != "true":
# Server is not online, do not run the sync
xbmcgui.Dialog().ok(heading="Emby for Kodi",
line1=("Unable to run the sync, the add-on is not "
"connected to the Emby server."))
utils.logMsg("EMBY", "Not connected to the emby server.", 1)
xbmcgui.Dialog().ok(heading=lang(29999),
line1=lang(33034))
log("Not connected to the emby server.", 1)
return
if utils.window('emby_dbScan') != "true":
if window('emby_dbScan') != "true":
import librarysync
lib = librarysync.LibrarySync()
if mode == "manualsync":
@ -121,35 +126,17 @@ class Main:
else:
lib.fullSync(repair=True)
else:
utils.logMsg("EMBY", "Database scan is already running.", 1)
log("Database scan is already running.", 1)
elif mode == "texturecache":
import artwork
artwork.Artwork().FullTextureCacheSync()
artwork.Artwork().fullTextureCacheSync()
else:
entrypoint.doMainListing()
if ( __name__ == "__main__" ):
xbmc.log('plugin.video.emby started')
if enableProfiling:
import cProfile
import pstats
import random
from time import gmtime, strftime
addonid = addon_.getAddonInfo('id').decode( 'utf-8' )
datapath = os.path.join( xbmc.translatePath( "special://profile/" ).decode( 'utf-8' ), "addon_data", addonid )
filename = os.path.join( datapath, strftime( "%Y%m%d%H%M%S",gmtime() ) + "-" + str( random.randrange(0,100000) ) + ".log" )
cProfile.run( 'Main()', filename )
stream = open( filename + ".txt", 'w')
p = pstats.Stats( filename, stream = stream )
p.sort_stats( "cumulative" )
p.print_stats()
else:
Main()
xbmc.log('plugin.video.emby stopped')
if __name__ == "__main__":
log('plugin.video.emby started', 1)
Main()
log('plugin.video.emby stopped', 1)

View file

@ -1,56 +1,28 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<strings>
<!-- Add-on settings -->
<string id="29999">Emby for Kodi</string>
<string id="30000">Primary Server Address</string><!-- Verified -->
<string id="30002">Play from HTTP instead of SMB</string><!-- Verified -->
<string id="30004">Log level</string><!-- Verified -->
<string id="30005">Username: </string>
<string id="30006">Password: </string>
<string id="30007">Network Username: </string>
<string id="30008">Network Password: </string>
<string id="30009">Transcode: </string>
<string id="30010">Enable Performance Profiling</string>
<string id="30011">Local caching system</string>
<string id="30014">Emby</string>
<string id="30015">Network</string>
<string id="30016">Device Name</string>
<string id="30016">Device Name</string><!-- Verified -->
<string id="30022">Advanced</string>
<string id="30024">Username</string><!-- Verified -->
<string id="30030">Port Number</string><!-- Verified -->
<string id="30036">Number of recent Movies to show:</string>
<string id="30037">Number of recent TV episodes to show:</string>
<string id="30035">Number of recent Music Albums to show:</string>
<string id="30038">Mark watched at start of playback:</string>
<string id="30039">Set Season poster for episodes</string>
<string id="30040">Genre Filter ...</string>
<string id="30041">Play All from Here</string>
<string id="30035">Number of recent Music Albums to show:</string>
<string id="30036">Number of recent Movies to show:</string>
<string id="30037">Number of recent TV episodes to show:</string>
<string id="30042">Refresh</string>
<string id="30043">Delete</string>
<string id="30046">Add Movie to CouchPotato</string>
<string id="30044">Incorrect Username/Password</string>
<string id="30045">Username not found</string>
<string id="30052">Deleting</string>
<string id="30053">Waiting for server to delete</string>
<string id="30059">Server Default</string>
<string id="30060">Title</string>
<string id="30061">Year</string>
<string id="30062">Premiere Date</string>
<string id="30063">Date Created</string>
<string id="30064">Critic Rating</string>
<string id="30065">Community Rating</string>
<string id="30066">Play Count</string>
<string id="30067">Budget</string>
<!-- Runtime added as 30226 below -->
<string id="30068">Sort By</string>
<string id="30069">None</string>
<string id="30070">Action</string>
<string id="30071">Adventure</string>
@ -75,73 +47,44 @@
<string id="30090">Genre Filter</string>
<string id="30091">Confirm file deletion</string><!-- Verified -->
<string id="30092">Delete this item? This action will delete media and associated data files.</string>
<string id="30093">Mark Watched</string>
<string id="30094">Mark Unwatched</string>
<string id="30095">Add to Favorites</string>
<string id="30096">Remove from Favorites</string>
<string id="30097">Sort By ...</string>
<string id="30093">Mark watched</string>
<string id="30094">Mark unwatched</string>
<string id="30097">Sort by</string>
<string id="30098">Sort Order Descending</string>
<string id="30099">Sort Order Ascending</string>
<string id="30100">Show People</string>
<!-- resume dialog -->
<string id="30105">Resume</string>
<string id="30106">Resume from</string>
<string id="30107">Start from beginning</string>
<string id="30110">Interface</string>
<string id="30111">Include Stream Info</string>
<string id="30112">Include People</string>
<string id="30113">Include Overview</string>
<string id="30114">Offer delete after playback</string><!-- Verified -->
<string id="30115">For Episodes</string><!-- Verified -->
<string id="30116">For Movies</string><!-- Verified -->
<string id="30117">Background Art Refresh Rate (seconds)</string>
<string id="30118">Add Resume Percent</string>
<string id="30119">Add Episode Number</string>
<string id="30120">Show Load Progress</string>
<string id="30121">Loading Content</string>
<string id="30122">Retrieving Data</string>
<string id="30125">Done</string>
<string id="30126">Processing Item : </string>
<string id="30128">Play Error</string>
<string id="30129">This item is not playable</string>
<string id="30130">Local path detected</string>
<string id="30131">Your MB3 Server contains local paths. Please change server paths to UNC or change XBMB3C setting 'Play from Stream' to true. Path: </string>
<string id="30132">Warning</string>
<string id="30133">Debug logging enabled.</string>
<string id="30134">This will affect performance.</string>
<string id="30132">Warning</string><!-- Verified -->
<string id="30135">Error</string>
<string id="30136">Monitoring service is not running</string>
<string id="30137">If you have just installed please restart Kodi</string>
<string id="30138">Search</string>
<string id="30139">Enable Theme Music (Requires Restart)</string>
<string id="30140"> - Loop Theme Music</string>
<string id="30141">Enable Background Image (Requires Restart)</string>
<string id="30142">Services</string>
<string id="30150">Skin does not support setting views</string>
<string id="30151">Select item action (Requires Restart)</string>
<string id="30156">Sort NextUp by Show Title</string>
<string id="30157">Enable Enhanced Images (eg CoverArt)</string><!-- Verified -->
<string id="30158">Metadata</string>
<string id="30159">Artwork</string>
<string id="30160">Video Quality</string><!-- Verified -->
<string id="30161">Enable Suggested Loader (Requires Restart)</string>
<string id="30162">Add Season Number</string>
<string id="30163">Flatten Seasons</string>
<string id="30164">Direct Play - HTTP</string>
<string id="30165">Direct Play</string>
<string id="30165">Direct Play</string><!-- Verified -->
<string id="30166">Transcoding</string>
<string id="30167">Server Detection Succeeded</string>
<string id="30168">Found server</string>
<string id="30169">Address : </string>
<string id="30169">Address:</string>
<!-- Video nodes -->
<string id="30170">Recently Added TV Shows</string><!-- Verified -->
@ -171,38 +114,24 @@
<string id="30194">TV Genres</string>
<string id="30195">TV Networks</string>
<string id="30196">TV Actors</string>
<string id="30197">Playlists</string>
<string id="30198">Search</string>
<string id="30199">Set Views</string>
<string id="30200">Select User</string>
<string id="30201">Profiling enabled.</string>
<string id="30202">Please remember to turn off when finished testing.</string>
<string id="30203">Error in ArtworkRotationThread</string>
<string id="30197">Playlists</string>
<string id="30199">Set Views</string>
<string id="30200">Select User</string><!-- Verified -->
<string id="30204">Unable to connect to server</string>
<string id="30205">Error in LoadMenuOptionsThread</string>
<string id="30206">Enable Playlists Loader (Requires Restart)</string>
<string id="30207">Songs</string>
<string id="30208">Albums</string>
<string id="30209">Album Artists</string>
<string id="30210">Artists</string>
<string id="30211">Music Genres</string>
<string id="30212">Enable Theme Videos (Requires Restart)</string>
<string id="30213"> - Loop Theme Videos</string>
<string id="30216">AutoPlay remaining episodes in a season</string>
<string id="30218">Compress Artwork</string>
<string id="30220">Latest </string>
<string id="30221">In Progress </string>
<string id="30222">NextUp </string>
<string id="30220">Latest</string>
<string id="30221">In Progress</string>
<string id="30222">NextUp</string>
<string id="30223">User Views</string>
<string id="30224">Report Metrics</string>
<string id="30225">Use Kodi Sorting</string>
<string id="30226">Runtime</string>
<string id="30227">Random Movies</string>
<string id="30228">Random Episodes</string>
<string id="30229">Random Items</string><!-- Verified -->
@ -214,15 +143,9 @@
<string id="30238">Sync Movie BoxSets</string>
<string id="30239">Reset local Kodi database</string><!-- Verified -->
<string id="30240">Enable watched/resume status sync</string>
<string id="30241">DB Sync Indication:</string>
<string id="30242">Play Count Sync Indication:</string>
<string id="30243">Enable HTTPS</string><!-- Verified -->
<string id="30245">Force Transcoding Codecs</string>
<string id="30246">Enable Netflix style next up notification</string>
<string id="30247"> - The number of seconds before the end to show the notification</string>
<string id="30248">Show Emby Info dialog on play/select action</string>
<string id="30249">Enable server connection message on startup</string><!-- Verified -->
<string id="30251">Recently added Home Videos</string><!-- Verified -->
@ -252,14 +175,13 @@
<!-- contextmenu -->
<string id="30401">Emby options</string>
<string id="30402">Clear like for this item</string>
<string id="30403">Like this item</string>
<string id="30404">Dislike this item</string>
<string id="30405">Add to Emby favorites</string>
<string id="30406">Remove from Emby favorites</string>
<string id="30407">Set custom song rating</string>
<string id="30408">Emby addon settings</string>
<string id="30409">Delete item from the server</string>
<string id="30410">Refresh this item</string>
<string id="30411">Set custom song rating (0-5)</string>
<!-- add-on settings -->
<string id="30500">Verify Host SSL Certificate</string>
@ -299,7 +221,8 @@
<string id="30534">Server messages</string>
<string id="30535">Generate a new device Id</string>
<string id="30536">Sync when screensaver is deactivated</string>
<string id="30537">Force Transcode Hi10P</string>
<string id="30537">Force Transcode Hi10P</string>
<string id="30538">Disabled</string>
<!-- service add-on -->
<string id="33000">Welcome</string>
@ -337,4 +260,60 @@
<string id="33032">Failed to generate a new device Id. See your logs for more information.</string>
<string id="33033">A new device Id has been generated. Kodi will now restart.</string>
</strings>
<string id="33034">Proceed with the following server?</string>
<string id="33035">Caution! If you choose Native mode, certain Emby features will be missing, such as: Emby cinema mode, direct stream/transcode options and parental access schedule.</string>
<string id="33036">Addon (Default)</string>
<string id="33037">Native (Direct Paths)</string>
<string id="33038">Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time.</string>
<string id="33039">Disable Emby music library?</string>
<string id="33040">Direct stream the music library? Select this option if the music library will be remotely accessed.</string>
<string id="33041">Delete file(s) from Emby Server? This will also delete the file(s) from disk!</string>
<string id="33042">Running the caching process may take some time. Continue anyway?</string>
<string id="33043">Artwork cache sync</string>
<string id="33044">Reset existing artwork cache?</string>
<string id="33045">Updating artwork cache:</string>
<string id="33046">Waiting for all threads to exit:</string>
<string id="33047">Kodi can't locate file:</string>
<string id="33048">You may need to verify your network credentials in the add-on settings or use the Emby path substitution to format your path correctly (Emby dashboard > library). Stop syncing?</string>
<string id="33049">Added:</string>
<string id="33050">If you fail to log in too many times, the Emby server might lock your account. Proceed anyway?</string>
<string id="33051">Live TV Channels (experimental)</string>
<string id="33052">Live TV Recordings (experimental)</string>
<string id="33053">Settings</string>
<string id="33054">Add user to session</string>
<string id="33055">Refresh Emby playlists/Video nodes</string>
<string id="33056">Perform manual sync</string>
<string id="33057">Repair local database (force update all content)</string>
<string id="33058">Perform local database reset</string>
<string id="33059">Cache all artwork</string>
<string id="33060">Sync Emby Theme Media to Kodi</string>
<string id="33061">Add/Remove user from the session</string>
<string id="33062">Add user</string>
<string id="33063">Remove user</string>
<string id="33064">Remove user from the session</string>
<string id="33065">Success!</string>
<string id="33066">Removed from viewing session:</string>
<string id="33067">Added to viewing session:</string>
<string id="33068">Unable to add/remove user from the session.</string>
<string id="33069">The task succeeded</string>
<string id="33070">The task failed</string>
<string id="33071">Direct Stream</string>
<string id="33072">Playback method for your themes</string>
<string id="33073">The settings file does not exist in TV Tunes. Change a setting and run the task again.</string>
<string id="33074">Are you sure you want to reset your local Kodi database?</string>
<string id="33075">Modify/Remove network credentials</string>
<string id="33076">Modify</string>
<string id="33077">Remove</string>
<string id="33078">Removed:</string>
<string id="33079">Enter the network username</string>
<string id="33080">Enter the network password</string>
<string id="33081">Added network credentials for:</string>
<string id="33082">Input the server name or IP address as indicated in your emby library paths. For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC"</string>
<string id="33083">Modify the server name or IP address</string>
<string id="33084">Enter the server name or IP address</string>
<string id="33085">Could not reset the database. Try again.</string>
<string id="33086">Remove all cached artwork?</string>
<string id="33087">Reset all Emby add-on settings?</string>
<string id="33088">Database reset has completed, Kodi will now restart to apply the changes.</string>
</strings>

View file

@ -5,7 +5,7 @@
##################################################################################################
import clientinfo
import utils
from utils import Logging, settings
##################################################################################################
@ -13,17 +13,16 @@ import utils
class API():
def __init__(self, item):
global log
log = Logging(self.__class__.__name__).log
# item is the api response
self.item = item
self.clientinfo = clientinfo.ClientInfo()
self.addonName = self.clientinfo.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getUserData(self):
# Default
@ -223,7 +222,7 @@ class API():
resume = 0
if resume_seconds:
resume = round(float(resume_seconds), 6)
jumpback = int(utils.settings('resumeJumpBack'))
jumpback = int(settings('resumeJumpBack'))
if resume > jumpback:
# To avoid negative bookmark
resume = resume - jumpback

View file

@ -12,9 +12,9 @@ import xbmc
import xbmcgui
import xbmcvfs
import utils
import clientinfo
import image_cache_thread
from utils import Logging, window, settings, language as lang, kodiSQL
#################################################################################################
@ -29,24 +29,25 @@ class Artwork():
imageCacheThreads = []
imageCacheLimitThreads = 0
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.clientinfo = clientinfo.ClientInfo()
self.addonName = self.clientinfo.getAddonName()
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit"))
self.enableTextureCache = settings('enableTextureCache') == "true"
self.imageCacheLimitThreads = int(settings('imageCacheLimit'))
self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5)
utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1)
log("Using Image Cache Thread Count: %s" % self.imageCacheLimitThreads, 1)
if not self.xbmc_port and self.enableTextureCache:
self.setKodiWebServerDetails()
self.userId = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userId)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
self.userId = window('emby_currUser')
self.server = window('emby_server%s' % self.userId)
def double_urlencode(self, text):
@ -56,8 +57,8 @@ class Artwork():
return text
def single_urlencode(self, text):
text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string
# urlencode needs a utf- string
text = urllib.urlencode({'blahblahblah':text.encode("utf-8")})
text = text[13:]
return text.decode("utf-8") #return the result again as unicode
@ -164,130 +165,138 @@ class Artwork():
except TypeError:
pass
def FullTextureCacheSync(self):
def fullTextureCacheSync(self):
# This method will sync all Kodi artwork to textures13.db
# and cache them locally. This takes diskspace!
dialog = xbmcgui.Dialog()
if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"):
if not dialog.yesno(
heading=lang(29999),
line1=lang(33042)):
return
self.logMsg("Doing Image Cache Sync", 1)
log("Doing Image Cache Sync", 1)
dialog = xbmcgui.DialogProgress()
dialog.create("Emby for Kodi", "Image Cache Sync")
pdialog = xbmcgui.DialogProgress()
pdialog.create(lang(29999), lang(33043))
# ask to rest all existing or not
if xbmcgui.Dialog().yesno("Image Texture Cache", "Reset all existing cache data first?", ""):
self.logMsg("Resetting all cache data first", 1)
if dialog.yesno(lang(29999), lang(33044)):
log("Resetting all cache data first.", 1)
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
path = xbmc.translatePath('special://thumbnails/').decode('utf-8')
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
if os.path.supports_unicode_filenames:
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
path = os.path.join(path+dir.decode('utf-8'),file.decode('utf-8'))
xbmcvfs.delete(path)
else:
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
# remove all existing data from texture DB
textureconnection = utils.kodiSQL('texture')
texturecursor = textureconnection.cursor()
texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = texturecursor.fetchall()
connection = kodiSQL('texture')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
texturecursor.execute("DELETE FROM " + tableName)
textureconnection.commit()
texturecursor.close()
if tableName != "version":
cursor.execute("DELETE FROM " + tableName)
connection.commit()
cursor.close()
# Cache all entries in video DB
connection = utils.kodiSQL('video')
connection = kodiSQL('video')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
result = cursor.fetchall()
total = len(result)
count = 1
percentage = 0
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
for url in result:
if dialog.iscanceled():
break
percentage = int((float(count) / float(total))*100)
textMessage = str(count) + " of " + str(total) + " (" + str(len(self.imageCacheThreads)) + ")"
dialog.update(percentage, "Updating Image Cache: " + textMessage)
self.CacheTexture(url[0])
count += 1
log("Image cache sync about to process %s images" % total, 1)
cursor.close()
count = 0
for url in result:
if pdialog.iscanceled():
break
percentage = int((float(count) / float(total))*100)
message = "%s of %s (%s)" % (count, total, self.imageCacheThreads)
pdialog.update(percentage, "%s %s" % (lang(33045), message))
self.cacheTexture(url[0])
count += 1
# Cache all entries in music DB
connection = utils.kodiSQL('music')
connection = kodiSQL('music')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
total = len(result)
count = 1
percentage = 0
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
for url in result:
if dialog.iscanceled():
break
percentage = int((float(count) / float(total))*100)
textMessage = str(count) + " of " + str(total)
dialog.update(percentage, "Updating Image Cache: " + textMessage)
self.CacheTexture(url[0])
count += 1
log("Image cache sync about to process %s images" % total, 1)
cursor.close()
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
self.logMsg("Waiting for all threads to exit", 1)
while len(self.imageCacheThreads) > 0:
count = 0
for url in result:
if pdialog.iscanceled():
break
percentage = int((float(count) / float(total))*100)
message = "%s of %s" % (count, total)
pdialog.update(percentage, "%s %s" % (lang(33045), message))
self.cacheTexture(url[0])
count += 1
pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads)))
log("Waiting for all threads to exit", 1)
while len(self.imageCacheThreads):
for thread in self.imageCacheThreads:
if thread.isFinished:
self.imageCacheThreads.remove(thread)
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1)
pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads)))
log("Waiting for all threads to exit: %s" % len(self.imageCacheThreads), 1)
xbmc.sleep(500)
dialog.close()
pdialog.close()
def addWorkerImageCacheThread(self, urlToAdd):
def addWorkerImageCacheThread(self, url):
while(True):
while True:
# removed finished
for thread in self.imageCacheThreads:
if thread.isFinished:
self.imageCacheThreads.remove(thread)
# add a new thread or wait and retry if we hit our limit
if(len(self.imageCacheThreads) < self.imageCacheLimitThreads):
if len(self.imageCacheThreads) < self.imageCacheLimitThreads:
newThread = image_cache_thread.image_cache_thread()
newThread.setUrl(self.double_urlencode(urlToAdd))
newThread.setUrl(self.double_urlencode(url))
newThread.setHost(self.xbmc_host, self.xbmc_port)
newThread.setAuth(self.xbmc_username, self.xbmc_password)
newThread.start()
self.imageCacheThreads.append(newThread)
return
else:
self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2)
log("Waiting for empty queue spot: %s" % len(self.imageCacheThreads), 2)
xbmc.sleep(50)
def CacheTexture(self, url):
def cacheTexture(self, url):
# Cache a single image url to the texture cache
if url and self.enableTextureCache:
self.logMsg("Processing: %s" % url, 2)
log("Processing: %s" % url, 2)
if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None):
#Add image to texture cache by simply calling it at the http endpoint
if not self.imageCacheLimitThreads:
# Add image to texture cache by simply calling it at the http endpoint
url = self.double_urlencode(url)
try: # Extreme short timeouts so we will have a exception.
response = requests.head(
url=(
"http://%s:%s/image/image://%s"
url=("http://%s:%s/image/image://%s"
% (self.xbmc_host, self.xbmc_port, url)),
auth=(self.xbmc_username, self.xbmc_password),
timeout=(0.01, 0.01))
@ -397,7 +406,7 @@ class Artwork():
except TypeError: # Add the artwork
cacheimage = True
self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
log("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
query = (
'''
@ -413,13 +422,12 @@ class Artwork():
cacheimage = True
# Only for the main backdrop, poster
if (utils.window('emby_initialScan') != "true" and
if (window('emby_initialScan') != "true" and
imageType in ("fanart", "poster")):
# Delete current entry before updating with the new one
self.deleteCachedArtwork(url)
self.logMsg(
"Updating Art url for %s kodiId: %s (%s) -> (%s)"
log("Updating Art url for %s kodiId: %s (%s) -> (%s)"
% (imageType, kodiId, url, imageUrl), 1)
query = ' '.join((
@ -434,9 +442,9 @@ class Artwork():
# Cache fanart and poster in Kodi texture cache
if cacheimage and imageType in ("fanart", "poster"):
self.CacheTexture(imageUrl)
self.cacheTexture(imageUrl)
def deleteArtwork(self, kodiid, mediatype, cursor):
def deleteArtwork(self, kodiId, mediaType, cursor):
query = ' '.join((
@ -445,18 +453,18 @@ class Artwork():
"WHERE media_id = ?",
"AND media_type = ?"
))
cursor.execute(query, (kodiid, mediatype,))
cursor.execute(query, (kodiId, mediaType,))
rows = cursor.fetchall()
for row in rows:
url = row[0]
imagetype = row[1]
if imagetype in ("poster", "fanart"):
imageType = row[1]
if imageType in ("poster", "fanart"):
self.deleteCachedArtwork(url)
def deleteCachedArtwork(self, url):
# Only necessary to remove and apply a new backdrop or poster
connection = utils.kodiSQL('texture')
connection = kodiSQL('texture')
cursor = connection.cursor()
try:
@ -464,21 +472,21 @@ class Artwork():
cachedurl = cursor.fetchone()[0]
except TypeError:
self.logMsg("Could not find cached url.", 1)
log("Could not find cached url.", 1)
except OperationalError:
self.logMsg("Database is locked. Skip deletion process.", 1)
log("Database is locked. Skip deletion process.", 1)
else: # Delete thumbnail as well as the entry
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
log("Deleting cached thumbnail: %s" % thumbnails, 1)
xbmcvfs.delete(thumbnails)
try:
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
except OperationalError:
self.logMsg("Issue deleting url from cache. Skipping.", 2)
log("Issue deleting url from cache. Skipping.", 2)
finally:
cursor.close()
@ -501,26 +509,26 @@ class Artwork():
return people
def getUserArtwork(self, itemid, itemtype):
def getUserArtwork(self, itemId, itemType):
# Load user information set by UserClient
image = ("%s/emby/Users/%s/Images/%s?Format=original"
% (self.server, itemid, itemtype))
% (self.server, itemId, itemType))
return image
def getAllArtwork(self, item, parentInfo=False):
itemid = item['Id']
artworks = item['ImageTags']
backdrops = item.get('BackdropImageTags',[])
backdrops = item.get('BackdropImageTags', [])
maxHeight = 10000
maxWidth = 10000
customquery = ""
if utils.settings('compressArt') == "true":
if settings('compressArt') == "true":
customquery = "&Quality=90"
if utils.settings('enableCoverArt') == "false":
if settings('enableCoverArt') == "false":
customquery += "&EnableImageEnhancers=false"
allartworks = {
@ -601,4 +609,4 @@ class Artwork():
% (self.server, parentId, maxWidth, maxHeight, parentTag, customquery))
allartworks['Primary'] = artwork
return allartworks
return allartworks

View file

@ -9,7 +9,7 @@ import xbmc
import xbmcaddon
import xbmcvfs
import utils
from utils import Logging, window, settings
#################################################################################################
@ -19,14 +19,12 @@ class ClientInfo():
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.addon = xbmcaddon.Addon()
self.addonName = self.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getAddonName(self):
# Used for logging
@ -42,11 +40,11 @@ class ClientInfo():
def getDeviceName(self):
if utils.settings('deviceNameOpt') == "false":
if settings('deviceNameOpt') == "false":
# Use Kodi's deviceName
deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8')
else:
deviceName = utils.settings('deviceName')
deviceName = settings('deviceName')
deviceName = deviceName.replace("\"", "_")
deviceName = deviceName.replace("/", "_")
@ -71,16 +69,18 @@ class ClientInfo():
def getDeviceId(self, reset=False):
clientId = utils.window('emby_deviceId')
clientId = window('emby_deviceId')
if clientId:
return clientId
addon_path = self.addon.getAddonInfo('path').decode('utf-8')
if os.path.supports_unicode_filenames:
GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8')
path = os.path.join(addon_path, "machine_guid")
else:
GUID_file = xbmc.translatePath(os.path.join(addon_path.encode("utf-8"), "machine_guid")).decode('utf-8')
path = os.path.join(addon_path.encode('utf-8'), "machine_guid")
GUID_file = xbmc.translatePath(path).decode('utf-8')
if reset and xbmcvfs.exists(GUID_file):
# Reset the file
xbmcvfs.delete(GUID_file)
@ -88,14 +88,14 @@ class ClientInfo():
GUID = xbmcvfs.File(GUID_file)
clientId = GUID.read()
if not clientId:
self.logMsg("Generating a new deviceid...", 1)
log("Generating a new deviceid...", 1)
clientId = str("%012X" % uuid4())
GUID = xbmcvfs.File(GUID_file, 'w')
GUID.write(clientId)
GUID.close()
self.logMsg("DeviceId loaded: %s" % clientId, 1)
utils.window('emby_deviceId', value=clientId)
log("DeviceId loaded: %s" % clientId, 1)
window('emby_deviceId', value=clientId)
return clientId

View file

@ -6,8 +6,8 @@ import json
import requests
import logging
import utils
import clientinfo
from utils import Logging, window
##################################################################################################
@ -34,28 +34,26 @@ class ConnectUtils():
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def setUserId(self, userId):
# Reserved for userclient only
self.userId = userId
self.logMsg("Set connect userId: %s" % userId, 2)
log("Set connect userId: %s" % userId, 2)
def setServer(self, server):
# Reserved for userclient only
self.server = server
self.logMsg("Set connect server: %s" % server, 2)
log("Set connect server: %s" % server, 2)
def setToken(self, token):
# Reserved for userclient only
self.token = token
self.logMsg("Set connect token: %s" % token, 2)
log("Set connect token: %s" % token, 2)
def startSession(self):
@ -73,7 +71,7 @@ class ConnectUtils():
if self.sslclient is not None:
verify = self.sslclient
except:
self.logMsg("Could not load SSL settings.", 1)
log("Could not load SSL settings.", 1)
# Start session
self.c = requests.Session()
@ -83,13 +81,13 @@ class ConnectUtils():
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
self.logMsg("Requests session started on: %s" % self.server, 1)
log("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
self.c.close()
except Exception as e:
self.logMsg("Requests session could not be terminated: %s" % e, 1)
log("Requests session could not be terminated: %s" % e, 1)
def getHeader(self, authenticate=True):
@ -103,7 +101,7 @@ class ConnectUtils():
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': "application/json"
}
self.logMsg("Header: %s" % header, 1)
log("Header: %s" % header, 1)
else:
token = self.token
@ -115,17 +113,17 @@ class ConnectUtils():
'X-Application': "Kodi/%s" % version,
'X-Connect-UserToken': token
}
self.logMsg("Header: %s" % header, 1)
log("Header: %s" % header, 1)
return header
def doUrl(self, url, data=None, postBody=None, rtype="GET",
parameters=None, authenticate=True, timeout=None):
window = utils.window
self.logMsg("=== ENTER connectUrl ===", 2)
log("=== ENTER connectUrl ===", 2)
default_link = ""
if timeout is None:
timeout = self.timeout
@ -209,25 +207,25 @@ class ConnectUtils():
verify=verifyssl)
##### THE RESPONSE #####
self.logMsg(r.url, 1)
self.logMsg(r, 1)
log(r.url, 1)
log(r, 1)
if r.status_code == 204:
# No body in the response
self.logMsg("====== 204 Success ======", 1)
log("====== 204 Success ======", 1)
elif r.status_code == requests.codes.ok:
try:
# UNICODE - JSON object
r = r.json()
self.logMsg("====== 200 Success ======", 1)
self.logMsg("Response: %s" % r, 1)
log("====== 200 Success ======", 1)
log("Response: %s" % r, 1)
return r
except:
if r.headers.get('content-type') != "text/html":
self.logMsg("Unable to convert the response for: %s" % url, 1)
log("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
@ -238,8 +236,8 @@ class ConnectUtils():
pass
except requests.exceptions.ConnectTimeout as e:
self.logMsg("Server timeout at: %s" % url, 0)
self.logMsg(e, 1)
log("Server timeout at: %s" % url, 0)
log(e, 1)
except requests.exceptions.HTTPError as e:
@ -255,11 +253,11 @@ class ConnectUtils():
pass
except requests.exceptions.SSLError as e:
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
self.logMsg(e, 1)
log("Invalid SSL certificate for: %s" % url, 0)
log(e, 1)
except requests.exceptions.RequestException as e:
self.logMsg("Unknown error connecting to: %s" % url, 0)
self.logMsg(e, 1)
log("Unknown error connecting to: %s" % url, 0)
log(e, 1)
return default_link
return default_link

View file

@ -9,14 +9,15 @@ import logging
import xbmc
import xbmcgui
import utils
import clientinfo
from utils import Logging, window, settings
##################################################################################################
# Disable requests logging
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
#logging.getLogger('requests').setLevel(logging.WARNING)
##################################################################################################
@ -36,40 +37,38 @@ class DownloadUtils():
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def setUsername(self, username):
# Reserved for userclient only
self.username = username
self.logMsg("Set username: %s" % username, 2)
log("Set username: %s" % username, 2)
def setUserId(self, userId):
# Reserved for userclient only
self.userId = userId
self.logMsg("Set userId: %s" % userId, 2)
log("Set userId: %s" % userId, 2)
def setServer(self, server):
# Reserved for userclient only
self.server = server
self.logMsg("Set server: %s" % server, 2)
log("Set server: %s" % server, 2)
def setToken(self, token):
# Reserved for userclient only
self.token = token
self.logMsg("Set token: %s" % token, 2)
log("Set token: %s" % token, 2)
def setSSL(self, ssl, sslclient):
# Reserved for userclient only
self.sslverify = ssl
self.sslclient = sslclient
self.logMsg("Verify SSL host certificate: %s" % ssl, 2)
self.logMsg("SSL client side certificate: %s" % sslclient, 2)
log("Verify SSL host certificate: %s" % ssl, 2)
log("SSL client side certificate: %s" % sslclient, 2)
def postCapabilities(self, deviceId):
@ -94,11 +93,11 @@ class DownloadUtils():
)
}
self.logMsg("Capabilities URL: %s" % url, 2)
self.logMsg("Postdata: %s" % data, 2)
log("Capabilities URL: %s" % url, 2)
log("Postdata: %s" % data, 2)
self.downloadUrl(url, postBody=data, action_type="POST")
self.logMsg("Posted capabilities to %s" % self.server, 2)
log("Posted capabilities to %s" % self.server, 2)
# Attempt at getting sessionId
url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
@ -107,20 +106,19 @@ class DownloadUtils():
sessionId = result[0]['Id']
except (KeyError, TypeError):
self.logMsg("Failed to retrieve sessionId.", 1)
log("Failed to retrieve sessionId.", 1)
else:
self.logMsg("Session: %s" % result, 2)
self.logMsg("SessionId: %s" % sessionId, 1)
utils.window('emby_sessionId', value=sessionId)
log("Session: %s" % result, 2)
log("SessionId: %s" % sessionId, 1)
window('emby_sessionId', value=sessionId)
# Post any permanent additional users
additionalUsers = utils.settings('additionalUsers')
additionalUsers = settings('additionalUsers')
if additionalUsers:
additionalUsers = additionalUsers.split(',')
self.logMsg(
"List of permanent users added to the session: %s"
log("List of permanent users added to the session: %s"
% additionalUsers, 1)
# Get the user list from server to get the userId
@ -158,7 +156,7 @@ class DownloadUtils():
if self.sslclient is not None:
verify = self.sslclient
except:
self.logMsg("Could not load SSL settings.", 1)
log("Could not load SSL settings.", 1)
# Start session
self.s = requests.Session()
@ -168,18 +166,18 @@ class DownloadUtils():
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
self.logMsg("Requests session started on: %s" % self.server, 1)
log("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
self.s.close()
except:
self.logMsg("Requests session could not be terminated.", 1)
log("Requests session could not be terminated.", 1)
def getHeader(self, authenticate=True):
deviceName = self.clientInfo.getDeviceName()
deviceName = utils.normalize_string(deviceName.encode('utf-8'))
deviceName = deviceName.encode('utf-8')
deviceId = self.clientInfo.getDeviceId()
version = self.clientInfo.getVersion()
@ -195,7 +193,7 @@ class DownloadUtils():
'Accept-Charset': 'UTF-8,*',
'Authorization': auth
}
self.logMsg("Header: %s" % header, 2)
log("Header: %s" % header, 2)
else:
userId = self.userId
@ -212,19 +210,20 @@ class DownloadUtils():
'Authorization': auth,
'X-MediaBrowser-Token': token
}
self.logMsg("Header: %s" % header, 2)
log("Header: %s" % header, 2)
return header
def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True):
def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None,
authenticate=True):
self.logMsg("=== ENTER downloadUrl ===", 2)
log("=== ENTER downloadUrl ===", 2)
default_link = ""
try:
# If user is authenticated
if (authenticate):
if authenticate:
# Get requests session
try:
s = self.s
@ -243,18 +242,18 @@ class DownloadUtils():
except AttributeError:
# request session does not exists
# Get user information
self.userId = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userId)
self.token = utils.window('emby_accessToken%s' % self.userId)
self.userId = window('emby_currUser')
self.server = window('emby_server%s' % self.userId)
self.token = window('emby_accessToken%s' % self.userId)
header = self.getHeader()
verifyssl = False
cert = None
# IF user enables ssl verification
if utils.settings('sslverify') == "true":
if settings('sslverify') == "true":
verifyssl = True
if utils.settings('sslcert') != "None":
verifyssl = utils.settings('sslcert')
if settings('sslcert') != "None":
verifyssl = settings('sslcert')
# Replace for the real values
url = url.replace("{server}", self.server)
@ -314,23 +313,23 @@ class DownloadUtils():
verify=verifyssl)
##### THE RESPONSE #####
self.logMsg(r.url, 2)
log(r.url, 2)
if r.status_code == 204:
# No body in the response
self.logMsg("====== 204 Success ======", 2)
log("====== 204 Success ======", 2)
elif r.status_code == requests.codes.ok:
try:
# UNICODE - JSON object
r = r.json()
self.logMsg("====== 200 Success ======", 2)
self.logMsg("Response: %s" % r, 2)
log("====== 200 Success ======", 2)
log("Response: %s" % r, 2)
return r
except:
if r.headers.get('content-type') != "text/html":
self.logMsg("Unable to convert the response for: %s" % url, 1)
log("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
@ -338,26 +337,26 @@ class DownloadUtils():
except requests.exceptions.ConnectionError as e:
# Make the addon aware of status
if utils.window('emby_online') != "false":
self.logMsg("Server unreachable at: %s" % url, 0)
self.logMsg(e, 2)
utils.window('emby_online', value="false")
if window('emby_online') != "false":
log("Server unreachable at: %s" % url, 0)
log(e, 2)
window('emby_online', value="false")
except requests.exceptions.ConnectTimeout as e:
self.logMsg("Server timeout at: %s" % url, 0)
self.logMsg(e, 1)
log("Server timeout at: %s" % url, 0)
log(e, 1)
except requests.exceptions.HTTPError as e:
if r.status_code == 401:
# Unauthorized
status = utils.window('emby_serverStatus')
status = window('emby_serverStatus')
if 'X-Application-Error-Code' in r.headers:
# Emby server errors
if r.headers['X-Application-Error-Code'] == "ParentalControl":
# Parental control - access restricted
utils.window('emby_serverStatus', value="restricted")
window('emby_serverStatus', value="restricted")
xbmcgui.Dialog().notification(
heading="Emby server",
message="Access restricted.",
@ -371,8 +370,8 @@ class DownloadUtils():
elif status not in ("401", "Auth"):
# Tell userclient token has been revoked.
utils.window('emby_serverStatus', value="401")
self.logMsg("HTTP Error: %s" % e, 0)
window('emby_serverStatus', value="401")
log("HTTP Error: %s" % e, 0)
xbmcgui.Dialog().notification(
heading="Error connecting",
message="Unauthorized.",
@ -387,11 +386,11 @@ class DownloadUtils():
pass
except requests.exceptions.SSLError as e:
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
self.logMsg(e, 1)
log("Invalid SSL certificate for: %s" % url, 0)
log(e, 1)
except requests.exceptions.RequestException as e:
self.logMsg("Unknown error connecting to: %s" % url, 0)
self.logMsg(e, 1)
log("Unknown error connecting to: %s" % url, 0)
log(e, 1)
return default_link

View file

@ -2,8 +2,10 @@
#################################################################################################
import utils
from sqlite3 import OperationalError
import clientinfo
from utils import Logging
#################################################################################################
@ -13,15 +15,14 @@ class Embydb_Functions():
def __init__(self, embycursor):
global log
log = Logging(self.__class__.__name__).log
self.embycursor = embycursor
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getViews(self):

View file

@ -24,28 +24,27 @@ import playlist
import playbackutils as pbutils
import playutils
import api
from utils import Logging, window, settings, language as lang
log = Logging('Entrypoint').log
#################################################################################################
def doPlayback(itemid, dbid):
def doPlayback(itemId, dbId):
emby = embyserver.Read_EmbyServer()
item = emby.getItem(itemid)
pbutils.PlaybackUtils(item).play(itemid, dbid)
item = emby.getItem(itemId)
pbutils.PlaybackUtils(item).play(itemId, dbId)
##### DO RESET AUTH #####
def resetAuth():
# User tried login and failed too many times
resp = xbmcgui.Dialog().yesno(
heading="Warning",
line1=(
"Emby might lock your account if you fail to log in too many times. "
"Proceed anyway?"))
if resp == 1:
utils.logMsg("EMBY", "Reset login attempts.", 1)
utils.window('emby_serverStatus', value="Auth")
heading=lang(30132),
line1=lang(33050))
if resp:
log("Reset login attempts.", 1)
window('emby_serverStatus', value="Auth")
else:
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
@ -57,65 +56,80 @@ def addDirectoryItem(label, path, folder=True):
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
def doMainListing():
xbmcplugin.setContent(int(sys.argv[1]), 'files')
# Get emby nodes from the window props
embyprops = utils.window('Emby.nodes.total')
embyprops = window('Emby.nodes.total')
if embyprops:
totalnodes = int(embyprops)
for i in range(totalnodes):
path = utils.window('Emby.nodes.%s.index' % i)
path = window('Emby.nodes.%s.index' % i)
if not path:
path = utils.window('Emby.nodes.%s.content' % i)
label = utils.window('Emby.nodes.%s.title' % i)
node_type = utils.window('Emby.nodes.%s.type' % i)
#because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing.
#for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window
if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node_type == "photos":
addDirectoryItem(label, path)
elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node_type != "photos":
addDirectoryItem(label, path)
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
addDirectoryItem(label, path)
path = window('Emby.nodes.%s.content' % i)
label = window('Emby.nodes.%s.title' % i)
node = window('Emby.nodes.%s.type' % i)
''' because we do not use seperate entrypoints for each content type,
we need to figure out which items to show in each listing.
for now we just only show picture nodes in the picture library
video nodes in the video library and all nodes in any other window '''
#experimental live tv nodes
addDirectoryItem("Live Tv Channels (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root")
addDirectoryItem("Live Tv Recordings (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root")
'''if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node == "photos":
addDirectoryItem(label, path)
elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)")
and node != "photos":
addDirectoryItem(label, path)
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) |
Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
addDirectoryItem(label, path)'''
if path:
if xbmc.getCondVisibility("Window.IsActive(Pictures)") and node == "photos":
addDirectoryItem(label, path)
elif xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node != "photos":
addDirectoryItem(label, path)
elif not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
addDirectoryItem(label, path)
# experimental live tv nodes
if not xbmc.getCondVisibility("Window.IsActive(Pictures)"):
addDirectoryItem(lang(33051),
"plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root")
addDirectoryItem(lang(33052),
"plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root")
# some extra entries for settings and stuff. TODO --> localize the labels
addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords")
addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings")
addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
addDirectoryItem("Refresh Emby playlists/nodes", "plugin://plugin.video.emby/?mode=refreshplaylist")
addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync")
addDirectoryItem("Repair local database (force update all content)", "plugin://plugin.video.emby/?mode=repair")
addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset")
addDirectoryItem("Cache all images to Kodi texture cache", "plugin://plugin.video.emby/?mode=texturecache")
addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia")
addDirectoryItem(lang(30517), "plugin://plugin.video.emby/?mode=passwords")
addDirectoryItem(lang(33053), "plugin://plugin.video.emby/?mode=settings")
addDirectoryItem(lang(33054), "plugin://plugin.video.emby/?mode=adduser")
addDirectoryItem(lang(33055), "plugin://plugin.video.emby/?mode=refreshplaylist")
addDirectoryItem(lang(33056), "plugin://plugin.video.emby/?mode=manualsync")
addDirectoryItem(lang(33057), "plugin://plugin.video.emby/?mode=repair")
addDirectoryItem(lang(33058), "plugin://plugin.video.emby/?mode=reset")
addDirectoryItem(lang(33059), "plugin://plugin.video.emby/?mode=texturecache")
addDirectoryItem(lang(33060), "plugin://plugin.video.emby/?mode=thememedia")
xbmcplugin.endOfDirectory(int(sys.argv[1]))
##### Generate a new deviceId
def resetDeviceId():
dialog = xbmcgui.Dialog()
language = utils.language
deviceId_old = utils.window('emby_deviceId')
deviceId_old = window('emby_deviceId')
try:
utils.window('emby_deviceId', clear=True)
window('emby_deviceId', clear=True)
deviceId = clientinfo.ClientInfo().getDeviceId(reset=True)
except Exception as e:
utils.logMsg("EMBY", "Failed to generate a new device Id: %s" % e, 1)
log("Failed to generate a new device Id: %s" % e, 1)
dialog.ok(
heading="Emby for Kodi",
line1=language(33032))
heading=lang(29999),
line1=lang(33032))
else:
utils.logMsg("EMBY", "Successfully removed old deviceId: %s New deviceId: %s"
% (deviceId_old, deviceId), 1)
log("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1)
dialog.ok(
heading="Emby for Kodi",
line1=language(33033))
heading=lang(29999),
line1=lang(33033))
xbmc.executebuiltin('RestartApp')
##### Delete Item
@ -123,50 +137,46 @@ def deleteItem():
# Serves as a keymap action
if xbmc.getInfoLabel('ListItem.Property(embyid)'): # If we already have the embyid
embyid = xbmc.getInfoLabel('ListItem.Property(embyid)')
itemId = xbmc.getInfoLabel('ListItem.Property(embyid)')
else:
dbid = xbmc.getInfoLabel('ListItem.DBID')
itemtype = xbmc.getInfoLabel('ListItem.DBTYPE')
dbId = xbmc.getInfoLabel('ListItem.DBID')
itemType = xbmc.getInfoLabel('ListItem.DBTYPE')
if not itemtype:
if not itemType:
if xbmc.getCondVisibility('Container.Content(albums)'):
itemtype = "album"
itemType = "album"
elif xbmc.getCondVisibility('Container.Content(artists)'):
itemtype = "artist"
itemType = "artist"
elif xbmc.getCondVisibility('Container.Content(songs)'):
itemtype = "song"
itemType = "song"
elif xbmc.getCondVisibility('Container.Content(pictures)'):
itemtype = "picture"
itemType = "picture"
else:
utils.logMsg("EMBY delete", "Unknown type, unable to proceed.", 1)
log("Unknown type, unable to proceed.", 1)
return
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
item = emby_db.getItem_byKodiId(dbid, itemtype)
item = emby_db.getItem_byKodiId(dbId, itemType)
embycursor.close()
try:
embyid = item[0]
except TypeError:
utils.logMsg("EMBY delete", "Unknown embyId, unable to proceed.", 1)
log("Unknown embyId, unable to proceed.", 1)
return
if utils.settings('skipContextMenu') != "true":
if settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file from Emby Server? This will "
"also delete the file(s) from disk!"))
heading=lang(29999),
line1=lang(33041))
if not resp:
utils.logMsg("EMBY delete", "User skipped deletion for: %s." % embyid, 1)
log("User skipped deletion for: %s." % itemId, 1)
return
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
utils.logMsg("EMBY delete", "Deleting request: %s" % embyid, 0)
doUtils.downloadUrl(url, action_type="DELETE")
embyserver.Read_EmbyServer().deleteItem(itemId)
##### ADD ADDITIONAL USERS #####
def addUser():
@ -176,7 +186,7 @@ def addUser():
clientInfo = clientinfo.ClientInfo()
deviceId = clientInfo.getDeviceId()
deviceName = clientInfo.getDeviceName()
userid = utils.window('emby_currUser')
userid = window('emby_currUser')
dialog = xbmcgui.Dialog()
# Get session
@ -203,7 +213,7 @@ def addUser():
# Display dialog if there's additional users
if additionalUsers:
option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"])
option = dialog.select(lang(33061), [lang(33062), lang(33063)])
# Users currently in the session
additionalUserlist = {}
additionalUsername = []
@ -216,21 +226,21 @@ def addUser():
if option == 1:
# User selected Remove user
resp = dialog.select("Remove user from the session", additionalUsername)
resp = dialog.select(lang(33064), additionalUsername)
if resp > -1:
selected = additionalUsername[resp]
selected_userId = additionalUserlist[selected]
url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
doUtils.downloadUrl(url, postBody={}, action_type="DELETE")
dialog.notification(
heading="Success!",
message="%s removed from viewing session" % selected,
heading=lang(29999),
message="%s %s" % (lang(33066), selected),
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000)
# clear picture
position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId)
utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
position = window('EmbyAdditionalUserPosition.%s' % selected_userId)
window('EmbyAdditionalUserImage.%s' % position, clear=True)
return
else:
return
@ -247,7 +257,7 @@ def addUser():
return
# Subtract any additional users
utils.logMsg("EMBY", "Displaying list of users: %s" % users)
log("Displaying list of users: %s" % users)
resp = dialog.select("Add user to the session", users)
# post additional user
if resp > -1:
@ -256,25 +266,25 @@ def addUser():
url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
doUtils.downloadUrl(url, postBody={}, action_type="POST")
dialog.notification(
heading="Success!",
message="%s added to viewing session" % selected,
heading=lang(29999),
message="%s %s" % (lang(33067), selected),
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000)
except:
utils.logMsg("EMBY", "Failed to add user to session.")
log("Failed to add user to session.")
dialog.notification(
heading="Error",
message="Unable to add/remove user from the session.",
heading=lang(29999),
message=lang(33068),
icon=xbmcgui.NOTIFICATION_ERROR)
# Add additional user images
# always clear the individual items first
totalNodes = 10
for i in range(totalNodes):
if not utils.window('EmbyAdditionalUserImage.%s' % i):
if not window('EmbyAdditionalUserImage.%s' % i):
break
utils.window('EmbyAdditionalUserImage.%s' % i, clear=True)
window('EmbyAdditionalUserImage.%s' % i, clear=True)
url = "{server}/emby/Sessions?DeviceId=%s" % deviceId
result = doUtils.downloadUrl(url)
@ -284,9 +294,9 @@ def addUser():
userid = additionaluser['UserId']
url = "{server}/emby/Users/%s?format=json" % userid
result = doUtils.downloadUrl(url)
utils.window('EmbyAdditionalUserImage.%s' % count,
window('EmbyAdditionalUserImage.%s' % count,
value=art.getUserArtwork(result['Id'], 'Primary'))
utils.window('EmbyAdditionalUserPosition.%s' % userid, value=str(count))
window('EmbyAdditionalUserPosition.%s' % userid, value=str(count))
count +=1
##### THEME MUSIC/VIDEOS #####
@ -297,7 +307,7 @@ def getThemeMedia():
playback = None
# Choose playback method
resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"])
resp = dialog.select(lang(33072), [lang(30165), lang(33071)])
if resp == 0:
playback = "DirectPlay"
elif resp == 1:
@ -318,15 +328,11 @@ def getThemeMedia():
tvtunes = xbmcaddon.Addon(id="script.tvtunes")
tvtunes.setSetting('custom_path_enable', "true")
tvtunes.setSetting('custom_path', library)
utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1)
log("TV Tunes custom path is enabled and set.", 1)
else:
# if it does not exist this will not work so warn user
# often they need to edit the settings first for it to be created.
dialog.ok(
heading="Warning",
line1=(
"The settings file does not exist in tvtunes. ",
"Go to the tvtunes addon and change a setting, then come back and re-run."))
dialog.ok(heading=lang(29999), line1=lang(33073))
xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)')
return
@ -442,8 +448,8 @@ def getThemeMedia():
nfo_file.close()
dialog.notification(
heading="Emby for Kodi",
message="Themes added!",
heading=lang(29999),
message=lang(33069),
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
@ -461,17 +467,17 @@ def refreshPlaylist():
# Refresh views
lib.refreshViews()
dialog.notification(
heading="Emby for Kodi",
message="Emby playlists/nodes refreshed",
heading=lang(29999),
message=lang(33069),
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
except Exception as e:
utils.logMsg("EMBY", "Refresh playlists/nodes failed: %s" % e, 1)
log("Refresh playlists/nodes failed: %s" % e, 1)
dialog.notification(
heading="Emby for Kodi",
message="Emby playlists/nodes refresh failed",
heading=lang(29999),
message=lang(33070),
icon=xbmcgui.NOTIFICATION_ERROR,
time=1000,
sound=False)
@ -480,9 +486,9 @@ def refreshPlaylist():
def GetSubFolders(nodeindex):
nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"]
for node in nodetypes:
title = utils.window('Emby.nodes.%s%s.title' %(nodeindex,node))
title = window('Emby.nodes.%s%s.title' %(nodeindex,node))
if title:
path = utils.window('Emby.nodes.%s%s.content' %(nodeindex,node))
path = window('Emby.nodes.%s%s.content' %(nodeindex,node))
addDirectoryItem(title, path)
xbmcplugin.endOfDirectory(int(sys.argv[1]))
@ -510,7 +516,7 @@ def BrowseContent(viewname, browse_type="", folderid=""):
break
if viewname is not None:
utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8')))
log("viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8')))
#set the correct params for the content type
#only proceed if we have a folderid
if folderid:
@ -795,7 +801,7 @@ def getNextUpEpisodes(tagname, limit):
pass
else:
for item in items:
if utils.settings('ignoreSpecialsNextEpisodes') == "true":
if settings('ignoreSpecialsNextEpisodes') == "true":
query = {
'jsonrpc': "2.0",
@ -1043,7 +1049,7 @@ def getExtraFanArt(embyId,embyPath):
if embyId:
#only proceed if we actually have a emby id
utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0)
log("Requesting extrafanart for Id: %s" % embyId, 0)
# We need to store the images locally for this to work
# because of the caching system in xbmc
@ -1072,7 +1078,7 @@ def getExtraFanArt(embyId,embyPath):
xbmcvfs.copy(backdrop, fanartFile)
count += 1
else:
utils.logMsg("EMBY", "Found cached backdrop.", 2)
log("Found cached backdrop.", 2)
# Use existing cached images
dirs, files = xbmcvfs.listdir(fanartDir)
for file in files:
@ -1083,7 +1089,7 @@ def getExtraFanArt(embyId,embyPath):
url=fanartFile,
listitem=li)
except Exception as e:
utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 0)
log("Error getting extrafanart: %s" % e, 0)
# Always do endofdirectory to prevent errors in the logs
xbmcplugin.endOfDirectory(int(sys.argv[1]))

View file

@ -1,8 +1,14 @@
# -*- coding: utf-8 -*-
#################################################################################################
import threading
import utils
import xbmc
import requests
from utils import Logging
#################################################################################################
class image_cache_thread(threading.Thread):
urlToProcess = None
@ -13,28 +19,32 @@ class image_cache_thread(threading.Thread):
xbmc_username = ""
xbmc_password = ""
def __init__(self):
self.monitor = xbmc.Monitor()
global log
log = Logging(self.__class__.__name__).log
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s" % className, msg, lvl)
def setUrl(self, url):
self.urlToProcess = url
def setHost(self, host, port):
self.xbmc_host = host
self.xbmc_port = port
def setAuth(self, user, pwd):
self.xbmc_username = user
self.xbmc_password = pwd
def run(self):
self.logMsg("Image Caching Thread Processing : " + self.urlToProcess, 2)
log("Image Caching Thread Processing: %s" % self.urlToProcess, 2)
try:
response = requests.head(
@ -46,7 +56,5 @@ class image_cache_thread(threading.Thread):
# We don't need the result
except: pass
self.logMsg("Image Caching Thread Exited", 2)
self.isFinished = True
log("Image Caching Thread Exited", 2)
self.isFinished = True

View file

@ -9,10 +9,10 @@ import xbmc
import xbmcgui
import xbmcaddon
import utils
import clientinfo
import downloadutils
import userclient
from utils import Logging, settings, language as lang, passwordsXML
#################################################################################################
@ -22,74 +22,66 @@ class InitialSetup():
def __init__(self):
self.addon = xbmcaddon.Addon()
self.__language__ = self.addon.getLocalizedString
global log
log = Logging(self.__class__.__name__).log
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.addonId = self.clientInfo.getAddonId()
self.doUtils = downloadutils.DownloadUtils()
self.addonId = clientinfo.ClientInfo().getAddonId()
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.userClient = userclient.UserClient()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def setup(self):
# Check server, user, direct paths, music, direct stream if not direct path.
string = self.__language__
addonId = self.addonId
dialog = xbmcgui.Dialog()
##### SERVER INFO #####
self.logMsg("Initial setup called.", 2)
log("Initial setup called.", 2)
server = self.userClient.getServer()
if server:
self.logMsg("Server is already set.", 2)
log("Server is already set.", 2)
return
self.logMsg("Looking for server...", 2)
log("Looking for server...", 2)
server = self.getServerDetails()
self.logMsg("Found: %s" % server, 2)
log("Found: %s" % server, 2)
try:
prefix, ip, port = server.replace("/", "").split(":")
except: # Failed to retrieve server information
self.logMsg("getServerDetails failed.", 1)
log("getServerDetails failed.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
return
else:
server_confirm = xbmcgui.Dialog().yesno(
heading="Emby for Kodi",
line1="Proceed with the following server?",
line2="%s %s" % (string(30169), server))
server_confirm = dialog.yesno(
heading=lang(29999),
line1=lang(33034),
line2="%s %s" % (lang(30169), server))
if server_confirm:
# Correct server found
self.logMsg("Server is selected. Saving the information.", 1)
utils.settings('ipaddress', value=ip)
utils.settings('port', value=port)
log("Server is selected. Saving the information.", 1)
settings('ipaddress', value=ip)
settings('port', value=port)
if prefix == "https":
utils.settings('https', value="true")
settings('https', value="true")
else:
# User selected no or cancelled the dialog
self.logMsg("No server selected.", 1)
log("No server selected.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
return
##### USER INFO #####
self.logMsg("Getting user list.", 1)
url = "%s/emby/Users/Public?format=json" % server
result = self.doUtils.downloadUrl(url, authenticate=False)
log("Getting user list.", 1)
result = self.doUtils("%s/emby/Users/Public?format=json" % server, authenticate=False)
if result == "":
self.logMsg("Unable to connect to %s" % server, 1)
log("Unable to connect to %s" % server, 1)
return
self.logMsg("Response: %s" % result, 2)
log("Response: %s" % result, 2)
# Process the list of users
usernames = []
users_hasPassword = []
@ -103,66 +95,55 @@ class InitialSetup():
name = "%s (secure)" % name
users_hasPassword.append(name)
self.logMsg("Presenting user list: %s" % users_hasPassword, 1)
user_select = xbmcgui.Dialog().select(string(30200), users_hasPassword)
log("Presenting user list: %s" % users_hasPassword, 1)
user_select = dialog.select(lang(30200), users_hasPassword)
if user_select > -1:
selected_user = usernames[user_select]
self.logMsg("Selected user: %s" % selected_user, 1)
utils.settings('username', value=selected_user)
log("Selected user: %s" % selected_user, 1)
settings('username', value=selected_user)
else:
self.logMsg("No user selected.", 1)
log("No user selected.", 1)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId)
return
##### ADDITIONAL PROMPTS #####
dialog = xbmcgui.Dialog()
directPaths = dialog.yesno(
heading="Playback Mode",
line1=(
"Caution! If you choose Native mode, you "
"will lose access to certain Emby features such as: "
"Emby cinema mode, direct stream/transcode options, "
"parental access schedule."),
nolabel="Addon (Default)",
yeslabel="Native (Direct Paths)")
heading=lang(30511),
line1=lang(33035),
nolabel=lang(33036),
yeslabel=lang(33037))
if directPaths:
self.logMsg("User opted to use direct paths.", 1)
utils.settings('useDirectPaths', value="1")
log("User opted to use direct paths.", 1)
settings('useDirectPaths', value="1")
# ask for credentials
credentials = dialog.yesno(
heading="Network credentials",
line1= (
"Add network credentials to allow Kodi access to your "
"content? Note: Skipping this step may generate a message "
"during the initial scan of your content if Kodi can't "
"locate your content."))
heading=lang(30517),
line1= lang(33038))
if credentials:
self.logMsg("Presenting network credentials dialog.", 1)
utils.passwordsXML()
log("Presenting network credentials dialog.", 1)
passwordsXML()
musicDisabled = dialog.yesno(
heading="Music Library",
line1="Disable Emby music library?")
heading=lang(29999),
line1=lang(33039))
if musicDisabled:
self.logMsg("User opted to disable Emby music library.", 1)
utils.settings('enableMusic', value="false")
log("User opted to disable Emby music library.", 1)
settings('enableMusic', value="false")
else:
# Only prompt if the user didn't select direct paths for videos
if not directPaths:
musicAccess = dialog.yesno(
heading="Music Library",
line1=(
"Direct stream the music library? Select "
"this option only if you plan on listening "
"to music outside of your network."))
heading=lang(29999),
line1=lang(33040))
if musicAccess:
self.logMsg("User opted to direct stream music.", 1)
utils.settings('streamMusic', value="true")
log("User opted to direct stream music.", 1)
settings('streamMusic', value="true")
def getServerDetails(self):
self.logMsg("Getting Server Details from Network", 1)
log("Getting Server Details from Network", 1)
MULTI_GROUP = ("<broadcast>", 7359)
MESSAGE = "who is EmbyServer?"
@ -176,15 +157,15 @@ class InitialSetup():
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2)
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2)
log("MultiGroup : %s" % str(MULTI_GROUP), 2)
log("Sending UDP Data: %s" % MESSAGE, 2)
sock.sendto(MESSAGE, MULTI_GROUP)
try:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
self.logMsg("Received Response: %s" % data)
log("Received Response: %s" % data)
except:
self.logMsg("No UDP Response")
log("No UDP Response")
return None
else:
# Get the address

View file

@ -14,11 +14,11 @@ import api
import artwork
import clientinfo
import downloadutils
import utils
import embydb_functions as embydb
import kodidb_functions as kodidb
import read_embyserver as embyserver
import musicutils
from utils import Logging, window, settings, language as lang, kodiSQL
##################################################################################################
@ -28,6 +28,9 @@ class Items(object):
def __init__(self, embycursor, kodicursor):
global log
log = Logging(self.__class__.__name__).log
self.embycursor = embycursor
self.kodicursor = kodicursor
@ -35,23 +38,18 @@ class Items(object):
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
self.directpath = utils.settings('useDirectPaths') == "1"
self.music_enabled = utils.settings('enableMusic') == "true"
self.contentmsg = utils.settings('newContent') == "true"
self.newvideo_time = int(utils.settings('newvideotime'))*1000
self.newmusic_time = int(utils.settings('newmusictime'))*1000
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
self.directpath = settings('useDirectPaths') == "1"
self.music_enabled = settings('enableMusic') == "true"
self.contentmsg = settings('newContent') == "true"
self.newvideo_time = int(settings('newvideotime'))*1000
self.newmusic_time = int(settings('newmusictime'))*1000
self.artwork = artwork.Artwork()
self.emby = embyserver.Read_EmbyServer()
self.emby_db = embydb.Embydb_Functions(embycursor)
self.kodi_db = kodidb.Kodidb_Functions(kodicursor)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def itemsbyId(self, items, process, pdialog=None):
# Process items by itemid. Process can be added, update, userdata, remove
@ -81,7 +79,7 @@ class Items(object):
if total == 0:
return False
self.logMsg("Processing %s: %s" % (process, items), 1)
log("Processing %s: %s" % (process, items), 1)
if pdialog:
pdialog.update(heading="Processing %s: %s items" % (process, total))
@ -102,7 +100,7 @@ class Items(object):
if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'):
if music_enabled:
musicconn = utils.kodiSQL('music')
musicconn = kodiSQL('music')
musiccursor = musicconn.cursor()
items_process = itemtypes[itemtype](embycursor, musiccursor)
else:
@ -173,7 +171,7 @@ class Items(object):
'remove': items_process.remove
}
else:
self.logMsg("Unsupported itemtype: %s." % itemtype, 1)
log("Unsupported itemtype: %s." % itemtype, 1)
actions = {}
if actions.get(process):
@ -192,7 +190,7 @@ class Items(object):
title = item['Name']
if itemtype == "Episode":
title = "%s - %s" % (item['SeriesName'], title)
title = "%s - %s" % (item.get('SeriesName', "Unknown"), title)
if pdialog:
percentage = int((float(count) / float(total))*100)
@ -204,19 +202,31 @@ class Items(object):
if musicconn is not None:
# close connection for special types
self.logMsg("Updating music database.", 1)
log("Updating music database.", 1)
musicconn.commit()
musiccursor.close()
return (True, update_videolibrary)
def pathValidation(self, path):
# Verify if direct path is accessible or not
if window('emby_pathverified') != "true" and not xbmcvfs.exists(path):
resp = xbmcgui.Dialog().yesno(
heading=lang(29999),
line1="%s %s. %s" % (lang(33047), path, lang(33048)))
if resp:
window('emby_shouldStop', value="true")
return False
return True
def contentPop(self, name, time=5000):
if time:
# It's possible for the time to be 0. It should be considered disabled in this case.
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
message="Added: %s" % name,
heading=lang(29999),
message="%s %s" % (lang(33049), name),
icon="special://home/addons/plugin.video.emby/icon.png",
time=time,
sound=False)
@ -272,11 +282,11 @@ class Movies(Items):
movieid = emby_dbitem[0]
fileid = emby_dbitem[1]
pathid = emby_dbitem[2]
self.logMsg("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1)
log("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1)
except TypeError:
update_item = False
self.logMsg("movieid: %s not found." % itemid, 2)
log("movieid: %s not found." % itemid, 2)
# movieid
kodicursor.execute("select coalesce(max(idMovie),0) from movie")
movieid = kodicursor.fetchone()[0] + 1
@ -290,12 +300,12 @@ class Movies(Items):
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("movieid: %s missing from Kodi, repairing the entry." % movieid, 1)
log("movieid: %s missing from Kodi, repairing the entry." % movieid, 1)
if not viewtag or not viewid:
# Get view tag from emby
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
self.logMsg("View tag found: %s" % viewtag, 2)
log("View tag found: %s" % viewtag, 2)
# fileId information
checksum = API.getChecksum()
@ -338,7 +348,7 @@ class Movies(Items):
try:
trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id']
except IndexError:
self.logMsg("Failed to process local trailer.", 1)
log("Failed to process local trailer.", 1)
trailer = None
else:
# Try to get the youtube trailer
@ -350,7 +360,7 @@ class Movies(Items):
try:
trailerId = trailer.rsplit('=', 1)[1]
except IndexError:
self.logMsg("Failed to process trailer: %s" % trailer, 1)
log("Failed to process trailer: %s" % trailer, 1)
trailer = None
else:
trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId
@ -367,22 +377,11 @@ class Movies(Items):
if self.directpath:
# Direct paths is set the Kodi way
if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
# Validate the path is correct with user intervention
resp = xbmcgui.Dialog().yesno(
heading="Can't validate path",
line1=(
"Kodi can't locate file: %s. Verify the path. "
"You may to verify your network credentials in the "
"add-on settings or use the emby path substitution "
"to format your path correctly. Stop syncing?"
% playurl))
if resp:
utils.window('emby_shouldStop', value="true")
return False
if not self.pathValidation(playurl):
return False
path = playurl.replace(filename, "")
utils.window('emby_pathverified', value="true")
window('emby_pathverified', value="true")
else:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.emby.movies/"
@ -398,7 +397,7 @@ class Movies(Items):
##### UPDATE THE MOVIE #####
if update_item:
self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1)
log("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1)
# Update the movie entry
query = ' '.join((
@ -418,7 +417,7 @@ class Movies(Items):
##### OR ADD THE MOVIE #####
else:
self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1)
log("ADD movie itemid: %s - Title: %s" % (itemid, title), 1)
# Add path
pathid = self.kodi_db.addPath(path)
@ -528,10 +527,10 @@ class Movies(Items):
try:
movieid = emby_dbitem[0]
except TypeError:
self.logMsg("Failed to add: %s to boxset." % movie['Name'], 1)
log("Failed to add: %s to boxset." % movie['Name'], 1)
continue
self.logMsg("New addition to boxset %s: %s" % (title, movie['Name']), 1)
log("New addition to boxset %s: %s" % (title, movie['Name']), 1)
self.kodi_db.assignBoxset(setid, movieid)
# Update emby reference
emby_db.updateParentId(itemid, setid)
@ -542,7 +541,7 @@ class Movies(Items):
# Process removals from boxset
for movie in process:
movieid = current[movie]
self.logMsg("Remove from boxset %s: %s" % (title, movieid))
log("Remove from boxset %s: %s" % (title, movieid))
self.kodi_db.removefromBoxset(movieid)
# Update emby reference
emby_db.updateParentId(movie, None)
@ -567,9 +566,7 @@ class Movies(Items):
try:
movieid = emby_dbitem[0]
fileid = emby_dbitem[1]
self.logMsg(
"Update playstate for movie: %s fileid: %s"
% (item['Name'], fileid), 1)
log("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid), 1)
except TypeError:
return
@ -585,7 +582,7 @@ class Movies(Items):
resume = API.adjustResume(userdata['Resume'])
total = round(float(runtime), 6)
self.logMsg("%s New resume point: %s" % (itemid, resume))
log("%s New resume point: %s" % (itemid, resume))
self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
emby_db.updateReference(itemid, checksum)
@ -601,7 +598,7 @@ class Movies(Items):
kodiid = emby_dbitem[0]
fileid = emby_dbitem[1]
mediatype = emby_dbitem[4]
self.logMsg("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
log("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
except TypeError:
return
@ -627,7 +624,7 @@ class Movies(Items):
kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,))
self.logMsg("Deleted %s %s from kodi database" % (mediatype, itemid), 1)
log("Deleted %s %s from kodi database" % (mediatype, itemid), 1)
class MusicVideos(Items):
@ -667,11 +664,11 @@ class MusicVideos(Items):
mvideoid = emby_dbitem[0]
fileid = emby_dbitem[1]
pathid = emby_dbitem[2]
self.logMsg("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1)
log("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1)
except TypeError:
update_item = False
self.logMsg("mvideoid: %s not found." % itemid, 2)
log("mvideoid: %s not found." % itemid, 2)
# mvideoid
kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo")
mvideoid = kodicursor.fetchone()[0] + 1
@ -685,12 +682,12 @@ class MusicVideos(Items):
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1)
log("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1)
if not viewtag or not viewid:
# Get view tag from emby
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
self.logMsg("View tag found: %s" % viewtag, 2)
log("View tag found: %s" % viewtag, 2)
# fileId information
checksum = API.getChecksum()
@ -726,22 +723,11 @@ class MusicVideos(Items):
if self.directpath:
# Direct paths is set the Kodi way
if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
# Validate the path is correct with user intervention
resp = xbmcgui.Dialog().yesno(
heading="Can't validate path",
line1=(
"Kodi can't locate file: %s. Verify the path. "
"You may to verify your network credentials in the "
"add-on settings or use the emby path substitution "
"to format your path correctly. Stop syncing?"
% playurl))
if resp:
utils.window('emby_shouldStop', value="true")
return False
if not self.pathValidation(playurl):
return False
path = playurl.replace(filename, "")
utils.window('emby_pathverified', value="true")
window('emby_pathverified', value="true")
else:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.emby.musicvideos/"
@ -757,7 +743,7 @@ class MusicVideos(Items):
##### UPDATE THE MUSIC VIDEO #####
if update_item:
self.logMsg("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1)
log("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1)
# Update path
query = "UPDATE path SET strPath = ? WHERE idPath = ?"
@ -783,7 +769,7 @@ class MusicVideos(Items):
##### OR ADD THE MUSIC VIDEO #####
else:
self.logMsg("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1)
log("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1)
# Add path
query = ' '.join((
@ -883,7 +869,7 @@ class MusicVideos(Items):
try:
mvideoid = emby_dbitem[0]
fileid = emby_dbitem[1]
self.logMsg(
log(
"Update playstate for musicvideo: %s fileid: %s"
% (item['Name'], fileid), 1)
except TypeError:
@ -915,7 +901,7 @@ class MusicVideos(Items):
mvideoid = emby_dbitem[0]
fileid = emby_dbitem[1]
pathid = emby_dbitem[2]
self.logMsg("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1)
log("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1)
except TypeError:
return
@ -941,7 +927,7 @@ class MusicVideos(Items):
kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,))
self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,))
self.logMsg("Deleted musicvideo %s from kodi database" % itemid, 1)
log("Deleted musicvideo %s from kodi database" % itemid, 1)
class TVShows(Items):
@ -1004,8 +990,8 @@ class TVShows(Items):
artwork = self.artwork
API = api.API(item)
if utils.settings('syncEmptyShows') == "false" and not item['RecursiveItemCount']:
self.logMsg("Skipping empty show: %s" % item['Name'], 1)
if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'):
log("Skipping empty show: %s" % item['Name'], 1)
return
# If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database
@ -1016,11 +1002,11 @@ class TVShows(Items):
try:
showid = emby_dbitem[0]
pathid = emby_dbitem[2]
self.logMsg("showid: %s pathid: %s" % (showid, pathid), 1)
log("showid: %s pathid: %s" % (showid, pathid), 1)
except TypeError:
update_item = False
self.logMsg("showid: %s not found." % itemid, 2)
log("showid: %s not found." % itemid, 2)
kodicursor.execute("select coalesce(max(idShow),0) from tvshow")
showid = kodicursor.fetchone()[0] + 1
@ -1033,7 +1019,7 @@ class TVShows(Items):
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("showid: %s missing from Kodi, repairing the entry." % showid, 1)
log("showid: %s missing from Kodi, repairing the entry." % showid, 1)
# Force re-add episodes after the show is re-created.
force_episodes = True
@ -1041,7 +1027,7 @@ class TVShows(Items):
if viewtag is None or viewid is None:
# Get view tag from emby
viewtag, viewid, mediatype = emby.getView_embyId(itemid)
self.logMsg("View tag found: %s" % viewtag, 2)
log("View tag found: %s" % viewtag, 2)
# fileId information
checksum = API.getChecksum()
@ -1078,21 +1064,10 @@ class TVShows(Items):
path = "%s/" % playurl
toplevelpath = "%s/" % dirname(dirname(path))
if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(path):
# Validate the path is correct with user intervention
resp = xbmcgui.Dialog().yesno(
heading="Can't validate path",
line1=(
"Kodi can't locate file: %s. Verify the path. "
"You may to verify your network credentials in the "
"add-on settings or use the emby path substitution "
"to format your path correctly. Stop syncing?"
% playurl))
if resp:
utils.window('emby_shouldStop', value="true")
return False
if not self.pathValidation(playurl):
return False
utils.window('emby_pathverified', value="true")
window('emby_pathverified', value="true")
else:
# Set plugin path
toplevelpath = "plugin://plugin.video.emby.tvshows/"
@ -1101,7 +1076,7 @@ class TVShows(Items):
##### UPDATE THE TVSHOW #####
if update_item:
self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1)
log("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1)
# Update the tvshow entry
query = ' '.join((
@ -1119,7 +1094,7 @@ class TVShows(Items):
##### OR ADD THE TVSHOW #####
else:
self.logMsg("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1)
log("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1)
# Add top path
toppathid = self.kodi_db.addPath(toplevelpath)
@ -1190,7 +1165,7 @@ class TVShows(Items):
if force_episodes:
# We needed to recreate the show entry. Re-add episodes now.
self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1)
log("Repairing episodes for showid: %s %s" % (showid, title), 1)
all_episodes = emby.getEpisodesbyShow(itemid)
self.added_episode(all_episodes['Items'], None)
@ -1239,11 +1214,11 @@ class TVShows(Items):
episodeid = emby_dbitem[0]
fileid = emby_dbitem[1]
pathid = emby_dbitem[2]
self.logMsg("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1)
log("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1)
except TypeError:
update_item = False
self.logMsg("episodeid: %s not found." % itemid, 2)
log("episodeid: %s not found." % itemid, 2)
# episodeid
kodicursor.execute("select coalesce(max(idEpisode),0) from episode")
episodeid = kodicursor.fetchone()[0] + 1
@ -1257,7 +1232,7 @@ class TVShows(Items):
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1)
log("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1)
# fileId information
checksum = API.getChecksum()
@ -1281,10 +1256,9 @@ class TVShows(Items):
seriesId = item['SeriesId']
except KeyError:
# Missing seriesId, skip
self.logMsg("Skipping: %s. SeriesId is missing." % itemid, 1)
log("Skipping: %s. SeriesId is missing." % itemid, 1)
return False
seriesName = item['SeriesName']
season = item.get('ParentIndexNumber')
episode = item.get('IndexNumber', -1)
@ -1320,7 +1294,7 @@ class TVShows(Items):
try:
showid = show[0]
except TypeError:
self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId))
log("Skipping: %s. Unable to add series: %s." % (itemid, seriesId))
return False
seasonid = self.kodi_db.addSeason(showid, season)
@ -1337,22 +1311,11 @@ class TVShows(Items):
if self.directpath:
# Direct paths is set the Kodi way
if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
# Validate the path is correct with user intervention
resp = xbmcgui.Dialog().yesno(
heading="Can't validate path",
line1=(
"Kodi can't locate file: %s. Verify the path. "
"You may to verify your network credentials in the "
"add-on settings or use the emby path substitution "
"to format your path correctly. Stop syncing?"
% playurl))
if resp:
utils.window('emby_shouldStop', value="true")
return False
if not self.pathValidation(playurl):
return False
path = playurl.replace(filename, "")
utils.window('emby_pathverified', value="true")
window('emby_pathverified', value="true")
else:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId
@ -1368,7 +1331,7 @@ class TVShows(Items):
##### UPDATE THE EPISODE #####
if update_item:
self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1)
log("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1)
# Update the movie entry
if self.kodiversion in (16, 17):
@ -1402,7 +1365,7 @@ class TVShows(Items):
##### OR ADD THE EPISODE #####
else:
self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1)
log("ADD episode itemid: %s - Title: %s" % (itemid, title), 1)
# Add path
pathid = self.kodi_db.addPath(path)
@ -1505,7 +1468,7 @@ class TVShows(Items):
kodiid = emby_dbitem[0]
fileid = emby_dbitem[1]
mediatype = emby_dbitem[4]
self.logMsg(
log(
"Update playstate for %s: %s fileid: %s"
% (mediatype, item['Name'], fileid), 1)
except TypeError:
@ -1524,7 +1487,7 @@ class TVShows(Items):
resume = API.adjustResume(userdata['Resume'])
total = round(float(runtime), 6)
self.logMsg("%s New resume point: %s" % (itemid, resume))
log("%s New resume point: %s" % (itemid, resume))
self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
if not self.directpath and not resume:
@ -1562,7 +1525,7 @@ class TVShows(Items):
pathid = emby_dbitem[2]
parentid = emby_dbitem[3]
mediatype = emby_dbitem[4]
self.logMsg("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
log("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
except TypeError:
return
@ -1652,14 +1615,14 @@ class TVShows(Items):
self.removeShow(parentid)
emby_db.removeItem_byKodiId(parentid, "tvshow")
self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
def removeShow(self, kodiid):
kodicursor = self.kodicursor
self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,))
self.logMsg("Removed tvshow: %s." % kodiid, 2)
log("Removed tvshow: %s." % kodiid, 2)
def removeSeason(self, kodiid):
@ -1667,7 +1630,7 @@ class TVShows(Items):
self.artwork.deleteArtwork(kodiid, "season", kodicursor)
kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,))
self.logMsg("Removed season: %s." % kodiid, 2)
log("Removed season: %s." % kodiid, 2)
def removeEpisode(self, kodiid, fileid):
@ -1676,7 +1639,7 @@ class TVShows(Items):
self.artwork.deleteArtwork(kodiid, "episode", kodicursor)
kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,))
kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
self.logMsg("Removed episode: %s." % kodiid, 2)
log("Removed episode: %s." % kodiid, 2)
class Music(Items):
@ -1685,12 +1648,12 @@ class Music(Items):
Items.__init__(self, embycursor, musiccursor)
self.directstream = utils.settings('streamMusic') == "true"
self.enableimportsongrating = utils.settings('enableImportSongRating') == "true"
self.enableexportsongrating = utils.settings('enableExportSongRating') == "true"
self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true"
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.directstream = settings('streamMusic') == "true"
self.enableimportsongrating = settings('enableImportSongRating') == "true"
self.enableexportsongrating = settings('enableExportSongRating') == "true"
self.enableupdatesongrating = settings('enableUpdateSongRating') == "true"
self.userid = window('emby_currUser')
self.server = window('emby_server%s' % self.userid)
def added(self, items, pdialog):
@ -1750,7 +1713,7 @@ class Music(Items):
artistid = emby_dbitem[0]
except TypeError:
update_item = False
self.logMsg("artistid: %s not found." % itemid, 2)
log("artistid: %s not found." % itemid, 2)
##### The artist details #####
lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
@ -1777,13 +1740,13 @@ class Music(Items):
##### UPDATE THE ARTIST #####
if update_item:
self.logMsg("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1)
log("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1)
# Update the checksum in emby table
emby_db.updateReference(itemid, checksum)
##### OR ADD THE ARTIST #####
else:
self.logMsg("ADD artist itemid: %s - Name: %s" % (itemid, name), 1)
log("ADD artist itemid: %s - Name: %s" % (itemid, name), 1)
# safety checks: It looks like Emby supports the same artist multiple times.
# Kodi doesn't allow that. In case that happens we just merge the artist entries.
artistid = self.kodi_db.addArtist(name, musicBrainzId)
@ -1831,7 +1794,7 @@ class Music(Items):
albumid = emby_dbitem[0]
except TypeError:
update_item = False
self.logMsg("albumid: %s not found." % itemid, 2)
log("albumid: %s not found." % itemid, 2)
##### The album details #####
lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
@ -1862,13 +1825,13 @@ class Music(Items):
##### UPDATE THE ALBUM #####
if update_item:
self.logMsg("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1)
log("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1)
# Update the checksum in emby table
emby_db.updateReference(itemid, checksum)
##### OR ADD THE ALBUM #####
else:
self.logMsg("ADD album itemid: %s - Name: %s" % (itemid, name), 1)
log("ADD album itemid: %s - Name: %s" % (itemid, name), 1)
# safety checks: It looks like Emby supports the same artist multiple times.
# Kodi doesn't allow that. In case that happens we just merge the artist entries.
albumid = self.kodi_db.addAlbum(name, musicBrainzId)
@ -2001,7 +1964,7 @@ class Music(Items):
albumid = emby_dbitem[3]
except TypeError:
update_item = False
self.logMsg("songid: %s not found." % itemid, 2)
log("songid: %s not found." % itemid, 2)
##### The song details #####
checksum = API.getChecksum()
@ -2048,27 +2011,15 @@ class Music(Items):
filename = playurl.rsplit("/", 1)[1]
# Direct paths is set the Kodi way
if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
# Validate the path is correct with user intervention
utils.window('emby_directPath', clear=True)
resp = xbmcgui.Dialog().yesno(
heading="Can't validate path",
line1=(
"Kodi can't locate file: %s. Verify the path. "
"You may to verify your network credentials in the "
"add-on settings or use the emby path substitution "
"to format your path correctly. Stop syncing?"
% playurl))
if resp:
utils.window('emby_shouldStop', value="true")
return False
if not self.pathValidation(playurl):
return False
path = playurl.replace(filename, "")
utils.window('emby_pathverified', value="true")
window('emby_pathverified', value="true")
##### UPDATE THE SONG #####
if update_item:
self.logMsg("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1)
log("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1)
# Update path
query = "UPDATE path SET strPath = ? WHERE idPath = ?"
@ -2091,7 +2042,7 @@ class Music(Items):
##### OR ADD THE SONG #####
else:
self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
log("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
# Add path
pathid = self.kodi_db.addPath(path)
@ -2104,27 +2055,27 @@ class Music(Items):
# Verify if there's an album associated.
album_name = item.get('Album')
if album_name:
self.logMsg("Creating virtual music album for song: %s." % itemid, 1)
log("Creating virtual music album for song: %s." % itemid, 1)
albumid = self.kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum'))
emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album")
else:
# No album Id associated to the song.
self.logMsg("Song itemid: %s has no albumId associated." % itemid, 1)
log("Song itemid: %s has no albumId associated." % itemid, 1)
return False
except TypeError:
# No album found. Let's create it
self.logMsg("Album database entry missing.", 1)
log("Album database entry missing.", 1)
emby_albumId = item['AlbumId']
album = emby.getItem(emby_albumId)
self.add_updateAlbum(album)
emby_dbalbum = emby_db.getItem_byId(emby_albumId)
try:
albumid = emby_dbalbum[0]
self.logMsg("Found albumid: %s" % albumid, 1)
log("Found albumid: %s" % albumid, 1)
except TypeError:
# No album found, create a single's album
self.logMsg("Failed to add album. Creating singles.", 1)
log("Failed to add album. Creating singles.", 1)
kodicursor.execute("select coalesce(max(idAlbum),0) from album")
albumid = kodicursor.fetchone()[0] + 1
if self.kodiversion == 16:
@ -2306,7 +2257,7 @@ class Music(Items):
try:
kodiid = emby_dbitem[0]
mediatype = emby_dbitem[4]
self.logMsg("Update playstate for %s: %s" % (mediatype, item['Name']), 1)
log("Update playstate for %s: %s" % (mediatype, item['Name']), 1)
except TypeError:
return
@ -2314,8 +2265,8 @@ class Music(Items):
#should we ignore this item ?
#happens when userdata updated by ratings method
if utils.window("ignore-update-%s" %itemid):
utils.window("ignore-update-%s" %itemid,clear=True)
if window("ignore-update-%s" %itemid):
window("ignore-update-%s" %itemid,clear=True)
return
# Process playstates
@ -2345,7 +2296,7 @@ class Music(Items):
try:
kodiid = emby_dbitem[0]
mediatype = emby_dbitem[4]
self.logMsg("Removing %s kodiid: %s" % (mediatype, kodiid), 1)
log("Removing %s kodiid: %s" % (mediatype, kodiid), 1)
except TypeError:
return
@ -2413,21 +2364,21 @@ class Music(Items):
# Remove artist
self.removeArtist(kodiid)
self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
def removeSong(self, kodiid):
def removeSong(self, kodiId):
kodicursor = self.kodicursor
self.artwork.deleteArtwork(kodiid, "song", self.kodicursor)
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
self.artwork.deleteArtwork(kodiId, "song", self.kodicursor)
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiId,))
def removeAlbum(self, kodiid):
def removeAlbum(self, kodiId):
self.artwork.deleteArtwork(kodiid, "album", self.kodicursor)
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
self.artwork.deleteArtwork(kodiId, "album", self.kodicursor)
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiId,))
def removeArtist(self, kodiid):
def removeArtist(self, kodiId):
self.artwork.deleteArtwork(kodiid, "artist", self.kodicursor)
self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
self.artwork.deleteArtwork(kodiId, "artist", self.kodicursor)
self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiId,))

View file

@ -7,7 +7,7 @@ import xbmc
import api
import artwork
import clientinfo
import utils
from utils import Logging
##################################################################################################
@ -19,16 +19,14 @@ class Kodidb_Functions():
def __init__(self, cursor):
global log
log = Logging(self.__class__.__name__).log
self.cursor = cursor
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.artwork = artwork.Artwork()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def addPath(self, path):
@ -153,7 +151,7 @@ class Kodidb_Functions():
query = "INSERT INTO country(country_id, name) values(?, ?)"
self.cursor.execute(query, (country_id, country))
self.logMsg("Add country to media, processing: %s" % country, 2)
log("Add country to media, processing: %s" % country, 2)
finally: # Assign country to content
query = (
@ -187,7 +185,7 @@ class Kodidb_Functions():
query = "INSERT INTO country(idCountry, strCountry) values(?, ?)"
self.cursor.execute(query, (idCountry, country))
self.logMsg("Add country to media, processing: %s" % country, 2)
log("Add country to media, processing: %s" % country, 2)
finally:
# Only movies have a country field
@ -232,7 +230,7 @@ class Kodidb_Functions():
query = "INSERT INTO actor(actor_id, name) values(?, ?)"
self.cursor.execute(query, (actorid, name))
self.logMsg("Add people to media, processing: %s" % name, 2)
log("Add people to media, processing: %s" % name, 2)
finally:
# Link person to content
@ -302,7 +300,7 @@ class Kodidb_Functions():
query = "INSERT INTO actors(idActor, strActor) values(?, ?)"
self.cursor.execute(query, (actorid, name))
self.logMsg("Add people to media, processing: %s" % name, 2)
log("Add people to media, processing: %s" % name, 2)
finally:
# Link person to content
@ -462,7 +460,7 @@ class Kodidb_Functions():
query = "INSERT INTO genre(genre_id, name) values(?, ?)"
self.cursor.execute(query, (genre_id, genre))
self.logMsg("Add Genres to media, processing: %s" % genre, 2)
log("Add Genres to media, processing: %s" % genre, 2)
finally:
# Assign genre to item
@ -507,7 +505,7 @@ class Kodidb_Functions():
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
self.cursor.execute(query, (idGenre, genre))
self.logMsg("Add Genres to media, processing: %s" % genre, 2)
log("Add Genres to media, processing: %s" % genre, 2)
finally:
# Assign genre to item
@ -566,7 +564,7 @@ class Kodidb_Functions():
query = "INSERT INTO studio(studio_id, name) values(?, ?)"
self.cursor.execute(query, (studioid, studio))
self.logMsg("Add Studios to media, processing: %s" % studio, 2)
log("Add Studios to media, processing: %s" % studio, 2)
finally: # Assign studio to item
query = (
@ -597,7 +595,7 @@ class Kodidb_Functions():
query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)"
self.cursor.execute(query, (studioid, studio))
self.logMsg("Add Studios to media, processing: %s" % studio, 2)
log("Add Studios to media, processing: %s" % studio, 2)
finally: # Assign studio to item
if "movie" in mediatype:
@ -728,7 +726,7 @@ class Kodidb_Functions():
self.cursor.execute(query, (kodiid, mediatype))
# Add tags
self.logMsg("Adding Tags: %s" % tags, 2)
log("Adding Tags: %s" % tags, 2)
for tag in tags:
self.addTag(kodiid, tag, mediatype)
@ -750,7 +748,7 @@ class Kodidb_Functions():
except TypeError:
# Create the tag, because it does not exist
tag_id = self.createTag(tag)
self.logMsg("Adding tag: %s" % tag, 2)
log("Adding tag: %s" % tag, 2)
finally:
# Assign tag to item
@ -779,7 +777,7 @@ class Kodidb_Functions():
except TypeError:
# Create the tag
tag_id = self.createTag(tag)
self.logMsg("Adding tag: %s" % tag, 2)
log("Adding tag: %s" % tag, 2)
finally:
# Assign tag to item
@ -815,7 +813,7 @@ class Kodidb_Functions():
query = "INSERT INTO tag(tag_id, name) values(?, ?)"
self.cursor.execute(query, (tag_id, name))
self.logMsg("Create tag_id: %s name: %s" % (tag_id, name), 2)
log("Create tag_id: %s name: %s" % (tag_id, name), 2)
else:
# Kodi Helix
query = ' '.join((
@ -835,13 +833,13 @@ class Kodidb_Functions():
query = "INSERT INTO tag(idTag, strTag) values(?, ?)"
self.cursor.execute(query, (tag_id, name))
self.logMsg("Create idTag: %s name: %s" % (tag_id, name), 2)
log("Create idTag: %s name: %s" % (tag_id, name), 2)
return tag_id
def updateTag(self, oldtag, newtag, kodiid, mediatype):
self.logMsg("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2)
log("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2)
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton
@ -858,7 +856,7 @@ class Kodidb_Functions():
except Exception as e:
# The new tag we are going to apply already exists for this item
# delete current tag instead
self.logMsg("Exception: %s" % e, 1)
log("Exception: %s" % e, 1)
query = ' '.join((
"DELETE FROM tag_link",
@ -882,7 +880,7 @@ class Kodidb_Functions():
except Exception as e:
# The new tag we are going to apply already exists for this item
# delete current tag instead
self.logMsg("Exception: %s" % e, 1)
log("Exception: %s" % e, 1)
query = ' '.join((
"DELETE FROM taglinks",
@ -943,7 +941,7 @@ class Kodidb_Functions():
def createBoxset(self, boxsetname):
self.logMsg("Adding boxset: %s" % boxsetname, 2)
log("Adding boxset: %s" % boxsetname, 2)
query = ' '.join((
"SELECT idSet",

View file

@ -11,7 +11,7 @@ import clientinfo
import downloadutils
import embydb_functions as embydb
import playbackutils as pbutils
import utils
from utils import Logging, window, settings, kodiSQL
#################################################################################################
@ -21,27 +21,25 @@ class KodiMonitor(xbmc.Monitor):
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.logMsg("Kodi monitor started.", 1)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
log("Kodi monitor started.", 1)
def onScanStarted(self, library):
self.logMsg("Kodi library scan %s running." % library, 2)
log("Kodi library scan %s running." % library, 2)
if library == "video":
utils.window('emby_kodiScan', value="true")
window('emby_kodiScan', value="true")
def onScanFinished(self, library):
self.logMsg("Kodi library scan %s finished." % library, 2)
log("Kodi library scan %s finished." % library, 2)
if library == "video":
utils.window('emby_kodiScan', clear=True)
window('emby_kodiScan', clear=True)
def onSettingsChanged(self):
# Monitor emby settings
@ -50,7 +48,7 @@ class KodiMonitor(xbmc.Monitor):
'''currentPath = utils.settings('useDirectPaths')
if utils.window('emby_pluginpath') != currentPath:
# Plugin path value changed. Offer to reset
self.logMsg("Changed to playback mode detected", 1)
log("Changed to playback mode detected", 1)
utils.window('emby_pluginpath', value=currentPath)
resp = xbmcgui.Dialog().yesno(
heading="Playback mode change detected",
@ -61,17 +59,17 @@ class KodiMonitor(xbmc.Monitor):
if resp:
utils.reset()'''
currentLog = utils.settings('logLevel')
if utils.window('emby_logLevel') != currentLog:
currentLog = settings('logLevel')
if window('emby_logLevel') != currentLog:
# The log level changed, set new prop
self.logMsg("New log level: %s" % currentLog, 1)
utils.window('emby_logLevel', value=currentLog)
log("New log level: %s" % currentLog, 1)
window('emby_logLevel', value=currentLog)
def onNotification(self, sender, method, data):
doUtils = self.doUtils
if method not in ("Playlist.OnAdd"):
self.logMsg("Method: %s Data: %s" % (method, data), 1)
log("Method: %s Data: %s" % (method, data), 1)
if data:
data = json.loads(data,'utf-8')
@ -84,23 +82,23 @@ class KodiMonitor(xbmc.Monitor):
kodiid = item['id']
item_type = item['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1)
log("Item is invalid for playstate update.", 1)
else:
if ((utils.settings('useDirectPaths') == "1" and not item_type == "song") or
(item_type == "song" and utils.settings('enableMusic') == "true")):
if ((settings('useDirectPaths') == "1" and not item_type == "song") or
(item_type == "song" and settings('enableMusic') == "true")):
# Set up properties for player
embyconn = utils.kodiSQL('emby')
embyconn = kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("No kodiid returned.", 1)
log("No kodiId returned.", 1)
else:
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
result = doUtils.downloadUrl(url)
self.logMsg("Item: %s" % result, 2)
log("Item: %s" % result, 2)
playurl = None
count = 0
@ -114,12 +112,10 @@ class KodiMonitor(xbmc.Monitor):
listItem = xbmcgui.ListItem()
playback = pbutils.PlaybackUtils(result)
if item_type == "song" and utils.settings('streamMusic') == "true":
utils.window('emby_%s.playmethod' % playurl,
value="DirectStream")
if item_type == "song" and settings('streamMusic') == "true":
window('emby_%s.playmethod' % playurl, value="DirectStream")
else:
utils.window('emby_%s.playmethod' % playurl,
value="DirectPlay")
window('emby_%s.playmethod' % playurl, value="DirectPlay")
# Set properties for player.py
playback.setProperties(playurl, listItem)
finally:
@ -134,31 +130,31 @@ class KodiMonitor(xbmc.Monitor):
kodiid = item['id']
item_type = item['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1)
log("Item is invalid for playstate update.", 1)
else:
# Send notification to the server.
embyconn = utils.kodiSQL('emby')
embyconn = kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("Could not find itemid in emby database.", 1)
log("Could not find itemid in emby database.", 1)
else:
# Stop from manually marking as watched unwatched, with actual playback.
if utils.window('emby_skipWatched%s' % itemid) == "true":
if window('emby_skipWatched%s' % itemid) == "true":
# property is set in player.py
utils.window('emby_skipWatched%s' % itemid, clear=True)
window('emby_skipWatched%s' % itemid, clear=True)
else:
# notify the server
url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
if playcount != 0:
doUtils.downloadUrl(url, action_type="POST")
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
log("Mark as watched for itemid: %s" % itemid, 1)
else:
doUtils.downloadUrl(url, action_type="DELETE")
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
log("Mark as unwatched for itemid: %s" % itemid, 1)
finally:
embycursor.close()
@ -172,7 +168,7 @@ class KodiMonitor(xbmc.Monitor):
kodiid = data['id']
type = data['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for emby deletion.", 1)
log("Item is invalid for emby deletion.", 1)
else:
# Send the delete action to the server.
embyconn = utils.kodiSQL('emby')
@ -182,19 +178,19 @@ class KodiMonitor(xbmc.Monitor):
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("Could not find itemid in emby database.", 1)
log("Could not find itemid in emby database.", 1)
else:
if utils.settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1="Delete file on Emby Server?")
if not resp:
self.logMsg("User skipped deletion.", 1)
log("User skipped deletion.", 1)
embycursor.close()
return
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
log("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, action_type="DELETE")
finally:
embycursor.close()'''
@ -203,13 +199,13 @@ class KodiMonitor(xbmc.Monitor):
elif method == "System.OnWake":
# Allow network to wake up
xbmc.sleep(10000)
utils.window('emby_onWake', value="true")
window('emby_onWake', value="true")
elif method == "GUI.OnScreensaverDeactivated":
if utils.settings('dbSyncScreensaver') == "true":
if settings('dbSyncScreensaver') == "true":
xbmc.sleep(5000);
utils.window('emby_onWake', value="true")
window('emby_onWake', value="true")
elif method == "Playlist.OnClear":

View file

@ -20,6 +20,7 @@ import kodidb_functions as kodidb
import read_embyserver as embyserver
import userclient
import videonodes
from utils import Logging, window, settings, language as lang
##################################################################################################
@ -42,6 +43,9 @@ class LibrarySync(threading.Thread):
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.__dict__ = self._shared_state
self.monitor = xbmc.Monitor()
@ -54,26 +58,20 @@ class LibrarySync(threading.Thread):
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def progressDialog(self, title, forced=False):
dialog = None
if utils.settings('dbSyncIndicator') == "true" or forced:
if settings('dbSyncIndicator') == "true" or forced:
dialog = xbmcgui.DialogProgressBG()
dialog.create("Emby for Kodi", title)
self.logMsg("Show progress dialog: %s" % title, 2)
log("Show progress dialog: %s" % title, 2)
return dialog
def startSync(self):
settings = utils.settings
# Run at start up - optional to use the server plugin
if settings('SyncInstallRunDone') == "true":
@ -88,7 +86,7 @@ class LibrarySync(threading.Thread):
for plugin in result:
if plugin['Name'] == "Emby.Kodi Sync Queue":
self.logMsg("Found server plugin.", 2)
log("Found server plugin.", 2)
completed = self.fastSync()
break
@ -103,37 +101,31 @@ class LibrarySync(threading.Thread):
def fastSync(self):
lastSync = utils.settings('LastIncrementalSync')
lastSync = settings('LastIncrementalSync')
if not lastSync:
lastSync = "2010-01-01T00:00:00Z"
lastSyncTime = utils.convertdate(lastSync)
self.logMsg("Last sync run: %s" % lastSyncTime, 1)
lastSyncTime = utils.convertDate(lastSync)
log("Last sync run: %s" % lastSyncTime, 1)
# get server RetentionDateTime
result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json")
retention_time = "2010-01-01T00:00:00Z"
if result and result.get('RetentionDateTime'):
retention_time = result['RetentionDateTime']
#Try/except equivalent
'''
try:
retention_time = result['RetentionDateTime']
except (TypeError, KeyError):
retention_time = "2010-01-01T00:00:00Z"
'''
retention_time = utils.convertdate(retention_time)
self.logMsg("RetentionDateTime: %s" % retention_time, 1)
retention_time = utils.convertDate(retention_time)
log("RetentionDateTime: %s" % retention_time, 1)
# if last sync before retention time do a full sync
if retention_time > lastSyncTime:
self.logMsg("Fast sync server retention insufficient, fall back to full sync", 1)
log("Fast sync server retention insufficient, fall back to full sync", 1)
return False
params = {'LastUpdateDT': lastSync}
result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json", parameters=params)
url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json"
result = self.doUtils(url, parameters=params)
try:
processlist = {
@ -145,11 +137,11 @@ class LibrarySync(threading.Thread):
}
except (KeyError, TypeError):
self.logMsg("Failed to retrieve latest updates using fast sync.", 1)
log("Failed to retrieve latest updates using fast sync.", 1)
return False
else:
self.logMsg("Fast sync changes: %s" % result, 1)
log("Fast sync changes: %s" % result, 1)
for action in processlist:
self.triage_items(action, processlist[action])
@ -163,60 +155,55 @@ class LibrarySync(threading.Thread):
result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json")
try: # datetime fails when used more than once, TypeError
server_time = result['ServerDateTime']
server_time = utils.convertdate(server_time)
server_time = utils.convertDate(server_time)
except Exception as e:
# If the server plugin is not installed or an error happened.
self.logMsg("An exception occurred: %s" % e, 1)
log("An exception occurred: %s" % e, 1)
time_now = datetime.utcnow()-timedelta(minutes=overlap)
lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ')
self.logMsg("New sync time: client time -%s min: %s" % (overlap, lastSync), 1)
log("New sync time: client time -%s min: %s" % (overlap, lastSync), 1)
else:
lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ')
self.logMsg("New sync time: server time -%s min: %s" % (overlap, lastSync), 1)
log("New sync time: server time -%s min: %s" % (overlap, lastSync), 1)
finally:
utils.settings('LastIncrementalSync', value=lastSync)
settings('LastIncrementalSync', value=lastSync)
def shouldStop(self):
# Checkpoint during the syncing process
if self.monitor.abortRequested():
return True
elif utils.window('emby_shouldStop') == "true":
elif window('emby_shouldStop') == "true":
return True
else: # Keep going
return False
def dbCommit(self, connection):
window = utils.window
# Central commit, verifies if Kodi database update is running
kodidb_scan = window('emby_kodiScan') == "true"
while kodidb_scan:
self.logMsg("Kodi scan is running. Waiting...", 1)
log("Kodi scan is running. Waiting...", 1)
kodidb_scan = window('emby_kodiScan') == "true"
if self.shouldStop():
self.logMsg("Commit unsuccessful. Sync terminated.", 1)
log("Commit unsuccessful. Sync terminated.", 1)
break
if self.monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
self.logMsg("Commit unsuccessful.", 1)
log("Commit unsuccessful.", 1)
break
else:
connection.commit()
self.logMsg("Commit successful.", 1)
log("Commit successful.", 1)
def fullSync(self, manualrun=False, repair=False, forceddialog=False):
window = utils.window
settings = utils.settings
# Only run once when first setting up. Can be run manually.
music_enabled = utils.settings('enableMusic') == "true"
music_enabled = settings('enableMusic') == "true"
xbmc.executebuiltin('InhibitIdleShutdown(true)')
screensaver = utils.getScreensaver()
@ -284,7 +271,7 @@ class LibrarySync(threading.Thread):
self.dbCommit(kodiconn)
embyconn.commit()
elapsedTime = datetime.now() - startTime
self.logMsg("SyncDatabase (finished %s in: %s)"
log("SyncDatabase (finished %s in: %s)"
% (itemtype, str(elapsedTime).split('.')[0]), 1)
else:
# Close the Kodi cursor
@ -312,7 +299,7 @@ class LibrarySync(threading.Thread):
musicconn.commit()
embyconn.commit()
elapsedTime = datetime.now() - startTime
self.logMsg("SyncDatabase (finished music in: %s)"
log("SyncDatabase (finished music in: %s)"
% (str(elapsedTime).split('.')[0]), 1)
musiccursor.close()
@ -333,9 +320,9 @@ class LibrarySync(threading.Thread):
window('emby_initialScan', clear=True)
if forceddialog:
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
heading=lang(29999),
message="%s %s %s" %
(message, utils.language(33025), str(elapsedtotal).split('.')[0]),
(message, lang(33025), str(elapsedtotal).split('.')[0]),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
return True
@ -378,7 +365,7 @@ class LibrarySync(threading.Thread):
if view['type'] == "mixed":
sorted_views.append(view['name'])
sorted_views.append(view['name'])
self.logMsg("Sorted views: %s" % sorted_views, 1)
log("Sorted views: %s" % sorted_views, 1)
# total nodes for window properties
self.vnodes.clearProperties()
@ -415,7 +402,8 @@ class LibrarySync(threading.Thread):
'Limit': 1,
'IncludeItemTypes': emby_mediatypes[mediatype]
} # Get one item from server using the folderid
result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
url = "{server}/emby/Users/{UserId}/Items?format=json"
result = self.doUtils(url, parameters=params)
try:
verifyitem = result['Items'][0]['Id']
except (TypeError, IndexError):
@ -430,14 +418,14 @@ class LibrarySync(threading.Thread):
# Take the userview, and validate the item belong to the view
if self.emby.verifyView(grouped_view['Id'], verifyitem):
# Take the name of the userview
self.logMsg("Found corresponding view: %s %s"
log("Found corresponding view: %s %s"
% (grouped_view['Name'], grouped_view['Id']), 1)
foldername = grouped_view['Name']
break
else:
# Unable to find a match, add the name to our sorted_view list
sorted_views.append(foldername)
self.logMsg("Couldn't find corresponding grouped view: %s" % sorted_views, 1)
log("Couldn't find corresponding grouped view: %s" % sorted_views, 1)
# Failsafe
try:
@ -453,7 +441,7 @@ class LibrarySync(threading.Thread):
current_tagid = view[2]
except TypeError:
self.logMsg("Creating viewid: %s in Emby database." % folderid, 1)
log("Creating viewid: %s in Emby database." % folderid, 1)
tagid = kodi_db.createTag(foldername)
# Create playlist for the video library
if (foldername not in playlists and
@ -472,7 +460,7 @@ class LibrarySync(threading.Thread):
emby_db.addView(folderid, foldername, viewtype, tagid)
else:
self.logMsg(' '.join((
log(' '.join((
"Found viewid: %s" % folderid,
"viewname: %s" % current_viewname,
@ -488,7 +476,7 @@ class LibrarySync(threading.Thread):
# View was modified, update with latest info
if current_viewname != foldername:
self.logMsg("viewid: %s new viewname: %s" % (folderid, foldername), 1)
log("viewid: %s new viewname: %s" % (folderid, foldername), 1)
tagid = kodi_db.createTag(foldername)
# Update view with new info
@ -553,23 +541,22 @@ class LibrarySync(threading.Thread):
self.vnodes.singleNode(totalnodes, "channels", "movies", "channels")
totalnodes += 1
# Save total
utils.window('Emby.nodes.total', str(totalnodes))
window('Emby.nodes.total', str(totalnodes))
# Remove any old referenced views
self.logMsg("Removing views: %s" % current_views, 1)
log("Removing views: %s" % current_views, 1)
for view in current_views:
emby_db.removeView(view)
def movies(self, embycursor, kodicursor, pdialog):
lang = utils.language
# Get movies from emby
emby_db = embydb.Embydb_Functions(embycursor)
movies = itemtypes.Movies(embycursor, kodicursor)
views = emby_db.getView_byType('movies')
views += emby_db.getView_byType('mixed')
self.logMsg("Media folders: %s" % views, 1)
log("Media folders: %s" % views, 1)
##### PROCESS MOVIES #####
for view in views:
@ -580,7 +567,7 @@ class LibrarySync(threading.Thread):
# Get items per view
if pdialog:
pdialog.update(
heading="Emby for Kodi",
heading=lang(29999),
message="%s %s..." % (lang(33017), view['name']))
# Initial or repair sync
@ -604,12 +591,12 @@ class LibrarySync(threading.Thread):
count += 1
movies.add_update(embymovie, view['name'], view['id'])
else:
self.logMsg("Movies finished.", 2)
log("Movies finished.", 2)
##### PROCESS BOXSETS #####
if pdialog:
pdialog.update(heading="Emby for Kodi", message=lang(33018))
pdialog.update(heading=lang(29999), message=lang(33018))
boxsets = self.emby.getBoxset(dialog=pdialog)
total = boxsets['TotalRecordCount']
@ -631,7 +618,7 @@ class LibrarySync(threading.Thread):
count += 1
movies.add_updateBoxset(boxset)
else:
self.logMsg("Boxsets finished.", 2)
log("Boxsets finished.", 2)
return True
@ -642,7 +629,7 @@ class LibrarySync(threading.Thread):
mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
views = emby_db.getView_byType('musicvideos')
self.logMsg("Media folders: %s" % views, 1)
log("Media folders: %s" % views, 1)
for view in views:
@ -655,8 +642,8 @@ class LibrarySync(threading.Thread):
if pdialog:
pdialog.update(
heading="Emby for Kodi",
message="%s %s..." % (utils.language(33019), viewName))
heading=lang(29999),
message="%s %s..." % (lang(33019), viewName))
# Initial or repair sync
all_embymvideos = self.emby.getMusicVideos(viewId, dialog=pdialog)
@ -679,7 +666,7 @@ class LibrarySync(threading.Thread):
count += 1
mvideos.add_update(embymvideo, viewName, viewId)
else:
self.logMsg("MusicVideos finished.", 2)
log("MusicVideos finished.", 2)
return True
@ -691,7 +678,7 @@ class LibrarySync(threading.Thread):
views = emby_db.getView_byType('tvshows')
views += emby_db.getView_byType('mixed')
self.logMsg("Media folders: %s" % views, 1)
log("Media folders: %s" % views, 1)
for view in views:
@ -701,8 +688,8 @@ class LibrarySync(threading.Thread):
# Get items per view
if pdialog:
pdialog.update(
heading="Emby for Kodi",
message="%s %s..." % (utils.language(33020), view['name']))
heading=lang(29999),
message="%s %s..." % (lang(33020), view['name']))
all_embytvshows = self.emby.getShows(view['id'], dialog=pdialog)
total = all_embytvshows['TotalRecordCount']
@ -737,7 +724,7 @@ class LibrarySync(threading.Thread):
pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
tvshows.add_updateEpisode(episode)
else:
self.logMsg("TVShows finished.", 2)
log("TVShows finished.", 2)
return True
@ -756,8 +743,8 @@ class LibrarySync(threading.Thread):
if pdialog:
pdialog.update(
heading="Emby for Kodi",
message="%s %s..." % (utils.language(33021), itemtype))
heading=lang(29999),
message="%s %s..." % (lang(33021), itemtype))
all_embyitems = process[itemtype][0](dialog=pdialog)
total = all_embyitems['TotalRecordCount']
@ -778,7 +765,7 @@ class LibrarySync(threading.Thread):
process[itemtype][1](embyitem)
else:
self.logMsg("%s finished." % itemtype, 2)
log("%s finished." % itemtype, 2)
return True
@ -799,7 +786,7 @@ class LibrarySync(threading.Thread):
itemids.append(item['ItemId'])
items = itemids
self.logMsg("Queue %s: %s" % (process, items), 1)
log("Queue %s: %s" % (process, items), 1)
processlist[process].extend(items)
def incrementalSync(self):
@ -833,7 +820,7 @@ class LibrarySync(threading.Thread):
}
for process_type in ['added', 'update', 'userdata', 'remove']:
if process[process_type] and utils.window('emby_kodiScan') != "true":
if process[process_type] and window('emby_kodiScan') != "true":
listItems = list(process[process_type])
del process[process_type][:] # Reset class list
@ -871,7 +858,7 @@ class LibrarySync(threading.Thread):
if update_embydb:
update_embydb = False
self.logMsg("Updating emby database.", 1)
log("Updating emby database.", 1)
embyconn.commit()
self.saveLastSync()
@ -880,8 +867,8 @@ class LibrarySync(threading.Thread):
self.forceLibraryUpdate = False
self.dbCommit(kodiconn)
self.logMsg("Updating video library.", 1)
utils.window('emby_kodiScan', value="true")
log("Updating video library.", 1)
window('emby_kodiScan', value="true")
xbmc.executebuiltin('UpdateLibrary(video)')
if pDialog:
@ -893,7 +880,7 @@ class LibrarySync(threading.Thread):
def compareDBVersion(self, current, minimum):
# It returns True is database is up to date. False otherwise.
self.logMsg("current: %s minimum: %s" % (current, minimum), 1)
log("current: %s minimum: %s" % (current, minimum), 1)
currMajor, currMinor, currPatch = current.split(".")
minMajor, minMinor, minPatch = minimum.split(".")
@ -911,25 +898,22 @@ class LibrarySync(threading.Thread):
try:
self.run_internal()
except Exception as e:
utils.window('emby_dbScan', clear=True)
window('emby_dbScan', clear=True)
xbmcgui.Dialog().ok(
heading="Emby for Kodi",
heading=lang(29999),
line1=(
"Library sync thread has exited! "
"You should restart Kodi now. "
"Please report this on the forum."))
raise
@utils.profiling()
def run_internal(self):
lang = utils.language
window = utils.window
settings = utils.settings
dialog = xbmcgui.Dialog()
startupComplete = False
self.logMsg("---===### Starting LibrarySync ###===---", 0)
log("---===### Starting LibrarySync ###===---", 0)
while not self.monitor.abortRequested():
@ -947,13 +931,13 @@ class LibrarySync(threading.Thread):
uptoDate = self.compareDBVersion(currentVersion, minVersion)
if not uptoDate:
self.logMsg("Database version out of date: %s minimum version required: %s"
log("Database version out of date: %s minimum version required: %s"
% (currentVersion, minVersion), 0)
resp = dialog.yesno("Emby for Kodi", lang(33022))
resp = dialog.yesno(lang(29999), lang(33022))
if not resp:
self.logMsg("Database version is out of date! USER IGNORED!", 0)
dialog.ok("Emby for Kodi", lang(33023))
log("Database version is out of date! USER IGNORED!", 0)
dialog.ok(lang(29999), lang(33023))
else:
utils.reset()
@ -967,24 +951,24 @@ class LibrarySync(threading.Thread):
videoDb = utils.getKodiVideoDBPath()
if not xbmcvfs.exists(videoDb):
# Database does not exists
self.logMsg(
log(
"The current Kodi version is incompatible "
"with the Emby for Kodi add-on. Please visit "
"https://github.com/MediaBrowser/Emby.Kodi/wiki "
"to know which Kodi versions are supported.", 0)
dialog.ok(
heading="Emby for Kodi",
heading=lang(29999),
line1=lang(33024))
break
# Run start up sync
self.logMsg("Database version: %s" % settings('dbCreatedWithVersion'), 0)
self.logMsg("SyncDatabase (started)", 1)
log("Database version: %s" % settings('dbCreatedWithVersion'), 0)
log("SyncDatabase (started)", 1)
startTime = datetime.now()
librarySync = self.startSync()
elapsedTime = datetime.now() - startTime
self.logMsg("SyncDatabase (finished in: %s) %s"
log("SyncDatabase (finished in: %s) %s"
% (str(elapsedTime).split('.')[0], librarySync), 1)
# Only try the initial sync once per kodi session regardless
# This will prevent an infinite loop in case something goes wrong.
@ -999,32 +983,32 @@ class LibrarySync(threading.Thread):
# Set in kodimonitor.py
window('emby_onWake', clear=True)
if window('emby_syncRunning') != "true":
self.logMsg("SyncDatabase onWake (started)", 0)
log("SyncDatabase onWake (started)", 0)
librarySync = self.startSync()
self.logMsg("SyncDatabase onWake (finished) %s" % librarySync, 0)
log("SyncDatabase onWake (finished) %s" % librarySync, 0)
if self.stop_thread:
# Set in service.py
self.logMsg("Service terminated thread.", 2)
log("Service terminated thread.", 2)
break
if self.monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
self.logMsg("###===--- LibrarySync Stopped ---===###", 0)
log("###===--- LibrarySync Stopped ---===###", 0)
def stopThread(self):
self.stop_thread = True
self.logMsg("Ending thread...", 2)
log("Ending thread...", 2)
def suspendThread(self):
self.suspend_thread = True
self.logMsg("Pausing thread...", 0)
log("Pausing thread...", 0)
def resumeThread(self):
self.suspend_thread = False
self.logMsg("Resuming thread...", 0)
log("Resuming thread...", 0)
class ManualSync(LibrarySync):
@ -1041,14 +1025,13 @@ class ManualSync(LibrarySync):
def movies(self, embycursor, kodicursor, pdialog):
lang = utils.language
# Get movies from emby
emby_db = embydb.Embydb_Functions(embycursor)
movies = itemtypes.Movies(embycursor, kodicursor)
views = emby_db.getView_byType('movies')
views += emby_db.getView_byType('mixed')
self.logMsg("Media folders: %s" % views, 1)
log("Media folders: %s" % views, 1)
# Pull the list of movies and boxsets in Kodi
try:
@ -1077,7 +1060,7 @@ class ManualSync(LibrarySync):
if pdialog:
pdialog.update(
heading="Emby for Kodi",
heading=lang(29999),
message="%s %s..." % (lang(33026), viewName))
all_embymovies = self.emby.getMovies(viewId, basic=True, dialog=pdialog)
@ -1095,7 +1078,7 @@ class ManualSync(LibrarySync):
# Only update if movie is not in Kodi or checksum is different
updatelist.append(itemid)
self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1)
log("Movies to update for %s: %s" % (viewName, updatelist), 1)
embymovies = self.emby.getFullItems(updatelist)
total = len(updatelist)
del updatelist[:]
@ -1121,7 +1104,7 @@ class ManualSync(LibrarySync):
embyboxsets = []
if pdialog:
pdialog.update(heading="Emby for Kodi", message=lang(33027))
pdialog.update(heading=lang(29999), message=lang(33027))
for boxset in boxsets['Items']:
@ -1137,7 +1120,7 @@ class ManualSync(LibrarySync):
updatelist.append(itemid)
embyboxsets.append(boxset)
self.logMsg("Boxsets to update: %s" % updatelist, 1)
log("Boxsets to update: %s" % updatelist, 1)
total = len(updatelist)
if pdialog:
@ -1161,13 +1144,13 @@ class ManualSync(LibrarySync):
if kodimovie not in all_embymoviesIds:
movies.remove(kodimovie)
else:
self.logMsg("Movies compare finished.", 1)
log("Movies compare finished.", 1)
for boxset in all_kodisets:
if boxset not in all_embyboxsetsIds:
movies.remove(boxset)
else:
self.logMsg("Boxsets compare finished.", 1)
log("Boxsets compare finished.", 1)
return True
@ -1178,7 +1161,7 @@ class ManualSync(LibrarySync):
mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
views = emby_db.getView_byType('musicvideos')
self.logMsg("Media folders: %s" % views, 1)
log("Media folders: %s" % views, 1)
# Pull the list of musicvideos in Kodi
try:
@ -1200,8 +1183,8 @@ class ManualSync(LibrarySync):
if pdialog:
pdialog.update(
heading="Emby for Kodi",
message="%s %s..." % (utils.language(33028), viewName))
heading=lang(29999),
message="%s %s..." % (lang(33028), viewName))
all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog)
for embymvideo in all_embymvideos['Items']:
@ -1218,7 +1201,7 @@ class ManualSync(LibrarySync):
# Only update if musicvideo is not in Kodi or checksum is different
updatelist.append(itemid)
self.logMsg("MusicVideos to update for %s: %s" % (viewName, updatelist), 1)
log("MusicVideos to update for %s: %s" % (viewName, updatelist), 1)
embymvideos = self.emby.getFullItems(updatelist)
total = len(updatelist)
del updatelist[:]
@ -1245,20 +1228,19 @@ class ManualSync(LibrarySync):
if kodimvideo not in all_embymvideosIds:
mvideos.remove(kodimvideo)
else:
self.logMsg("MusicVideos compare finished.", 1)
log("MusicVideos compare finished.", 1)
return True
def tvshows(self, embycursor, kodicursor, pdialog):
lang = utils.language
# Get shows from emby
emby_db = embydb.Embydb_Functions(embycursor)
tvshows = itemtypes.TVShows(embycursor, kodicursor)
views = emby_db.getView_byType('tvshows')
views += emby_db.getView_byType('mixed')
self.logMsg("Media folders: %s" % views, 1)
log("Media folders: %s" % views, 1)
# Pull the list of tvshows and episodes in Kodi
try:
@ -1287,7 +1269,7 @@ class ManualSync(LibrarySync):
if pdialog:
pdialog.update(
heading="Emby for Kodi",
heading=lang(29999),
message="%s %s..." % (lang(33029), viewName))
all_embytvshows = self.emby.getShows(viewId, basic=True, dialog=pdialog)
@ -1305,7 +1287,7 @@ class ManualSync(LibrarySync):
# Only update if movie is not in Kodi or checksum is different
updatelist.append(itemid)
self.logMsg("TVShows to update for %s: %s" % (viewName, updatelist), 1)
log("TVShows to update for %s: %s" % (viewName, updatelist), 1)
embytvshows = self.emby.getFullItems(updatelist)
total = len(updatelist)
del updatelist[:]
@ -1332,7 +1314,7 @@ class ManualSync(LibrarySync):
# Get all episodes in view
if pdialog:
pdialog.update(
heading="Emby for Kodi",
heading=lang(29999),
message="%s %s..." % (lang(33030), viewName))
all_embyepisodes = self.emby.getEpisodes(viewId, basic=True, dialog=pdialog)
@ -1349,7 +1331,7 @@ class ManualSync(LibrarySync):
# Only update if movie is not in Kodi or checksum is different
updatelist.append(itemid)
self.logMsg("Episodes to update for %s: %s" % (viewName, updatelist), 1)
log("Episodes to update for %s: %s" % (viewName, updatelist), 1)
embyepisodes = self.emby.getFullItems(updatelist)
total = len(updatelist)
del updatelist[:]
@ -1363,7 +1345,8 @@ class ManualSync(LibrarySync):
if pdialog:
percentage = int((float(count) / float(total))*100)
pdialog.update(percentage, message="%s - %s" % (episode['SeriesName'], episode['Name']))
title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name'])
pdialog.update(percentage, message=title)
count += 1
tvshows.add_updateEpisode(episode)
@ -1373,13 +1356,13 @@ class ManualSync(LibrarySync):
if koditvshow not in all_embytvshowsIds:
tvshows.remove(koditvshow)
else:
self.logMsg("TVShows compare finished.", 1)
log("TVShows compare finished.", 1)
for kodiepisode in all_kodiepisodes:
if kodiepisode not in all_embyepisodesIds:
tvshows.remove(kodiepisode)
else:
self.logMsg("Episodes compare finished.", 1)
log("Episodes compare finished.", 1)
return True
@ -1419,8 +1402,8 @@ class ManualSync(LibrarySync):
for data_type in ['artists', 'albums', 'songs']:
if pdialog:
pdialog.update(
heading="Emby for Kodi",
message="%s %s..." % (utils.language(33031), data_type))
heading=lang(29999),
message="%s %s..." % (lang(33031), data_type))
if data_type != "artists":
all_embyitems = process[data_type][0](basic=True, dialog=pdialog)
else:
@ -1445,7 +1428,7 @@ class ManualSync(LibrarySync):
if all_kodisongs.get(itemid) != API.getChecksum():
# Only update if songs is not in Kodi or checksum is different
updatelist.append(itemid)
self.logMsg("%s to update: %s" % (data_type, updatelist), 1)
log("%s to update: %s" % (data_type, updatelist), 1)
embyitems = self.emby.getFullItems(updatelist)
total = len(updatelist)
del updatelist[:]
@ -1466,15 +1449,15 @@ class ManualSync(LibrarySync):
if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None:
music.remove(kodiartist)
else:
self.logMsg("Artist compare finished.", 1)
log("Artist compare finished.", 1)
for kodialbum in all_kodialbums:
if kodialbum not in all_embyalbumsIds:
music.remove(kodialbum)
else:
self.logMsg("Albums compare finished.", 1)
log("Albums compare finished.", 1)
for kodisong in all_kodisongs:
if kodisong not in all_embysongsIds:
music.remove(kodisong)
else:
self.logMsg("Songs compare finished.", 1)
return True
log("Songs compare finished.", 1)
return True

View file

@ -14,20 +14,18 @@ from mutagen import id3
import base64
import read_embyserver as embyserver
import utils
from utils import Logging, window
log = Logging('MusicTools').log
#################################################################################################
# Helper for the music library, intended to fix missing song ID3 tags on Emby
def logMsg(msg, lvl=1):
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
def getRealFileName(filename, isTemp=False):
#get the filename path accessible by python if possible...
if not xbmcvfs.exists(filename):
logMsg( "File does not exist! %s" %(filename), 0)
log("File does not exist! %s" % filename, 0)
return (False, "")
#if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string
@ -104,7 +102,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
elif file_rating is None and not currentvalue:
return (emby_rating, comment, False)
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
log("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
updateFileRating = False
updateEmbyRating = False
@ -171,7 +169,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei
if updateEmbyRating and enableexportsongrating:
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
emby.updateUserRating(embyid, like, favourite, deletelike)
return (rating, comment, hasEmbeddedCover)
@ -183,7 +181,7 @@ def getSongTags(file):
hasEmbeddedCover = False
isTemp,filename = getRealFileName(file)
logMsg( "getting song ID3 tags for " + filename)
log( "getting song ID3 tags for " + filename)
try:
###### FLAC FILES #############
@ -217,14 +215,14 @@ def getSongTags(file):
#POPM rating is 0-255 and needs to be converted to 0-5 range
if rating > 5: rating = (rating / 255) * 5
else:
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
log( "Not supported fileformat or unable to access file: %s" %(filename))
#the rating must be a round value
rating = int(round(rating,0))
except Exception as e:
#file in use ?
utils.logMsg("Exception in getSongTags", str(e),0)
log("Exception in getSongTags", str(e),0)
rating = None
#remove tempfile if needed....
@ -246,7 +244,7 @@ def updateRatingToFile(rating, file):
xbmcvfs.copy(file, tempfile)
tempfile = xbmc.translatePath(tempfile).decode("utf-8")
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
log( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
if not tempfile:
return
@ -263,7 +261,7 @@ def updateRatingToFile(rating, file):
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
audio.save()
else:
logMsg( "Not supported fileformat: %s" %(tempfile))
log( "Not supported fileformat: %s" %(tempfile))
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
#safety check: we check the file size of the temp file before proceeding with overwite of original file
@ -274,14 +272,14 @@ def updateRatingToFile(rating, file):
xbmcvfs.delete(file)
xbmcvfs.copy(tempfile,file)
else:
logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile))
log( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile))
#always delete the tempfile
xbmcvfs.delete(tempfile)
except Exception as e:
#file in use ?
logMsg("Exception in updateRatingToFile %s" %e,0)
log("Exception in updateRatingToFile %s" %e,0)

View file

@ -16,7 +16,7 @@ import downloadutils
import playutils as putils
import playlist
import read_embyserver as embyserver
import utils
from utils import Logging, window, settings, language as lang
#################################################################################################
@ -26,6 +26,9 @@ class PlaybackUtils():
def __init__(self, item):
global log
log = Logging(self.__class__.__name__).log
self.item = item
self.API = api.API(self.item)
@ -33,28 +36,20 @@ class PlaybackUtils():
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.userid = window('emby_currUser')
self.server = window('emby_server%s' % self.userid)
self.artwork = artwork.Artwork()
self.emby = embyserver.Read_EmbyServer()
self.pl = playlist.Playlist()
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def play(self, itemid, dbid=None):
window = utils.window
settings = utils.settings
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(self.item)
self.logMsg("Play called.", 1)
log("Play called.", 1)
playurl = playutils.getPlayUrl()
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
@ -77,9 +72,9 @@ class PlaybackUtils():
introsPlaylist = False
dummyPlaylist = False
self.logMsg("Playlist start position: %s" % startPos, 2)
self.logMsg("Playlist plugin position: %s" % currentPosition, 2)
self.logMsg("Playlist size: %s" % sizePlaylist, 2)
log("Playlist start position: %s" % startPos, 2)
log("Playlist plugin position: %s" % currentPosition, 2)
log("Playlist size: %s" % sizePlaylist, 2)
############### RESUME POINT ################
@ -91,12 +86,11 @@ class PlaybackUtils():
if not propertiesPlayback:
window('emby_playbackProps', value="true")
self.logMsg("Setting up properties in playlist.", 1)
log("Setting up properties in playlist.", 1)
if (not homeScreen and not seektime and
window('emby_customPlaylist') != "true"):
if not homeScreen and not seektime and window('emby_customPlaylist') != "true":
self.logMsg("Adding dummy file to playlist.", 2)
log("Adding dummy file to playlist.", 2)
dummyPlaylist = True
playlist.add(playurl, listitem, index=startPos)
# Remove the original item from playlist
@ -116,18 +110,18 @@ class PlaybackUtils():
getTrailers = True
if settings('askCinema') == "true":
resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016))
resp = xbmcgui.Dialog().yesno("Emby for Kodi", lang(33016))
if not resp:
# User selected to not play trailers
getTrailers = False
self.logMsg("Skip trailers.", 1)
log("Skip trailers.", 1)
if getTrailers:
for intro in intros['Items']:
# The server randomly returns intros, process them.
introListItem = xbmcgui.ListItem()
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
self.logMsg("Adding Intro: %s" % introPlayurl, 1)
log("Adding Intro: %s" % introPlayurl, 1)
# Set listitem and properties for intros
pbutils = PlaybackUtils(intro)
@ -143,7 +137,7 @@ class PlaybackUtils():
if homeScreen and not seektime and not sizePlaylist:
# Extend our current playlist with the actual item to play
# only if there's no playlist first
self.logMsg("Adding main item to playlist.", 1)
log("Adding main item to playlist.", 1)
self.pl.addtoPlaylist(dbid, self.item['Type'].lower())
# Ensure that additional parts are played after the main item
@ -160,7 +154,7 @@ class PlaybackUtils():
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
self.logMsg("Adding additional part: %s" % partcount, 1)
log("Adding additional part: %s" % partcount, 1)
# Set listitem and properties for each additional parts
pbutils = PlaybackUtils(part)
@ -174,13 +168,13 @@ class PlaybackUtils():
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
self.logMsg("Processed as a playlist. First item is skipped.", 1)
log("Processed as a playlist. First item is skipped.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
self.logMsg("Resetting properties playback flag.", 2)
log("Resetting properties playback flag.", 2)
window('emby_playbackProps', clear=True)
#self.pl.verifyPlaylist()
@ -190,7 +184,7 @@ class PlaybackUtils():
if window('emby_%s.playmethod' % playurl) == "Transcode":
# Filter ISO since Emby does not probe anymore
if self.item.get('VideoType') == "Iso":
self.logMsg("Skipping audio/subs prompt, ISO detected.", 1)
log("Skipping audio/subs prompt, ISO detected.", 1)
else:
playurl = playutils.audioSubsPref(playurl, listitem)
window('emby_%s.playmethod' % playurl, value="Transcode")
@ -201,23 +195,22 @@ class PlaybackUtils():
############### PLAYBACK ################
if homeScreen and seektime and window('emby_customPlaylist') != "true":
self.logMsg("Play as a widget item.", 1)
log("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
self.logMsg("Play playlist.", 1)
log("Play playlist.", 1)
xbmc.Player().play(playlist, startpos=startPos)
else:
self.logMsg("Play as a regular item.", 1)
log("Play as a regular item.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def setProperties(self, playurl, listitem):
window = utils.window
# Set all properties necessary for plugin path playback
itemid = self.item['Id']
itemtype = self.item['Type']
@ -233,7 +226,7 @@ class PlaybackUtils():
window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = utils.window('%s.playmethod' % embyitem)
playmethod = window('%s.playmethod' % embyitem)
# Only for direct stream
if playmethod in ("DirectStream"):
# Direct play automatically appends external
@ -272,7 +265,7 @@ class PlaybackUtils():
kodiindex += 1
mapping = json.dumps(mapping)
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
window('emby_%s.indexMapping' % playurl, value=mapping)
return externalsubs

View file

@ -7,11 +7,11 @@ import json
import xbmc
import xbmcgui
import utils
import clientinfo
import downloadutils
import kodidb_functions as kodidb
import websocket_client as wsc
from utils import Logging, window, settings, language as lang
#################################################################################################
@ -28,6 +28,9 @@ class Player(xbmc.Player):
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.__dict__ = self._shared_state
self.clientInfo = clientinfo.ClientInfo()
@ -36,20 +39,13 @@ class Player(xbmc.Player):
self.ws = wsc.WebSocket_Client()
self.xbmcplayer = xbmc.Player()
self.logMsg("Starting playback monitor.", 2)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
log("Starting playback monitor.", 2)
def GetPlayStats(self):
return self.playStats
def onPlayBackStarted(self):
window = utils.window
# Will be called when xbmc starts playing a file
self.stopAll()
@ -67,7 +63,7 @@ class Player(xbmc.Player):
except: pass
if count == 5: # try 5 times
self.logMsg("Cancelling playback report...", 1)
log("Cancelling playback report...", 1)
break
else: count += 1
@ -84,12 +80,12 @@ class Player(xbmc.Player):
xbmc.sleep(200)
itemId = window("emby_%s.itemid" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
self.logMsg("Could not find itemId, cancelling playback report...", 1)
log("Could not find itemId, cancelling playback report...", 1)
break
else: tryCount += 1
else:
self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
# Only proceed if an itemId was found.
embyitem = "emby_%s" % currentFile
@ -102,7 +98,7 @@ class Player(xbmc.Player):
customseek = window('emby_customPlaylist.seektime')
if window('emby_customPlaylist') == "true" and customseek:
# Start at, when using custom playlist (play to Kodi from webclient)
self.logMsg("Seeking to: %s" % customseek, 1)
log("Seeking to: %s" % customseek, 1)
self.xbmcplayer.seekTime(int(customseek)/10000000.0)
window('emby_customPlaylist.seektime', clear=True)
@ -189,7 +185,7 @@ class Player(xbmc.Player):
if mapping: # Set in playbackutils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
log("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
@ -207,7 +203,7 @@ class Player(xbmc.Player):
# Post playback to server
self.logMsg("Sending POST play started: %s." % postdata, 2)
log("Sending POST play started: %s." % postdata, 2)
self.doUtils(url, postBody=postdata, action_type="POST")
# Ensure we do have a runtime
@ -215,7 +211,7 @@ class Player(xbmc.Player):
runtime = int(runtime)
except ValueError:
runtime = self.xbmcplayer.getTotalTime()
self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1)
log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
# Save data map for updates and position calls
data = {
@ -232,7 +228,7 @@ class Player(xbmc.Player):
}
self.played_info[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_info, 1)
log("ADDING_FILE: %s" % self.played_info, 1)
# log some playback stats
'''if(itemType != None):
@ -251,7 +247,7 @@ class Player(xbmc.Player):
def reportPlayback(self):
self.logMsg("reportPlayback Called", 2)
log("reportPlayback Called", 2)
# Get current file
currentFile = self.currentFile
@ -345,11 +341,11 @@ class Player(xbmc.Player):
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = utils.window("emby_%s.indexMapping" % currentFile)
mapping = window("emby_%s.indexMapping" % currentFile)
if mapping: # Set in PlaybackUtils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
log("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
@ -369,13 +365,13 @@ class Player(xbmc.Player):
# Report progress via websocketclient
postdata = json.dumps(postdata)
self.logMsg("Report: %s" % postdata, 2)
log("Report: %s" % postdata, 2)
self.ws.sendProgressUpdate(postdata)
def onPlayBackPaused(self):
currentFile = self.currentFile
self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
log("PLAYBACK_PAUSED: %s" % currentFile, 2)
if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = True
@ -385,7 +381,7 @@ class Player(xbmc.Player):
def onPlayBackResumed(self):
currentFile = self.currentFile
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
log("PLAYBACK_RESUMED: %s" % currentFile, 2)
if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = False
@ -395,7 +391,7 @@ class Player(xbmc.Player):
def onPlayBackSeek(self, time, seekOffset):
# Make position when seeking a bit more accurate
currentFile = self.currentFile
self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
log("PLAYBACK_SEEK: %s" % currentFile, 2)
if self.played_info.get(currentFile):
position = self.xbmcplayer.getTime()
@ -404,39 +400,34 @@ class Player(xbmc.Player):
self.reportPlayback()
def onPlayBackStopped(self):
window = utils.window
# Will be called when user stops xbmc playing a file
self.logMsg("ONPLAYBACK_STOPPED", 2)
log("ONPLAYBACK_STOPPED", 2)
window('emby_customPlaylist', clear=True)
window('emby_customPlaylist.seektime', clear=True)
window('emby_playbackProps', clear=True)
self.logMsg("Clear playlist properties.", 1)
log("Clear playlist properties.", 1)
self.stopAll()
def onPlayBackEnded(self):
# Will be called when xbmc stops playing a file
self.logMsg("ONPLAYBACK_ENDED", 2)
utils.window('emby_customPlaylist.seektime', clear=True)
log("ONPLAYBACK_ENDED", 2)
window('emby_customPlaylist.seektime', clear=True)
self.stopAll()
def stopAll(self):
lang = utils.language
settings = utils.settings
if not self.played_info:
return
self.logMsg("Played_information: %s" % self.played_info, 1)
log("Played_information: %s" % self.played_info, 1)
# Process each items
for item in self.played_info:
data = self.played_info.get(item)
if data:
self.logMsg("Item path: %s" % item, 2)
self.logMsg("Item data: %s" % data, 2)
log("Item path: %s" % item, 2)
log("Item data: %s" % data, 2)
runtime = data['runtime']
currentPosition = data['currentPosition']
@ -447,7 +438,7 @@ class Player(xbmc.Player):
playMethod = data['playmethod']
# Prevent manually mark as watched in Kodi monitor
utils.window('emby_skipWatched%s' % itemid, value="true")
window('emby_skipWatched%s' % itemid, value="true")
if currentPosition and runtime:
try:
@ -457,7 +448,7 @@ class Player(xbmc.Player):
percentComplete = 0
markPlayedAt = float(settings('markPlayed')) / 100
self.logMsg("Percent complete: %s Mark played at: %s"
log("Percent complete: %s Mark played at: %s"
% (percentComplete, markPlayedAt), 1)
# Send the delete action to the server.
@ -475,18 +466,18 @@ class Player(xbmc.Player):
if percentComplete >= markPlayedAt and offerDelete:
resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000)
if not resp:
self.logMsg("User skipped deletion.", 1)
log("User skipped deletion.", 1)
continue
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid, 1)
log("Deleting request: %s" % itemid, 1)
self.doUtils(url, action_type="DELETE")
self.stopPlayback(data)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemid, 1)
log("Transcoding for %s terminated." % itemid, 1)
deviceId = self.clientInfo.getDeviceId()
url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils(url, action_type="DELETE")
@ -495,7 +486,7 @@ class Player(xbmc.Player):
def stopPlayback(self, data):
self.logMsg("stopPlayback called", 2)
log("stopPlayback called", 2)
itemId = data['item_id']
currentPosition = data['currentPosition']

View file

@ -13,7 +13,7 @@ import playutils
import playbackutils
import embydb_functions as embydb
import read_embyserver as embyserver
import utils
from utils import Logging, window, settings, language as lang, kodiSQL
#################################################################################################
@ -23,25 +23,21 @@ class Playlist():
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.userid = window('emby_currUser')
self.server = window('emby_server%s' % self.userid)
self.emby = embyserver.Read_EmbyServer()
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def playAll(self, itemids, startat):
window = utils.window
embyconn = utils.kodiSQL('emby')
embyconn = kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
@ -49,8 +45,8 @@ class Playlist():
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
self.logMsg("---*** PLAY ALL ***---", 1)
self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1)
log("---*** PLAY ALL ***---", 1)
log("Items: %s and start at: %s" % (itemids, startat), 1)
started = False
window('emby_customplaylist', value="true")
@ -66,14 +62,14 @@ class Playlist():
mediatype = embydb_item[4]
except TypeError:
# Item is not found in our database, add item manually
self.logMsg("Item was not found in the database, manually adding item.", 1)
log("Item was not found in the database, manually adding item.", 1)
item = self.emby.getItem(itemid)
self.addtoPlaylist_xbmc(playlist, item)
else:
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
self.logMsg("Adding %s to playlist." % itemid, 1)
log("Adding %s to playlist." % itemid, 1)
if not started:
started = True
@ -84,12 +80,12 @@ class Playlist():
def modifyPlaylist(self, itemids):
embyconn = utils.kodiSQL('emby')
embyconn = kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
self.logMsg("Items: %s" % itemids, 1)
log("---*** ADD TO PLAYLIST ***---", 1)
log("Items: %s" % itemids, 1)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
@ -107,7 +103,7 @@ class Playlist():
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
self.logMsg("Adding %s to playlist." % itemid, 1)
log("Adding %s to playlist." % itemid, 1)
self.verifyPlaylist()
embycursor.close()
@ -130,17 +126,17 @@ class Playlist():
else:
pl['params']['item'] = {'file': url}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
log(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def addtoPlaylist_xbmc(self, playlist, item):
playurl = playutils.PlayUtils(item).getPlayUrl()
if not playurl:
# Playurl failed
self.logMsg("Failed to retrieve playurl.", 1)
log("Failed to retrieve playurl.", 1)
return
self.logMsg("Playurl: %s" % playurl)
log("Playurl: %s" % playurl)
listitem = xbmcgui.ListItem()
playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
@ -164,7 +160,7 @@ class Playlist():
else:
pl['params']['item'] = {'file': url}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
log(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def verifyPlaylist(self):
@ -178,7 +174,7 @@ class Playlist():
'playlistid': 1
}
}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
log(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def removefromPlaylist(self, position):
@ -193,4 +189,4 @@ class Playlist():
'position': position
}
}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
log(xbmc.executeJSONRPC(json.dumps(pl)), 2)

View file

@ -7,7 +7,7 @@ import xbmcgui
import xbmcvfs
import clientinfo
import utils
from utils import Logging, window, settings, language as lang
#################################################################################################
@ -17,41 +17,37 @@ class PlayUtils():
def __init__(self, item):
global log
log = Logging(self.__class__.__name__).log
self.item = item
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
self.userid = window('emby_currUser')
self.server = window('emby_server%s' % self.userid)
def getPlayUrl(self):
window = utils.window
playurl = None
if (self.item.get('Type') in ("Recording", "TvChannel") and
self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"):
if (self.item.get('Type') in ("Recording", "TvChannel") and self.item.get('MediaSources')
and self.item['MediaSources'][0]['Protocol'] == "Http"):
# Play LiveTV or recordings
self.logMsg("File protocol is http (livetv).", 1)
log("File protocol is http (livetv).", 1)
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, self.item['Id'])
window('emby_%s.playmethod' % playurl, value="Transcode")
elif self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http":
# Only play as http, used for channels, or online hosting of content
self.logMsg("File protocol is http.", 1)
log("File protocol is http.", 1)
playurl = self.httpPlay()
window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isDirectPlay():
self.logMsg("File is direct playing.", 1)
log("File is direct playing.", 1)
playurl = self.directPlay()
playurl = playurl.encode('utf-8')
# Set playmethod property
@ -59,14 +55,14 @@ class PlayUtils():
elif self.isDirectStream():
self.logMsg("File is direct streaming.", 1)
log("File is direct streaming.", 1)
playurl = self.directStream()
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
self.logMsg("File is transcoding.", 1)
log("File is transcoding.", 1)
playurl = self.transcoding()
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="Transcode")
@ -88,21 +84,18 @@ class PlayUtils():
def isDirectPlay(self):
lang = utils.language
settings = utils.settings
dialog = xbmcgui.Dialog()
# Requirement: Filesystem, Accessible path
if settings('playFromStream') == "true":
# User forcing to play via HTTP
self.logMsg("Can't direct play, play from HTTP enabled.", 1)
log("Can't direct play, play from HTTP enabled.", 1)
return False
videotrack = self.item['MediaSources'][0]['Name']
transcodeH265 = settings('transcodeH265')
videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x]
transcodeHi10P = utils.settings('transcodeHi10P')
transcodeHi10P = settings('transcodeHi10P')
if transcodeHi10P == "true" and "H264" in videotrack and "High 10" in videoprofiles:
return False
@ -116,7 +109,7 @@ class PlayUtils():
'2': 720,
'3': 1080
}
self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
log("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1)
if res[transcodeH265] <= resolution:
return False
@ -124,25 +117,25 @@ class PlayUtils():
canDirectPlay = self.item['MediaSources'][0]['SupportsDirectPlay']
# Make sure direct play is supported by the server
if not canDirectPlay:
self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
log("Can't direct play, server doesn't allow/support it.", 1)
return False
location = self.item['LocationType']
if location == "FileSystem":
# Verify the path
if not self.fileExists():
self.logMsg("Unable to direct play.")
log("Unable to direct play.", 1)
try:
count = int(settings('failCount'))
except ValueError:
count = 0
self.logMsg("Direct play failed: %s times." % count, 1)
log("Direct play failed: %s times." % count, 1)
if count < 2:
# Let the user know that direct play failed
settings('failCount', value=str(count+1))
dialog.notification(
heading="Emby for Kodi",
heading=lang(29999),
message=lang(33011),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
@ -151,7 +144,7 @@ class PlayUtils():
settings('playFromStream', value="true")
settings('failCount', value="0")
dialog.notification(
heading="Emby for Kodi",
heading=lang(29999),
message=lang(33012),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
@ -192,27 +185,26 @@ class PlayUtils():
# Convert path to direct play
path = self.directPlay()
self.logMsg("Verifying path: %s" % path, 1)
log("Verifying path: %s" % path, 1)
if xbmcvfs.exists(path):
self.logMsg("Path exists.", 1)
log("Path exists.", 1)
return True
elif ":" not in path:
self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
log("Can't verify path, assumed linux. Still try to direct play.", 1)
return True
else:
self.logMsg("Failed to find file.", 1)
log("Failed to find file.", 1)
return False
def isDirectStream(self):
videotrack = self.item['MediaSources'][0]['Name']
transcodeH265 = utils.settings('transcodeH265')
transcodeH265 = settings('transcodeH265')
videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x]
transcodeHi10P = utils.settings('transcodeHi10P')
transcodeHi10P = settings('transcodeHi10P')
if transcodeHi10P == "true" and "H264" in videotrack and "High 10" in videoprofiles:
return False
@ -226,7 +218,7 @@ class PlayUtils():
'2': 720,
'3': 1080
}
self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
log("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1)
if res[transcodeH265] <= resolution:
return False
@ -239,7 +231,7 @@ class PlayUtils():
# Verify the bitrate
if not self.isNetworkSufficient():
self.logMsg("The network speed is insufficient to direct stream file.", 1)
log("The network speed is insufficient to direct stream file.", 1)
return False
return True
@ -258,15 +250,14 @@ class PlayUtils():
def isNetworkSufficient(self):
settings = self.getBitrate()*1000
try:
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
except (KeyError, TypeError):
self.logMsg("Bitrate value is missing.", 1)
log("Bitrate value is missing.", 1)
else:
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
log("The add-on settings bitrate is: %s, the video bitrate required is: %s"
% (settings, sourceBitrate), 1)
if settings < sourceBitrate:
return False
@ -325,11 +316,10 @@ class PlayUtils():
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(utils.settings('videoBitrate'), 2147483)
return bitrate.get(settings('videoBitrate'), 2147483)
def audioSubsPref(self, url, listitem):
lang = utils.language
dialog = xbmcgui.Dialog()
# For transcoding only
# Present the list of audio to select from

View file

@ -4,21 +4,22 @@
import xbmc
import utils
import clientinfo
import downloadutils
from utils import Logging, window, settings, kodiSQL
#################################################################################################
class Read_EmbyServer():
limitIndex = int(utils.settings('limitindex'))
limitIndex = int(settings('limitindex'))
def __init__(self):
window = utils.window
global log
log = Logging(self.__class__.__name__).log
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
@ -27,17 +28,11 @@ class Read_EmbyServer():
self.userId = window('emby_currUser')
self.server = window('emby_server%s' % self.userId)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def split_list(self, itemlist, size):
# Split up list in pieces of size. Will generate a list of lists
return [itemlist[i:i+size] for i in range(0, len(itemlist), size)]
def getItem(self, itemid):
# This will return the full item
item = {}
@ -60,7 +55,8 @@ class Read_EmbyServer():
'Ids': ",".join(itemlist),
'Fields': "Etag"
}
result = self.doUtils("{server}/emby/Users/{UserId}/Items?&format=json", parameters=params)
url = "{server}/emby/Users/{UserId}/Items?&format=json"
result = self.doUtils(url, parameters=params)
if result:
items.extend(result['Items'])
@ -86,7 +82,8 @@ class Read_EmbyServer():
"MediaSources,VoteCount"
)
}
result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
url = "{server}/emby/Users/{UserId}/Items?format=json"
result = self.doUtils(url, parameters=params)
if result:
items.extend(result['Items'])
@ -96,14 +93,15 @@ class Read_EmbyServer():
# Returns ancestors using embyId
viewId = None
for view in self.doUtils("{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid):
url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid
for view in self.doUtils(url):
if view['Type'] == "CollectionFolder":
# Found view
viewId = view['Id']
# Compare to view table in emby database
emby = utils.kodiSQL('emby')
emby = kodiSQL('emby')
cursor_emby = emby.cursor()
query = ' '.join((
@ -124,7 +122,8 @@ class Read_EmbyServer():
return [viewName, viewId, mediatype]
def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""):
def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True,
limit=None, sortorder="Ascending", filter=""):
params = {
'ParentId': parentid,
@ -137,39 +136,54 @@ class Read_EmbyServer():
'SortBy': sortby,
'SortOrder': sortorder,
'Filters': filter,
'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
'Fields': (
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
)
}
return self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
def getTvChannels(self):
params = {
'EnableImages': True,
'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
'Fields': (
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
)
}
return self.doUtils("{server}/emby/LiveTv/Channels/?userid={UserId}&format=json", parameters=params)
url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json"
return self.doUtils(url, parameters=params)
def getTvRecordings(self, groupid):
if groupid == "root": groupid = ""
if groupid == "root":
groupid = ""
params = {
'GroupId': groupid,
'EnableImages': True,
'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
'Fields': (
"Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
)
}
return self.doUtils("{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json", parameters=params)
url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json"
return self.doUtils(url, parameters=params)
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
@ -197,7 +211,7 @@ class Read_EmbyServer():
items['TotalRecordCount'] = total
except TypeError: # Failed to retrieve
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
log("%s:%s Failed to retrieve the server response." % (url, params), 2)
else:
index = 0
@ -239,27 +253,27 @@ class Read_EmbyServer():
# Something happened to the connection
if not throttled:
throttled = True
self.logMsg("Throttle activated.", 1)
log("Throttle activated.", 1)
if jump == highestjump:
# We already tried with the highestjump, but it failed. Reset value.
self.logMsg("Reset highest value.", 1)
log("Reset highest value.", 1)
highestjump = 0
# Lower the number by half
if highestjump:
throttled = False
jump = highestjump
self.logMsg("Throttle deactivated.", 1)
log("Throttle deactivated.", 1)
else:
jump = int(jump/4)
self.logMsg("Set jump limit to recover: %s" % jump, 2)
log("Set jump limit to recover: %s" % jump, 2)
retry = 0
while utils.window('emby_online') != "true":
while window('emby_online') != "true":
# Wait server to come back online
if retry == 5:
self.logMsg("Unable to reconnect to server. Abort process.", 1)
log("Unable to reconnect to server. Abort process.", 1)
return items
retry += 1
@ -287,7 +301,7 @@ class Read_EmbyServer():
increment = 10
jump += increment
self.logMsg("Increase jump limit to: %s" % jump, 1)
log("Increase jump limit to: %s" % jump, 1)
return items
def getViews(self, mediatype="", root=False, sortedlist=False):
@ -304,7 +318,7 @@ class Read_EmbyServer():
try:
items = result['Items']
except TypeError:
self.logMsg("Error retrieving views for type: %s" % mediatype, 2)
log("Error retrieving views for type: %s" % mediatype, 2)
else:
for item in items:
@ -373,15 +387,18 @@ class Read_EmbyServer():
return belongs
def getMovies(self, parentId, basic=False, dialog=None):
return self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
def getBoxset(self, dialog=None):
return self.getSection(None, "BoxSet", dialog=dialog)
def getMovies_byBoxset(self, boxsetid):
return self.getSection(boxsetid, "Movie")
def getMusicVideos(self, parentId, basic=False, dialog=None):
return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
def getHomeVideos(self, parentId):
@ -389,6 +406,7 @@ class Read_EmbyServer():
return self.getSection(parentId, "Video")
def getShows(self, parentId, basic=False, dialog=None):
return self.getSection(parentId, "Series", basic=basic, dialog=dialog)
def getSeasons(self, showId):
@ -404,7 +422,8 @@ class Read_EmbyServer():
'IsVirtualUnaired': False,
'Fields': "Etag"
}
result = self.doUtils("{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId, parameters=params)
url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
result = self.doUtils(url, parameters=params)
if result:
items = result
@ -422,7 +441,6 @@ class Read_EmbyServer():
return self.getSection(seasonId, "Episode")
def getArtists(self, dialog=None):
items = {
@ -444,7 +462,7 @@ class Read_EmbyServer():
items['TotalRecordCount'] = total
except TypeError: # Failed to retrieve
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
log("%s:%s Failed to retrieve the server response." % (url, params), 2)
else:
index = 1
@ -478,17 +496,20 @@ class Read_EmbyServer():
return items
def getAlbums(self, basic=False, dialog=None):
return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
def getAlbumsbyArtist(self, artistId):
return self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
def getSongs(self, basic=False, dialog=None):
return self.getSection(None, "Audio", basic=basic, dialog=dialog)
def getSongsbyAlbum(self, albumId):
return self.getSection(albumId, "Audio")
return self.getSection(albumId, "Audio")
def getAdditionalParts(self, itemId):
@ -497,8 +518,8 @@ class Read_EmbyServer():
'Items': [],
'TotalRecordCount': 0
}
result = self.doUtils("{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId)
url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
result = self.doUtils(url)
if result:
items = result
@ -518,23 +539,36 @@ class Read_EmbyServer():
return sorted_items
def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False):
def updateUserRating(self, itemid, favourite=None):
# Updates the user rating to Emby
doUtils = self.doUtils
if favourite:
self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST")
elif favourite == False:
self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="DELETE")
if not deletelike and like:
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid, action_type="POST")
elif not deletelike and like is False:
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid, action_type="POST")
elif deletelike:
self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid, action_type="DELETE")
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
doUtils(url, action_type="POST")
elif not favourite:
url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
doUtils(url, action_type="DELETE")
else:
self.logMsg("Error processing user rating.", 1)
log("Error processing user rating.", 1)
self.logMsg("Update user rating to emby for itemid: %s "
"| like: %s | favourite: %s | deletelike: %s"
% (itemid, like, favourite, deletelike), 1)
log("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite), 1)
def refreshItem(self, itemid):
url = "{server}/emby/Items/%s/Refresh?format=json" % itemid
params = {
'Recursive': True,
'ImageRefreshMode': "FullRefresh",
'MetadataRefreshMode': "FullRefresh",
'ReplaceAllImages': False,
'ReplaceAllMetadata': True
}
self.doUtils(url, postBody=params, action_type="POST")
def deleteItem(self, itemid):
url = "{server}/emby/Items/%s?format=json" % itemId
self.doUtils(url, action_type="DELETE")

View file

@ -11,9 +11,9 @@ import xbmcaddon
import xbmcvfs
import artwork
import utils
import clientinfo
import downloadutils
from utils import Logging, window, settings, language as lang
##################################################################################################
@ -39,6 +39,9 @@ class UserClient(threading.Thread):
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.__dict__ = self._shared_state
self.addon = xbmcaddon.Addon()
@ -47,25 +50,20 @@ class UserClient(threading.Thread):
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getAdditionalUsers(self):
additionalUsers = utils.settings('additionalUsers')
additionalUsers = settings('additionalUsers')
if additionalUsers:
self.AdditionalUser = additionalUsers.split(',')
def getUsername(self):
username = utils.settings('username')
username = settings('username')
if not username:
self.logMsg("No username saved.", 2)
log("No username saved.", 2)
return ""
return username
@ -73,7 +71,7 @@ class UserClient(threading.Thread):
def getLogLevel(self):
try:
logLevel = int(utils.settings('logLevel'))
logLevel = int(settings('logLevel'))
except ValueError:
logLevel = 0
@ -81,9 +79,6 @@ class UserClient(threading.Thread):
def getUserId(self):
window = utils.window
settings = utils.settings
username = self.getUsername()
w_userId = window('emby_currUser')
s_userId = settings('userId%s' % username)
@ -93,22 +88,20 @@ class UserClient(threading.Thread):
if not s_userId:
# Save access token if it's missing from settings
settings('userId%s' % username, value=w_userId)
self.logMsg("Returning userId from WINDOW for username: %s UserId: %s"
log("Returning userId from WINDOW for username: %s UserId: %s"
% (username, w_userId), 2)
return w_userId
# Verify the settings
elif s_userId:
self.logMsg("Returning userId from SETTINGS for username: %s userId: %s"
log("Returning userId from SETTINGS for username: %s userId: %s"
% (username, s_userId), 2)
return s_userId
# No userId found
else:
self.logMsg("No userId saved for username: %s." % username, 1)
log("No userId saved for username: %s." % username, 1)
def getServer(self, prefix=True):
settings = utils.settings
alternate = settings('altip') == "true"
if alternate:
# Alternate host
@ -124,7 +117,7 @@ class UserClient(threading.Thread):
server = host + ":" + port
if not host:
self.logMsg("No server information saved.", 2)
log("No server information saved.", 2)
return False
# If https is true
@ -141,9 +134,6 @@ class UserClient(threading.Thread):
def getToken(self):
window = utils.window
settings = utils.settings
username = self.getUsername()
userId = self.getUserId()
w_token = window('emby_accessToken%s' % userId)
@ -154,23 +144,21 @@ class UserClient(threading.Thread):
if not s_token:
# Save access token if it's missing from settings
settings('accessToken', value=w_token)
self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s"
log("Returning accessToken from WINDOW for username: %s accessToken: %s"
% (username, w_token), 2)
return w_token
# Verify the settings
elif s_token:
self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s"
log("Returning accessToken from SETTINGS for username: %s accessToken: %s"
% (username, s_token), 2)
window('emby_accessToken%s' % username, value=s_token)
return s_token
else:
self.logMsg("No token found.", 1)
log("No token found.", 1)
return ""
def getSSLverify(self):
# Verify host certificate
settings = utils.settings
s_sslverify = settings('sslverify')
if settings('altip') == "true":
s_sslverify = settings('secondsslverify')
@ -182,8 +170,6 @@ class UserClient(threading.Thread):
def getSSL(self):
# Client side certificate
settings = utils.settings
s_cert = settings('sslcert')
if settings('altip') == "true":
s_cert = settings('secondsslcert')
@ -201,16 +187,16 @@ class UserClient(threading.Thread):
self.userSettings = result
# Set user image for skin display
if result.get('PrimaryImageTag'):
utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary'))
window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary'))
# Set resume point max
result = doUtils("{server}/emby/System/Configuration?format=json")
utils.settings('markPlayed', value=str(result['MaxResumePct']))
settings('markPlayed', value=str(result['MaxResumePct']))
def getPublicUsers(self):
# Get public Users
result = self.doUtils.downloadUrl("%s/emby/Users/Public?format=json" % self.getServer(), authenticate=False)
url = "%s/emby/Users/Public?format=json" % self.getServer()
result = self.doUtils.downloadUrl(url, authenticate=False)
if result != "":
return result
else:
@ -220,13 +206,11 @@ class UserClient(threading.Thread):
def hasAccess(self):
# hasAccess is verified in service.py
window = utils.window
result = self.doUtils.downloadUrl("{server}/emby/Users?format=json")
if result == False:
# Access is restricted, set in downloadutils.py via exception
self.logMsg("Access is restricted.", 1)
log("Access is restricted.", 1)
self.HasAccess = False
elif window('emby_online') != "true":
@ -234,15 +218,13 @@ class UserClient(threading.Thread):
pass
elif window('emby_serverStatus') == "restricted":
self.logMsg("Access is granted.", 1)
log("Access is granted.", 1)
self.HasAccess = True
window('emby_serverStatus', clear=True)
xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
xbmcgui.Dialog().notification(lang(29999), lang(33007))
def loadCurrUser(self, authenticated=False):
window = utils.window
doUtils = self.doUtils
username = self.getUsername()
userId = self.getUserId()
@ -290,9 +272,6 @@ class UserClient(threading.Thread):
def authenticate(self):
lang = utils.language
window = utils.window
settings = utils.settings
dialog = xbmcgui.Dialog()
# Get /profile/addon_data
@ -304,12 +283,12 @@ class UserClient(threading.Thread):
# If there's no settings.xml
if not hasSettings:
self.logMsg("No settings.xml found.", 1)
log("No settings.xml found.", 1)
self.auth = False
return
# If no user information
elif not server or not username:
self.logMsg("Missing server information.", 1)
log("Missing server information.", 1)
self.auth = False
return
# If there's a token, load the user
@ -319,9 +298,9 @@ class UserClient(threading.Thread):
if result is False:
pass
else:
self.logMsg("Current user: %s" % self.currUser, 1)
self.logMsg("Current userId: %s" % self.currUserId, 1)
self.logMsg("Current accessToken: %s" % self.currToken, 2)
log("Current user: %s" % self.currUser, 1)
log("Current userId: %s" % self.currUserId, 1)
log("Current accessToken: %s" % self.currToken, 2)
return
##### AUTHENTICATE USER #####
@ -341,7 +320,7 @@ class UserClient(threading.Thread):
option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
if not password:
self.logMsg("No password entered.", 0)
log("No password entered.", 0)
window('emby_serverStatus', value="Stop")
self.auth = False
return
@ -356,37 +335,38 @@ class UserClient(threading.Thread):
# Authenticate username and password
data = {'username': username, 'password': sha1}
self.logMsg(data, 2)
log(data, 2)
result = self.doUtils.downloadUrl("%s/emby/Users/AuthenticateByName?format=json" % server, postBody=data, action_type="POST", authenticate=False)
url = "%s/emby/Users/AuthenticateByName?format=json" % server
result = self.doUtils.downloadUrl(url, postBody=data, action_type="POST", authenticate=False)
try:
self.logMsg("Auth response: %s" % result, 1)
log("Auth response: %s" % result, 1)
accessToken = result['AccessToken']
except (KeyError, TypeError):
self.logMsg("Failed to retrieve the api key.", 1)
log("Failed to retrieve the api key.", 1)
accessToken = None
if accessToken is not None:
self.currUser = username
dialog.notification("Emby for Kodi",
dialog.notification(lang(29999),
"%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
settings('accessToken', value=accessToken)
settings('userId%s' % username, value=result['User']['Id'])
self.logMsg("User Authenticated: %s" % accessToken, 1)
log("User Authenticated: %s" % accessToken, 1)
self.loadCurrUser(authenticated=True)
window('emby_serverStatus', clear=True)
self.retry = 0
else:
self.logMsg("User authentication failed.", 1)
log("User authentication failed.", 1)
settings('accessToken', value="")
settings('userId%s' % username, value="")
dialog.ok(lang(33001), lang(33009))
# Give two attempts at entering password
if self.retry == 2:
self.logMsg("Too many retries. "
log("Too many retries. "
"You can retry by resetting attempts in the addon settings.", 1)
window('emby_serverStatus', value="Stop")
dialog.ok(lang(33001), lang(33010))
@ -396,23 +376,21 @@ class UserClient(threading.Thread):
def resetClient(self):
self.logMsg("Reset UserClient authentication.", 1)
log("Reset UserClient authentication.", 1)
if self.currToken is not None:
# In case of 401, removed saved token
utils.settings('accessToken', value="")
utils.window('emby_accessToken%s' % self.getUserId(), clear=True)
settings('accessToken', value="")
window('emby_accessToken%s' % self.getUserId(), clear=True)
self.currToken = None
self.logMsg("User token has been removed.", 1)
log("User token has been removed.", 1)
self.auth = True
self.currUser = None
def run(self):
window = utils.window
monitor = xbmc.Monitor()
self.logMsg("----===## Starting UserClient ##===----", 0)
log("----===## Starting UserClient ##===----", 0)
while not monitor.abortRequested():
@ -447,8 +425,8 @@ class UserClient(threading.Thread):
# The status Stop is for when user cancelled password dialog.
if server and username and status != "Stop":
# Only if there's information found to login
self.logMsg("Server found: %s" % server, 2)
self.logMsg("Username found: %s" % username, 2)
log("Server found: %s" % server, 2)
log("Username found: %s" % username, 2)
self.auth = True
@ -461,7 +439,7 @@ class UserClient(threading.Thread):
break
self.doUtils.stopSession()
self.logMsg("##===---- UserClient Stopped ----===##", 0)
log("##===---- UserClient Stopped ----===##", 0)
def stopClient(self):
# When emby for kodi terminates

View file

@ -9,10 +9,10 @@ import pstats
import sqlite3
import StringIO
import os
from datetime import datetime, time
import time
import unicodedata
import xml.etree.ElementTree as etree
from datetime import datetime, time
import xbmc
import xbmcaddon
@ -20,66 +20,82 @@ import xbmcgui
import xbmcvfs
#################################################################################################
# Main methods
class Logging():
LOGGINGCLASS = None
def logMsg(title, msg, level=1):
def __init__(self, classname=""):
# Get the logLevel set in UserClient
try:
logLevel = int(window('emby_logLevel'))
except ValueError:
logLevel = 0
self.LOGGINGCLASS = classname
if logLevel >= level:
def log(self, msg, level=1):
if logLevel == 2: # inspect.stack() is expensive
try:
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
except UnicodeEncodeError:
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
else:
try:
xbmc.log("%s -> %s" % (title, msg))
except UnicodeEncodeError:
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
self.logMsg("EMBY %s" % self.LOGGINGCLASS, msg, level)
def window(property, value=None, clear=False, windowid=10000):
def logMsg(self, title, msg, level=1):
# Get the logLevel set in UserClient
try:
logLevel = int(window('emby_logLevel'))
except ValueError:
logLevel = 0
if logLevel >= level:
if logLevel == 2: # inspect.stack() is expensive
try:
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
except UnicodeEncodeError:
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
else:
try:
xbmc.log("%s -> %s" % (title, msg))
except UnicodeEncodeError:
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
# Initiate class for utils.py document logging
log = Logging('Utils').log
def window(property, value=None, clear=False, window_id=10000):
# Get or set window property
WINDOW = xbmcgui.Window(windowid)
#setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues
'''if isinstance(property, unicode):
property = property.encode("utf-8")
if isinstance(value, unicode):
value = value.encode("utf-8")'''
WINDOW = xbmcgui.Window(window_id)
if clear:
WINDOW.clearProperty(property)
elif value is not None:
WINDOW.setProperty(property, value)
else: #getproperty returns string so convert to unicode
return WINDOW.getProperty(property)#.decode("utf-8")
else:
return WINDOW.getProperty(property)
def settings(setting, value=None):
# Get or add addon setting
addon = xbmcaddon.Addon(id='plugin.video.emby')
if value is not None:
xbmcaddon.Addon(id='plugin.video.emby').setSetting(setting, value)
else:
return xbmcaddon.Addon(id='plugin.video.emby').getSetting(setting) #returns unicode object
addon.setSetting(setting, value)
else: # returns unicode object
return addon.getSetting(setting)
def language(stringid):
# Central string retrieval
string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(stringid) #returns unicode object
def language(string_id):
# Central string retrieval - unicode
string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id)
return string
#################################################################################################
# Database related methods
def kodiSQL(media_type="video"):
if media_type == "emby":
dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
elif media_type == "music":
dbPath = getKodiMusicDBPath()
elif media_type == "texture":
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
elif media_type == "music":
dbPath = getKodiMusicDBPath()
else:
dbPath = getKodiVideoDBPath()
@ -98,8 +114,8 @@ def getKodiVideoDBPath():
}
dbPath = xbmc.translatePath(
"special://database/MyVideos%s.db"
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
"special://database/MyVideos%s.db"
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
return dbPath
def getKodiMusicDBPath():
@ -114,10 +130,13 @@ def getKodiMusicDBPath():
}
dbPath = xbmc.translatePath(
"special://database/MyMusic%s.db"
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
"special://database/MyMusic%s.db"
% dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
return dbPath
#################################################################################################
# Utility methods
def getScreensaver():
# Get the current screensaver value
query = {
@ -145,141 +164,10 @@ def setScreensaver(value):
'value': value
}
}
logMsg("EMBY", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1)
result = xbmc.executeJSONRPC(json.dumps(query))
log("Toggling screensaver: %s %s" % (value, result), 1)
def reset():
dialog = xbmcgui.Dialog()
if dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") == 0:
return
# first stop any db sync
window('emby_shouldStop', value="true")
count = 10
while window('emby_dbScan') == "true":
logMsg("EMBY", "Sync is running, will retry: %s..." % count)
count -= 1
if count == 0:
dialog.ok("Warning", "Could not stop the database from running. Try again.")
return
xbmc.sleep(1000)
# Clean up the playlists
deletePlaylists()
# Clean up the video nodes
deleteNodes()
# Wipe the kodi databases
logMsg("EMBY", "Resetting the Kodi video database.", 0)
connection = kodiSQL('video')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
if settings('enableMusic') == "true":
logMsg("EMBY", "Resetting the Kodi music database.")
connection = kodiSQL('music')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
# Wipe the emby database
logMsg("EMBY", "Resetting the Emby database.", 0)
connection = kodiSQL('emby')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
cursor.execute('DROP table IF EXISTS emby')
cursor.execute('DROP table IF EXISTS view')
connection.commit()
cursor.close()
# Offer to wipe cached thumbnails
resp = dialog.yesno("Warning", "Remove all cached artwork?")
if resp:
logMsg("EMBY", "Resetting all cached artwork.", 0)
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
if os.path.supports_unicode_filenames:
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
else:
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
# remove all existing data from texture DB
connection = kodiSQL('texture')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
cursor.execute("DELETE FROM " + tableName)
connection.commit()
cursor.close()
# reset the install run flag
settings('SyncInstallRunDone', value="false")
# Remove emby info
resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
if resp:
# Delete the settings
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
dataPath = "%ssettings.xml" % addondir
xbmcvfs.delete(dataPath)
logMsg("EMBY", "Deleting: settings.xml", 1)
dialog.ok(
heading="Emby for Kodi",
line1="Database reset has completed, Kodi will now restart to apply the changes.")
xbmc.executebuiltin('RestartApp')
def profiling(sortby="cumulative"):
# Will print results to Kodi log
def decorator(func):
def wrapper(*args, **kwargs):
pr = cProfile.Profile()
pr.enable()
result = func(*args, **kwargs)
pr.disable()
s = StringIO.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
logMsg("EMBY Profiling", s.getvalue(), 1)
return result
return wrapper
return decorator
def convertdate(date):
def convertDate(date):
try:
date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
except TypeError:
@ -344,6 +232,139 @@ def indent(elem, level=0):
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def profiling(sortby="cumulative"):
# Will print results to Kodi log
def decorator(func):
def wrapper(*args, **kwargs):
pr = cProfile.Profile()
pr.enable()
result = func(*args, **kwargs)
pr.disable()
s = StringIO.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
log(s.getvalue(), 1)
return result
return wrapper
return decorator
#################################################################################################
# Addon utilities
def reset():
dialog = xbmcgui.Dialog()
if not dialog.yesno(language(29999), language(33074)):
return
# first stop any db sync
window('emby_shouldStop', value="true")
count = 10
while window('emby_dbScan') == "true":
log("Sync is running, will retry: %s..." % count)
count -= 1
if count == 0:
dialog.ok(language(29999), language(33085))
return
xbmc.sleep(1000)
# Clean up the playlists
deletePlaylists()
# Clean up the video nodes
deleteNodes()
# Wipe the kodi databases
log("Resetting the Kodi video database.", 0)
connection = kodiSQL('video')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
if settings('enableMusic') == "true":
log("Resetting the Kodi music database.", 0)
connection = kodiSQL('music')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
# Wipe the emby database
log("Resetting the Emby database.", 0)
connection = kodiSQL('emby')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
cursor.execute('DROP table IF EXISTS emby')
cursor.execute('DROP table IF EXISTS view')
connection.commit()
cursor.close()
# Offer to wipe cached thumbnails
resp = dialog.yesno(language(29999), language(33086))
if resp:
log("Resetting all cached artwork.", 0)
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
if os.path.supports_unicode_filenames:
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
else:
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
# remove all existing data from texture DB
connection = kodiSQL('texture')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
cursor.execute("DELETE FROM " + tableName)
connection.commit()
cursor.close()
# reset the install run flag
settings('SyncInstallRunDone', value="false")
# Remove emby info
resp = dialog.yesno(language(29999), language(33087))
if resp:
# Delete the settings
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
dataPath = "%ssettings.xml" % addondir
xbmcvfs.delete(dataPath)
log("Deleting: settings.xml", 1)
dialog.ok(heading=language(29999), line1=language(33088))
xbmc.executebuiltin('RestartApp')
def sourcesXML():
# To make Master lock compatible
path = xbmc.translatePath("special://profile/").decode('utf-8')
@ -401,7 +422,7 @@ def passwordsXML():
credentials = settings('networkCreds')
if credentials:
# Present user with options
option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"])
option = dialog.select(language(33075), [language(33076), language(33077)])
if option < 0:
# User cancelled dialog
@ -413,17 +434,16 @@ def passwordsXML():
for path in paths:
if path.find('.//from').text == "smb://%s/" % credentials:
paths.remove(path)
logMsg("EMBY", "Successfully removed credentials for: %s"
% credentials, 1)
log("Successfully removed credentials for: %s" % credentials, 1)
etree.ElementTree(root).write(xmlpath)
break
else:
logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
log("Failed to find saved server: %s in passwords.xml" % credentials, 1)
settings('networkCreds', value="")
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
message="%s removed from passwords.xml" % credentials,
heading=language(29999),
message="%s %s" % (language(33078), credentials),
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
@ -431,28 +451,22 @@ def passwordsXML():
elif option == 0:
# User selected to modify
server = dialog.input("Modify the computer name or ip address", credentials)
server = dialog.input(language(33083), credentials)
if not server:
return
else:
# No credentials added
dialog.ok(
heading="Network credentials",
line1= (
"Input the server name or IP address as indicated in your emby library paths. "
'For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC".'))
server = dialog.input("Enter the server name or IP address")
dialog.ok(heading=language(29999), line1=language(33082))
server = dialog.input(language(33084))
if not server:
return
# Network username
user = dialog.input("Enter the network username")
user = dialog.input(language(33079))
if not user:
return
# Network password
password = dialog.input(
heading="Enter the network password",
option=xbmcgui.ALPHANUM_HIDE_INPUT)
password = dialog.input(heading=language(33080), option=xbmcgui.ALPHANUM_HIDE_INPUT)
if not password:
return
@ -473,7 +487,7 @@ def passwordsXML():
# Add credentials
settings('networkCreds', value="%s" % server)
logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1)
log("Added server: %s to passwords.xml" % server, 1)
# Prettify and write to file
try:
indent(root)
@ -481,8 +495,8 @@ def passwordsXML():
etree.ElementTree(root).write(xmlpath)
dialog.notification(
heading="Emby for Kodi",
message="%s added to passwords.xml" % server,
heading=language(29999),
message="%s %s" % (language(33081), server),
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
@ -501,7 +515,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
# Create the playlist directory
if not xbmcvfs.exists(path):
logMsg("EMBY", "Creating directory: %s" % path, 1)
log("Creating directory: %s" % path, 1)
xbmcvfs.mkdirs(path)
# Only add the playlist if it doesn't already exists
@ -509,7 +523,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
if delete:
xbmcvfs.delete(xsppath)
logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1)
log("Successfully removed playlist: %s." % tagname, 1)
return
@ -517,11 +531,11 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
itemtypes = {
'homevideos': "movies"
}
logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1)
log("Writing playlist file to: %s" % xsppath, 1)
try:
f = xbmcvfs.File(xsppath, 'w')
except:
logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1)
log("Failed to create playlist: %s" % xsppath, 1)
return
else:
f.write(
@ -535,7 +549,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
'</smartplaylist>'
% (itemtypes.get(mediatype, mediatype), plname, tagname))
f.close()
logMsg("EMBY", "Successfully added playlist: %s" % tagname)
log("Successfully added playlist: %s" % tagname, 1)
def deletePlaylists():
@ -557,10 +571,10 @@ def deleteNodes():
try:
shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
except:
logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8'))
log("Failed to delete directory: %s" % dir.decode('utf-8'), 0)
for file in files:
if file.decode('utf-8').startswith('emby'):
try:
xbmcvfs.delete("%s%s" % (path, file.decode('utf-8')))
except:
logMsg("EMBY", "Failed to file: %s" % file.decode('utf-8'))
log("Failed to file: %s" % file.decode('utf-8'), 0)

View file

@ -11,6 +11,7 @@ import xbmcvfs
import clientinfo
import utils
from utils import Logging, window, language as lang
#################################################################################################
@ -20,16 +21,14 @@ class VideoNodes(object):
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
clientInfo = clientinfo.ClientInfo()
self.addonName = clientInfo.getAddonName()
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def commonRoot(self, order, label, tagname, roottype=1):
@ -54,8 +53,6 @@ class VideoNodes(object):
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
window = utils.window
if viewtype == "mixed":
dirname = "%s - %s" % (viewid, mediatype)
else:
@ -82,7 +79,7 @@ class VideoNodes(object):
for file in files:
xbmcvfs.delete(nodepath + file)
self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
log("Sucessfully removed videonode: %s." % tagname, 1)
return
# Create index entry
@ -184,7 +181,7 @@ class VideoNodes(object):
# Get label
stringid = nodes[node]
if node != "1":
label = utils.language(stringid)
label = lang(stringid)
if not label:
label = xbmc.getLocalizedString(stringid)
else:
@ -319,8 +316,6 @@ class VideoNodes(object):
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
window = utils.window
tagname = tagname.encode('utf-8')
cleantagname = utils.normalize_nodes(tagname)
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
@ -342,7 +337,7 @@ class VideoNodes(object):
'Favorite tvshows': 30181,
'channels': 30173
}
label = utils.language(labels[tagname])
label = lang(labels[tagname])
embynode = "Emby.nodes.%s" % indexnumber
window('%s.title' % embynode, value=label)
window('%s.path' % embynode, value=windowpath)
@ -369,9 +364,7 @@ class VideoNodes(object):
def clearProperties(self):
window = utils.window
self.logMsg("Clearing nodes properties.", 1)
log("Clearing nodes properties.", 1)
embyprops = window('Emby.nodes.total')
propnames = [

View file

@ -14,10 +14,7 @@ import downloadutils
import librarysync
import playlist
import userclient
import utils
import logging
logging.basicConfig()
from utils import Logging, window, settings, language as lang
#################################################################################################
@ -32,6 +29,9 @@ class WebSocket_Client(threading.Thread):
def __init__(self):
global log
log = Logging(self.__class__.__name__).log
self.__dict__ = self._shared_state
self.monitor = xbmc.Monitor()
@ -43,15 +43,10 @@ class WebSocket_Client(threading.Thread):
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def sendProgressUpdate(self, data):
self.logMsg("sendProgressUpdate", 2)
log("sendProgressUpdate", 2)
try:
messageData = {
@ -60,23 +55,21 @@ class WebSocket_Client(threading.Thread):
}
messageString = json.dumps(messageData)
self.client.send(messageString)
self.logMsg("Message data: %s" % messageString, 2)
log("Message data: %s" % messageString, 2)
except Exception as e:
self.logMsg("Exception: %s" % e, 1)
log("Exception: %s" % e, 1)
def on_message(self, ws, message):
window = utils.window
lang = utils.language
result = json.loads(message)
messageType = result['MessageType']
data = result['Data']
dialog = xbmcgui.Dialog()
if messageType not in ('SessionEnded'):
# Mute certain events
self.logMsg("Message: %s" % message, 1)
log("Message: %s" % message, 1)
if messageType == "Play":
# A remote control play command has been sent from the server.
@ -84,11 +77,10 @@ class WebSocket_Client(threading.Thread):
command = data['PlayCommand']
pl = playlist.Playlist()
dialog = xbmcgui.Dialog()
if command == "PlayNow":
dialog.notification(
heading="Emby for Kodi",
heading=lang(29999),
message="%s %s" % (len(itemIds), lang(33004)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
@ -97,7 +89,7 @@ class WebSocket_Client(threading.Thread):
elif command == "PlayNext":
dialog.notification(
heading="Emby for Kodi",
heading=lang(29999),
message="%s %s" % (len(itemIds), lang(33005)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
@ -126,10 +118,10 @@ class WebSocket_Client(threading.Thread):
seekto = data['SeekPositionTicks']
seektime = seekto / 10000000.0
action(seektime)
self.logMsg("Seek to %s." % seektime, 1)
log("Seek to %s." % seektime, 1)
else:
action()
self.logMsg("Command: %s completed." % command, 1)
log("Command: %s completed." % command, 1)
window('emby_command', value="true")
@ -199,11 +191,11 @@ class WebSocket_Client(threading.Thread):
header = arguments['Header']
text = arguments['Text']
xbmcgui.Dialog().notification(
heading=header,
message=text,
icon="special://home/addons/plugin.video.emby/icon.png",
time=4000)
dialog.notification(
heading=header,
message=text,
icon="special://home/addons/plugin.video.emby/icon.png",
time=4000)
elif command == "SendString":
@ -250,11 +242,11 @@ class WebSocket_Client(threading.Thread):
xbmc.executebuiltin(action)
elif messageType == "ServerRestarting":
if utils.settings('supressRestartMsg') == "true":
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
message=lang(33006),
icon="special://home/addons/plugin.video.emby/icon.png")
if settings('supressRestartMsg') == "true":
dialog.notification(
heading=lang(29999),
message=lang(33006),
icon="special://home/addons/plugin.video.emby/icon.png")
elif messageType == "UserConfigurationUpdated":
# Update user data set in userclient
@ -262,7 +254,7 @@ class WebSocket_Client(threading.Thread):
self.librarySync.refresh_views = True
def on_close(self, ws):
self.logMsg("Closed.", 2)
log("Closed.", 2)
def on_open(self, ws):
self.doUtils.postCapabilities(self.deviceId)
@ -272,11 +264,10 @@ class WebSocket_Client(threading.Thread):
# Server is offline
pass
else:
self.logMsg("Error: %s" % error, 2)
log("Error: %s" % error, 2)
def run(self):
window = utils.window
loglevel = int(window('emby_logLevel'))
# websocket.enableTrace(True)
@ -290,7 +281,7 @@ class WebSocket_Client(threading.Thread):
server = server.replace('http', "ws")
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId)
self.logMsg("websocket url: %s" % websocket_url, 1)
log("websocket url: %s" % websocket_url, 1)
self.client = websocket.WebSocketApp(websocket_url,
on_message=self.on_message,
@ -298,7 +289,7 @@ class WebSocket_Client(threading.Thread):
on_close=self.on_close)
self.client.on_open = self.on_open
self.logMsg("----===## Starting WebSocketClient ##===----", 0)
log("----===## Starting WebSocketClient ##===----", 0)
while not self.monitor.abortRequested():
@ -310,10 +301,10 @@ class WebSocket_Client(threading.Thread):
# Abort was requested, exit
break
self.logMsg("##===---- WebSocketClient Stopped ----===##", 0)
log("##===---- WebSocketClient Stopped ----===##", 0)
def stopClient(self):
self.stopWebsocket = True
self.client.close()
self.logMsg("Stopping thread.", 1)
log("Stopping thread.", 1)

View file

@ -33,7 +33,7 @@
<setting id="imageCacheLimit" type="enum" label="30513" values="Disabled|5|10|15|20|25" default="0" visible="eq(-1,true)" subsetting="true" />
<setting id="syncEmptyShows" type="bool" label="30508" default="false" />
<setting id="dbSyncScreensaver" label="30536" type="bool" default="false" />
<setting id="useDirectPaths" type="enum" label="30511" values="Addon(Default)|Native(Direct paths)" default="0" />
<setting id="useDirectPaths" type="enum" label="30511" lvalues="33036|33037" default="0" />
<setting id="enableMusic" type="bool" label="30509" default="true" />
<setting id="streamMusic" type="bool" label="30510" default="false" visible="eq(-1,true)" subsetting="true" />
<setting type="lsep" label="30523" />

View file

@ -16,9 +16,9 @@ import xbmcvfs
#################################################################################################
_addon = xbmcaddon.Addon(id='plugin.video.emby')
addon_path = _addon.getAddonInfo('path').decode('utf-8')
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource)
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
_base_resource = xbmc.translatePath(os.path.join(_addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(_base_resource)
#################################################################################################
@ -28,9 +28,9 @@ import initialsetup
import kodimonitor
import librarysync
import player
import utils
import videonodes
import websocket_client as wsc
from utils import Logging, window, settings, language as lang
#################################################################################################
@ -49,8 +49,8 @@ class Service():
def __init__(self):
log = self.logMsg
window = utils.window
global log
log = Logging(self.__class__.__name__).log
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
@ -58,15 +58,14 @@ class Service():
self.monitor = xbmc.Monitor()
window('emby_logLevel', value=str(logLevel))
window('emby_kodiProfile', value=xbmc.translatePath("special://profile"))
window('emby_pluginpath', value=utils.settings('useDirectPaths'))
window('emby_kodiProfile', value=xbmc.translatePath('special://profile'))
# Initial logging
log("======== START %s ========" % self.addonName, 0)
log("Platform: %s" % (self.clientInfo.getPlatform()), 0)
log("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0)
log("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0)
log("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0)
log("Using plugin paths: %s" % (settings('useDirectPaths') == "0"), 0)
log("Log Level: %s" % logLevel, 0)
# Reset window props for profile switch
@ -86,22 +85,13 @@ class Service():
# Set the minimum database version
window('emby_minDBVersion', value="1.1.63")
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def ServiceEntryPoint(self):
log = self.logMsg
window = utils.window
lang = utils.language
# Important: Threads depending on abortRequest will not trigger
# if profile switch happens more than once.
monitor = self.monitor
kodiProfile = xbmc.translatePath("special://profile")
kodiProfile = xbmc.translatePath('special://profile')
# Server auto-detect
initialsetup.InitialSetup().setup()
@ -119,7 +109,7 @@ class Service():
if window('emby_kodiProfile') != kodiProfile:
# Profile change happened, terminate this thread and others
log("Kodi profile was: %s and changed to: %s. Terminating old Emby thread."
% (kodiProfile, utils.window('emby_kodiProfile')), 1)
% (kodiProfile, window('emby_kodiProfile')), 1)
break
@ -167,7 +157,7 @@ class Service():
else:
# Start up events
self.warn_auth = True
if utils.settings('connectMsg') == "true" and self.welcome_msg:
if settings('connectMsg') == "true" and self.welcome_msg:
# Reset authentication warnings
self.welcome_msg = False
# Get additional users
@ -177,7 +167,7 @@ class Service():
else:
add = ""
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
heading=lang(29999),
message=("%s %s%s!"
% (lang(33000), user.currUser.decode('utf-8'),
add.decode('utf-8'))),
@ -252,7 +242,7 @@ class Service():
break
# Alert the user that server is online.
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
heading=lang(29999),
message=lang(33003),
icon="special://home/addons/plugin.video.emby/icon.png",
time=2000,
@ -291,7 +281,7 @@ class Service():
log("======== STOP %s ========" % self.addonName, 0)
# Delay option
delay = int(utils.settings('startupDelay'))
delay = int(settings('startupDelay'))
xbmc.log("Delaying emby startup by: %s sec..." % delay)
if delay and xbmc.Monitor().waitForAbort(delay):